Analysis of the Linux Kernel (11) Interprocess Communication - Shared Memory Shared Memory

Shared memory


Shared memory is one of the easiest ways to communicate between processes.

Shared memory is a memory area reserved by the system for communication between multiple processes.

Shared memory allows two or more processes to access the same piece of memory, just as the malloc() function returns pointers to the same physical memory area to different processes. When a process changes the content of this address, other processes will be aware of the change.

About shared memory


When a program is loaded into memory, it is divided into blocks called pages.

Communication will exist between two pages of memory or between two separate processes.

In short, when a program wants to communicate with another program, the memory will generate a common memory area for the two programs. This area of ​​memory shared by two processes is called shared memory

Because all processes share the same piece of memory, shared memory is the most efficient of the various inter-process communication methods. Accessing a shared memory area is as fast as accessing a process-specific memory area, and does not require a system call or other process that needs to cut into the kernel. At the same time it also avoids all kinds of unnecessary duplication of data.

If there is no concept of shared memory, that one process cannot access the memory part of another process, thus causing shared data or communication failure. Because the system kernel does not synchronize access to shared memory, you must provide your own synchronization measures.

A common solution to these problems is synchronization through the use of semaphores . However, only one process in our program has access to shared memory, so by focusing on the shared memory mechanism, we avoid cluttering the code with synchronization logic.

In order to simplify the integrity of shared data and avoid concurrent access to data, the kernel provides a mechanism for dedicated access to shared memory resources. This is called a mutex or mutex object

For example, a process is not allowed to read information from shared memory until the data is written, and two processes are not allowed to write data to the same shared memory address at the same time.

When a process wants to communicate with another process, it will run in the following order:

  • Get the mutex object, lock the shared area.

  • Write the data to be communicated into the shared area.

  • Release the mutex object.

When a process reads from this area, it repeats the same steps, but turns the second step into a read.

memory model


To use a piece of shared memory

  • the process must allocate it first

  • Each process that subsequently needs to access this shared memory block must bind this shared memory to its own address space

  • When the communication is complete, all processes will be detached from the shared memory , and the shared memory block will be freed by one process

In the /proc/sys/kernel/directory, some restrictions on shared memory are recorded, such as the maximum number of bytes of a shared memory area, the maximum number shmmaxof shared memory area identifiers in the system, shmmnietc., which can be adjusted manually, but this is not recommended.

write picture description here

Understanding the Linux system memory model can help explain this binding process.

Linux system memory model


In a Linux system, the virtual memory of each process is divided into many pages. These memory pages contain the actual data. Each process maintains a mapping from memory addresses to virtual memory pages. Although each process has its own memory address, different processes can map the same memory page into their own address space at the same time, thus achieving the purpose of sharing memory.

Allocating a new shared memory block creates new memory pages. Because all processes want to share access to the same piece of memory, only one process should create a new piece of shared memory. Allocating an already existing block again does not create a new page, it just returns an identifier that identifies the block.

If a process needs to use this shared memory block, it first needs to bind it to its own address space.

This creates a mapping from the virtual address of the process itself to the shared page. When the use of shared memory ends, the mapping relationship will be deleted.

When no more processes need to use the shared memory block, there must be one (and only one) process responsible for freeing the shared memory page.

The size of all shared memory blocks must be an integer multiple of the system page size. System page size refers to the number of bytes contained in a single memory page in the system. On Linux systems, the memory page size is 4KB, but you should still get this value by calling getpagesize.

The implementation of shared memory is divided into two steps:

  • To create shared memory, use the shmget function.

  • Map the shared memory, map the created shared memory to the specific process space, and use the shmat function.

functions for shared memory


The use of shared memory mainly includes the following APIs: ftok(), shmget(), shmat(), shmdt()and shmctl().

#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg); int shmctl(int shm_id, int cmd, struct shmid_ds *buf); int shmdt(const void *shm_addr); int shmget(key_t key, size_t size, int shmflg);

 

write picture description here

Similar to semaphores, it is usually necessary to include the two header files sys/types.h and sys/ipc.h before including the shm.h file.

Get an ID number with the ftok() function


Application note, in IPC, we often use the value of key_t to create or open semaphores, shared memory and message queues.

key_t ftok(const char *pathname, int proj_id);

 

parameter describe
pathname Must exist in the system and be accessible by the process
proj_id An integer value between 1 and 255, typically an ASCII value.

When executed successfully, a key_t value will be returned, otherwise -1 will be returned. We can use strerror(errno) to determine the specific error message.

Considering that the application system may be applied on different hosts, you can directly define a key instead of obtaining it with ftok:

#define IPCKEY 0x344378

 

Create shared memory


A process allocates a shared memory block by calling shmget (Shared Memory GET, get shared memory).

int shmget(key_t key ,int size,int shmflg)

 

parameter describe
key A key used to identify the shared memory block
size Specifies the size of the requested memory block
shmflg Manipulating shared memory identifiers

Return Value: Returns the shared memory indicator on success, -1 on failure.

  • The second parameter key of this function is a key used to identify the shared memory block.

