信号量其实也是一种锁,线程获取不到信号量的时候进入睡眠,直至有信号量释放出来时,才会被唤醒,进入临界区继续执行。信号量有二值信号量和计数信号量两种,其中二值信号量比较常用。
二值信号量表示信号量只有两个值,即0和1。信号量为1时,表示临界区可用,信号量为0时,表示临界区不可访问。
和消息队列和共享内存一样,信号量也有两个版本,分别为System V信号量和POSIX信号量。那么两者有什么区别:
- POSIX信号量常用于线程,而System V信号量常用于进程的同步
- 从使用的角度,System V 信号量的使用比较复杂,而 POSIX 信号量使用起来相对简单。
- POSIX 操作的是单个信号量,而 System V 信号量操作的是信号量集
- 对于System V信号量你可以控制每次自增或是自减的信号量计数,而在POSIX里面,信号量计数每次只能自增或是自减1
- POSIX信号量是基于内存的,即信号量值是放在共享内存中的,它是由可能与文件系统中的路径名对应的名字来标识的。而System V信号量则是基于内核的,它放在内核里面。
- Posix还有有名信号量,一般用于进程同步, 有名信号量是内核持续的。
目录
一、POSIX信号量
1、初始化
初始化一个信号量。
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
函数参数:
sem:要初始化的信号量
pshared:设置信号量的共享属性,有以下两个值可供选择:
- PTHREAD_PROCESS_PRIVATE):由同一个进程创建的线程才能够处理该信号量
- PTHREAD_PROCESS_SHARED:多个进程中的线程之间共享该信号量
value:信号量的初始值,即需要的信号量数目,一般为1
返回值:
成功返回0,失败返回非0值
2、等待信号量
等待一个信号量,它的作用是从信号量的值原子操作的减去1,对于sem_wait(),它会永远先等待该信号量为一个非零值才开始做减法。而对于sem_trywait(),如果信号量的值大于0,则同sem_wait(),但如果信号量值等于0 ,则会立即返回-1.
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
函数参数:
sem:已初始化的信号量
返回值:
成功返回0,失败返回非0值
3、发送信号量
以原子操作的方式将信号量的值加1,这样,其它正在调用sem_wait等待信号量的线程将被唤醒
#include <semaphore.h>
int sem_post(sem_t *sem);
函数参数:
sem:已初始化的信号量
返回值:
成功返回0,失败返回非0值
4、销毁信号量
销毁一个信号量,以释放其占用的内核资源
#include <semaphore.h>
int sem_destroy(sem_t *sem);
函数参数:
sem:已初始化的信号量
返回值:
成功返回0,失败返回非0值
二、System V 信号量
System V 子系统提供的信号量机制是比较复杂的。我们不能单独定义一个信号量,而只能定义一个信号量集,其中包括一组信号量,同一信号量集中的信号量可以使用同一 ID 引用。每个信号量集都有一个与其相对应的结构,其中包含了信号量集的各种信息,该结构的声明如下:
struct semid_ds
{
struct ipc_perm sem_perm; // 对应于该信号量集的 ipc_perm 结构
struct sem *sem_base; // 指向信号量集中第一个信号量的 sem 结构
ushort sem_nsems; // 信号量集中信号量的个数
time_t sem_otime; // 最近一次调用 semop 函数的时间
time_t sem_ctime; // 最近一次改变该信号量集的时间
};
struct sem
{
ushort semval; /* 信号量的值 */
pid_t sempid; /* 最后一次返回该信号量的进程ID 号 */
ushort semncnt; /* 等待可利用资源出现的进程数 */
ushort semzcnt; /* 等待全部资源可被独占的进程数 */
};
1、获取key值
Linux系统建立IPC通讯(如消息队列、共享内存,信号量时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
key_t ftok( char * fname, int id )
函数参数:
fname:指定的文件路径+文件名,该文件必须是存在而且可以访问
id:随机值,其前8个比特会被用做生成key值
返回值:
成功则返回key_t值,失败返回 -1
2、新建与打开信号量集
要使用信号量,首先要创建一个信号量集,创建信号量集的函数声明如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
函数参数:
key:ftok()函数生成的key值
semflag:表示信号量的访问权限,它与文件的访问权限一样,可以与以下值做或操作,以实现特定功能:
- #define IPC_CREAT 00001000 //如果key值不存在则创建
- #define IPC_EXCL 00002000 //如果key存在,则返回失败
- #define IPC_NOWAIT 00004000 //如果需要等待时,直接返回错误
返回值:
成功则返回该信号量的ID,失败返回 -1
3、操作信号量
要使用信号量,首先要创建一个信号量集,此函数是一个原子操作,一旦执行就将执行数组中所有的操作。函数声明如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *abs_timeout);
函数参数:
semid:信号量集标识符
sops:存储信号操作结构的数组指针,struct sembuf的结构的原型如下:
struct sembuf { short sem_num; short sem_op; short sem_flag; };
各参数含义如下:
- sem_num:要操作的信号在信号集中的编号,从0开始
- sem_op:通常用于释放所控资源的使用权,有以下三种可能值:
- sem_op > 0:表示进程对资源使用完毕,交回该资源。此时信号量集的 semid_ds 结构的 sem_base.semval 将加上 sem_op 的值。若此时设置了 SEM_UNDO 位,则信号量的调整值将减去 sem_op 的绝对值。
- sem_op = 0:表示进程要等待,直至 sem_base.semval 变为 0。
- sem_op < 0:表示进程希望使用资源。此时将比较 sem_base.semval 和 sem_op 的绝对值大小:
- sem_base.semval >= sem_op 的绝对值:说明资源足够分配给此进程,则 sem_base.semval 将减去 sem_op 的绝对值。若此时设置了 SEM_UNDO 位,则信号量的调整值将加上 sem_op 的绝对值。
- sem_base.semval < sem_op 的绝对值:表示资源不足。若设置了 IPC_NOWAIT 位,则函数出错返回,否则 semid_ds 结构中的 sem_base.semncnt 加 1,进程等待直至 sem_base.semval 大于等于 sem_op 的绝对值或该信号量被删除。
- sem_flag:信号操作标志,可能的选择有两种:
- IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回错误
- IPC_UNDO:程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
nsops:信号操作结构struct sembuf的数量
abs_timeout:超时等待的绝对时间:
struct timespec { time_t tv_sec; /* 秒 */ long tv_nsec; /* 纳秒*/ }
返回值:成功则返回0,失败返回 -1
4、控制信号量集
要使用信号量,首先要创建一个信号量集,创建信号量集的函数声明如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
函数参数:
semid:信号量集标识符
semnum:要操作的信号在信号集中的编号,从0开始
cmd/arg:cmd表示要执行的操作,arg为配置cmd的参数,其中arg的结构体为:
union semun { int val; struct semid_ds *buf; ushort *array; };
cmd的取值为:
- GETALL:获得 semid 所表示的信号量集中信号量的个数,并将该值存放在无符号短整型数组 array 中。
- GETNCNT:获得 semid 所表示的信号量集中的等待给定信号量锁的进程数目,即 semid_ds 结构中 sem.semncnt 的值。
- GETPID:获得 semid 所表示的信号量集中最后一个使用 semop 函数的进程 ID,即 semid_ds 结构中的 sem.sempid 的值。
- GETVAL:获得 semid 所表示的信号量集中 semunm 所指定信号量的值。
- GETZCNT:获得 semid 所表示的信号量集中的等待信号量成为 0 的进程数目,即 semid_ds 结构中的 sem.semncnt 的值。
- IPC_RMID:删除该信号量。
- IPC_SET:按参数 arg.buf 指向的结构中的值设置该信号量对应的 semid_ds 结构。只有有效用户 ID 和信号量的所有者 ID 或创建者 ID 相同的用户进程,以及超级用户进程可以执行这一操作。
- IPC_STAT:获得该信号量的 semid_ds 结构,保存在 arg.buf 指向的缓冲区。
- SETALL:以 arg.array 中的值设置 semid 所表示的信号量集中信号量的个数。
- SETVAL:设置 semid 所表示的信号量集中 semnum 所指定信号量的值。
返回值:
成功则返回0,失败返回 -1