上次书写了进程间通信的消息队列,这次是IPC中的另一个模块。信号量

   信号量是什么?

        荷兰计算机科学家Dijkstra把互斥的关键含义抽象称为信号量(semaphore)概念。信号量是一个被保护的量。

    信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的

通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号

量在此过程中负责数据操作的互斥、同步等功能。

    当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可

用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。

当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减

操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。

而在信号量的创建及初始化上,不能保证操作均为原子性。

    其实指的就是我们在操作系统说到的P,V操作;

    为什么会有信号量的存在?

    其实操作系统都是进程与线程之间的一个大集合,用进程+线程的模式去不断进行逻辑实现,所以用常规的程序来实现进程之间的同步,互斥关系需要复杂的算法,而且会造成“忙等待”,浪费CPU资源,其实就是我们在进程间通信的时候,存在着多个进程去抢占多个共同资源,就会出现进程抢占上的争执与浪费。所以引入了信号量的概念,其实信号量就是一种特殊的变量,它的表面形式就是一个整形变量附加一个队列;而且啊,他只能够被特殊的操作使用,也就是P/V操作,在Linux中,进程对共享资源的互斥访问就是通过信号量机制来实现的。

    什么是P/V操作?

    其实P/V操作很容易理解,其实P操作是荷兰文的“等待”的首字母:

    对于P操作而言我们就是想共享资源中申请一个资源,我这个进程需要占有她了,所以我们需要对当前的临界资源进行申请,也就是-1操作。

    P(S):

        S = S-1;

        若S<0,将该进程的状态设置为等待状态,然后将该进程的PCB插入相应的S信号量等待队列末尾,知道有其他进程在S上执行V操作为止。

    V操作是荷兰文的“发信号”的首字母:

    对于V操作而言我们进程就是想声明资源我所获取的已经使用完毕,可以放回了。也就是归还临界资源的资源,也就是+1操作。

    V(S);

        S = S+1;

        若S<= 0,释放信号量队列中等待的一个进程,改变其等待转台未就绪态,并将其插入就绪队列。也存在一个上限临界值。

    总结一下:

    虽然P/V操作可以比较有效地实现进程同步与互斥问题,但是也有这明显的弱点,由于是多个进程进行操作,当一个进程一次使用多个资源,就需要多次的P,多次的V,这个上面就增加了程序的复杂性,也降低了通信效率,严重的可能够会致使进程之间需要相互等待很长的还是件,可能导致死锁的发生。

    所以对于PV操作而言,他们的出现于存在必须是成对的,要不然会产生很严重给的问题。

这就涉及到了生产者-消费者问题。读写问题。哲学家就餐问题。还有死锁。还有信号量的原子操作这个我后面补充书写博客,这里就暂时不提。

    既然是学习Linux下的信号量,那么我们就来看一下信号量的相关函数

    新建信号量函数semget(key_  key,int semnum, int semflg);

        其中key为ftok创建的ID,semnum是制定在新的集合中应该创建的信号量数目,

        第3个参数就是进程间通信所有实现中都相同的打开方式:

         IPC_CREAT:打开,不存在则创建,

         IPC_EXCL:单独使用不存在太多意义,与IPC_CREAT一起使用则表示,若信号量集合存在。则操作失败,返回-1.不存在创建一个新的。

    这个也会用到下面的联合体结构。

            

    信号量操作函数semop(int semid,sembuf *sops,unsinged nsops);

        semid 为semget所创建的信号量ID值。

        sembuf是一个指针,指向的是将要在信号量操作上执行的一个数组。

        sembuf在Linux/sem.h中定义(sys/sem.h)。

        nsops则是操作 +1为V操作,-1为p操作。

        

        sembuf结构

        struct sembuf

        {

                ushort sem_num ;        //信号量编号

                short sem_op;            //信号量操作

                short sem_flg;       //信号量的操作标志

        }


        其中sem_flg是信号量的操作标志,sem_op为负,则从信号两种减掉一个值。如果sem_op为真,则从信号量中加上值。如果sem_op为0,则将进程设置为睡眠状态,直到信号量的值为0为止。

    在父子进程,也是fork操作中需要把sem_flg设置为0

