[Linux development—multi-process programming]

  • Before, I did some sorting out Linux network programming on socket programming , but if you want to implement a real server-side, socket alone is not enough. You also need to know what is required to build an actual web service .
  • A process is the smallest unit scheduled and allocated by the operating system .

Preface

We can build a server that serves the first client through the hundredth client in sequence. Of course, the first client won't complain about the server side, but if the average service time per client is 0.5 seconds, the 100th client will have considerable dissatisfaction with the server side.

1. Two types of servers

If you really think about your customers, you should raise the average customer satisfaction standard. If you have the following type of server, you should be satisfied, right? ? ?
"The processing time for the first connection request is 0 seconds, the processing time for the 50th connection request is 50 seconds, and the processing time for the 100th connection request is 100 seconds! But as long as it is accepted, the service only takes 1 second."

If the number of top requests could be counted on one hand, the client would certainly be satisfied with the server. But as soon as this number is exceeded, the client will start complaining. It is better to provide services in the following way.
"The processing time of all connection requests does not exceed 1 second, but the average service time is 2~3 seconds."

2. Implementation method of concurrent server:

Even if it is possible to extend the service time, it is necessary to improve the server side so that it can provide services to all clients that initiate requests at the same time to increase the average satisfaction.
Moreover, data communication time in network programs accounts for a larger proportion than CPU computing time. Therefore, providing services to multiple clients is an efficient way to utilize the CPU. Next, we discuss concurrent server-side services that serve multiple clients simultaneously. Listed below are representative concurrent server-side implementation models and methods.

  • 1Multi -process server : Provide services by creating multiple processes.
  • 2 Multiplexing server : Provides services by bundling and uniformly managing I/O objects.
  • 3Multi -threaded server : Provides services by generating the same number of threads as the client.

Here we focus on multi-process servers .

1. Understanding and application

1. Process understanding

  • Process: " A running program that occupies memory space ", such as Plants vs. Zombies. If multiple Plants vs. Zombies game programs are run at the same time, a corresponding number of processes will be generated and the memory space of the corresponding number of processes will also be occupied.
  • From the perspective of the operating system, a process is the basic unit of program flow . If multiple processes are created, the operating system will run at the same time. Sometimes multiple processes will be generated during the running of a program . The multi-process server to be created next is one of the representatives. Before writing the server side, first understand how to create a process through a program.

2. Number of CPU cores and number of processes

  • A CPU with 2 computing devices is called a dual-core CPU, and a CPU with 4 computing devices is called a quad-core CPU. In other words, a CPU may contain multiple computing devices (cores). The number of cores is the same as the number of processes that can run simultaneously. On the contrary, if the number of processes exceeds the number of cores, the processes will use CPU resources in a time-sharing manner. But because the CPU runs so fast, we feel like all processes are running at the same time. Of course, the more cores there are, the more obvious this feeling will be.

3. Process ID

  • All processes are assigned an ID from the operating system , regardless of how they are created . This ID is called the "process ID" and its value is an integer greater than 2. 1 is to be assigned to the first process after the operating system is started (used to assist the operating system), so the user process cannot get the ID value 1 . You can view all currently running processes
    through the command. ps auIt is important to note that this command can also list the PID (process ID). All process details can be listed by specifying the a and u parameters.

4. Process creation

  • There are many ways to create a process. Here we introduce the fork function used to create a multi-process server.
#include <unistd.h>
//→成功时返回进程 ID,失败时返回-1。
pid_t fork(void);
  • The fork function creates a copy of the calling process. That is, instead of creating a process from a completely different program, you copy the running process that calls the fork function. Additionally, both processes will execute the statements following the fork function call (after the fork function returns, to be precise). However, because the same memory space is copied through the same process, subsequent program flows must be distinguished based on the return value of the fork function. That is, the following characteristics of the fork function are used to distinguish the program execution flow.
    • Parent process: The fork function returns the child process ID.
    • Child process: the fork function returns 0.
    • Here, "Parent Process" refers to the original process, that is, the subject that calls the fork function, and "Child Process" (Child Process) is the process copied by the parent process calling the fork function.

5. The program running process after calling the fork function:

Insert image description here

  • If pid is 0, it means that the child process is started. If it is greater than 0, it means the parent process. If it is less than 0, it means failure.
    After calling the fork function, the parent and child processes have completely independent memory structures.

2. Zombie process

1. Definition

In file operations, closing a file is equally important as opening a file. Similarly, process destruction is equally important as process creation . If process destruction is not taken seriously, they will become zombie processes and plague everyone.

The same is true in the world of processes. Processes should be destroyed after they have completed their work (after executing the program in the main function), but sometimes these processes will become zombie processes and occupy important resources in the system . Processes in this state are called "zombie processes", which is one of the reasons why it puts a burden on the system.

2. Causes and solutions to zombie processes

  • First, use the following two examples to show how to terminate the child process generated by calling the fork function.
    1 Pass parameters and call the exit function.
    2. Execute the retun statement in the main function and return the value.
    The parameter values ​​passed to the exit function and the value returned by the return statement of the main function will be passed to the operating system . The operating system will not destroy the child process until these values ​​are passed to the parent process that spawned the child process. A process in this state is a zombie process. In other words, it is the operating system that turns the child process into a zombie process .
  • Solution : The operating system will pass this value only when the parent process actively initiates a request (function call). In other words, if the parent process does not actively ask for the end status value of the child process, the operating system will always save it and keep the child process in the zombie process state for a long time. In other words, parents are responsible for taking back their children.
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    
    
	pid_t pid=fork();
	
	if(pid==0)     // if Child Process
	{
    
    
		puts("Hi I'am a child process");
	}
	else
	{
    
    
		printf("Child Process ID: %d \n", pid);
		sleep(30);     // Sleep 30 sec.
	}

	if(pid==0)
		puts("End child process");
	else
		puts("End parent process");
	return 0;
}

The results show: The process in Z state is a zombie process, S: sleeping, R: running, Z zombie
Insert image description here

3. Signal processing

1. Definition

  • We have learned above that the parent process is often as busy as the child process when creating and destroying processes. We do not know when to end the child process. Therefore, we cannot just call the waitpid function endlessly to wait for the child process to terminate. This requires signal processing to respond . association.

  • The main body that recognizes the termination of a child process is the operating system . Therefore, if the operating system can tell the parent process that is busy working, it will help to build an efficient program.

  • Signal handling : A message sent by the operating system to a process when a specific event occurs. In addition, in response to the message, the process of performing custom operations related to the message.

2. signal function

  • When a process discovers that its child process has ended, it requests the operating system to call a specific function. The request is completed through the signal function call (so signal is called the signal registration function)
//→为了在产生信号时调用,返回之前注册的函数指针。
/*
函数名∶signal
参数∶int signo, void(* func)(int)
返回类型∶参数为int型,返回void型函数指针。
*/
#include<signal.h>
void(*signal(int signo, void(*func)(int))(int);

//等价于下面的内容:
typedef void(*signal_handler)(int);
signal_handler signal(int signo,signal_handler func);

  • When calling the above function, the first parameter is the special case information, and the second parameter is the address value (pointer) of the function to be called in the special case. When the situation represented by the first parameter occurs, the function pointed to by the second parameter is called. Below are some special cases and corresponding constants that can be registered in the signal function.
    • SIGALRM : The time to register by calling the alarm function has arrived.
    • SIGINT : Enter CTRL+C.
    • SIGCHLD : The child process terminates. (English is child)

3. Calling method

Write a statement to call the signal function to complete the following request
1. " When the child process terminates, the mychild function is called ."
代码:signal(SIGCHLD, mychild);

  • At this time, the parameters of the mychild function should be int, and the return value type should be void. Corresponds to the second parameter of the signal function. In addition, the constant SIGCHLD indicates the termination of the child process and should become the first parameter of the signal function.

2. "The time to register through the alarm function has arrived, please call the timeout function."
3. "Call the keycontrol function when entering CTRL+C."

  • The constants representing these two situations are SIGALRM and SIGINT respectively, so the signal function is called as follows.
    2. signal(SIGALRM, timeout);
    3. signal(SIGINT, keycontrol);

The above is the signal registration process. After the signal is registered, when the registration signal occurs (when the registration occurs), the operating system will call the function corresponding to the signal.

#include<unistd.h>
//→返回0或以秒为单位的距SIGALRM信号发生所剩时间。
unsigned int alarm(unsigned int seconds);

If you call this function and pass it a positive integer parameter, the SIGALRM signal will be generated after the corresponding time (in seconds). If 0 is passed to this function, the previous reservation for the SIGALRM signal will be cancelled. If the signal processing function corresponding to the signal is not specified after reserving the signal through this function, the process will be terminated (by calling the signal function) without any processing.

4. Example:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
// 定义信号处理函数timeout,返回值为void
void timeout(int sig)
{
    
    
	if(sig==SIGALRM)
		puts("Time out!");
//为了每隔2秒重复产生SIGALRM信号,在信号处理器中调用alarm函数
	alarm(2);	
}
// 定义信号处理函数keycontrol,返回值为void
void keycontrol(int sig)
{
    
    
	if(sig==SIGINT)
		puts("CTRL+C pressed");
}

int main(int argc, char *argv[])
{
    
    
	int i;
//注册SIGALRM、SIGINT信号及相应处理器
	signal(SIGALRM, timeout);
	signal(SIGINT, keycontrol);
//预约2秒后发生SIGALRM信号
	alarm(2);

	for(i=0; i<3; i++)
	{
    
    
		puts("wait...");
		sleep(100);
	}
	return 0;
}
  • The above for loop:
    In order to view the signal generation and signal processor execution and provide a waiting time of 100 seconds each time, a total of 3 times, the
    sleep function is called in the loop. In other words, the program will be terminated after another 300 seconds and about 5 minutes. This is a long // period of time, but it actually takes less than 10 seconds to execute.

Why is this? It's obviously 300 seconds. . .

  • Reason: "When a signal occurs, the process that enters the blocked state due to the call to the sleep function will be awakened."
    The subject of calling the function is indeed the operating system, but the function cannot be called when the process is in the sleep state. Therefore, when a signal is generated, in order to call the signal handler, the process that entered the blocked state due to calling the sleep function will be awakened. Moreover, once the process is awakened, it will not go to sleep again. This is true even if the time specified in the sleep function has not yet reached. Therefore, the program will end in less than 10 seconds. If CTRL+C is entered continuously, it may not even take 1 second.

4. Concurrent server based on multi-tasking

1. Principle

(空间与时间的平衡,以时间换取空间,还是以空间换取时间)
Every time the client has an access request, the server in the accept phase goes to fork to create a child process to handle the client access request . At the same time, the parent process returns to the accept phase and continues to wait for new client requests . The problem is that if multiple users are online at the same time, the bottleneck will lead to a sudden increase in memory.

2. Multi-tasking concurrent server instance

//-----------------------------------------------------多任务并发服务器(进程)---------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //Linux标准数据类型
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char* message);
//------------------------------------服务端-----------------------

void hand_childProc(int sig)
{
    
    
    pid_t pid;
    int status = 0;
    waitpid(-1, &status, WNOHANG);//-1:回收僵尸进程,WNOHANG:非挂起方式,立马返回status状态
    printf("%s(%d):%s removed sub proc:%d\r\n", __FILE__, __LINE__, __FUNCTION__, pid);
}
//服务器
void ps_moretask_server()
{
    
    
    
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = hand_childProc;
    sigaction(SIGCHLD, &act, 0);//当发现有SIGCHLD信号时进入到子进程函数。处理任务和进程回收(防止僵尸进程)

    int serv_sock;
    struct sockaddr_in server_adr, client_adr;
    memset(&server_adr, 0, sizeof(server_adr));
    server_adr.sin_family = AF_INET;
    server_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_adr.sin_port = htons(9527);
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(serv_sock, (sockaddr*)&server_adr, sizeof(server_adr)) == -1)
        error_handling("ps_moretask server bind error");
    if (listen(serv_sock, 5) == -1)
    {
    
    
        error_handling("ps_moretask server listen error");
    }
    int count = 0;
    char buffer[1024];
    while (true)
    {
    
    
        socklen_t size = sizeof(client_adr);
        int client_sock = accept(serv_sock, (sockaddr*)&client_adr, &size);
        if (client_sock < 0) {
    
    
            error_handling("ps_moretask server accept error");
            close(serv_sock);
            return;
        }

        pid_t pid = fork();//会复制客户端和服务端的socket
        if (pid == 0)
        {
    
    
            close(serv_sock);//子进程关闭服务端的socket,因为子进程为了处理客户端的任务

            ssize_t len = 0;
            while ((len = read(client_sock, buffer, sizeof(buffer))) > 0)
            {
    
    
                len = write(client_sock, buffer, strlen(buffer));
                if (len != (ssize_t)strlen(buffer)) {
    
    
                    //error_handling("write message failed!");
                    std::cout << "ps_moretask server write message failed!\n";

                    close(serv_sock);
                    return;
                }

                std::cout << "ps_moretask server read & write success!, buffer:" << buffer << "__len:" << len << std::endl;

                memset(buffer, 0, len);//清理

                close(client_sock);
                return;
            }

        }
        else if (pid < 0)
        {
    
    
            close(client_sock);
            error_handling("ps_moretask server accept fork error");
            break;
        }

       

        close(client_sock);//服务端关闭的时候,客户端会自动关闭
    }

    close(serv_sock);
}

