posts - 15, comments - 9, trackbacks - 0, articles - 0

最近一直在研究多进程间通过共享内存来实现通信的事情,以便高效率地实现对同一数据的访问。本文中对共享内存的实现采用了系统V的机制,我们的重点在于通过信号量来完成对不同进程间共享内存资源的一致性访问,共享内存的具体方法请参见相关资料,这里不再赘述。

首先我们先实现最简单的共享内存,一个进程对其更新,另一个进程从中读出数据。同时,通过信号量的PV操作来达到对共享内存资源的保护。思路如下:
1.server端首先建立一块共享内存的映射,然后创建一个信号量。通过信号量获得对共享资源的使用权限,更新内存中的内容,等待客户端读进程读取共享资源中的数据后,释放共享内存和信号量,然后退出。

2.client端获得对server端创建的共享内存的映射,以及信号量的映射,通过信号量获得对共享资源的访问权限,然后读取其内容,接着解除与共享内存的映射后退出。客户端每次读取共享内存数据时首先调用wait_v()检测信号量的值,当信号量值为0时才能读取共享内存数据然后进行打印。

先来看下程序,然后再对其中的一些API做相关的使用说明。

server端源码:

  1/*编译命令:gcc -o shm shm.c -g */
  2
  3#include<sys/sem.h>
  4#include<sys/ipc.h>
  5
  6#define SEGSIZE   1024
  7#define READTIME  1
  8
  9union semum
 10{
 11    int                 val;
 12    struct semid_ds     *buf;
 13    unsigned short      *array;
 14}
