Introduction to interprocess communication
Purpose of interprocess communication
- Data transfer: one process needs to send its data to another process
- Resource sharing: multiple processes share the same resources.
- Notification event: a process needs to send a message to another process or group of processes to notify it (them) of an event (such as notifying the parent process when the process terminates).
- Process control: some processes want to fully control the execution of another process (such as the Debug process). At this time, the control process wants to be able to intercept all traps and exceptions of another process and know its state changes in time.
Interprocess communication development
- The Conduit
- System V interprocess communication
- POSIX interprocess communication
Interprocess communication classification
The Conduit
- Anonymous pipe
- name pipes
System V IPC
- System V message queuing
- System V shared memory
- System V semaphore
POSIX IPC
- Message queue
- Shared memory
- Semaphore
- mutex
- Conditional variable
- Read write lock
To make two different processes communicate, the premise is to let the two processes see the same resource
The Conduit
What is a pipe
- Pipeline is the oldest form of interprocess communication in Unix.
- We call a data flow from one process to another a "pipe"
Anonymous Pipe
#include <unistd.h> function:Create an anonymous pipe Function prototype int pipe(int fd[2]); parameter fd: File descriptor array,among fd[0]Indicates the reading end, fd[1]Indicates the write end Return value:0 is returned for success and 0 is returned for failure-1
Example code
//Example: read data from the keyboard, write to the pipeline, read the pipeline, and write to the screen #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main( void ) { int fds[2]; char buf[100]; int len; if ( pipe(fds) == -1 )//Create anonymous pipe perror("make pipe"),exit(1); // read from stdin while ( fgets(buf, 100, stdin) ) //Loop write data to buf { len = strlen(buf); // write into pipe //Write first and then judge if (write(fds[1], buf, len) != len) //When the number of characters actually written to the pipeline is not equal to the buf length, an error is written { perror("write to pipe"); break; } memset(buf, 0x00, sizeof(buf));//Set the contents of buf to 0 // read from pipe if ( (len=read(fds[0], buf, 100)) == -1 ) //Read before Judge { perror("read from pipe"); break; } // write to stdout if ( write(1, buf, len) != len ) //Write first and then judge { perror("write to stdout"); break; } } }
Using fork to share pipeline principle
From the perspective of file descriptor - deep understanding of pipeline
Because our initial process opens three files: standard input, standard output and standard error, all subsequent processes open these three files by default
Why aren't the files private? Because the file does not belong to the process
From the perspective of kernel - Pipeline essence
f_op is the function pointer we talked about before.
The figure simplifies the understanding. The inode pointer is not in the struct file
So, look at the pipeline, just as you look at the file! The use of pipes is consistent with files, which caters to the "Linux everything is a file idea".
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(int argc, char *argv[]) { int pipefd[2]; if (pipe(pipefd) == -1) ERR_EXIT("pipe error"); pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork error"); if (pid == 0) //Subprocess { close(pipefd[0]);//Close the reader //Write to pipe write(pipefd[1], "hello", 5); //Close the write side to reduce the occupation of file descriptors close(pipefd[1]); exit(EXIT_SUCCESS); } //The parent process closes the write side close(pipefd[1]); char buf[10] = {0}; //Read data from pipe read(pipefd[0], buf, 10); printf("buf=%s\n", buf); return 0; }
Pipeline read / write rules
-
If the writer does not close the file descriptor or write, the reader may be blocked for a long time (there may be data in the pipeline at the beginning, so it will not be blocked immediately)
Example:
#include<stdio.h> #include <unistd.h> #include<string.h> int main() { int pi[2] = {0}; if (pipe(pi) < 0)//Failed to create anonymous pipeline { perror("pipe"); return 1; } int pid = fork(); if (pid < 0) { perror("fork"); return 1; } //Let the child process write and the parent process read if (pid == 0)//Subprocess { //For fear of mistakes, another interface is used, so the read of the child process is turned off close(pi[0]); const char* buf = "I am a child!\n"; while (1) { write(pi[1], buf, strlen(buf)); sleep(3);//The child process writes every 3s } } else//Parent process, read only { close(pi[1]); char buf[64]; while (1) { ssize_t s = read(pi[0], buf, sizeof(buf) - 1); if (s > 0) { buf[s] = 0; } printf("father get info from pipe:%s\n", buf); } } return 0; }
Because the child process writes to the pipeline every 3s, while the parent process reads the pipeline data continuously, the parent process will block for a period of time:
Therefore, if we close the file descriptor of the child process, the parent process will be waiting all the time, that is, it will be blocked for a long time.
-
When we write, if the write conditions are not met (the pipeline is full and the reading speed of the reader is too slow), the writer will block
#include<stdio.h> #include <unistd.h> #include<string.h> int main() { int pi[2] = {0}; if (pipe(pi) < 0)//Failed to create anonymous pipeline { perror("pipe"); return 1; } int pid = fork(); if (pid < 0) { perror("fork"); return 1; } //Let the child process write and the parent process read if (pid == 0)//Subprocess { //For fear of mistakes, another interface is used, so turn off the reading of the child process close(pi[0]); const char* buf = "I am a child!\n"; int count = 0; while (1) { write(pi[1], buf, strlen(buf)); printf("CHILD:%d\n", count++); } } else//Parent process, read only { close(pi[1]); char buf[64]; while (1) { ssize_t s = read(pi[0], buf, sizeof(buf) - 1); sleep(1);//The parent process reads every 1s if (s > 0) { buf[s] = 0; } printf("father get info from pipe:%s\n", buf); } } return 0; }
It can be seen that the child process writes suddenly at the beginning, while the parent process reads data every 1s, and reads data from the parent process, and the child process does not write again because the pipeline is full and the writing conditions are not met, so the writing cannot be completed.
-
If the writer closes the file descriptor, the reader will read 0 when reading the pipeline, that is, the read function returns 0
#include<stdio.h> #include <unistd.h> #include<string.h> int main() { int pi[2] = {0}; if (pipe(pi) < 0)//Failed to create anonymous pipeline { perror("pipe"); return 1; } int pid = fork(); if (pid < 0) { perror("fork"); return 1; } //Let the child process write and the parent process read if (pid == 0)//Subprocess { //For fear of mistakes, another interface is used, so the read of the child process is turned off close(pi[0]); const char* buf = "I am a child!\n"; int count = 0; while (1) { if (count == 5)//The child process writes only 5 times close(pi[1]); write(pi[1], buf, strlen(buf)); ++count; } } else//Parent process, read only { close(pi[1]); char buf[64]; while (1) { ssize_t s = read(pi[0], buf, sizeof(buf) - 1); sleep(1);//The parent process sleeps for 1s and reads again if (s > 0) { buf[s] = 0; printf("father get info from pipe:%s\n", buf); } printf("father exit return : %d\n", s); } } return 0; }
The child process writes only 5 times without sleep, while the parent process reads once in 1s of sleep. When the child process stops writing, the data read by the parent process from the pipeline is 0.
-
If the reader is closed, the writer process may be killed directly
#include<stdio.h> #include <unistd.h> #include<string.h> #include<stdilb.h> int main() { int pi[2] = {0}; if (pipe(pi) < 0)//Failed to create anonymous pipeline { perror("pipe"); return 1; } int pid = fork(); if (pid < 0) { perror("fork"); return 1; } //Let the child process write and the parent process read if (pid == 0)//Subprocess { //For fear of mistakes, another interface is used, so the read of the child process is turned off close(pi[0]); const char* buf = "I am a child!\n"; int count = 0; while (1) { write(pi[1], buf, strlen(buf)); printf("CHILD:%d\n", count++); } exit(2); } else//Parent process, read only { close(pi[1]); char buf[64]; int count = 0; while (1) { if (5 == count++) { close(pi[0]); break; } ssize_t s = read(pi[0], buf, sizeof(buf) - 1); sleep(1); if (s > 0) { buf[s] = 0; printf("father get info from pipe:%s\n", buf); } } } return 0; }
Preliminary observation:
We can see that after the parent process reads 5 times, both the parent and child processes end, and the child process does not continue printing, but exits.
Instead of letting the parent process end, we use the script to observe the status of the parent-child process:
shell script:
while :; do ps axj |grep mypipe | grep -v grep; echo "######################################"; sleep 1; done
effect:
You can see that the child process is actually in a zombie state, that is, it exits.
In fact, it was killed by the operating system, because no one in the pipeline had read the data, and the sub process was wasting resources by writing data into the pipeline. The operating system did not allow this behavior to happen, so it killed the sub process.
Since the child process is killed, you need to recycle it with the parent process and get its signal
Modify the code to make the parent process wait and get the signal:
You can see that the exit signal of the child process is 13
Let's use kill -l to see kill's command 13:
As the name suggests, this signal is used to kill pipeline related processes.
Modification code:
#include<stdio.h> #include <unistd.h> #include<string.h> #include<stdlib.h> #include<sys/wait.h> int main() { int pi[2] = {0}; if (pipe(pi) < 0)//Failed to create anonymous pipeline { perror("pipe"); return 1; } int pid = fork(); if (pid < 0) { perror("fork"); return 1; } //Let the child process write and the parent process read if (pid == 0)//Subprocess { //For fear of mistakes, another interface is used, so the read of the child process is turned off close(pi[0]); const char* buf = "I am a child!\n"; int count = 0; while (1) { write(pi[1], buf, strlen(buf)); printf("CHILD:%d\n", count++); } exit(2); } else//Parent process, read only { close(pi[1]); char buf[64]; int count = 0; while (1) { if (5 == count++) { close(pi[0]); break; } ssize_t s = read(pi[0], buf, sizeof(buf) - 1); sleep(1); if (s > 0) { buf[s] = 0; printf("father get info from pipe:%s\n", buf); } } //The parent process gets the exit signal of the child process int status = 0; waitpid(pid, &status, 0); printf("child exited, father gets the signal:%d\n", status & 0x7f); } return 0; }
In the | pipeline in the command line, there is brotherhood between processes
We use the sleep 2000 | sleep 1000 command to run, and then copy a session to view their process pid information:
-
When no data is readable
- O_NONBLOCK disable: the read call is blocked, that is, the process suspends execution until data arrives.
- O_NONBLOCK enable: the read call returns - 1, and the errno value is EAGAIN.
-
When the pipe is full
- O_NONBLOCK disable: the write call blocks until a process reads the data
- O_NONBLOCK enable: the call returns - 1, and the errno value is EAGAIN
-
If the file descriptors corresponding to all pipeline write ends are closed, read returns 0
-
If the file descriptors corresponding to all pipeline readers are closed, the write operation will generate the signal SIGPIPE, which may lead to the write process
sign out -
When the amount of data to be written is not greater than pipe_ When buf, linux will guarantee the atomicity of writing.
-
When the amount of data to be written is greater than pipe_ When buf, linux will no longer guarantee the atomicity of writes.
Pipeline characteristics
- It can only be used for communication between processes with common ancestors (processes with kinship); Typically, a pipeline is created by a process
After that, the process calls fork, and then the pipeline can be applied between the parent and child processes. - Pipeline provides streaming services
- Generally speaking, the process exits and the pipeline is released, so the pipeline life cycle changes with the process
- Generally speaking, the kernel will synchronize and mutually exclusive pipeline operations
- The pipeline is half duplex, and the data can only flow in one direction; When both parties communicate, two pipelines need to be established
Anonymous pipes have brotherhood between processes
name pipes
- One limitation of pipeline application is that it can only communicate between processes with a common ancestor (kinship).
- If we want to exchange data between unrelated processes, we can use FIFO files to do this, which is often called named pipes.
- Named pipes are a special type of file
Create a named pipe
-
Named pipes can be created from the command line by using the following command:
$ mkfifo filename
-
Named pipes can also be created from the program. The related functions are:
int mkfifo(const char *filename, mode_t mode); //filename is the pipe name
-
To create a named pipe:
int main(int argc, char *argv[]) { mkfifo("p2", 0644); return 0; }
The difference between anonymous pipes and named pipes
- Anonymous pipes are created and opened by the pipe function.
- The named pipe is created by the mkfifo function and opened with open
- The only difference between FIFO (named pipe) and pipe (anonymous pipe) is that they are created and opened in different ways, but once these works are completed
After they become, they have the same meaning
Opening rules for named pipes
-
If the current open operation is to open FIFO for reading
- O_NONBLOCK disable: blocks the FIFO until a corresponding process opens it for writing
- O_NONBLOCK enable: returns success immediately
-
If the current open operation is to open FIFO for writing
- O_NONBLOCK disable: blocks until a corresponding process opens the FIFO for reading
- O_NONBLOCK enable: failure is returned immediately with error code ENXIO
Using named pipes to realize server client communication
makefile
# ll total 12 -rw-rw-r-- 1 ysj ysj 568 Oct 6 01:18 client.c -rw-rw-r-- 1 ysj ysj 138 Oct 6 01:07 makefile -rw-rw-r-- 1 ysj ysj 662 Oct 6 01:15 server.c # cat Makefile .PHONY:all all:server client server:server.c gcc $^ -o $@ client:client.c gcc $^ -o $@ .PHONY:clean clean: rm -f server client fifo
server.c
#include<stdio.h> #include<string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include<unistd.h> #define FIFE_FIFO "fifo" int main() { umask(0); mkfifo(FIFE_FIFO, 0666); int rfd = open(FIFE_FIFO, O_RDONLY); if (rfd < 0) { perror("open"); return 1; } char buf[64]; while (1) { buf[0] = 0; printf("p") ssize_t s = read(rfd, buf, sizeof(buf) - 1); if (s > 0) { buf[s - 1]= 0; printf("client say:# %s\n", buf); } else if(s == 0) { printf("exit,too\n"); break; } else { perror("read"); return 1; } } close(rfd); return 0; }
client.c
#include<stdio.h> #include<string.h> #include<unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define FIFE_FIFO "fifo" int main() { int wfd = open(FIFE_FIFO, O_WRONLY); if (wfd < 0) { perror("open"); return 1; } char buf[64]; while (1) { printf("Please Enter:# "); fflush(stdout); ssize_t s = read(0, buf, sizeof(buf) - 1); if (s > 0) { buf[s]= 0; write(wfd, buf, strlen(buf)); } else { printf("exit\n"); break; } } close(wfd); return 0; }
result:
Why didn't you print please wait... At first?
Because in the server, the reader is blocked at the open when it is not opened. When the client starts running and the writer is opened, the server ends blocking and starts running before printing please wait
servet has nothing to do with client, so named pipes are different from anonymous pipes in this regard: