APUE_共享存储区

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;
}


 

 

 

 

你可能感兴趣的:(unix)