更多linux知识点:linux目录索引
1. 什么是信号量
信号量的本质是数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
个人理解: 信号量就是具有原子性的计数器,当使用了资源时,计数器就要减一,表示可用的资源就少了一个,当用完这个资源将其还回去时,计数器加一,表示可用的资源又多了一个
2. 信号量的工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
例子:
假设桌子上只有一个盘子,那我们就记可用盘子的数量为count=1,小明现在将这个盘子拿走,则桌子上就没有盘子,(执行p操作)count = 0,此时小红也想从桌子上拿盘子,发现count = 0,没有盘子,他就在这等着,过了一会,小明用完盘子并将其放回了桌子(执行v操作),小红此时发现count = 1(表示有盘子),就拿走盘子(执行p操作)
3. 对于信号量的操作
创建
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> //如果信号量不存在就创建,存在就打开 int semget(key_t key, int nsems, int semflg); //返回值:函数成功返回一个相应信号标识符,失败返回-1
参数:
- key:信号量的标识符,一般由ftok函数获得
- nsems:信号量的个数,表示你要创建几个信号量
- semflg:一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
pv操作
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> //改变信号量的值,也就是执行pv操作 int semop(int semid, struct sembuf *sops, unsigned nsops);
参数:信号量的标识符semget
semget的返回值,信号量的标识符
参数:sembuf结构体
struct sembuf{ short sem_num;//除非使用一组信号量,否则它为0 short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, //一个是+1,即V(发送信号)操作。 short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号, //并在进程没有释放该信号量而终止时,操作系统释放信号量 };
参数:信号量的个数
表示你要操作几个信号量
控制
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, union semun arg); //返回值:成功返回信号量集的标识符,失败返回-1
参数:信号量的标识符semid
semget的返回值
参数:信号量的编号semnum
要操作信号在信号集中的编号。编号从0开始。
参数:选项cmd
cmd: 命令,表示要进行的操作。(SETVAL:设置初值;GETVAL:获取初值
命令 解释 IPC_STAT 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中 IPC_SET 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值 GETALL 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中 GETNCNT 返回当前等待资源的进程个数 GETPID 返回最后一个执行系统调用semop()进程的PID GETZCNT 返回当前等待100%资源利用的进程个数 SETALL 与GETALL正好相反 GETVAL
返回信号量集合内单个信号量的值
SETVAL
用联合体中val成员的值设置信号量集合中单个信号量的值
IPC_RMID
从内核中删除信号量集合
参数:联合体arg
union semun { * short val; /*SETVAL用的值,一般写这一个就够了*/ * struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/ * unsigned short* array; /*SETALL、GETALL用的数组值*/ * struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/ * } arg;
4. 实例:利用信号量解决哲学家就餐问题
问题描述:
假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。
问题分析:
当5个哲学家进程并发执行时,某个时刻恰好每个哲学家进程都执行申请筷子,并且成功申请到第i支筷子(相当于5个哲学家同时拿起他左边的筷子), 接着他们又都执行申请右边筷子, 申请第i+1支筷子。此时每个哲学家仅拿到一支筷子, 另外一支只得无限等待下去, 引起死锁。
解决思路
- 对每个哲学家和筷子进行编号:0~4
- 对每个筷子都设置一个信号量(计数器)
- 每个哲学家每次取筷子时,同意给他左右两个筷子,即自己编号(num)和 num+1,并且对这两个筷子执行p操作,其他哲学家取筷子时就在等待
4.当用完两个筷子就将其归还,执行v操作,以便其他哲学家进行使用 - 这样保证每个哲学家拿筷子时都能拿到足够的筷子,避免死锁
4. 代码:
#include <unistd.h> #include <sys/sem.h> #include <stdio.h> #include <stdlib.h> #include <string.h> union su { int val; }; int semid; void p(int num) { struct sembuf array[2] = { {num,-1,0}, {(num+1)%5,-1,0} }; semop(semid,array,2); } void v(int num) { struct sembuf array[2] = { {num,1,0}, {(num+1)%5,1,0} }; semop(semid,array,2); } void zxj(int num) { while(1) { printf("%d 哲学家开始思考.....\n",num); sleep(rand()%5); printf("%d 哲学家开始饿了.....\n",num); p(num); printf("%d 哲学家吃饭,拿筷子\n",num); sleep(rand()%3); printf("%d 哲学家吃完,放筷子\n",num); v(num); } } //哲学家就餐,先创建信号量,用于管理筷子,在创建5个进程,表示哲学家 int main() { srand(getpid()); key_t key = ftok(".",0x06666); if(key < 0){ perror("ftok"); return -1; } semid = semget(key,5,IPC_CREAT|0644); if(semid < 0){ perror("semid"); return -2; } int i=0;//对5个信号量进行初始化,每个信号量初始化为1 for(; i < 5;++i) { union su s = {1}; semctl(semid,i,SETVAL,s); } int num = 0; for(i = 1;i<5;++i) { pid_t pid = fork(); if(pid == 0){//给子进程编号 num = i; break; } } zxj(num); return 0; }
结果: