进程同步实验

ipcs -*

ipcs -m

linux中可用命令ipcs -m 观察共享内存情况

  • key 共享内存关键值

  • shmid 共享内存标识

  • owner 共享内存所由者

  • perm 共享内存使用权限

  • byte 共享内存字节数

  • nattch 共享内存使用计数

  • status 共享内存状态

ipcs -s

linux中可用命令ipcs -s 观察信号量数组的情况

  • semid 信号量的标识号

  • nsems 信号量的个数

ipcs -q

linux中可用命令ipcs -q 观察消息队列的情况。

  • msgmid 消息队列的标识号
  • used-bytes 消息的字节长度
  • messages 消息队列中的消息条数

ipcrm

在权限允许的情况下您可以使用ipcrm命令删除系统当前存在的IPC 对象中的任一个对象。

ipcrm -m 21482: 删除标号为21482 的共享内存。

ipcrm -s 32673: 删除标号为32673 的信号量数组。

ipcrm -q 18465: 删除标号为18465 的消息队列。

系统调用

IPC 对象有关的系统调用函数原型都声明在以下的头文件中 :

1
2
#include <sys/types.h>
#include <sys/ipc.h>

其余参见指导书

共享内存

创建

创建一段共享内存系统调用语法 :

1
2
#include <sys/shm.h>
int shmget(key_t key,int size,int flags);

key 共享内存的键值,可以为IPC_PRIVATE,也可以用整数指定一个

size 共享内存字节长度

flags 共享内存权限位。

shmget 调用成功后,如果key 用新整数指定,且flags 中设置了IPC_CREAT位,则返回一个新建立的共享内存段标识符。 如果指定的key 已存在则返回与key关联的标识符。 不成功返回-1

附加

令一段共享内存附加到调用进程中的系统调用语法:

1
2
#include <sys/shm.h>
char *shmat(int shmid, char *shmaddr,int flags)

shmidshmget创建的共享内存的标识符

shmaddr 总为0,表示用调用者指定的指针指向共享段

flags 共享内存权限位

shmat调用成功后返回附加的共享内存首地址

分离

令一段共享内存从到调用进程中分离出去的系统调用语法:

1
2
#include <sys/shm.h>
int shmdt(char *shmadr);

shmadr 进程中指向附加共享内存的指针

shmdt 调用成功将递减附加计数,当计数为0,将删除共享内存。调用不成功返回-1。

信号量

创建

创建一个信号量数组的系统调用有语法:

1
2
#include <sys/sem.h>
int semget(key_t key,int nsems, int flags);

key 信号量数组的键值,可以为IPC_PRIVATE,也可以用整数指定一个

nsems 信号量数组中信号量的个数

flags 信号量数组权限位。如果key用整数指定,应设置IPC_CREAT 位。

semget调用成功,如果key用新整数指定,且flags 中设置了IPC_CREAT 位,则返回一个新建立的信号等数组标识符。 如果指定的整数key已存在则返回与key关联的标识符。 不成功返回-1

操作

操作信号量数组的系统调用语法:

1
2
#include <sys/sem.h>
int semop(int semid,struct sembuf *semop, unsigned nops);

semidsemget创建的信号量数组的标识符

semop 指向sembuf数据结构的指针

nops 信号量上的操作数,例如该值为1 相当于P操作,-1 相当于V操作.

semop调用成功返回0,不成功返回-1。

控制

控制信号量数组的系统调用语法:

1
2
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd, union semun arg);

semidsemget创建的信号量数组的标识符

semnum 该信号量数组中的第几个信号量

cmd 对信号量发出的控制命令。例如:

GETVAL 返回当前信号量状态

SETVAL 设置信号量状态

IPC_RMD 删除标号为semid的信号量

arg 保存信号量状态的联合体,信号量的值是其中一个基本成员

1
2
3
4
union semun {
int val; /* value for SETVAL */
......
};

semctl 执行不成功返回-1,否则返回指定的cmd的值。

semget()

semget()函数既可以用于获取之前创建的信号量集合,也可以用于创建新的信号量集合。semget()的函数原型如下:

1
2
// 成功返回信号量集合的标识符,否则返回-1
int semget(key_t key, int nsems, int semflg);