下面资料来源于网络:

    sem_num对应信号集中的信号灯,0对应第一个信号灯。sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用就释放了,但信号灯值却没有改变,此时,信号灯值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。 

    控制信号量参数semctl(int semid,int semnum,int cmd,...);

        这个函数的使用涉及到一个结构体。

        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特有) 使用缓存区

};

注意:该联合体没有定义在任何系统文件中,因此得用户自己声明。 <Centos 下确实是这样,但是UNIX下不同,不需要自己定义声明

    好了。关于信号量的操作就这几个函数,我们来看一下代码吧。

//semm.h
   #pragma once
   #include<stdio.h>
   #include<stdlib.h>
   #include<unistd.h>
   #include<sys/types.h>
   #include<sys/ipc.h>
   #include<sys/sem.h>
   #define _PATH_ "."
   #define _PROJ_ID_ 0x8888
  union semun
  {
      int val;
      struct semid_ds* buf;
      unsigned short  *array;
      struct seminfo  *__buf;
  };

  static int _sem_set(int snum,int flags);
  static int sem_op(int sem_id,int nsops,int flags);
  int create_sem(int snum);
  int get_sem(int snum);
  int init_sem(int sem_id,int snum,int unit_val);
  int sem_p_element(int sem_id,int nsops);
  int sem_v_element(int sem_id,int nsops);
  int destory_sem_element(int sem_id);
 //semm.c
   #include"comm.h"
   static int _sem_set(int snum,int flags)
   {
       key_t _key=ftok(_PATH_,_PROJ_ID_);
       if(_key<0)
       {
           perror("ftok");
           return -1;
       }
      int sem_id=-1;
      sem_id=semget(_key,snum,flags);
      if(sem_id<0)
      {
         
          perror("semget");
          return -1;
      }
      return sem_id;
  }
  
  int create_sem(int snum)
  {
      int flags=IPC_CREAT|IPC_EXCL|0666;
      int ret= _sem_set(snum,flags);
      return ret;
  }
  int get_sem(int snum)
  {
      return _sem_set(snum,IPC_CREAT);
  }
  int init_sem(int sem_id,int snum,int unit_val)
  {
      union semun _un;
      _un.val=unit_val;
      if(semctl(sem_id,snum,SETVAL,_un)<0)
      {
              perror("semctl\n");
          return -1;
      }
      return 0;
  }
  static int sem_op(int sem_id,int seqnum,int op)
  {
      struct sembuf _sm;
      _sm.sem_num=seqnum;
      _sm.sem_op=op;
      _sm.sem_flg=0;
      if(semop(sem_id,&_sm,1)<0)
      {
          perror("semop");
          return -1;
      }
      return 0;
  }
  int sem_p_element(int sem_id,int seqnum)
  {
      return sem_op(sem_id,seqnum,-1);
  }
  int sem_v_element(int sem_id,int seqnum)
  {
      return sem_op(sem_id,seqnum,1);
  }
  int destory_sem_element(int sem_id)
  {
      if(semctl(sem_id,IPC_RMID,0,NULL)<0)
      {
          perror("semctl\n");
          return -1;
      }
      return 0;
  }
 //test.c
   #include"comm.h"
   int main()
   {
       int sem_id=create_sem(1);
       if(sem_id<0)
       {
           printf("error\n");
            return -1;
       }
      init_sem(sem_id,1,1);
      pid_t pid=fork();
      if(pid<0)
      {
          perror("pid");
          return -1;
      }
      else if(pid==0)
      {
          int sem_pid=get_sem(1);
          while(1)
          {
              sem_p_element(sem_pid,0);
              printf("A");                                 
              sleep(1);
              fflush(stdout);
              printf("A");
              sleep(8);
              fflush(stdout);
              sem_v_element(sem_pid,0);
          }
      }
      else
      {
          while(1)
          {
              sem_p_element(sem_id,0);
              sleep(3);
              printf("B");
              sleep(2);
              fflush(stdout);
              printf("B");
                sleep(5);
              fflush(stdout);
              sem_v_element(sem_id,0);
          }        
          waitpid(pid,NULL,0);
          sestory(sem_id);
   
      }
      return 0;
  }
 //Makefile
   .PHONY:all
    all:test
   test:test.c semm.c
       gcc -o $@ $^
   .PHONY:clean
   clean:
       rm -f test


    wKiom1cQ-6iia2mHAACno20V5MU799.png

好了,就这么简单。