【Linux】--Inter-process communication

Table of contents

1. Introduction to inter-process communication

2. Pipeline

1. What is a pipe?

2. Redirects and Pipelines

(1) Why do pipelines exist?

(2) The difference between redirection and pipeline

3. Anonymous pipe

(1) Principle of anonymous pipeline 

(2) Understand anonymous pipes from the perspective of file descriptors

(3) Create an anonymous pipeline

(4) Anonymous pipe reading and writing rules

(5) Characteristics of anonymous pipes

(6) Four special situations of anonymous pipes

(7) Anonymous pipe size

4. Named pipes

(1) Principle of named pipes 

(2) Create a named pipe

(3) Named pipe data will not be flushed to disk

5. The difference between anonymous pipes and named pipes

3. System V IPC

1.System V standard

2. Shared memory

(1) Principle 

(2) Steps

(3) Function 

shmget

shmctl

shmat

shmdt

(4) Use

3. The difference between shared memory and pipelines 

4. Message queue 

1.Principle 

2.Data structure

3. Steps

4. Function 

(1)msgget

(2)msgctl

(3)msgsnd

(4)msgrcv

5. Semaphore

1.Principle 

2.Data structure

3. Function

(1)semget

(2)semctl

(3)smop

6. Summary of System V IPC


1. Introduction to inter-process communication

        The previously learned processes all run independently without interfering with each other, and there is no coordination between processes. However, there are many scenarios where processes need to collaborate with each other. Since processes are written by programmers, the collaboration between processes is essentially collaboration between programmers. For example, one programmer gets data from the database, and another The programmer needs to format the data obtained from the database and write it into a specific format. There is also a programmer who performs statistics based on the formatted data. If these workloads are treated as opinion work, if these three links If there is an error in any link, then the work will not continue. It is necessary to investigate which link has gone wrong one by one, which is time-consuming and inefficient.

        Therefore, this work can be divided into 3 parts, and let 3 different processes do it: 1 process to get data from the database, 1 process to do data formatting, and 1 process to do data analysis. This achieves decoupling using processes at the business level. Once there is a problem with data retrieval, go to the process of retrieving the data. Once there is a problem with formatting, go to the formatting process. Once there is a problem with data analysis, go to the data analysis process. Decoupling at the business level can increase the maintainability of the code. This is the collaboration between processes. For example, to filter out lines containing the letter 'i' in a file:

cat fdProcess.c runs as a process. The core job is just to print data. Use grep to filter lines containing the letter 'i'. The data source is handed over to grep through a pipe from the previous process cat fdProcess.c. This is called collaboration.

Even if it is a parent-child process, the code and data of the shared process must be separated when writing, and written using copy-on-write. The cost of exchanging data between two independent processes is very high. Each of them cannot even see the address space where the other party saves the data. Because independent processes use independent process address spaces and page tables are mapped to different physical memories, they cannot see each other. to the other party’s data, so to complete inter-process communication, it cannot be solved only at the application layer. The operating system must also be involved, and the operating system must design a communication method.

The essence of communication is to transfer data. This data requires one process to put it into the public resource, and another process to take it out from the public resource, and the public resource also needs the ability to temporarily store data. This public resource definitely does not belong to these two processes, because the processes are independent. If this public resource belongs to process A, then process B cannot see it:

As can be seen from the above figure, there are the following three methods of inter-process communication. The purpose is to allow different processes to see the same resource:

  • pipeline
  • System V interprocess communication
  • POSIX inter-process communication 

At the same time, you need to understand the following concepts first:

Data transfer : One process needs to send its data to another process

Resource sharing : sharing the same resources between multiple processes.

Notification event : A process needs to send a message to another process or a group of processes to notify it (them) that a certain event has occurred (such as notifying the parent process when the process terminates).

Process control : Some processes want to completely control the execution of another process (such as the Debug process). At this time, the control process hopes to intercept all traps and exceptions of another process and be able to know its status changes in time.

2. Pipeline

1. What is a pipe?

Pipes are Unix's oldest form of inter-process communication. The data flow that connects one process to another is called a pipe. Linux pipes use the vertical bar '|' to connect multiple commands, which is called the pipe character by the vertical bar '|'.

When a pipe is set between two commands, the output of the command on the left side of the pipe symbol '|' becomes the input of the command on the right side. As long as the first command writes to standard output and the second command reads from standard input, the two commands can form a pipe. Most Linux commands can be used to form pipes.

As shown below, for the command cat fdProcess.c|grep -i 'i', the process cat fdProcess.c before the pipe character '|' is the standard input process, and the process after the pipe character '|' is grep -i 'i' The standard output process, the first command writes to the standard output, and the second command reads from the standard input. These two commands form a pipe, and the pipe acts on the kernel.

If there is no pipeline, then these two commands are executed twice. Therefore, the same effect can be achieved using pipeline execution. For some backup compression and copying requirements, the command can avoid the creation of temporary files. 

Pipeline features:

  • The command syntax is compact and simple to use.
  • Pipes chain multiple commands together to complete complex tasks.
  • The standard error output from the pipe is mixed together.

2. Redirects and Pipelines

(1) Why do pipelines exist?

Since there are redirects, why do we need pipes? For example, the following command uses redirection to put the output of the executable program process1 into file:

process1 > file

But what if you want the output of the executable program process1 to be passed to the executable program process2? need:

process1 > temp && process2 < temp

This command does 3 steps:

  • Run the process named process1 
  • Save the output to a file named temp
  • Run the program named process2 and pretend that the user enters the contents of temp on the keyboard.

Have you found that it is very troublesome to do this, as it requires both creating a temporary file and requiring the user to input on the keyboard, but the pipeline is very simple:

process1 | process2

The effect is the same as the command process1 > temp && process2 < temp.

(2) The difference between redirection and pipeline