key是绑定在信号量集合上的,我们可以用key来寻找已经创建的信号量集合,或者用于创建并绑定新的信号量集合。注意,key和函数的返回值(信号量集合)不是一样的。nsems表示该信号量集合有多少个信号量,如果用信号量来创建binary semaphore,我们只需要将nsems设置为1。semflg是设置信号量集合的标志位,其中最低9位是信号量集合的访问权限(和文件一样,3个8进制数字,比如0777)。其他位和文件创建的标志位类似,比如IPC_CREAT是创建新的信号量集合(文件是O_CREAT),在使用信号量前,我们都需要创建信号量集合。

在调用该系统调用后,与信号量集合绑定的数据结构semid_ds也被初始化。

semop()

semop()用于操作信号量,简单来说就是对信号量的计数器进行加减。函数原型如下:

1
int semop(int semid, struct sembuf *sops, size_t nsops);

semid是信号量集合的标识符,注意,这不是semget()中的key而是这个函数的返回值。nsops是有多少个信号量需要被操作,在我们的例子中只有1个信号量需要被操作。sops是对信号量操作的具体命令,它是一个struct sembuf类型的结构体,一共有nsops个这样的结构体:

1
2
3
unsigned short sem_num;  /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */

sem_num是从0开始计数的,表示是第几个信号量;sem_flg可以选择IPC_NOWAITSEM_UNDOSEM_UNDO表示在进程结束后,内核会自动释放没有主动释放的信号量。sops是按照数组的顺序执行的,并且是原子的,如果所有的信号量不能同时操作,那么就不进行操作。

sem_op的使用很简单,但具体如何设置、不同设置有什么不同却比较麻烦。在binary semaphore的例子中,sem_op设置为1就是信号量计数器加1;设置为-1就是信号量计数器减1,下面具体讲讲sem_op设置背后的故事。

除了上面提到的sembuf结构体,每个信号量还对应一系列变量:

1
2
3
4
unsigned short  semval;   /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* process ID of [the] last operation */

之前提到的信号量计数器就是semvalsemzcnt是等待semval为0的进程数,semncnt是等待semval增加的进程数。semop()对信号量的操作都会改变这些变量。

如果sem_op设置为正数,那么每次操作后semval将变成semval + sem_op;如果sem_flg设置了SEM_UNDO,那么semadj将变成semadj - sem_op。进程需要有写的权限才能修改信号量。

如果sem_op设置为负数,情况要复杂些:

  1. 如果abs(sem_op)小于等于semval,那么semval将变成semval - abs(sem_op);如果sem_flg设置了SEM_UNDO,那么semadj将变成semadj + abs(sem_op)
  2. 如果abs(sem_op)大于semval并且sem_flg设置了IPC_NOWAIT,那么将返回错误码EAGAIN,并且sem_op的操作不会进行;
  3. 如果2中没有设置IPC_NOWAIT,那么semval将变成semval - abs(sem_op),同时semncnt 变成semncnt + 1,并且进程会进入sleep状态。当semval >= abs(sem_op)时,进程会被唤醒。

其他具体细节请参考man semop

从加锁和解锁的角度进行思考,当semval = 0时,如果操作是sem_op = -1,那么semval将变成-1,因此进程进入睡眠状态:这就是加锁的操作;同理,sem_op = 1是解锁的操作。

semctl()

通过上面两个系统调用,我们知道如何创建信号量以及如何操作信号量,下面这个系统调用可以初始化信号量以及删除信号量。我们先来看看semctl()的函数原型:

1
int semctl(int semid, int semnum, int cmd, ...);

semctl()支持的命令有很多,这里我们主要介绍两个 - IPC_RMIDSETVALsemid是信号量集合的索引,semnum是其中第几个信号量(从0开始),cmd就是命令。这个函数是可变长度参数,有些命令是4个参数,第4个参数是union semun,这个union定义如下,注释中注明了哪些命令要用这个union

1
2
3
4
5
6
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
};

使用SETVAL命令时,我们就是通过valsemval赋值,用于初始化信号量。我们这里看看如何使用semctl()来初始化和删除信号量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 初始化信号量
int set_sem(int sem_id) {
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1) {
fprintf(stderr, "Failed to set sem\n");
return 0;
}
return 1;
}

