LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   How are programs able to handle events, given the procedural nature of most languages? (https://www.linuxquestions.org/questions/programming-9/how-are-programs-able-to-handle-events-given-the-procedural-nature-of-most-languages-4175637183/)

hazel 08-26-2018 12:40 PM

How are programs able to handle events, given the procedural nature of most languages?
 
Lots of programs can handle unpredictable events. You can write a handler for a Unix signal or for an X-event like a mouse click. There will be a marshalling function of some kind that lets you link your handler to the event in question. But how does this actually work, given that the cpu processes code sequentially? Where in this sequence of small steps does the handler lurk, ready to jump in when required?

dugan 08-26-2018 01:13 PM

They (AFAIK all) have the following architecture:

https://en.wikipedia.org/wiki/Event_loop

jlinkels 08-26-2018 01:23 PM

More to the basics, when a program is loaded it can set addresses of certain handlers. Then the OS is told that at a certain event a certain function must be executed by telling the OS the handler address. Once this event occurs, the program is literally interrupted, the complete programs state is preserved, the handler executed and once finished the program state is restored and the program continues execution where it left off.

In GUI programs often an event loop is running, which receives events from the OS and dispatches those events to the application code. Often, the running program is not interrupted but simple handles the next event when it is ready. That is event-driven programming.

jlinkels

astrogeek 08-26-2018 01:42 PM

An important part of the process, and what you seem to be asking about is what are called "interrupts". The normal sequential execution of code is interrupted by an "interrput" signal.

There are two basic kinds of interrupt, hardware and software. Hardware interrupts are generated by hardware events, such as a key press, a network interface receiving data or a disk drive I/O operation completing, for example. Software interrupts can be generated by any running process, and also by certain conditions internal to the CPU.

Here is a simplistic description of how it works.

When the kernel receives an interrupt it pushes its current state, what it is doing now, onto the stack and jumps to whatever handler has been assigned for the particular interrupt. This is usually a device driver for hardware interrupts. Some interrupts will block others until they are complete, others may themselves be interrupted as any other code.

Different interrupt sources have different priorities, the kernel handles them in priority order so that things which need immediate attention receive it, typically changes in hardware state.

When the interrupt handler code returns, the kernel pulls its previous state from the stack and resumes what it was doing.

If your program was waiting for some change of state, i.e. an event, it may provide an interrupt handler itself, or it may detect a change in system state and respond to the event it was waiting for.

hazel 08-26-2018 01:53 PM

So the sequence is basically:

1) Request to be notified of events and go to sleep
2) Wake up if an event is reported
3) Test the reported event to find out what kind of event it is.
4) Run the appropriate handler for that kind of event.
5) When handler returns, go back to 1).

The signals I'm most familiar with in programming are gtk signals and their underlying gdk events and they certainly follow that loop model. You set up your graphical interface, link your handlers to these signals or events, and call gtk_main(). After that the only code that can be processed is handler code. Any code beyond the gtk_main() call is inaccessible until some handler calls gtk_main_quit() to break the loop.

But Unix signals don't seem to work like that. They affect programs that aren't looping or waiting. That Wikipedia article tries to explain it but it's beyond my level. And what about signals that come from other programs via something like dbus? A running program can't simply sit and wait for those.

Oops! We have a race condition!
@jlinkels: your mechanism would explain how Unix signals are handled. It isn't the program that runs the handler, it's the kernel.
@astrogeek: I think hardware interrupts are a bit different.

astrogeek 08-26-2018 02:03 PM

Quote:

Originally Posted by hazel (Post 5896252)
But Unix signals don't seem to work like that. They affect programs that aren't looping or waiting. That Wikipedia article tries to explain it but it's beyond my level. And what about signals that come from other programs via something like dbus? A running program can't simply sit and wait for those.

The whole idea of an interrupt in this context is that they are asynchronous, the system or program does not have to sit and wait - they can go on about their business! That "business" may be a simple event loop, where it is waiting for a keypress, for example. But it may be be anything - the program is not constrained to wait.

ntubski 08-26-2018 08:36 PM

Quote:

Originally Posted by hazel (Post 5896252)
And what about signals that come from other programs via something like dbus? A running program can't simply sit and wait for those.

D-Bus messages come over a socket, the receiving program does have to explicitly check for them.

Quote:

It isn't the program that runs the handler, it's the kernel.
The kernel arranges for the program to start running the handler (that is, the handler code itself is run in userspace).

pan64 08-27-2018 01:33 AM

I think what you are talking about in post #5 is more or less callback function, not an interrupt or signal.

In X you register callback functions if you want to handle events. X itself catches the interrupt or signal an look for the registered callback functions and invoke them.
But also there are message buses where anyone can send a message and anyone can read (if interested...). This is another approach again.

mina86 08-27-2018 06:57 AM

Quote:

Originally Posted by hazel (Post 5896252)
@astrogeek: I think hardware interrupts are a bit different.

Hardware interrupts and Unix signals are pretty much the same idea: you set up handlers for interrupts/signals then go about your business until that business is interrupted when interrupt/signal arrives. In the former it’s the kernel setting up pointers in an interrupt table and hardware jumping to that code on an interrupt. In the latter it’s user space setting up signal handlers and kernel calling the handler functions when signal arrives to the process. In both cases, the handlers might do relatively little and rather signal to the ‘main’ code that an interrupt/signal needs to be processed.

