1、什么是信号量
从概念上来说,信号量是一个由内核维护的非负整数计数器。
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或者临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。是Linux中进程主要的同步与互斥手段就是信号量。
- 互斥 : 同一时刻只有一个进程访问临界资源
- 同步 : 在互斥的基础上增加了进程对临界资源的访问顺序
信号可以执行如下操作:
将信号量设置成一个具体的值。
在信号量当前值的基础上加上一个数值。
在信号量当前值的基础上减上一个数值。
等待信号量的值为 0。
在 Linux 系统中查询信号量使用 ipcs -s
, 删除某个信号量使用 ipcrm -s
2、信号量的使用
Linux 内核提供了一套对信号量的操作,包括创建,初始化,操作信号量。
2.1、创建信号量
创建信号量集合调用 semget 函数。
//头文件
#include
#include
#include
//函数原型
int semget(key_t key, int nsems, int semflg);
函数参数
- key : 由 ftok() 函数生成
- nsems : 信号量的数量
- semflg : 信号量集合的标志
- IPC_CREAT : 创建标志
- IPC_EXCL : 与 IPC_CREAT 标志一起使用,如果信号量集合存在就报错
- 权限标志
函数返回值
- 成功 : 返回信号量集合的 id
- 失败 : -1,并设置 errno
2.2、初始化信号量
初始化信号量调用 semctl 函数,设置一个信号量的初始值。在 semid 标识的信号量集上,或者该集合的第semnum 个信号量上执行 cmd 指定的控制命令。
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
函数参数
-
semid : 信号量集合的id
-
semnum : 信号量的编号, 信号量的编号从 0 开始
-
cmd : 命令控制字
- SETVAL: 设置信号量的值
- GETVAL: 获取信号量的值
-
… : 后面是属于可变参参数列表,根据不同的命令有不同的参数
函数返回值
- 成功 : 根据不同的命令有不同的返回值,可以查看帮助文档关于 RETURN 的说明
-
- GETNCNT the value of semncnt
- GETPID the value of sempid
- GETVAL the value of semval
- GETZCNT the value of semzcnt.
-
- All other cmd values return 0 on success.
失败 : 返回 -1,并设置 errno
2.3、操作信号量
操作信号量调用 semop 函数。
#include
#include
#include
int semop(int semid, struct sembuf *sops, size_t nsops);
函数参数
- semid : 信号量集合id
- sops : 信号量操作结构体指针
- nsops : 操作的信号量的数量
函数返回值
- 成功 : 返回 0
- 失败 : 返回 -1,并设置 errno
3、Demo实例:实现进程间同步
在进程间实现同步操作,按顺序打印 充电 玩手机 没电了。
父进程:
- 占用 SEM_CONTROL_P,此时父进程正常运行,输出 充电
- 释放 SEM_CONTROL_C,占用 SME_CONTROL_P,此时父进程阻塞,子进程继续执行
- 当子进程输出 玩手机 之后,释放 SEM_CONTROL_P,父进程继续执行,输出 没电了
- 父进程 释放 SEM_CONTROL_P 循环结束
子进程:
- 占用 SEM_CONTROL_C ,此时子进程阻塞
- 当父进程释放 SEM_CONTROL_C 时, 子进程输出 玩手机 ,释放 SEM_CONTROL_P
- 循环占用 SEM_CONTROL_C,由于之前已经占用,此时进入子进程阻塞,等待父进程释放 SEM_CONTROL_C
#include
#include
#include
#include
#include
#include "sem_handle.h"
#define SEM_CONTROL_P 0
#define SEM_CONTROL_C 1
int main(void)
{
pid_t cpid;
int semid;
unsigned short values[2] = {1,0};
//创建一个信号量集合,包含 2个信号量
// 一个信号量 编号为 0(SEM_CONTROL_P)控制父进程的运行与暂停
// 一个信号量 编号为 1(SEM_CONTROL_C) 控制子进程的运行与暂停
semid = sem_create(2, values);
if (semid == -1)
exit(EXIT_FAILURE);
cpid = fork();
if (cpid == -1){
perror("[ERROR] fork(): ");
exit(EXIT_FAILURE);
} else if (cpid == 0){
while(1){
sem_p(semid,SEM_CONTROL_C);
printf(" 玩手机 ");
fflush(stdout);
sem_v(semid,SEM_CONTROL_P);
}
} else {
while(1){
sem_p(semid,SEM_CONTROL_P);
printf(" 充电 ");
fflush(stdout);
sem_v(semid,SEM_CONTROL_C);
sem_p(semid,SEM_CONTROL_P);
printf(" 没电了 ");
fflush(stdout);
sem_v(semid,SEM_CONTROL_P);
sleep(1);
putchar('n');
}
wait(NULL);
}
return 0;
}
运行结果
sem_handle.h 封装的代码
#ifndef CDEMO_SEM_HANDLE_H
#define CDEMO_SEM_HANDLE_H
#include
#include
#include
#include
extern int sem_create(int nsems,unsigned short values[]); //创建信号量集合
extern int sem_p(int semid,int semnum);// 占用资源
extern int sem_v(int semid,int semnum);// 释放资源
extern int sem_del(int semid); // 删除信号量集合
#endif
#include "sem_handle.h"
#define SEM_PATHNAME "."
#define SEM_PRO_ID 100
union semun{
int val;
unsigned short *array;
};
/**
* 创建信号量集合
* @param nsems 信号量的数量
* @param values 信号量的值
* @return 成功 :信号量集合的id 失败 : -1
*/
int sem_create(int nsems, unsigned short values[])
{
int semid,ret;
key_t key;
union semun s;
key = ftok(SEM_PATHNAME,SEM_PRO_ID); // 创建 key
if (key == -1){
perror("[ERROR] ftok() : ");
return -1;
}
semid = semget(key, nsems, IPC_CREAT|0666); // 创建信号量集合
if (semid == -1){
perror("[ERROR] semget() : ");
return -1;
}
s.array = values;
ret = semctl(semid,0,SETALL,s); // 设置信号量的值
if (ret == -1){
perror("[ERROR] semctl() : ");
return -1;
}
return semid;
}
/**
* 占用信号量资源
* @param semid 信号量集合的id
* @param semnum 操作的信号量编号
* @return
*/
int sem_p(int semid, int semnum)
{
struct sembuf sops;
sops.sem_num = semnum;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;// 进程终止,会自动释放
return semop(semid,&sops,1);
}
/**
* 释放信号量资源
* @param semid 信号量集合的id
* @param semnum 操作的信号量编号
* @return
*/
int sem_v(int semid, int semnum)
{
struct sembuf sops;
sops.sem_num = semnum;
sops.sem_op = 1;
sops.sem_flg = SEM_UNDO;
return semop(semid, &sops, 1);
}
/**
* 删除信号量集合
* @param semid 信号量集合的id
* @return
*/
int sem_del(int semid)
{
return semctl(semid, 0, IPC_RMID,NULL);
}