《Unix高级环境编程》第十章 信号

来源:互联网 发布:fc2最新域名怎么设置 编辑:程序博客网 时间:2024/05/22 19:48

一、 介绍

1- Signals 是什么?

signals 是软件中断
signals 提供了处理异步事件(asynchronous events)的能力

二、 Signal Concepts

2- 信号注意点

  1. 每个信号都有名字,以SIG开头。
  2. Linux支持31个不同的信号
  3. signal.h是头文件
  4. 没有信号数字为0
  5. 信号SIGKILL 和 SIGSTOP绝对不会被忽略,也不能被捕获!

3- 产生信号的五种情况

  1. 硬件异常:如,除以0
  2. 用户按下明确的终端按键(如ctrl c)产生的终端信号
  3. kill function 允许一个进程向另一个进程发送信号
  4. kill command 是kill的接口
  5. 软件条件(software condition)可以产生信号:如失序的数据到达network connection会产生SIGURG信号

4- 产生信号后kernel可以做哪些事?(三种)

  1. 忽略信号—信号SIGKILL 和 SIGSTOP绝对不会被忽略。如果忽略hardware exception,结果是未定义的
  2. Catch the signal(捕获信号)—SIGCHLD代表子进程已经终止,signal-catching函数就会调用waitpid来获取子进程的进程ID和终止信息
  3. 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产生时调用的函数

  1. handler为SIG_IGN为忽略
  2. handler为SIG_DEL采用默认的行为
  3. 其余是信号服务函数

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包含哪些内容

  1. read
  2. write
  3. open
  4. pasue and wait
  5. certain ioctl operation(控制设备)
  6. 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也会产生问题,因为mallocallocated 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有哪些:

figure 10.4

24- 为什么一些funtions不是reentrant functions

  • 他们使用静态数据结构
  • 调用malloc or free
  • 是standard IO library,因为绝大多数标准IO以nonreentrant way使用全局数据结构。

注意printf也不是能确保结果的调用,信号可能会打断main中的printf

25- 注意点

  • 即使我们使用表中的函数,每个线程仅仅有1个errno。main中read出错改变了errno的值,进入signal handler调用出错也改变了errno的值,这也会导致覆盖。因此,我们需要调用表中functions时,保存和还原errno值
  • longjmpsiglongjmp没有出现在表中,因为其以非重进入方法更新数据结构。我们需要在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被blockedpending

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 errorint 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 pendingcaught SIGQUITSIGQUIT unblocked^\退出 (核心已转储)$./a.out ^\^\^\^\^\^\^\^\^\^\^\^\^\  '无论多少次unblock后只发送一次'SIGQUIT pendingcaught SIGQUITSIGQUIT 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. 如果信号是SIGCHLDsi_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 handlerbraching的时候使用

2. sigsetjmp使用非负的savemaskenv会被保存

具体example参照书10.15

十六、sigsuspend Function

49 用于保护核心区域不被signal中断

如果一般是使用sigprocmask的SIG_BLOCKSIG_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在终止进程前进行了哪些清理工作

  1. flush out streams
  2. delete temporary files
  3. 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空间足够大,能够装下最长的字符串。

72 str2sig可以使用没有SIG前缀的信号名,或者表示十进制signal number的字符串如“9”

0 0
原创粉丝点击