《Unix环境高级编程》读书笔记 第10章-信号

1.引言

  • 信号是软件中断
  • 信号提供了一种处理异步事件的方法。

2. 信号概念

  • 信号的名字都是以3个字符SIG开头。
  • Linux3.2.0支持31种信号。FreeBSD、Linux和Solaris作为实时扩展都支持另外的应用程序定义的信号。
  • 在头文件signal.h(其中include的bits/signum.h)中,信号名都被定义为正整数常量,不存在编号为0的信号。kill函数对信号编号0有特殊的应用。
  • 很多条件可以产生信号:
    1. 用户按下某些终端键时:Ctrl+C、Ctrl+\、Ctrl+Z
    2. 硬件异常产生信号:除数为0、无效的内存引用
    3. 进程调用kill函数可将任意信号发送给另一个进程或进程组
    4. 当检测到某些软件条件已经发生,并应将其通知有关进程时产生信号。如:SIGURG(网络连接上传来带外数据)、SIGPIPE(在管道的读进程已经终止后,一个进程写此管道)、SIGALRM(进程所设置的定时器超时)
  • 信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量(如errno)来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。
  • 当某个信号出现时,可以告诉内核按下列3种方式之一进行处理,称之为信号的处理
    1. 忽略此信号。SIG_IGN。只有两种信号不能被忽略:SIGKILL和SIGSTOP。原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。
    2. 捕捉信号。即通知内核在某种信号发生后,调用一个用户函数。
    3. 执行系统默认动作。对大多数信号的默认动作是终止该进程。
  • 终止+core。大多数Unix系统调试程序都使用core文件检查进程终止时的状态。
  • 在下列条件下不产生core文件:
    1. 进程是设置用户ID的,而且当前用户并非程序文件的所有者
    2. 进程是设置组ID的,而且当前用户并非程序文件的组所有者
    3. 用户没有写当前工作目录的权限
    4. 文件已存在,而且用户对该文件没有写权限
  • 4个平台对各种signal的支持及默认处理方式
  • 主要信号简要说明:
    • SIGABRT。调用abort函数时产生此信号。
    • SIGALRM。当用alarm函数设置的定时器超时时,产生此信号。
    • SIGCHLD。在一个进程终止或停止时,该信号被送给其父进程。按系统默认,将忽略此信号。
    • SIGFPE。表示算术运算异常,如除以0、浮点溢出等。
    • SIGHUP。如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)。通常使用此信号通知守护进程再次读取它们的配置文件。选用此信号的理由是:守护进程不会有控制终端,通常决不会接收到这种信号。
    • SIGILL。表示进程执行一条非法硬件指令。
    • SIGINT。当用户按下中断键Ctrl+C时,终端驱动程序产生此信号并发送至前台进程组的每一个进程。
    • SIGIO。指示一个异步I/O事件。
    • SIGTERM。由kill命令发送的系统默认终止信号。
    • SIGKILL。不能捕获或忽略。它向管理员提供了一红杀死任一进程的可靠方法。
    • SIGPIPE。如果在管道的读进程已终止时写管道,则产生此信号。
    • SIGQUIT。当用户在终端上按下退出键Ctrl+\时,终端驱动程序产生此信号并发送给前台进程组中的所有进程。此信号除了终止前台进程组(和SIGINT一样),同时产生一个core文件。
    • SIGSEGV。指示进程进行了一次无效的内存引用。
    • SIGTSTP。交互停止信号。当用户在终端上按下挂起键Ctrl+Z时,终端驱动程序产生此信号,并发送至前台进程组的所有进程。
    • SIGSTOP。类似于交互停止信号(SIGTSTP),但它不能被捕获或忽略。
    • SIGCONT。此作业控制信号发送给需要继续运行,但当前处于停止状态的进程。
    • SIGTTIN。当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。下列情况例外:1. 读进程忽略或阻塞此信号;2. 读进程所属的进程组是孤儿进程组,此时读操作返回出错,errno设置为EIO
    • SIGTTOU。当一个后台进程组进程试图写其控制终端时,终端驱动程序产生此信号。
    • SIGURG。通知进程已经发生一个紧急情况。如带外数据到达。
    • SIGUSR1、SIGUSR2。用户定义的信号,可用于应用程序。

3. 函数signal

  1. #include <signal.h>
  2. void (*signal(int signo, void (*func)(int)))(int);
  3. Returns: previous disposition of signal (see following) if OK, SIG_ERR on error
  • signal函数由ISO C定义。不涉及多进程、进程组以及终端I/O等,所以它对信号的定义非常含糊,以致于对Unix系统而言几乎毫无用处。
  • 因为signal的语义与实现有关,所以最好使用sigaction函数代替signal函数。
  • 本书中的所有实例均使用图10-18中给出的signal函数,该函数使用sigaction函数是一个平台无关、语义一致的实现。
  • signo参数是上面的信号名。func参数可以是常量SIG_IGN、SIG_DFL或接收到该信号后要调用的函数的地址,即信号处理程序的地址。signal函数的返回值是指向在此之前的信号处理程序的指针。
  1. typedef void Sigfunc(int);
  2. Sigfunc* signal(int, Sigfunc*);
  3. #define SIG_ERR (void (*)())-1
  4. #define SIG_DFL (void (*)())0
  5. #define SIG_IGN (void (*)())1
  • exec,程序启动

    当exec执行一个程序时,所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号(则继续保持忽略)。也就是说,exec函数将原先设置为要捕获的信号都更改为默认动作,其他保持不变。因为当exec一个新程序时,信号处理程序的地址很可能在新程序中已无意义。

  • fork,进程创建

    当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为信号处理程序的地址在子进程中是有意义的。

4. 不可靠的信号

  • 早期的Unix版本中,信号是不可靠的。不可靠指的是,信号可能会丢失:一个信号发生了,当进程却可能一直不知道。同时,进程对信号的控制能力很差,它能捕获或忽略它,但不能阻塞。
  • 早期版本的另一个问题是:在进程每次接到信号对其进行处理时,随即将该信号动作重置为默认值。故需要再次建立对该信号的捕获,但在此期间有一个时间窗口。
  • 早期版本的另一个问题是:在进程不希望某种信号发生时,它不能关闭该信号,只能忽略它。

5. 中断的系统调用

  • 早期Unix系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续运行。该系统调用返回出错,其errno设置为EINTR。
  • 为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。
  • 低速系统调用是可能会使进程永远阻塞的一类系统调用。
  • 与被中断的系统调用相关的问题是必须显式地处理出错返回。典型的代码序列如下:
  1. again:
  2. if ((n = read(fd, buf, BUFFSIZE)) < 0) {
  3. if (errno == EINTR)
  4. goto again; /* just an interrupted system call */
  5. /* handle other errors */
  6. }
  • 4.2 BSD引进了某些被中断系统调用的自动重启动,包括ioctl、read、readv、write、writev、wait、waitpid。但是这种自动重启动的处理方式也会带来问题,某些应用程序并不希望这些函数被中断后重启动。为此,4.3 BSD运行进程基于每个信号禁用此功能。
  • POSIX.1要求只有中断信号的SA_RESTART标志有效时,实现才重启动系统调用。
  • 历史上,使用signal函数建立信号处理程序时,对于如何处理被中断的系统调用,各种实现的做法各不相同。

6. 可重入函数

  • 进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。但是,在信号处理程序中,不能判断捕捉到信号时进程执行到何处:

    1. 如果进程正在执行malloc,而在信号处理程序中又再次调用malloc,这时会?
    2. 如果进程正在执行getpwnam,这是将其结果存放在静态存储单元中的函数,而在信号处理程序中又再次调用getpwnam,这时会?
  • SUS说明了在信号处理程序中保证调用安全的函数。这些函数是可重入的,并被称为异步信号安全的。
  • 没有列入上图的大多数函数是不可重入的,因为:
    1. 它们使用静态数据结构
    2. 它们调用malloc或free
    3. 它们是标准的I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。
  • 应当了解,即使信号处理程序调用的是上图中的函数,但是由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。故作为一个通用的规则,先保存,后恢复。

7. SIGCLD语义

