15.8 信号量
(1)概述
1.信号量是一个计数器
2.信号量用于多个进程对共享数据的访问。
3.信号量由内核实现,测试及-1是原子操作。
4.常用的简单信号量称为二元信号量。
5.一般信号量的初值可以是任意一个正值,表明有多少个共享资源单位可供共享使用。
6. 系统对信号量的限制
SEMMNI: 系统允许的信号量集的上线
SEMMSL: 单个信号量集中信号量的上限
SEMMNS: 系统允许的信号量集的上限
7.信号量集结构
struct {
unsigned short semval; // 信号量值,总是大于等于0
pid_t sempid; // 是一个执行semop的进程
unsigned short semncnt; // 返回等待第semnum个信号量增大的进程的个数。
unsigned short semzcnt; // 返回等待第semnum个信号量变为0的进程的个数。
...
}
(2)为了获得共享资源,进程需要执行的操作
1.测试控制该资源的信号量
若信号量的值为正:则进程可以使用该资源。进程将信号量值-1,表示使用了一个资源
若信号量的值为0: 则进程进入休眠状态,直至信号量的值大于0,进程被唤醒,然后重新测试该资源的信号量。
2.当进程不再使用信号量控制共享资源时,信号量的值+1,如果有进程正在等待该信号量,则唤醒他们。
(3)创建/获取信号量
int semget(key_t key, int nsems, int flag)
nsems:
该集合中的信号量数。如果创建新信号量集合必须指定nsems;如果引用现有的信号量集合,则将nsems指定为0
flag:
----------------------------------------------------------------
oflag参数 key不存在(ENOENT) key已经存在
无特殊标志 出错,返回-1 成功,获取到已有标识符
IPC_CREATE 成功 成功,获取到已有标识符
IPC_CREATE|IPCEXCL 成功 出错,返回-1(EEXIST)
----------------------------------------------------------------
ret:
成功返回信号量标识符(id),失败返回-1。
(4)控制信号量
int semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
semid: 信号量标识符
semnum: 信号量集中第几个信号量,该参数与cmd相关
cmd: 想要执行的操作
-------------------------------------------------------------------------------
IPC_RMID 删除信号量集,所有阻塞在semop函数上的进程被唤醒。semnum参数被忽略
IPC_STAT 获取信号量集信息,结果存放在semun.buf中
IPC_SET 使用semun->buf中的值来设置信号量集的sem_perm.uid、sem_perm.gid、sem_perm.mode
GETVAL 返回信号量集中第semnum个信号量的值,第四个参数被忽略
SETVAL 信号量集中第semnum个信号量的值为semun.val
GETALL 获取信号量集中所有信号的值,存放到semun.array中, semnum参数被忽略
SETALL 用semun.array中的数据初始化信号量集中的所有信号, semnum参数被忽略
GETPID 返回上一个对第semnum个信号量执行semop的进程ID, 不存在时返回0。
GETNCNT 返回正在等待信号量的值大于当前值(第semnum个信号量的值)的进程个数
GETZCNT 正在等待信号量的值变为0的进程个数。
-------------------------------------------------------------------------------
第四个参数:
union semun{
int val;
struct senid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
}
注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
返回值:
1.除了GETALL以外的所有GET命令, semctl函数都返回相应的值。
2.对于其他命令,成功返回0,出错设置error并返回-1;
(5)操作信号量
int semop(int semid, struct sembuf semoparry[], size_t nops);
semid: 信号量标识符
semoparry: // 对型号量集中的操作
struct sembuf {
unsigned short sem_num; // 操作信号量集中的第几个信号量。0表示第一个,1表示第2个,以此类推
short sem_op; // 信号量操作
short sem_flag; // 操作标记
};
--------------------------------------------------------------------------------------------------------------------
sem_op 作用 含义
--------------------------------------------------------------------------------------------------------------------
>0 用于释放资源 第sem_num个信号量的信号量值(semval)加上sem_op的值。如有其他进程在等待资源且增加后的信号量满足其要求,则将其唤醒。
--------------------------------------------------------------------------------------------------------------------
=0 用于等待信号 1.如果semval等于0则立刻返回成功
量值(semval)等于0 2.如果semval不等于0
如果指定了IPC_NOWAIT, 立刻返回失败
如果未指定IPC_NOWAIT, 则陷入阻塞,semzcnt的值+1
--------------------------------------------------------------------------------------------------------------------
<0 用于申请资源。 1.若信号量值(semval)>|sem_num| , 则semval -= |sem_num|,立即返回。
2.若信号量值(semval)≤|sem_num|,
如果指定了IPC_NOWAIT,立即返回失败
如果未指定IPC_NOWAIT,则陷入阻塞,semncnt+1。
--------------------------------------------------------------------------------------------------------------------
sem_flag说明:
IPC_NOWAIT:
SEM_UNDO: // 该标志多用于二值信号量
1.一般情况下,如果进程申请了资源,修改了信号量的值,却没来得及释放资源就异常退出了,这会造成资源泄露。
2.如果设置了SEM_UNDO标志(sem_flag |= SEM_UNDO),
当进程退出时,会撤销对信号量的操作
nops: // 指定了semoparry数组中的操作个数
系统对semop函数的限制
SEMOPM:单次调用能够操作信号量的最大值
SEMVMX: 信号量的上限
(6)测试
场景:
1.现某个中转站有2个仓库
2.每一次来货占用一个仓库
3.仓库中的货存放一天后发出(腾空厂库)
4.需要中转的客户陆续的送货来,只有有空余的仓库时送的货才能被中转
解析:
1.两个仓库用信号量的值2来表示。
2.接收货物时信号量-1,表示暂用一个仓库
发出货物时信号量+1,表示腾出一个仓库
3.没有厂库时,来货进程被阻塞。直到有仓库空闲
测试代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/acct.h>
#include <errno.h>
#include <sys/times.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include "mysem.h"
/*
场景:
1.现某个中转站有2个仓库
2.每一次来货占用一个仓库
3.仓库中的货存放一天后发出(腾空厂库)
4.需要中转的客户陆续的送货来,只有有空余的仓库时送的货才能被中转
解析:
1.两个仓库用信号量的值2来表示。
2.接收货物时信号量-1,表示暂用一个仓库
发出货物时信号量+1,表示腾出一个仓库
3.没有厂库时,来货进程被阻塞。直到有仓库空闲
测试结果:
storeid=65537
handle_goods: customer1 goods 1
handle_goods: customer3 goods 1
handle_goods: customer2 goods 1
handle_goods: customer1 goods 2
handle_goods: customer3 goods 2
handle_goods: customer2 goods 2
handle_goods: customer1 goods 3
*/
store_t store1; // 仓库1
store_t store2; // 仓库2
//注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
union semun{
int val;
struct senid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
// 初始化仓库管理
int init_store_manage(const int num) // num:仓库个数
{
int semid;
key_t key;
union semun arg;
arg.val = num;
assert((key_t)-1 != (key = ftok(FTOK_PATH, PRJ_NUM)));
// 信号量集中只有1个信号量(及一个中转站)
assert(-1 != (semid = semget(key, 1, IPC_CREAT | S_IRUSR| S_IWUSR))); // 不存在该消息队列就返回错误
// 0: 操作信号量集中的第一个信号量
assert(0 == semctl(semid, 0, SETVAL, arg));
memset(&store1, 0, sizeof(store_t));
memset(&store2, 0, sizeof(store_t));
return semid;
}
void get_store(const int storeid) // 获得一个仓库
{
int ret;
struct sembuf semoparry[1];
semoparry[0].sem_num = 0;
semoparry[0].sem_op = -1;
semoparry[0].sem_flg |= SEM_UNDO;
if(-1 == (ret == semop(storeid, semoparry, 1))){
printf("ERROR: semop1, errno=%d\n",errno);
exit(0);
}
}
void put_store(const int storeid) // 腾出一个仓库
{
int ret;
struct sembuf semoparry[1];
semoparry[0].sem_num = 0;
semoparry[0].sem_op = 1;
semoparry[0].sem_flg |= SEM_UNDO;
if(-1 == (ret == semop(storeid, semoparry, 1))){
printf("ERROR: semop2, errno=%d\n",errno);
exit(0);
}
}
// 处理来货
void handle_goods(const int storeid, const char *goods)
{
store_t *s;
get_store(storeid); // 获得一个空闲仓库
if (!store1.noempty){
s = &store1;
memcpy(store1.buf, goods,strlen(goods));
store1.noempty = true;
}
else if (!store2.noempty){
s = &store2;
memcpy(store2.buf, goods,strlen(goods));
store2.noempty = true;
}
else
{
printf("ERROR: store_goods\n");
exit(-1);
}
sleep(1); // 在厂库存放一天
printf("handle_goods: %s\n", goods); // 从仓库中取出货物发出
memset(s, 0, sizeof(store_t));
put_store(storeid);
}
int main()
{
int storeid;
key_t key;
pid_t pid;
pid_t pid2;
int i;
char goods[255];
storeid = init_store_manage(2); // 有2个仓库
printf("storeid=%d\n", storeid);
pid =fork();
assert(pid >= 0);
if (pid > 0){ // 客户1
while(1)
{
i++;
sprintf(goods, "customer1 goods %d", i);
handle_goods(storeid, goods);
}
}
else if (pid == 0){
pid2 =fork();
assert(pid2 >= 0);
if (pid2 == 0){ // 客户2
while(1)
{
i++;
sprintf(goods, "customer2 goods %d", i);
handle_goods(storeid, goods);
}
}
else if (pid2 > 0){ // 客户3
while(1)
{
i++;
sprintf(goods, "customer3 goods %d", i);
handle_goods(storeid, goods);
}
}
}
pause();
}
头文件
#ifndef MYSEM_H
#define MYSEM_H
#include <stdbool.h>
#define FTOK_PATH "/data1/zhouhouping/nfs/testCode/chapter15/sem/ftokpath"
#define PRJ_NUM 333333
#define NOFLAG 0
typedef struct {
bool noempty; // 仓库是否空闲
char buf[255]; // 仓库里
}store_t;
void handle_goods(const int storeid, const char *goods);
#endif // MYSEM_H