共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效带来的问题是,我们必须使用其他辅助手段来同步进程对内存的访问,否则会产生竞态条件(一般我们与信号量结合使用)。因此,共享内存通常和其他进程间通信方式一起使用。
Linux共享内存的API都定义在sys/shm.h头文件中,包括4个系统调用:shmget、shmat、shmdt和shmctl
#include <sys/shm.h>
int sys_shmget (key_t key, int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int sys_shmdt (char *shmaddr);
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
l shmget系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。
和shmget系统调用一样,key参数是一个键值,用来标识一段全局唯一的共享内存。size参数指定共享的大小,单位是字节。如果是创建新的共享内存,则size值必须被指定。如果是获取已经存在的共享内存,则可以把size设置为0。
shmflg参数的使用和含义与semget系统调用的semflag参数相同
shmget成功时返回一个正整数,它是共享内存的标识符。shmget失败时返回-1,并设置errno。如果shmget用于创建共享内存,则这段共享内存的所有字节都被初始化为0,与之关联的内核数据结构shmid_ds将被创建并初始化。
l shmat和shmdt用于关联/分离共享内存,共享内存被创建/获取后,我们不能立即访问它,需要先将它关联到进程的地址空间中,使用完后,再从地址空间分离。
其中shmid参数是由shmget调用返回的共享内存标识符。shmaddr参数指定共享内存关联到进程的哪块地址空间,最终的效果还是受到shmflg参数的可选标志SHM_RND的影响:
如果shmaddr为NULL,则被关联的地址由操作系统选择。这是推荐的做法,以确保可移植性。
l shmctl用于控制共享内存的某些属性,shmid参数是由shmget调用返回的共享内存标识符。cmd参数指定要执行的命令。shmctl成功时的返回值取决于cmd参数,失败时返回-1,并设置errno。
共享内存程序示例
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo * __buf;
};
void pv(int sem_id, int op)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = op;
sem_b.sem_flg = SEM_UNDO;
semop(sem_id, &sem_b, 1);
}
int main(void)
{
int sem_id = semget(IPC_PRIVATE, 1, 0666);
int shm_id = shmget(IPC_PRIVATE, 4, 0666);
union semun sem_un;
sem_un.val = 1;
semctl(sem_id, 0, SETVAL, sem_un);
pid_t pid = fork();
if(pid < 0)
err_sys("fork");
else if(pid == 0)
{
char *shmp;
pv(sem_id, -1);
shmp = shmat(shm_id, NULL, 0);
if(shmp == (char *)(-1))
err_sys("child shmat");
*(int *)shmp = 10;
shmdt(shmp);
printf("child get the sem and would sleep 2s.\n");
sleep(2);
pv(sem_id, 1);
exit(0);
}
else
{
char *shmp;
sleep(1);
pv(sem_id, -1);
shmp = shmat(shm_id, NULL, 0);
if(shmp == (char *)(-1))
err_sys("child shmat");
printf("shm: %d\n", *(int *)shmp);
shmdt(shmp);
printf("parent get the sem and would sleep 2s.\n");
sleep(2);
pv(sem_id, 1);
}
waitpid(pid, NULL, 0);
shmctl(shm_id, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID, sem_un);
return 0;
}
参考:
1、《Linux高性能服务器编程》 第13章 多进程编程/共享内存
2、Linux 进程间通信 - 共享内存 (这个讲的比较详细....)