概述:知识点:
- Linux 共享内存方式原理介绍
- Linux 共享内存相关 API 介绍
0x01 Linux 进程间通信方式之共享内存概述
在 Linux 中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。如下图所示:

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。
对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
0x02 Linux 共享内存使用步骤
- 创建共享内存。也就是从内存中获得一段共享内存区域,这里用到的函数是
shmget() - 映射共享内存。也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是
shmat()。到这一步就可以使用这段共享内存了,也就是可以使用不带缓冲的 I/O 读写命令对其进行操作。 - 撤销映射。使用完共享内存就需要撤销,用到的函数是
shmdt()。
其中用到的 Linux 系统函数介绍如下:
shmget()函数创建共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
/*
参数:
key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中 IPC_PRIVATE 用于创建当前进程私有的共享内存
size:共享内存的大小
shmflg:同 open() 函数的权限位
返回值:
成功:共享内存段标识符
失败:-1
*/shmat()函数映射共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
char *shmat(int shmid, const void *shmaddr, int shmflg);
/*
参数:
shmid:要映射的共享内存标识符
shmaddr:将共享内存映射到指定地址,注意这里的是 void 型指针,如果是 0 则系统将自动分配地址并将共享内存映射到调用进程的地址空间
shmflg:SHM_RDONLY(共享内存只读),0(可读写)
返回值:
成功:被映射的段地址
失败:-1
*/实验一 shmem_demo
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 256
int main(int argc, char *argv[])
{
pid_t pid;
int shmid;//共享内存标识符
char *shm_addr = NULL;
char flag[] = "SHIYANLOU";//标志字符串
char buf[BUFFER_SIZE] = {0};
/* 创建共享内存 */
shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
if(shmid < 0)
{
perror("shmget error");
exit(1);
}
printf("Create shared-memory: %d \n", shmid);
/* 显示共享内存情况 */
system("ipcs -m");
/* 创建子进程 */
pid = fork();
if(pid < 0)
{
perror("fork error");
exit(1);
}
else if(pid == 0)
{
/* 子进程 */
printf("[%d]Child prograss ...\n", getpid());
//映射共享内存
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Child: shmat");
exit(1);
}
printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
/* 显示内存情况 */
system("ipcs -m");
while(strncmp(shm_addr, flag, strlen(flag)))
{
printf("[%d]Child : Wait for enable data...\n", getpid());
sleep(5);
}
/* 共享内存有效数据显示 */
strcpy(buf, shm_addr+strlen(flag));
printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);
/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("shmdt error");
exit(1);
}
printf("[%d]Child : Deattach shared-memory\n", getpid());
system("ipcs -m");
/* 删除共享内存 */
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child : shmctl(IPC_RMID");
exit(1);
}
printf("[%d]Child : Delete shared-memory\n", getpid());
system("ipcs -m");
/* 子进程退出 */
exit(0);
}
else
{
/* 父进程 */
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Parent: shmat");
exit(1);
}
printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
sleep(1);
printf("\nInput some string:\n");
fgets(buf, BUFFER_SIZE, stdin);
strncpy(shm_addr + strlen(flag), buf, strlen(buf));
strncpy(shm_addr, flag, strlen(flag));
/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("Parent : shmdt error");
exit(1);
}
printf("[%d]Parent : Deattach shared-memory\n", getpid());
system("ipcs -m");
}
waitpid(pid, NULL, 0);
printf("\nFinished...\n");
return 0;
}在 VS Code 平台下的终端窗口使用 gcc 工具完成可执行文件编译:
gcc shmem_demo.c -o shmem_demo
./shmem_demo执行结果如下:

实验二——shmem_sem.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 256
/* 定义联合体 */
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int init_sem(int sem_id, int init_val)
{
union semun sem_union;
sem_union.val = init_val;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
{
perror("init semaphore error.\n");
return -1;
}
return 0;
}
int del_sem(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
// perror("Delete semaphore error.\n");
return -1;
}
return 0;
}
int sem_p(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
perror("P error.\n");
return -1;
}
return 0;
}
int sem_v(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
perror("P error.\n");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
pid_t pid;
int sem_id;
int shmid;
char *shm_addr = NULL;
char buf[BUFFER_SIZE];
//创建信号量
sem_id = semget(ftok(".", 'a'), 1, 0666|IPC_CREAT);
init_sem(sem_id, 0);
//创建共享内存
shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666);
if(shmid < 0)
{
perror("shmget");
exit(1);
}
printf("Create Shared-memory : %d\n", shmid);
//显示共享内存情况
system("ipcs -m");
pid = fork();
if(pid < 0)
{
perror("fork() error");
exit(1);
}
else if(pid == 0)
{
//子进程
//映射共享内存
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Child: shmat");
exit(1);
}
printf("[%d]Child : Attach shared-memory [%p]\n", getpid(), shm_addr);
/* 显示内存情况 */
system("ipcs -m");
printf("[%d]Child : Wait for enable data...\n", getpid());
/* 共享内存有效数据显示 */
while(1)
{
memset(buf, 0, BUFFER_SIZE);
sem_p(sem_id);
strcpy(buf, shm_addr);
memset(shm_addr, 0, BUFFER_SIZE);
printf("[%d]Child : Shared-memory: %s\n", getpid(), buf);
if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
{
printf("[%d]Child Will Quit.\n", getpid());
break;
}
}
sem_v(sem_id);
del_sem(sem_id);
/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("shmdt error");
exit(1);
}
printf("[%d]Child : Deattach shared-memory\n", getpid());
system("ipcs -m");
/* 删除共享内存 */
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child : shmctl(IPC_RMID");
exit(1);
}
printf("[%d]Child : Delete shared-memory\n", getpid());
system("ipcs -m");
/* 子进程退出 */
exit(0);
}
else
{
//父进程
shm_addr = shmat(shmid, 0, 0);
if(shm_addr == (char *)-1)
{
perror("Parent: shmat");
exit(1);
}
printf("[%d]Parent : Attach shared-memory : [%p]\n", getpid(), shm_addr);
sleep(1);
while(1)
{
memset(buf, 0, BUFFER_SIZE);
printf("\nInput some string:\n");
fgets(buf, BUFFER_SIZE, stdin);
strncpy(shm_addr, buf, strlen(buf));
sem_v(sem_id);
if((!strncmp(buf, "q", 1)) || (!strncmp(buf, "Q", 1)))
{
printf("[%d]Parent Will Quit.\n", getpid());
break;
}
}
/* 解除共享内存映射 */
if((shmdt(shm_addr)) < 0)
{
perror("Parent : shmdt error");
exit(1);
}
printf("[%d]Parent : Deattach shared-memory\n", getpid());
system("ipcs -m");
}
waitpid(pid, NULL, 0);
printf("\nFinished...\n");
return 0;
}