一、IPC(Inter-Process Communication,进程间通信)对象的介绍
System V 的IPC对象有共享内存、消息队列、信号灯。
二、共享内存的介绍
![](https://i-blog.csdnimg.cn/blog_migrate/ce684cb53ba7c7cdf061c4edc6899c2d.png)
注意:在IPC的通信模式下,不管是使用消息队列还是共享内存,甚至是信号灯,每个IPC的对象都有唯一的名字,称为"键"(key)。通过"键",进程能够识别所用的对象。"键"与IPC对象的关系就如同文件名称于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够公用一个文件。而在 IPC的通讯模式下,通过"键"的使用也使得一个IPC对象能为多个进程所共用。
<1>共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
<2>
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
<3>由于多个进程共享一段内存,因此也需要依靠某种同步机制。
三、共享内存的特点
![](https://i-blog.csdnimg.cn/blog_migrate/37abc623851c1080636ac9fde9b50271.png)
四、共享内存的操作流程
<1>创建/打开共享内存
<2>映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
<3>撤销共享内存映射
<4>删除共享内存对象
五、相关API
A.获取一块共享内存
![](https://i-blog.csdnimg.cn/blog_migrate/288d05997e0fbdf74a385605e8236813.png)
功能:分配一块共享内存
返回值:
调用成功返回一个shmid(类似打开一个或创建一个文件获得的文件描述符一样);
调用失败返回-1。
参数说明:
<1>key标识共享内存的键值(就像文件的标识是文件名):0 / IPC_PRIVATE。
当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;
如果key的取值为0,而参数shmflg中设置了IPC_CREATE这个标志,则同样创建一块新的共享内存。
通过这种方式分配的共享内存,一般用来亲缘关系的进程间通信。
注意:我们一般是通过ftok这个函数获取键值
功能 : 获取一个IPC对象的键值
参数说明:
pthname就是你指定文件名的路径(该文件必须是存在而且可以访问的),一般情况我们都写一个目录
proj_id : 和pthname一起完成创建键值的参数,虽然为int,但是只有8个比特被使用。一般我们写一个字符代替。
例如:
案例:
运行的结果:
![](https://i-blog.csdnimg.cn/blog_migrate/a945e3b30ae4ad41a603f2c07687cdf6.png)
运行的结果:
![](https://i-blog.csdnimg.cn/blog_migrate/c3ed79f57dbe40358e0581a0f4b0b579.png)
<2>
size是要建立共享内存的长度。所有的内存分配操作都是以页为单位的。所以如果一个进程只申请一块只有一个字节的内存,内存也会分配整整一页(在i386机器中一页的缺省大小PACE_SIZE = 4096字节)。
<3>shmflg有效的标志包括IPC_CREAT 和IPC_EXCL,他们的功能与open()的O_CREAT和O_EXCL相当。
IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则直接打开已存在的
IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误
![](https://i-blog.csdnimg.cn/blog_migrate/7dfa4409581f452a5657fab61d07b7a5.png)
例子一:假设键值为key,创建一个共享内存大小为4k,访问权限为066,如果已经存在则返回其标识号
int shmid;
if( (shmid = shmget(key,4 * 1024,0666 | IPC_CREAT)) < 0)
{
perror("Fail to shmget");
exit(EXIT_FAILURE)
}
例子二、假设键值为key,创建一个共享内存大小为1k,访问权限为0666,如果已经存在则报错
int shmid;
if((shmid = shmget(key,1024,0666 | IPC_CREAT | IPC_EXCL)) < 0)
{
perror("Fail to shmget");
exit(EXIT_FAILURE);
}
B.共享内存的映射
![](https://i-blog.csdnimg.cn/blog_migrate/26db4c20c178f94ed89bcd89581975b0.png)
函数shmat将标识号为shmid共享内存映射到调用进程的地址空间中。
参数说明:
shmid : 要映射的共享内存区标识符
shmaddr : 将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射)
shmflg : SHM_RDONLY 共享内存只读
默认0:共享内存可读写。
返回值 :调用成功放回映射后的地址 ,出错放回(void *)-1;
C.取消共享内存与用户进程之间的映射
![](https://i-blog.csdnimg.cn/blog_migrate/66b74e48f52227fcfe3d655898e0d8bf.png)
参数shmaddr是shmat映射成功放回的地址。
注意:当一个进程不再需要共享内存段时,它将调用shmdt()系统调用取消这个段,但是,这并不是从内核真正地删除这个段,
而是把相关shmid_ds结构的shm_nattch域的值减1,
当这个值为0时,内核才从物理上删除这个共享段。
D.控制共享内存
![](https://i-blog.csdnimg.cn/blog_migrate/b82e0709b8dbed3f1111b1842a2253e7.png)
参数说明:
shmid 共享内存标识ID
cmd IPC_STAT得到共享内存的状态
IPC_SET改变共享内存的状态
IPC_RMID删除共享内存
buf 是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定;
![](https://i-blog.csdnimg.cn/blog_migrate/d9125b3d73354e98009c233f4a4b5e56.png)
注意:
1.IPC_RMID命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生最后一个进程离开这个共享段时。
2.当cmd为IPC_RMID时,第三个参数应为NULL。呵呵,大部分我们都是这样做,用这个函数删除共享内存。
案例探究:
运行结果:
点击(此处)折叠或打开
- #include <sys/shm.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <semaphore.h>
- #include <fcntl.h>
- #include <sys/stat.h>
-
-
- #define BUFF_SIZE 1024
-
- int father_do_work(int shmid)
- {
- char *buf;
- void *shmaddr;
- sem_t *prsem;
- sem_t *pwsem;
-
- //有名信号量
- if((prsem = sem_open("rsem",O_CREAT,0666,0)) == SEM_FAILED)
- {
- perror("Fail to sem open");
- return -1;
- }
-
- //有名信号量
- if((pwsem = sem_open("wsem",O_CREAT,0666,1)) == SEM_FAILED)
- {
- perror("Fail to sem open");
- return -1;
- }
-
- //映射共享内存
- if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
- {
- perror("Fail to shmat");
- exit(EXIT_FAILURE);
- }
-
- buf = (char *)shmaddr;
-
- while(1)
- {
- if(sem_wait(pwsem) < 0)
- {
- perror("Fail to sem wait");
- break;
- }
-
- printf(">");
- fgets(buf,BUFF_SIZE,stdin);
- buf[strlen(buf) - 1] = '\0';
-
- if(sem_post(prsem) < 0)
- {
- perror("Fail to sem post");
- break;
- }
-
- if(strncmp(buf,"quit",4) == 0)
- {
- if(shmdt(shmaddr) < 0)
- {
- perror("Fail to shmaddr");
- exit(EXIT_FAILURE);
- }
-
- break;
- }
-
- usleep(500);
- }
-
- return 0;
- }
-
- int child_do_work(int shmid)
- {
- char *buf;
- void *shmaddr;
- sem_t *prsem;
- sem_t *pwsem;
-
- //
- if((prsem = sem_open("rsem",O_CREAT,0666,0)) == SEM_FAILED)
- {
- perror("Fail to sem open");
- return -1;
- }
-
- if((pwsem = sem_open("wsem",O_CREAT,0666,1)) == SEM_FAILED)
- {
- perror("Fail to sem open");
- return -1;
- }
-
- //映射共享内存
- if((shmaddr = shmat(shmid,NULL,0)) == (void *)-1)
- {
- perror("Fail to shmat");
- exit(EXIT_FAILURE);
- }
-
- buf = (char *)shmaddr;
-
- while(1)
- {
- if(sem_wait(prsem) < 0)
- {
- perror("Fail to prsem");
- break;
- }
-
- printf("read buf : %s.\n",buf);
-
- if(sem_post(pwsem) < 0)
- {
- perror("Fail to pwsem");
- break;
- }
-
- if(strncmp(buf,"quit",4) == 0)
- {
- if(shmdt(shmaddr) < 0)
- {
- perror("Fail to shmaddr");
- exit(EXIT_FAILURE);
- }
-
- break;
- }
- }
-
- return 0;
- }
-
- int main()
- {
- int shmid;
- int pid;
- void *shmaddr;
-
- //创建共享内存
- if((shmid = shmget(IPC_PRIVATE,BUFF_SIZE,0666 | IPC_CREAT)) < 0)
- {
- perror("Fail to shmget");
- exit(EXIT_FAILURE);
- }
-
-
- if((pid = fork()) < 0)
- {
-
- perror("Fail to fork");
- exit(EXIT_FAILURE);
-
- }else if(pid == 0){
-
- child_do_work(shmid);
-
- }else{
-
- father_do_work(shmid);
- wait(NULL);
-
- if(shmctl(shmid,IPC_RMID,NULL) < 0)
- {
- perror("Fail to shmctl");
- exit(EXIT_FAILURE);
- }
- }
-
- exit(EXIT_SUCCESS);
- }
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/67a7bfd022f450c6e7763121c5afc5f3.png)