Linux:信号量

来源:互联网 发布:网络扣字大手排行榜 编辑:程序博客网 时间:2024/06/07 09:24

接下来我将围绕着以下三点,对信号量的知识做出相应的总结,希望能够为大家带来帮助:

  • 编写信号量代码,实现二元信号量对显示器进行保护
  • 实现父子进程输出成对AA或BB
  • 调研SEM_UNDO标志代表什么含义,用途是什么,如何做到回滚

信号量

信号量:有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码完成了。那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码的首末端,确认这些信号量VI引用的是初始创建的信号量。

信号量的分类

二进制信号量(binary semaphone): 只允许信号量取0或1值,其同时只能被一个线程获取。

整型信号量(integer semaphone):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。

记录型信号量(record semaphone):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

信号量的工作原理

这里主要讨论的是二进制信号量的工作原理。
信号量是一个特殊的变量,程序对它防问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作,二进制的信号变量只能取0和1的变量。
P(sv): 如果sv的值大于0,就给它减1;如果它的值为0,就挂起该进程的执行。
V(sv): 如果有其他进程因等待sv而挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给他加1。
创建一个信号量

#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
  1. 参数:
    key:所创建或打开信号量集的键值。
    nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效。
    semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过or表示
  2. 返回值:
    如果成功,则返回信号量集的IPC标识符。
    如果失败,则返回-1,errno被设定成以下的某个值

删除一个信号量

#include <sys/types.h>  #include <sys/ipc.h>  #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...);
  1. 功能:
    控制信号量的信息。 返回值:成功返回0,失败返回-1;
  2. 参数:
    _semid 信号量的标志码(ID),也就是semget()函数的返回值;
    _semnum, 操作信号在信号集中的编号。从0开始。
    _cmd 命令,表示要进行的操作。删除操作时cmd设置成为IPC_RMID

semctl() 在 semid 标识的信号量集上,或者该集合的第 semnum 个信号量上执行 cmd 指定的 控制命令。(信号量集合索引起始于零。)根据 cmd 不同,这个函数有三个或四个参数。当有 四个参数时,第四个参数的类型是 union 。调⽤用程序必须按照下⾯面方式定义这个联合体:

union semun  {                int val;   //  设置信号量的初始值            struct semid_ds *buf;  // IPC_STAT、IPC_SET 使用缓存区              unsigned short *array; // GETALL,、SETALL 使用的数组            struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区   };
struct  sembuf {        unsigned short  sem_num; //信号在信号集的索引        short   sem_op;  //操作类型(P操作,V操作)        short   sem_flg; //操作标志,在设置P,V操作时一般设置成为SEM_UNDO};

进行P V操作时需要用到的函数

 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, unsigned nsops); // semid表示对哪一个信号量集进行操作 

接下来小编将展示信号量代码,实现二元信号量对显示器进行保护,实现父子进程输出成对AA或BB
试验环境:CentOs6.5

//sem.h#ifndef _SEM_H_#define _SEM_H_#define PATHNAME "."#define PROJ_ID 0X6666#include<stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>union semun {       int val; //  使⽤用的值     struct semid_ds *buf; // IPC_STAT、IPC_SET 使⽤用缓存区    unsigned short *array; // GETALL,、SETALL 使⽤用的数组         struct seminfo *__buf; // IPC_INFO(Linux特有) 使⽤用缓存区 };int CreateSem(int nums);int DestroySem(int semid);int GetSem(int nums);int InitSem(int semid,int which);int P(int semid,int which);int V(int semid,int which);#endif
//sem.c#include"sem.h"int ComSem(int nums,int flag){   key_t key=ftok(PATHNAME,PROJ_ID);   if(key<0)   {       perror("ftok");       return -1;   }   int semid=semget(key,nums,flag|0666);   if(semid<0)   {       perror("semget");       return -1;   }   return semid;} static int op_sem(int semid,int op,int which){    struct sembuf sem;    sem.sem_num=which;    sem.sem_op= op;    return semop(semid,&sem,1);}int P(int semid,int which){  int ret=op_sem(semid,-1,which);  if(ret==0)  {      return 0;  }  else  {      return -1;  }}int V(int semid,int which){    int ret=op_sem(semid,1,which);    if(ret==0)    {        return 0;    }    else    {        return -1;    }}int CreateSem(int nums){    return ComSem(nums,IPC_CREAT|IPC_EXCL);}int GetSem(int nums){    return ComSem(nums,IPC_CREAT);}int DestroySem(int semid){   if(semctl(semid,0,IPC_RMID)<0)   {       perror("semctl");       return -1;   }   return 0;}int InitSem(int semid,int which){  union semun sem;  sem.val=1;  int ret=semctl(semid,which,SETVAL,sem);  if(ret<0)  {      perror("semctl");      return -1;  }  return 0;}
//test.c#include"sem.h"int main(){   int semid=CreateSem(12);   InitSem(semid,1);     sleep(5);   DestroySem(semid);   pid_t id=fork();   if(id==0)  {//child  while(1)    {    P(semid,1);    printf("A");    fflush(stdout);    usleep(100000);    printf("A ");    fflush(stdout);       usleep(100000);    V(semid,1);  }}  else  {     while(1)    {    P(semid,1);    printf("B");    fflush(stdout);    usleep(100000);    printf("B ");    fflush(stdout);     usleep(100000);    V(semid,1);    }  }   wait(NULL);  return 0;}

这里写图片描述

中途可能会遇到一些问题:
semget:File exists
semid:File exists
表示该信号已经创建
这里写图片描述
解决方法:
这里写图片描述

这里写图片描述
浅谈为什么要设置SEM_UNDO标志位
因为一个进程在运行的时候有可能因为异常而中止,而这里我们采用信号量实现两个不同进程间的同步与互斥机制,那么很可能一个进程刚执行过P操作还没来得及执行V操作,也就是一个进程将它之前所独占的资源上锁了,但由于还没来得及解锁就被异常终止了,那么另一进程就会因为资源没有被解锁而长期得不到资源,处于饥饿状态。为了解决这个问题,引入SEM_UNDO这个标志来解决这个问题。

当操作信号量(semop)时,sem_flg可以设置SEM_UNDO标识;SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。

它能做到一个回滚操作,如何实现?
如果一个信号量的初始值是1,对它进行P操作是信号量变成0(表示当前资源不可用),在进程异常退出之前它将之前信号量的值恢复成初始值,也就是将它变成之前的初始值1,这样另一个进程就不会因为资源没有被解锁而长期得不到资源,处于饥饿状态。