arg;
 15
 16/* 创建信号量 */
 17int sem_creat(key_t  key)
 18{
 19    union semun sem;
 20    int         semid;
 21    sem.val = 0;
 22    semid = semget(key, 1, IPC_CREAT | 0666);
 23
 24    if (semid == -1)
 25    {
 26        printf("Create semaphore error\n");
 27        exit(-1);
 28    }

 29
 30    semctl(semid, 0, SETVAL, sem);
 31
 32    return semid;
 33}

 34
 35/* 删除信号量*/
 36int del_sem(int semid)
 37{
 38    union semun  sem;
 39    sem.val = 0;
 40    semctl(semid, 0, IPC_RMID, sem);
 41}

 42
 43/* 信号量的P操作,使得信号量的值加1 */
 44int p(int semid)
 45{
 46    struct sembuf sops = {0,
 47                          +1,
 48                          IPC_NOWAIT
 49                         }
;
 50
 51    return (semop(semid, &sops, 1));
 52}

 53
 54/* 信号量的v操作,使得信号量的值减1 */
 55int v(int semid)
 56{
 57    struct sembuf sops = {0,
 58                          -1,
 59                          IPC_NOWAIT
 60                         }
;
 61
 62    return (semop(semid, &sops, 1));
 63}

 64
 65/* server主程序 */
 66int main(int argc, char **argv)
 67{
 68    key_t            key;
 69    int              shmid, semid;
 70    char             *shm;
 71    char             msg[7= "-data-";
 72    char             i;
 73    struct semid_ds  buf;
 74
 75    key = ftok("/"0);
 76    shmid = shmget(key, SEGSIZE, IPC_CREAT|0604);
 77    
 78    if shmid == -1)
 79    {
 80        printf(" create shared memory error\n");
 81        return -1;
 82    }

 83
 84    shm = (char *)shmat(shmid, 00);
 85    if (-1 == (int)shm)
 86    {
 87        printf(" attach shared memory error\n");
 88        return -1;
 89    }

 90
 91    semid = sem_creat(key);
 92
 93    for (i = 0; i <= 3; i++)
 94    {
 95        sleep(1);
 96        p(semid);
 97        sleep(READTIME);
 98        msg[5= '0' + i;
 99        memcpy(shm,msg,sizeof(msg));
100        sleep(58);
101        v(semid);
102    }

103
104    shmdt(shm);
105
106    shmctl(shmid,IPC_RMID,&buf);
107
108    del_sem(semid);
109
110    return 0;
111
112}

113
114
115
116
117
118
119
120
121

client端源码:
 1/* 编译命令:gcc -o client client.c -g*/
 2#include<sys/sem.h>
 3#include<time.h>
 4#include<sys/ipc.h>
 5
 6#define SEGSIZE  1024
 7#define READTIME 1
 8
 9union semun
10{
11    int              val;
12    struct semid_ds  *buf;
13    unsigned short   *array;
14}
arg;
15
16/* 打印程序的执行时间函数 */
17void out_time(void)
18{
19    static long start = 0;
20    time_t      tm;
21
22    if (start == 0)
23    {
24        tm = time(NULL);
25        start = (long)tm;
26        printf("now start \n");
27    }

28
29    printf("second: %d\n", (long)(time(NULL)) - start);
30}

31
32/* 创建信号量 */
33int new_sem(key_t key)
34{
35    union semun sem;
36    int semid;
37    sem.val = 0;
38    semid = semget(key, 00);
39
40    if (-1 ==  semid)
41    {
42        printf("create semaphore error\n");
43        exit(-1);
44    }

45
46    return semid;
47}

48
49/* 信号量等待函数,等待信号量的值变为0 */
50void wait_v(int semid)
51{
52    struct sembuf sops = {0,
53                          0,
54                          0
55                         }
;
56
57    semop(semid, &sops, 1);
58}

59
60int main(int argc, char **argv)
61{
62    key_t  key;
63    int    shmid, semid;
64    char   *shm;
65    char   msg[100];
66    char   i;
67
68    key = ftok("/"0);
69    shmid = shmget(key, SEGSIZE, 0);
70
71    if (shmid == -1)
72    {
73        printf("create shared memory error\n");
74        return -1;
75    }

76
77    semid = new_sem(key);
78
79    for (i = 0;i < 3;i ++)
80    {
81        sleep(2);
82        wait_v(semid);
83        printf("Message geted is: %s \n",shm + 1);
84        out_time();
85    }

86
87    shmdt(shm);
88
89    return 0;
90
91}

下面我们来解释一下程序中的细节问题。

一、信号量:

        一个信号量实际上是一个整数,其值大于或等于0代表可供并发进程使用的资源实体;其值小于0时代表正在等待使用的临界区的进程数。用于互斥的信号量初始值应该大于0,且其值只能通过P、V原语操作而改变。

       信号量元素组成:
            1、表示信号量元素的值;
            2、最后操作信号量元素的进程ID
            3、等待信号量元素值+1的进程数;
            4、等待信号量元素值为0的进程数;

二、主要函数

1.1 创建信号量
      int semget( key_t key,  /* 标识信号量的关键字,有三种方法:
                                                  1、使用IPC——PRIVATE让系统产生,
                                                  2、挑选一个随机数,
                                                  3、使用ftok从文件路径名中产生
                                         */
                       int nSemes,  /* 信号量集中元素个数 */
                       int flag          /*IPC_CREAT;IPC_EXCL 只有在信号量集不存在时创建*/
 )

 成功:返回信号量句柄
 失败:返回-1
 
 1.2 使用ftok函数根据文件路径名产生一个关键字
        key_t ftok(const char *pathname,int proj_id);
        路径名称必须有相应权限 
 
 1.3 控制信号量
       int semctl( int semid,     /* 信号量集的句柄 */
                       int semnum,  /* 信号量集的元素数 */
                       int cmd,        /* 命令 */
                      /*union senum arg */... //  
                      )
       成功:返回相应的值
       失败:返回-1
 
      命令详细说明:
          IPC_RMID 删除一个信号量
          IPC_EXCL 只有在信号量集不存在时创建
          IPC_SET 设置信号量的许可权
          SETVAL 设置指定信号量的元素的值为 agc.val
          GETVAL 获得一个指定信号量的值
          GETPID 获得最后操纵此元素的最后进程ID
          GETNCNT 获得等待元素变为1的进程数
          GETZCNT 获得等待元素变为0的进程数
  
         union senum 定义如下:
              union senum{
                                 int val;
                                 struct semid_ds *buf;
                                 unsigned short * array;
                                }agc;


 其中 semid_ds 定义如下:
             struct semid_ds{
                                      struct ipc_pem sem_pem;  //operation pemission struct
                                      time_t sem_otime;  //last semop()time
                                      time_t sem_ctime;  //last time changed by semctl()
                                      struct sem *sembase;  //ptr to first semaphore in array
                                      struct sem_queue *sem_pending; //pending operations
                                      struct sem_queue *sem_pending_last; //last pending operations
                                      struct sem_undo *undo;  //undo requests on this arrary
                                      unsigned short int sem_nsems; //number of semaphores in set
                                     };
  
 1.4 对信号量 +1 或 -1 或测试是否为0

         int semop(
                         int semid, 
                         struct sembuf *sops, //指向元素操作数组
                         unsigned short nsops //数组中元素操作的个数
                        )
 
 结构 sembuf 定义
        sembuf{
                   short int sem_num; //semaphore number
                   short int sem_op; //semaphore operaion
                   short int sem_flg //operation flag
                   };

运行编译好的程序可以看到实际结果如下:

好了,今天先到这里,第二篇将对这两个程序进行改进,重点测试共享内存和信号量在使用上的一些细节问题。


参考资料:http://jengle.csai.cn/user1/27828/archives/2007/16482.html

Feedback

# re: Linux下用信号量实现对共享内存的访问保护(一)  回复  更多评论   

2010-03-03 13:15 by luckycat
你的代码中是通过判断信号量的值为0来作为对共享内存读取操作的依据,即是如下代码:
wait_v(semid);
printf("Message geted is: %s \n",shm + 1);

但实际上这里有一个潜在的问题:
即如果 wait_v(semid); 成功后,在执行接下来的printf("Message geted is: %s \n",shm + 1)之前,进程被挂起.
那么此时 server 进程可能会重新获取这个信号量并对共享内存中的数据进行写操作(当然,你这里用server sleep的时间远大于client sleep的时间来解决这个问题)
当挂起的进程重新被调度投入运行后,此时printf("Message geted is: %s \n",shm + 1)的数据实际上就不是wait_v(semid)成功后共享内存中对应的数据.

我觉得对于这种典型的 "provider/consumer" 模型,一种更好的做法是
// for provider // for consumer
P( write_sem ); P( read_sem );

// write operation // read operation

V( read_sem ); V( write_sem );


当然,这里我们需要使用 write_sem 和 read_sem 两个信号量.

# re: Linux下用信号量实现对共享内存的访问保护(一)  回复  更多评论   

2010-03-03 13:19 by 沙漠里的海豚
呵呵,你说的没错,我上面的程序在这一点上只是用一种取巧的方式来做的,是一个demo版本,还会在后续更多的介绍中进行完善。

非常感谢你提出的建议,也欢迎你继续关注,一起交流问题,分享经验。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理