十四章
共享内存
管道 , 消息队列和信号量都需要借助第三方对象进行通信 ; 而共享内存正好弥补了这些缺陷 , 它是最快的 IPC 对象 . 在本质上 , 共享内存是一端物理内存 .
共享内存简介
共享内存中最重要的属性是内存大小和内存地址 , 进程在访问共享内存前必须先将共享内存映射到进程空间的一个虚拟地址中 , 然后任何对该虚拟地址的数据操作都将直接作用到物理内存上 .
共享内存由进程创建 , 但是进程结束时共享内存仍然保留 , 除非该共享内存被显式地删除或者重启操作系统 .
UNIX 的内核采用结构 shmid_ds 来管理消息队列 , 它的数据成员与命令 'ipcs -a -m' 的结果一一对应
struct shmid_ds { struct ipc_perm shm_perm; /* operation perms */ int shm_segsz; /* size of segment (bytes) */ __kernel_time_t shm_atime; /* last attach time */ __kernel_time_t shm_dtime; /* last detach time */ __kernel_time_t shm_ctime; /* last change time */ __kernel_ipc_pid_t shm_cpid; /* pid of creator */ __kernel_ipc_pid_t shm_lpid; /* pid of last operator */ unsigned short shm_nattch; /* no. of current attaches */ unsigned short shm_unused; /* compatibility */ void *shm_unused2; /* ditto - used by DIPC */ void *shm_unused3; /* unused */ }; |
命令 'ipcs -m' 查询系统中共享内存的基本信息 :
[bill@billstone bill]$ ipcs -m
------ Shared Memory Segments -------- key shmid owner perms bytes nattch status
[bill@billstone bill]$ |
使用共享内存
(1) 共享内存的创建
在 UNIX 中 , 函数 shmget 用来创建或获取共享内存 , 原型如下 :
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, int size, int shmflg); |
同样的 , 参数 shm_flg 有 IPC_CREAT 和 IPC_EXCL 两种取值 .
(2) 共享内存的映射
与消息队列和信号量不同 , 共享内存在获取标志号后 , 仍需要调用 shmat 函数将共享内存映射到进程的地址空间后才能访问 , 原型如下
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); |
(3) 共享内存的释放
当进程不再需要共享内存时 , 可以使用函数 shmdt 释放共享内存内存映射 , 原型如下
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr); |
(4) 共享内存的控制
与消息队列和信号量一样 , 共享内存也有自给的控制函数 shmctl, 原型如下 :
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
这里设计一个类似于命令 'ipcs' 和命令 'ipcrm' 的程序 ipcshm: 它从命令行参数中获取要执行的操作 , 包括创建共享内存 , 读取共享内存信息和删除共享内存等 , 主体代码如下代码如下
#include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdio.h> #include <sys/stat.h>
#define VerifyErr(a, b) / if (a) fprintf(stderr, "%s failed./n", (b)); / else fprintf(stderr, "%s success./n", (b));
int main(int argc, char *argv[]) { int shmid, size;
if(argc != 3) exit(1); shmid = atoi(argv[1]); if(strcmp(argv[2], "v") == 0){ StatShm(shmid); } else if(strcmp(argv[2], "d") == 0){ VerifyErr(shmctl(shmid, IPC_RMID, NULL) < 0, "Delete Shm"); } else{ size = atoi(argv[2]); VerifyErr(shmget(shmid, size, 0666|IPC_CREAT|IPC_EXCL) < 0, "Create Shm"); }
return 0; } |
其中用到了两个函数 :
char * GetFileMode(mode_t st_mode, char *resp){ if(resp == NULL) return 0; memset(resp, '-', 9); if(st_mode & S_IRUSR) resp[0] = 'r'; if(st_mode & S_IWUSR) resp[1] = 'w'; if(st_mode & S_IXUSR) resp[2] = 'x'; if(st_mode & S_IRGRP) resp[3] = 'r'; if(st_mode & S_IWGRP) resp[4] = 'w'; if(st_mode & S_IXGRP) resp[5] = 'x'; if(st_mode & S_IROTH) resp[6] = 'r'; if(st_mode & S_IWOTH) resp[7] = 'w'; if(st_mode & S_IXOTH) resp[8] = 'x';
return resp; }
int StatShm(int shmid){ char resp[10]; struct shmid_ds buf;
memset(&buf, 0, sizeof(buf)); memset(resp, 0, sizeof(resp)); shmctl(shmid, IPC_STAT, &buf); fprintf(stderr, "T ID KEY MODE OWNER GROUP NATTCH SEGSZ CPID LPID/n"); fprintf(stderr, "m %6d %#6x %s %6d %6d %6d %10d %10d %10d/n", shmid, buf.shm_perm.__key, GetFileMode(buf.shm_perm.mode, resp), buf.shm_perm.uid, buf.shm_perm.gid, buf.shm_nattch, buf.shm_segsz, buf.shm_cpid, buf.shm_lpid);
return 0; } |
执行情况如下 :
[bill@billstone Unix_study]$ gcc -o ipcshm ipcshm.c [bill@billstone Unix_study]$ ./ipcshm 2000 100 Create Shm success. [bill@billstone Unix_study]$ ipcs -m
------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x000007d0 229377 bill 666 100 0
[bill@billstone Unix_study]$ ./ipcshm 229377 v T ID KEY MODE OWNER GROUP NATTCH SEGSZ CPID LPID m 229377 0x7d0 rw-rw-rw- 500 500 0 100 1796 0 [bill@billstone Unix_study]$ ./ipcshm 229377 d Delete Shm success. [bill@billstone Unix_study]$ ipcs -m
------ Shared Memory Segments -------- key shmid owner perms bytes nattch status
[bill@billstone Unix_study]$ |
(5) 共享内存实例
共享内存的应用可以分为打开 ( 创建 ) 共享内存 , 映射共享内存 , 读写共享内存和释放共享内存映射等四个步骤 .
程序 shm1 想共享内存中指定位置写入数据 .
#include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdio.h> #include <sys/stat.h>
#define VerifyErr(a, b) / if (a) { fprintf(stderr, "%s failed./n", (b)); return; } / else fprintf(stderr, "%s success./n", (b));
int main(void) { int shmid, no; char *pmat = NULL, buf[1024];
VerifyErr((shmid = shmget(0x1234, 10*1024, 0666|IPC_CREAT)) == -1, "Open(Create) Shm"); VerifyErr((pmat = (char *)shmat(shmid, 0, 0)) == 0, "Link Shm"); printf("Please input NO.(0-9): "); scanf("%d", &no); VerifyErr(no<0 || no>9, "Input No."); printf("Please input data: "); memset(buf, 0, sizeof(buf)); getchar(); // 读入 '/n' 回车符 fgets(buf, sizeof(buf), stdin); memcpy(pmat+no*1024, buf, 1024); shmdt(pmat);
return 0; } |
程序 shm2 从共享内存指定位置读取数据
#include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdio.h> #include <sys/stat.h>
#define VerifyErr(a, b) / if (a) { fprintf(stderr, "%s failed./n", (b)); return; } / else fprintf(stderr, "%s success./n", (b));
int main(void) { int shmid, no; char *pmat = NULL, buf[1024];
VerifyErr((shmid = shmget(0x1234, 10*1024, 0666|IPC_CREAT)) == -1, "Open(Create) Shm"); VerifyErr((pmat = (char *)shmat(shmid, 0, 0)) == 0, "Link Shm"); printf("Please input NO.(0-9): "); scanf("%d", &no); VerifyErr(no<0 || no>9, "Input No."); memcpy(buf, pmat+no*1024, 1024); printf("Data: [%s]/n", buf); shmdt(pmat);
return 0; } |
运行结果如下
[bill@billstone Unix_study]$ make shm1 cc shm1.c -o shm1 [bill@billstone Unix_study]$ make shm2 cc shm2.c -o shm2 [bill@billstone Unix_study]$ ./shm1 Open(Create) Shm success. Link Shm success. Please input NO.(0-9): 1 Input No. success. Please input data: this is a test! [bill@billstone Unix_study]$ ./shm2 Open(Create) Shm success. Link Shm success. Please input NO.(0-9): 1 Input No. success. Data: [this is a test! ] [bill@billstone Unix_study]$ |