Pipes also have a redirection effect because they change the input and output direction of data. A redirect uses "">" to connect a file to a command, using a file to receive the output of the command, while a pipe uses "I" to connect a command to a command, using a second command to receive the output of the first command.

Be careful when using redirection. If you type the following two commands in succession:

cd /usr/bin
ls > less

The first command switches the current directory to the directory where most programs are stored, and the second command tells the shell to use the output of the ls command to rewrite the file less. Because the /usr/bin directory already contains a file named less, the second command rewrites the less program with the text output by ls, thereby destroying the less program in the file system, which destroys the less file. This is a lesson in using redirection operators to incorrectly rewrite files, so be careful when using redirections.

Pipes are divided into anonymous pipes and named pipes.

3. Anonymous pipe

(1) Principle of anonymous pipeline 

Anonymous pipes are limited to communication between local parent and child processes and do not support communication between two processes across a network.

When the process operates a file, it finds the file through the file descriptor. If it needs to read the file, it directly executes the read method. After using fork to create a child process, the child process will have its own PCB, and the struct file file descriptor table structure pointed to by the parent process also needs to be copied to the child process.

 This is because:

  • The file_struct structure belongs to the process, because file_struct allows the process to see how many files have been opened and the relationship between files, so file_struct belongs to the process. file_struct belongs to the process, then it must belong to the parent process. When creating a child process, this file_struct structure must also be copied for the child process. Because processes are independent, kernel data structures must also remain independent.
  • If the child process also sees the files of the parent process, then when the files of the parent process are read and written, the buffer will also be seen by the child process, which will not ensure process independence.

Therefore, the operating system will also copy this structure to the child process:

File-based communication is called a pipe. Process, struct_file, buffer, operation methods, etc. are all provided by the operating system. Files do not belong to the process but to the operating system. The parent process opens the file first and lets the child process inherit it. Although they are structurally independent, they point to the same file. One writes to the file and the other reads from the file. The two processes see the same public resource, which satisfies the process prerequisite for communication.

(2) Understand anonymous pipes from the perspective of file descriptors

  • Parent process creates pipe

The pipe can be regarded as the kernel buffer of the file. When the parent process creates the pipe, it opens the same file in read mode and write mode respectively:

  •  The parent process forks the child process

 When the parent process creates a child process, all the file descriptor table information of the parent process will be inherited by the child process. Although the parent and child processes each have independent file descriptors, the contents are the same, so both the parent and child processes can see the files that have been opened. The reading end and writing end perform reading and writing, but the pipeline can only communicate in one direction, and can only have one reading end and one writing end.

Therefore, the parent process has two file descriptors at the beginning, one reading end and one writing end. In this way, after the child process inherits and copies the file descriptor of the parent process, it also has a reading end and a writing end. Otherwise, if the parent process only has a reading end and no writing end at the beginning, then the child process also only has a reading end and no writing end, then the two reading ends cannot read and write.

  •  The parent process closes the reading end (writing end), and the child process closes the writing end (reading end)

 As for who closes the reading end and the writing end of the parent and child processes, it depends on whether the parent process reads or the child process reads. Now let's take a look at the parent process writing and the child process reading. Now close the reading end of the parent process and the writing end of the child process:

(3) Create an anonymous pipeline

Step 1: The parent process uses the pipe function to create the pipe

 #include <unistd.h>

 int pipe(int pipefd[2]);

Parameters: pipefd file descriptor array, the number of elements is 2, it is an output parameter, through this parameter, the two open file descriptors are read. Among them, pipefd[0] is a read operation, pipefd[1] is a write operation, and the order cannot be reversed.

Return value: Returns 0 on success, -1 on failure.

Now let's create a pipeline:

#include<stdio.h>
#include<unistd.h>

int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        return 0;
}

The execution results are as follows:

 You can see that the file descriptors are 3 and 4 respectively, because 0, 1, and 2 are all occupied by standard input, standard output, and standard error:

Step 2: The parent process forks the child process

#include<stdio.h>
#include<unistd.h>

int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败了
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        if(fork() == 0)//子进程
        {

        }

        //父进程

        return 0;
}

Step 3: Create a one-way channel

Now if you want the parent process to read and the child process to write, then you need to close the writing end of the parent process and the reading end of the child process, that is, close the write file descriptor of the parent process and the read file descriptor of the child process. In order to prevent the child process from continuing to execute backwards after closing the read file descriptor, use the eixt function to terminate.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败了
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        if(fork() == 0)//子进程
        {
                close(pipefd[0]);//子进程关闭读文件描述符
                exit(0);
        }

        //父进程
        close(pipefd[1]);//父进程关闭写文件描述符

        return 0;
}

Now the parent-child process has been established, and the parent-child process has seen the same resource. Now to let the child process write, you need to call the write method. To let the parent process read, you need to call the read method. For the use of write and read methods, please refer to the article [ Linux] - Basic IO and dynamic and static libraries Chapter 1, Section 1 Contents  of Chapter 1, Section 1.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败了
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        if(fork() == 0)//子进程
        {
                close(pipefd[0]);//子进程关闭读文件描述符

                const char *string_write = "lunch ";
                while(1)
                {
                        write(pipefd[1],string_write,strlen(string_write));//子进程向文件缓冲区写,pipe只要有缓冲区就一直写入
                }
                close(pipefd[1]);

                exit(0);
        }

        //父进程
        close(pipefd[1]);//父进程关闭写文件描述符

        while(1)
        {
                sleep(1);
                char string_read[64] ={0};
                size_t readLength = read(pipefd[0],string_read,sizeof(string_read));//父进程从文件缓冲区读,pipe只要有缓冲区就一直读
                if(readLength == 0)//读到内容为空
                {
                        printf("child quit...\n");
                        break;
                }
                else if(readLength > 0)//读到了正常内容
                {
                        string_read[readLength] = 0;
                        printf("child write# %s\n",string_read);
                }
                else//读出错
                {
                        printf("read error...\n");
                        break;
                }
                close(pipefd[0]);

        }
        return 0;
}

 You can see the execution results as follows, the child process writes and the parent process reads:

 For byte streams, as long as there is data in the buffer, all data in the buffer will be read out, one byte at a time.

