上节和上上节我们分享了Linux进程间通信的管道、消息队列、信号以及信号量的基本原理和实践,文章如下:
这节我们就来分享一下Linux的最后一种进程间通信的方式:共享内存。
1、什么是共享内存
共享内存就是两个不相关的进程之间可以直接访问同一段内存,共享内存在两个正在运行的进程之间共享和传递数据起到了非常有效的方式。在不同的进程之间共享的内存通常安排为同一段物理内存,进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以直接访问共享内存中的地址。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程;其实就是映射一段能够被其它内存所访问到的内存,这段内存由一个进程创建,但是多个进程都可以去访问。共享内存是最快的IPC方式,它是通过其它通信方式的效率不足而专门设计的。往往都是和其它通信机制配合使用,来实现进程间的同步和通信。
共享内存的使用和信号量其实也是差不多的,都是使用接口的形式,共享内存的接口比信号量的接口更加的简单,我们一起去了解下共享内存的使用。
共享内存函数由shmget、shmat、shmdt、shmctl
四个函数组成。我们下面来分析每一个函数的用法。
1.1、创建共享内存
int shmget(key_t key, size_t size, int shmflg);
第一个参数是共享内存段的命名,shmget成功时返回一个关于key相关的标识符,用于后续的共享内存函数。当调用失败返回-1。其它进程也可以通过shmget函数返回值访问同一个共享内存。第二个参数是指定共享内存的容量;第三个shmflg是一个权限标志,它的作用和open和mode函数都是相同的,当共享内存不存在的时候则通过IPC_CREAT来创建。共享内存的权限标准和文件读写的权限一样。
1.2、启动对共享内存的访问
void *shmat(int shm_id, const void *shm_addr, int shmflg);
当我们第一次创建完共享内存时,它还不能被任何进程访问,shmat函数就是用来启动对共享内存的访问,并把共享内存连接到当前进程的地址空间。
shm_id是由shmget函数返回的共享内存标识;shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。最后一个参数是标志位通常都是0。调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。
1.3、共享内存从当前内存中分离
int shmdt(const void *shmaddr);
这个函数只是从共享内存中分离而不是删除,这一点要分清楚,对于初学者而言这里很容易掉坑,使共享内存在当前进程中不可再用。
参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1。
1.4、控制共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数是shaget函数返回的共享内存标识符;command参数是要采取的操作,它由 IPC_STAT、IPC_SET和IPC_RMID组成,分别IPC_STAT代表把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值;IPC_SET代表如果进程有足够的权限,就可以把共享内存的当前关联值设置为shmid_ds结构中给出的值;IPC_RMID代表删除共享内存段。第三个参数buf代表一个结构指针,它指向共享内存的模式或访问权限的结构。
shmid_ds结构至少包括以下成员:
struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
2、共享内存案例
shm_snd.c
#include <stdlib.h> #include <string.h> #include <signal.h> #include <fcntl.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHM_SIZE 1024 int main() { int shmid; char *shmptr; //创建共享内存 shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666); //创建失败 if(shmid < 0) { perror("shmget"); return -1 ; } //对共享内存的访问 shmptr = shmat(shmid, 0, 0); if (shmptr == (void *)-1) { perror("shmat"); return -2 ; } // 往共享内存写数据 strcpy(shmptr, "shmat write ok"); shmdt(shmptr); return 0 ; }
shm_rcv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <fcntl.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHM_SIZE 1024 int main() { int shmid; char *shmptr; shmid = shmget(0x66, SHM_SIZE, IPC_CREAT|0666); if(shmid < 0) { perror("shmget"); return -1 ; } shmptr = shmat(shmid, 0, 0); if (shmptr == (void *)-1) { perror("shmat"); return -2 ; } // 从共享内存读数据 printf("read:%s\n", shmptr); shmdt(shmptr); return 0 ; }
运行结果:
先分别编译shm_snd.c和shmrcv.c这两个程序,生成shmrcv和shmsnd这两个可执行程序。
接下来,首先执行shmsnd,会得到以下结果:
什么都没有?共享内存创建成功了吗?当然是成功了,可以通过ipcs –m
命令查看:
如图上图所示,nattch项下的数字为0那个就是刚刚使用shmsnd这个可执行程序创建的一段共享内存。当然,我们还往共享内存发了shmat write ok
这个字符串,下面运行shmrcv这个程序,看看是否能把写进共享内存的数据读出来。
成功读出。同样的,也可以删除共享内存,如何删除?也一样有两种方法。
(1)使用ipcrm –m shmid可以删除共享内存
如上图,我们已经知道0x66的shmid为1835021,所以只要执行ipcrm –m 1835021
命令即可删除,如下图所示,成功删除。
(2)使用shmctl 函数写入IPC_RMID指令删除共享内存
shmrm.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> int main(void) { int shmid ; //同样,首先先打开共享内存 shmid = shmget(0x66 , 0 , 0); if(-1 == shmid) { perror("open shmkey 0x66 fail"); return -1 ; } //成功的话,向shmctl写入参数,IPC_RMID表示立刻删除,后面的参数被忽略,为0 int ret ; //写入的是参数 ret = shmctl(shmid , IPC_RMID , NULL); if(ret < 0) { perror("remove shm fail"); return -2 ; } printf("remove key:%d success ... \n" , 0x66); return 0 ; }
运行结果: