Linux knowledge points--interprocess communication (2)

Linux knowledge points - inter-process communication (2)


1. System V shared memory

1. Principle

insert image description here
First apply for space in the memory, and then map this space to the address space of different processes, which is called shared memory;
generally it is a shared area mapped between the stacks of processes;
shared memory does not belong to any process, it It belongs to the operating system;
the management of the shared memory by the operating system is to describe and then organize, first describe the attribute information of the shared memory through the kernel data structure, and then organize them;
shared memory = shared memory block + corresponding kernel data of the shared memory structure;
the shared area belongs to user space and can be accessed directly without going through system calls;
if the processes of both parties want to communicate, they can directly read and write at the memory level; the
previous pipeline is a kind of file. It is a data structure in the OS, so the user has no right to directly access it and needs to make a system call;

2. Apply for shared memory

insert image description here
The shmget interface can apply for shared memory;

  • parameter:
    key:The processes of the two parties in communication use the key value to ensure that the shared memory is created by the two parties in communication, which is equivalent to a verification value, which needs to be unique in the system, and the two parties in communication use the same key;
    size:Memory size, generally an integer multiple of a page (4byte);
    shmflag:There are two options: IPC_CREAT and IPC_EXCL;
    IPC_CREAT can appear alone, which means that if the shared memory exists, get it; if it does not exist, create it and return;
    IPC_EXCL must be used in combination with IPC_CREAT, which means that if the shared memory does not exist, Just create it and return; if it already exists, make an error and return;
    0 means IPC_CREAT;

    return value:If it succeeds, it will return the shared memory id, if it fails, it will return -1;

ftok function: generate a unique key
insert image description here

  • parameter:
    ==pathname:==file path, you must ensure that the user has permission;
    ==id:==Project id, give it at will, usually 0-255;
    Return value: success, return key value; failure, return -1;
    ftok will take the inode of the path file and form a unique key with the id, and the generated result may be repeated;

3. Use of System V shared memory

  • Makefile:
.PHONY:all
all:shmClient shmServer

shmServer:shmServer.cc
	g++ -o $@ $^ -std=c++11 
shmClient:shmClient.cc
	g++ -o $@ $^ -std=c++11 

.PHONY:clean
claen:
	rm -f shmServer shmClient
  • Log.hpp
#ifndef _LOG_H_
#define _LOG_H_

#include<iostream>
#include<ctime>

#define DeBug   0
#define Notice  1
#define Waring  2
#define Error   3

const std::string msg[] = {
    
    
    "DeBug",
    "Notice",
    "Waring",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    
    
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}
#endif
  • comm.hpp
#ifndef _COMM_H_
#define _COMM_H_

#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cassert>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/usr/lmx" //路径,一定保证有权限
#define PROJ_ID 0X66   
#define SHM_SIZE 4096 //共享内存大小,最好是页(4byte)的整数倍

#endif
  • shmServer.cc
    #include “comm.hpp”

string TransToHex(key_t k)
{
char buffer[32];
snprintf(buffer, sizeof(buffer), “0x%x”, k);
return buffer;
}

int main()
{ // 1. Create a public key value key_t key = ftok(PATH_NAME, PROJ_ID); if (key == -1) { perror("ftok"); exit(1); }






Log("creat key done", DeBug) << "server key : " << TransToHex(key) << endl;

// 2.创建共享内存 -- 建议创建一个全新的共享内存 -- 通信的发起者
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1)
{
    perror("shmget");
    exit(2);
}
Log("shm creat done", DeBug) << "shmid : " << shmid << endl;

//3.将指定的共享内存,挂接到自己的地址空间
char* shmaddr = (char*)shmat(shmid, nullptr, 0);
Log("attach shm done", DeBug) << "shmid : " << shmid << endl;

//这里就是通信逻辑了
//将共享内存看作一个大字符串
//shmaddr就是这个字符串的起始地址
for(;;)
{
    printf("%s\n", shmaddr);//不断打印这个字符串的内容
    if(strcmp(shmaddr, "quit") == 0)
    {
        break;
    }
    sleep(1);
}