(4) Anonymous pipe reading and writing rules

The pipe2 function is similar to the pipe function and is also used to create anonymous pipes. Its function prototype is as follows:

int pipe2(int pipefd[2], int flags);

For flags: 

  • When there is no data to read

O_NONBLOCK disable: The read call blocks, 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 the write ends of all pipes are closed, read returns 0
  • If the file descriptors corresponding to all pipe readers are closed, the write operation will generate the signal SIGPIPE, which may cause the write process to exit.
  • When the amount of data to be written <= PIPE_BUF, Linux will guarantee the atomicity of the write.
  • When the amount of data to be written is >PIPE_BUF, Linux will no longer guarantee the atomicity of writing.

(5) Characteristics of anonymous pipes

  • It can only be used for communication between processes with a common ancestor; usually, a pipe is created by a process, and then the process calls fork, and the pipe can be used to communicate between the parent and child processes (processes with a kinship relationship, ancestor-grandson processes Also possible)
  • The pipeline provides streaming services and atomic writing (the data read by the reader is arbitrary, the bottom layer does not clearly segment the data, and the message segments are uncertain, so it is a streaming service)
  • When the parent-child process exits, the pipe file is released, so the life cycle of the pipe changes with the process.
  • The kernel will synchronize and exclude pipeline operations
  • The pipeline is half-duplex, and data can only flow in one direction; when communication between two parties is required, two pipelines need to be established.

(6) Four special situations of anonymous pipes

  • The reading end does not read or reads slowly, and the writing end has to wait for the reading end.
  • The read end is closed and the write end is terminated directly after receiving the SIGPIPE signal.
  • The writing end does not write or writes slowly, and the reading end has to wait for the writing end.
  • The writing end is closed. The reading end reads the data inside the pipe and then reads it again. It will read 0, indicating that the end of the file has been read.

(7) Anonymous pipe size

 If you let the child process write one character into the pipe each time in an infinite loop and count, the parent process will not read data from the pipe. When the count no longer increases, the count value will be the size of the pipe:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败了
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        if(fork() == 0)//子进程
        {
                close(pipefd[0]);//子进程关闭读文件描述符

                int count = 0;
                while(1)
                {
                        write(pipefd[1],"a",1);
                        count++;
                        printf("count:%d\n",count);
                }

                close(pipefd[1]);
                exit(0);
        }

        //父进程
        close(pipefd[1]);//父进程关闭写文件描述符
        while(1)//父进程不读取
        {
                sleep(1);
        }
        return 0;
}

 The running results are as follows, printed from 1 to 65536:

 This means the pipe size is 65536B = 64KB . This also shows that if the writing end writes full data to the pipe, the writing end will stop writing and waits for the reading end to read; similarly, if the reading end has finished reading the pipe data and the pipe has no data, then the reading end will not read. , waiting for the write end to write.

 After the pipe is filled up by the writing end, the reading end needs to take away the data. If it takes away 4KB at a time, the writing end will write. Otherwise, the writing end will not be triggered to write. Why is it 4KB? When letting the parent process read, the write end only writes when the size of the array storing data increases from 1KB to 4KB:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
        int pipefd[2] = {0};
        if(pipe(pipefd) != 0)//匿名管道创建失败了
        {
                perror("pipe error!");
                return 1;
        }

        printf("pipefd[0]:%d\n",pipefd[0]);
        printf("pipefd[1]:%d\n",pipefd[1]);

        if(fork() == 0)//子进程
        {
                close(pipefd[0]);//子进程关闭读文件描述符

                const char *string_write = "lunch ";
                int count = 0;
                while(1)
                {
                        //write(pipefd[1],string_write,strlen(string_write));//子进程向文件缓冲区写,pipe只要有缓冲区就一直写入
                        write(pipefd[1],"a",1);
                        count++;
                        printf("count:%d\n",count);
                }

                close(pipefd[1]);
                exit(0);
        }

        //父进程
        close(pipefd[1]);//父进程关闭写文件描述符
		while(1)
        {
                sleep(3);
                char string_read[1024*4+1] ={0};//按照1024*1   1024*2   1024*3   1024*4向上递增
                size_t readLength = read(pipefd[0],string_read,sizeof(string_read));//父进程从文件缓冲区读,pipe只要有缓冲区就一直读
                printf("readLength = %d\n",readLength);
                string_read[readLength] = 0;
                printf("father take:%c\n",string_read[0]);
		}
        return 0;
}

It can be seen that the count of characters written in the pipe initially increased to 65536B. After the parent process read 4KB, the child process continued to write. Each time it was written, the count count would be ++:

 Why does the writing end write when 4KB is read, but not when 1KB, 2KB, and 3KB are read? This is because we need to ensure the atomicity of writing and reading: if the writing end wakes up before reading enough 4KB, then the writing end will come to write, which means that while the writing end is writing, The reader has to come to read, which violates the principle of half-duplex communication in the pipeline and cannot read and write at the same time. In the same way, if the writing side writes very slowly and the reading side reads very fast, when there is no data in the buffer, it will wait for the data to be written in before the reading side reads again. Therefore, synchronization must be ensured.

4. Named pipes

(1) Principle of named pipes 

 Anonymous pipes are used for communication between processes that are related by blood. So how do processes that are not related by blood communicate with each other? This requires the use of named pipes. Named pipes are special files that use FIFO (First In First Out) for communication.

 How to let two unrelated processes that are not related by blood see the same resource provided by the operating system? For the file system, when process A opens the disk file, writes data to the disk, closes the disk file after writing, process B opens the disk file again and reads the data:

But this is a bit slow, because process A opens the file in the memory and establishes memory-related data structures and buffers for the file. Process B also opens the same file in the memory. In this way, one is opened by reading, and the other is opened by reading. Open by writing, the process can write to this memory file, and process B can read from this memory file. The data will not be refreshed to the disk for the time being, otherwise the efficiency will be reduced. This is memory-based communication between data, then A Process and process B can communicate between unrelated processes through this memory file.

How do unrelated processes A and B see the same resource? Path + file name can uniquely specify a file, so that process A and process B can open the same file. Now you need 1 file that meets the following requirements:

  • When the file is opened, the data is not flushed to disk, but temporary data is saved.
  • This file must also have a corresponding file name on disk

Only named pipes meet these conditions. And this file has a name, which is achieved by determining the uniqueness by path + file name:

(2) Create a named pipe

There are two ways to create named pipes: 

  • Created through the mkfifo command
mkfifo name

For example, create a pipeline file named testFifo:

 You can see that the file type is p, and p indicates that this is a pipeline file. After creating the named pipe file, you can communicate:

 Echo and cat are two different instructions, but they run as two processes. The message on the left is printed to the screen on the right. One process writes its own content into the named pipe file, and the data is transferred through the named pipe file. passed to another process.

  • Created through the mkfifo function

The function of the mkfifo function is to generate a special file for FIFO, that is, a named pipe 

 #include <sys/types.h>
 #include <sys/stat.h>

 int mkfifo(const char *pathname, mode_t mode);

pathname: file name

mode: the default permissions of the pipe, which can be set with umask

Return value: 0 on success, -1 on failure

Now use the mkfifo function to create a named pipe, server.c creates the pipe file, and assign permissions to the pipe file:

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>

#define fifo_file ./fifo_file

int main()
{
        if(mkfifo(fifo_file,0666) < 0)//创建一个命名管道
        {
                perror("mkfifo");
                return 1;
        }

        return 0;
}

client.c does nothing for now:

#include<stdio.h>

int main()
{
        return 0;
}

The Makefile generates two executable files at once:

.PHONY:all
all:client server

client:client.c
        gcc -o $@ $^

server:server.c
        gcc -o $@ $^

.PHONY:clean
clean:
        rm -rf client server fifo_file

After compilation, two executable programs are generated:

Now the communication wants the client and server executable programs to transfer details to each other. Then the client and server executable programs run as two processes, and they are two unrelated processes and have no blood relationship.

After executing the srver class execution program, the fifo_file named pipe is generated. The file type is p, but the permission is 644, not 666:

 This is because the parameter mode of the fifo file is affected by the system umask. You can see that the value of Umask is 2:

 Then it can be seen that mode = mode & ~umask (666&~002), if you modify the value of umask, for example, clear umask to 0 when creating a named pipe file:

server.c

#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#define fifo_file "./fifo_file"

int main()
{
        umask(0);//将umask清0
        if(mkfifo(fifo_file,0666) < 0)//创建一个命名管道
        {
                perror("mkfifo");
                return 1;
        }

        int fd = open(fifo_file,O_RDONLY);
        if(fd < 0)
        {
                perror("open");
                return 2;
        }

        while(1)
        {
                char buffer[64] = {0};
                ssize_t read_length = read(fd,buffer,sizeof(buffer)-1);
                if(read_length > 0)//读取成功
                {
                        buffer[read_length-1] = 0;
                        printf("client # %s\n",buffer);
                }
                else if(read_length == 0)
                {
                        printf("client quit\n");
                }
                else
                {
                        perror("read");
                        break;
                }
        }
        close(fd);
        return 0;
}

At this time, you can see that the permissions of the named pipe file have changed to 666:

 For client and server processes, if you want the server to read and the client to write, it is not recommended to use the c/c++ interface. There is a buffer, but the system call does not have a buffer. It is recommended to use the system call interface. The client uses the system call to receive the standard input and write to In the named pipe file:

client.c 

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

#define fifo_file "./fifo_file"

//client不需要创建命名管道文件,只需要获取就可以了
int main()
{
        int fd = open(fifo_file,O_WRONLY);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }

        while(1)
        {
                printf("请输入# ");//client的输入提示
                fflush(stdout);//刷新一下标准输出
                char buffer[64] = {0};
                //先把数据从标准输入拿到client进程内部
                ssize_t read_length = read(0,buffer,sizeof(buffer)-1);
                if(read_length > 0)
                {
                        buffer[read_length-1] = 0;

                        //拿到了数据
                        write(fd,buffer,strlen(buffer));
                }
        }
        close(fd);
        return 0;
}

 Now to run, you must first let the server run to create a named pipe, and then run the client, then you can write data to the client:

 As can be seen from the above, for two processes that do not want to be managed, one process sends a message to the other process through the named pipe. Therefore, once you have a named pipe, you only need to let the processes on both sides of the communication operate according to the file. Since named pipes are also based on byte streams, in fact, when transmitting information, both communicating parties need to customize the "protocol".

Now let the client control the server and let the server perform tasks. You can let the server execute program replacement. For example, when the client receives the standard input and writes the string "show" to the named pipe file, the ls command will be executed:

server.c

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

#define fifo_file "./fifo_file"

int main()
{
        umask(0);
        if(mkfifo(fifo_file,0666) < 0)//创建一个命名管道
        {
                perror("mkfifo");
                return 1;
        }

        int fd = open(fifo_file,O_RDONLY);
        if(fd < 0)
        {
                perror("open");
                return 2;
        }

        //业务逻辑,进行读写
        while(1)
        {
                char buffer[64] = {0};
                ssize_t read_length = read(fd,buffer,sizeof(buffer)-1);
                if(read_length > 0)//读取成功
                {
                        buffer[read_length] = 0;
                        if(strcmp(buffer,"show") == 0)
                        {
                                printf("the string is show\n");
                                if(fork() == 0)
                                {
                                        execl("/usr/bin/ls","ls","-l",NULL);//程序替换
                                        exit(1);
                                }
                                waitpid(-1,NULL,0);
                        }
                        else
                        {
                                printf("client # %s\n",buffer);
                        }
                }
                else if(read_length == 0)
                {
                        printf("client quit\n");
                }
                else
                {
                        perror("read");
                        break;
                }
        }
        close(fd);
        return 0;
}