//------------------------------------客户端-----------------------
void ps_moretask_client()
{
    
    
    int client = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(9527);
    int ret = connect(client, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if (ret == -1) {
    
    
        std::cout << "ps_moretask client connect failed!\n";
        close(client);
        return;
    }
    std::cout << "ps_moretask client connect server is success!\n";

    char buffer[256] = "hello ps_moretask server, I am client!";
    while (1)
    {
    
    
        //fputs("Input message(Q to quit):", stdout);//提示语句,输入Q结束
        //fgets(buffer, sizeof(buffer), stdin);//对文件的标准输入流操作 读取buffer的256字节
        //if (strcmp(buffer, "q\n") == 0 || (strcmp(buffer, "Q\n") == 0)) {
    
    
        //    break;
        //}

        size_t len = strlen(buffer);
        size_t send_len = 0;

        //当数据量很大时,并不能一次把所有数据全部发送完,因此需要分包发送
        while (send_len < len)
        {
    
    
            ssize_t ret = write(client, buffer + send_len, len - send_len);//send_len 记录分包的标记
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed,make client write failed!\n", stdout);
                close(client);
                return;
            }
            send_len += (size_t)ret;

            std::cout << "ps_moretask client write success, msg:" << buffer << std::endl;

        }
        memset(buffer, 0, sizeof(buffer));

        //当数据量很大时,并不能一次把所有数据全部读取完,因此需要分包读取
        size_t read_len = 0;
        while (read_len < len)
        {
    
    
            size_t ret = read(client, buffer + read_len, len - read_len);
            if (ret <= 0) {
    
    //连接出了问题
                fputs("may be connect newwork failed, make client read failed!\n", stdout);
                close(client);
                return;
            }
            read_len += (size_t)ret;
        }
        std::cout << "from server:" << buffer << std::endl;
    };
    sleep(2);//延时2秒关闭客户端
    close(client);
    std::cout << "ps_moretask client done!" << std::endl;
}

//------------------------------------调用函数-----------------------
void ps_moretask_func()
{
    
    
    pid_t pid = fork();
    if (pid > 0)
    {
    
    
        printf("%s(%d):%s wait ps_moretask server invoking!\r\n", __FILE__, __LINE__, __FUNCTION__);

        sleep(1);
        for (int i = 0; i < 5; i++)
        {
    
    
            pid = fork();
            if (pid > 0) {
    
    
                continue;
            }
            else if (pid == 0)
            {
    
    
                //子进程启动客户端
                ps_moretask_client();
                break;//到子进程终止,避免指数级创建进程 n的2次方。
            }
        }
    }
    else if (pid == 0) {
    
    
        //启动服务端
        ps_moretask_server();
    }
}

5. Inter-process communication (IPC)

  • IPC (Inter Process Communication): Inter-process communication , a mechanism for data exchange through the buffer provided by the kernel . Inter-process communication means that data can be exchanged between two different processes. In order to accomplish this, the operating system should provide a memory space that both processes can access simultaneously .

  • The following example between processes A and B is an inter-process communication rule.

    • "If I have 1 piece of bread, the value of the variable bread becomes 1. If I eat the bread, the value of bread changes back to 0. Therefore, my status can be judged by the value of the variable bread." In other words, process A Process B is notified of its status through the variable bread, and process B hears what process A said through the variable bread.
  • As long as there is a memory space that two processes can access simultaneously, data can be exchanged through this space . But processes have completely independent memory structures. Even the child process created through the fork function will not share memory space with the parent process . Therefore, inter-process communication can only be done through other special methods.

  • In the process, the child process will copy the memory of the parent process, but the parent process will not copy the memory of the child process, so some operations of the child process are not known to the parent process.

1. Anonymous pipe—PIPE

亲族管道,处理两个不相干的进程时会有问题

  • Pipes are not resources that belong to the process, but belong to the operating system like sockets (that is, they are not the copy objects of the fork function). So, two processes communicate through the memory space provided by the operating system.
  • There are actually many types of pipelines:
    • The most commonly used one is probably "|" in the shell. It is actually a pipe character, which takes the output of the previous expression and introduces the following expression as input. For example, we commonly use "ps aux|grep ssh" to view ssh-related processes.
    • There are two commonly used inter-process communication pipes. One is the pipe pipe, which can also be called a family pipe .
    • Corresponding to this is the fifo pipe , which can also be called a public pipe .
#include <unistd.h>
//→成功时返回 0,失败时返回-1。
int pipe(int filedes[2]);
/*
Filedes[0] 通过管道接收数据时使用的文件描述符,即管道出口。
Fledes[1] 通过管道传输数据时使用的文件描述符,即管道入口。
*/
  • Bidirectional pipeline:
    Insert image description here
//进程通信——双管道(PIPE)
void ps_pipe_double_func()
{
    
    
    int fds_server[2] = {
    
     -1, -1 };
    int fds_client[2] = {
    
     -1, -1 };

    pipe(fds_server);//父进程创建管道
    pipe(fds_client);
    
    pid_t pid = fork();

    if (pid == 0)
    {
    
    
        char buffer[64] = "client send by child process!\n";
        char readBuf[128] = "";
        //子进程数据写入
        write(fds_client[1], buffer, sizeof(buffer));

        read(fds_server[0], readBuf, sizeof(readBuf));

        printf("%s(%d):%s child process read ps_pipe by server :%s\r\n", __FILE__, __LINE__, __FUNCTION__, readBuf);

        printf("%s(%d):%s ---pid:%d\r\n", __FILE__, __LINE__, __FUNCTION__, getpid());

    }
    else
    {
    
    
        char buffer[64] = "server send by father process!\n";
        char readBuf[128] = "";
        //父进程读取数据
        read(fds_client[0], readBuf, sizeof(readBuf));

        printf("%s(%d):%s father process read ps_pipe by client :%s\r\n", __FILE__, __LINE__, __FUNCTION__, readBuf);

        write(fds_server[1], buffer, sizeof(buffer));

    }

    printf("%s(%d):%s ---pid:%d\r\n", __FILE__, __LINE__, __FUNCTION__, getpid());


}