//4.将指定的共享内存,从自己的地址空间中去关联
int n = shmdt(shmaddr);
if(n == -1)
{
    perror("shmdt");
    exit(3);
}
Log("detach shm done", DeBug) << "shmid : " << shmid << endl;

//5.删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
n = shmctl(shmid, IPC_RMID, nullptr);
if(n == -1)
{
    perror("shmctl");
    exit(4);
}
Log("delete shm done", DeBug) << "shmid : " << shmid << endl;

return 0;

}


Notice:
(1)
insert image description here
Make sure to create a unique key; *
(2)
insert image description here
Create a new shared memory, 0666 represents the permission of the shared memory;
the size of the shared memory should be an integer multiple of the page, otherwise it will cause waste of space, open more space, but there is no permission to access;
insert image description here
when creating the second time, prompt the shared memory existed;
insert image description here
(3) ipcs -m: view shared memory information;
insert image description here
ipcrm -m shmid: Delete shared memory (cannot be deleted with key)
The life cycle of shared memory depends on the kernel;
unlike files, the life cycle of a file will be recycled if the process exits and no other process is associated with the file; the

insert image description here
insert image description here
perms attribute is the permission of the shared memory.

(4) Therefore, when the process ends, the shared memory still exists, we continue to delete it, using the system interface:
shmctl: delete shared memory
insert image description here
insert image description here
(5) The nattch attribute is the number of shared memory to be mounted. After the shared memory is created, it needs to be mounted in its own process address space;
shmat: mount shared memory
insert image description here
Parameters:
shmid: shared memory id
shmaddr: mount virtual address, directly set to 0, let os mount
shmflg: mount mode
Return value: return the virtual address of addr in shared memory on success, return -1 on failure

Use: use
the return value as shared The starting address of the memory;
insert image description here
shmdt: deassociation
insert image description here
Parameters:
shmaddr: shared memory address
Return value: return 0 if successful, return -1 if failed

  • shmClient.cc
#include "comm.hpp"

int main()
{
    
    
    // 客户端也获取key
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
    
    
        Log("creat key failed", Error) << "client key : " << key << endl;
        exit(1);
    }
    Log("creat key done", DeBug) << "client key : " << key << endl;

    // 获取共享内存
    int shmid = shmget(key, SHM_SIZE, 0);
    if (shmid == -1)
    {
    
    
        Log("creat shm failed", Error) << "client key : " << key << endl;
        exit(2);
    }
    Log("creat shm done", DeBug) << "client key : " << key << endl;


    // 挂接共享内存
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
    
    
        Log("attach shm failed", Error) << "client key : " << key << endl;
    }
    Log("attach shm done", DeBug) << "client key : " << key << endl;
    
    // 使用
    //client将共享内存看作一个char类型的buffer
    //客户端从键盘读取消息,直接读到共享内存中
    while (true)
    {
    
    
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
    
    
            shmaddr[s - 1] = 0;
            if(strcmp(shmaddr, "quit") == 0)//读到quit,客户端退出
            {
    
    
                break;
            }
        }
    }
    
    // char a = 'a';
    // for(; a <= 'z'; a++)
    // {
    
    
    //     //每一次都向shmaddr(共享内存的起始地址)写入
    //     snprintf(shmaddr, SHM_SIZE - 1, 
    //             "hello server, 我是其他进程,我的pid: %d, inc: %c\n", 
    //             getpid(), a);
    //     sleep(2);
    // }

    // 去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
    
    
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "client key : " << key << endl;

    // client不需要删除shm

    return 0;
}

Notice:
(1) The use of shared memory, directly regard the shared memory as a buffer of char type, and write data directly into it
insert image description here
Read the message from the keyboard in stdin, and directly read the address of shmaddr, which is the starting address of the shared memory;

operation result:
Server:
insert image description here
Client:
insert image description here

  • Note:
    (1) As long as both communication parties use shm, one party directly writes data to the shared memory, and the other party can immediately see the data written by the other party; shared memory is the fastest among all inter-process communication, and does not require too much Copy;
    (2) In pipeline communication, multiple copies are required for one communication. The user inputs data from the keyboard to the buffer is one copy, writing data from the buffer to the pipeline file is another copy, and reading from the pipeline file to the buffer The data is a copy, and printing the data from the buffer is another copy;

    insert image description here

(3) The shared memory only needs two copies. The data input from the keyboard is directly written into shm. This is a copy. The data of shm is directly printed out. This is the second copy;
insert image description here

4. Add access control for shared memory

From the above results, it can be seen that even if the client has not mounted the shared memory, the server has already started to read data continuously, which shows that the shared memory does not have access control, which will bring certain concurrency problems ;
However, the pipeline comes with access control, we can use the pipeline communication to add access control to the shared memory;
comm.hpp

#ifndef _COMM_H_
#define _COMM_H_

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include <cstring>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;

#define PATH_NAME "/home/lmx" // 路径,一定保证有权限
#define PROJ_ID 0X66
#define SHM_SIZE 4096 // 共享内存大小,最好是页(4byte)的整数倍

#define FIFO_NAME "./fifo"

class Init
{
    
    
public:
    Init()
    {
    
    
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("creat fifo succsee", Notice) << "\n";
    }

    ~Init()
    {
    
    
        unlink(FIFO_NAME);
        Log("remove fifo succsee", Notice) << "\n";
    }
};


#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    
    
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    
    
    Log("waiting...", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}

void Signal(int fd)
{
    
    
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("aweaking...", Notice) << "\n";
}

void CloseFIFO(int fd)
{
    
    
    close(fd);
}

#endif

Note:
(1) A class is created, and the constructor of the class creates a pipeline file. Once the class instantiates an object and calls the constructor, a pipeline file can be created, followed by the read and write control of the pipeline file;
insert image description here
shmServer.cc

#include "comm.hpp"

string TransToHex(key_t k)
{
    
    
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "0x%x", k);
    return buffer;
}

int main()
{
    
    
    Init init;
    // 对应的程序在加载的时候,会自动构建全局变量,就要调用该类构造函数 -- 创建管道文件
    // 程序退出的时候,全局变量会被析构,会自动删除管道文件

    // 1.创建公共的key值
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key == -1)
    {
    
    
        perror("ftok");
        exit(1);
    }

    Log("creat key done", DeBug) << "server key : " << TransToHex(key) << endl;

    // 2.创建共享内存 -- 建议创建一个全新的共享内存 -- 通信的发起者
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
    
    
        perror("shmget");
        exit(2);
    }
    Log("shm creat done", DeBug) << "shmid : " << shmid << endl;

    // 3.将指定的共享内存,挂接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attach shm done", DeBug) << "shmid : " << shmid << endl;

    // 这里就是通信逻辑了
    // 将共享内存看作一个大字符串
    // shmaddr就是这个字符串的起始地址

    //使用管道进行访问控制
    int fd = OpenFIFO(FIFO_NAME, READ);

    for (;;)
    {
    
    
        Wait(fd);//等待客户端响应,
                    //使用管道文件的访问控制,如果客户端没有向管道内写入数据,那么该进程会一直阻塞
        printf("%s\n", shmaddr); // 不断打印这个字符串的内容
        if (strcmp(shmaddr, "quit") == 0)
        {
    
    
            break;
        }
        sleep(1);
    }

    CloseFIFO(fd);

    // 4.将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
    
    
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "shmid : " << shmid << endl;

    // 5.删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    if (n == -1)
    {
    
    
        perror("shmctl");
        exit(4);
    }
    Log("delete shm done", DeBug) << "shmid : " << shmid << endl;

    return 0;
}

Note:
(1) First create a pipeline file on the server side
insert image description here
(2) Before reading the data in the shared memory, first read the pipeline data to see if the client responds;
insert image description here
shmClient.cc

#include "comm.hpp"

