1.引言
共享存储(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结构,用以规定存储段的访问权限和所有者。系统中,关于共享存储段的限制包括:共享存储段的最大字节数、共享存储段的最小字节数、共享存储段的最大段数以及每个进程共享存储段的最大段数。当然不同系统不一样!
2.获得共享存储标识符
为了获得一个共享存储标识符,调用的第一个函数通常是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。
3.示例程序
//用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;
}