I. Inter-process communication overview process communication has the following purposes: A. data transmission: A process needs to send its data to another process, the sent data volume is between one byte and several MB. B. shared data: if multiple processes want to operate on shared data, other processes should immediately see the modifications to the shared data. C. notification event: a process needs to send a message to another process or a group of processes to notify it of an event (such as notifying the parent process when the process is terminated ). D. resource sharing: multiple processes share the same resource. In order to achieve this, we need to first, the Inter-process communication overview.
Process communication has the following purposes:
A. data transmission: A process needs to send its data to another process. The data volume sent is between one byte and several MB.
B. shared data: if multiple processes want to operate on shared data, the modifications made by one process to the shared data should be immediately visible to other processes.
C. notification event: a process needs to send a message to another process or a group of processes to notify it of an event (such as notifying the parent process when the process is terminated ).
D. resource sharing: multiple processes share the same resource. To achieve this, the kernel needs to provide a lock and synchronization mechanism.
E. Process Control: some processes want to completely control the execution of another process (such as the Debug process). at this time, the control process wants to block all the traps and exceptions of another process, and be able to know its status changes in time.
Linux inter-process communication (IPC) is developed in the following parts:
Early UNIX inter-process communication, System V-based Inter-process communication, Socket-based Inter-process communication, and POSIX inter-process communication.
The communication methods between UNIX processes include pipelines, FIFO, and signals.
System V inter-process communication methods include: System V message queue, System V signal light, System V shared memory,
POSIX inter-process communication includes posix message queue, posix signal lamp, and posix Shared memory.
Currently, in linux, the Inter-process communication mode is as follows:
(1) pipelines (pipe) and named pipelines (FIFO)
(2) signal (signal)
(3) Message Queue
(4) shared memory
(5) semaphores
(6) socket (socket) 2. redirection is allowed for common Linux shells for pipeline communication, and redirection uses pipelines. For example:
Ps | grep vsftpd. A pipe is a one-way, first-in-first-out, non-structured, fixed-size byte stream that connects the standard output of one process to the standard input of another process. The write process writes data at the end of the pipeline, and the read process reads data at the channel end of the pipeline. After reading the data, it will be removed from the pipeline. No other read process can read the data. Pipelines provide a simple flow control mechanism. When a process tries to read an empty MPs queue, the process will be blocked until data is written to the MPs queue. Similarly, when the pipeline is full, the process tries to write the pipeline again. before other processes move data from the pipeline, the write process will be blocked. MPs queues are mainly used for communication between different processes.
Create and close an MPs queue
To create a simple pipeline, you can use the system to call pipe (). It accepts a parameter, that is, an array containing two integers. If the system call is successful, this array includes two file descriptors used by the pipeline. After creating an MPs queue, a new process is generated.
System call: pipe ();
Prototype: int pipe (int fd [2]);
Return value: if the system call is successful, 0 is returned. If the system call fails, return-1:
Errno = EMFILE (no empty file descriptor)
EMFILE (the system file table is full)
EFAULT (fd array invalid)
Note: fd [0] is used to read the MPs queue, and fd [1] is used to write data to the MPs queue.
See the attachment for the figure.
MPs queue creation
# Include
# Include
# Include
# Include Int main ()
{
Int pipe_fd [2];
If (pipe (pipe_fd) <0 ){
Printf ("pipe create error \ n ");
Return-1;
}
Else
Printf ("pipe create success \ n ");
Close (pipe_fd [0]);
Close (pipe_fd [1]);
} MPs queue read/write
MPs queues are mainly used for communication between different processes. In fact, a pipeline is usually created first, and then a sub-process is created through the fork function. The figure is shown in the attachment. Name pipeline for sub-process writing and read by the parent process: Figure shows considerations for read and write of the attachment pipeline:
You can open two pipelines to create a two-way pipeline. However, you must set the file descriptor correctly in the sub-process. Pipe () must be called in the system call fork (), otherwise the child process will not inherit the file descriptor. When a half-duplex pipeline is used, any associated process must share a related ancestor process. Because the pipeline exists in the system kernel, any process that is not in the ancestor process of the pipeline creation cannot address it. This is not the case in the named pipeline. For more information about MPs queue instances, see pipe_r1_c # include.
# Include
# Include
# Include
# Include Int main ()
{
Int pipe_fd [2];
Pid_t pid;
Char buf_r [100];
Char * p_wbuf;
Int r_num; memset (buf_r, 0, sizeof (buf_r); clear 0 data in the array; if (pipe_fd) <0 ){
Printf ("pipe create error \ n ");
Return-1;
} If (pid = fork () = 0 ){
Printf ("\ n ");
Close (pipe_fd [1]);
Sleep (2 );
If (r_num = read (pipe_fd [0], buf_r, 100)> 0 ){
Printf ("% d numbers read from be pipe is % s \ n", r_num, buf_r );
}
Close (pipe_fd [0]);
Exit (0 );
} Else if (pid> 0 ){
Close (pipe_fd [0]);
If (write (pipe_fd [1], "Hello", 5 )! =-1)
Printf ("parent write success! \ N ");
If (write (pipe_fd [1], "Pipe", 5 )! =-1)
Printf ("parent wirte2 succes! \ N ");
Close (pipe_fd [1]);
Sleep (3 );
Waitpid (pid, NULL, 0 );
Exit (0 );
}
}
Standard Stream pipeline
Like the standard I/O for file stream operations in linux, pipeline operations also support the file stream-based mode. The interface functions are as follows:
Library function: popen ();
Prototype: FILE * open (char * command, char * type );
Returned value: if successful, a new file stream is returned. If a process or MPs queue cannot be created, NULL is returned. The direction of the data stream in the pipeline is controlled by the second parameter type. This parameter can be r or w, indicating read or write respectively. But it cannot be both read and write. In Linux, the pipeline is opened as the first character in the type parameter. Therefore, if you write rw to the type parameter, the pipeline will be opened in read mode. The MPs queue created using popen () must be closed using pclose. In fact, popen/pclose is very similar to fopen ()/fclose () in the input/output stream of the standard file.
Library function: pclose ();
Prototype: int pclose (FILE * stream );
Return value: return the status of the system call wait4.
If stream is invalid or wait4 () fails to be called,-1 is returned. Note that this library function waits for the pipeline process to finish running, and then closes the file stream. The library function pclose () executes the wait4 () function on the process created using popen (), which destroys pipelines and file systems.
Example of a stream pipeline.
# Include
# Include
# Include
# Include
# Define BUFSIZE 1024
Int main (){
FILE * fp;
Char * cmd = "ps-ef ";
Char buf [BUFSIZE];
Buf [BUFSIZE] = '\ 0 ';
If (fp = popen (cmd, "r") = NULL)
Perror ("popen ");
While (fgets (buf, BUFSIZE, fp ))! = NULL)
Printf ("% s", buf );
Pclose (fp );
Exit (0 );
} Named pipe (FIFO)
Basic concepts
Named pipelines are basically the same as general pipelines, but there are also some significant differences:
A. The named pipe exists as A special device file in the file system.
B. data can be shared between different ancestor processes through pipelines.
C. after all I/O operations are performed by the process of the shared MPs queue, the named MPs queue will be stored in the file system for future use.
Pipelines can only be used by related processes, and their common ancestor processes create pipelines. However, through FIFO, unrelated processes can also exchange data. Create and operate named MPs queues
Create named MPs queue
# Include
# Include
Int mkfifo (const char * pathname, mode_t mode );
Return: 0 if the operation is successful, and-1 if an error occurs.
Once you have created a FIFO with mkfifo, open it. Indeed, general file I/O functions (close, read, write, unlink, etc.) can be used for FIFO. When a FIFO is enabled, the non-blocking mark (O_NONBLOCK) has the following effects:
(1) in general (O_NONBLOCK is not described), read-only access is blocked until another process opens This FIFO for writing. Similarly, opening a FIFO for writing will block another process to open it for reading.
(2) if it refers to an O_NONBLOCK, it will be returned immediately after it is read-only. However, if no process has opened a FIFO for reading, an error is returned when only writing is enabled, and its errno is ENXIO. Similar to a pipeline, if you write a FIFO that has no process opened for reading, the SIGPIPE signal is generated. If the last write process of a FIFO instance closes the FIFO instance, a file termination mark is generated for the read process of the FIFO instance.
FIFO error information:
EACCES (no access permission)
EEXIST (the specified file does not exist)
ENAMETOOLONG (the path name is too long)
ENOENT (the included directory does not exist)
ENOSPC (insufficient file system space)
ENOTDIR (invalid file path)
EROFS (the specified file exists in the read-only file system) implements o_write.c
# Include
# Include
# Include
# Include
# Include
# Include
# Include
# Define FIFO "/tmp/myfifo" main (int argc, char ** argv)
{
Char buf_r [100];
Int fd;
Int nread;
If (mkfifo (FIFO, O_CREAT | O_EXCL) <0) & (errno! = EEXIST ))
Printf ("cannot create external oserver \ n ");
Printf ("Preparing for reading bytes... \ n ");
Memset (buf_r, 0, sizeof (buf_r ));
Fd = open (FIFO, O_RDONLY | O_NONBLOCK, 0 );
If (fd =-1)
{
Perror ("open ");
Exit (1 );
}
While (1 ){
Memset (buf_r, 0, sizeof (buf_r ));
If (nread = read (fd, buf_r, 100) =-1 ){
If (errno = EAGAIN)
Printf ("no data yet \ n ");
}
Printf ("read % s from FIFO \ n", buf_r );
Sleep (1 );
}
Pause ();
Unlink (FIFO );
} Else o_read.c
# Include
# Include
# Include
# Include
# Include
# Include
# Include
# Define export o_server "/tmp/myfifo"
Main (int argc, char ** argv)
{
Int fd;
Char w_buf [100];
Int nwrite;
If (fd =-1)
If (errno = ENXIO)
Printf ("open error; no reading process \ n ");
Fd = open (FIFO_SERVER, O_WRONLY | O_NONBLOCK, 0 );
If (argc = 1)
Printf ("Please send something \ n ");
Strcpy (w_buf, argv [1]);
If (nwrite = write (fd, w_buf, 100) =-1)
{
If (errno = EAGAIN)
Printf ("The FIFO has not been read yet. Please try later \ n ");
}
Else
Printf ("write % s to the FIFO \ n", w_buf );
}
III. signal
Signal overview
The signal is software interruption. The signal mechanism is one of the oldest processes in Unix systems. It is used to transmit asynchronous signals between one or more processes. Many conditions can generate a signal.
A. When A user presses certain terminal keys, A signal is generated. Pressing the DELETE key on the terminal usually generates a SIGINT ). This is a way to stop a program that has lost control.
B. Hardware exceptions generate signals such as division 0 and invalid storage access. These conditions are usually detected by hardware and notified to the kernel. Then the kernel generates an appropriate signal for the process that is running when this condition occurs. For example, a SIGSEGV is generated for a process that executes an invalid storage access.
C. The process uses the kill (2) function to send signals to another process or process Group. Naturally, there are some restrictions: all processes that receive and send signals must be the same, or the owner of the processes that send signals must be superusers.
D. you can use the Kill command to send signals to other processes. This program is the interface of the Kill function. This command is often used to terminate an out-of-control background process.
E. when a software condition has been detected and the process is notified, a signal is generated. This does not mean that the hardware production conditions (such as division by 0) are software conditions. For example, SIGURG (data uploaded over a network connection with an unspecified baud rate) and SIGPIPE (a process writes to This MPs queue after the read process of the MPs queue is terminated ), and SIGALRM (the alarm time set by the process has timed out ). The kernel generates signals for processes to respond to different events. these events are the signal sources. The main signal sources are as follows:
(1) exception: an exception occurs during the process;
(2) Other processes: a process can send signals to another process or a group of processes;
(3) terminal interruption: Ctrl-c, Ctro-\, etc;
(4) job control: Management of foreground and background processes;
(5) quota: the CPU times out or the file size exceeds the limit;
(6) notification: notifies a process of an event, such as I/O readiness;
(7) alarm: timer expired; Linux signal
1. SIGHUP 2. SIGINT (terminated) 3. SIGQUIT (exited) 4. SIGILL 5, SIGTRAP 6, SIGIOT 7, SIGBUS 8, SIGFPE 9, SIGKILL 10, SIGUSER 11, sigsegv siguser 12, SIGPIPE 13, SIGALRM 14, SIGTERM 15, SIGCHLD 16, SIGCONT 17. common signals of SIGSTOP 18, SIGTSTP 19, SIGTTIN 20, SIGTTOU 21, SIGURG 22, SIGXCPU 23, SIGXFSZ 24, SIGVTALRM 25, SIGPROF 26, SIGWINCH 27, SIGIO 28, and SIGPWR:
SIGHUP: The end signal sent from the terminal;
SIGINT: the interrupt signal from the keyboard (Ctrl + c)
SIGQUIT: Exit signal from the keyboard;
SIGFPE: Floating point exception signal (for example, floating point overflow );
SIGKILL: this signal ends the process of receiving the signal;
SIGALRM: this signal is sent when the timer of the process expires;
SIGTERM: the signal generated by the kill command;
SIGCHLD: indicates the signal that the sub-process stops or stops;
SIGSTOP: the stop sweep signal from the keyboard (Ctrl-Z) or debugging program may require the system to operate in one of the following three ways when a signal appears.
(1) ignore this signal. Most signals can be processed in this way, but there are two types of signals that cannot be ignored. They are: SIGKILL and SIGSTOP. These two signals cannot be ignored because they provide super users with a reliable method to terminate or stop the process. In addition, if you ignore some signals generated by hardware exceptions (such as illegal storage access or divided by 0), the process behavior is defined.
(2) capture signals. To do this, we need to notify the kernel to call a user function when a signal occurs. In user functions, the executable user wants to process such events. If the SIGCHLD signal is captured, the sub-process has been terminated. Therefore, the capture function of this signal can call waitpid to obtain the ID of the sub-process and its termination status.
(3) execute the default system action. The default action for most signals is to terminate the process. Each signal has a default action. when a process does not specify a processing program for the signal, the kernel processes the signal. There are 5 default actions:
(1) abort: In the current directory of the process, save the address space content and register content of the process to a file called core, and then terminate the process.
(2) exit: terminate the process without generating a core file.
(3) ignore (ignore): ignore this signal.
(4) stop: stop the process.
(5) continue (contiune): If the process is suspended, the dynamic line of the process has just been restored. Otherwise, ignore the signal. Signal sending and capturing
Kill () and raise ()
Kill () can not only stop a process, but also send other signals to the process.
Unlike the kill function, the raise () function sends signals to the process itself.
# Include
# Include
Int kill (pid_t pid, int signo );
Int raise (int signo );
Two functions return: 0 if successful, and-1 if an error occurs.
There are four different conditions for the pid parameter of kill:
(1) pid> 0 sends the signal to the process whose ID is pid.
(2) pid = 0 send the signal to the process Group ID is equal to the process Group ID of the sending process, and the sending process has the permission to send the signal to all processes.
(3) pid <0 sends the signal to all processes whose Process Group ID is equal to the absolute value of the pid and the sending process has the permission to send the signal to it. As mentioned above, "all processes" do not include processes in the system process set.
(4) pid =-1 POSIX.1 undefined
Kill. c
# Include
# Include
# Include
# Include
# Include
Int main ()
{
Pid_t pid;
Int ret;
If (pid = fork () <0 ){
Perro ("fork ");
Exit (1 );
}
If (pid = 0 ){
Raise (SIGSTOP );
Exit (0 );
} Else {
Printf ("pid = % d \ n", pid );
If (waitpid (pid, NULL, WNOHANG) = 0 ){
If (ret = kill (pid, SIGKILL) = 0)
Printf ("kill % d \ n", pid );
Else {
Perror ("kill ");
}
}
}
} Alarm and pause functions
You can use the alarm function to set a time value (alarm time), which will be exceeded at a certain time point in the future. When the set time is exceeded, a SIGALRM signal is generated. If you do not ignore or capture the cited signal, the default action is to terminate the process.
# Include
Unsigned int alarm (unsigned int secondss );
Return value: 0 or the remaining seconds of the previously set alarm time. The value of seconds is the number of seconds. after the specified seconds, the signal SIGALRM is generated. Each process can have only one alarm time. If the alarm time has been set for the process before calling alarm, and it has not timed out, the residual value of the alarm time is returned as the value of this alarm function call. The previously registered alarm time is replaced by a new value.
If the previously registered alarm time has not exceeded and the seconds value is 0, the previous alarm time is canceled, and the remaining values are still returned by the function.
The pause function suspends a call process until a signal is captured.
# Include
Int pause (void );
Return value:-1. errno is set to EINTR.
Pause is returned only when a signal processing program is executed and returned from it. Alarm. c
# Include
# Include
# Include
Int main ()
{
Int ret;
Ret = alarm (5 );
Pause ();
Printf ("I have been waken up. \ n", ret );
} Signal processing
When the system captures a signal, it can ignore the signal or use the specified processing function to process the signal, or use the system default method. There are two main methods for signal processing: one is to use a simple signal function, and the other is to use a signal set function Group.
Signal ()
# Include
Void (* signal (int signo, void (* func) (int)
Return: successful indicates the previous signal processing configuration. If an error occurs, it indicates SIG_ERR.
The value of func is: (a) constant SIGIGN, or (B) constant SIGDFL, or (c) the address of the function to be called after receiving this signal. If SIGIGN is specified, this signal is ignored to the kernel (SIGKILL and SIGSTOP are two signals that cannot be ignored ). If SIGDFL is specified, it indicates that the action after receiving this signal is the default action of the system. When the function address is specified, we call this to capture this signal. We call this function signal handler or signal-catching funcgion ). the signal function prototype is too complex. if the following typedef is used, it can be simplified.
Type void sign (int );
Sign * signal (int, handler *);
For more information, see mysignal. c.
# Include
# Include
# Include
Void my_func (int sign_no)
{
If (sign_no = SIGINT)
Printf ("I have get SIGINT \ n ");
Else if (sign_no = SIGQUIT)
Printf ("I have get SIGQUIT \ n ");
}
Int main ()
{
Printf ("Waiting for signal SIGINT or sigqui\ n ");
Signal (SIGINT, my_func );
Signal (SIGQUIT, my_func );
Pasue ();
Exit (0 );
}
Signal set function Group
We need a data type that can represent multiple signals-signal sets. This data type will be used in functions such as sigprocmask () to tell the kernel that signals in this signal set are not allowed. Signal set function set package has several major water content modules: Create function set, register signal set, and detect signal set.
The figure is shown in the attachment. Create a function set
# Include
Int sigemptyset (sigset_t * set );
Int sigfillset (sigset_t * set );
Int sigaddset (sigset_t * set, int signo );
Int sigdelset (sigset_t * set, int signo );
Returns four functions: 0 if the operation succeeds, and-1 if an error occurs.
Int sigismember (const sigset_t * set, int signo );
Return value: if it is true, it is 1. if it is false, it is 0;
Signemptyset: the initialization signal set is empty.
Sigfillset: the initial signal set is a set of all signals.
Sigaddset: Adds a specified signal to an existing set.
Sigdelset: deletes a specified signal from the signal set.
Sigismember: queries whether the specified signal is in the signal set. Register a signal set
The register signal processor is mainly used to determine how processes process signals. First, determine whether the current process blocking can be passed to the signal set. In this case, the sigprocmask function is used to determine whether to detect or change the blocked signal, and then the sigaction function is used to change the process's behavior after receiving a specific signal.
The signal shielding word of a process can specify the signal set that the current blocking cannot be delivered to the process. Call the sigprocmask function to detect or change the signal shielding characters of (or both) processes.
# Include
Int sigprocmask (int how, const sigset_t * set, sigset_t * oset );
Return value: 0 if the operation is successful, and-1 if an error occurs.
Oset is a non-null pointer, and the process is returned by the current signal shielding word through oset. Second, if set is a non-null pointer, the how parameter indicates how to modify the current signal shielding word.
Use sigprocmask to change the current blocked signal. How parameter settings:
SIG_BLOCK the new signal shielding word of the process is the union of the current signal shielding word and the set pointing to the signal set. Set contains the additional signals we want to block.
SIG_NUBLOCK the new signal shielding word of the process is the intersection of the current signal shielding word and the signal set pointed to by the set. Set contains the signal we want to unblocking.
SIG_SETMASK the new signal shielding of this process is the value pointed to by set. If set is a null pointer, the signal shielding word of the process is not changed, and the value of how is meaningless.
The sigaction function checks or modifies (or both) the processing action associated with the specified signal. This function replaces the signal function used in earlier versions of UNIX.
# Include
Int sigaction (int signo, const struct sigaction * act, struct sigaction * oact );
Return value: 0 if the operation is successful, and-1 if an error occurs.
The signo parameter is the number of signals to detect or modify specific actions. If the act pointer is not empty, modify its action. If the oact pointer is null, the system returns the original action of the signal. This function uses the following structure:
Struct sigaction {
Void (* sa_handler) (int signo );
Sigset_t sa_mask;
Int sa_flags;
Void (* sa_restore );
};
Sa_handler is a function pointer that specifies the signal correlation function. it can be a custom processing function or SIG_DEF or SIG_IGN;
Sa_mask is a signal set that specifies which signals should be blocked during signal processing program execution.
Sa_flags contains many flag spaces, which are various options for signal processing. The details are as follows:
SA_NODEFER \ SA_NOMASK: when this signal is captured, the system will not automatically block this signal when executing its signal capture function.
SA_NOCLDSTOP: processes ignore any SIGSTOP, SIGTSTP, SIGTTIN, and SIGTOU signals generated by sub-processes.
SA_RESTART: enables restart of system calls to take effect again.
SA_ONESHOT \ SA_RESETHAND: the default action to restore the signal after the custom signal is executed only once.
Detection signals are subsequent steps of signal processing, but they are not necessary. The sigpending function runs the process to detect the "pending" signal (the process does not know its existence) and further decide what to do with it.
Sigpending returns the signal set that cannot be delivered and is currently pending when the calling process is blocked.
# Include
Int sigpending (sigset_t * set );
Return value: 0 if the operation is successful, and-1 if an error occurs.
For signal set instances, see sigaction. c.
# Include
# Include
# Include
# Include
# Include
Void my_func (int signum ){
Printf ("If you want to quit, please try SIGQUIT \ n ");
}
Int main ()
{
Sigset_t set, pendset;
Struct sigaction action1, action2;
If (sigemptyse (& set) <0)
Perror ("sigemptyset ");
If (sigaddset (& set, SIGQUIT) <0)
Perror ("sigaddset ");
If (sigaddset (& set, SIGINT) <0)
Perror ("sigaddset ");
If (sigprocmask (SIG_BLOCK, & set, NULL) <0)
Perror ("sigprcmask ");
Esle {
Printf ("blocked \ n ");
Sleep (5 );
}
If (sigprocmask (SIG_UNBLOCK, & set, NULL)
Perror ("sigprocmask ");
Else
Printf ("unblock \ n ");
While (1 ){
If (sigismember (& set, SIGINT )){
Sigemptyset (& action1.sa _ mask );
Action1.sa _ handler = my_func;
Sigaction (SIGINT, & action1, NULL );
} Else if (sigismember (& set, SIGQUIT )){
Sigemptyset (& action2.sa _ mask );
Action2.sa _ handler = SIG_DEL;
Sigaction (SIGTERM, & action2, NULL );
}
}
}