2. Named pipe—FIFO

FIFO: first in first out, first in first out. ( 每个进程都要有个命名文件)

  • Compared with the pipe pipeline, it can already complete the task of communicating between two processes, but it seems that it is not completed well enough, or it can be said that it is not thorough enough. It can only communicate between two related processes, which greatly limits the application scope of the pipe pipeline . Many times we often want to be able to communicate between two independent processes, so pipes cannot be used, so a pipe that can satisfy independent process communication has emerged, which is the fifo pipe .

  • The essence of fifo pipe is a named file in the operating system

  • It exists in the form of a named file in the operating system. We can see fifo pipes in the operating system. If you have permission, you can even read and write them.

    • Use command: mkfifo myfifo
    • Use function: int mkfifo(const char *pathname, mode_t mode); Success: 0; Failure: -1
  • The kernel will open a buffer for the FIFO file, operate the FIFO file , and operate the buffer to achieve process communication. Once a FIFO is created using mkfifo, it can be opened using open. Common file IO functions can be used for FIFO. Such as: close, read, write, unlink, etc.

//进程通信-命名管道(FIFO)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>//创建命名管道头文件
#include <fcntl.h>
#include <string.h>
void ps_fifo_func()
{
    
    
    mkfifo("./test_fifo.fifo", 0666);//创建FIFO命名管道,并设置mode
    pid_t pd = fork();
    if (pd == 0)
    {
    
    
        sleep(1);
        int fd = open("./test_fifo.fifo", O_RDONLY);//打开创建的fifo文件,并申请读权限

        char buffer[64] = "";
        ssize_t len = read(fd, buffer, sizeof(buffer));

        printf("%s(%d):%s read ps_fifo server :%s  len: %d\r\n", __FILE__, __LINE__, __FUNCTION__, buffer, len);

        close(fd);
    }
    else
    {
    
    
        int fd = open("./test_fifo.fifo", O_WRONLY);//打开创建的fifo文件, 并申请读写权限

        char buffer[128] = "hello, I am fifo server!";
        ssize_t len = write(fd, buffer, sizeof(buffer));

        printf("%s(%d):%s ps_fifo server wait success!\r\n", __FILE__, __LINE__, __FUNCTION__);

        close(fd);
    }
}

3. Shared memory

(数据同步时,具有部分时间差,比较耗时)

1. Definition

  • Before understanding shared memory, you must first understand the System V IPC communication mechanism.
    The System V IPC mechanism was originally introduced with the AT&T System V.2 version of UNIX. These mechanisms are specifically used for IPC (Inter-Process Communication) . They are used in the same version and have similar programming interfaces, so they are often called System V IPC communication mechanisms.
    Shared memory is the second of three System V IPC mechanisms. Shared memory allows different processes to share the same logical memory. They can all access or modify this memory without any restrictions. So it is a very efficient way to transfer large amounts of data between processes. "Shared memory allows different processes to share the same logical memory", here is logical memory. In other words, processes that share memory may not access the same physical memory . There is no clear regulation on this, but most system implementations arrange the shared memory between processes into the same physical memory .
  • Shared memory is actually a special piece of physical memory allocated by the IPC mechanism. It can be mapped to the address space of the process and can also be mapped to the address space of other processes with permissions. It's like using malloc to allocate memory, except that this memory can be shared.

2. Creation, mapping, access and deletion of shared memory

  • IPC provides a set of APIs to control shared memory. The steps for using shared memory are usually:
    • 1) Create or obtain a shared memory;
    • 2) Map the shared memory created in the previous step to the address space of the process;
    • 3) Access shared memory;
    • 4) Separate shared memory from the current process address space;
    • 5) Delete this shared memory;
  • details as follows:

1) Use the shmget() function to create a shared memory :

int shmget( key_t key, size_t size, int shmflg );
key: The name of this shared memory. The system uses it to distinguish shared memory. Different processes that access the same shared memory need to pass in the same name.
- size: The size of the shared memory
- shmflg: It is the flag of the shared memory, including 9 bits, and its content is the same as the mode when creating the file. There is a special flag IPC_CREAT that can be passed in with the permission flag in the form of or

2) Use the function shmat() to map shared memory :

