信号是Posix标准的重要内容,它提供一种处理异步事件的方法。
不可靠信号
信号不可靠指的是:信号可能丢失,信号可控制性差。
信号处理程序设置函数为:
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
// 此声明较为晦涩,其等价于:
typedef void (*SigHandler)(int);
SigHandler signal(int signo, SigHandler handler);
// 成功返回该信号之前的处理程序,出错返回SIG_ERR
对于任何一个信号都只能按下列三种方式中的一种来执行:
- 忽略(
SIG_IGN
) - 执行系统默认动作(
SIG_DFL
) - 调用配置的信号处理函数
在早期版本中,当通过配置的信号处理函数处理产生的信号时,该信号动作将被复位为默认值。 我们不得不在信号处理程序中重新配置信号处理程序。如下:
...
signal(SIG_INT, sig_int);
...
void sig_int()
{
signal(SIG_INT, sig_int);
...;
}
这种代码有个问题,在发生中断信号之后与在sig_int中调用signal之前有一个时间窗口, 若在此时间窗口中再次发生中断信号。那么第二个中断信号将会导致执行默认动作, 而SIG_INT的默认动作是终止进程。
发送自定义信号
信号不仅产生自内核,我们在用户态应用进程内也可自发向自身或其他进程发送信号。
信号发送函数:
#include <signal.h>
int kill(pid_t pid, int signo); // 成功返回0,失败返回-1
int raise(int signo); // 成功返回0,失败返回-1(向自身发送信号)
kill 函数的 pid 参数取值如下:
pid>0
将信号发送给进程ID等于pid的进程pid==0
将信号发送给调用进程所在组的所有进程pid==-1
将信号发送给调用进程所有有权限发送信号的进程pid<-1
将信号发送给进程组ID等于pid绝对值的进程组
进程将信号发送给其他进程需要权限,其规则是:
- root用户进程(实际用户ID等于0)可将信号发送给任何进程;
- 发送者的实际用户ID或有效用户ID需等于接收者的实际用户ID或有效用户ID。
alarm函数
alarm函数在指定时间之后为进程产生一个SIGALRM信号。若指定seconds为0,则不会产生SIGALRM信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds); // 0或以前设置的闹钟时钟余留值
pause函数
调用pause函数将使进程挂起,直到捕捉到一个信号。该函数总是返回-1,并将errno设置为EINTR。
#include <unistd.h>
int pause();
可靠信号机制
可靠信号机制引入了未决信号、递送等可靠信号术语。
一种可能的信号递送实现:
在可靠信号机制中,所有产生的信号都先进入未决状态,进程可以选择处理处于未决状态的信号(递送)或将其阻塞。
信号集
信号集类型定义为sigset_t,其操作函数如下:
#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);
// 以上四个函数若成功则返回0,失败返回-1
int sigismember(const sigset* set, int signo); // 若signo是set成员则返回1,否则返回0,出错返回-1
信号设置函数
#include <signal.h>
struct sigaction {
void (*sa_handler)(int signo); // 信号处理函数
int sa_mask; // 进入信号处理函数时的信号屏蔽字,返回时会恢复
int sa_flags;
void (*sa_sigaction)(int signo, siginfo_t *info, void *context);
};
int sigaction(int signo, const struct sigaction *newact, struct sigaction *oldact); // 成功返回0,失败返回-1
相比signal此函数除设置信号处理程序之外还拥有的特性包括:
- 在内核向进程递送信号之后,信号执行动作不会被复位
- 执行信号处理程序时,指定的sa_mask与当前信号都会被屏蔽,这样就可以保证信号处理程序不被我们所不期望的信号所中断
- 可指定被信号中断的系统调用在信号递送之后的行为(中断或重启)
sa_flags常用常值定义如下:
SA_INTERRUPT
由此信号中断的系统调用不会自动重启动SA_RESTART
由此信号中断的系统调用将会自动重启动
进程信号屏蔽字操作接口
#include <signal.h>
// 设置、获取进程的信号屏蔽字
int sigprocmask(int how, const sigset_t *newmeask, sigst *oldmask); // 成功返回0,失败返回-1
// 获取进程当前的信号屏蔽字
int sigpending(sigset_t *mask); // 成功返回0,失败返回-1
how可取值:
SIG_BLOCK
新的信号屏蔽字为原先屏蔽字与newmask的并集SIG_UNBLOCK
新的信号屏蔽字为原先屏蔽字与newmask的差集SIG_SETMASK
新的信号屏蔽字为newmask
sigprocmask常用来保护不希望由信号中断的代码临界区。
进程挂起
与 pause 类似,新的可靠信号利用 sigsuspend 函数挂起进程。
#include <signal.h>
// 将进程信号屏蔽字设为mask,然后挂起直到捕捉到一个信号,返回值:-1,并将errno设为EINTR
int sigsuspend(cosnt sigset_t *mask);
«Previous: 进程会话
»Next: Asyncnet中的数据结构