client.c

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

#define fifo_file "./fifo_file"

//client不需要创建命名管道文件,只需要获取就可以了
int main()
{
        int fd = open(fifo_file,O_WRONLY);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        while(1)
        {
                printf("请输入# ");//client的输入提示
                fflush(stdout);//刷新一下标准输出
                char buffer[64] = {0};
                //先把数据从标准输入拿到client进程内部
                ssize_t read_length = read(0,buffer,sizeof(buffer)-1);
                if(read_length > 0)
                {
                        buffer[read_length - 1] = 0;

                        //拿到了数据
                        write(fd,buffer,strlen(buffer));

                }
        }
        close(fd);
        return 0;
}

Now to run, you must first let the server run to create a named pipe, and then run the client. Then you can write data to the client. After the client enters "show", the server will display the contents of ls:

 It can be seen that data is transferred from one process to another through named pipes, and it is also possible to allow one process to control another process to perform tasks, achieving the purpose of inter-process communication.

(3) Named pipe data will not be flushed to disk

If the server process is allowed to read every 20 seconds, and the client keeps sending messages to the pipe, then the data can only be in the pipe file:

server.c

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

#define fifo_file "./fifo_file"

int main()
{
        umask(0);
        if(mkfifo(fifo_file,0666) < 0)//创建一个命名管道
        {
                perror("mkfifo");
                return 1;
        }

        int fd = open(fifo_file,O_RDONLY);
        if(fd < 0)
        {
                perror("open");
                return 2;
        }

        //业务逻辑,进行读写
        while(1)
        {
                char buffer[64] = {0};
                sleep(20);//等待20秒再读
                ssize_t read_length = read(fd,buffer,sizeof(buffer)-1);
                if(read_length > 0)//读取成功
                {
                        buffer[read_length] = 0;
                        if(strcmp(buffer,"show") == 0)
                        {
                                printf("the string is show\n");
                                if(fork() == 0)
                                {
                                        execl("/usr/bin/ls","ls","-l",NULL);//程序替换
                                        exit(1);
                                }
                                waitpid(-1,NULL,0);
                        }
                        else
                        {
                                printf("client # %s\n",buffer);
                        }
                }
                else if(read_length == 0)
                {
                        printf("client quit\n");
                }
                else
                {
                        perror("read");
                        break;
                }
        }
        close(fd);
        return 0;
}

client.c does not need to be modified:

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

#define fifo_file "./fifo_file"

//client不需要创建命名管道文件,只需要获取就可以了
int main()
{
        int fd = open(fifo_file,O_WRONLY);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        while(1)
        {
                printf("请输入# ");//client的输入提示
                fflush(stdout);//刷新一下标准输出
                char buffer[64] = {0};
                //先把数据从标准输入拿到client进程内部
                ssize_t read_length = read(0,buffer,sizeof(buffer)-1);
                if(read_length > 0)
                {
                        buffer[read_length - 1] = 0;

                        //拿到了数据
                        write(fd,buffer,strlen(buffer));

                }
        }
        close(fd);
        return 0;
}

During these 20 seconds, the client writes to the named pipe, but the server does not read from the named pipe. Logically speaking, there is content in the named pipe and the size is not 0. However, within these 20 seconds, it is found that the size of the fifo_file of the named pipe is 0. , which means that the data of the named pipe will not be flushed to the disk due to efficiency issues.

5. The difference between anonymous pipes and named pipes

There are different ways to create and open:

  • Anonymous pipes are created and opened by the pipe function
  • Named pipes are created by the mkfifo function and opened by the open function

There will be the same semantics later

3. System V IPC

1.System V standard

System V is a standard for inter-process communication at the operating system level. The system V standard provides users with a system call interface. Inter-process communication can be completed as long as the user uses the system calls it provides. IPC (Inter-Process Communication) is inter-process communication. System V IPC does not use file-based communication.

How to provide the system call interface to users? System V is part of the operating system kernel and is a communication solution provided for multiple processes in the operating system. But the operating system does not trust any user and uses system calls to provide functions to users. Therefore, System V inter-process communication has an interface specifically used for communication: System call (system call)

This requires the development of a standard for inter-process communication within the same host: System V. System V inter-process communication is divided into three types:

  • System V message queue
  • System V shared memory
  • System V semaphore

The message queue model implements communication by exchanging messages between cooperating processes. The shared memory model establishes a memory area shared by cooperating processes. The processes exchange information by reading or writing data to this shared area. The following is the communication model for message queues and shared memory:

 The implementation of message queues often uses system calls, so it takes more time for the kernel to intervene. However, shared memory only requires system calls when establishing a shared memory area. Once shared memory is established, all accesses are regular memory accesses without the help of the kernel. .

Since message queues and shared memory are used to deliver messages, semaphores are used to implement inter-process synchronization and mutual exclusion. Therefore, we mainly look at the more efficient shared memory among inter-process communication methods.

2. Shared memory

(1) Principle 

Map the requested shared memory into the address space of different processes. There are process A and process B. Process A finds the code and data of process A through page table mapping. Similarly, process B also finds the code and data of process B through page table mapping. Since the data structures of the two processes are independent of each other, and the physical The code and data in memory are also independent of each other, so the two processes will not interfere with each other.

After a shared memory space is opened in physical memory, the opened memory space needs to be mapped to the process address space through the page table through system calls. Then the shared memory also has a virtual address in the process address space, which is called a shared memory mapping area. Then the shared memory The virtual address of the mapping area is filled in the page table, so that a corresponding relationship is established between the virtual address and the physical address of the shared memory, and each process also sees the same resource of the shared memory.

 The above process is also the process of attaching the process to the shared memory space. There may be multiple shared memories in the operating system, so the operating system needs to manage these shared memories. Management should be described first and then organized.

 How to ensure that multiple processes can see the same shared memory?

 Shared memory must have a unique identification ID so that different processes can identify the same shared memory resource. Then this ID must be in the data structure describing the shared memory.

