5 共享内存
- 共享内存允许两个或多个进程访问给定的同一块存储区域。它是效率最高的一种进程通信方式,节省了不同进程间多次读写的时间;
- 在写进程的操作尚未完成时,不应有进程从共享内存中读取数据。共享内存自身不限制对共享内存的读写次序,程序开发人员应自觉遵循读写规则;
- 一般情况下,共享内存与信号量一起使用,由信号量帮它实现读写操作的同步;
- Linux提供了如下系统调用来实现共享内存的申请、管理与释放:
– shmget
– shmat
– shmdt
– shmctl
5.1 shmget函数
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
功能:创建一个新的共享内存或打开一块已存在的共享内存。
参数说明:
- key:传入参数,共享内存的键值,通常为一个整数;
- size:设置共享内存的大小;
- shmflg:设置shmget函数的创建条件(一般设置为IPC_CREAT或IPC_EXCL)及进程对共享内存的读写权限。
返回值说明:
- 成功:返回共享内存的标识符,一个非负整数;
- 不成功:返回-1并设置errno。
5.2 shmat函数
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:进行地址映射,将共享内存映射到进程虚拟地址空间中。
参数说明:
- shmid:共享内标识符,通常为shmget函数的返回值;
- shmaddr:指针类型的传入参数,用于指定共享内存映射到虚拟内存时的虚拟地址,设置为NULL时,映射地址由系统决定;
- shmflg:设置共享内存的使用方式,如果shmflg = SHM_RDONLY,则共享内存将以只读的方式进行映射,当前进程只能从共享内存中读取数据。
返回值说明:
- 成功:返回映射的地址并更改共享内存shmid_ds结构中的属性信息;
- 不成功:返回-1并设置errno。
5.3 shmdt函数
#include <sys/shm.h>int shmdt(const void *shmaddr);
功能:解除物理内存与进程虚拟地址空间的映射关系。
参数说明:
- shmaddr:shmat函数返回的虚拟空间地址。
返回值说明:
- 成功:返回映射的地址并更改共享内存shmid_ds结构中的属性信息;
- 不成功:返回-1并设置errno。
5.4 shmctl函数
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//为了方便对共享内存进行管理,由内核维护的存储共享内存属性信息的结构体:
struct shmid_ds{struct ipc_perm shm_perm; /* 操作权限*/size_t shm_segsz; /*段的大小(以字节为单位)*/time_t shm_atime; /*最后一个进程附加到该段的时间*/time_t shm_dtime; /*最后一个进程离开该段的时间*/time_t shm_ctime; /*最后一个进程修改该段的时间*/unsigned short shm_cpid; /*创建该段进程的pid*/unsigned short shm_lpid; /*在该段上操作的最后1个进程的pid*/short shm_nattch; /*当前附加到该段的进程的个数*//*下面是私有的*/unsigned short shm_npages; /*段的大小(以页为单位)*/unsigned long *shm_pages; /*指向frames->SHMMAX的指针数组*/struct vm_area_struct *attaches; /*对共享段的描述*/
};
功能:对已存在的共享内存进行操作,具体的操作由参数确定。
参数说明:
- shmid:共享内存标识符;
- cmd:要执行的操作,常用的设置为IPC_RMID,功能为删除共享内存;
- buf:对共享内存的管理信息进行设置,该参数是结构体shmid_ds的指针。
返回值说明:
- 成功:返回0;
- 不成功:返回-1并设置errno。
特殊说明:
- 共享内存与消息队列、信号量相同,在使用完毕后都应该进行释放;
- 调用fork函数创建子进程时,子进程会继承父进程已绑定的共享内存;
- 当调用exec函数更改子进程功能及调用exit函数时,子进程都会解除与共享内存的映射关系,因此在必要时仍应使用shmctl函数对共享内存进行删除。
【案例1】使用共享内存机制实现两个进程间的通信。
shm_w.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#define SEGSIZE 4096 //定义共享内存容量
typedef struct{ //读写数据结构体char name[8];int age;
} Stu;
int main() {int tempShmId, i;key_t tempKey;char tempName[8];Stu *tempSmap;/*ftok函数的作用:系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。*/tempKey = ftok("/", 0); //获取关键字if (tempKey == -1) {perror("ftok error");return -1;}//of ifprintf("key=%d\n", tempKey);//创建共享内存tempShmId= shmget(tempKey, SEGSIZE, IPC_CREAT | IPC_EXCL | 0664);if (tempShmId == -1) {perror("create shared memory error\n");return -1;}//of ifprintf("shm_id=%d\n", tempShmId);tempSmap = (Stu*)shmat(tempShmId, NULL, 0); //将进程与共享内存绑定memset(tempName, 0x00, sizeof(tempName));strcpy(tempName, "Jhon");tempName[4] = '0';for (i = 0; i < 3; i++){ //写数据tempName[4] += 1;strncpy((tempSmap+ i)->name, tempName, 5);(tempSmap + i)->age = 20 + i;}//of for iif (shmdt(tempSmap) == -1){ //解除绑定perror("detach error");return -1;}//of ifreturn 0;
}//of mainshm_r.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct{char name[8];int age;
} Stu;
int main() {int tempShmId, i;key_t tempKey;Stu *tempSmap;struct shmid_ds tempBuf;tempKey = ftok("/", 0); //获取关键字if (tempKey == -1) {perror("ftok error");return -1;}//of ifprintf("key=%d\n", tempKey);tempShmId = shmget(tempKey, 0, 0); //创建共享内存if (tempShmId == -1) {perror("shmget error");return -1;}//of ifprintf("shm_id=%d\n", tempShmId);tempSmap = (Stu*)shmat(tempShmId, NULL, 0); //将进程与共享内存绑定for (i = 0; i < 3; i++){ //读数据printf("name:%s\n", (*(tempSmap + i)).name);printf("age :%d\n", (*(tempSmap + i)).age);}//of for iif (shmdt(tempSmap) == -1){ //解除绑定perror("detach error");return -1;}//of ifshmctl(tempShmId, IPC_RMID, &tempBuf); //删除共享内存return 0;
}//of main
6 小结
了解Linux进程间的通信方式是学习Linux编程的基础,也是学习和实现复杂编程的基石。
7 编程题
(1)父进程向无名管道写入小写字母,子进程读出后转换为大写字母写入管道,父进程再将其读出;
(2)父进程向共享内存输入字符,子进程判断是否是自己要接收的信息,是的话,输出共享内存中的信息。