// 删除信号量
void del_sem(int sem_id) {
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete sem, sem has been del.\n");
}

参考:https://rdou.github.io/2020/06/22/Linux编程基础3-进程间通信-信号量/

基本逻辑

生产者:首先如果缓冲区满则生产者阻塞,其次生产者不能同时进行提供材料,所以要设置互斥锁使其不能同时进行执行。生产者每次提供其中的两种后,唤醒抽烟者。

抽烟者:而对于抽烟者来说,刚开始并没有所需要的材料,要等待生产者的唤醒,同时还要设置互斥锁防止抽烟者同时进行吸烟。判断当前放的两个材料是否是其所需,是则从共享缓冲区间读取所需要的数据,然后唤起两个生产者和其他抽烟者。若不是,则唤醒其他抽烟者抽烟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
semaphore tobacco_paper     = 0; // waiting for tobacco and paper
semaphore tobacco_matches = 0; // waiting for tobacco and matches
semaphore paper_matches = 0; // waiting for paper and matches
semaphore doneSomking = 0;
//生产者
while( true ) {
pick a random number from 1-3
if random number is 1
// put these two ingredient on table
signal( tobacco_paper )
else if random number is 2
// put these two ingredient on table
signal( tobacco_matches ) // put on table
else if random number is 3
// put these two ingredient on table
signal( paper_matches ) // put on table
wait( doneSmoking )
}

// the smoker that has matches
while(true){
wait( tobacco_paper ); /* picks up tobacco and paper */
  // roll cigarette and smoke
  signal( doneSmoking );
}

// the smoker that has paper
while(true){
wait(tobacco_matches); /* picks up tobacco and match */
  // roll cigarette and smoke
  signal( doneSmoking );
}

// the smoker that has tobacco
while(true){
wait(matches_paper ); /* picks up matches and paper */
  // roll cigarette and smoke
  signal( doneSmoking );
}

Linux下的代码设计

参考

①关系分析:供应者与三个抽烟者分别是同步关系。由于抽烟者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。

②整体思路:一共设计六个文件,ipc.hipc.c设计信号量机制实现的底层逻辑,例如消息队列、P操作、V操作等。设计四个进程,供应者作为生产者向三个抽烟者提供材料。

③信号量设置:信号量offer1、offer2、offer3分别表示烟草和纸组合,烟草和胶水组合,胶水和纸组合。信号量finish表示抽烟完成信号。它们之间的协同关系如下图:

同时还要设置一个互斥信号量,保证4个进程互斥访问缓冲区,但该互斥信号量不是必须的,因为本例中缓冲区大小只为1。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ipc.h
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#define BUFSZ 256
//建立或获取 ipc 的一组函数的原型说明
int get_ipc_id(char *proc_file,key_t key);
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
int set_msq(key_t msq_key,int msq_flag);
int set_sem(key_t sem_key,int sem_val,int sem_flag);
int down(int sem_id);
int up(int sem_id);
/*信号灯控制用的共同体*/
typedef union semuns
{
int val;
} Sem_uns;
/* 消息结构体*/
typedef struct msgbuf
{
long mtype;
char mtext[1];
} Msg_buf;
//生产消费者共享缓冲区即其有关的变量
key_t buff_key;
int buff_num;
char *buff_ptr;
//生产者放产品位置的共享指针
key_t pput_key;
int pput_num;
int *pput_ptr;
//消费者取产品位置的共享指针
key_t cget_key;
int cget_num;
int *cget_ptr;
//生产者和消费者有关的信号量
key_t offer1_key;
key_t offer2_key;
key_t offer3_key;
key_t finish_key;
key_t mutex_key;

int offer1;
int offer2;
int offer3;
int finish;
int mutex;

int sem_val;
int sem_flg;
int shm_flg;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// ipc.c
#include "ipc.h"

