Message passing is a mechanism that can send a message from one process to another. The advantage of message passing is that it can be used between processes on a single system as well as between processes on multiple systems connected by a network without having to change the interface between the processes and message passing.
Message passing is synchronous when the procedure that sends a message can not return until the message is received. Message passing is asynchronous when the procedure that sends a message can return before the message is received.
The procedures that send or receive a message are blocking when they can wait before returning, and non blocking when they do not wait before returning. When a non blocking procedure needs to wait, it can replace blocking by polling or callbacks.
Message passing can use symmetrical, asymmetrical and indirect addressing. The symmetrical addressing requires both the sender and the receiver to specify the address of the other party. The asymmetrical addressing requires the sender to specify the address of the receiver. The indirect addressing requires both the sender and the receiver to specify an address of the same message queue.
The message sent from the sender to the receiver can be anything from a single integer number through an unformatted stream of bytes to a formatted structure of records.
Signals are messages that can be delivered to processes or threads. A signal is identified by a number, with numbers from 1 to 31 allocated to standard signals with predefined meaning and numbers from SIGRTMIN to SIGRTMAX allocated to real time signals.
Figure 2.14. Standard Signals
Name | Number | Meaning |
---|---|---|
SIGHUP | 1 | Controlling terminal closed |
SIGINT | 2 | Request for interrupt sent from keyboard |
SIGQUIT | 3 | Request for quit sent from keyboard |
SIGILL | 4 | Illegal instruction |
SIGTRAP | 5 | Breakpoint instruction |
SIGABRT | 6 | Request for abort |
SIGBUS | 7 | Illegal bus cycle |
SIGFPE | 8 | Floating point exception |
SIGKILL | 9 | Request for kill |
SIGUSR1 | 10 | User defined signal 1 |
SIGSEGV | 11 | Illegal memory access |
SIGUSR2 | 12 | User defined signal 2 |
SIGPIPE | 13 | Broken pipe |
SIGALRM | 14 | Timer alarm |
SIGTERM | 15 | Request for termination |
SIGTERM | 16 | Illegal stack access |
SIGCHLD | 17 | Child process status changed |
SIGCONT | 18 | Request to continue when stopped |
SIGSTOP | 19 | Request to stop |
SIGTSTP | 20 | Request for stop sent from keyboard |
SIGTTIN | 21 | Input from terminal when on background |
SIGTTOU | 22 | Output to terminal when on background |
Signals are processed by signal handlers. A signal handler is a procedure that is called by the operating system when a signal occurs. Default handlers are provided by the operating system. New handlers can be registered for some signals.
Figure 2.15. Signal Handler Registration System Call
typedef void (*sighandler_t) (int); sighandler_t signal (int signum, sighandler_t handler);
SIG_DFL - use default signal handler
SIG_IGN - ignore the signal
struct sigaction { void (*sa_handler) (int); void (*sa_sigaction) (int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; } struct siginfo_t { int si_signo; // Signal number int si_errno; // Value of errno int si_code; // Additional signal code pid_t si_pid; // Sending process PID uid_t si_uid; // Sending process UID int si_status; // Exit value clock_t si_utime; // User time consumed clock_t si_stime; // System time consumed sigval_t si_value; // Signal value int si_int; // Integer value sent with signal void * si_ptr; // Pointer value sent with signal void * si_addr; // Associated memory address int si_fd; // Associated file descriptor } int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
sa_handler - signal handler with limited arguments
sa_sigaction - signal handler with complete arguments
sa_mask - what other signals to mask while in signal handler
SA_RESETHAND - restore default signal handler after one signal
SA_NODEFER - allow recursive invocation of this signal handler
SA_ONSTACK - use alternate stack for this signal handler
Due to the ability of signals to interrupt processes at arbitrary times, the actions that can be taken inside a signal handler are severely limited. Access to shared variables and system calls are not safe in general. This can be somewhat alleviated by masking signals.
Figure 2.16. Signal Masking System Call
int sigprocmask (int how, const sigset_t *set, sigset_t *oset); int pthread_sigmask (int how, const sigset_t *set, sigset_t *oset);
SIG_BLOCK - add blocking to signals that are not yet blocked
SIG_UNBLOCK - remove blocking from signals that are blocked
SIG_SETMASK - replace existing mask
Signals are usually reliable, even though unreliable signals did exist. Signals are delivered asynchronously, usually on return from system call. Multiple instances of some signals may be queued.
Figure 2.17. Signal Send System Call
int kill (pid_t pid, int sig); int pthread_kill (pthread_t thread, int sig); union sigval { int sival_int; void *sival_ptr; } int sigqueue (pid_t pid, int sig, const union sigval value);
Jako první příklad message passing lze asi uvést System V message passing API. Zpráva tam vypadá jednoduše, na začátku je long message type, za ním následuje pole bajtů, jejichž délka se udává jako další argument při volání API. Volání jsou pak triviální:
int msgsnd (int que, message *msg, int len, int flags); int msgrcv (int que, message *msg, int len, int type, int flags);
Při odesílání zprávy lze specifikovat, zda se má při zaplnění bufferu zablokovat volající proces nebo vrátit chyba, jako drobný detail i zablokovanému volajícímu procesu se může vrátit chyba třeba pokud se zruší message queue.
Při příjmu zprávy se udává maximální velikost bufferu, flagy říkají zda se větší zprávy mají oříznout nebo zda se má vrátit chyba. Typ zprávy může být buď 0, což znamená any message, nebo konkrétní typ, pak se ve flazích dá říci zda se vrátí první zpráva uvedeného nebo jiného než uvedeného typu. Záporný argument pak znamená přijmout zprávu s nejnižším typem menším než je absolutní hodnota argumentu. Ve flazích se samozřejmě dá také říci, zda se čeká na zprávu.
Adresuje se pomocí front zpráv. Fronta se vytvoří voláním int msgget (key, flags), ve kterém se uvádí identifikátor fronty a flagy. Identifikátor je globální, případně může mít speciální hodnotu IPC_PRIVATE, která zaručuje vytvoření nové fronty. Přístup ke frontě ovlivňují access rights ve flazích, ty jsou stejné jako například u souborů.
int msgget (key_t key, int msgflg);
V Machu jsou procesy vybaveny frontami zpráv spravovanými kernelem, těmto frontám se říká porty. Oprávnění k práci s portem jsou uložena v tabulkách pro každý proces spravovaných kernelem, těmto oprávněním se říká capabilities. Proces identifikuje port jeho handlerem, což je index do příslušné tabulky capabilities. Capability může opravňovat číst z portu, zapisovat do portu, nebo jednou zapsat do portu. Pouze jeden proces může mít právo číst z portu, to je jeho vlastník. Při startu je proces vybaven několika významnými porty, například process portem pro komunikaci s kernelem, syscalls jsou pak posílání zpráv na tento port.
Zpráva v Machu se skládá z hlavičky, ta obsahuje destination a reply port handler, velikost zprávy, kernelem ignorované message kind a function code pole, a potom posloupnost datových polí tvořících zprávu. Zvláštností Machu je, že data zprávy jsou tagged, tedy před každou položkou je napsáno co je zač. Tag obsahuje out of line flag, velikost dat v počtu položek, velikost položky v bitech, a konečně typ položky, ten je ze standardních typů plus handler portu. Kernel interpretuje předání handleru portu jako předání příslušné capability.
Pro odeslání a příjem zpráv slouží volání mach_msg, to má jako argument adresu hlavičky zprávy, flags s bity jako expect reply, enable timeout, enable delivery notification, velikost zprávy, velikost místa na odpověď, plus porty.