Workshop o mikrokontrolérech na SKSP 2024.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
4.7 KiB

3 months ago
Mainloop
========
Not every program is strictly sequential. Sometimes, an event-driven
model is much easier to grasp. A fine example of such a program could
be a railway server. It has a separate connection to each station
and also to each train, so that it knows where each of them is (and
that neither a train nor a station have got missing). So it has to wait
for events coming from these connections and handle them appropriately.
It also processes other events that it has itself generated -- for
example various timers telling that a train is scheduled to depart
from some station.
The mainloop module takes care of the low-level part of event-driven
programs: it provides an event loop (often called a main loop), which
watches for events requested by the rest of the program and calls
a supplied callback when an event happens.
More precisely, for each type of an event (file descriptor activity,
timer etc.), there is a +handler structure+, which contains the description
of the event (e.g., the time where the timer should fire), a pointer to
a +handler function+ (the event callback) and data for use by the handler
function. The handler is then registered with the main loop.
- <<simple,Simple use>>
- <<contexts,Using multiple contexts>>
- <<threads,Forking and threading>>
- <<basic,Basic operations>>
- <<time,Time and timers>>
- <<hooks,Loop hooks>>
- <<file,Activity on file descriptors>>
- <<blockio,Asynchronous block I/O>>
- <<recordio,Asynchronous record I/O>>
- <<process,Child processes>>
- <<signal,Synchronous delivery of signals>>
[[contexts]]
Simple use
----------
Simple programs usually employ the main loop in a straightforward way:
- Call @main_init() to initialize the main loop machinery.
- Add an initial set of event handers (@file_add(), @timer_add(), etc.).
- Enter the event loop by calling @main_loop(). This function runs for
the rest of the lifetime of the program. It watches for events and
handles them by calling the appropriate handler functions. These functions
can of course add new events or modify/delete the existing ones.
- When the program decides it wants to stop, it calls @main_shut_down(),
or alternatively it returns <<enum_main_hook_return,`HOOK_SHUTDOWN`>> from some hook functions.
Soon after that, @main_loop() returns.
- Remove all event hooks and call @main_cleanup().
The event structures (like <<struct_main_file,`struct main_file`>>) are
always allocated by the user, but please touch only the fields marked
in this documentation with `[*]`. The other fields are used internally;
you should initialize them to zeroes before adding the event and avoid
accessing them afterwards.
[[contexts]]
Using multiple contexts
-----------------------
In a more complex program, it can be useful to keep several sets of events
and run a separate instance of the event loop for each such set. A typical
example would be a multi-threaded program or a function which needs to
communicate with a network server locally, ignoring all other events
before the operation is finished.
For such cases, you can create multiple instances of <<struct_main_context,`struct main_context`>>
by calling @main_new(). Each thread then keeps its own current context,
which can be changed by @main_switch_context(). All mainloop functions
then either take an explicit pointer to a context or (more typically)
they operate on the current context. When you no longer need the context, you
can delete it by @main_delete().
It is even possible to use nested main loops: in a hook called by the
top-level instance of @main_loop(), you can switch to a different context,
call @main_loop() recursively and when you are done, switch back and return
to the top-level loop.
*CAVEAT:* In the present implementation, only a single context per process
can handle process exit events. If you use @process_add() in multiple contexts,
it can happen that the current context catches the `SIGCHLD` signal and obtains
information about a child process associated with another context, which it does
not know how to handle. If you ever need this, please let us know.
[[threads]]
Forking and threading
---------------------
Using the event loop in a multi-threaded or multi-process program is possible,
but it should be done very carefully.
Multiple threads can use the main loop, but each of them must use a separate
context (or contexts).
When you fork() a child process, either the parent or the child must give up
use of each main loop context. The @main_teardown() and @main_destroy() functions
can be useful for that. (The reason is that some parts of the main loop context,
like file descriptors used internally, become shared between the processes, so
the processes could influence each other in crazy ways. You do not want to hunt
for such bugs.)
!!ucw/mainloop.h