Linux进程间通讯(中)之信号、信号量实践

上节咱们分享了Linux进程间通讯的其中两种方式:管道、消息队列,文章以下:linux

Linux进程间通讯(上)之管道、消息队列实践
web

这节咱们就来分享一下Linux的另外两种进程间通讯的方式:信号、信号量。编程

一、信号

咱们使用过windows的都知道,当一个程序被卡死的时候无论怎样都没反应,这样咱们就能够打开任务管理器直接强制性的结束这个进程,这个方法的实现就是和Linux上经过生成信号和捕获信号来实现类似的,运行过程当中进程捕获到这些信号作出相应的操做使最终被终止。
windows

信号的主要来源是分为两部分,一部分是硬件来源,一部分是软件来源;进程在实际中能够用三种方式来响应一个信号:一是忽略信号,不对信号作任何操做,其中有两个信号是不能别忽略的分别是SIGKILL和SIGSTOP。二是捕捉信号,定义信号处理函数,当信号来到时作出响应的处理。三是执行缺省操做,Linux对每种信号都规定了默认操做。注意,进程对实时信号的缺省反应是当即终止。数组

发送信号的函数有不少,主要使用的有:kill()、raise()、abort()、alarm()微信

先来熟悉下kill函数,进程能够经过kill()函数向包括它自己在内的其它进程发送一个信号,若是程序没有发送这个信号的权限,对kill函数的调用将会失败,失败的缘由一般是因为目标进程由另外一个用户所拥有。app

kill函数的原型为:异步

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);

它的做用是把信号sig发送给进程号为pid的进程,成功时返回0。kill调用失败返回-1,调用失败一般有三大缘由:编辑器

  • 一、给定的信号无效
  • 二、发送权限不够
  • 三、目标进程不存在

还有一个很是重要的函数,信号处理signal函数。程序能够用signal函数来处理指定的信号,主要经过恢复和忽略默认行为来操做。signal函数原型以下:函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

咱们来看一个例程了解一下signal函数。signal.c

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

//函数ouch对经过参数sig传递进来的信号做出响应。
void ouch(int sig)
{
 printf("signal %d\n", sig);
 //恢复终端中断信号SIGINT的默认行为
 (void) signal(SIGINT, SIG_DFL);
}
int main()
{
  //改变终端中断信号SIGINT的默认行为,使之执行ouch函数
  (void) signal(SIGINT, ouch);
 
  while(1)
  {
   printf("Hello World!\n");
   sleep(1); 
  }
 return 0;
}

运行结果:


能够看出当我按下ctrl+c的时候并不会退出,只有当再次按下ctrl+c的时候才会退出。形成的缘由是由于SIGINT的默认行为被signal函数改变了,当进程接受到信号SIGINT时,它就去调用函数ouch去处理,注意ouch函数把信号SIGINT的处理方式改变成默认的方式,因此当你再按一次ctrl+c时,进程就像以前那样被终止了。

下面是几种常见的信号:

  • SIGHUP :从终端上发出的结束信号
  • SIGINT :来自键盘的中断信号 ( ctrl + c )
  • SIGKILL :该信号结束接收信号的进程
  • SIGTERM:kill 命令发出的信号
  • SIGCHLD:标识子进程中止或结束的信号
  • SIGSTOP:来自键盘 ( ctrl + z ) 或调试程序的中止执行信号。

信号发送主要函数有kill和raise。上面咱们知道kill函数的用法也清楚kill函数是能够向自身发送信号和其它进程发送信号,raise与之不一样的是只能够向自己发送信号。

经过raise函数向自身发送数据,使子进程暂停经过测试以下: raise.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  pid_t pid;
  int ret;
  if((pid=fork())<0)
  {
   printf("Fork error\n");
   exit(1);
  }
  //子进程
  if(pid==0)
  {
   //在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停
   printf("I am child pid:%d.I am waiting for any signal\n",getpid());
   raise(SIGSTOP);
   printf("I am child pid:%d.I am killed by progress:%d\n",getpid(),getppid());
   exit(0);
  }
  //父进程
  else  
  {
   sleep(2);  
   //在父进程中收集子进程发出的信号,并调用kill()函数进行相应的操做
   if((waitpid(pid,NULL,WNOHANG))==0) 
   { 
  //若pid指向的子进程没有退出,则返回0,且父进程不阻塞,继续执行下边的语句
    if((ret=kill(pid,SIGKILL))==0)
    {
     printf("I am parent pid:%d.I am kill %d\n",getpid(),pid);
    }
   }
   //等待子进程退出,不然就一直阻塞
   waitpid(pid,NULL,0);
   exit(0);
  }
}

当调用raise的时候子进程就会暂停:

信号是对终端机的一种模拟,也是一种异步通讯方式。


二、信号量

主要做为进程间,以及同一进程不一样线程之间的同步手段。信号量是用来解决进程之间的同步与互斥问题的一种进程之间的通讯机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操做。信号量对应于某一种资源,取一个非负的整形值。信号量的值是指当前可用的资源数量。

因为信号量只有两种操做,一种是等待信号,另外一种是发送信号。即P和V,它们的行为以下:

  • P(sv):若是sv的值大于零,就给它减1;若是它的值为零,就挂起该进程的执行。
  • V(sv):若是有其余进程因等待sv而被挂起,就让它恢复运行,若是没有进程因等待sv而挂起,就给它加1。

Linux特别提供了一组信号量接口来对信号操做,它们不仅是局限的针对二进制信号量,下面咱们来对每一个函数介绍,须要注意的是这些函数都是用来成对组的信号量值进行操做的。

2.一、semget函数

它的做用是建立一个新信号量或取得一个已有信号量。

int semget(key_t key, int nsems, int semflg); 

第一个参数是key整数型,不相关的进程能够经过它访问一个信号量,它表明程序可能要使用的某个资源,程序对全部信号量的访问都是间接的,先经过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,全部其余的信号量函数使用由semget函数返回的信号量标识符。若是多个程序使用相同的key值,key将负责协调工做。

第二个参数是制定须要的信号数量,一般状况下为1。

第三个参数是一组标志位,当想要当信号量不存在时建立一个新的信号量,能够和值IPC_CREAT作按位或操做。设置了IPC_CREAT标志后,即便给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则能够建立一个新的,惟一的信号量,若是信号量已存在,返回一个错误。

semget函数成功返回一个相应信号标识符(非零),失败返回-1。

2.二、semop函数

它的做用是改变信号量的值。

int semop(int semid, struct sembuf *sops, unsigned nsops);

sops是一个指针,它指向这样一个数组:元素用来描述对semid表明的信号量集合中第几个信号进行怎么样的操做。nops规定该数组中操做的数量。

semop函数返回0表示成功,返回-1表示失败。

2.三、semctl函数

该函数用来直接控制信号量信息。

int semctl(int semid, int semnum, int cmd, …);

semget并不会初始化每一个信号量的值,这个初始化必须经过SETVAL命令或SETALL命令调用semctl来完成。

例程:semctl.c

#include <stdio.h>
#include <linux/sem.h>
#define NUMS 10  

int get_sem_val(int sid,int semnum)//取得当前信号量
{  
  return(semctl(sid,semnum,GETVAL,0));  
}  

int main(void)
{  
  int I ;
  int sem_id;  
  int pid;  
  int ret;  
  struct sembuf sem_op;//信号集结构
  union semun sem_val;//信号量数值

  //创建信号量集,其中只有一个信号量
  sem_id = semget(IPC_PRIVATE,1,IPC_CREAT|0600);
  //IPC_PRIVATE私有,只有本用户使用,若是为正整数,则为公共的;1为信号集的数量;
  if (sem_id==-1)
  {  
    printf("create sem error!\n");  
    exit(1);      
  }  
  printf("create %d sem success!\n",sem_id);      
  //信号量初始化
  sem_val.val=1;  
  //设置信号量,0为第一个信号量,1为第二个信号量,...以此类推;SETVAL表示设置
   ret = semctl(sem_id,0,SETVAL,sem_val);  
  if (ret < 0){  
    printf("initlize sem error!\n");  
    exit(1);      
   }  
   //建立进程
  pid = fork();  
  if (pid < 0)
  {  
    printf("fork error!\n");  
    exit(1);             
  }  
  else if(pid == 0)
  {
      //一个子进程,使用者
      for ( i=0;i<NUMS;i++)
      {  
        sem_op.sem_num=0;  
        sem_op.sem_op=-1;  
        sem_op.sem_flg=0;  
        semop(sem_id,&sem_op,1);//操做信号量,每次-1                  
        printf("%d 使用者: %d\n",i,get_sem_val(sem_id,0));  
      }       
  }  
  else
  {
      //父进程,制造者
     for (i=0;i<NUMS;i++)
     {  
          sem_op.sem_num=0;  
          sem_op.sem_op=1;  
          sem_op.sem_flg=0;  
          semop(sem_id,&sem_op,1);//操做信号量,每次+1                   
          printf("%d 制造者: %d\n",i,get_sem_val(sem_id,0));  
     }       
 }  
 exit(0);  
}

运行结果:

信号量的出现就是保证资源在一个时刻只能有一个进程(线程),因此例子当中只有制造者在制造(+1操做)过程当中,使用者这个进程是没法随sem_id进行操做的。也就是说信号量是协调进程对共享资源操做的,起到了相似互斥锁的做用,但却比锁拥有更强大的功能。

往期精彩

Linux进程间通讯(上)之管道、消息队列实践

C语言三剑客之《C陷阱与缺陷》一书精华提炼

C语言三剑客之《C专家编程》一书精华提炼

【为宏正名】99%人都不知道的"##"里用法

【Linux系统编程】可重入和不可重入函数

以为本次分享的文章对您有帮助,随手点[在看]并转发分享,也是对个人支持。

本文分享自微信公众号 - 嵌入式云IOT技术圈(gh_d6ff851b4069)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索