转自:
最近一直在研究多进程间通过共享内存来实现通信的事情,以便高效率地实现对同一数据的访问。本文中对共享内存的实现采用了系统V的机制,我们的重点在于通过信号量来完成对不同进程间共享内存资源的一致性访问,共享内存的具体方法请参见相关资料,这里不再赘述。
首先我们先实现最简单的共享内存,一个进程对其更新,另一个进程从中读出数据。同时,通过信号量的PV操作来达到对共享内存资源的保护。思路如下:1.server端首先建立一块共享内存的映射,然后创建一个信号量。通过信号量获得对共享资源的使用权限,更新内存中的内容,等待客户端读进程读取共享资源中的数据后,释放共享内存和信号量,然后退出。2.client端获得对server端创建的共享内存的映射,以及信号量的映射,通过信号量获得对共享资源的访问权限,然后读取其内容,接着解除与共享内存的映射后退出。客户端每次读取共享内存数据时首先调用wait_v()检测信号量的值,当信号量值为0时才能读取共享内存数据然后进行打印。先来看下程序,然后再对其中的一些API做相关的使用说明。server端源码1/*编译命令:gcc -o shm shm.c -g */ 2 3#include4#include 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, 0, 0); 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 }103104 shmdt(shm);105106 shmctl(shmid,IPC_RMID,&buf);107108 del_sem(semid);109110 return 0;111112}
client端源码:
1/* 编译命令:gcc -o client client.c -g*/ 2#include3#include 4#include 5 6#define SEGSIZE 1024 7#define READTIME 1 8 9union semun10{11 int val;12 struct semid_ds *buf;13 unsigned short *array;14}arg;1516/* 打印程序的执行时间函数 */17void out_time(void)18{19 static long start = 0;20 time_t tm;2122 if (start == 0)23 {24 tm = time(NULL);25 start = (long)tm;26 printf("now start \n");27 }2829 printf("second: %d\n", (long)(time(NULL)) - start);30}3132/* 创建信号量 */33int new_sem(key_t key)34{35 union semun sem;36 int semid;37 sem.val = 0;38 semid = semget(key, 0, 0);3940 if (-1 == semid)41 {42 printf("create semaphore error\n");43 exit(-1);44 }4546 return semid;47}4849/* 信号量等待函数,等待信号量的值变为0 */50void wait_v(int semid)51{52 struct sembuf sops = { 0,53 0,54 055 };5657 semop(semid, &sops, 1);58}5960int main(int argc, char **argv)61{62 key_t key;63 int shmid, semid;64 char *shm;65 char msg[100];66 char i;6768 key = ftok("/", 0);69 shmid = shmget(key, SEGSIZE, 0);7071 if (shmid == -1)72 {73 printf("create shared memory error\n");74 return -1;75 }7677 semid = new_sem(key);7879 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 }8687 shmdt(shm);8889 return 0;9091}
下面我们来解释一下程序中的细节问题。
一、信号量: 一个信号量实际上是一个整数,其值大于或等于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 };