8. 可靠信号术语和语义

  • 首先,当造成信号的事件发生时,向进程发送一个信号。
  • 当对信号采取了某种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的。
  • 进程可以选用“阻塞信号递送”。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。因此,进程在信号递送给它之前仍可改变对该信号的动作。
  • 每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。进程可以调用sigprocmask函数来检测和更改其当前信号屏蔽字。
  • 进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
  • 如果在进程解除对某个信号的阻塞之前,该信号发生了多次,那么?如果递送该信号多次,则称这些信号进行了排队。除非支持POSIX.1实时扩展,否则大多数Unix并不对信号排队,而只递送一次。

9. 函数kill和raise

  • kill函数将信号发送给进程或进程组
  • raise函数则允许进程向自身发送信号。
  1. #include <signal.h>
  2. int kill(pid_t pid, int signo);
  3. int raise(int signo);
  4. Both return: 0 if OK, −1 on error
  • raise(signo); 等价于 kill(getpid(), signo);
  • kill的pid参数有以下4种情况:
    • pid > 0,发送给进程ID为pid的进程
    • pid == 0,发送给与发送进程属于同一进程组的所有进程
    • pid < 0,发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程
    • pid == -1,发送给发送进程具有权限向它们发送信号的所有进程
  • 关于发送信号的权限
    1. 超级用户可将信号发送给任一进程。
    2. 非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。

      特例:如果被发送的信号是SIGCONT,则进程可以将它发送给属于同一会话的任一其他进程。

  • POSIX.1 将信号编号为0定义为空信号。如果signo参数为0,则kill仍执行正常的错误检查,当不发送信号。这常被用来确定一个特定进程是否仍然存在。但是,在返回测试结果时,原来存在的被测试进程可能已经终止,所以这种测试并无多大意义。

10. 函数alarm、pause

  • 当定时器超时时,产生SIGALRM信号。信号由内核产生。
  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
  3. Returns: 0 or number of seconds until previously set alarm
  • 每个进程只能有一个闹钟时间。多次调用alarm以新值代替旧值,并返回旧值的余留值。参数为0,则取消以前的闹钟。
  • pause函数使调用进程挂起直到捕捉到一个信号
  1. #include <unistd.h>
  2. int pause(void);
  3. Returns: −1 with errno set to EINTR
  • 只有执行了一个信号处理程序并从其中返回时,pause才返回。返回-1,errno设置为EINTR。

11. 信号集

  • POSIX.1定义数据类型sigset_t包含一个信号集,并定义以下5个处理信号集的函数
  1. #include <signal.h>
  2. int sigemptyset(sigset_t *set);
  3. int sigfillset(sigset_t *set);
  4. int sigaddset(sigset_t *set, int signo);
  5. int sigdelset(sigset_t *set, int signo);
  6. All four return: 0 if OK, −1 on error
  7. int sigismember(const sigset_t *set, int signo);
  8. Returns: 1 if true, 0 if false, −1 on error

12. 函数sigprocmask

  • 调用函数sigprocmask可以检测或更改,或同时检测和更改进程的信号屏蔽字
  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
  3. Returns: 0 if OK, −1 on error
  • how参数:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
  • 在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在其返回之前,至少将其中之一递送给该进程。

13. 函数sigpending

  • sigpending函数返回一信号集,它对于调用进程而言,其中的各信号是阻塞不能递送的
  1. #include <signal.h>
  2. int sigpending(sigset_t *set);
  3. Returns: 0 if OK, −1 on error

14. 函数sigaction

  • sigaction函数的功能是检查或修改与指定信号相关联的处理动作
  1. #include <signal.h>
  2. int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
  3. Returns: 0 if OK, −1 on error
  1. struct sigaction {
  2. void (*sa_handler)(int); /* addr of signal handler, or SIG_IGN, or SIG_DFL */
  3. sigset_t sa_mask; /* additional signals to block */
  4. int sa_flags; /* signal options, Figure 10.16 */
  5. void (*sa_sigaction)(int, siginfo_t *, void *); /* alternate handler */
  6. };

原创文章,转载请声明出处:http://www.cnblogs.com/DayByDay/p/3948397.html

时间: 2024-12-10 05:19:53

《Unix环境高级编程》读书笔记 第10章-信号的相关文章

UNIX环境高级编程学习笔记(第一章UNIX基础知识)

