信号量
# 信号量
# 信号量简介
为了防止出现多个程序同时访问一个共享资源而引发的问题,需要采取一种机制,使得在任一时刻,只有一个进程可以访问到临界区域,即信号量机制。
- 信号量分类
- 二进制信号量(二元信号量):取值为0、1
- 通用信号量:取值可为0或任意正整数。
# PV操作
P操作:
- 如果信号量的值大于0,就给它减1;
- 如果信号量的值等于0,就挂起该进程的执行。
V操作:
- 如果有其他进程因等待信号量而被挂起,就让它恢复运行;
- 如果没有进程因等待信号量而挂起,就给它加1。
对普通变量进行加减是不行的,因为在c/c++等语言中,没有一个原子操作可以完成变量检测和加减的功能。
# 信号量处理函数
以下几个函数用于处理信号量操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops);
int semctl(int sem_id, int sem_num, int command, ...);
1
2
3
4
5
6
2
3
4
5
6
# semget函数
该函数的作用是创建一个新信号量,或取得一个已有信号量。
int semget(key_t key, int num_sems, int sem_flags);
1
key
:整数值。不相关的进程可以通过它来访问同一个信号量。程序对信号量的访问都是间接的,它先提供一个键,再由系统生成一个信号量标识符,其他进程都通过
semget
函数返回信号量标识符。num_sems
:整数值。需要的信号量数目,一般总是1。sem_flags
:一组标志,代表该信号量的权限。IPC_CREAT
表示创建一个新的信号量(即使对应键已经创建了信号量,也不会错误),可以和权限位按位或。
# semop函数
该函数的作用是改变信号量的值
int semop(int sem_id, struct sem_buf* sem_ops, size_t num_sem_ops);
1
sem_id
:由semget
返回的信号量标识符。sem_ops
:指向一个结构数组的指针,该结构描述了信号量操作情况/* Structure used for argument to `semop' to describe operations. */ struct sembuf { unsigned short int sem_num; /* 信号量编号,如果不使用一组信号量,则取值一般为0 */ short int sem_op; /* 一次操作中希望改变的值,可以为-1,即P操作,也可以为1,即V操作 */ short int sem_flg; /* 通常设为SEM_UNDO,指示操作系统将跟踪当前进程对这个信号量的修改情况, 如果这个进程没有释放信号量即终止,那么操作系统将自动释放该进程持有的信号量。*/ };
1
2
3
4
5
6
7
8num_sem_ops
:上述结构数组中元素的个数。
# semctl函数
该函数允许直接控制信号量信息。
int semctl(int sem_id, int sem_num, int command, ...);
1
如果存在第四个参数,将会是一个union semun结构。这个需要程序员自己定义。
union semun { int val; //信号量初始值 struct semid_ds *buf; unsigned short int *array; struct seminfo *__buf; };
1
2
3
4
5
6
7
# 信号量使用案例
用两个不同字符的输出代表进入和离开缓冲区。
如果程序启动时带有一个参数,它将在进入和退出临界区域时打印字符X,而程序的其他运行实例将在进入和退出临界区域时打印字符O。
因为在任意给定时刻,只有一个进程能进入临界区域,因此字符X和O都应成对出现。
# 示例代码
// sem.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
int main(int argc, char *argv[])
{
int i;
int pause_time;
char op_char = 'O';
srand((unsigned int)getpid());
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if (argc > 1)
{
if (!set_semvalue())
{
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
op_char = 'X';
sleep(5);
}
for(i = 0; i < 10; i++)
{
if (!semaphore_p())
exit(EXIT_FAILURE);
printf("%c", op_char);
fflush(stdout);
pause_time = rand() % 3;
sleep(pause_time);
printf("%c", op_char);
fflush(stdout);
if (!semaphore_v())
exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (argc > 1)
{
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;
if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
return(0);
return(1);
}
static void del_semvalue(void)
{
union semun sem_union;
if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1; /* P() */
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_p failed\n");
return(0);
}
return(1);
}
static int semaphore_v(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1; /* V() */
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v failed\n");
return(0);
}
return(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
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
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
# 输出
$ ./sem a &
[1] 3705
$ ./sem
OOXXXXOOXXOOOOOOOOOOOOOOOOXXX
3706 - finished
$ XXXXXXXXXX#没有输入,这是后台进程的输出。
3705 - finished
1
2
3
4
5
6
7
2
3
4
5
6
7
# 分析
O和X都是成对出现的,说明在临界区访问时是独占的。P操作和V操作之间是临界区。
set_semvalue()
函数将信号量设置为 1。del_semvalue()
函数删除信号量。semaphore_p()
函数执行 P 操作。semaphore_v()
函数执行 V 操作。
编辑 (opens new window)
上次更新: 2023/03/31, 22:34:04
- 01
- Linux系统移植(五)--- 制作、烧录镜像并启动Linux02-05
- 03
- Linux系统移植(三)--- Linux kernel移植02-05