(2) Steps

The process of using shared memory can be summarized:

  • Create shared memory
  • Association (hook)
  • Disassociate (disconnect)
  • Release shared memory

(3) Function 

shmget

Use the shmget function to create shared memory to apply for a shared memory space:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
key Generated through ftok function
size It is recommended to be an integer multiple of 4KB. In order to increase the speed of data exchange between memory and hard disk, the operating system uses 4KB as the unit.
shmflg There are multiple hmflg flags. Just understand the two most commonly used flags, IPC_CREAT and IPC_EXCL.
return value The shared memory address is returned on success, -1 on failure.

 Among them, the first parameter key of shmget is generated through the ftok function:

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
pathname Customized file path name
proj_id Serial number, the lower 8 bits are used, non-zero
return value The returned key will be set into the shared memory in the kernel data structure.

There are multiple shmflg flags in the third parameter of shmget. Just understand the two most commonly used flags, IPC_CREAT and IPC_EXCL:

 After creating shared memory, how to check the shared memory? The ipcs command is used to report the status of inter-process communication facilities, where:

ipcs -m //查看共享内存(Shared Memory Segments)
ipcs -q //查看消息队列(Message Queue)
ipcs -s //查看信号量(Semaphore Arrays)

shmctl

After using the shared memory, if it is not deleted, the shared memory will remain until the system is restarted. How to delete it? There are two ways to delete, one is to delete by command:

ipcrm -m shmid

The key is only used for unique identification at the system level and cannot be used to manage shared memory. And shmid is the id returned by the operating system to the user, which is used for shared memory management at the user level, so ipcrm is a user-level command. The above is the command to delete, so how to delete the shared memory in the code?

 Therefore, another way to delete shared memory is to use the shmctl function to control shared memory:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid The id returned by the operating system to the user
cmd options, there are multiple
buf data structuredata structure type pointer
return value Returns 0 if deletion is successful, -1 if failed.

There are multiple cmd options:

IPC_STAT Copy the shmid kernel data structure to the shmid_ds structure pointed to by buf
IPC_SET Write the values ​​of some members of the shmid_ds structure pointed to by buf to the kernel data structure related to this shared memory segment, and update its shm_ctime member at the same time
IPC_RMID Delete shared memory

The third parameter​​​ 

 Among them, the shmid_ds data structure is as follows:

struct shmid_ds
{
	struct ipc_perm shm_perm;    /* Ownership and permissions */
	size_t          shm_segsz;   /* Size of segment (bytes) */
	time_t          shm_atime;   /* Last attach time */
	time_t          shm_dtime;   /* Last detach time */
	time_t          shm_ctime;   /* Last change time */
	pid_t           shm_cpid;    /* PID of creator */
	pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
	shmatt_t        shm_nattch;  /* No. of current attaches */
	...
};

shmat

 Use shmat to map shared memory to the address space of the calling process (association: add page table entries for the mapping relationship between shared memory and process address space)

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid The id returned by the operating system to the user
shmaddr Indicates which ranges of the process address space the shared memory is attached to 
shmflg There are many. Just understand the two most commonly used flags, IPC_CREAT and IPC_EXCL. The same as the shmflg flag of the shmget function. Just set it to 0 here.
return value Returns the virtual address of the shared memory attached to the process address space, which is the same as the return value of malloc when applying for heap space.

shmdt

 shmdt is used to disconnect the mapping between shared memory and process address space (deassociation: delete the page table entry of the mapping relationship between shared memory and process address space, instead of releasing the shared memory)

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);
shmaddr The shared memory address to be disconnected must be the same as shmaddr parameter of shmat.
return value Returns 0 if disconnected successfully, -1 if failed.

(4) Use

Two processes use shared memory to communicate, which requires the steps of creation, association, disassociation, and deletion. Now use the above function to communicate between the server and client processes.

comm.h to include header files

#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>

#define PATH_NAME "/home/delia/linux/20230627-sharedMemory/shared/server.c"  //ftok的路径
#define PROJ_ID 0x6666
#define SIZE 4097

The server side needs to generate a unique ID, create shared memory, associate shared memory, unassociate shared memory, and delete shared memory:

server.c

#include "comm.h"

int main()
{
        key_t key = ftok(PATH_NAME,PROJ_ID);//生成唯一ID保证在统一系统当中
找到共享内存
        if(key < 0)
        {
                perror("fork");
                return 1;
        }

        //1.创建共享内存
        int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//共享内存不存在就创建,权限为666,共享内存可以用文件权限来约束

        if(shmid < 0)
        {
                perror("shmget");
                return 2;
        }

        printf("key = %u,shmid = %d\n",key,shmid);

        sleep(1);

        //2.关联
        char *mem = shmat(shmid,NULL,0);
        printf("attaches shm success\n");
        sleep(15);

        //通信逻辑
        while(1)
        {
                sleep(1);
                //printf("%s\n",mem);
        }

        //3.去关联
        shmdt(mem);
        printf("detaches shm success\n");

        //4.删除共享内存
        shmctl(shmid,IPC_RMID,NULL);
        sleep(5);

        printf("key = %u,shmid = %d after shmctl\n",key,shmid);
        return 0;
}
   

The client needs to generate the same unique ID as the client, create and use the same shared memory, associate the shared memory, and de-associate the shared memory. There is no need to delete the shared memory because the server has already deleted it:

client.c

#include "comm.h"

int main()
{
        key_t key = ftok(PATH_NAME,PROJ_ID);//生成唯一ID保证在统一系统当中
找到共享内存
        if(key < 0)
        {
                perror("ftok");
                return 1;
        }

        printf("%u\n",key);
        
        //1.创建共享内存
        int shmid = shmget(key,SIZE,IPC_CREAT);//共享内存已存在就返回已存在共享内存
        if(shmid < 0)
        {
                perror("shmget");
                return 2;
        }
        
        //2.关联
        char *mem = shmat(shmid,NULL,0);
        sleep(5);
        printf("client process attaches success\n");

        //通信逻辑
        char c = 'A';
        while(c <= 'G')
        {
                mem[c - 'A'] = c;
                c++;
                        mem[c - 'A'] = 0;
                sleep(2);
        }

        //3.去关联
        shmdt(mem);

        printf("client process detaches success\n");
        return 0;
}

 Makefile

.PHONY:all
all:server client

server:server.c
        gcc -o $@ $^
client:client.c
        gcc -o $@ $^

.PHONY:clean
clean:
        rm -f server client

After make, use the command

while :; do ipcs -m;sleep 1;echo "#################"; done

Let’s check the changes in the number of processes attached to the shared memory: When neither the server nor the client processes are started, the number of nattchs that see the shared memory information is 0. When both the server and the client are running, the number of nattchs is found. The number becomes 2, and the message written by the client will be read by the server. When the client disassociates, nattch becomes 1. Finally, when the server exits, the shared memory is deleted, and nattch becomes 0 again. :

key The system uniquely identifies each shared memory
shmid User layer id (handle) of shared memory
owner Shared memory owner
perms Shared memory permissions
bytes Shared memory size
nattch Number of processes associated with shared memory
status Shared memory status

As can be seen from the above, shared memory has the following characteristics:

  • Once the shared memory is established and mapped into the address space of its own process, the process can see the shared memory, just like the malloc space, without any system call interface (such as read and write, which will copy data from the kernel to the user). or copied from user to kernel).
  • Shared memory is the fastest among all inter-process communications. This is because a piece of shared memory is mapped to a different process address space, and the space on the memory corresponding to the shared memory address is obtained, so either the server or the client can write , the other party saw it immediately.
  • The life cycle depends on the kernel and does not provide a synchronization mutual exclusion mechanism. Programmers need to ensure the security of data by themselves.

3. The difference between shared memory and pipelines 

It can be seen from the characteristics of shared memory:

(1) After the shared memory is created, there is no need to call the system interface for communication. After the pipeline is created, it is still necessary to call the read, write and other system interfaces for communication.

(2) Shared memory does not have a synchronous mutual exclusion mechanism, but pipelines have a synchronous mutual exclusion mechanism.

(3) Shared memory is the fastest among all inter-process communication methods. To transfer data from one process to another, the pipeline requires 4 copies, and the shared memory requires 2 copies. The shared memory requires fewer copies. .

Using pipes, transferring a file from one process to another requires 4 copies:

  • The server copies the information from the input file to the server's temporary buffer.
  • Copy the temporary buffer information of the server to the pipeline
  • The client copies information from the pipe to the client's buffer
  • Copy the client's temporary buffer information to the output file

 Using shared memory, transferring a file from one process to another requires 2 copies:

  • Copy information from input file to shared memory
  • Copy information from shared memory to output file

4. Message queue 

1.Principle 

The message queue is a linked list of messages. The message can be regarded as a record, with a specific format and a specific priority. A process with write permissions on the message queue can add new messages to the message queue according to certain rules, and a process with read permissions on the message queue can read messages from the message queue. The life cycle of the message queue depends on the kernel. 

Each member of the queue is a data block, and each data block contains two parts: type and information. This queue also follows first-in-first-out, that is, messages are read from the head of the queue and written to the tail of the queue:

Each data block has a type, which means that the types of each data block can be different. Therefore, the data blocks received by the receiver process can have different type values. Message queue resources must be deleted manually because the life cycle of system V IPC resources depends on the kernel.

2.Data structure

 How to manage the data blocks in the message pair? Let’s describe it first, then organize it. Use command:

cat /usr/include/linux/msg.h

You can see that the data structure of the message queue is as follows: 

struct msqid_ds 
{ 
    struct ipc_perm msg_perm;     /* Ownership and permissions */
    time_t          msg_stime;    /* Time of last msgsnd(2) */
    time_t          msg_rtime;    /* Time of last msgrcv(2) */
    time_t          msg_ctime;    /* Time of last change */
    unsigned long   __msg_cbytes; /* Current number of bytes in
                                     queue (nonstandard) */
    msgqnum_t       msg_qnum;     /* Current number of messages
                                     in queue */
    msglen_t        msg_qbytes;   /* Maximum number of bytes
                                     allowed in queue */
    pid_t           msg_lspid;    /* PID of last msgsnd(2) */
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};

 Is the first ipc_perm structure a bit familiar? It is the same type of structure as shm_perm. Use the command:

cat /usr/include/linux/ipc.h

You can see that the structure definition of ipc_perm is as follows:

struct ipc_perm 
{
   key_t          __key;       /* Key supplied to msgget(2) */
   uid_t          uid;         /* Effective UID of owner */
   gid_t          gid;         /* Effective GID of owner */
   uid_t          cuid;        /* Effective UID of creator */
   gid_t          cgid;        /* Effective GID of creator */
   unsigned short mode;        /* Permissions */
   unsigned short __seq;       /* Sequence number */
};

3. Steps

The message queue usage process is as follows: 

  • create
  • send
  • take over
  • freed

4. Function 

(1)msgget

Use msgget to create a message queue:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
key Generated through ftok function
msgflg There are multiple msgflg flags. Just understand the two most commonly used flags, IPC_CREAT and IPC_EXCL.
return value The message queue identifier is returned if the creation is successful, and -1 is returned if it fails.

Like shmget, the first parameter key of msgget is generated through the ftok function:

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
pathname Customized file path name
proj_id Serial number, the lower 8 bits are used, non-zero
return value The returned key will be set into the shared memory in the kernel data structure.