总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时候使用的平台是Windows+VMware+debian,使用secureCRT来连接(可以实现多个终端连接). 因为第一章是本书大概的描述,所以第一章的我打算写得详细一点,而且书本的原话占的比例会比较多,重点的东西会用粗体显示出来. 1.1  引言 所有操作系统都为他们所运行的程序提供服务.典型的

《UNIX环境高级编程》笔记——3.文件IO

一.引言 说明几个I/O函数:open.read.write.lseek和close,这些函数都是不带缓冲(不带缓冲,只调用内核的一个系统调用),这些函数不输入ISO C,是POSIX的一部分: 多进程共享资源(包括文件)时,会有很多额外的烦恼,需要对共享资源.原子操作等概念深入理解,需要理解涉及的内核有关数据结构,这些数据结构对理解文件.共享有重要作用: 最后介绍dup.fcntl.sync.fsync和ioctl函数. 二.文件描述符 open或creat文件时,内核--文件描述符fd-->

Unix环境高级编程学习笔记(五):进程控制

1 getpid函数,getppid函数,得到进程id,得到父进程id #include<unistd.h> pid_t getpid(void) pid_t getppid(void) uid_t getuid(void)得到实际用户id uid_t geteuid(void)得到有效用户id gid_t getgid(void)得到实际组id gid_t getegid(void)得到有效组id 2 fork函数,当前进程创建新进程 #include<unistd.h> pid

Unix环境高级编程学习笔记(四):进程环境

1 exit函数与_Exit函数 #include<stdlib.h> void exit(int status) void _Exit(int status) 这两个函数的不同之处在于exit函数先执行清理工作后再进入内核(清理I/O缓冲),_Exit函数直接进入内核 2 atexit函数,登记函数,在exit的时候执行 int atexit(void (* func) (void)); 被登记的函数称为终止处理函数,这些函数的调用顺序与登记顺序相反,如果一个函数被登记多次,也会被调用多次

Unix环境高级编程学习笔记(七):线程

1 线程包含线程ID,一组寄存器的值,栈,调度优先级和策略,信号屏蔽字,errno变量,以及线程私有数据.进程的所有信息对于该进程的所有线程都是共享的,包括可执行程序文本,程序全局内存和堆内存,栈以及文件描述符. 线程可以通过pthread_self函数获得自身线程ID #include<pthread.h> pthread_t pthread_self(void) 新增进程可以通过pthread_create函数创建 #include <pthread.h> int pthrea

Unix环境高级编程学习笔记(三):标准I/O , 系统数据文件和信息

1 标准I/O函数不同于read,write函数,是其在流上进行操作, 当首次调用标准I/O函数时,系统会首先调用malloc,为流创造缓冲区, 2 fopen函数 #include<stdio.h> file * fopen(const char* pathname, const char * restrict name); 打开返回指针,出错返回NULL, type的取指有r(读),w(写),a(追加),r+/w+(读+写),a+(读+写+追加) int fclose(file* fp)

UNIX环境高级编程(阅读笔记)---多线程信号

多线程信号 1.默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的 2. 每个线程均有自己的信号屏蔽字,可以使用sigprocmask函数来屏蔽某个线程对该信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号. 3. 对某个信号处理函数,以程序执行时最后一次注册的处理函数为准,即在所有的线程里,同一个信号在任何线程里对该信号的处理一定相同 4. 可以使用pthread_kill对指定的线程发送信号 5. 在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进

(九) 一起学 Unix 环境高级编程 (APUE) 之 线程

. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录 (四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息 (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境 (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制 (七) 一起学 Unix 环境高级编程 (APUE)

《Unix环境高级编程》读书笔记 第3章-文件I/O

1. 引言 Unix系统的大多数文件I/O只需用到5个函数:open.read.write.lseek以及close 本章描述的函数经常被称为不带缓冲的I/O.术语不带缓冲指的是在用户的进程中对其不会自动缓冲,每个read和write都调用内核中的一个系统调用.但是,所有磁盘I/O都要经过内核的块缓存区(也称为内核的缓冲区高速缓存).唯一例外的是对原始磁盘设备的I/O. 2. 文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数,其变化范围是0~OPEN_MAX