信号量

信号量

信号量是用来解决进程之间的同步与互斥问题的一种进程之间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。

信号量对应于某一种资源,去一个非负的整型值。信号量值指的是当前可用的资源的数量,若等于0则说明没有可用的资源。PV原子操作的具体定义如下:

  • P操作:如果有可用资源(信号量值>0),则占用一个资源(信号值减一,进入临界区代码);如果没有可用资源(信号量值=0),则被阻塞,直到系统将资源分配给该进程。
  • V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程。如果没有进程等待它,则释放一个资源(信号量值加一)。

最简单的信号量只能取0和1,叫做二维信号量。

使用信号量的步骤

  1. 创建信号量或获得在系统已存在的信号量,此时需要调用semget()函数。不同进程通过使用同一个信号量键值来获得同一个信号量
  2. 初始化信号量,此时使用semctl()函数的SETVAL操作。当使用二维信号量时,通常将信号量初始化为1
  3. 进行信号量的PV操作,此时调用semop()函数。这一步是实现进程之间的同步和互斥的核心工作部分
  4. 如果不需要信号量,则从系统中删除它,此时使用semctl()函数的IPC_RMID操作

相关函数

semget()

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget (key_t ket, int semflag);

key:信号量的键值,多个进程可以通过它访问同一个信号量,其中有个特殊值IPC_PRIVATE。它用于创建当前进程的私有信号量

name:需要创建的信号量数目,通常取值为1

semflg:同open()函数的权限位,也可以用八进制表示法,其中使用IPC_CREAT标志创建新的信号量,即使该信号量已经存在,也不会出错。如果同时使用IPC_EXCL标志可以创建一个新的唯一的信号量,如果该信号量已经存在,该函数会出错

成功时,返回信号量标识符;出错时返回-1

semctl()

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl (int semid, int semnum, int cmd, union semun arg);

semid:semget()函数返回的信号量标识

semnum:信号量编号,当使用信号量集时才会被用到,通常取值为0

cmd:指定对信号量的各种操作,使用单个信号量时通常有以下几种:

  • IPC_STAT:获得该信号量的semid_ds结构,并存放在由第四个参数arg的buf指向的semid_ds结构中。semid_ds是系统中描述信号量的数据结构
  • IPC_SETVAL:将信号量值设置为arg的val值
  • IPC_GETVAL:返回信号量的当前值
  • IPC_RMID:从系统中删除信号量

arg:是union semun结构,该结构可能在某些系统中并不给出定义,此时必须由自己定义:

1
2
3
4
5
6
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}

cmd为IPC_STAT、IPC_SETVAL、IPC_RMID时返回0,为IPC_GETVAL时返回信号量的当前值;出错返回-1

semop()

1
2
3
4
5
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

semid:semget()函数返回的信号量标识

sops:指向信号量操作数组,一个数组包含以下成员

1
2
3
4
5
6
struct sembuf
{
short sem_num; //信号量编号,使用单个信号量时,通常为0
short sem_op; //信号量操作-1表示P操作,+1表示V操作
short sem_flag;//通常设置为SEM_UNDO。这样在进程没释放信号量而退出时会自动释放该信号量
}

成功返回信号量标识符,失败返回+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
#include "sem_com.h"

/*信号量初始化*/
int init_sem (int sem_id, int init_value)
{
union semun sem_union;
sem_union.val = init_value;
if (semctl (sem_id, 0, SETVAL, sem_union) == -1)
{
perror ("Initialize semaphore");
return -1;
}
return 0;
}

/*删除信号量*/
int del_sem (int sem_id)
{
union semun sem_union;
if (semctl (sem_id, 0, IPC_RMID, sem_union) == -1)
{
perror ("Delete semaphore");
return -1;
}
return 0;
}

/*P操作*/
int sem_P (int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror ("P operation");
return -1;
}
return 0;
}

/*V操作*/
int sem_V (int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1)
{
perror ("V operation");
return -1;
}
return 0;
}

使用封装

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
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "sem_com.h"

#define DELAY_TIME 3

int main (void)
{
pid_t result;
int sem_id;
int status;

sem_id = semget (ftok (".", 'a'), 1, 0666|IPC_CREAT);
init_sem (sem_id, 1);

result = fork ();
if (result == -1)
{
perror ("fork");
}
else if (result == 0)
{
sem_P (sem_id);
printf ("child process will wait for som seconds...\n");
sleep (DELAY_TIME);
printf ("the returned value is %d in the child process (PID = %d)\n", result, getpid());
sem_V (sem_id);
exit(0);
}
else
{
sem_P (sem_id);
printf ("the returned value is %d in the parent process (PID = %d)\n", result, getpid());
sem_V (sem_id);

}
if (waitpid (result, &status, 0) == -1)
{
return -1;
}
else if (WIFEXITED (status))
{
del_sem (sem_id);
return WEXITSTATUS (status);
}
return -1;
}

信号量
https://carl-5535.github.io/2021/05/31/Linux系统编程/信号量/
作者
Carl Chen
发布于
2021年5月31日
许可协议