void* shmat( int shm_id, const void* shm_addr, int shmflg );
shm_id: is the ID of the shared memory, the return value of the shmget() function.
shm_addr: Specify the location where the shared memory is connected to the address space of the current process. Usually NULL is passed in, indicating that the system will make the selection to prevent memory confusion.
shmflg: A set of control flags, usually 0, or SHM_RDONLY may be entered, indicating that the shared memory segment is read-only.
– The return value of the function is the first address pointer of the shared memory.

3) Use the function shmdt() to separate shared memory :

int shmdt(void* shm_p);
–shm_p: It is the first address pointer of the shared memory, which is the return value of shmat().
– Returns 0 on success, -1 on failure.

4) Use the shmctl() function to control shared memory :

int shmctl( int shm_id, int command, struct shmid_ds* buf );
shm_id: is the identifier of shared memory, which is the return value of shmget().
command: is the action to be taken, it has three valid values ​​as follows:
Insert image description here

3. Code example:

#include <sys/ipc.h>
#include <sys/shm.h>//共享内存头文件

//共享的结构体
typedef struct {
    
    
    int id;
    char name[128];
    int age;
    bool sex;
    int signal;
}STUDENT, *P_STUDENT;

void ps_sharememory_func()
{
    
    
    pid_t pid = fork();
    if (pid > 0)
    {
    
    
        //shmget创建共享文件 ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        int shm_id = shmget(ftok(".", 1), sizeof(STUDENT), IPC_CREAT | 0666);
        if (shm_id == -1) {
    
    
            printf("%s(%d):%s share memory creat failed!\r\n", __FILE__, __LINE__, __FUNCTION__);
            return;
        }
        P_STUDENT pStu = (P_STUDENT)shmat(shm_id, NULL, 0);
        pStu->id = 666666;
        strcpy(pStu->name, "welcome moon");
        pStu->age = 19;
        pStu->sex = true;
        pStu->signal = 99;
        while (pStu->signal == 99)//同步
        {
    
    
            usleep(100000);
        }
        shmdt(pStu);
        shmctl(shm_id, IPC_RMID, NULL);
           
    }
    else {
    
    
        usleep(500000);//休眠500ms,等待父进程写入数据, 1000单位为1ms,100000为100ms
        //shmget创建共享文件 ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        int shm_id = shmget(ftok(".", 1), sizeof(STUDENT), IPC_CREAT | 0666);
        if (shm_id == -1) {
    
    
            printf("%s(%d):%s share memory creat failed!\r\n", __FILE__, __LINE__, __FUNCTION__);
            return;
        }
        P_STUDENT pStu = (P_STUDENT)shmat(shm_id, NULL, 0);
        while (pStu->signal != 99)//同步
        {
    
    
            usleep(100000);
        }
        printf("student msg: %d, %s, %d, %s\n", pStu->id, pStu->name, pStu->age, pStu->sex == true ? "male":"famale");
        pStu->signal = 0;
        shmdt(pStu);
        shmctl(shm_id, IPC_RMID, NULL);
    }
}
  • Shared memory, data must be synchronized with the while loop processing of the two processes in the case, otherwise the data cannot be accessed. Disadvantages: There is a partial time difference during data synchronization, which is relatively time-consuming.
  • operation result:
    Insert image description here

4, Signal amount

1. Definition

  • In order to prevent a series of problems caused by multiple programs accessing a shared resource at the same time, we need a method that can generate and use tokens to authorize so that only one thread of execution can access the critical area of ​​​​the code at any time. . A critical section means that the code that performs data updates needs to be executed exclusively. The semaphore can provide such an access mechanism, allowing only one thread to access a critical section at the same time, which means that the semaphore is used调协进程对共享资源的访问 .
  • A semaphore is a special variable, and program access to it is an atomic只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作 operation . The simplest semaphore is a variable that can only take 0 and 1. This is also the most common form of semaphore, called 二进制信号量. A semaphore that can take multiple positive integers is called 通用信号量. Here we mainly explore binary semaphores.

2. Working principle

  • 1. Since the semaphore can only perform two operations: waiting for and sending signals, namely P(sv) and V(sv), their behavior is as follows: P(sv): If the value of sv is greater than zero, decrement it 1; if its value is zero, suspend the execution of the process
    V(sv): if other processes are suspended waiting for sv, let it resume running. If no process is suspended waiting for sv, just Add 1 to it.
  • 2. For example, two processes share the semaphore sv. Once one of the processes performs the P(sv) operation, it will get the semaphore and can enter the critical section to decrease sv by 1. And the second process will be blocked from entering the critical section because when it tries to execute P(sv), sv is 0, it will be hung waiting for the first process to leave the critical section and execute V(sv) to release the semaphore , then the second process can resume execution.

3. Linux semaphore mechanism

  • View the current semaphore array:
    ipcs -s

1. semget function—create

Insert image description here

2. semop function—perform PV operation

Insert image description here

3. semctl function—control semaphore information

Insert image description here

4. Code example:

  • Replace the while loop in the shared memory module -> code example above with a semaphore:
//进程通信————————————————————共享内存+信号量

#include <sys/ipc.h>
#include <sys/shm.h>//共享内存头文件
#include <sys/sem.h>//信号量头文件

//共享的结构体
typedef struct {
    
    
    int id;
    char name[128];
    int age;
    bool sex;
    int signal;
}STUDENT, *P_STUDENT;

void ps_sharememory_func()
{
    
    
    pid_t pid = fork();
    if (pid > 0)
    {
    
    
        //创建信号量
        key_t key = ftok(".", 2);//ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        int sem_id = semget(key, 2, IPC_CREAT);//信号量id创建,key:代表当前路径,2:创建两个信号量,IPC_CREAT:表示进程通信创建

        //pv 生产者 消费者模式(P通过,V释放)
        //两个信号量
        semctl(sem_id, 0, SETVAL, 0);
        semctl(sem_id, 1, SETVAL, 0);


        //shmget创建共享文件id 
        int shm_id = shmget(ftok(".", 1), sizeof(STUDENT), IPC_CREAT | 0666);//ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        if (shm_id == -1) {
    
    
            printf("%s(%d):%s share memory creat failed!\r\n", __FILE__, __LINE__, __FUNCTION__);
            return;
        }
        P_STUDENT pStu = (P_STUDENT)shmat(shm_id, NULL, 0);
        pStu->id = 666666;
        strcpy(pStu->name, "welcome moon");
        pStu->age = 19;

        pStu->sex = true;

        
        //第一个信号量 v操作
        sembuf sop = {
    
    
            .sem_num = 0,
            .sem_op = 1
        };
        semop(sem_id, &sop, 1);//v操作,

        semctl(sem_id, 0, GETVAL);


        //第二个信号量 P操作
        sop.sem_num = 1;
        sop.sem_op = -1;
        semop(sem_id, &sop, 1);//P操作
        semctl(sem_id, 1, GETVAL);

        //删除共享内存
        shmdt(pStu);
        shmctl(shm_id, IPC_RMID, NULL);


        //主进程删除信号量
        semctl(sem_id, 0, IPC_RMID);//释放掉第一个信号量
        semctl(sem_id, 1, IPC_RMID);//释放掉第二个信号量
        sleep(10);//休眠10秒

    }
    else {
    
    
        usleep(500000);//休眠500ms,等待父进程写入数据, 1000单位为1ms,100000为100ms

        //创建信号量
        key_t key = ftok(".", 2);//ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        int sem_id = semget(key, 2, IPC_CREAT);//信号量id创建


        //shmget创建共享文件 ftok指定文件或文件夹路径,创建一个安全可靠的key,规避重复
        int shm_id = shmget(ftok(".", 1), sizeof(STUDENT), IPC_CREAT | 0666);
        if (shm_id == -1) {
    
    
            printf("%s(%d):%s share memory creat failed!\r\n", __FILE__, __LINE__, __FUNCTION__);
            return;
        }

        //第一个信号量 P操作
        sembuf sop = {
    
    
           .sem_num = 0,
           .sem_op = -1
        };
        semop(sem_id, &sop, 1);//P操作,
        semctl(sem_id, 0, GETVAL);

        P_STUDENT pStu = (P_STUDENT)shmat(shm_id, NULL, 0);

        //第二个信号量 V操作
        sop.sem_num = 1;
        sop.sem_op = 1;
        semop(sem_id, &sop, 1);//V操作,
        semctl(sem_id, 1, GETVAL);

        printf("student msg: %d, %s, %d, %s\n", pStu->id, pStu->name, pStu->age, pStu->sex == true ? "male":"famale");
        pStu->signal = 0;
        

        shmdt(pStu);
        shmctl(shm_id, IPC_RMID, NULL);

        sleep(10);//休眠10秒

    }
}

5. Message queue

1. Definition

  • Message queues provide a way to send a block of data from one process to another . Each data block is considered to contain a type, and the receiving process can independently receive data structures containing different types. We can avoid the synchronization and blocking problems of named pipes by sending messages . But the message queue, like the named pipe FIFO, has a maximum length limit for each data block .

2. Message Queue API

1. msgget function—create message queue

This function is used to create and access a message queue. Its prototype is:
int msgget(key_t key, int msgflg);

  • As with other IPC mechanisms, the program must provide a key to name a specific message queue. msgflg is a permission flag, indicating the access permission of the message queue, which is the same as the access permission of the file. msgflg can be ORed with IPC_CREAT, which means to create a message queue when the message queue named by key does not exist. If the message queue named by key exists, the IPC_CREAT flag will be ignored and only an identifier will be returned. It returns an identifier (non-zero integer) of the message queue named by key, or -1 on failure.

2. msgsnd function—add data to the message queue

This function is used to add messages to the message queue. Its prototype is:
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

  • msgid is the message queue identifier returned by the msgget function.
  • msg_ptr is a pointer to the message to be sent, but the data structure of the message has certain requirements. The message structure pointed to by the pointer msg_ptr must be a structure starting with a long integer member variable. The receiving function will use this member to determine The type of message. So the message structure should be defined like this:
    struct my_message{ long int message_type; /* The data you wish to transfer*/ };
  • msg_sz is the length of the message pointed to by msg_ptr. Note that it is the length of the message, not the length of the entire structure. That is to say, msg_sz is the length excluding the long integer message type member variable.
  • msgflg is used to control what will happen when the current message queue is full or the queue message reaches a system-wide limit. If the call is successful, a copy of the message data will be placed in the message queue and 0 will be returned. If it fails, -1 will be returned.

3. msgrcv function—get data from the message queue:

This function is used to get messages from a message queue. Its prototype is
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

  • The functions of msgid, msg_ptr, and msg_st are the same as the msgsnd function.
  • msgtype can implement a simple receive priority. If msgtype is 0, get the first message in the queue.
    • If its value is greater than zero, the first message with the same message type will be obtained.
    • If it is less than zero, get the first message whose type is equal to or less than the absolute value of msgtype.
  • msgflg is used to control what will happen when there are no messages of the corresponding type in the queue to receive.
    When called successfully, the function returns the number of bytes placed in the receive buffer, the message is copied to the user-allocated buffer pointed to by msg_ptr, and then the corresponding message in the message queue is deleted. Returns -1 on failure.

4. msgctl function—control message queue:

This function is used to control the message queue. It is similar to the shmctl function of shared memory. Its prototype is:
int msgctl(int msgid, int command, struct msgid_ds *buf);

  • command is the action to be taken, it can take 3 values,
    • IPC_STAT : Set the data in the msgid_ds structure to the current association value of the message queue, that is, overwrite the value of msgid_ds with the current association value of the message queue.
    • IPC_SET : If the process has sufficient permissions, set the current association value of the message queue to the value given in the msgid_ds structure
    • IPC_RMID : Delete message queue.
  • buf is a pointer to the msgid_ds structure, which points to the message queue mode and access rights structure. The msgid_ds structure includes at least the following members:
//成功时返回0,失败时返回-1.
struct msgid_ds {
    
     
uid_t shm_perm.uid; 
uid_t shm_perm.gid; 
mode_t shm_perm.mode;
 };

3. Code examples

//进程通信----------消息队列
#include <sys/msg.h>

typedef struct {
    
    
    int type;
    struct {
    
    
        int id;
        char name[64];
        int age;
        char msg[256];
    }data, *pdata;
}MSG, *PMSG;

void ps_msg_func()
{
    
    
    pid_t pid = fork();
    if (pid > 0)
    {
    
    
        int msg_id = msgget(ftok(".", 3), IPC_CREAT | 0666);
        if (msg_id == -1)
        {
    
    
              printf("%s(%d):%s ps_msg server error=%d:%s!\r\n", __FILE__, __LINE__, __FUNCTION__, errno,strerror(errno));

            return;
        }

        MSG msg;
        memset(&msg, 0, sizeof(msg));
        while (true)
        {
    
    
            ssize_t ret = msgrcv(msg_id, &msg, sizeof(msg.data), 1, 0);
            if (ret == -1) {
    
    
                sleep(1);
                printf("sleeping ~");
            }
            else
            {
    
    
                break;
            }
        }
        printf("accept msg: %d, %s, %d, %s\n", msg.data.id, msg.data.name, msg.data.age, msg.data.msg);

        getchar();
        msgctl(msg_id, IPC_RMID, 0);

    }
    else
    {
    
    
        int msg_id = msgget(ftok(".", 3), IPC_CREAT | 0666);
        MSG msg;
        memset(&msg, 0, sizeof(msg));
        msg.data.id = 5555;
        msg.data.age = 19;

        strcpy(msg.data.name, "moon");
        strcpy(msg.data.msg, "hello friend!");

        msgsnd(msg_id, &msg, sizeof(msg), 0);
        printf("send msg: %d, %s, %d, %s\n", msg.data.id, msg.data.name, msg.data.age, msg.data.msg);

        //休眠两秒后,待数据发送出去再做删除
        sleep(2);
        msgctl(msg_id, IPC_RMID, 0);
    }
    
}

  • Running result, error->38, 38 indicates the underlying unimplemented method ( Function not implemented), because I am using Ubuntu, and the message queue cannot be processed on the Ubuntu system:
    Insert image description here

Guess you like

Origin blog.csdn.net/MOON_YZM/article/details/130891757