共享存储(Shared Memory)允许两个或更多进程共享一个给定的存储区。由于数据不需要在进程间进行复制,因此它是最快的一种IPC。唯一需要注意的问题是,多个进程之间对一给定存储区的同步访问。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应当去取这些数据。通常,信号量被用来实现对共享存储访问的同步,此外,当然记录锁也可用于这种场合。
内核为每个共享存储段设置了一个shmid_ds结构,与msqid_ds类似只是该共享存储区的一些信息和记录。
struct shmid_ds { struct ipc_perm shm_perm; /* see Section 15.6.2 */ size_t shm_segsz; /* 段所含字节数 */ pid_t shm_lpid; /* pid of last shmop() */ 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 */ . . . };
其中包含ipc_perm结构,用以规定存储段的访问权限和所有者。系统中,关于共享存储段的限制包括:共享存储段的最大字节数、共享存储段的最小字节数、共享存储段的最大段数以及每个进程共享存储段的最大段数。当然不同系统不一样!
为了获得一个共享存储标识符,调用的第一个函数通常是shmget。
#include <sys/shm.h> int shmget(key_t key, size_t size, int flag) //成功返回共享存储ID,失败返回-1
其中size是该共享存储段的长度(单位为字节),实现通常将其向上取为系统页长的整数倍。但是如果指定的size值并非系统页长的整数倍,那么最后一页的余下部分是不可用的。如果正在创建一个新段,则必须指定其size,如果正在引用一个现存的段,则将size指定为0。当创建一个新段时,将段内内容初始化为0。
每一个XSI IPC都会有一个控制的垃圾箱函数,共享内存也不例外:
#include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); //成功返回0,出错返回-1
具体的cmd依然是IPC_STAT,IPC_SET,IPC_RMID。
linux提供了另外两个命令:
SHM_LOCK 将共享存储段锁定在内存中。此命令只能由超级用户执行。
SHM_UNLOCK 解锁共享存储段,此命令只能由超级用户执行。
一旦创建了一个共享存储段,进程就可以调用shmat将其连接到自己的地址空间中。
#include <sys/shm.h> void *shmat(int shmid, const void *addr, int flag) //成功返回指向共享存储的指针,出错返回-1
addr参数表示将共享存储段连接到进程的地址,指定为0表示右内核选取,为了可移植性,一般指定为0。
如果在flag中指定SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。
对共享存储段的操作已经结束时,则调用shmdt脱离此段。注意这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器进程)调用带IPC_RMID命令的shmctl特地删除它。
#include <sys/shm.h> int shmdt(void *addr); //成功返回0,出错返回-1 //addr是以前调用shmat时的返回值,如果成功shmdt将使得shmid_ds结构中的shm_nattch //计数器减1。
//用POSIX信号量来同步共享存储区 //这里用的是命名信号量,其实无名信号量也是可以的,不过无名信号量一般用于线程 #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <semaphore.h> #include <fcntl.h> /* For O_* constants */ #include <sys/stat.h> /* For mode constants */ #include <stdlib.h> #define SHM_KEY 0x33 int main(int argc, char **argv) { pid_t pid; int i, shmid; int *ptr; sem_t *sem; /* 创建一块共享内存, 存一个int变量 */ if ((shmid = shmget(SHM_KEY, sizeof(int), IPC_CREAT | 0600)) == -1) { perror("msgget"); } /* 将共享内存映射到进程, fork后子进程可以继承映射 */ if ((ptr = (int *)shmat(shmid, NULL, 0)) == (void *)-1) { perror("shmat"); } *ptr = 0; /* posix的有名信号量是kernel persistent的 * 调用sem_unlink删除以前的信号量 */ sem_unlink("/mysem"); /* 创建新的信号量, 初值为1, sem_open会创建共享内存 * 所以信号量是内核持续的 */ if ((sem = sem_open("/mysem", O_CREAT, 0600, 1)) == SEM_FAILED) { perror("sem_open"); } if ((pid = fork()) < 0) { perror("fork"); } else if (pid == 0) { /* Child */ /* 子进程对共享内存加1 */ for (i = 0; i < 100000; i++) { sem_wait(sem); (*ptr)++; sem_post(sem); printf("child: %d\n", *ptr); } } else { /* Parent */ /* 父进程对共享内存减1 */ for (i = 0; i < 100000; i++) { sem_wait(sem); (*ptr)--; sem_post(sem); printf("parent: %d\n", *ptr); } waitpid(pid); /* 如果同步成功, 共享内存的值为0 */ printf("finally: %d\n", *ptr); sem_unlink("/mysem"); } return 0; }