int main()
{
    
    
    // 客户端也获取key
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if (key < 0)
    {
    
    
        Log("creat key failed", Error) << "client key : " << key << endl;
        exit(1);
    }
    Log("creat key done", DeBug) << "client key : " << key << endl;

    // 获取共享内存
    int shmid = shmget(key, SHM_SIZE, 0);
    if (shmid == -1)
    {
    
    
        Log("creat shm failed", Error) << "client key : " << key << endl;
        exit(2);
    }
    Log("creat shm done", DeBug) << "client key : " << key << endl;


    // 挂接共享内存
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr)
    {
    
    
        Log("attach shm failed", Error) << "client key : " << key << endl;
    }
    Log("attach shm done", DeBug) << "client key : " << key << endl;
    
    // 使用
    //client将共享内存看作一个char类型的buffer
    //客户端从键盘读取消息,直接读到共享内存中

    //使用管道进行访问控制
    int fd = OpenFIFO(FIFO_NAME, WRITE);

    while (true)
    {
    
    
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
    
    
            shmaddr[s - 1] = 0;
            Signal(fd);//向管道写入数据
            if(strcmp(shmaddr, "quit") == 0)//读到quit,客户端退出
            {
    
    
                break;
            }
        }
    }

    CloseFIFO(fd);
    
    // 去关联
    int n = shmdt(shmaddr);
    if (n == -1)
    {
    
    
        perror("shmdt");
        exit(3);
    }
    Log("detach shm done", DeBug) << "client key : " << key << endl;

    // client不需要删除shm

    return 0;
}

Note:
(1) Before writing data to the shared memory, first write a signal to the pipeline, indicating that the client is ready to write data, and wake up the server:
insert image description here

operation result:
When the server is running but the client does not respond, the server will wait for the client to respond, and the process will be blocked;
insert image description here
when the client responds, the server will be woken up to read the data in the shared memory:
insert image description here
Exit:
insert image description here

2. Semaphore (conceptual understanding)

1. Concept

  • Based on the understanding of shared memory:
    in order to allow inter-process communication, so that different processes can see the same resource, all the inter-process communication we talked about before is based on this method;
    and let different processes see the same resource Shared resources, such as shared memory, also bring some timing problems, which will cause data inconsistency

  • concept
    (1) Critical resources:A common resource seen by multiple processes (execution flows);
    (2) Critical section:Your own process, code that accesses critical resources;
    (3) Mutual exclusion:In order to better maintain the critical area, only one process can enter the critical area at any time in the multi-execution flow;
    (4) Atomicity:Either don't do it or do it, there is no intermediate state;

2. Signal amount

We usually buy tickets before watching a movie. The seats in the movie theater are equivalent to resources. When you buy a ticket, the seat really belongs to you. The essence of buying a ticket is the reservation mechanism for the seat
; Part of the critical resources, the process cannot be used directly to use the critical resources, you need to firstApply for semaphore;
The essence of the semaphore is acounter

  • Apply for semaphore:
    (1) The essence of applying for a semaphore is to let the semaphore technology implement - -;
    (2) If the application for a semaphore is successful, the required resources must be reserved for the process inside the critical resources. The essence of applying for a semaphore isA reservation mechanism for critical resources;

  • Release the semaphore:
    To release the semaphore is to set the counter ++;

If the semaphore counter is set toglobal variable(Integer n, stored in shared memory), so that multiple processes can see the same global variable, and everyone can apply for a semaphore, which is not possible; because when the CPU
executes the n++ instruction, it actually executes three statements :
(1) Load the data in the memory to the register in the CPU (read instruction);
(2) n– (execute instruction);
(3) Write the value modified by the CPU into the memory (write instruction);

and execute When the flow is executed, it can be switched at any time;
for example:
if the semaphore is 5 at the beginning, when the client applies for the semaphore, the first step is switched, and the data in the register is saved as context data ,
the server applies for the semaphore. If the server reduces the semaphore to 2, the server is switched at this time. When the client comes back, the client
will restore the context data, restore the semaphore to 5, and then apply for the semaphore. At this time The semaphore becomes 4;

There is only one set of registers, which are shared by all execution flows, but the data in the registers belong to each execution flow and the context data of the execution flow; this design will cause the
semaphore to be unsafe;
therefore, apply and release the semaphore These two operations must beatomic
insert image description here

Guess you like

Origin blog.csdn.net/kissland96166/article/details/132105277