Linux下的进程通信方式之POSIX-信号量

Linux下的通信方式之 POSIX 信号量

前言

POSIX 中的信号量和 System 中的信号量不同点在于:

  • System 其实是信号量集,而不是单纯的信号量。
  • POSIX 中的信号量有两种,一种称为命名信号量,一种称为未命令信号量。这两个东⻄的唯⼀区别就是多个进程如何找到该信号量⽽已

命名信号量:不相关的进程通过这个名字能够访问同一个信号量。

未命名信号量:没有名字,位于内存中一个约定的位置,可以在进程之间或一组线程之间共享。进程间共享中,需要位于一个共享内存区域中。

命令信号量

前面提到,不相关的进程可以通过名字访问同一个信号量,这就是命名信号量。

命令信号量的创建和使用方式如下:

创建命令信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

/*
@brief 打开或创建一个命名信号量
@param name 标识了信号量,名字格式斜杠开头,如 /myobject 需要以单斜杠开头
@param oflag 位掩码,标识创建还是打开一个信号量,可取
- O_CREAT 创建一个信号量
- O_EXCL 配合 O_CREAT 使用,如果已存在则创建失败
- 0 打开一个信号量
@param mode oflag 为 O_CREAT 使用,指定了权限可取
- O_RDONLY 仅读
- O_WRONLY 仅写
- O_RDWR 读写,默认
@param value 无符号整数。指定了新信号量的初值
@retrun 成功返回指向信号量的指针,否则返回 SEM_FAILED
*/
sem_t *sem_open(const char *name, int oflag, ...
/* mode_t mode, unsigned int value */);

SUSv3 规定只能在原始返回的 sem_t 指针上操作,其副本操作是未定义的,即不能:

1
2
3
4
sem_t *sp, sem2;
sp = sem_open(...);
sem2 = *sp;
sem_wait(&sem2); // WRONG, Not Defined

注意点

  • 信号量的名字必须以斜杠开头,如 /myobject。但是不可以 // 两个斜杠开头,如 //myobject
  • 信号量的名字必须唯一,不能与其他信号量的名字相同。
  • 信号量的名字长度不能超过 NAME_MAX。

关闭命令信号量

注意,关闭命名信号量和删除命名信号量不是同一个操作。

1
2
#include <semaphore.h>
int sem_close(sem_t *sem);

当一个进程打开一个命名信号量时,系统会记录进程与信号量之间的关联关系。sem_close 会终止这种关联关系(即关闭信号量),释放系统为该进程关联到该信号量之上的所有资源,并递减引用该信号量的进程数。 在进程终止或者调用了 exec 时,会被自动关闭,关闭信号量并不代表删除信号量。

删除命令信号量

1
2
3
#include <semaphore.h>
// 删除 name 标识的信号量,当所有进程均使用完(close)这个信号量后销毁
int sem_unlink(const char *name);

命令信号量操作

等待命令信号量

这个操作有两个函数,一个是 sem_wait,另一个是 sem_trywait。

这两者的区别在于,前者是阻塞版本,后者是非阻塞版本。

1
2
3
#include <semaphore.h>
// 如果当前信号量大于 0,将信号量 -1,并立即返回;信号量为 0 则阻塞直到其大于 0
int sem_wait(sem_t *sem);

如果一个阻塞的 sem_wait 调用被一个信号处理器中断了,那么它就会失败并返回 EINTR 错误,不管在使用 sigaction 建立这个信号处理器时是否采用了 SA_RESTART 标记。 非阻塞版本:

1
2
3
#include <semaphore.h>
// 如果递减操作不能被立即执行,失败并返回 EAGAIN 错误
int sem_trywait(sem_t *sem);

发布一个信号量

1
2
3
#include <semaphore.h>
// 获取 sem 指向的信号量的当前值.写入 sval 指向的内存,成功返回 0,失败返回 -1
int sem_getvalue(sem_t *sem, int *sval);

获取信号量当前值

1
2
3
#include <semaphore.h>
// 获取 sem 指向的信号量的当前值.写入 sval 指向的内存,成功返回 0,失败返回 -1
int sem_getvalue(sem_t *sem, int *sval);

未命名信号量

未命名信号量的操作与信号量是一样的,它的创建与删除会用到额外的两个函数,这两个函数不应该用在命名信号量上。 在线程间共享的信号量不需要名字。在相关进程间共享的信号量不需要名字。如果一个父进程在一块共享内存区域中(如一个共享匿名映射)分配了一个未命名信号量,那么作为 fork 操作的一部分,子进程会自动继承这个映射,从而继承这个信号量。

初始化一个未命名信号量

1
2
3
4
5
6
7
8
9
10
11
12
#include <semaphore.h>

/*
@brief 初始化一个未命名信号量
@param sem 信号量指针
@param pshared 表明信号量是在线程间共享还是在进程间共享
- 0: 信号量将在线程间共享
- else:信号量在进程间共享
@param value 信号量初始值
@return 成功返回 0,失败返回 -1
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);

删除一个未命名信号量

1
2
3
4
#include <semaphore.h>

// 销毁一个信号量,只有在不存在进程或线程等待一个信号量时才可以安全销毁这个信号量
int sem_destroy(sem_t *sem);

信号量限制

下面限制定义在 limits.h 中:

1
2
3
SEM_NSEMS_MAX:一个进程能够拥有的 POSIX 信号量最大数目

SEM_VALUE_MAX :可取的信号值最大值

总结

  1. System V的信号量一般用于进程同步, 且是内核持续的, api为:semget、semctl、semop
  2. Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为:sem_open、sem_close、sem_unlink
  3. Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为:sem_init、sem_destroy

Linux下的进程通信方式之POSIX-信号量
https://ysc2.github.io/ysc2.github.io/2024/08/17/Linux下的进程通信方式之POSIX-信号量/
作者
Ysc
发布于
2024年8月17日
许可协议