This article explores the essence of event handlers as callback functions, relinquishing control to event initiators. Events are obtained through either polling or operating system-assisted methods like blocking calls. The operating system serves as an event callback system, handling interrupts, and integrating callbacks into event loops.
Event handlers are fundamentally callback functions, wherein the calling privilege is relinquished to the event initiator, and the program responsible for the event is automatically invoked when the event occurs. Regarding the source of events, it depends on the perspective. From a process standpoint, a small portion of events is indeed obtained through polling, while the majority is processed through a message loop structure. A message loop typically monopolizes a thread and can be expressed in pseudocode as follows:
This represents a loop that retrieves an event and processes it. Handling events usually involves calling event handler functions. So, how are events obtained? Aside from polling, it is often accomplished with the assistance of the operating system. The operating system provides certain blocking calls, such as
epoll, and Windows'
WaitForMultipleObjects, etc. These blocking calls generally handle multiple signal sources, returning when any of them undergoes a change, allowing the simultaneous tracking of various inputs.
In addition to event loops, another design involves the operating system directly triggering specific callback functions. Typical examples include Linux's
SendMessageWithCallback, etc. Such callbacks typically interrupt the ongoing system call immediately. This integration of callbacks with event loops involves setting flags during the callback process, checking these flags when the blocking concludes, thereby seamlessly incorporating these callbacks into the usual event loop.
Now, from the perspective of the operating system, the kernel can arbitrarily schedule user processes. When a user process makes a system call, transitioning to kernel mode, the kernel suspends the current process. Upon the occurrence of the corresponding event, the kernel then awakens the respective user process, allowing it to resume execution. Thus, the kernel essentially functions as an event callback system.
Delving deeper, where do the events in the kernel come from? A crucial mechanism is known as interrupts, which, in simple terms, are hardware-implemented signal handlers. CPUs typically execute instructions sequentially, with the kernel sequentially executing user programs. In instances where all user processes are suspended, the kernel executes an idle process, engaging in a loop executing low-power instructions. A notable exception is interrupts. CPUs possess interrupt pins, and a change in voltage on these pins immediately initiates the interrupt process. The hardware structure within the CPU promptly enters kernel mode, reads the interrupt vector table, and then jumps to the corresponding code based on the address in the table for execution.
Interrupt handling procedures usually begin by saving the context, querying information from hardware sharing the interrupt, saving it to specific data structures, resetting the interrupt, and then promptly returning to kernel mode. The kernel subsequently queries interrupt information and executes specialized handling procedures. These procedures ultimately restore the execution of user-space programs, and the cycle repeats.
In essence, this is a counterpart to signal handlers and event loops in user space. Apart from hardware interrupts, there are interrupts triggered internally by the CPU. A crucial example is the timer interrupt, and the handling process is identical. Some hardware employs timer interrupts for polling, such as USB. In summary, hardware notifies the kernel through interrupts, the kernel informs user processes (threads) through blocking calls and event handlers, and the user framework manages notifications, triggering event handlers.