3/1/2011

Overview

Real-time Systems D0003E • Posix signals Lecture 13: • Posix timers More on interprocess communication • timers in cygwin (lab environment) (Burns & Wellings ch. 8, 11 & 10.6) • loop pattern • semaphores

POSIX signals Generation of signals

A can be sent to a process by executing A group of threads executing in a protected virtual address space forms the POSIX notion of a process kill(pid, sig); where pid is process number (0 means self) A process can thus be though of as a virtual computer The POSIX signal concept is analogous to a virtual ... or by interrupt to such a virtual computer dividing by zero (equals kill(0, SIGFPE); ) Signals predate threads and shared memory addressing outside your address space ( SIGSEGV ) communication in , although they are now etc... standardized by POSIX This actually mimics the interrupt sources on most The default handling of most signals is to kill the real architectures: receiving process, but userdefined handler can be •external hardware signals installed to override this behavior •internal hardware misuse

Some predefined signals Handling signals

•SIGINT "interrupt" (ControlC) To install a handler for signal X: #include •SIGILL illegal instruction (internal) •SIGKILL kill process (can't be ignored) void handler(int signo) { /* Your signal handling code here */ •SIGALRM timer alarm } •SIGCHLD child process terminated •SIGTSTP terminal stop (ControlZ) struct sigaction act; act.sa_handler = handler; // your handler function •SIGUSR1 .. SIGUSR2 act.sa_flags = 0; // extras, normally 0 • user defined sigemptyset( &act.sa_mask ); // signals to block while running handler •SIGRTMIN .. SIGRTMAX sigaction ( X, &act, NULL ); • user defined (with extra data payload)

1 3/1/2011

Masking a signal Synchronizing with signals

To wait for the handling of a signal: To temporarily mask (disable) signal handling: sigset_t ignore; // signals to ignore sigset_t sigs; sigemptyset(&sigs); sigemptyset( &ignore ); sigaddset( &sigs, SIGINT ); sigaddset( &ignore, SIGINT ); sigprocmask ( SIG_BLOCK, &sigs, NULL ); sigsuspend ( &ignore ); // return after handler has run once To unmask (enables handling of pending signals): sigprocmask ( SIG_UNBLOCK, &sigs, NULL ); Note: sigsuspend stops execution until a signal not mentioned in the argument has arrived and the [C.f. the macros that enable or disable interrupts for corresponding handler has run, just as if it were various devices on the AVR] implemented as a busywait loop

Alternatively POSIX timers

To wait for a signal that doesn't have a handler: OS abstraction built on top of the system ticks sigset_t accept; signalled by the clock hardware int received; sigemptyset( &accept ); To create a new timer abstraction: sigaddset( &accept, SIGALRM ); #include sigaddset( &accept, ... ); timer_t tmr; How alarms should be delivered sigprocmask( SIG_BLOCK, &accept, NULL ); struct sigevent ev; — doesn’t have to be SIGALRM sigwait ( &accept, &received ); SIGEV_SIGNAL( &ev, signo ); timer_create ( CLOCK_REALTIME, &ev, &tmr ); That is, sigwait stops execution until one of the signals in These commands amount to "buying" a new alarm its first argument has arrived. Note that these signals clock, aligning it with real time, and configuring it must be blocked when sigwait is called (so that a handler with a certain alarm signal won’t interfere)

Setting the alarm time More timer settings

A timer is armed by supplying a single alarm time and To set a timer to ring just once, set the it_interval to 0 an optional repetition period spec.it_value.tv_sec = 1; The alarm time can be relative (to the time of the spec.it_value.tv_nsec = 0; call), or absolute spec.it_interval.tv_sec = 0; To set a timer to ring in one second, and thereafter spec.it_interval.tv_nsec = 0; every 50 milliseconds, write timer_settime ( tmr, 0, &spec, NULL ); struct itimerspec spec; To temporarily disable a timer, set the it_value to 0 spec.it_value.tv_sec = 1; spec.it_value.tv_sec = 0; spec.it_value.tv_nsec = 0; spec.it_value.tv_nsec = 0; spec.it_interval.tv_sec = 0; spec.it_interval.tv_sec = 0; spec.it_interval.tv_nsec = 50000000; spec.it_interval.tv_nsec = 50000000; timer_settime ( tmr, 0, &spec, NULL ); timer_settime ( tmr, 0, &spec, NULL );

2 3/1/2011

A cyclic POSIX C.f. the periodic reactive object

