共享内存是允许两个或多个进程共享同一块内存区域,并通过该区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要任何介质,而是数据由内存直接映射到进程空间,即数据不需要在客户进程和服务进程之间复制,所以共享内存是最快的 IPC 机制。共享内存必须解决多个进程之间同步访问的问题,必须控制同一时刻只允许一个进程对共享内存区域进行写入数据操作,否则会造成数据混乱。同步访问问题可以使用信号量或者记录锁进行解决。
每个共享内存都有相对应的 shmid_ds 结构,其定义如下:
- /* 共享内存 */
- /* shmid_ds 结构 */
- struct shmid_ds
- {
- struct ipc_perm shm_perm; /* operation permission struct */
- size_t shm_segsz; /* size of segment in bytes */
- pid_t shm_lpid; /* pid of last shmop() operation */
- pid_t shm_cpid; /* pid of creator */
- shmatt_t shm_nattch; /* number of current attaches */
- time_t shm_atime; /* last-attach time */
- time_t shm_dtime; /* last-detach time */
- time_t shm_ctime; /* last-change time */
- };
共享内存创建与打开
为了能够使用共享内存,我们要创建一个共享内存区域,并获取该共享内存的标识符,可以调用函数 shmget 实现该功能:
- /*
- * 函数功能:创建一个新的共享内存区域或打开一个现有的共享内存区域;
- * 返回值:若成功则返回共享内存ID,若出错则返回-1;
- * 函数原型:
- */
- #include <sys/shm.h>
- int shmget(key_t key, size_t size, int flag);
- /*
- * 说明:
- * key是共享内存的键值;
- * size是共享内存区域的大小;
- * flag设置共享内存的访问权限;
- * (1)当key为IPC_PRIVATE时,此时flag的取值对该函数不起作用;
- * (2)当key不为IPC_PRIVATE时,且flag设置为IPC_CREAT,则执行操作有key值决定;
- * (3)当key不为IPC_PRIVATE时,且flag同时设置为IPC_CREAT | IPC_EXCL,则只执行创建共享内存操作,此时key必须不同于内核已存在的共享内存的键值,否则出错返回;
- */
共享内存与地址空间的连接和断开
当一个共享内存被创建或打开之后,进程若要使用该共享内存,则必须要是该共享内存连接到它的地址空间,可以调用函数 shmat 实现该功能,若共享内存不再使用时,必须从内核系统中删除,可以调用函数 shmdt 实现:
- /*
- * 函数功能:将共享内存连接到它的地址空间;
- * 返回值:若成功则返回指向共享内存的指针,若出错则返回-1;
- * 函数原型:
- */
- #include <sys/shm.h>
- void *shmat(int shmid, void *addr, int flag);
- /*
- * 说明:
- * shmid是共享内存的ID;
- * adrr和flag共同决定函数的返回:
- * (1)若addr为0,则此段连接到由内核选择的第一个可用地址上,此时flag取任何值都无效;
- * (2)若addr不为0,而flag未设置SHM_RND,则共享内存区域连接到由addr指定的地址处;
- * (3)若addr不为0,而flag设置了SHM_RND,则共享内存区域连接到由(adrr-(addr mod ulus SHMLBA))指定的地址处;
- * 其中SHM_RND表示取整,SHMLBA表示低边界地址倍数;
- * 若flag指定SHM_RDONLY,则以只读方式连接,否则以读写方式连接;
- */
- /*
- * 函数功能:将共享内存与它的地址空间断开;
- * 返回值:若成功则返回0,若出错则返回-1;
- * 函数原型:
- */
- #include <sys/shm.h>
- int shmdt(void *addr);
共享内存的控制操作
对共享内存区域进行多中控制操作可以通过函数 shmctl 实现,其定义如下:
- /*
- * 函数功能:对共享内存进行控制操作;
- * 返回值:若成功则返回0,若出错则返回-1;
- * 函数原型:
- */
- #include <sys/shm.h>
- int shmctl(int shmid, int cmd, struct shmid_ds *buf);
该函数提供了三个 cmd 命令:
- IPC_RMID:从系统中删除由 shmid 标识的共享内存区并拆除它。
- IPC_SET :给所指定的共享内存区设置其 shmid_ds 结构的以下三个成员:shm_perm.uid,shm_perm.gid和shm_perm.mode,他们的值来自buff参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。
- IPC_STAT :(通过buff参数)向调用者返回所指定共享内存区当前的 shmid_ds 结构,将其保存在buf所指向的缓冲区。
另外 Linux 提供了下列另外两种命令:
- SHM_LOCK:将共享内存锁定在内存中,此命令只能超级用户才可以执行。
- SHM_UNLOCK:解锁共享内存,此命令只能超级用户才可以执行。
测试程序:创建一个共享内存区,然后打印该共享内存区的属性;
- #include "apue.h"
- #include <sys/shm.h>
- #include <fcntl.h>
- #include <sys/ipc.h>
- #include <sys/types.h>
- #define ARRAY_SIZE 4000
- #define MALLOC_SIZE 10000
- #define SHM_SIZE 10000
- #define SHM_MODE (SHM_R | SHM_W)
- void printf_shm(struct shmid_ds *buf);
- key_t MakeKey(const char *pathname);
- char array[ARRAY_SIZE];
- int main(int argc, char *argv[])
- {
- if(argc != 2)
- err_quit("usage: a.out <pathname>");
- int shmid;
- char *ptr;
- void *shmptr;
- struct shmid_ds shmids;
- key_t key;
- key = MakeKey(argv[1]);
- printf("array[] from %x to %x\n", &array[0],&array[ARRAY_SIZE]);
- printf("stack around %x\n", &shmid);
- if((ptr = (char *)malloc(MALLOC_SIZE)) == NULL)
- err_sys("malloc error");
- printf("malloced from %x to %x\n", ptr, ptr+MALLOC_SIZE);
- if((shmid = shmget(key, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0)
- err_quit("shmget error");
- if((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
- err_sys("shmat error");
- if(shmctl(shmid, IPC_STAT, &shmids) == -1)
- err_sys("shmctl error");
- printf_shm(&shmids);
- if(shmctl(shmid, IPC_RMID, 0) < 0)
- err_sys("shmctl error");
- exit(0);
- }
- key_t MakeKey(const char *pathname)
- {
- int fd;
- if((fd = open(pathname, O_CREAT, 0666)) < 0)
- err_quit("open error");
- close(fd);
- return ftok(pathname, 0);
- }
- void printf_shm(struct shmid_ds *buf)
- {
- printf("Struct shmid_ds:\n");
- printf("\tshm_nattch = %d\n", buf->shm_nattch);
- printf("\tshm_segsz = %d\n", buf->shm_segsz);
- printf("\tStruct ipc_perm:\n");
- printf("\t\tuid = %d\n", buf->shm_perm.uid);
- printf("\t\tgid = %d\n", buf->shm_perm.gid);
- printf("\t\tcuid = %d\n", buf->shm_perm.cuid);
- printf("\t\tcgid = %d\n", buf->shm_perm.cgid);
- return;
- }
- ./shm Shm
- array[] from 804b0a0 to 804c040
- stack around bfa5a47c
- malloced from 8b6e008 to 8b70718
- Struct shmid_ds:
- shm_nattch = 1
- shm_segsz = 10000
- Struct ipc_perm:
- uid = 1000
- gid = 1000
- cuid = 1000
- cgid = 1000