Table of contents
1. Introduction to shared memory
(2). Advantages of shared memory
(3). Disadvantages of shared memory
3. Shared memory and access control
1. Introduction to shared memory
(1). What is shared memory
Shared memory is essentially an area in memory used for inter-process communication. This memory space is allocated and managed by the operating system . Similar to the file system, when the operating system manages shared memory, it not only has memory data blocks, but also creates a corresponding structure to record the attributes of the shared memory for easy management.
Therefore, there is not only one copy of shared memory, you can apply for more than one according to your needs.
When communicating between processes, the address of the shared memory will be obtained, the write-end process writes data, and the read-end process completes the data reading by directly accessing the memory.
(2). Advantages of shared memory
Compared with pipelines, shared memory can not only be used for communication between non-parent-child processes, but also access data faster than pipelines . This is due to the fact that the communication directly accesses the memory, while the pipeline needs to access the file through the operating system before obtaining the memory data.
(3). Disadvantages of shared memory
When used for inter-process communication, shared memory itself does not support blocking wait operations. This is because when the reader reads the data, the data will not be cleared in memory. Therefore, the read end and the write end can access the memory space at the same time, that is, full duplex. Because the essence of shared memory is that the process directly accesses the memory, it cannot actively stop reading. If the reading end does not limit it, it will continue to read data. Similarly, the write end will continue to write data. In other words, shared memory itself has no access controls .
2. Use of shared memory
(1). Create—shmget
If you want to use shared memory, you must first create shared memory.
①key
shmget will create a shared memory according to the key value, so when creating multiple shared memories, each key value must be unique.
To obtain the key value, you can use the library function ftok to obtain a unique key_t type value.
The parameter pathname is a path, which must be a real and accessible path.
The parameter proj_id is an int type number, and a non-zero value must be passed in.
The key_t value is returned successfully, and -1 is returned on failure.
The ftok function will generate a unique key_t return value through an algorithm based on the path and proj_id.
In multi-process communication, both communication parties need to use the same key value, so the ftok parameters used by both parties should be consistent.
②size
This parameter is used to determine the shared memory size.
Generally speaking, it is an integer multiple of 4096, because the size of a memory block is 4KB or 4096B. Therefore , even if the size of the space we need is not an integer multiple of the block size, the operating system actually allocates multiples of the block. But when in use, those excess allocated space exceeding size cannot be accessed .
③shmflg
This parameter is used to determine shared memory properties.
In use: flag bit | memory permission
There are two flag parameters: IPC_CREAT, IPC_EXCL
There are two commonly used methods:
Way | meaning |
---|---|
shmget(..., IPC_CREAT | permission) | If the creation fails, no error will be reported and the existing shmid will be returned. |
shmget(..., IPC_CREAT | IPC_EXCL | permissions) | Creation failure returns -1 |
It is worth noting that PC_EXCL cannot be used alone.
Usually, in multi-process communication, the creator uses IPC_CREAT | IPC_EXCL, and the receiver uses 0.
④ return value
The return value is int type, called shmid. Each shared memory will have a shmid, which is used to pass parameters when connecting and detaching.
(2). Connection—shmat
After the shared memory is created, it cannot be used directly. It needs to find the memory address before using it, that is, to connect.
shmid is the return value of shmget.
shmaddr is used to determine where to hang the shared memory in the virtual address of the process. Generally, filling in nullptr means letting the kernel determine the location by itself.
shmflg is used to determine the mount mode, generally fill in 0 .
If the connection is successful, it returns the starting address of the shared memory in the process, and if it fails, it returns -1.
(3). Separation—shmdt
When you are done using it, you need to detach the mounted shared memory.
shmaddr is the same as shmat, which is the address position of the shared memory in the process, and generally fills in nullptr.
Returns 0 if the separation is successful, and -1 if it fails.
(4). Destroy—shmctl
The interface itself is used to control shared memory and can be used for destruction.
shmid is no longer introduced, cmd is passed to IPC_RMID, and buf is passed to nullptr.
Returns 0 on success and -1 on failure.
(5). View -ipcs
This command is a system command.
When using it, you can view all the current shared memory.
ipcs -m
(6).Delete—ipcrm
Delete by specifying the shared memory shmid.
ipcrm -m [shmid]
(7). Read and write
After calling shmat, an address will be returned, the reader can directly read the address data, and the write terminal can directly write to the address.
//读端, 将共享内存数据读取到文件,此处为显示器文件
char* p = (char*)shmat(...);
write(1, p, sizeof p);
//写端,将文件中数据写入共享内存,此处为键盘文件
char* p = (char*)shmat(...);
read(0, p, 4096);
3. Shared memory and access control
(1). Add access control
Through the first part of the blog, we know that shared memory does not support access control, so can we add access control to shared memory-it is absolutely possible.
The method is to borrow the access control of the named pipe, that is, blocking.
First of all, we have the following code, the code is that the reader keeps reading the data from the write end until the write end enters quit.
//写端
int main()
{
key_t key = ftok(".", 131);
int shmid = shmget(key, 4096, IPC_CREAT|0660);//获取shmid
char* p = (char*)shmat(shmid, nullptr, 0);//连接
while(1){
ssize_t s = read(0, p, 4096);//写入shm
p[s - 1] = 0;
assert(s > 0);
(void)s;
}
shmdt(p);//分离
return 0;
}
//读端
int main()
{
key_t key = ftok(".", 131);
int shmid = shmget(key, 4096, IPC_CREAT|IPC_EXCL|0660);//创建
char* p = (char*)shmat(shmid, nullptr, 0);//连接
while(1){
assert(p != nullptr);
if(strcmp(p, "quit") == 0)break;
printf("%s\n", p);//读取shm中数据
sleep(1);
}
shmdt(p);//分离
shmctl(shmid, IPC_RMID, nullptr);//销毁
return 0;
}
However, because the shared memory cannot be accessed and controlled, the reader will always read the data. Even if we add the sleep function, the problem cannot be fundamentally solved.
The solution is to add the read end and write end of the pipeline respectively at the read end and the write end. Because we know that the pipe reader will block until it reads data from the writer, we place the pipe reader before the shared memory read and the pipe write after the shared memory write.
In this way, when the shm write end writes data, it will trigger the pipeline write end to write data, and when the pipeline write end writes data, the pipeline read end will stop blocking, and then execute the shm read end.
The legend is as follows:
code show as below:
//写端
int main()
{
key_t key = ftok(".", 131);
int shmid = shmget(key, 4096, IPC_CREAT|0660);//获取shmid
char* p = (char*)shmat(shmid, nullptr, 0);//连接
int fd = open(..., O_WRONLY);//打开命名管道
while(1){
ssize_t s = read(0, p, 4096);//写入shm
p[s - 1] = 0;
assert(s > 0);
(void)s;
char i[4] = { 0 };
write(fd, i, sizeof i);//写入管道
}
shmdt(p);//分离
close(fd);
return 0;
}
//读端
int main()
{
int i = mkfifo(PATH_FIFO, 0660);//创建管道
assert(i >= 0);
key_t key = ftok(".", 131);
int shmid = shmget(key, 4096, IPC_CREAT|IPC_EXCL|0660);//创建
char* p = (char*)shmat(shmid, nullptr, 0);//连接shm
int fd = open(..., O_RDONLY);//连接管道
while(1){
char buf[4];
read(fd, buf, sizeof buf);//管道等待读取,阻塞
assert(p != nullptr);
if(strcmp(p, "quit") == 0)break;
printf("%s\n", p);//读取shm中数据
sleep(1);
}
shmdt(p);//分离
shmctl(shmid, IPC_RMID, nullptr);//销毁
close(fd);
return 0;
}
(2). Possible pitfalls
When adding access control, there will be a possible trap, that is, can the named pipe be opened (open) before creating shm?
No, because opening a pipe requires the read and write ends to be open at the same time to continue, otherwise it will block.
If the write end is blocked, it is fine. When the read end creates shm, the write end creation fails and returns shmid, but if the read end is blocked, then after the write end creates shm, the read end fails because of the addition of IPC_EXCL. Return -1, and then shmat also fails to return nullptr, and then the address obtained by the reader is empty.
Pay attention to encapsulation for simple modules, and layering for complex modules——Unnamed
Please correct me if there is any mistake