Processes unrelated to each other can gain access to the same shared memory block by specifying the same key. Unfortunately, other programs may also pick the same specific value as the key for their own allocation of shared memory, resulting in a conflict.

Using the special constant IPC_PRIVATE as the key value ensures that the system creates a brand new shared memory block. |

key identifies the key value of shared memory: 0/IPC_PRIVATE. When the value of key is IPC_PRIVATE, the function shmget will create a new shared memory; if the value of key is 0 and the IPC_PRIVATE flag is set in the parameter, a new shared memory will also be created.

  • The second parameter size of this function specifies the size of the requested memory block.

Because these memory blocks are allocated in page units, the actual allocated memory block size will be expanded to an integer multiple of the page size.

  • The third parameter, shmflg, is a set of flags to shmget by bitwise ORing of specific constants. These specific constants include:

IPC_CREAT: This flag indicates that a new shared memory block should be created. By specifying this flag, we can create a new shared memory block with the specified key value.

IPC_EXCL: This flag can only be used with IPC_CREAT. When this flag is specified, if a shared memory block with this key already exists, the shmget call will fail. That is, this flag will cause the thread to acquire an "exclusive" block of shared memory. If this flag is not specified and a shared memory block with the same key exists in the system, shmget will return the already established shared memory block instead of recreating one.

Mode flag: This value consists of 9 bits, which represent the access rights of the owner, group and other users to the memory block.

The bit that represents execute permission is ignored. An easy way to specify access rights is to use

map shared memory


shmat() is a function used to allow the process to access a piece of shared memory and map this memory area to the virtual address space of the process.

int shmat(int shmid,char *shmaddr,int flag)

 

parameter describe
shmid The ID of the shared memory is the shared memory identifier returned by the shmget function
shmaddr is the starting address of the shared memory. If shmaddr is 0, the kernel will map the shared memory to the selected location in the address space of the calling process; if shmaddr is not 0, the kernel will map the shared memory to the location specified by shmaddr. So generally set shmaddr to 0.
shmflag is the operation mode of this process for this memory. If it is SHM_RDONLY, it is read-only mode. Others are read and write modes

On success, this function returns the starting address of shared memory. Returns -1 on failure.

For a process to gain access to a piece of shared memory, the process must first call shmat (SHared Memory Attach, bound to shared memory).

Pass the shared memory identifier SHMID returned by shmget to this function as the first argument.

The second parameter of this function is a pointer to the process memory address you want to use to map the shared memory block; if you specify NULL, Linux will automatically choose a suitable address for mapping. The third parameter is a flag bit that contains the following options:

SHM_RND indicates that the address specified by the second parameter should be rounded down to an integer multiple of the memory page size. If you do not specify this flag, you will have to manually align the shared memory block size to page size when calling shmat.
SHM_RDONLY indicates that this memory block will only allow read operations and prohibit writes. If this function is called successfully, it will return the address corresponding to the bound shared memory block. The child process created by the fork function also inherits these shared memory blocks;

They can actively detach from these shared memory blocks if needed. When a process no longer uses a shared memory block

Shared memory unmap


When a process no longer needs shared memory, it needs to be removed from the process address space.

int shmdt(char *shmaddr)

 

parameter describe
shmaddr the starting address of that shared memory

Returns 0 on success. Returns -1 on failure.

It should be detached from the shared memory block by calling the shmdt (Shared Memory Detach, detached from the shared memory block) function. Pass the address returned by the shmat function to this function. If the process that released the memory block is the last process to use the memory block, the memory block will be deleted. A call to exit or any of the exec-family functions automatically takes the process out of the shared memory block.

controlled release


shmctl controls the use of this shared memory

function prototype

int  shmctl( int shmid , int cmd , struct shmid_ds *buf );

 

parameter describe
shmid is the ID of the shared memory.
cmd control commands
buf A struct pointer. When IPC_STAT, the obtained status is placed in this structure. If you want to change the state of shared memory, specify it with this structure.

The value of cmd is as follows

cmd describe
IPC_STAT Get the status of shared memory
IPC_SET Change the state of shared memory
IPC_RMID delete shared memory

Return Value: Success: 0 Failure: -1

Calling the shmctl("Shared Memory Control", control shared memory) function will return information about a shared memory block. At the same time shmctl allows programs to modify this information.

The first parameter of this function is a shared memory block identifier.
To get information about a shared memory block, pass IPC_STAT as the second parameter to the function and a pointer to a struct shmid_ds object as the third parameter.

To delete a shared memory block, IPC_RMID should be used as the second parameter and NULL should be used as the third parameter. The shared memory block is deleted when the last process bound to the shared memory block detaches from it.

You should use shmctl to free each shared memory block at the end of its use to prevent exceeding the limit on the total number of shared memory blocks allowed by the system. Calling exit and exec takes the process out of the shared memory block, but does not delete the memory block. For other descriptions of operations on shared memory blocks, refer to the man page for the shmctl function.

Example


