最近一直在研究多进程间通过共享内存来实现通信的事情,以便高效率地实现对同一数据的访问。本文中对共享内存的实现采用了系统V的机制,我们的重点在于通过信号量来完成对不同进程间共享内存资源的一致性访问,共享内存的具体方法请参见相关资料,这里不再赘述。
首先我们先实现最简单的共享内存,一个进程对其更新,另一个进程从中读出数据。同时,通过信号量的PV操作来达到对共享内存资源的保护。思路如下:
1.server端首先建立一块共享内存的映射,然后创建一个信号量。通过信号量获得对共享资源的使用权限,更新内存中的内容,等待客户端读进程读取共享资源中的数据后,释放共享内存和信号量,然后退出。
2.client端获得对server端创建的共享内存的映射,以及信号量的映射,通过信号量获得对共享资源的访问权限,然后读取其内容,接着解除与共享内存的映射后退出。客户端每次读取共享内存数据时首先调用wait_v()检测信号量的值,当信号量值为0时才能读取共享内存数据然后进行打印。
先来看下程序,然后再对其中的一些API做相关的使用说明。
server端源码:
1
/*编译命令:gcc -o shm shm.c -g */
2
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
3
#include
<
sys
/
sem.h
>
4
#include
<
sys
/
ipc.h
>
5
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
6
#define
SEGSIZE 1024
7
#define
READTIME 1
8
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
9
union semum
10
{
11
int val;
12
struct semid_ds *buf;
13
unsigned short *array;
14
}
arg;
15
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
16
/* 创建信号量 */
17
int
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
24
if (semid == -1)
25
{
26
printf("Create semaphore error\n");
27
exit(-1);
28
}
29![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
30
semctl(semid, 0, SETVAL, sem);
31![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
32
return semid;
33
}
34
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
35
/* 删除信号量*/
36
int
del_sem(
int
semid)
37
{
38
union semun sem;
39
sem.val = 0;
40
semctl(semid, 0, IPC_RMID, sem);
41
}
42
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
43
/* 信号量的P操作,使得信号量的值加1 */
44
int
p(
int
semid)
45
{
46
struct sembuf sops = {0,
47
+1,
48
IPC_NOWAIT
49
};
50![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
51
return (semop(semid, &sops, 1));
52
}
53
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
54
/* 信号量的v操作,使得信号量的值减1 */
55
int
v(
int
semid)
56
{
57
struct sembuf sops = {0,
58
-1,
59
IPC_NOWAIT
60
};
61![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
62
return (semop(semid, &sops, 1));
63
}
64
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
65
/* server主程序 */
66
int
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
91
semid = sem_creat(key);
92![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
104
shmdt(shm);
105![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
106
shmctl(shmid,IPC_RMID,&buf);
107![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
108
del_sem(semid);
109![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
110
return 0;
111![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
112
}
113
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
114
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
115
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
116
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
117
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
118
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
119
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
120
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
121
client端源码:
1
/* 编译命令:gcc -o client client.c -g*/
2
#include
<
sys
/
sem.h
>
3
#include
<
time.h
>
4
#include
<
sys
/
ipc.h
>
5
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
6
#define
SEGSIZE 1024
7
#define
READTIME 1
8
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
9
union semun
10
{
11
int val;
12
struct semid_ds *buf;
13
unsigned short *array;
14
}
arg;
15
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
16
/* 打印程序的执行时间函数 */
17
void
out_time(
void
)
18
{
19
static long start = 0;
20
time_t tm;
21![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
22
if (start == 0)
23
{
24
tm = time(NULL);
25
start = (long)tm;
26
printf("now start ![](http://www.cppblog.com/Images/dot.gif)
\n");
27
}
28![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
29
printf("second: %d\n", (long)(time(NULL)) - start);
30
}
31
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
32
/* 创建信号量 */
33
int
new_sem(key_t key)
34
{
35
union semun sem;
36
int semid;
37
sem.val = 0;
38
semid = semget(key, 0, 0);
39![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
40
if (-1 == semid)
41
{
42
printf("create semaphore error\n");
43
exit(-1);
44
}
45![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
46
return semid;
47
}
48
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
49
/* 信号量等待函数,等待信号量的值变为0 */
50
void
wait_v(
int
semid)
51
{
52
struct sembuf sops = {0,
53
0,
54
0
55
};
56![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
57
semop(semid, &sops, 1);
58
}
59
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
60
int
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
68
key = ftok("/", 0);
69
shmid = shmget(key, SEGSIZE, 0);
70![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
71
if (shmid == -1)
72
{
73
printf("create shared memory error\n");
74
return -1;
75
}
76![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
77
semid = new_sem(key);
78![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
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![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
87
shmdt(shm);
88![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
89
return 0;
90![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
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
# re: Linux下用信号量实现对共享内存的访问保护(一) 回复 更多评论
2010-03-03 13:15 by
你的代码中是通过判断信号量的值为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版本,还会在后续更多的介绍中进行完善。
非常感谢你提出的建议,也欢迎你继续关注,一起交流问题,分享经验。