There are multiple msgflg flags in the third parameter of msgget. Just understand the two most commonly used flags, IPC_CREAT and IPC_EXCL:

(2)msgctl

Use msgctl to release the message queue:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

After using the message queue, if it is not deleted, the message queue will continue to exist until the system is restarted. How to delete it? There are two ways to delete, one is to delete by command:

ipcrm -q msqid

那么如何在代码中删除共享内存呢?因此另外一种删除消息队列的方式就是使用msgctl函数控制消息队列:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid 消息队列的用户层id
cmd 选项,有多个
buf data structure数据结构类型指针
返回值 删除成功返回0,失败返回-1

其中cmd选项有多个: 

IPC_STAT 将msqid的内核数据结构拷贝到buf指向的msqid_ds结构中
IPC_SET 将buf指向的msqid_ds结构的一些成员的值写入与此共享内存段相关的内核数据结构,同时更新其msq_ctime成员
IPC_RMID 删除共享内存

(3)msgsnd

使用msgsnd向消息队列发送数据:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid 操作系统给用户返回的id
msgp 待发送数据块
msgsz 待发送数据块大小
msgflg 发送数据块的方式,一般为0
返回值 0表示调用成功,-1表示调用失败

其中第二个参数msgp的结构为:

struct msgbuf{
	long mtype;       /* message type, must be > 0 */
	char mtext[1];    /* message data */
};

其中mutex为待发送的信息,mutex大小可以由我们自己指定。

(4)msgrcv

使用msgrcv从消息队列获取消息:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msqid 操作系统给用户返回的id
msgp 获取到的数据块
msgsz 获取到的数据块大小
msgtyp 获取到的数据块的类型
msgflg 获取数据块的方式,一般为0
返回值 >0表示实际获取的字节数,-1表示调用失败


 

五、信号量

1.原理 

前面的管道、共享内存、消息队列都以传输数据为目的,但是信号量不以传输数据为目的,通过共享资源的方式,来达到多个进程同步互斥的目的。 

信号量,有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。本质就是一个计数器,衡量临界资源中的资源数。

这就像坐火车一样,并不是因为坐在座位上,这个作为才属于某一个人,而是买了票的时候,这个作为就已经属于买票的人了,因此买票的本质就是对临界资源的预订,票的数量就是信号量。如以下代码:

 

信号量相关概念:

  • 临界资源:被多个执行流同时访问的资源,一次只允许一个进程使用。比如管道、共享内存、消息队列、信号量。
  • 临界区:进程中访问临界资源的代码(和临界资源配套)为了保护数据安全,就要把临界区保护起来,就有了信号量。
  • 原子性:一件事情要么做完,要么不做,没有中间状态。
  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核。

 信号量本质是对临界资源的统计,更是操作系统对临界资源的预定机制,信号量要诶预订,所有线程要访问临界资源,得先申请信号量,那么所有的进程就得先看到信号量,信号量就是临界资源,要保护信号量这个临界资源,信号量的常见操作即PV操作就必须保证原子性。

2.数据结构

使用命令:

cat /usr/include/linux/sem.h

就能够看到信号量的数据结构如下: 

struct semid_ds 
{
    struct ipc_perm sem_perm;               /* permissions .. see ipc.h */
    __kernel_time_t sem_otime;              /* last semop time */
    __kernel_time_t sem_ctime;              /* last change time */
    struct sem      *sem_base;              /* ptr to first semaphore in array */
    struct sem_queue *sem_pending;          /* pending operations to be processed */
    struct sem_queue **sem_pending_last;    /* last pending operation */
    struct sem_undo *undo;                  /* undo requests on this array */
    unsigned short  sem_nsems;              /* no. of semaphores in array */
};

 第一个ipc_perm 结构体是不是有点熟悉呢?它和shm_perm、msg_perm是同类型的结构体,使用命令:

cat /usr/include/linux/ipc.h

就能够看到ipc_perm 的结构体定义如下:

struct ipc_perm 
{
   key_t          __key;       /* Key supplied to msgget(2) */
   uid_t          uid;         /* Effective UID of owner */
   gid_t          gid;         /* Effective GID of owner */
   uid_t          cuid;        /* Effective UID of creator */
   gid_t          cgid;        /* Effective GID of creator */
   unsigned short mode;        /* Permissions */
   unsigned short __seq;       /* Sequence number */
};

3.函数

(1)semget

 使用semget创建信号量:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
key 操作系统给用户返回的id
nsems 创建的信号量的个数
semflg semflg标志有多个,先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了
返回值

创建成功就返回信号量标识符,-1表示创建失败

(2)semctl

 使用semctl删除信号量:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);
semid 信号量的用户层id
semnum 信号量序号
cmd 信号量的控制操作标识
返回值

创建成功就返回信号量标识符,-1表示创建失败

(3)semop

使用semop来进行信号量的PV操作:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);
semid 信号量的用户层id
sops 是sembuf类型的操作指针
nsops 单个信号量的操作
返回值

创建成功就返回信号量标识符,-1表示创建失败

使用命令:

cat /usr/include/linux/sem.h

可以看到sembuf结构体:

struct sembuf
{
    unsigned short  sem_num;        /* semaphore index in array */
    short           sem_op;         /* semaphore operation */
    short           sem_flg;        /* operation flags */
};
sem_num 指定要操作的信号量,0表示第一个信号量,1表示第二个信号量,……
sem_op 信号量操作
sem_flg 操作标识

六、System V IPC总结

 As can be seen from the above, although shared memory, message queues, and semaphores have different properties and implementations, the members of the data structures they maintain are the same, that is, the ipc_perm structure. In this way, you need to apply for System V IPC every time. When, whether it is shared memory, message queue, or semaphore, a structure like ipc_perm will be opened in the array:

 Then the kernel can allocate an ipc_perm array to point to each IPC resource.

 

 

 

Guess you like

Origin blog.csdn.net/gx714433461/article/details/130880576