Another way to handle signals is via signalfd.

But there are many ways of how asynchronous events can be handled interrupts and signals being one. Event loops being another. Multithreading being yet another.

hazel 08-27-2018 07:40 AM

Quote:

Originally Posted by mina86 (Post 5896495)
But there are many ways of how asynchronous events can be handled interrupts and signals being one. Event loops being another. Multithreading being yet another.

Yes, it's obvious that having a parallel thread to wait for events and handle them would be a complete solution. It would leave the main instruction thread able to concentrate on the job in hand.

So let me see if I can understand this. There isn't a single way to handle events, there are several different ways. A parallel event-monitoring thread is one possibility. Graphical programs can just wait in a loop until a user-triggered event takes place, run a handler and loop again. These programs often don't have anything else to do but wait for user instructions. Or the kernel can stop a program and restart it at a handler entry point, which is how Unix inter-process signals work. I think I'll mark this one as solved.

mina86 08-29-2018 03:36 AM

Quote:

Originally Posted by hazel (Post 5896510)
A parallel event-monitoring thread is one possibility.

This is essentially how futures work. And depending on type of events, you might have one or multiple threads waiting for them. And this does not preclude an event-loop since such events may simply wake up the main thread which will do the actual work.

Quote:

Originally Posted by hazel (Post 5896510)
Graphical programs can just wait in a loop until a user-triggered event takes place, run a handler and loop again.

Note that whether program is graphical or not isn’t really relevant. A text-based IRC/chat client may use a single-threaded event loop and even handle signals synchronously in that loop. Also remember that not all events are user-triggered.

hazel 08-29-2018 05:33 AM

Quote:

Originally Posted by mina86 (Post 5897384)
This is essentially how futures work.

What are futures?
Quote:

Note that whether program is graphical or not isn’t really relevant. A text-based IRC/chat client may use a single-threaded event loop and even handle signals synchronously in that loop.
Good point! After all, the shell works in a very similar way: waits for a command, finds an executable with the same name, runs it, and goes back to waiting.

I remember once seeing some code in which a signal handler changed a global variable and the main loop just checked that variable synchronously each time it ran. The trouble is that if the loop is waiting anyway (say for user input) it might not get to checking for signals for quite some time.

jlinkels 08-29-2018 07:25 AM

It would take a few pages (about the size of a book) to explain the current state of interrupts and event handling. The mechanisms behind have evolved for about 50 years. Although there was a restart at zero when DOS was introduced.

No, event driven programming and GUI can be seen separately, although there is a reason that event driven programming became almost a requirement for GUI programming.

First, in a classic system where you only have a single foreground process on a real terminal (a 70-ies Unix system so to say) the keyboard input hardware would generate an interrupt at every keypress. The OS would be interrupted on whatever it was doing, to handle the keyboard input. This was done by the keyboard interrupt handler, which translated the keyboard scan code to an ASCII code, and put it in a buffer somewhere, ready fo the foreground process to read.

However, some keys were (are) special, like CTRL-C. This input would be handed directly to the OS, which signalled the OS that the running foreground process had to be sent a software interrupt: SIGHUP. So this is part of your question how a running process gets interrupted.

All other keycodes would be in the keyboard buffer, which was polled by the shell. Once a predefined character was read from the buffer (usually 0x0D, or return) the shell understood the command was complete and executed the command. It can be somewhat regarded as event driven. The shell would read events (key presses) from the buffer and act accordingly.

In these situations there was just one foreground process, and it was easy to see where all keypresses had to go.

However, when GUI programming became current, a program would consist of many graphical elements, like a window canvas, menu bar, buttons, and whatnot. When then a key was pressed, the program had to decide where each keypress had to be handled. It was not so clear anymore. So whenever an event (keypress or mouse event) is received, it is sent through a chain of graphical objects, where usually the objects themselves knows whether to handle the event, or send it to the next object in the chain.

For example, while I am typing this, the keypresses are sent to the active window by the X driver. The active window, Firefox, sends the keypresses into its chain of objects. Eventually the editor object knows it should handle the keypresses and does so, ending the journey of the event. Before that, the event was ignored bu all other objects, tabs, menu bars, links and buttons because they knew by their state they should not handle the event.

And yes, events remain in the queue when the application is doing other things. If for example you open a Word document, OOWriter is busy opening the document and while it is doing so nothing happens when you click on File on the menu bar. Once the document is loaded, you suddenly see the File menu drop down.

jlinkels

scasey 08-29-2018 08:23 AM

Helen,
Thank you for starting a very interesting thread!

mina86 08-29-2018 01:17 PM

Quote:

Originally Posted by hazel (Post 5897421)
What are futures?

https://en.wikipedia.org/wiki/Futures_and_promises

Quote:

Originally Posted by hazel (Post 5897421)
I remember once seeing some code in which a signal handler changed a global variable and the main loop just checked that variable synchronously each time it ran. The trouble is that if the loop is waiting anyway (say for user input) it might not get to checking for signals for quite some time.

This can be solved by using non-blocking I/O, blocking all signals during normal code execution and using pselect to unblocked signals and wait for all file descriptors (like the code I’ve linked to). This way, waiting for signals and user input is done in at the same time. signalfd(2)+select(2) may be even simpler solution.


All times are GMT -5. The time now is 04:32 AM.