进程间通信——信号
宗旨:技术的学习是有限的,分享的精神的无限的。
一、信号和中断
1、信号基本概念
(1)发送信号:产生信号,有多种发送信号的方式【一个进程到另一个进程,内核向用户,进程向自己】
(2)安装信号:设置信号到来时不再执行默认操作,而是执行自定义的代码。
(3)递送信号:一个信号被操作系统发送到目标进程引起某段处理程序的执行。
(4)捕获信号:被递送的信号在目标进程引起某段处理程序的执行。
(5)屏蔽信号:进程告诉操作系统暂时不接受某些信号。
(6)忽略信号:进程被递送到目标进程,但目标进程不处理,直接丢弃。
(7)未决信号:信号已经产生,但因目标进程暂时屏蔽该信号而不能被目标进程捕获到的信号。
(8)可靠信号和不可靠信号:编号小于32的信号是不可靠信号,大于32的是可靠信号。
信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段过程。信号的“屏蔽”是一个开关动作,指的是暂时阻止该信号被处理。
输入命令kill –l查看系统定义的信号列表:
$ kill-l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGEMT 8) SIGFPE 9) SIGKILL 10) SIGBUS
11)SIGSEGV 12) SIGSYS 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16)SIGURG 17) SIGSTOP 18)SIGTSTP 19) SIGCONT 20) SIGCHLD
21)SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU 25) SIGXFSZ
26)SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGPWR 30) SIGUSR1
31)SIGUSR2 32) SIGRTMAX
二、发送信号
发送信号是指一个进程向另一个进程发送某个信号值,但实际并不是直接发送的,而是由OS转发的。产生一个信号有多种情况:
(1) 用户按下Ctrl-C,这个键盘输入产生一个硬件中断。
(2) 硬件异常产生信号。对一个无效存储访问的进程产生一个SIGSEGV
(3) 终止进程信号。其他进程调用kill()函数可将信号发送给另一个进程或进程组。
(4) 软件异常产生信号。
1、kill发送一个信号到进程
——传递一个信号给指定进程使用kill()函数,给当前进程使用raise(),唤醒设置定时使用alarm()
(1)函数原型
#include<signal.h>
intkill(pid_t pid, int sig);
(2)函数参数
pid:要被传递信号的进程号(PID)
pid>0:将信号发送给进程的PID值为pid的进程
pid=0:将信号发送给和当前进程在同一进程组的所有进程
pid=-1:将信号发送给系统内的所有进程
pid<0:将信号发送给进程组号PGID为pid绝对值的所有信号
sig:发送的信号值
(3)返回值
成功返回0,失败返回-1
可向某个进程发送kill -0信号以检测进程是否存在,因为当前进程总是存在的:
kill(getpid, 0);
2、raise自举一个信号
——给当前进程发送一个信号,即唤醒一个进程。
(1)函数原型
#include <signal.h>
int raise(int sig);
(2)函数参数
sig:发送的信号值
(3)返回值
成功返回0,失败返回-1
此函数相当于:
if(kill(getpid(),sig) == -1)
{
perror(“raise”);
}
3、alarm定时
——传递定时信号,即在多少时间内产生SIGALRM信号,调用一次产生一个信号。
(1)函数原型
#include <unistd.h>
int alarm(unsigned int seconds);
(2)函数参数
seconds:多少时间内发送SIGALRM信号给当前进程
seconds=0:取消所有发出的报警请求
(3)返回值
在调用alarm之前没有调用过alarm,成功返回0,失败返回-1;
此前调用过alarm函数,则将重新设置调用进程的闹钟,成功,将以当前时间为基准,返回值为上次设置的alarm将在多少时间内产生SIGALRM信号。
4、ualarm定时
——使当前进程在指定时间内产生SIGALRM信号,然后每隔指定时间重复产生SIGALRM信号。
(1)函数原型
useconds_t ualarm(useconds_t value,useconds_t interval);
(2)函数参数
value:指定时间(us)内产生SIGALRM信号
interval:每隔指定时间重复产生SIGALRM
(3)返回值
成功返回0,
三、安装信号和捕捉信号
1、信号处理方法
(1) 忽略此信号。
(2) 执行该信号的默认处理动作。
(3) 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方 式称为捕捉(Catch)一个信号。
2、signal安装信号
——信号都有默认的处理方式,未做特殊处理,将执行默认操作;若要做特殊处理,则要安装信号处理函数。
(1)函数原型
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandlerhandler);
(2)函数参数
sig:接收到的信号
handler:接收到此信号后的处理代码入口
(3)返回值
成功返回指向针对此信号的上一次设置,执行失败返回SIG_EER(-1)错误。
3、sigaction安装信号
——signal只能提供简单的信号安装操作,并逐步并淘汰。此函数可用来检查和更改信号处理操作。
(1)函数原型
#include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); struct sigaction { void (*sa_handler)(int);/* addr of signal handler, */ /* or SIG_IGN, or SIG_DFL*/ sigset_t sa_mask; /*additional signals to block */ int sa_flags; /* signaloptions, Figure 10.16 */ /* alternate handler */ void (*sa_sigaction)(int, siginfo_t *, void *); };
(2)函数参数
signo:指定信号的编号
若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。
(3)返回值
成功返回0,失败返回-1。
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默 认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以 用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返 回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返 回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,我把sa_flags设为0, sa_sigaction是实时信号的处理函数。
四、信号集操作
信号忽略:系统仍然传递该信号,指示相应进程对该信号不做任何处理。
信号屏蔽:即使传递信号给该进程,该进程也不捕捉信号。
#define SIGSET_NWOEDS (1024 / (8 * sizeof(unsigned long int))) typedef struct { unsigned long int val[SIGSET_NWOEDS]; } sigset_t;
1、sigprocmask
——设置进程屏蔽信号集
(1)函数原型
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
(2)函数参数
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
SIG_BLOCK |
set包含了我们希望添加到当前信号屏蔽字的信号,相当 于mask=mask|set |
SIG_UNBLOCK |
set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当 于mask=mask&~set |
SIG_SETMASK |
设置当前信号屏蔽字为set所指向的值,相当于mask=set |
(3)返回值
若成功则为0,若出错则为-1
2、sigpending
#include <signal.h>
int sigpending(sigset_t *set);
——读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
#include<signal.h> #include<stdio.h> #include<unistd.h> void printsigset(const sigset_t *set) { int i; for (i = 1; i < 32; i++) if (sigismember(set, i) == 1) { putchar('1'); } else { putchar('0'); } puts(""); } int main(void) { sigset_t s, p; sigemptyset(&s); sigaddset(&s, SIGINT); sigprocmask(SIG_BLOCK, &s, NULL); while (1) { sigpending(&p); printsigset(&p); sleep(1); } return 0; }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
3、信号集
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismember(const sigset_t *set, int signo);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。注意,在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返
回0,出错返回-1。 sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
五、等待信号
进程可以因等待某些特定的信号而阻塞,pause()函数用来等待除当前进程外的任意信号,而sigsuspend()用来等待指定信号以外的任意信号。
1、pause
——使调用进程挂起直到有信号递达
(1)函数原型
#include <unistd.h>
int pause(void);
(2)参数返回值
如果信号的处理动作是终止进程,则进程终止, pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态, pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1, errno设置为EINTR, 所以pause只有出错的返回值。
// 用alarm和pause实现sleep(3)函数 #include <unistd.h> #include <signal.h> #include <stdio.h> void sig_alrm(int signo) { /* nothing to do */ } unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; unsigned int unslept; newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); alarm(nsecs); pause(); unslept = alarm(0); sigaction(SIGALRM, &oldact, NULL); return unslept; } int main(void) { while(1) { mysleep(2); printf("Two secondspassed\n"); } return 0; }
(1) main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
(2) 调用alarm(nsecs)设定闹钟。
(3) 调用pause等待,内核切换到别的进程运行。
(4) nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
(5) 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数 是sig_alrm。
(6) 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽,从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程( main函数调用的mysleep函数)。
(7)pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理。
可重入函数——如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
【通俗说:使用了全局变量的都是不可重入的】