#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid , int sig);
该函数把信号sig发送给目标进程pid。
pid>0 信号发送给PID为pid的进程。
pid=0 信号发送给本进程组内的其他进程
pid=-1 信号发送给除init以外的所有进程,但发送者需要拥有对目标进程发送信号的权限。
pid<-1 信号发送给组ID为-pid的进程组中的所有成员
该函数成功时返回0,失败则返回-1,并设置errno。 几种可能的errno:
EINVAL---无效的信号 EPERM---该进程没有权限发送信号给任何一个进程
ESRCH---目标进程或进程组不存在。
Linux定义的信号值都大于0,如果sig取值为0,则kill函数不发送任何信号。但将sig设置为0可以用来检测目标进程或进程组是否存在,因为检查工作总是在信号发送之前就执行。不过这种检测方式是不可靠的。一方面由于进程pid的回绕。可能导致被检测的pid不是我们期望的进程pid。另一方面,这种检测方法不是原子操作。
信号处理方式
目标进程在收到信号的时候,需要定义一个信号处理函数来处理这个信号。
#include <signal.h>
typedef void(*__sighandler_t) (int);
信号处理函数只带有一个整形参数,该参数用来指示信号类型。
信号处理函数应该是可重入的。否则很容易引发一些竞态条件。
所以在信号处理函数中严禁调用一些不安全的函数。
除了用户自定义的信号处理函数外,bits/signum.h 头文件中还定义
了信号的两种处理方式---SIG_IGN , SIG_DEL;
#include <bits/signum.h>
#define SIG_DEL ((__sighandler_t) 0)
#define SIG_IGN ((__sighanlder_t) 1)
SIG_IGN表示忽略信号,SIG_DEL表示默认处理-----
--- 结束进程(Term),忽略信号(Lgn),结束进程并产生核心转储文件(Core)
暂停进程(Stop),继续进程(Cont)
终端系统调用
如果程序在执行处于阻塞状态的系统调用时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。我们可以使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用。 对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN),如果我们没有为他们设置信号处理函数,则他们也可以中断某些系统调用(比如connect 、epoll_wait)。POSIX没有规定这种行为,这是LINUX独有的。
信号函数
signal系统调用
#include<signal.h>
_sighandler_t signal(int sig , _sighandler_t _handler)
sig参数指出要捕获的信号类型。_handler参数是_sighandler_t类型的函数指针。这个返回值是前一次
调用signal时传入的函数指针,或者是信号sig对应的默认处理函数指针SIG_DEL(如果第一次调用的话)
signal出错返回SIG_ERR , 并这是errno。
与信号处理有关的问题
1)重入问题
A. 阻塞当前信号: 当一个信号正在被处理的过程中,与之对应的信号处理函数尚为返回,同样的信号又发生了,系统内核会暂缓对第二个信号的递送,阻塞当前信号,待对第一个信号的处理完成以后,再补充递送。
B. 尽量避免用同一个信号处理函数处理多个不同的信号。
C. 尽量避免在不同的信号处理函数中调用相同的函数。
D. 尽量避免在信号处理过程中使用全局或静态资源-----可重入函数。所有的标准I/O函数都是不可重入函数
在信号处理过程中避免使用。
2)一次性问题
在某些Unix系统上,通过signal函数注册的信号处理函数只能一次性有效。系统内核在每次调用信号处理函数之前,会先将对该信号的处理恢复为默认
sigaction系统调用
#include <signal.h>
int sigaction(int sig, const struct sigaction *act,
struct sigaction *oldact)
sig参数指出要捕获的信号类型,act参数指定新的信号处理方式,oldact参数输出先前信号的处理方式(如果不为NULL的话)
struct sigaction {
void (*sa_handler)(int);
// 新风格的信号处理函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);
// 附加信号掩码
sigset_t sa_mask;
// 信号处理标志
int sa_flags;
// 预留字段,目前置NULL
void (*sa_restorer)(void);
}
typedef struct siginfo{
// 发送信号进程的PID
pid_t si_pid;
// 信号附加数据
sigval_t si_value; //配合sigqueue使用
...
}siginfo_t;
typedef union sigval{
int sigval_int;
void* sigval_ptr;
}sigval_t;
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
A. 除了正在被处理的信号被屏蔽以外还需要屏蔽更多的信号。
struct sigaction sigact = {};
sigaction.sa_handler = sigint;
sigaddset(&sigaction.sa_mask , SIGQUIT) // 同时屏蔽3
sigaction(SIGINT , &sigact , NULL);
B. 不屏蔽正在被处理的信号
sa_flags = SA_NOMASK;
C. 使用新风格的信号处理函数处理信号
sa_flags = SA_SIGINFO;
D. 一次性信号处理
sa_flags = SA_ONESHOT;
E. 防止某些系统调用被信号打断
sa_flags = SA_RESTART;
2). 现在风格的信号发送
#include <signal.h>
int sigqueue(pid_t pid , int signum , const union sigval value);
成功返回0 , 失败返回-1
pid - 接收信号的进程pid signum - 信号编号
value - 附加数据
信号集
#include<bits/sigset.h>
#define _SIGSET_NWORDS (1024 / (8 * sizeof(unsigned long int)))
typedef struct{
unsigned long int __val[_SIGSET_NWORDS];
}__sigset_t; // 包含32个元素的无符号整型数组
#include <signal.h>
#define __sigset_t sigset_t;
包含32个元素的无符号整型数组,包含32 * 32 = 1024位的大整数
填满信号集,将信号集全部信号位置1
int sigfillset(sigset_t* sigset);
清空信号集, 将信号集全部信号位置0
int sigemptyset(sigset_t* sigset);
加入信号,将信号集中与指定信号编号对应的信号位置1
int sigaddset(sigset_t* sigset , int signum);
删除信号,将信号集中与指定信号编号对应的信号位置0
int sigdelset(sigset_t* sigset , int signum);
成功返回0,失败返回-1;
判断信号集中是否有某信号,检查信号集中与指定信号编号对应的信号位是否为1
int sigismember(sigset_t* sigset , int signum);
有则返回1,没有返回0 , 失败返回-1
信号屏蔽
进程表项:
...
信号递送集: 0 0 0 ... 0 1 0 - 1024
信号屏蔽集: 0 0 0 ... 0 0 0 - 1024
^
SIGINT(2)- |
V
信号未决集: 0 0 0 ... 0 0 0 - 1024
//
当信号被发送给某个进程时,系统内核会在其维护的进程表项中,
为该进程的信号递送集设置相应的信号位,
这就叫信号的递送(delivery)。信号从产生到完成递送的过程被称为未决(pending)。
每个进程都有一个信号屏蔽集(掩码)集,
位于该集中的信号,一旦产生并不会被递送到相应的进程,
而是被缓存于信号未决集中。如果进程信号屏蔽集中的某个信号被删除了,
与之对应的未决集中的信号就会被补充递送到特定的进程。
1)设置调用进程的信号屏蔽集
#include <signal.h>
int sigprocmack(int how , _const sigset_t* sigset , sigset* oldset);
成功返回0 , 失败返回-1
how:设置信号屏蔽集的方式
SIG_BLOCK - 将sigset中的信号加入调用进程的当前信号屏蔽集中。
信号屏蔽集 = 信号屏蔽集 U sigset
SIG_UNBLOCK - 从调用进程的当前信号屏蔽集中删除sigset中的信号。
SIG_SETMASK - 讲sigset设置为调用进程的当前信号屏蔽集。
sigset: 用于修改调用进程当前的信号屏蔽集的信号集
oldset: 输出原信号屏蔽集,置NULL则忽略此参数
2)获取调用进程未决的信号集(被挂起的信号)
#include <signal.h>
int sigpending(sigset_t* sigset);
成功返回0,失败返回-1,并设置errno
sigset用于保存被挂起的信号集,显然,进程即使多次收到同一个被挂起的信号,
sigpending函数也只能反映一次。并且,当我们再次使用sigprocmask取消屏蔽该信号
,该信号的处理函数也只能被触发一次。
fork调用产生的子进程将继承父进程的信号掩码,但具有一个空的屏蔽集。
3)可靠信号和不可靠信号的屏蔽
A.对于不可靠信号,通过sigprocmask函数设置为被屏蔽,该信号在屏蔽期间最多只有一个
会被缓存在信号未决集中,并在接触屏蔽后,补充递送,其他则被丢弃。
B.对于可靠信号,通过sigprocmask函数设置为屏蔽,
该信号在被屏蔽期间会按照发送的顺序,全部被缓存在信号未决集队列中,
并在接触屏蔽后,按照发送的顺序,一次被补充递送。