昨天粗略的介绍了一些简单的进程的概念,以及使用简单的方法创立进程。今天这篇文章主要有关进程间的通信方法之一--信号量进行使用说明。所谓信号的概念但凡上过操作系统课的人肯定对老师乐此不疲的铁路信号灯的比喻念念不忘,这也是很多教材喜欢使用的经典比喻。信号量是由于某些错误条件而生成的,与消息不同,我们使用的信号量大多是系统已经定义好了的,除非你重写中断例程,重新定义系统的信号量。我记得linux的信号量好像是256个,具体多少我也没有考量过。信号是由系统产生的,但是我们可以定义一个进程人为发送或者捕捉一个信号量。通常情况下,如果一个信号量没有被捕捉到,那么该进程往往会被终止。一般情况下,当进程接收到一个信号量时,自身会被挂起或者终止,但是SIGCONT信号量则是一个可能会让进程继续运行的信号量。
(所谓中断,指的是CPU在处理当前任务时被打断,转而去处理另一个任务,完成后返回前一任务现场继续执行的过程。中断的具体过程可以百度,因为不同的教材一般会有不同的说法。)
以下是一些常用的有关信号量的方法
一.signal方法
这是一个老版本的linux信号量处理方法,根据《linux程序设计》(第四版)中的说法,这个方法目前已经不再推荐使用,但是在一些老版本的UNIX/Linux代码中可能还会看到其身影。
<signal.h> void(*signal(int sig,void(*func)(int)))(int);
是这个函数的声明。这是一个复杂到让我想吐的声明,也难怪会被淘汰!关于它,我们只要清楚两点就行了。首先它是一个设置信号量处理函数的方法;其次它的信号量处理函数必须是一个接受int参数,返回void值的函数。关于这个signal的返回值,我们一般不怎么会用它,除非你写的代码要求很严格,要做严谨的错误检测啊之类的等等,它返回的是你设置的那个信号处理函数的函数指针。
关于它的例子,下面是一个简单的处理SIGINT的示例。第一次按下ctrl+c会显示一行字,就是你自个儿的信号处理函数;第二次按下ctrl+c就是系统默认的处理方法了。之前要记住两个重要的常量,SIG_IGN--忽略信号,SIG_DFL--恢复默认行为:
#include<signal.h> #include<stdio.h> #include<unistd.h> #include<stdlib.h> void sig_int(int sig){ printf("\nthis is SIGINT-----%d\n",sig); (void)signal(SIGINT,SIG_DFL); } int main(){ (void)signal(SIGINT,sig_int); while(1){ printf("wait for sigint\n"); sleep(1); } exit(0); }
写到这里是不是感觉还挺酷的,嗯?因为这实际上是一个"hello world"级的系统中断例程,如果你将它再编入linux内核的话,它就是系统默认的行为了。所以到这儿的话,其实感觉写个中断好像不像想象中那么难。。。(扯淡吧。。。让你对256个信号量每个写个处理函数,还要保证可重入性,看不写死你!)
到这里如果对信号量和中断有点了解的话可能会问一个问题了:如果在处理这个信号量的过程中,又来一个信号量,那么怎么处理?或者你在信号量处理函数过程中(一般会进入到内核态运行)再次调用系统中断会有什么情况?
对于这些问题,我也不是非常肯定的回答。根据我的知识层次和《linux程序设计》这本书的回答。第一个问题是“不一定”,因为这个得看很多不同的情况。有可能不受影响,有可能系统崩溃,因为这将取决于当前的系统环境以及该处理函数是否是可再入的。。。对于第二个问题,是只有一部分系统调用是进程安全的,而不幸的是prinf()不在其中。。。后面会示例如何不用printf又能打印信息。所以到这儿,我们应该明白另一个概念:进程安全不是指本身,而是这个方法在内核态可能面临的诸多问题。
二.重写signal,使用sigaction
现代linux推荐使用的是sigaction方法,它更简单,方便,安全性可靠性也更高
#include<signal.h> int sigaction(int sig,const struct sigaction *act,struct sigaction *oact);
act是你要设置的信号处理有关信息,oact是用来保存先前的设置的,如果不用可以置为空。对于结构sigaction:
void (*)(int)sa_handler;//信号处理函数指针
sigset_t sa_mask;//信号屏蔽字
int sa_flags;//信号标志
其中sa_flags有四种可取值,分别为:
SA_NOCLDSTOP 子进程停止时不产生SIGCHLD信号;
SA_RESETHAND 信号处理方式在其信号处理函数入口处重置为SIG_DFL;
SA_RESTART 重启可中断函数
SA_NODEFER 捕获到信号时
用sigaction重写上面的signal示例如下:
#include<signal.h> #include<stdio.h> #include<unistd.h> void sig_int(int sig){ printf("\nSIGINT -- %d\n",sig); } int main(){ struct sigaction act,oact; act.sa_handler = sig_int; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT,&act,&oact); while(1){ printf("wait for SIGINT\n"); sleep(1); } }
三.kill
发送一个终止信号:
#include<sys/types.h>#include<signal.h> int kill(pid_t pid,int sig);
errno:EINVAL,给定信号无效;EPERM:发送者权限不够;ESRCH:目标进程不存在
使用kill模拟的一个闹钟定时程序如下,其子进程五秒钟后给父进程发送一个闹钟信号:
#include<sys/types.h> #include<signal.h> #include<unistd.h> #include<stdlib.h> #include<stdio.h> int alarm_field = 0; void alarm_ring(int sig){ alarm_field = 1; printf("this is SIGALRM"); } int main(){ pid_t pid; printf("alarm ring simulation\n"); pid = fork(); switch(pid){ case -1:perror("fork wrong\n"); exit(-1); case 0:printf("child procedure\n"); sleep(5); kill(getppid(),SIGALRM); exit(0); } printf("wait for the SIGALRM\n"); (void)signal(SIGALRM,alarm_ring); pause(); if(alarm_field = 1){ printf("alarm finished \n"); } exit(0); }
四.信号量
有关信号量有一些常用的操作如下:
#include<signal.h>
int sigaddset(sigset_t *set,int signo);//添加
int sigemptyset(sigset_t *set);//清空
int sigfillset(sigset_t *set);//填充
int sigdelset(sigset_t *set, int signo);//删除
int sigismember(sigset_t *set,int signo);//判断
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);//前者取代后者
int sigpending(sigset_t *set);//待处理信号写到集中
int sigsuspend(const sigset_t *sigmask);//挂起信号直到信号集中的信号到来
其中how值可取如下:
SIG_BLOCK 添加;SIG_SETMASK 取代; SIG_UNBLOCK 删除;