Linux下用信号量实现对共享内存的访问保护

时间:2023-03-09 00:42:56
Linux下用信号量实现对共享内存的访问保护

转自:http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html

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

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

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

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

server端源码

 /*编译命令:gcc -o shm shm.c -g */

  #include<sys/sem.h>
#include<sys/ipc.h> #define SEGSIZE 1024
#define READTIME 1 9union semum
{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg; /* 创建信号量 */int sem_creat(key_t key)
{
union semun sem;
int semid;
sem.val = ;
semid = semget(key, , IPC_CREAT | ); if (semid == -)
{
printf("Create semaphore error\n");
exit(-);
} semctl(semid, , SETVAL, sem); return semid;
} /* 删除信号量*/int del_sem(int semid)
{
union semun sem;
sem.val = ;
semctl(semid, , IPC_RMID, sem);
} /* 信号量的P操作,使得信号量的值加1 */int p(int semid)
{
struct sembuf sops = {,
+,
IPC_NOWAIT
}; return (semop(semid, &sops, ));
} /* 信号量的v操作,使得信号量的值减1 */int v(int semid)
{
struct sembuf sops = {,
-,
IPC_NOWAIT
}; return (semop(semid, &sops, ));
} /* server主程序 */int main(int argc, char **argv)
{
key_t key;
int shmid, semid;
char *shm;
char msg[] = "-data-";
char i;
struct semid_ds buf; key = ftok("/", );
shmid = shmget(key, SEGSIZE, IPC_CREAT|); if shmid == -)
{
printf(" create shared memory error\n");
return -;
} shm = (char *)shmat(shmid, , );
if (- == (int)shm)
{
printf(" attach shared memory error\n");
return -;
} semid = sem_creat(key); for (i = ; i <= ; i++)
{
sleep();
p(semid);
sleep(READTIME);
msg[] = '' + i;
memcpy(shm,msg,sizeof(msg));
sleep();
v(semid);
} shmdt(shm); shmctl(shmid,IPC_RMID,&buf); del_sem(semid); return ; }

client端源码:

 /* 编译命令:gcc -o client client.c -g*/
#include<sys/sem.h>
#include<time.h>
#include<sys/ipc.h> #define SEGSIZE 1024
#define READTIME 1 9union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg; /* 打印程序的执行时间函数 */void out_time(void)
{
static long start = ;
time_t tm; if (start == )
{
tm = time(NULL);
start = (long)tm;
printf("now start \n");
} printf("second: %d\n", (long)(time(NULL)) - start);
} /* 创建信号量 */int new_sem(key_t key)
{
union semun sem;
int semid;
sem.val = ;
semid = semget(key, , ); if (- == semid)
{
printf("create semaphore error\n");
exit(-);
} return semid;
} /* 信号量等待函数,等待信号量的值变为0 */void wait_v(int semid)
{
struct sembuf sops = {,
, }; semop(semid, &sops, );
}
int main(int argc, char **argv)
{
key_t key;
int shmid, semid;
char *shm;
char msg[];
char i; key = ftok("/", );
shmid = shmget(key, SEGSIZE, ); if (shmid == -)
{
printf("create shared memory error\n");
return -;
} semid = new_sem(key); for (i = ;i < ;i ++)
{
sleep();
wait_v(semid);
printf("Message geted is: %s \n",shm + );
out_time();
} shmdt(shm); return ; }

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

一、信号量:

一个信号量实际上是一个整数,其值大于或等于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
                   };