void *proc (void *arg) { int m( Class *self, int arg ) { timer_t tmr; // do periodic action sigset_t accept; AFTER( MILLISEC(500), self, m, exp ); struct sigevent ev; } int sig_received; struct itimerspec spec = {{0,50000000}, {0,50000000}}; SIGEV_SIGNAL( &ev, SIGALRM ); Apart from the obvious differences in size, there’s another, timer_create ( CLOCK_REALTIME, &ev, &tmr ); more fundamental, difference in structure as well: timer_settime ( tmr, 0, &spec, NULL ); N.B.: signals are global within a sigemptyset( &accept ); Reactive POSIX process, and not tied to a etc. sigaddset( &accept, SIGALRM ); object: m m m specific thread. This means that sigprocmask ( SIG_BLOCK, &accept, NULL ); for this example to work, no while (1) { other thread should use this sigwait ( &sigset, &sig_received ); POSIX etc. signal (SIGALRM) for any other // do... sigwait // do... sigwait // do... sigwait // do periodic action purpose thread: } }

Cygwin timers A cyclic POSIX thread (2)

void *fun( void *arg ) { sigset_t accept; As it happens, Cygwin – the PC side lab programming Like an itimerspec, but units are struct itimerval x; environment – doesn’t support timer_create() and seconds and micro seconds x.it_value.tv_sec = 0; timer_settime() (so much for POSIX compatibility!) x.it_value.tv_usec = 1000; Instead, a predefined interval timer for each process x.it_interval.tv_sec = 0; is offered, in the older style of BSD Unix x.it_interval.tv_usec = 1000; setitimer ( ITIMER_REAL, &x, NULL); This timer always generates the signal SIGALRM , Older name for sigemptyset( &accept ); CLOCK_REALTIME and is configured via the system call setitimer() sigaddset( &accept, SIGALRM ); [ Actually, one such interval timer can be configured for each sigprocmask ( SIG_BLOCK, &accept, NULL ); thread, but since they all use SIGALRM for output, using multiple while (1) { interval timers in a process is a little tricky and not recommended int sig_received; ] sigwait ( &accept, &sig_received ); // do periodic action } }

Handling multiple events The event-loop pattern

int m( Class *self, int arg ) { int m2( Class *self, int x ) { A standard pattern for encoding reactions in terms of a // do periodic action // update self continuous (POSIX) thread: AFTER( MILLISEC(500), self, m, exp ); } } void *fun( void *arg ) { Indefinite length INITIALIZE; while (1) { Reactive etc. PARK; PAR m m m2 m INITIALIZE REACT object: REACT; K } }

POSIX etc. thread: // do... sigwait // do... sigwait ? // do... sigwait The “parking” operation is at the core of this pattern – it causes the thread to stop execution indefinitely

How do we tell a POSIX thread to wait for a timer signal Implicit sidecondition: neither INITIALIZE nor REACT and be prepared to update its state at the same time? must call any parking operation

3 3/1/2011

Parking operations (def) The multi-event-loop pattern

Ideally we would like a parking operation that waits for In principle : any operation implemented by busywaiting exactly those events a thread is interested in: on an external state change void *fun( void *arg ) { In practice : an operation provided by the underlying INITIALIZE; while (1) { PAR INITIALIZE REACT0 operating system that behaves as if it were implemented x = PARK; K by busywaiting on an external state change switch (x) { case 0: REACT0; break; REACT1 Core questions regarding a parking op: what events does case 1: REACT1; break; it wait for? Just one? Many? Is it extensible? ...... case n: REACTn; break; REACTn Example: sigwait waits for a defineable set of POSIX }

signals (but not for keyboard entry, for example) to analogous thread A object! reactive a } } Example: getchar waits for data from one input source only (the keyboard), but not for any signals Unfortunately, a truly generic parking op doesn’t exist...

POSIX () Semaphores

A way to wait for input on multiple files (keyboard, pipes, network sockets, etc) simultaneously Original process synchronization device (due to Dijkstra 1965) Returns info on which file descriptor data arrived first Supports two operations: wait and signal (signal is called Also supports an optional timeout value, equivalent to a post in POSIX) multiple wait that includes a delay wait: if value >0: decrease; if =0: block! Syntax: Sets of files that signal: increase value have pending The general semaphore is counting ; i.e., it remembers can be read can be written exceptions number of bits in sets the number of signal calls, and allows the same number of wait calls to succeed without stopping int select( int nfds, fd_set *r, fd_set *w, fd_set *e, A counting semaphore must be initialized with its struct timeval *t ) optional timeout starting value (the number of initially allowed wait calls) number of ready descriptors

Note: select still doesn’t wait for signals or internal events

Semaphore vs. mutex A semaphore Bounded Buffer #include sem_t mut; sem_t space, items; int head = 0, tail = 0; T buf[SIZE]; ... Notice the A mutex is a semaphore that is sem_init ( &mut, 0, 1 ); lock/unlock • sem_init ( &items, 0, 0 ); pattern binary (only distinguishes between the values 0 and 1) sem_init ( &space, 0, SIZE ); • automatically initialized to 1 • restricted so that signal/post can only be called by the And the void put(T item) { “parking” pattern void get(T *item) { thread that has successfully called wait sem_wait ( &space ); sem_wait ( &items ); sem_wait ( &mut ); sem_wait ( &mut ); Mutexes use the name lock for wait , and unlock for signal buf[head] = item; *item = buf[tail]; head = (head + 1) % SIZE; tail = (tail + 1) % SIZE; sem_post ( &mut ); sem_post ( &mut ); sem_post ( &items ); sem_post ( &space ); } }

4 3/1/2011

Semaphores for parking Generic parking

General pattern: In general, a generic parking mechanism cannot be • One thread produces tokens by means of the signal implemented under POSIX call Instead, the established coding style is to use separate • Another thread consumes tokens by means of the threads for each kind of event , and rely on shared memory wait call, and parks if none are available and semaphores to synchronize these threads in turn Some operating systems like QNX offer a message • The initial number of tokens can be any number ≥ 0 mechanism as an alternative, but even here the parking This parking pattern is often referred to as operation is not fully extensible to handle signals, etc conditional synchronization Reactive objects as we know them can therefore not be Notice: the producer and the consumer must agree directly supported in POSIX on what the tokens mean – i.e., number of items in a Neither can our direct interrupt handling, and the notion buffer, the fact that a variable is not empty, the of baselines and deadlines... fact that one of two variables is not empty, etc... But of course: POSIX programs are much more portable!

5