--详细概念见《unix环境高级编程》15.9小节linux
共享内存能够说是Linux 下最快速、最有效的进程间通讯方式。两个不一样进程A 、B 共享内存的意思是,同一块物理内存被映射到进程A 、B 各自的进程地址空间,进程A 能够即时看到进程B 对共享内存中数据的更新;反之,进程B 也能够即时看到进程A对共享内存中数据的更新。编程
这里简单说下映射的概念:bash
Linux系统会为每一个进程分配 4GB 的虚拟地址空间,必定状况下,须要将虚拟内存转换成物理内存,这就须要内存映射。为何咱们须要使用虚拟地址呢?最主要的缘由是不一样PC的物理内存会不同,若是直接使用物理地址,会形成程序的移植性不好,另外虚拟地址访问内存有如下优点:服务器
一、程序可使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。app
二、程序可使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(一般大小为 4 KB)保存到磁盘文件。数据或代码页会根据须要在物理内存与磁盘之间移动。函数
三、不一样进程使用的虚拟地址彼此隔离。一个进程中的代码没法更改正在由另外一进程或操做系统使用的物理内存。工具
进程可用的虚拟地址范围称为该进程的“虚拟地址空间”。每一个用户模式进程都有其各自的专用虚拟地址空间。对于 32 位进程,虚拟地址空间一般为 4 GB,范围从 0x00000000 至 0xFFFFFFFF。ui
共享内存从字面意义解释就是多个进程能够把一段内存映射到本身的进程空间,以此来实现数据的共享及传输,这也是全部进程间通讯方式最快的一种,共享内存是存在于内核级别的一种资源。操作系统
在Shell 中可使用ipcs 命令查看当前系统IPC中的状态,在文件系统中/proc目录下有对其描述的相应文件.net
root@jonathan-pc:~/test/shm# ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x01011d17 0 root 666 2176 1 0x02011d17 32769 root 666 2176 1 0xffffffff 65538 root 666 1024 0
ipcs -m ,其中 -m 是 memory 的意思 。
在系统内核为一个进程分配内存地址时,经过分页机制可让一个进程的物理地址不连续,同时也可让一段内存同时分配给不一样的进程。共享内存机制就是经过该原理实现的,共享内存机制只是提供数据的传送,如何控制服务器端和客户端的读写操做互斥,这就须要一些其余的辅助工具,例如信号量。
采用共享内存通讯的一个显而易见的好处就是效率高,由于进程能够直接读写内存,而不须要任何数据的拷贝。对于像管道和消息队列等通讯方式,则须要在内核和用户控件进行四次数据的拷贝,而共享内存只拷贝两次数据:一次从输入文件到共享区,另外一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不老是读写少许数据后就解除映射,有新的通讯时,再从新创建共享内存区域。而是保持共享区域,知道通讯完毕为止,这样,数据内同一直保存在共享内存中,并无写回文件。共享内存中的内容每每是在接触映射时才写回文件的。所以,采用共享内存的通讯方式效率是最高的。
共享内存最大不足之处在乎,因为多个进程对同一块内存区域具备访问的权限,各个进程之间的同步问题显得尤其重要。必须控制同一时刻只有一个进程对共享内存区域写入数据,不然会形成数据的混乱。同步控制问题能够由信号量来解决;
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> /* 建立或打开共享内存 Key:IPC_PRIVATE 或 ftok 的返回值 size:共享内存区大小 shmflag :同open函数的权限位,也可用8进制表示法 返回值:成功:共享内存段标识符 出错:-1 */ int shmget(key_t, int size ,int shmflg ); /* 当一个共享内存建立或打开后,某个进程若是要使用该共享内存则必须将此内存区附加到它的地址空间 shmid :要映射的共享内存区标示符 shmaddr:将共享内存映射到指定地址(若为NULL,则表示由系统自动完成映射) shmflg:默认0:共享内存只读 返回值:成功:映射后的地址 出错:-1 */ void *shmat (int shmid, const void *shaddr, int shmflg); /*当进程对共享内存段的操做完成后,应调用 shmdt 函数,做用是将指定的共享内存段从当前进程空间中脱离出去 shmaddr:共享内存映射后的地址 返回值:0 出错:-1 */ int shmdt(const void *shmaddr); /* 共享内存的控制 shmid:要操做的共享内存标示符 cmd: IPC_STAT (获取对象属性) IPC_SET(设置对象属性) IPC_RMID(删除对象) buf:指定IPC_STAT/ IPC_SET 时用以保存/设置属性 返回值:0 出错:-1 */ int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shm.h
、client.c
、server.c
//shm.h #ifndef __SHM_H__ #define __SHM_H__ struct people { char name[10]; int age; }; union semun { int val; // value for SETVAL struct semid_ds *semid_dsbuf; // buffer for IPC_STAT, IPC_SET unsigned short *array; // array foror GETALL, SETALL struct seminfo *__buf; // buffer for IPC_INFO }; #define SHM_KEY_FILE "./client.c" #define SEM_KEY_FILE "./server.c" #endif
//server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <errno.h> #include <signal.h> #include "shm.h" int semid, shmid; int p(int semid, int semnum) { struct sembuf sops = {semnum, -1, SEM_UNDO}; return semop(semid, &sops, 1); } int v(int semid, int semnum) { struct sembuf sops = {semnum, +1, SEM_UNDO}; return semop(semid, &sops, 1); } void * signal_set(int signo, void (*func)(int)) { int ret; struct sigaction sig; struct sigaction osig; sig.sa_handler = func; sigemptyset(&sig.sa_mask); sig.sa_flags = 0; #ifdef SA_RESTART sig.sa_flags |= SA_RESTART; #endif ret = sigaction(signo, &sig, &osig); if (ret < 0) return SIG_ERR; else return osig.sa_handler; } void sigint(int sig) { union semun arg; if (shmctl(shmid, IPC_RMID, NULL) == -1) { fprintf(stderr, "shmctl delete error"); } if (semctl(semid, 0, IPC_RMID, 0) == -1) { fprintf(stderr, "semctl delete error"); } exit(sig); } int main(int argc, const char *argv[]) { key_t semkey; key_t shmkey; struct people *addr = NULL; union semun arg; signal_set(SIGINT, sigint); signal_set(SIGTERM, sigint); semkey = ftok(SEM_KEY_FILE, 0); if (semkey < 0) { fprintf(stderr, "ftok semkey error"); return -1; } shmkey = ftok(SHM_KEY_FILE, 0); if (shmkey < 0) { fprintf(stderr, "ftok shmkey error"); return -1; } semid = semget(semkey, 1, 0666 | IPC_CREAT); if (semid < 0) { fprintf(stderr, "semget error"); return -1; } shmid = shmget(shmkey, sizeof(struct people), 0666 | IPC_CREAT); if (shmid < 0) { fprintf(stderr, "shmget error"); goto err1; } arg.val = 0; if (semctl(semid, 0, SETVAL, arg) < 0) { fprintf(stderr, "ctl sem error"); goto err2; } addr = (struct people *)shmat(shmid, NULL, 0); if (addr == (struct people *)-1) { fprintf(stderr, "shmat error"); goto err2; } p(semid, 0); strncpy(addr->name, "xiaoming", 10); addr->age = 10; v(semid, 0); if (shmdt(addr) == -1) { fprintf(stderr, "shmdt is error"); goto err2; } p(semid, 0); err2: if (shmctl(shmid, IPC_RMID, NULL) == -1) { fprintf(stderr, "shmctl delete error"); } err1: if (semctl(semid, 0, IPC_RMID, 0) == -1) { fprintf(stderr, "semctl delete error"); } return 0; }
//client.c #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <errno.h> #include "shm.h" int p(int semid, int semnum) { struct sembuf sops = {semnum, -1, SEM_UNDO}; return semop(semid, &sops, 1); } int v(int semid, int semnum) { struct sembuf sops = {semnum, +1, SEM_UNDO}; return semop(semid, &sops, 1); } int main(int argc, const char *argv[]) { int semid, shmid; key_t semkey; key_t shmkey; struct people *addr = NULL; //获取ipc key semkey = ftok(SEM_KEY_FILE, 0); if (semkey < 0) { fprintf(stderr, "ftok semkey error"); return -1; } shmkey = ftok(SHM_KEY_FILE, 0); if (shmkey < 0) { fprintf(stderr, "ftok shmkey error"); return -1; } //获取semid和shmid semid = semget(semkey, 0, 0666); if (semid < 0) { fprintf(stderr, "semget error"); return -1; } shmid = shmget(shmkey, 0, 0666); if (shmid < 0) { fprintf(stderr, "shmget error"); return -1; } //将共享内存连接到进程内存 addr = (struct people*)shmat(shmid, 0, 0); if (addr == (struct people *)-1) { fprintf(stderr, "shmat error"); return -1; } v(semid, 0);//取消server的阻塞 sleep(1); p(semid, 0);//获取共享内存内容 printf("name is : %s\n", addr->name); printf("age is : %d\n", addr->age); v(semid, 0); if (shmdt(addr) == -1) {//断开共享内存 fprintf(stderr, "shmdt is error"); return -1; } return 0; }
运行结果
先运行server,再运行client
root@jonathan-pc:~/test/shm# ls client.c server.c shm.h root@jonathan-pc:~/test/shm# gcc client.c -o c root@jonathan-pc:~/test/shm# gcc server.c -o s root@jonathan-pc:~/test/shm# ./s
root@jonathan-pc:~/test/shm# ./c name is : xiaoming age is : 10
TELL_WAIT
、WAIT_PARENT
、err_sys
等相关函数见《unix环境高级编程》的代码
相关联进程之间的共享内存,能够对/dev/zero
使用mmap,建立未命名的存储区
#include "apue.h" #include <fcntl.h> #include <sys/mman.h> #define NLOOPS 1000 #define SIZE sizeof(long) /* size of shared memory area */ static int update(long *ptr) { return((*ptr)++); /* return value before increment */ } int main(void) { int fd, i, counter; pid_t pid; void *area; if ((fd = open("/dev/zero", O_RDWR)) < 0) err_sys("open error"); if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) err_sys("mmap error"); close(fd); /* can close /dev/zero now that it's mapped */ TELL_WAIT(); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ for (i = 0; i < NLOOPS; i += 2) { if ((counter = update((long *)area)) != i) err_quit("parent: expected %d, got %d", i, counter); TELL_CHILD(pid); WAIT_CHILD(); } } else { /* child */ for (i = 1; i < NLOOPS + 1; i += 2) { WAIT_PARENT(); if ((counter = update((long *)area)) != i) err_quit("child: expected %d, got %d", i, counter); TELL_PARENT(getppid()); } } exit(0); }
相关联进程之间的共享内存,能够对文件描述符-1使用mmap,建立未命名的存储区
#include "apue.h" #include <fcntl.h> #include <sys/mman.h> #define NLOOPS 1000 #define SIZE sizeof(long) /* size of shared memory area */ static int update(long *ptr) { return((*ptr)++); /* return value before increment */ } int main(void) { int i, counter; pid_t pid; void *area; if ((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED) err_sys("mmap error"); TELL_WAIT(); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ for (i = 0; i < NLOOPS; i += 2) { if ((counter = update((long *)area)) != i) err_quit("parent: expected %d, got %d", i, counter); TELL_CHILD(pid); WAIT_CHILD(); } } else { /* child */ for (i = 1; i < NLOOPS + 1; i += 2) { WAIT_PARENT(); if ((counter = update((long *)area)) != i) err_quit("child: expected %d, got %d", i, counter); TELL_PARENT(getppid()); } } exit(0); }