Linux:进程间通信-信号量

Linux学习目录


1、 什么是信号量?
在对于临界区资源管理过程中,为了防止多个程序同时访问一个共享资源而引发的一系列问题。比如:死锁

为了解决这种问题,巨人们就发明了信号量。
信号量就是为了解决在一个临界区只有一个进程访问它,也就是说信号量相当于交警,来协调进程对共享资源有序的访问而不造成死锁等行为。

信号量是一个特殊的变量,程序对它访问都是原子操作,并且她只能进行两种操作,等待(P信号量)和发送(V信号量) 。

P(sv):如果sv的值大于0,说明资源可用,就给它减1,如果它等于0,就挂起该进程说明资源不可用,直到资源可用。
V(sv):如果sv值大于0,并且有其他进程因为等待sv而被挂起,则该进程恢复运行,如果没有进程等待,则sv加1

2、信号量的函数操作

  • 2.1、semget函数
    它的作用是创建一个新信号量集或获取一个已有的信号量集
int semget(key_t key, int nsems, int semflg);
  • key:每一个信号量集都有一个唯一的信号量集标识符id,这个id是通过一个键值key获得,key值可以通过ftok函数获得。而semget必须有一个key值,并由系统生成一个相应的进程标识符id作为semget的返回值。如果多个进程共用同一个key值,那么key将负责协调工作。
  • nsems:指定信号量集的数目
  • semflg:当信号量集不存在时创建一个信号量用IPC_CREAT,当信号量已经存在时用IPC_CREAT|IPC_EXCL,返回一个错误。
  • semget成功返回一个相应信号量集标识符,失败返回-1.

  • 2.2、 semop函数
    作用:改变信号量的值,只要是为PV操作做准备的
int semop(int semid, struct sembuf *sops, unsigned nsops);
  • semid:信号量集标识符id,semget的返回值
  • struct sembuf *sops:sops指向一个结构体数组的指针,这个结构体对一个特定的信号进行操作,因此你要熟悉该结构体里的内容才能进行更好的操作。

    • struct sembuf
      {
      unsigned short sem_num; //信号量在信号集中的下标,0代表第一个信号,1代表第二个信号
      short val(sem_op); //操作类型
      short sem_flg; //操作标志
      };

      • 若val>0进行V操作信号量值加val,表示进程释放控制的资源
        若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程挂起,直到资源可用;
        若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误
        若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误

      • sem_flg:
        1、若为0 设置信号量的默认操作
        2、IPC_NOWAIT设置信号量操作不等待
        3、SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值

  • nsops:数组元素个数
  • 2.3、semctl函数
    作用:控制信号量集
int semctl(int semid,int senum,int cmd,...)
返回值:信号量的当前值
  • senum:信号集中信号量的编号(下标),第一个信号量从0开始
  • cmd:红色字体是我们一般常用的

命令解释
IPC_STAT从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
IPC_SET设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
IPC_RMID 从内核中删除信号量集合
GETALL从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中
GETNCNT返回当前等待资源的进程个数
GETPID返回最后一个执行系统调用semop()进程的PID
GETVAL 返回信号量集合内单个信号量的值
GETZCNT返回当前等待100%资源利用的进程个数
SETALL与GETALL正好相反
SETVAL 用联合体中val成员的值设置信号量集合中单个信号量的值

对于该函数,只有当cmd取某些特定的值的时候,才会使用到第4个参数,第4个参数它通常是一个union semum结构,定义如下

union semun
{
    int val;//当执行SETVAl命令时,用到该值,用于指定把信号设计为什么值
    struct semid_ds *buf; //  当执行IPC_STAT/IPC_SET时,它代表内核中所使用内部信号量数据结构的一个复制 ,用到成员:buf
    unsigned short  *array; //当执行GETALL/SETALL命令时,他代表指向一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,用到成员:array
    struct seminfo  *__buf; //设计IPC_INFO时,会使用,但是一般很少用到
}

3、哲学家就餐问题:


  • 3.1:问题描述

假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗米饭,每两个哲学家之间有一只筷子。因为用一只筷子很难吃到米饭,所以假设哲学家必须用两只筷子吃东西。他们只能使用自己左右手边的那两只筷子。所以现在就出现了问题。

这里写图片描述
- 3.2:问题分析
当五个哲学家在某个时刻同时申请到拿筷子操作时,并且申请成功了,这时他们都拿到了左手边的一只筷子,但是当他们申请右手边筷子时,发现右手边筷子没有资源只能继续等,这时五个哲学家都在等,这样就产生了死锁。
- 3.3 : 解决思路

3.3.1:给每个哲学家和筷子进行编号,从0开始到4,因为senum中第一个信号量的下标是从0开始的。

3.3.2:每个筷子代表一个信号量

3.3.3:每个哲学家在取筷子时每次拿左右两只筷子,并且执行P操作使其它哲学家处于等待状态,直到该哲学家吃饱,放下筷子(释放资源)时,其它哲学家才可以拿筷子,从而继续循环。

3.3.4:通过信号量以及PV操作保证每次只有一个哲学家拿一双筷子,其它哲学家都等待,直到该哲学家释放资源。从而避免死锁。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>

int id;

union semun
{
    int val;//设置信号量值
};
void P(int num)
{
    struct sembuf sops[2] = //两只筷子啊
    {
        {num,-1,0},
        {(num+1)%5,-1,0}
    };
    semop(id,sops,2);
}
void V(int num)
{
    struct sembuf sops[2] = //两只筷子啊
    {
        {num,1,0},
        {(num+1)%5,1,0}
    };
    semop(id,sops,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()%5);
         printf("%d哲学家吃饱了\n",num);
         V(num);

    }

}
int main()
{
    srand(getpid());//以当前进程返回的pid值作为产生随机值的源头
    key_t key = ftok(".",'a');//"."代表当前路径
    if(key < -1)
    {
        perror("ftok\n");
        return -1;
    }
    id = semget(key,5,IPC_CREAT|0644);
    if(id == -1)
    {
        perror("perror semget\n");
        exit(-2);
    }
    union semun su = {1};//每个哲学家的筷子数为1
    int i  = 0;
    for(i = 0;i < 5 ; i++)
    {
        semctl(id,i,SETVAL,su);
    }
    int num = 0; 
    for(i  = 1;i < 5; i++ )
    {
       pid_t pid = fork();
       if(pid == 0)
       {
         num = i;
         break;
       }
    }
    zxj(num);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值