Linux inter-process communication IPC (pipe fifo mmap)

1. Introduction to inter-process communication:

In the Linux environment, the address spaces of each process are independent of each other. Variables in any process are invisible in another process, so they cannot be accessed between processes. To exchange data, you must go through the kernel and open up a buffer in the kernel. Area, process 1 copies the data from user space to the kernel buffer, and process 2 reads from it. This is called process communication, or IPC for short.

2. Methods of inter-process communication:

Pipe: The most basic IPC mechanism, also called anonymous pipe, is suitable for communication between processes that are related by blood, that is, communication between father and son processes.

Command pipe fifo: can be used for communication between processes that are related by blood, and can be used for communication between processes that are not related by blood.

Memory mapping area mmap: It can be used for communication between processes that are related by blood or for communication between processes that are not related by blood.

Three. Pipe

The essence of a pipe is a kernel buffer, consisting of two file descriptors, one representing the writing end and the other representing the reading end. It is stipulated that data flows into the pipe from the writing end of the pipe and the reading end flows out of the pipe. When both processes exit At this time, the pipe will also disappear. Note that the read and write ends of the pipe are blocked by default. Pipes are half-duplex, meaning data can only flow in one direction.


The function prototype is as above. When using it, just pass in an array of size 2. fd[0] represents the reading end and fd[1] represents the writing end. If the creation is successful, 0 will be returned. If the creation fails, -1 will be returned. The following is Let’s take a look at the specific usage and how to use pipe to achieve communication between parent and child processes:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>

int main()
{
    int fd[2];
    int pip=pipe(fd);
    if(pip<0)
    {
        perror("pipe error\\
");
    }
    pid_t pid = fork();
    if(pid<0)
    {
        perror("fork error\\
");
        return -1;
    }
    else if(pid>0)//Write data to the pipe in the parent process
    {
        close(fd[0]);
        write(fd[1],"HELLO WORLD",sizeof("HELLO WORLD"));
        close(fd[1]);
    }
    else if(pid==0)//The child process reads data in the pipe
    {
        close(fd[1]);
        char buf[BUFSIZ];
        memset(buf,0,sizeof(buf));
        int size=read(fd[0],buf,sizeof(buf));
        printf("Data read: size=%d,contents=%s\\
",size,buf);
    }
    return 0;

}

Write to the pipe in the parent process, and read the pipe in the child process. Note: the writing end needs to be closed when reading, and the reading end needs to be closed when writing. Only one end is allowed to be opened. Combined with the use of the previous fork function, we should also pay attention to the use of header files. The above process can be summarized as follows: the parent process creates a pipe, the parent process creates a child process, the parent process closes one end, the child process closes one end, and the parent and child processes call the read and write functions to implement communication.

Pipe communication is blocked. What will happen if we only operate on one end of it? First, close the reading end, and then keep writing. At this time, the pipe will be filled, and finally a SIGPIPE signal will be returned:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    //signal(SIGPIPE,handler);
    struct sigaction act;
    act.sa_handler=handler;
    sigemptyset( & amp;act.sa_mask);
    act.sa_flags=0;
    sigaction(SIGPIPE, & amp;act,NULL);
    int fd[2];
    int pip=pipe(fd);
    if(pip<0)
    {
        perror("fork error\\
");
        return -1;
    }
    close(fd[0]);
    while(1)
    {
        write(fd[1],"HELLO WORLD",sizeof("HELLO WORLD"));
    }
    return 0;
}

Here we use the signal registration function sigaction. Of course, you can also use signal. Anyway, here we just simply capture the returned signal. Here we first create a pipeline, close the reading end, write to it in a loop, and observe when a certain moment occurs. Will the SIGPIPE signal be returned when it is full? This signal indicates that the pipe has broken.

All the results returned in the running results are signo=13. This 13 is SIGPIPE. We can view the values of all signals through kill -l:

When the read end is not closed and no reading is performed, the pipe will be filled and blocked. When the pipe is blocked, you can actually set it to non-blocking manually. We have already learned about the fcntl function before. This function can obtain the flag attribute of the file and modify its flag attribute:

//The read end is set to non-blocking
int flag=fcntl(fd[0],F_GETFL,0);
flag = flag | O_NOBLOCK;
fcntl(fd[0],F_SETFL,flag);

//Set the write end to non-blocking
int flag=fcntl(fd[1],F_GET1FL,0);
flag = flag | O_NOBLOCK;
fcntl(fd[1],F_SETFL,flag);

By default, both ends of the pipe are blocked.

4. Named pipe FIFO

FIFO is used for communication between two processes that are not related by blood. You can refer to the following steps. One process creates a FIFO file and writes to it, and the other process opens the FIFO file for reading; create a FIFO file and call the mkfio function. Or a command will do. Here we will use the mkfifo function. The function prototype is as follows. The first one is the file name, and the second one is the permissions of the created FIFO file. Generally, it can be set to 0777. If it is successfully created, 0 will be returned. If the creation fails, -1 will be returned.

Let’s take a look at how to write the function of the write-side process: According to the above template, first create the FIFO file, and then perform the read and write operations. Finally, remember to keep the window and not allow the program to exit directly as soon as it is executed, so here is the writing process getchar() is used, and do not close the file during the writing process, otherwise the data will not be read.

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>

int main()
{
    int result = mkfifo("./myfifo",0777);
    if(result<0)
    {
        perror("mkfifo error\\
");
        return -1;
    }
    int fd=open("./myfifo",O_RDWR);
    if(fd<0)
    {
        perror("open error\\
");
        return -1;
    }
    write(fd,"HELLO WORLD",sizeof("HELLO WORLD"));
    getchar();
    return 0;
}

The reading end is relatively simple, as long as the operation of reading a file is enough:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<signal.h>
#include<sys/stat.h>

int main()
{
    int fd=open("./myfifo",O_RDWR);
    if(fd<0)
    {
        perror("open error\\
");
        return -1;
    i}
    char buf[BUFSIZ];
    memset(buf,0,sizeof(buf));
    int ret=read(fd,buf,sizeof(buf));
    printf("Received message: %s,size=%d\\
",buf,ret);
    close(fd);
    return 0;
}

Note that the file needs to be closed at the end of the reading process, and there are many references to the header files here, which are easy to miss.

When running, we need to open two windows, one to open the reading side and one to open the writing side. However, once running once, there will be an additional myfifo file in the current directory. This file must be deleted the next time it is executed to continue running. This way It seems very complicated, so we need to add judgment in the writing process, and create the file when it does not exist. Here we need to use the access function to judge whether the file exists. If the file exists, the function will return 0, otherwise If it exists, -1 will be returned.

Make the following modifications to the writing process and add a judgment condition. The second parameter F_OK of the access function is specifically used to judge whether the file exists. Of course, the access function can also be used to determine what permissions a file has. Readers can learn more by consulting the manual.

 if(access("./myfifo",F_OK)!=0)
    {
        int result = mkfifo("./myfifo",0777);
        if(result<0)
        {
            perror("mkfifo error\\
");
            return -1;
        }
    }

After modification, there is no need to delete it slowly, and it can run normally.

5. Shared mapping area mmap

The shared mapping area maps a disk file to a buffer in the storage space. In this way, fetching data from the buffer is equivalent to reading the corresponding number of bytes from the file; writing data to the buffer will determine whether to write the written content to the file based on the parameters passed in.

The above is the function prototype of mmap. Note that when using it, the header file must be added with ; the first parameter is an address space in the memory, which is generally set to NULL, indicating that the operating system will allocate a block to us. Buffer, the second length is the size of the file we open, which is usually obtained through the lseek function. prot represents the protection method of the mapping area. The most commonly used PROT_READ PROT_WRITE or two together are PROT_READ | PROT_WRITE; flags represents the characteristics of the mapping area. The most commonly used one is MAP_SHARED, which means that the content written to the buffer will be associated with the file. MAP_PRIVATE has the opposite effect of the former. The content written to the buffer will not be written back to the file; fd is the descriptor of the open file. , offset is the offset at the beginning of the file, which must be an integer multiple of 4k, and is usually set to 0, which means mapping starts from the beginning of the file. When the mapping area is created successfully, 0 will be returned. If the creation fails, MAP_FAILED will be returned. Note that the return value here must be checked, because the creation of the memory mapping area may fail. Let’s look at mmap to implement communication between unrelated processes:

Write process:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/mman.h>

int main()
{
    int fd=open("./txt.log",O_RDWR);
    if(fd<0)
    {
        perror("open error\\
");
        return -1;
    }
    int len=lseek(fd,0,SEEK_END);
    void *addr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(addr==MAP_FAILED)
    {
        perror("mmap errro\\
");
        return -1;
    }
    memcpy(addr,"HELLO WORLD",sizeof("HELLO WORLD"));
    getchar();
    close(fd);
    return 0;
}

Reading process:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/mman.h>

int main()
{
    int fd=open("./txt.log",O_RDWR);
    if(fd<0)
    {
        perror("open error\\
");
        return -1;
    }
    int len=lseek(fd,0,SEEK_END);
    void *addr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(addr==MAP_FAILED)
    {
        perror("mmap errro\\
");
        return -1;
    }
    char *str=(char*)addr;
    printf("Read content: %s\\
",str);
    close(fd);
    return 0;
}

At first glance, I found that the codes of the two processes are basically the same. Only the subsequent read and write operations are different. Note that the opened file must already exist and contain content, but the size of the file cannot be 0.

Here we are Using MAP_SHARED, we will find that the file has been modified (it was randomly written in the beginning).

Let’s look at mmap to implement communication between parent and child processes:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/mman.h>

int main()
{
    int fd = open("./txt.log",O_RDWR);
   if(fd<0)
   {
        perror("open error\\
");
        return -1;
   }
   int len=lseek(fd,0,SEEK_END);
   void *addr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
   if(addr==MAP_FAILED)
   {
        perror("mmap error\\
");
        return -1;
   }

   pid_t pid=fork();
   if(pid<0)
   {
        perror("fork error\\
");
        return -1;
   }
   else if(pid>0)
   {
        memcpy(addr,"haha",sizeof("haha"));
        wait(NULL);
   }
   else if(pid==0)
   {
        sleep(1);
        char *str = (char*)addr;
        printf("Data read: %s\\
",str);
   }

    return 0;
}

The mapping area must be established first, and then communication can be carried out. Note that communication cannot be completed using MAP_PRIVATE here. MAP_SHARED must be used. sleep(1) at the child process ensures that the parent process executes first, and the wait function of the parent process ensures that zombies will not be generated. Process, these are some small points that need attention.

mmap can also be used as an anonymous mapping area: MAP_ANONYMOUS needs to be used together with MAP_SHARED. Use -1 for fd.

void *addr=mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);

Just modify the line of code that creates the mapping area. This anonymous mapping method is still very commonly used. After all, there is no need to create a file and the operation is relatively simple. Note that this can only be used for communication between processes that are related by blood.

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. Cloud native entry-level skills treeHomepageOverview 16674 people are learning the system