Linux Signal (1):
基本概念
1.
信号是软件中断:
信号提供了一种处理异步事件的方法. 每个信号都有一个名字,
他们以SIG开头, 如SIGALRM是闹钟信号, 当由alarm函数设置的计时器超时后产生此信号,
然后由SIGALRM的信号处理函数接管处理, 处理之后返回调用alarm函数的应用程序中.
2. 信号编号:
在头文件中,
信号都用宏定义为正整数的信号编号, 不存在编号为0的信号, kill函数对编号为0的信号有特殊的作用.
POSIX.1将编号为0的信号定义为空信号, 如果kill中的signo参数为0, 则kill仍执行正常的错误检查, 但不发送信号.
这常被用来通过kill的返回值确定一个特定进程是否存在.
3.
不可忽略/捕捉的信号:
大多数信号都可以通过忽略进行处理,
但SIGKILL和SIGSTOP这两个信号不能被忽略. 因为它们是向超级用户提供使进程终止或停止的可靠方法. 另外,
如果忽略某些硬件异常产生的信号(例如非法内存引用或除0), 则进程的行为是未定义的.
4.
介绍几个常见信号:
SIGINT:
当用户按某些终端键时, 引发终端产生的信号. 如Ctrl+C键, 这将产生中断信号(SIGINT).
它将停止一个已失去控制的程序.
SIGSEGV:
由硬件异常(除数为0, 无效的内存引用等等)产生的信号. 这些条件通常由硬件检测到, 并将其通知内核.
然后内核为该条件发生时正在运行的进程产生该信号.
SIGURG:
在网络连接上传来带外数据时产生.
SIGPIPE:
在管道的读进程已终止后, 一个进程写此管道时产生. 当类型为SOCK_STREAM的socket已不再连接时,
进程写到该socket也产生此信号.
SIGALRM:
进程所设置的闹钟时钟超时的时候产生.
SIGABRT:
进程调用abort函数时产生此信号, 进程异常终止.
SIGCHLD:
在一个进程终止或停止时, 它将把该信号发送给其父进程. 按系统默认, 将忽略此信号. 如果父进程希望被告知其子进程的这种状态改变,
则应该捕捉此信号. 通常是用wait系列函数捕捉, 如果不wait的话, 子进程将成为一个僵尸进程.
SIGIO:
此信号指示一个异步I/O事件.
SIGSYS:
该信号指示一个无效的系统调用.
SIGTSTP: 交互式停止信号.
Ctrl+Z, 按下时, 终端将产生此信号, 进程被挂起.
Linux Signal (2):
signal函数
1. 原型:
#include
void (*signal(int signo, void (*func)(int))(int);
成功则返回该信号以前的处理配置,
出错则返回SIG_ERR.
参数说 明:
signo: 信号名,
如SIGINT.
func: 对应signo的信号处理函数的函数名, 这个函数没有返回值, 有一个整型参数,
这是捕捉的情况, 当然也可以是以下两种宏:
SIG_IGN: 忽略.
SIG_DFL: 默认动作.
2.
改写原型:
typedef void
(*sigfunc)(int);
sigfunc *signal(int,
sigfunc);
3.
三个宏定义:
#define SIG_ERR (void (*)()) -1
// 错误编号
#define SIG_DFL (void (*)())
0 // 默认动作编号
#define SIG_IGN (void (*)())
1 // 忽略编号
4.
kill命令:
在shell里面执行kill命令可以向进程发送信号:
kill -USR1 7216
;向pid为7216的进程发送SIGUSR1信号.
kill
7216 ;向pid为7216的进程发送SIGTERM信号.
5. 注意事项:
exec函数执行后,
把该进程所有信号设为默认动作.
exec函数执行后, 把原先要捕捉的信号设为默认,
其他不变.
fork之后, 子进程继承父进程的信号处理方式.
---------------------------------------
Linux Signal (3): kill和raise
1.
函数说明:
kill和raise是用来发送信号的:
kill把信号发送给进程或进程组;
raise把信号发送给(进程)自身.
他们的原型如下:
#include
int kill(pid_t pid, int
signo);
int raise(int signo);
成功则返回0, 出错则返回-1
从原型上可以看出, raise函数是可以通过kill实现的.
raise(signo);
等价于:
kill(getpid(), signo);
2. pid参数:
kill函数中的pid参数,
它有以下4种情况:
pid >
0: 将该信号发送给进程ID为pid的进程.
pid == 0: 将该信号发送给与发送进程属于同一进程组的所有进程(不包括内核进程和init进程).
此时, 发送进程必须具有向这些进程发送信号的权限.
pid < 0:
将该信号发给其进程组ID等于pid绝对值的所有进程(不包括内核进程和init进程). 此时,
发送进程必须具有向这些进程发送信号的权限.
pid == -1:
将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程.(不包括内核进程和init进程).
3.
signo参数:
POSIX.1将编号为0的信号定义为空信号.
如果signo参数是0, 则kill仍执行正常的错误检查, 但不发送信号. 这被用来确定一个进程是否存在.
--------------------
Linux Signal (4): alarm和pause
1.
alarm函数:
alarm函数是设置一个计时器, 在计时器超时的时候,
产生SIGALRM信号. 如果不忽略或捕捉此信号, 它的默认操作是终止调用该alarm函数的进程.
原型如下:
#include
unsigned int alarm(unsigned int
seconds);
返回0或余留秒数
说一下alarm的返回值问题, 每个进程只能有一个alarm维护的"闹钟".
如果该"闹钟"顺利超时, 则返回0;
如果该"闹钟"在计时过程中, 调用了另一个alarm函数,
则该"闹钟"的余留秒数作为该次alarm的返回值,
并且新的"闹钟"开始计时.(实际上是新的闹钟替代了以前的闹钟)
代码举例:
#include
#include
#include
static unsigned int my_alarm(unsigned int nsec)
{
printf("Wait for %u secs to alarm ",
nsec);
return alarm(nsec);
}
static unsigned int my_sleep(unsigned int nsec)
{
printf("Sleep for %u secs ", nsec);
return sleep(nsec);
}
static void sig_alarm(int signo)
{
printf("SIGALRM ");
}
int main()
{
unsigned int ret1, ret2;
if (signal(SIGALRM, sig_alarm) <
0)
perror("signal");
printf("Alarm
start: ");
ret1 = my_alarm(5);
my_sleep(3);
printf("New alarm: ");
ret2 = my_alarm(2);
my_sleep(4);
printf("Alarm
end ");
printf("First return: %u
", ret1);
printf("Second return: %u ",
ret2);
return
0;
}这段代码中我自己封装了my_alarm和 my_sleep,
在其中添加了printf的代码用于跟踪.
程序的运行结果如下:
Alarm
start:
Wait for 5 secs to alarm
Sleep for 3 secs
New alarm:
Wait for 2 secs to alarm
Sleep for 4 secs
SIGALRM
Alarm end
First return: 0
Second return: 2由上面这个结果,
我想我对这个程序就不用多解释了.
由此可见alarm的返回值问题, 一目了然.
2.
pause函数:
pause函数使调用进程挂起, 直到捕捉到一个信号.
它的原型如下:
#include
int pause();
返回-1, 并将errno设置为EINTR.这个函数很简单,
由字面意思就可以理解出来"暂停". pause只有在执行了一个信号处理程序并从其返回时, pause才返回.
----------------------------
Linux Signal (5): 信号集
信号集给我们提供了一个能表示多个信号的是数据类型(sigset_t), 它将在sigprocmask, sigpending,
sigsuspend之类的函数中用到, 这些函数我会在以后的文章中介绍.
1.
信号集相关函数:
#include
int sigemptyset(sigset_t
*set);
成功则返回0, 出错则返回-1.
这个函数用作初始化set指向的信号 集, 清空其中的所有信号.
#include
int sigfillset(sigset_t
*set);
成功则返回0, 出错则返回-1.
这个函数用作初始化set指向的信号 集, 填充其中的所有信号.
#include
int sigaddset(sigset_t *set, int
signo);
int sigdelset(sigset_t *set, int signo);
成功则返回0, 出错则返回-1.
这两个函数用作向set指向的信号集 中, 增加/删除一个signo代表的信号.
#include
int sigismember(const sigset_t
*set, int signo);
真则返回1, 假则返回0, 出错则返回-1.
这个函数用作判断 signo信号是否在set指向的信号集中.
2. 宏:
在signal.h中有两个宏:
#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*ptr = ~(sigset_t)0, 0)
这两个宏分别定义了sigemptyset和 sigfillset两个函数的行为.
sigemptyset:
把ptr指向的地址的内容设为0.
sigfillset: 这是一个逗号表达式, 把0转换为sigset_t类型, 然后按位取反,
并返回0.
通过以上两个宏, 我们可以确切地说, sigset_t是用多位(比信号总数更多的位数)来表示信号集概念的.
因此,
sigaddset:
将某一位设置为1.
sigdelset:
将某一位设置为0.
sigismember:
测试某一个指定位.
下面我们来实现这些函数.
3. 实例:
#include
#include
#define SIGBAD(signo) ((signo) <= 0 || (signo)
>= NSIG)
int sigaddset(sigset_t *set, int
signo)
{
if (SIGBAD(signo))
{
errno = EINVAL;
return (-1);
}
*set |= 1 <<
(signo -1);
return 0;
}
int sigdelset(sigset_t *set, int
signo)
{
if (SIGBAD(signo))
{
errno = EINVAL;
return (-1);
}
*set &= ~(1
<< (signo -1));
return
0;
}
int sigismember(const sigset_t
*set, int signo)
{
if (SIGBAD(signo))
{
errno = EINVAL;
return (-1);
}
return ((*set & (1
<< (signo -1))) != 0);
}
说明一下里面的几个细节:
signo - 1:
因为不存在编号为0的信号, 也就是第0位与编号为1的信号是对应的, 所以减1.
SIGBAD: 小于等于0,
或者大于最大信号编号NSIG.
1
<< : 1的左移位操作, 右边补0,
所以执行或操作时直接实现添加; 执行与操作时需要先取反, 以保证本位为0, 其他位不变, 这样来实现删除.
sigismember:
最后一个return语句看上去有些复杂. 想起来很难想到, 但看起来应该不难. 执行移位后的与操作, 当存在时使本位保持不变,
其他位清零; 当不存在时, 全部清零. 然后判断是否为0, 这样来实现判断存在性.
-----------------------------------
Linux Signal (6): 信号屏蔽字
1. 概念:
信号屏蔽字就是进程中被阻塞的信号集, 这些信号不能发送给该进程,
它们在该进程中被"屏蔽"了. 后面我们会提到, 实际上它们是被阻塞了.
2.
信号屏蔽函数:
#include
int sigprocmask(int how, const
sigset_t *restrict set, sigset_t *restrict oset);
成功则返回0, 出错则返回-1.
sigprocmask函数有3个参 数:
how:
修改信号屏蔽字的方式.
set: 把这个信号集设为新的当前信号屏蔽字. 如果为NULL则不改变.
oset: 保存进程旧的信号屏蔽字. 如果为NULL则不保存.
参 数中的how可以取3个值:
sigprocmask中的how参数 how 说明
SIG_BLOCK 修改后, 该进程新的信号屏蔽字是其当前屏蔽字和set指向的信号集的并集.
SIG_UNBLOCK 修改后, 该进程新的信号屏蔽字是其当前屏蔽字和set指向的信号集的补集的交集.
SIG_SETMASK 修改后, 该进程新的信号屏蔽字将被set指向的信号集的值代替
另外要说的是, sigprocmask只为单线程定义的,
在多线程中要使用pthread_sigmask.
3.
未处理的信号:
在调用信号屏蔽的相关函数后, 被屏蔽的信号对于调用进程是阻塞的,
不能发送给调用进程, 因此是未决的. 取得这些阻塞的信号集, 可以通过调用sigpending函数.
#include
int sigpending(sigset_t
*set);
成功则返回0, 出错则返回-1.
4. 实例:
下面通过一个简单的实例来说明这篇文章中所讲到的两个函数.
#include
#include
#include
#include
static void sig_quit(int signo)
{
printf("SIGQUIT is caught ");
}
int main()
{
sigset_t new, old, pend;
if (signal(SIGQUIT, sig_quit) == SIG_ERR)
{
perror("signal");
exit(1);
}
if (sigemptyset(&new)
< 0)
perror("sigemptyset");
if (sigaddset(&new, SIGQUIT)
< 0)
perror("sigaddset");
if (sigprocmask(SIG_SETMASK,
&new, &old) <
0)
{
perror("sigprocmask");
exit(1);
}
printf("SIGQUIT is blocked ");
printf("Now try Ctrl \ ");
sleep(5);
if (sigpending(&pend)
< 0)
perror("sigpending");
if
(sigismember(&pend, SIGQUIT))
printf("SIGQUIT pending
");
if (sigprocmask(SIG_SETMASK,
&old, NULL) < 0)
{
perror("sigprocmask");
exit(1);
}
printf("
SIGQUIT unblocked ");
printf("Now try Ctrl \ ");
sleep(5);
return
0;
}
这个程序在开始的时候用sigprocmask屏蔽了
SIGQUIT(ctrl+\触发), 在5秒内触发的该信号将可以从sigpending中获得;
然后程序把SIGQUIT解除屏蔽(恢复以前的屏蔽字), 此时再触发该信号将调用sig_quit信号处理函数.
----------------------------------------
Linux Signal (7): sigaction
sigaction函数是用作检查/修改与指定信号相关联的处理动作.
在UNIX早期版本中使用signal, 后来改用了sigaction, 可见它的功能比signal要强大. 另外,
signal函数也是可以用sigaction实现的.
1.
sigaction原型:
#include
int sigaction(int signo, const
struct sigaction *restrict act,
struct sigaction *restrict oact);
成功则返回0, 出错则返回-1.
首先说一下struct sigaction这个结构:
struct sigaction
{
void (*sa_handler)(int);
sigset_t
sa_mask; int
sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void
*);
};
sa_hanlder: 一个带有int参数的函数指针, 或者SIG_IGN(忽略),
或者SIG_DFL(默认).
sa_mask: 信号屏蔽字(集). 当该信号处理函数返回时, 屏蔽字恢复.
sa_sigaction: 替代的信号处理程序, 当使用了SA_SIGINFO标志时,
使用该信号处理程序.
对于sa_flags和siginfo结构, 具体参考APUE 262页.
2. 实例:
用sigaction实现signal:
typedef void
(*sig_func)(int);
sig_func *signal(int signo,
sig_func *func);
{
struct sigaction act, oact;
act.sa_handler
= func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if
(sigaction(signo, &act, &oact)
< 0)
return
SIG_ERR;
return
oact.sa_hanlder;
}
---------------------------------------
Linux Signal
(8): sigsetjmp和siglongjmp
进程部分介绍过了setjmp和longjmp函数,
这两个函数在跳转时会带信号屏蔽字跳转,
在信号处理程序(hanlder)中使用longjmp会导致后来产生的这种信号被屏蔽.
POSIX.1
也没有具体说明setjmp和longjmp对信号屏蔽字的作用, 而是定义了两个新函数:
sigsetjmp和siglongjmp.
1. 原型:
#include
int sigsetjmp(sigjmp_buf env, int
savemask);
直接调用则返回0, 从siglongjmp调用返回则返回非0值.
void siglongjmp(sigjmp_buf env,
int val);
可见发现sigsetjmp比setjmp多了一个参数savemask, 如果非0,
则sigsetjmp在env中保存进程的当前信号屏蔽字.
2. 实例:
还是老习惯, 用代码来验证
#include
#include
#include
#include
static sigjmp_buf jmpbuf;
static void myfunc(int signo)
{
printf("SIGQUIT ");
sleep(1);
siglongjmp(jmpbuf, 1);
}
int main()
{
char *p = NULL;
struct sigaction act;
act.sa_handler = myfunc;
act.sa_flags = SA_INTERRUPT;
sigemptyset(&act.sa_mask);
if
(sigaction(SIGQUIT, &act, NULL) <
0)
perror("sigaction");
if
(sigsetjmp(jmpbuf, 1) == 1)
{
printf("I'm jumped
");
}
else
{
raise(SIGQUIT);
}
printf("I'm here ");
return
0;
}
这段代码首先用sigaction设定SIGQUIT信号的处理函数,
然后sigsetjmp设定sigjmp_buf, 当它从siglongjmp返回时显示I'm jumped,
第一次设定完成后产生SIGQUIT信号.
运行结果:
SIGQUIT
I'm jumped
I'm here