一、 介绍
1- Signals 是什么?
signals 是软件中断
signals 提供了处理异步事件(asynchronous events)的能力
二、 Signal Concepts
2- 信号注意点
- 每个信号都有名字,以
SIG
开头。 - Linux支持31个不同的信号
- signal.h是头文件
- 没有信号数字为0
- 信号
SIGKILL 和 SIGSTOP
绝对不会被忽略,也不能被捕获!
3- 产生信号的五种情况
- 硬件异常:如,除以0
- 用户按下明确的终端按键(如ctrl c)产生的终端信号
kill
function 允许一个进程向另一个进程发送信号kill
command 是kill
的接口- 软件条件(software condition)可以产生信号:如失序的数据到达network connection会产生
SIGURG
信号
4- 产生信号后kernel可以做哪些事?(三种)
- 忽略信号—信号
SIGKILL 和 SIGSTOP
绝对不会被忽略。如果忽略hardware exception
,结果是未定义的 - Catch the signal(捕获信号)—
SIGCHLD
代表子进程已经终止,signal-catching
函数就会调用waitpid
来获取子进程的进程ID和终止信息 - Let default action apply—很多信号的默认行为都是终止进程
5- 文件core
是什么?
进程的memory image都会保存在文件core
中
6- 没有产生文件core
的原因?(5种)
三、 signal - ANSI C signal handling
原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
7- signal 语义取决于C库和你的如何编译代码
8- 由于signal取决于不同实现,最好使用sigaction
代替
signal提供signal
的一种实现
9- 参数signum
是如SIGABRT这些参数
10- handler为signal产生时调用的函数
- handler为
SIG_IGN
为忽略 - handler为
SIG_DEL
采用默认的行为 - 其余是信号服务函数
11- pause造成调用者睡眠直到接收到信号(终止进程或者引起signal-catching function
)
#include <unistd.h>
int pause(void);
example
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
static void sig_usr(int signo);
int main()
{
if(signal( SIGUSR1, sig_usr) == SIG_ERR)
{
fprintf(stderr, "SIGUSR1 error!\n");
exit(-1);
}
while(1) pause();
return 0;
}
static void sig_usr(int signo)
{
if(signo == SIGUSR1)
printf("receive SIGUSR1\n");
else
printf("receive signal %d\n", signo);
}
12- 在后台运行程序
./a.out &
13- kill的作用和注意点
kill的取名是misnomer
(用词不当的)
作用:发送信号给进程或者进程组
14- 进程启动后,所有信号被设置为default action,除非调用exec
的进程会忽略信号
15- 进程只能捕获当前没有被忽略的信号
16- signal的缺点和sigaction的优点
signal:can’t determine the current disposition of a signal without changing disposition(排列)
sigaction:能决定disposition
但是不会改变它
五、 Interrupted System Calls
17- system call分为哪两类?
slow
system call, 会永久性阻塞- other system call
18- slow system call包含哪些内容
- read
- write
- open
- pasue and wait
- certain
ioctl
operation(控制设备) - some IPC functions(进程间通信)
19- 与slow system call显著不同的是disk I/O
相关内容
尽管disk file
的写入和读取会暂时阻塞。该IO操作总是返回,且不会阻塞调用者。
20- 我们需要显式处理被中断的系统调用
的返回值
again:
if((n = read(fd, buf, BUFFSIZE)) < 0){
if(errno == EINTR)
goto again; /*just an interrupted system call*/
}
21- Linux默认重新启动被信号中断的系统调用
signal:automatic restart of interrupted system calls ,Linux中默认
sigaction: Linux中 optional(可选的)
六、Reentrant Functions
(重进入函数)
问题
程序在被信号打断进入signal handler
之后,会继续执行进程原来的内容。类似于hardware interrupt
。但是在signal handler
,我们不能告诉当signal
被捕获时进程正在执行哪里。
example:
1. 在进程中执行getwnam
,然后进入signal handler
执行getwnam
。这样会导致被signal handler
调用产生的信息覆盖了原先的信息。
2. 调用malloc
也会产生问题,因为malloc
在allocated areas
上使用了linked list
,可能在改变链表的时候,就被signal中断了。
22- reentrant functions是什么?特点
Single UNIX Specification 规定了在signal handler
调用中安全的函数。
These functions are reentrant and are called async-signal safe by the Single UNIX Specification.
They block any signals during operation if delivery of a signal might cause inconsistencies.
23- reentrant functions有哪些:
24- 为什么一些funtions不是reentrant functions
- 他们使用静态数据结构
- 调用
malloc
orfree
- 是standard IO library,因为绝大多数标准IO以
nonreentrant way
使用全局数据结构。
注意printf也不是能确保结果的调用,信号可能会打断main中的printf
25- 注意点
- 即使我们使用表中的函数,每个线程仅仅有1个
errno
。main中read
出错改变了errno
的值,进入signal handler
调用出错也改变了errno
的值,这也会导致覆盖。因此,我们需要调用表中functions
时,保存和还原errno值
longjmp
和siglongjmp
没有出现在表中,因为其以非重进入方法更新数据结构。我们需要在catch signals that cause sigsetjmp to be execured
的时候,阻塞信号(while updating the data structures)
八、Reliable-Signal Terminology and Semantics
(可靠信号 术语和语义)
26- 信号产生的原因有哪些?
由事件引发
1. hardware exception(除以0)
2. software condition(alarm timer expiring)
3. terminal-generated signal
4. a call to kill
27- 信号的注意点
1.信号产生时,内核会在process table
设置一些标志(flag)
2.信号产生和发送期间的signal处于pending
3.允许进程在signal
发送之前,改变signal
的action
4.sigpending
被进程调用用于决定哪个signals被blocked
和pending
28- 多个信号准备好发送给一个进程,与current state of the process
相关的信号先发送
如:SIGSEGV
十、kill and raise Functions
链接:http://blog.csdn.net/feather_wch/article/details/50809548
29- kill和raise的作用
kill:发送信号给进程
raise:发送信号给调用者
30- kill的参数signo为0有什么用?
- signo为0时,kill会执行一般的错误检查,但不发送信号。
- 用途: 用来检查特定的进程是否仍然存在。
如果不存在,kill返回-1,errno被设置为ESRCH - 注意:进程的存在不是atomic原子性的。
十一、alarm and pause Functions
链接:http://blog.csdn.net/feather_wch/article/details/50809813
31 alarm和pause作用
- alarm(seconds)谁知定时器在seconds秒后引发SIGALARM信号
调用alarm
会使得之前调用alarm
的参数 seconds 返回。如果seconds
参数为0,之前的alarm clock
取消。 - pause(void)等待捕获到信号,否则一直阻塞
十一、Signals Sets
32 信号集作用
用于表示多个信号
33 数据类型sigset_t作用是包含信号集
34 下列信号集相关函数的作用
#include <signal.h>
int sigemptyset(sigset_t *set);//初始化signal set,set中均不包括(清0)
int sigfillset(sigset_t *set);//初始化signal set,set中均包括(反转)
int sigaddset(sigset_t *set, int signum);//信号集中增加一个信号
int sigdelset(sigset_t *set, int signum);//信号集中删除一个信号
//return: 0 if OK, -1 on error
int sigismember(const sigset_t *set, int signum);//检测信号集中是否包含该信号
//return : 1 if true, 0 if false, -1 on error
十二、sigprocmask function
35 作用
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//return : 0 if OK, -1 on error
检查或者改变signal mask,能告诉kernel信号集中任何一个信号都不允许发生
36 参数作用
oset 非空,进程当前signal mask通过oset返回
set 非空时,how表明current signal mask是如何改变的
set = NULL,不改变任何signal mask
37 注意点
在调用sigprocmask之后,任何unblocked signals被pending(挂起),sigprocmask返回之前,至少信号之一会被发送给process
编写的pr_mask
需要保存和还原errno
的值
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno; //保存errno,使得该函数能用于所有signal handler
if(sigprocmask(0, NULL, &sigset) == -1)
{
fprintf(stderr, "sigprocmask error\n");
exit(-1);
}
printf("%s ", str);
if(sigismember(&sigset, SIGINT)) printf("SIGINT");
if(sigismember(&sigset, SIGQUIT)) printf("SIGQUIT");
if(sigismember(&sigset, SIGUSR1)) printf("SIGUSR1");
if(sigismember(&sigset, SIGALRM)) printf("SIGALRM");
errno = errno_save;//restore errno
printf("\n");
}
十三、sigpending Function
38 作用
返回当前阻塞于delivery(发送)和当前为调用者挂起的signal sets.
(换句话说,返回的信号集中的信号都是阻塞或者挂起的)
example
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
static void sig_quit(int);
int main(void)
{
sigset_t newmask, oldmask, pendmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
{
fprintf(stderr, "can’t catch SIGQUIT");
exit(-1);
}
/*
* Block SIGQUIT and save current signal mask.
*/
sigemptyset(&newmask); //清空signal set
sigaddset(&newmask, SIGQUIT); //增加 SIGQUIT信号
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) //使得信号阻塞
{
fprintf(stderr, "SIG_BLOCK error");
exit(-1);
}
sleep(5);
/* SIGQUIT here will remain pending */
if (sigpending(&pendmask) < 0) //获取阻塞和pending的信号
{
fprintf(stderr, "sigpending error");
exit(-1);
}
if (sigismember(&pendmask, SIGQUIT)) //判断哪些信号是pending的
printf("\nSIGQUIT pending\n");
/*
* Restore signal mask which unblocks SIGQUIT.
*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) //unblock signal
{
fprintf(stderr, "SIG_SETMASK error");
exit(-1);
}
printf("SIGQUIT unblocked\n");
sleep(5);
exit(0);
/* SIGQUIT here will terminate with core file */
}
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
{
fprintf(stderr, "can’t reset SIGQUIT");
exit(-1);
}
}
结果
$./a.out
^\ 'pending中'
SIGQUIT pending
caught SIGQUIT
SIGQUIT unblocked
^\退出 (核心已转储)
$./a.out
^\^\^\^\^\^\^\^\^\^\^\^\^\ '无论多少次unblock后只发送一次'
SIGQUIT pending
caught SIGQUIT
SIGQUIT unblocked
^\退出 (核心已转储) 'Quit(coredump)'
39 注意点
1. 如果我们编写的函数会被其他人调用并且我们需要block一个信号的情况下,如我们要unblock一个信号,我们需要使用SIG_SETMASK,而不要使用SET_UNBLOCK
2. 在block时,发生了n次同一个信号,在unblock后,仅仅会发生一次
3. Quit(coredump)产生在shell发现它的child会异常中止
十四、sigaction Function
原型
sigaction - examine and change a signal action
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
40 作用:允许我们检查和修改特定信号的action
41 参数
act 非null,修改action
oact 非null,返回之前的action
42 struct sigaction
struct sigaction{
void (*sa_handler)(int); //signal-catching function,or SIG_IGN, SIG_DFL
sigset_t sa_mask; //额外阻塞的信号.当 signal-catching function返回,signal mask恢复到先前的值。好处,无论signal handler何时被调用,我们能block明确的信号。
int sa_flags; //指定处理signal的多种选项
void (*sa_sigaction)(int, siginfo_t *, void *);//当flag为`SA_SIGINFO`被设置时调用,实现同时存储`sa_handler和sa_sigaction`
};
44 相同信号额外产生总是不会排队
45 sa_sigaction参数注意点
siginfo_t是结构体,包含signal为何产生的信息。
具体参见书10.14 sigaction Function
对于siginfo_t
1. 如果信号是SIGCHLD
,si_pid, si_status, si_uid
将被设置。
2. 如果信号时SIGILL or SIGSEGV
, si_addr 包含fault的地址
3. si_errno
包含error number
对应于造成信号产生的条件发生。
对于context
无类型指针,可以指向ucontext_t
结构体在signal delivery
的时候识别process context
46 实现signal和signal_intr
signal
/* Reliable version of signal(), using POSIX sigaction().*/
Sigfunc * signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func; //signal handler
sigemptyset(&act.sa_mask); //act.sa_mask = 做了同样的事情
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
} else {
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
十五、sigsetjmp和siglongjmp Function
原型
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
47 和setjmp、longjmp的区别?
sigsetjmp和siglongjmp会保存和恢复signal mask
(linux中),而setjmp and longjmp
不会保存和恢复signal mask
48 注意点:
1. 需要在signal handler
中braching
的时候使用
2. sigsetjmp使用非负的savemask
,env
会被保存
具体example参照书10.15
十六、sigsuspend Function
49 用于保护核心区域不被signal中断
如果一般是使用sigprocmask的SIG_BLOCK和SIG_SETMASK之间的window来进行核心的code保护。这样是有问题的,因为该window之间产生的signal
会被直接舍弃。
50 sigsuspend是能reset signal mask和使得process睡眠的single atomic operation(单一的原子操作)
原型sigsuspend- wait for a signal
#include <signal.h>
int sigsuspend(const sigset_t *mask);
//Returns: -1 with errno set to EINTR
51 sigsuspend参数
参数mask
将进程的signal mask设置为相应值。进程会中止,直到捕获到信号或者产生结束进程的信号。
suspend
会在信号被捕获和signal handler返回后返回。signal maks会被恢复为之前的值。
52 sigsuspend的几种用法
- 保护特定信号的核心代码区域(critical region of code)
- 使用
sigssuspend
等待signal handler
去设置global variable
。 - 同步父子进程
具体参考10.16的三份例程
53 如果我们想在waiting的时候调用其他系统调用该怎么办?
这必须使用多线程来实现。在不使用现成的时候,我们最好的办法是在signal handler中设置global variable
十七、abort Function
54 abort的作用和注意点
作用
发送信号SIGABRT
给调用者(进程不应该ignore 该信号),类似于raise(SIGABRT)
注意点
- ISO C需求 signal 被捕获和signal handler return后,
abort
仍然不能返回到它的调用者。 - abort override 该进程
ignore or block
signals
55 signal handler不会返回的几种可能
signal handler
中执行如下函数
1. exit, _Exit
2. _exit
3. longjmp
4. siglongjmp
56 abort在终止进程前进行了哪些清理工作
- flush out streams
- delete temporary files
- fclose all standard I/O streams
57 kill(getpid(), SIGABRT);是干什么的?
将信号SIGABRT
发送给调用者。如果信号没有被阻塞,那么在kill
返回前会将signal
发送出去
figure 10.25演示了abort的实现方法
十八、system Function
58 POSIX1要求system
忽略SIGINT or SIGQUIT
,为什么呢?
输入interrupt signal
,会使得signal
被发送给所有的前台进程组
example:见10.18
59 system的返回值是什么?
返回的是shell
的终止状态,不是command string
的终止状态。
* bourne shell的终止状态是 128 + signal number(SIGINT value = 2, SIGQUIT value = 3)
60 system注意点
- 使用system的时候,需要正确翻译返回值
- system仅在shell本身异常终止的时候,报告异常终止(abnormal termination)
- 你自己调用fork,exec和wait, 其termination status与你调用
system
是不一样的
十九、sleep Function
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
//Return: 0 or number of unslept seconds
61 sleep会暂停进程直到什么情况发生?
- 指定的时间到了
- 进程捕获signal和signal handler返回
62 没有规范指明alarm和sleep的交互问题
例如:同时使用alarm(10)和sleep(5),在5秒后sleep返回5的时候,SIGALRM会在剩下的5秒后产生吗?这是依赖于实现的(implementation)
63 Linux中sleep是如何实现的?
使用nanosleep(2)
实现,提供高精度延时(high-resolution delay),这是Single Unix Specification的real-time extension中提出的。
example:见figure 10.29
二十、Job-Control Signals
SIGCHLD,SIGCONT,SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
64 除了SIGCHLD,大多数应用程序不处理这些信号
65 不支持 job-contrl的shell启动的任务会将信号设置为SIG_IGN
66 init
程序设置三个job-contrl signals的排序到SIG_IGN.仅仅job-contrl shell
reset 这三个信号到SIG_DFL
二十一、额外特性
67所有signal的name如何获得?
Linux提供了数组extern char * sys_siglist[];
提供了所有信号的名字
68 如何显示信号的错误信息?
使用psignal
#include <signal.h>
void psignal(int signo, const char * msg);
msg就是你想要显示的信息,可以使程序名。
错误会如下格式打印:
msg: Child exited
类似于perror
69 strsignal类似于strerror
#include <string.h>
char * strsignal(int signo);
//Returns: a pointer to a string describing the signal
70 map a signal number to a signal name and vice versa
#include <signal.h>
int sig2str(int signo, char *str);
int str2sig(const char * str, int *signo);
- 经常用于需要显示signal name和signal number的程序中
71 sig2str注意点
caller需要确保str
空间足够大,能够装下最长的字符串。