Linux进程间通信:信号量

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

信号量是什么?

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

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

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

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

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

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

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

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

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

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

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

其实操作系统都是进行与线程之间的一个大集合,用进程+线程的模式去不断进行逻辑实现,所以用常规的程序来实现进程之间的同步,互斥关系需要复杂的算法,而且会造成“忙等待”,浪费CPC资源,其实就是我们在进程间通信的时候,存在着多个进程去抢占多个共同资源,就会出现进程抢占上的争执与浪费。所以引入了信号量的概念,其实信号量就是一种特殊的变量,它的表面形式就是一个整形变量附加一个队列;而且啊,他只能够被特殊的操作使用,也就是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 [email protected] $^
   .PHONY:clean
   clean:
       rm -f test

好了,就这么简单。

时间: 2024-10-29 19:06:08

Linux进程间通信:信号量的相关文章

Linux进程间通信 -- 信号量 semget()、semop()、semctl()

这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信 -- 信号.下面就进入信号量的讲解. 一.什么是信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域.临界区域是指执行数据更新的代码需要独占式地执行.而信号量就可以提供这样的一种访问机制,让一个临界区同

Linux进程间通信 -- 信号量函数 signal()、sigaction()

一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止. 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动.通常信号是由一个错误产生的.但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程.一个信号的产生叫生成,接收到一个信号

Linux进程间通信—信号量

二.信号量(semophore) 信号量是一种计数器,可以控制进程间多个线程或者多个进程对资源的同步访问,它常实现为一种锁机制.实质上,信号量是一个被保护的变量,并且只能通过初始化和两个标准的原子操作(P/V)来访问.(P,V操作也常称为wait(s),signal(s)) semaphore 与mutex类似,用于处理同步问题.我们说mutex像是一个只能容纳一个人的洗手间,那么semaphore就像是一个能容纳N个人的洗手间.其实从意义上来说,semaphore就是一个计数锁(我觉得将sem

Linux - 进程间通信 - 信号量

一.概念 简单来讲,信号量是一个用来描述临界资源的资源个数的计数器. 信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件.外部设备等)来实现进程间通信, 他本身更只是一种外部资源的标识.信号量在此过程中负责数据操作的同步.互斥等功能. 当请求 一个使用信号量来表示 的资源时,进程需要先读取信号量的值来判断资源是否可用.大于0,资源可以请求:等于0,无资源可用, 进程会进入睡眠状态直至资源可用. 当进程不再使用一个使用信号量控制的共享资源时,信号量的值+1,

Linux进程间通信-信号量

当多个进程表同时访问系统上的某个资源的时候,比如同时写一个数据库的某条记录,或者同时修改某个文件,就需要考虑进城的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问.通常,程序对共享资源的访问的代码只是很短的一段,你就是这一段代码引发了进程之间的竞态条件.我们称这段代码为关键代码段,或者临界区. 信号量是一种特殊的变量,它只能取自然数并只支持两种操作:等待(wait)和信号(signal),这两种操作更常见的称呼是P.V操作.假设有信号量SV,则对它的P.V操作含义如下: l  P(

Linux进程间通信 -- 信号集函数 sigemptyset()、sigprocmask()、sigpending()、sigsuspend()

我们已经知道,我们可以通过信号来终止进程,也可以通过信号来在进程间进行通信,程序也可以通过指定信号的关联处理函数来改变信号的默认处理方式,也可以屏蔽某些信号,使其不能传递给进程.那么我们应该如何设定我们需要处理的信号,我们不需要处理哪些信号等问题呢?信号集函数就是帮助我们解决这些问题的. 有关Linux进程间使用信号通信的更多内容,可以参阅我的另一篇文章,Linux进程间通信 -- 信号量函数 signal().sigaction() 下面是信号函数集: 1.int sigemptyset(si

Linux程序设计学习笔记----System V进程间通信(信号量)

关于System V Unix System V,是Unix操作系统众多版本中的一支.它最初由AT&T开发,在1983年第一次发布,因此也被称为AT&T System V.一共发行了4个System V的主要版本:版本1.2.3和4.System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如"SysV 初始化脚本"(/etc/init.d),用来控制系统启动和关闭,System V Interface Definitio

【转载】Linux的进程间通信-信号量

原文:Linux的进程间通信-信号量 Linux的进程间通信-信号量 版权声明: 本文章内容在非商业使用前提下可无需授权任意转载.发布. 转载.发布请务必注明作者和其微博.微信公众号地址,以便读者询问问题和甄误反馈,共同进步. 微博ID:orroz 微信公众号:Linux系统技术 前言 信号量又叫信号灯,也有人把它叫做信号集,本文遵循<UNIX环境高级编程>的叫法,仍称其为信号量.它的英文是semaphores,本意是“旗语”“信号”的意思.由于其叫法中包含“信号”这个关键字,所以容易跟另一个

Linux进程间通信——使用信号量(转)

这篇文章将讲述别一种进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信--使用信号.下面就进入信号量的讲解. 一.什么是信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域.临界区域是指执行数据更新的代码需要独占式地执行.而信号量就可以提供这样的一种访问机制,让一个临界区同

Linux进程间通信——使用信号量【转】

本文转载自:http://blog.csdn.net/ljianhui/article/details/10243617 这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信——使用信号.下面就进入信号量的讲解. 一.什么是信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线