// 从/proc/sysvipc/文件系统中获取IPC中3个对象的id号
// key是要获取的对象的id号
int get_ipc_id(char *proc_file, key_t key)
{
FILE *pf;
int i,j;
char line[BUFSZ], colum[BUFSZ];
if((pf = fopen(proc_file,"r")) == NULL)
{
perror("Proc file not open");
exit(EXIT_FAILURE);
}
// 从文件中读取一行并储存在line[]中
fgets(line, BUFSZ, pf);
while(!feof(pf))
{
i = j = 0;
fgets(line, BUFSZ,pf);
while(line[i] == ' ')
i++;
while(line[i] != ' ')
colum[j++] = line[i++];
colum[j] = '\0';
// 找不到对应的key,就继续读
if(atoi(colum) != key)
continue;
// 找到了对应的key,读出紧挨着的id
j = 0;
while(line[i] == ' ')
i++;
while(line[i] !=' ')
colum[j++] = line[i++];
colum[j] = '\0';
i = atoi(colum);
fclose(pf);
// 返回id值
return i;
}
fclose(pf);
return -1;
}

// 定义信号量sem_id上的down操作(P操作)
int down(int sem_id)
{
struct sembuf buf;
// 对索引值为0的信号量定义P操作
buf.sem_num = 0;
// 调用sem_op后,信号量的值-1
buf.sem_op = -1;
// 若P操作后信号量<=0,进程等待
buf.sem_flg = SEM_UNDO;
// 系统调用执行定义的P操作
if((semop(sem_id,&buf,1)) < 0)
{
perror("P error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

// 定义信号量sem_id上的up操作(V操作)
int up(int sem_id)
{
struct sembuf buf;
buf.sem_op = 1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) < 0)
{
perror("V error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

// 把sem_key对应的信号量赋值为sem_val,权限位为sem_flag
int set_sem(key_t sem_key, int sem_val, int sem_flg)
{
int sem_id;
// 保存信号量状态的结构体
Sem_uns sem_arg;

// 测试由 sem_key 标识的信号量数组是否已经建立
if((sem_id = get_ipc_id("/proc/sysvipc/sem", sem_key)) < 0 )
{
// 新建1个信号量,键值为sem_key,权限位为sem_flag,标号返回到sem_id
if((sem_id = semget(sem_key, 1, sem_flg)) < 0)
{
perror("semaphore create error");
exit(EXIT_FAILURE);
}
// 设置信号量的初值
sem_arg.val = sem_val;
// 在sem_id标识的信号量数组的第0个信号量上执行SETVAL操作,arg保存信号量状态的联合体
// SETVAL为设置信号量集中的一个单独的信号量的值
if(semctl(sem_id, 0, SETVAL, sem_arg) < 0)
{
perror("semaphore set error");
exit(EXIT_FAILURE);
}
}
return sem_id;
}

// 把shm_key对应的缓冲区开辟空间shm_num,权限位为shm_flag
char* set_shm(key_t shm_key, int shm_num, int shm_flg)
{
int i, shm_id;
// 指向共享缓冲区的指针
char* shm_buf;

// 测试由 shm_key 标识的共享内存区是否已经建立
if((shm_id = get_ipc_id("/proc/sysvipc/shm", shm_key)) < 0 )
{
// shmget 新建 一个长度为 shm_num 字节的共享内存,其标号返回到 shm_id
if((shm_id = shmget(shm_key, shm_num, shm_flg)) < 0)
{
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
// shmat 将由 shm_id 标识的共享内存附加给指针shm_buf,即映射到调用进程的地址空间上
if((shm_buf = (char*)shmat(shm_id, 0, 0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
// 初始化内存为 0
for(i = 0; i < shm_num; i++)
shm_buf[i] = 0;
}

//shm_key 标识的共享内存区已经建立,将由 shm_id 标识的共享内存附加给指 针 shm_buf
if((shm_buf = (char*)shmat(shm_id, 0, 0)) < (char*)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}

// 创建消息队列
int set_msq(key_t msq_key,int msq_flg)
{
int msq_id;
//测试由 msq_key 标识的消息队列是否已经建立
if((msq_id = get_ipc_id("/proc/sysvipc/msg", msq_key)) < 0 )
{
//msgget 新建一个消息队列,其标号返回到 msq_id
if((msq_id = msgget(msq_key,msq_flg)) < 0)
{
perror("messageQueue set error");
exit(EXIT_FAILURE);
}
}
return msq_id;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// producer.c
#include "ipc.h"
#include<stdlib.h>
int main(int argc,char *argv[])
{
int rate;
// 可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度,不指定则为1秒
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 1;
// 共享内存使用的变量
buff_key = 101; // 缓冲区键值
buff_num = 1; // 缓冲区长度
pput_key = 102; // 生产者放产品指针的键值
pput_num = 1; // 指针数
// 0644 = 110100100,生产者可读可写,抽烟者只可读
shm_flg = IPC_CREAT | 0644;

// 获取缓冲区使用的共享内存, buff_ptr指向缓冲区首地址
buff_ptr = (char*)set_shm(buff_key, buff_num, shm_flg);
// 获取消费者取产品位置指针pput_ptr
pput_ptr = (int*)set_shm(pput_key, pput_num, shm_flg);

// 信号量键值设置
offer1_key = 201;
offer2_key = 202;
offer3_key = 203;
finish_key = 204;
mutex_key = 205;

// 访问权限设置
sem_flg = IPC_CREAT | 0644;

// 同步信号量设置
sem_val = 0;
offer1 = set_sem(offer1_key, sem_val, sem_flg);
offer2 = set_sem(offer2_key, sem_val, sem_flg);
offer3 = set_sem(offer3_key, sem_val, sem_flg);
finish = set_sem(finish_key, sem_val, sem_flg);

// 互斥信号量设置
sem_val = 1;
mutex = set_sem(mutex_key, sem_val, sem_flg);

int i = 0;
while(1)
{
i = (i + 1) % 3;
buff_ptr[*pput_ptr] = i + 1;
sleep(rate);
down(mutex); // 互斥访问缓冲区
*pput_ptr = (*pput_ptr + 1) % buff_num;
if(i == 0) {
printf("%d put offer1 - tobacco and paper into buffer[%d]\n", getpid(), *pput_ptr);
up(offer1); // 将组合1放到桌上
}
else if(i == 1) {
printf("%d put offer2 - glue and tobacco into buffer[%d]\n", getpid(), *pput_ptr);
up(offer2); // 将组合2放到桌上
}
else {
printf("%d put offer3 - glue and paper into buffer[%d]\n", getpid(), *pput_ptr);
up(offer3); // 将组合3放到桌上
}
up(mutex); // 互斥访问缓冲区

down(finish); // 前V后P
}

return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// consumer_C.c
#include "ipc.h"

int main(int argc,char *argv[])
{
int rate;
// 可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 3; // 不指定为 3 秒
//共享内存 使用的变量
buff_key = 101; // 缓冲区键值
buff_num = 1; // 缓冲区长度
cget_key = 103; // 抽烟者取产品指针的键值
cget_num = 1; // 指针数
shm_flg = IPC_CREAT | 0644;
// 获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char*)set_shm(buff_key, buff_num, shm_flg);
// 获取抽烟者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int*)set_shm(cget_key, cget_num, shm_flg);

// 信号量键值设置
offer1_key = 201;
offer2_key = 202;
offer3_key = 203;
finish_key = 204;
mutex_key = 205;

// 访问权限设置
sem_flg = IPC_CREAT | 0644;

// 同步信号量设置
sem_val = 0;
offer3 = set_sem(offer3_key, sem_val, sem_flg);
finish = set_sem(finish_key, sem_val, sem_flg);

// 互斥信号量设置
sem_val = 1;
mutex = set_sem(mutex_key, sem_val, sem_flg);

while(1)
{
sleep(rate);
down(offer3);

down(mutex);
*cget_ptr = (*cget_ptr + 1) % buff_num;
printf("%d smoker get glue and paper offer%d from buffer[%d]\n", getpid(), buff_ptr[*cget_ptr], *cget_ptr);
up(mutex);

up(finish);
}
return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// consumer_B.c
#include "ipc.h"

int main(int argc,char *argv[])
{
int rate;
// 可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 3; // 不指定为 3 秒
//共享内存 使用的变量
buff_key = 101; // 缓冲区键值
buff_num = 1; // 缓冲区长度
cget_key = 103; // 抽烟者取产品指针的键值
cget_num = 1; // 指针数
shm_flg = IPC_CREAT | 0644;
// 获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char*)set_shm(buff_key, buff_num, shm_flg);
// 获取抽烟者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int*)set_shm(cget_key, cget_num, shm_flg);

// 信号量键值设置
offer1_key = 201;
offer2_key = 202;
offer3_key = 203;
finish_key = 204;
mutex_key = 205;

// 访问权限设置
sem_flg = IPC_CREAT | 0644;

// 同步信号量设置
sem_val = 0;
offer2 = set_sem(offer2_key, sem_val, sem_flg);
finish = set_sem(finish_key, sem_val, sem_flg);

// 互斥信号量设置
sem_val = 1;
mutex = set_sem(mutex_key, sem_val, sem_flg);

while(1)
{
sleep(rate);
down(offer2);

down(mutex);
*cget_ptr = (*cget_ptr + 1) % buff_num;
printf("%d smoker get glue and tobacco offer%d from buffer[%d]\n", getpid(), buff_ptr[*cget_ptr], *cget_ptr);
up(mutex);

up(finish);
}
return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// consumer_A.c
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 3;
//共享内存 使用的变量
buff_key = 101; // 缓冲区键值
buff_num = 1; // 缓冲区长度
cget_key = 103; // 抽烟者取产品指针的键值
cget_num = 1; // 指针数
shm_flg = IPC_CREAT | 0644;
// 获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char*)set_shm(buff_key, buff_num, shm_flg);
// 获取抽烟者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int*)set_shm(cget_key, cget_num, shm_flg);

// 信号量键值设置
offer1_key = 201;
offer2_key = 202;
offer3_key = 203;
finish_key = 204;
mutex_key = 205;

// 访问权限设置
sem_flg = IPC_CREAT | 0644;

// 同步信号量设置
sem_val = 0;
offer1 = set_sem(offer1_key, sem_val, sem_flg);
finish = set_sem(finish_key, sem_val, sem_flg);

// 互斥信号量设置
sem_val = 1;
mutex = set_sem(mutex_key, sem_val, sem_flg);

while(1)
{
sleep(rate);
down(offer1);

down(mutex);
*cget_ptr = (*cget_ptr + 1) % buff_num;
printf("%d smoker get tobacco and paper offer%d from buffer[%d]\n", getpid(), buff_ptr[*cget_ptr], *cget_ptr);
up(mutex);

up(finish);
}

return EXIT_SUCCESS;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
hdrs = ipc.h
opts = -g -c

p_src = producer.c ipc.c
p_obj = producer.o ipc.o
c_T_src = consumer_tobacco.c ipc.c
c_T_obj = consumer_tobacco.o ipc.o
c_P_src = consumer_paper.c ipc.c
c_P_obj = consumer_paper.o ipc.o
c_G_src = consumer_glue.c ipc.c
c_G_obj = consumer_glue.o ipc.o

all: producer consumer_tobacco consumer_paper consumer_glue

producer: $(p_obj)
gcc $(p_obj) -o producer
producer.o: $(p_src) $(hdrs)
gcc $(opts) $(p_src)

consumer_tobacco: $(c_T_obj)
gcc $(c_T_obj) -o consumer_tobacco
consumer_tobacco.o: $(c_T_src) $(hdrs)
gcc $(opts) $(c_T_src)

consumer_paper: $(c_P_obj)
gcc $(c_P_obj) -o consumer_paper
consumer_paper.o: $(c_P_src) $(hdrs)
gcc $(opts) $(c_P_src)

consumer_glue: $(c_G_obj)
gcc $(c_G_obj) -o consumer_glue
consumer_glue.o: $(c_G_src) $(hdrs)
gcc $(opts) $(c_G_src)

clean:
rm producer consumer_tobacco consumer_paper consumer_glue *.o

一些问题

  • 由于该程序会修改文件/proc/sysvipc/sem中的记录,且运行完后不会删除,因此如果多次对文件进行修改,并make后执行,可能使用到一个错误的信号量id,对该信号量的操作不会反映到程序中,程序会直接跳过对信号量的P操作和V操作。解决方法是重启虚拟机。

比如,就会这样:

  • finish同步信号量的设置可以为1或0,如果是1,则应该在while循环开始处调用P操作,这样第一遍循环可以顺利执行,阻塞在第二次循坏的开始处;如果是0,则阻塞在while循坏的尾部。