信号概念
信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量来判断是否发生了一个信号,而是必须告诉内核"在此信号发生时,请执行下列操作"。
某个信号出现时,可以告诉内核按照下列3种方式处理:
忽略信号
SIGKILL和SIGSTOP不能被忽略,其他信号可以用signal(signo, SIG_IGN)
表示忽略信号。捕捉信号
调用一个信号处理函数。执行系统默认动作
大多数系统默认动作是终止该进程。可以用signal(signo, SIG_DFL)
表示执行系统默认对该信号的动作。-
信号说明
名字 说明 默认动作 SIGABRT 异常终止 终止+core SIGCHLD 子进程终止 忽略
- SIGSEGV
说明进程进行了一次无效的内存引用,比如访问了一个未经初始化的指针。
键盘快捷键发送信号
ctrl-c: ( kill foreground process ) 发送 SIGINT 信号给前台进程组中的所有进程,强制终止程序的执行;
ctrl-z: ( suspend foreground process ) 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程,而并非结束进程,用户可以使用使用fg/bg操作恢复执行前台或后台的进程。fg命令在前台恢复执行被挂起的进程,此时可以使用ctrl-z再次挂起该进程,bg命令在后台恢复执行被挂起的进程,而此时将无法使用ctrl-z再次挂起该进程;一个比较常用的功能:正在使用vi编辑一个文件时,需要执行shell命令查询一些需要的信息,可以使用ctrl-z挂起vi,等执行完shell命令后再使用fg恢复vi继续编辑你的文件(当然,也可以在vi中使用!command方式执行shell命令,但是没有该方法方便)。
ctrl-d: ( Terminate input, or exit shell ) 一个特殊的二进制值,表示 EOF,作用相当于在终端中输入exit后回车;
-ctrl-/ 发送 SIGQUIT 信号给前台进程组中的所有进程,终止前台进程并生成 core 文件ctrl-s 中断控制台输出
ctrl-q 恢复控制台输出
ctrl-l 清屏
其实,控制字符都是可以通过stty命令更改的,可在终端中输入命令"stty -a"查看终端配置
函数signal
#include
void (*signal(int signo, void (*func)(int)))(int);
函数返回值:成功返回以前信号处理函数的指针,出错返回SIG_ERR
#define SIG_ERR (void (*)())-1
#define SIG_ERR (void (*)())0 // 默认
#define SIG_ERR (void (*)())1 // 忽略
中断的系统调用
如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行,该系统调用返回出错,其errno设置为EINTR。
这样处理时因为一个信号发生了,进程捕捉到它,这意味着已经发生了某种事情,所以是个好机会唤醒阻塞的系统调用。
为了支持这一特性,将系统调用分为2类:低速的系统调用和其他系统调用。低速系统调用是指可能会使进程永远阻塞的一类系统调用。包括:
- 某些类型文件按不存在(如管道,终端设备和网络设备)的数据不存在。则读操作可能会使调用者永久阻塞
与被中断的系统调用相关的是必须显示地处理出错并返回。
again:
if ( (n = read(fd, buf, BUFSIZE)) < 0)
{
if (errno == EINTR)
goto again;
/* 处理其他错误 */
}
函数kill和raise
kill
#include
int kill(pid_t pid, int signo);
函数返回值:成功返回0, 出错返回-1
kill的pid参数有以下4种情况:
a pid > 0 将该信号发送给进程ID为pid的进程
b pid == 0 将该信号发送给与发送
c pid < 0 将该信号发送给进程组ID等于pid绝对值。
d pid == -1 将该信号发送给进程有权限向他们发送信号的所有进程。
kill 将信号发送给进程或者进程组。
信号0定义为空信号。kill可以发送信号0用来确定进程是否存在,如果向一个并不存在的进程发送空信号,则 kill返回-1,errno被设置为ESRCH。
rasize
#include
int raise(int signo);
函数返回值:成功返回0, 出错返回-1
raise函数允许进程向自身发送信号。
函数alarm和pause
alarm
#include
unsigned int alarm(unsigned int seconds)
返回值:0或以前设置的闹钟时间的预留秒数
alarm用来设置一个定时器,当定时器超时,会给调用进程产生一个SIGALRM信号。
每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该就弄成注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm调用返回值。以前注册的闹钟时间则被新值代替。
如有以前注册的尚未超过闹钟时间,而且本次调用的seconds值是0,则取消以前的闹钟时间,则余留值仍作为alarm函数的返回值。
pause
#include
int pause();
返回值:-1,errno设置为EINTR
pause函数使调用进程一直挂起直至捕捉到一个信号,只有执行了了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR。
信号集
不可靠信号与可靠信号
在早期的UNIX版本中,信号是不可靠的,不可靠这里指的是:信号可能会丢失,一个信号发生了,但进程却可能一直不知道这一点。同时,进程对信号的控制能力也很差。它能捕捉信号或忽略它。但是有有时用户希望通知内核阻塞某个信号,不要忽该信号。在其发生时记住它。然后再进程做好了准备时再通知它。
当一个信号产生时,内核通常在进程表中以某种形式设置了一个标志。当对信号采取这种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。
进程可以选择"阻塞信号递送"。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者默认将对此信号的动作改为忽略。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。
sigset_t 信号集,用来表示多个信号的数据类型。
#include
int sigemptyset(sigset_t *set); // 清楚所有
int sigfillset(sigset_t *set); // 包括所有
int sigaddset(sigset_t *set, int signo); // 添加
int sigdelset(sigset_t *set, int signo); // 删除
4个函数返回值:成功返回0, 出错返回-1
int sigismmeber(const sigset_t* set, int signo)
若真,返回1,若假,返回-1
函数sigprocmask
#include
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)
返回值:成功返回0。出错返回-1
前面提到了一个进程的信号屏蔽字。函数sigprocmask可以检测或更改。
检测
如果oset是非空指针,那么进程当前屏蔽字通过oset返回,此时参数how设置为0-
修改
如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。名字 说明 SIG_BLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了希望阻塞的附 加信号 SIG_ UNBLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字减去set指向信号集的并集。set包含了希望解除的阻塞附加信号 SIG_ SETMASK 该进程新的信号屏蔽字是set信号集
函数sigpending
#include
int sigpend(sigset_t *set)
返回值:成功返回0;出错返回-1
sigpend函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的。所以一定是当前未决信号的集合。
static void sig_quit(int signo) {
printf("\ncaught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
err_sys("can't reset SIGQUIT");
}
int main(int argc, char **argv) {
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
err_sys("can't catch SIGQUIT");
sigemptyset(&oldmask); // 初始化信号集
sigemptyset(&newmask); // 初始化信号集
/* 将SIGQUIT加入信号屏蔽字 */
sigaddset(&newmask, SIGQUIT); // 将SIGQUIT加入信号集
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
printf("SIGQUIT block\n");
sleep(5);
/* 测试未决信号集中是否含有SIGQUIT */
if (sigpending(&pendmask) < 0) // 未决信号集合保存在pendmask中
err_sys("sigpending error");
if (sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending");
/* 恢复之前的信号屏蔽字 */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblock\n");
sleep(5);
}
函数sigaction
#include
int sigaction(int signo, const struct sigaction *restrict, struct sigaction*oldrestrict)
返回值:成功返回0,出错返回-1
struct sigaction {
void (*sa_handler)(int); // 信号捕捉函数地址
sigset_t sa_mask; // 要阻塞的信号集
int sa_flags; //
void (*sa_sigaction)(int, siginfo_t *, void);
};
|选项|说明|
|SA_INTERRUPT|由此信号中断的系统调用不会自动重启
|SA_RESTART|由此信号中断的系统调用会自动重启
#include
void sig_chld(int signo) {
printf("get signal: %s\n", strsigno(signo));
}
int main() {
pid_t pid;
struct sigaction new_action, old_action;
new_action.sa_handler = sig_chld;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(SIGCHLD, &new_action, &old_action);
if ( (pid=fork()) == -1){
perror("fork error");
exit(1);
}
else if(pid == 0) {
if ( kill(getpid(), SIGSTOP) == -1){
printf("send SIGSTOP to child error\n");
exit(1);
}
printf("child done\n");
} else {
sleep(1);
kill(pid, SIGCONT);
sleep(2);
printf("parent done\n");
}
}