Simply map a piece of shared memory

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

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h> #define IPCKEY 0x366378 typedef struct st_setting { char agen[10]; unsigned char file_no; }st_setting; int main(int argc, char** argv) { int shm_id; //key_t key; st_setting *p_setting; // 首先检查共享内存是否存在,存在则先删除 shm_id = shmget(IPCKEY , 1028, 0640); if(shm_id != -1) { p_setting = (st_setting *)shmat(shm_id, NULL, 0); if (p_setting != (void *)-1) { shmdt(p_setting); shmctl(shm_id,IPC_RMID,0) ; } } // 创建共享内存 shm_id = shmget(IPCKEY, 1028, 0640 | IPC_CREAT | IPC_EXCL); if(shm_id == -1) { printf("shmget error\n"); return -1; } // 将这块共享内存区附加到自己的内存段 p_setting = (st_setting *)shmat(shm_id, NULL, 0); strncpy(p_setting->agen, "gatieme", 10); printf("agen : %s\n", p_setting->agen); p_setting->file_no = 1; printf("file_no : %d\n",p_setting->file_no); system("ipcs -m");// 此时可看到有进程关联到共享内存的信息,nattch为1 // 将这块共享内存区从自己的内存段删除出去 if(shmdt(p_setting) == -1) perror(" detach error "); system("ipcs -m");// 此时可看到有进程关联到共享内存的信息,nattch为0 // 删除共享内存 if (shmctl( shm_id , IPC_RMID , NULL ) == -1) { perror(" delete error "); } system("ipcs -m");// 此时可看到有进程关联到共享内存的信息,nattch为0 return EXIT_SUCCESS; } 

 

write picture description here

ipcrm command to delete shared memory

After using shared memory, end the program exits. If you did not delete the shared memory with shmctl() in the program, be sure to delete the shared memory with the ipcrm command on the command line. If you don't care, it's just sitting there.
Briefly explain the ipcs command and the ipcrm command.

Get ipc information:

usage : ipcs -asmq -tclup 
    ipcs [-s -m -q] -i id ipcs -h for help. m 输出有关共享内存(shared memory)的信息 -q 输出有关信息队列(message queue)的信息 -s 输出有关“遮断器”(semaphore)的信息

 

delete ipc

usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
          [-Q msgkey] [-M shmkey] [-S semkey] ... ]

 

Procedure for communication between two ends


reader program


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define N 64 typedef struct { pid_t pid; char buf[N]; } SHM; void handler(int signo) { //printf("get signal\n"); return; } int main() { key_t key; int shmid; SHM *p; pid_t pid; if ((key = ftok(".", 'm')) < 0) { perror("fail to ftok"); exit(-1); } signal(SIGUSR1, handler);//注册一个信号处理函数 if ((shmid = shmget(key, sizeof(SHM), 0666|IPC_CREAT|IPC_EXCL)) < 0) { if (EEXIST == errno)//存在则直接打开 { shmid = shmget(key, sizeof(SHM), 0666); p = (SHM *)shmat(shmid, NULL, 0); pid = p->pid; p->pid = getpid();//把自己的pid写到共享内存 kill(pid, SIGUSR1); } else//出错 { perror("fail to shmget"); exit(-1); } } else//成功 { p = (SHM *)shmat(shmid, NULL, 0); p->pid = getpid(); pause(); pid = p->pid;//得到写端进程的pid } while ( 1 ) { pause();//阻塞,等待信号 if (strcmp(p->buf, "quit\n") == 0) exit(0);//输入"quit结束" printf("read from shm : %s", p->buf); kill(pid, SIGUSR1);//向写进程发SIGUSR1信号 } return 0; } 

 

writer program


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define N 64 typedef struct { pid_t pid; char buf[N]; } SHM; void handler(int signo) { //printf("get signal\n"); return; } int main() { key_t key; int shmid; SHM *p; pid_t pid; if ((key = ftok(".", 'm')) < 0) { perror("fail to ftok"); exit(-1); } signal(SIGUSR1, handler); // 注册一个信号处理函数 if ((shmid = shmget(key, sizeof(SHM), 0666 | IPC_CREAT | IPC_EXCL)) < 0) { if (EEXIST == errno) // 存在则直接打开 { shmid = shmget(key, sizeof(SHM), 0666); p = (SHM *)shmat(shmid, NULL, 0); pid = p->pid; p->pid = getpid(); kill(pid, SIGUSR1); } else//出错 { perror("fail to shmget"); exit(-1); } } else//成功 { p = (SHM *)shmat(shmid, NULL, 0); p->pid = getpid(); // 把自己的pid写到共享内存 pause(); pid = p->pid; // 得到读端进程的pid } while ( 1 ) { printf("write to shm : "); fgets(p->buf, N, stdin); // 接收输入 kill(pid, SIGUSR1); // 向读进程发SIGUSR1信号 if (strcmp(p->buf, "quit\n") == 0) break; pause(); // 阻塞,等待信号 } shmdt(p); shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 return 0; } 

 

write picture description here

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325347532&siteId=291194637