Bootstrap

信号Signals--APUE第三版

10.1 介绍

Signals 提供了一种 处理异步事件的 方式, 例如用户在中断 键入ctrl + C 来终止一个程序.

我们即将描述的就是 POSIX.1 标准化的 reliable-signal routines .

10.2 Signal Concepts 信号概念

首先每个信号都有一个名字。这些名字以SIG开头。

例如: SIGABRT 是 abort signal ,当一个程序调用abort 函数时生成。

            SIGALRM 是 alarm signal .

Linux 3.2.0 支持 31种不同的信号。注意信号都是正整数常量, 没有值为0的信号。

Linux 3.2.0 定义了信号在 <bits/signum.h>

下列多种情况(numerous conditions) 可以生成一个信号:

a: 终端生成: 当用户按 特定的 终端键。

如 DELETE 键, Control -C 键 会引起 interrupt signal (SIGINT) 产生。 #18章 说 如 何映射这个信号 给任何 字符。

b: 硬件异常产生信号:被零除, 非法的内存引用,等。

通常由硬件(hardware)侦测到,并 且内核被通知, 内核然后 产生合适的信号 给 那个引起这种情况的进程。

如 SIGSEGV 的产生就是因为 进程执行了非法的内存引用。

c: kill(2) 函数 : 允许一个进程发送 任何信号 给 另外的进程或进程组。

自然地,有前提 条件: 我们要发送信号 给的那个进程,我们要么是这个进程的拥有者,要么我们 是超级用户。

d: kill(1) 命令: 允许我们发送信号给其它进程。它是kill 函数的接口。

这个命令通常用于 终止 后台运行的进程

e: 软件情况 可以产生信号:

例如 SIGURG( 当网络连接上的out-of-band data 达到了)

SIGPIPE(当一个进程试着去往一个pipe中写入,这个pipe没有 reader)

SIGALRM( 由进程设置的 alarm clock 过期时)

信号来了,进程怎么处理?

signal 是典型的异步事件的例子。它们发生在进程的任何可能的时间。进程不能简单地 测试一个变量(如errno) 来看是否一个信号已经产生;替代地,进程不得不告诉kernel “如果并且合适信号产生, 做如下的...”

当信号来了,告诉内核 做三件事其一:

1. 忽略signal。 这对大多数信号都是有效的,除了2个信号(它们从不被忽略):SIGKILL 和SIGSTOP。 不能被忽略的原因:是为了提供给内核和超级用户  一种必定成功执行(要么杀死,要么停止进程)的一种方式。

2. 捕获信号。 为了这样做,需要告诉内核 去调用 函数,这个函数中我们可以做任何我想要去处理的事。注意有两个信号不能被捕获“SIGKILL, SIGSTOP”

3. 应用默认的动作(action):  每个信号都有 一个默认的action, 注意大多数默认的action是终止进程。

信号 及 其 默认的action, 及所被支持的系统。表

信号名称

描述

Linux3.2.0

默认 action

SIGABRT

abnormal termination (abort)

支持

terminate+core

SIGALRM

timer expired(alarm)

支持

terminate

SIGBUS

hardware fault

支持

terminate+core

SIGCANCEL

threads library internal use

\

ignore

SIGCHLD

change in status of child

支持

ignore

SIGCONT

continue stopped process

支持

continue/ ignore

SIGEMT

hardware fault

支持

terminate + core

SIGFPE

arithmetic exception

支持

terminate + core

SIGFREEZE

checkpoint freeze

\

ignore

SIGHUP

hangup

支持

terminate

SIGILL

illegal instruction

支持

terminate + core

SIGINFO

status request from keyboard

\

ignore

SIGINT

terminal interrupt character

支持

terminate

SIGIO

asynchronous I/O

支持

terminate / ignore

SIGIOT

hardware fault

支持

terminate + core

SIGJVM1

Java virtual machine internal use

\

ignore

SIGJVM2

Java virtual machine internal use

\

ignore

SIGKILL

termination

支持

terminate

SIGLOST

resource lost

\

terminate

SIGLWP

threads library internal use

\

terminate/ignore

SIGPIPE

write to pipe with no readers

支持

terminate

SIGPOLL

pollable event(poll)

支持

terminate

SIGPROF

profifiling time alarm (setitimer)

支持

terminate

SIGPWR

power fail / restart

terminate /ignore

SIGQUIT

terminal quit character

支持

terminate+core

SIGSEGV

invalid memory reference

支持

terminate+core

SIGSTKFLT

coprocessor stack fault

支持

terminate

SIGSTOP

stop

支持

stop process

SIGSYS

invalid system call

支持

terminate+core

SIGTERM

termination

支持

terminate

SIGTHAW

checkpoint thaw

\

ignore

SIGTHR

threads library internal use

\

terminate

SIGTRAP

hardware fault

支持

terminate+core

SIGTSTP

terminal stop character

支持

stop process

SIGTTIN

background read from control tty

支持

stop process

SIGTTOU

background write to control tty

支持

stop process

SIGURG

urgent condition(sockets)

支持

ignore

SIGUSR1

user-defined signal

支持

terminate

SIGUSR2

user-defined signal

支持

terminate

SIGVTALRM

virtual time alarm(setitimer)

支持

terminate

SIGWAITING

threads library internal use

\

ignore

SIGWINCH

terminal window size change

支持

ignore

SIGXCPU

CPU limit exceeded (setrlimit)

支持

terminate or

terminate+core

SIGXFSZ

fifile size limit exceeded (setrlimit)

支持

terminate or

terminate+core

SIGXRES

resource control exceeded

\

ignore

在以下情况中,core file 将不会被生成,

a: 进程 被 set-user-ID 并且当前的用户不是程序文件的拥有者。

b: 进程被 set-group-ID 并且当前用户不是程序文件的 组拥有者。

c:  在当前工作目录下,用户没有写权限。

d:  file 已经存在 并且 用户没有写权限去 写它。

e: file 是太大了。

core file 的权限 通常 是 user-read , user-write.

概述每一种信号。

SIGABRT :

        通过调用abort 函数 产生。

SIGALRM :

        当 timer(使用alarm 设置) 过期;

        interval timer ( 使用setitimer设置)过期;

SIGBUS :

        硬件错误;通常 在特定类型的内存错误时 产生。

SIGCANCEL :

        这个信号 在Solaris threads library 内部中使用

SIGCHLD :

        不论何时 进程终止 或停止, SIGCHLD 信号就被发送给 parent。默认地,信号被忽略。

        信号捕获函数的正常的action 是:调用 其中一个wait函数来 获取child的进程ID和 终止状态。

SIGCONT :

        job-control signal 要被发送给 一个停止的进程,以使它可以继续。默认的action 是继续进程, 但是 如果这个进程不是停止的,他就会忽略它。

SIGEMT :

        硬件错误; 来自PDP-11 “emulator trap “指令;并不是所有的平台都支持这个信号。在 Linux 上,只有某些架构支持 如: SPARC, MIPS, and PA-RISC

SIGFPE :

        这个信号 是一个 算法异常(arithmetic exception): 如 被0 除,浮点数overflow 等等

SIGHUP :

        如果terminal interface 检测到连接断了,这种信号就会 被发送给 控制进程(即,session leader; 它关联了一个 控制终端)。

        注意,session leader 接收这个信号 可能是处于background; 和 正常的 terminal-generated 信号(interrupt, quit, suspend)不同的是,它们总是被发送给 前台进程组。

        如果session leader 终止了, 这个信号也会产生,在这种情况下:信号会被发送给 前台进程组的 每个进程。

        这个信号 常常被用来 通知守护进程 去重新加载它们的配置文件。选择SIGHUP 做这项任务的原因是:守护进程 不应该有一个 控制终端 并且 正常情况下从不接收这个信号。

SIGILL

        进程已经执行了非法的 软件指令。

SIGINFO

        Linux 不支持SIGINFO

SIGINT

        当我们按了中断键(通常是 DELETE 或 Control-C), terminal driver 就会生成这个信号。

这个信号 通常被发送给 前台进程组的 所有进程。这个信号被通常用于 终止一个 跑 偏的程序。

SIGIO

        这个信号 指明了 一个 异步 I/O 事件

        Linux 3.2.0 定义SIGIO 是和 SIGPOLL 相同的值,所以它的默认行为是:终止进程。

SIGIOT

        硬件错误

        在Linux3.2.0 Mac OS X 10.6.8 被定义 和 SIGABRT 有相同的值。

SIGJVM1

        在Solaris 使用java 虚拟机 时会用到

SIGJVM2

        在Solaris 使用java 虚拟机 时会用到

SIGKILL

        不能被捕获 或 忽略。它提供给系统管理员 一种确定无疑的 杀死任何进程的 途径。

SIGLOST

        在Solaris 上使用

SIGLWP

        在Solaris 的线程库中 使用。

SIGPIPE

        如果我们向一个管道写,当这个reader 已经终止了,SIGPIPE 就会产生了。

        当一个进程向 一个 类型为SOCK_STREAM 的socket (并且它不再connected)  写入时, 信号就会产生。

SIGPOLL

        这个信号在SUSv4 被标记为废弃的, 所以在未来版本有可能被移除。当在pollable 设备上一个特定的事件发送时,信号会产生。

SIGPROF

        这个信号在SUSv4 被标记为废弃的, 所以在未来版本有可能被移除。

SIGPWR

        这个信号是 依赖系统。它主要用于 一种系统(它用一个不中断的电源供给 UPS).如果power 失败,则UPS接管并且软件会得到通知。在这个时间点,不需要做什么,因为系统继续运行在电池电源上。但是如果电池 电量很低,软件通常会被通知;在这个时间点。在大多数系统,init process 会收到SIGPWR 信号,来处理 system shutdown.

        在Linux 上 默认是 终止进程。

SIGQUIT

        当我们按quit键(通常是Control-backslash),terminal driver 会生成这个信号。这个信号被发送给前台进程组中的所有进程。这个信号不仅终止 前台进程组,还会产生 core file.

SIGSEGV

        这个信号 表明了 进程 有一个非法的内存引用(这通常是程序的bug,如使用了一个未初始化的指针)

        SEGV 名 是“ segmentation violation”

SIGSTKFLT

        Linux 定义的。出现在早期的Linux版本。 这个信号不再被内核生成了,保留它是为了 向后兼容

SIGSTOP

        job-control 信号,来停止一个进程。他和 SIGTSTP 相似,但是SIGSTOP 不能够被捕获 或忽略。

SIGSYS

        非法的系统调用。 如:你构建了一个程序它使用了一个新的系统调用,然后你把程序放在老版本的操作系统上运行,它没有某些系统调用。

SIGTERM

        kill(1)命令默认地 产生的 termination signal. 由于它可以被捕获,程序就有机会去 做一些清理工作后愉快地终止。 对比SIGKILL, 它(SIGKILL)不能被捕获或忽略。

SIGTHAW

        Solaris 定义的

SIGTHR

        FreeBSD 使用的线程库

SIGTRAP

        硬件错误; 名字来源于 PDP-11 TRAP 指令。实现常常使用这个信号 去传递控制给debugger(当断点指令被执行时)。

SIGTSTP

        当我们按 terminal suspend key (通常时Control-Z)时,terminal driver 生成信号。这个信号被发送给 前台进程组 的所有进程。

SIGTTIN

        当一个处于 后台进程组的 进程 尝试去从他的 控制终端 读取时,terminal driver 就会产生这个信号。

有特殊情况 a: 读进程忽略或 阻塞了 这个信号

b: 读进程的进程组 是孤儿时

这两种情况都不会生成改信号;代替地read操作失败,并设置errno 为EIO

SIGTTOU

        当一个处于后台进程组中的进程 尝试去写它的控制终端时,信号就会有terminal driver 产生。 不像 后台 读, 一个进程 可以选择 去允许 后台写 到 控制终端。

如果后台写 不被允许 则有SIGTTIN 信号产生。

这有两种特殊情况:

a: 要写 的进程 忽略或阻塞了信号

b: 写进程的 进程组是 孤儿的

这两种情况下 都不产生信号;替代地,写操作返回一个error 并且errno 被设置为EIO

忽略是否 后台写是 被允许的, 其它终端操作(不是写)也可能产生SIGTTOU 信号。

如: tcsetattr, tcdrain, tcflush, tcflow, tcsetpgrp.

SIGURG

        这个信号通知进程一个紧急的情况发生了。当out-of-band data 在网络连接中 收到时, 这个信号 是可选的 会生成。

SIGUSR1

        这是用户定义 的信号,用在应用程序中。

SIGUSR2

        这是用户定义 的信号,用在应用程序中。

SIGVTALRM

        当 一个 virtual interval timer(由setitimer 设置) 过期时,产生信号

SIGWAITING

        Solaris 线程库使用

SIGWINCH

        内核维护每个终端 或 伪终端的窗口大小。一个进程 可以使用 ioctl 函数来获取或设置窗口size。进程 可以改变窗口size (从之前的值),内核就会生成 SIGWINCH 信号给前台进程组。

SIGXCPU

        如果进程超出了它的 soft CPU time limit, 信号就会产生。

        Linux 3.2.0 支持 一个默认的action : 终止并且有core file.

SIGXFSZ

        如果进程超出了 soft file size limit

        Linux 3.2.0 支持 一个默认的action : 终止并且有core file.

SIGXRES

        仅仅 Solaries

10.3 signal Function

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

Returns: previous disposition of signal (see following) if OK, SIG_ERR on error

由于signal的语义在 各个实现是不同的,我们必须使用sigaction 函数替代。

所以 不要用<signal.h> 中的这个signal函数,用也是用sigaction实现的signal( 覆盖系统的signal 函数)

signo 参数 是 信号名对应的值。

参数func 可以是:

        a: 常量 SIG_IGN

        b: 常量 SIG_DFL

        c:  一个函数的地址

        SIG_IGN : 告诉系统 忽略信息

        SIG_DFL:  设置和信号关联的action 为默认值。

        一个函数的地址: 我们安排去“catch”信号。我们叫这个函数为 signal handler 或 signal-catching function.

signal 返回一个指针,这个指针指向 之前的signal handler .

使用typedef 可以简化signal 的定义。

typedef void Sigfunc(int);

然后原型 变成如下:

SigFunc * signal(int, Sigfunc *);

在<signal.h> 将会找到 SIG_IGN , SIG_DFL 的声明

#define SIG_ERR (void (*)())-1

#define SIG_DFL (void (*)())0

#define SIG_IGN (void (*)())1

例子:10.2 简单的程序,用来捕获SIGUSR1 和 SIGUSR2

1.#include "apue.h"  
2.  
3.static void sig_usr(int);   /* one handler for both signals */  
4.  
5.int  
6.main(void)  
7.{  
8.    if (signal(SIGUSR1, sig_usr) == SIG_ERR)  
9.        err_sys("can't catch SIGUSR1");  
10.    if (signal(SIGUSR2, sig_usr) == SIG_ERR)  
11.        err_sys("can't catch SIGUSR2");  
12.    for ( ; ; )  
13.        pause();  
14.}  
15.  
16.static void  
17.sig_usr(int signo)      /* argument is signal number */  
18.{  
19.    if (signo == SIGUSR1)  
20.        printf("received SIGUSR1\n");  
21.    else if (signo == SIGUSR2)  
22.        printf("received SIGUSR2\n");  
23.    else  
24.        err_dump("received signal %d\n", signo);  
25.}  
26.  
27.  
28./** 
29.[xingqiji@work78 signals]$ ./sigusr & 
30.[1] 51592 
31.[xingqiji@work78 signals]$ kill -USR1 51592 
32.received SIGUSR1 
33.[xingqiji@work78 signals]$ kill -USR2 51592 
34.received SIGUSR2 
35.[xingqiji@work78 signals]$ kill 51592 
36.[xingqiji@work78 signals]$  
37.[1]+  已终止               ./sigusr 
38.[xingqiji@work78 signals]$  
39. *  
40. */  

Program Start-Up  程序启动

许多交互式的程序,捕获这两个信号 有如下类似的代码:

void sig_int(int), sig_quit(int);

if (signal(SIGINT, SIG_IGN) != SIG_IGN)

signal(SIGINT, sig_int);

if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)

signal(SIGQUIT, sig_quit);

进程创建:

当一个进程调用 fork, child 会继承parent的 signal dispositions.

10.4 Unreliable Signals (不可靠的信号

10.5 Interrupted System Calls (中断系统调用

为了支持这个功能,系统调用 系统调用被分为 两类:slow 系统调用 和 其它的。

这些 slow 系统调用可能会 永远阻塞。永远阻塞的有如下类:

a: 读(reads) 也可能永远阻塞调用者( 读特定的文件读不数据,这类文件类型 如:pipes, terminal devices, 和network devices)

b: 写(writers ) 也可能会永远阻塞调用者

c: 打开特定的文件类型 阻塞了 调用者, 直到 一些情况发生

d: pause 函数(它使调用者进程 sleep,直到收到一个信号); wait 函数;

e: 特定的ioctl 操作

f: 一些 进程间通信的 函数(15章)

值得记录的 slow 系统调用 的异常 就是 那些 和 disk I/O 相关的。尽管 对文件的读或写可能会 临时性的 阻塞 调用者(当硬盘驱动排队请求,然后请求被执行)。除非硬件错误,I/O 操作通常会 很快 返回和不阻塞调用。

当一个进程 从 终端device 初始化了read ,并且在终端的用户 离开了很长一段时间。这种情况进程可能被阻塞 数小时,甚至数天,除非系统坏掉。

有中断系统调用这种问题,我们就不得不显式地处理返回的错误了。典型的代码写法是(假设有 读操作,如果它被中断了,我们想继续读)

again:

if ((n = read(fd, buf, BUFFSIZE)) < 0) {

        if (errno == EINTR)

        goto again; /* just an interrupted system call */

        /* handle other errors */

}

为了 避免 如上这样的 处理 中断的系统调用,4.2BSD 产生了 一些特定的的 可以 自动 restart 的中断的系统调用。自动restart  的系统调用是:ioctl, read, readv, write, writev,  wait, waitpid.

POSIX.1 要求,实现去 restart 系统调用,前提是 中断的信号 设置了SA_RESTART flag

在10.14部分中,flag 通常被用于sigaction 函数, 允许应用程序去 请求 中断的系统调用 重新开始。On FreeBSD 8.0, Linux 3.2.0, and Mac OS X 10.6.8 中, 当信号handler 使用signal 函数,被安装进去, 中断系统调用 将 被 restart. 在Solaris 10 中 不是这样。。。。我们自己实现signal 函数就会 避免处理这些不同

10.18 我们尝试提供我们自己的signal 版本,它会自动地restart 中断的系统调用(除了SIGALRM signal) 10.19 我们实现了signal_intr, 它从不restart.

10.3 总结 各个实现下  signal 函数 和 他们的 语义

10.6 Reentrant Functions  重入函数

当一个 信号被捕获并处理,那么 正常的执行序列 被signal handler 临时中断。进程然后 继续执行,但是在 signal handler 中的指令 现在被 执行。但是在signal handler 中,我们不能告诉进程正执行到哪里,何时信号被捕获。如果是..怎么办:一个进程正在它的heap 调用malloc 来分配额外的内存,并且signal handler 也正在使用 malloc 怎么办?

又或者是:进程正调用 一个函数间 如getpwnam(存储结果到一个static 区),并且我们在signal handler 也调用这个函数,会怎么样?

在malloc的例子,进程可能会发生灾难,因为:malloc 通常维护了一个它分配的 一个链表,并且它可能处于 改变list 的中间态。在 getpwnam例子,则 正常程序中调用返回的信息可能会被覆盖(被signal handler 返回的信息)。

UNIX 规范指出 :一些函数 在signal handler 中保证 是调用安全地,这些是可重入的,被叫做UNIX 规范 叫做 async-signal safe的。除了是 可重入的,它们阻塞操作期间 的任何信号。

10.4 列表列出了这些 async-signal safe 函数。大多数没有出现是因为:a: 它们使用了 static data structures, b: 它们调用了malloc, free, c: 它们是 标准I/O 的一部分。 大多数标准I/O 使用 全局 data structures( 以非重入的方式)。 注意尽管 我们从 signal handler 中调用printf, 它也不保证产生 期望的结果, 是由于signal handler 可能中断 来自主程序中的printf调用。

abort

faccessat

linkat

select

socketpair

accept

fchmod

listen

sem_post

stat

access

fchmodat

lseek

send

symlink

aio_error

fchown

lstat

sendmsg

symlinkat

aio_return

fchownat

mkdir

sendto

tcdrain

aio_suspend

fcntl

mkdirat

setgid

tcflow

alarm

fdatasync

mkfifo

setpgid

tcflush

bind

fexecve

mkfifoat

setsid

tcgetattr

cfgetispeed

fork

mknod

setsockopt

tcgetpgrp

cfgetospeed

fstat

mknodat

setuid

tcsendbreak

cfsetispeed

fstatat

open

shutdown

tcsetattr

cfsetospeed

fsync

openat

sigaction

tcsetpgrp

chdir

ftruncate

pause

sigaddset

time

chmod

futimens

pipe

sigdelset

timer_getoverrun

chown

getegid

poll

sigemptyset

timer_gettime

clock_gettime

geteuid

posix_trace_event

sigfillset

timer_settime

close

getgid

pselect

sigismember

times

connect

getgroups

raise

signal

umask

create

getpeername

read

sigpause

uname

dup

getpgrp

readlink

sigpending

unlink

dup2

getpid

readlinkat

sigprocmask

unlinkat

execl

getppid

recv

sigqueue

utime

execle

getsockname

recvfrom

sigset

utimensat

execv

getsockopt

recvmsg

sigsuspend

utimes

execve

getuid

rename

sleep

wait

_Exit

kill

renameat

sockatmark

waitpid

_exit

link

rmdir

socket

write

10.4 Reentrant functions that may be called from a signal handler

当心:即便是我们调用了10.4 中列出的函数,由于每个线程只有一个 errno 变量,并且可能潜在地修改它的值。考虑到一个情况,一个信号signal handler 在main 刚设置完errno后就被调用。如果signal handler 调用read, 例如 ,这个调用可能会改变 errno 的值。

因此,作为一个通常的规则, 当我们在 signal handler 中调用10.4 列表中的函数时,我们应该 save 并 restore errno的值。(注意当心, 信号SIGCHLD, 它的signal handler 通常调用 wait系列之一的函数,所有的wait 函数都可能改变errno)。

例子: 10.5 展示在 signal handler 中 每秒调用 getpwnam 函数 ,使用alarm 函数来生成 SIGALRM signal

10.7 SIGCLD semantics (SIGCLD 含义

1.#include    "apue.h"  
2.#include    <sys/wait.h>  
3.  
4.static void sig_cld(int);  
5.  
6.int  
7.main()  
8.{  
9.    pid_t   pid;  
10.  
11.    if (signal(SIGCLD, sig_cld) == SIG_ERR)  
12.        perror("signal error");  
13.    if ((pid = fork()) < 0) {  
14.        perror("fork error");  
15.    } else if (pid == 0) {      /* child */  
16.        sleep(2);  
17.        _exit(0);  
18.    }  
19.  
20.    pause();    /* parent */  
21.    exit(0);  
22.}  
23.  
24.static void  
25.sig_cld(int signo)  /* interrupts pause() */  
26.{  
27.    pid_t   pid;  
28.    int     status;  
29.  
30.    printf("SIGCLD received\n");  
31.  
32.    if (signal(SIGCLD, sig_cld) == SIG_ERR) /* reestablish handler  为什么要重新定义呢???*/  
33.        perror("signal error");  
34.  
35.    if ((pid = wait(&status)) < 0)       /* fetch child status */  
36.        perror("wait error");  
37.  
38.    printf("pid = %d\n", pid);  
39.}  
40.  
41./** 
42.当用signal对某个信号设定信号处理函数的时候,有些信号的处理函数会被重置,有些则不会!!! 
43.信号处理程序会被重置的信号: 
44.SIGALRM 
45.SIGCHLD信号(SIGCLD) 
46. 
47.信号处理程序不会被重置的信号: 
48.SIG_USR1和SIG_USR2 
49. 
50. *  
51.[xingqiji@localhost signals]$ ./child  
52.SIGCLD received 
53.pid = 105041 
54.[xingqiji@localhost signals]$  
55. *  
56. */   

注意:只有Linux 3.2.0 和 Solaris 10 定义了 SIGCLD, 在其它平台,SIGCLD 是等效于 SIGCHLD。

10.8 Reliable-Signal Terminology and Semantics  可靠的信号术语和 语义

一些术语:

一个signal 被 “generated” 对于一个进程。

当事件引起信号产生 。

事件 可能是 一个 硬件异常,(如 被0 整除),软件情况(如,alarm timer 过期), 一个终端生成信号,或 kill函数 生成。当信号产生,内核通常 在 进程的 table 中设置 一个 flag

我们说 一个 signal 被 “delivered”  给一个进程,这时针对signal的action就会被采取了。

在 生成信号 和  它被投送  之间的时段,被叫做 signal 正在 pending.

一个进程 有 “blocking” 的选项 ,这个选项关系到 信号的投送。

如果一个 信号(被 blocked)   被生成 对于一个进程,并且 如果 此信号的action 要么是

default action 要么是 捕获signal , 则 signal 保持 pending 对此进程,直到进程 要么 unblocks 此信号,要么将 action 改变为 “ignore the signal”。system决定 在当信号被delivered时 做些什么事情;而不是当信号生成 时,就决定对信号做些什么事情。这就决定了/允许 进程 在信号被投递之前,就改变 action.

 sigpending 函数 可以被一个进程调用,用来决定哪个signal 被 blocked and pending.

如果.. 会发生什么:如果一个 信号(blocked) 产生了超过一次,在进程unblocks signals 之前。  发生什么: 大多数的unix 系统, 不会 排队signals (除非他们支持 real-time extensions to POSIX.1)。

替代地,unix 内核会简单地 投递一次信号。

如果 有超过一个的信号 准备投递给 一个进程,POSIX.1 没有指定 信号的 先后被投递的顺序。然而,那些和当前 进程状态相关的信号 应该 先于其它信号。(SIGSEGV 就是一个这样的信号).

每个进程 有 一个 “signal mask” ,它定义了  当前信号blocked 的集合。

“signal mask”  每个bit 对应 相应的一个信号。如果 给定信号的 bit 是 on ,那么这个信号就是 “currently blocked” . 一个进程 可以 检查或改变 他的“signal mask”(通过 sigprocmask, 这我们描述在10.12) .

由于信号的个数 有可能超过integer 的 位数,POSIX.1 定义了一个 数据类型,叫 sigset_t ,它包含了 一个“signal set” .

10.9 kill and raise Functions

kill 发送一个信号 给 进程 或 进程组,raise 允许 一个进程发送 信号给它自己。

#include <signal.h>

        int kill(pid_t pid, int signo);

        int raise(int signo);

                Both return: 0 if OK, -1 on error

调用raise(signo)

相当于kill(getpid(), signo)

pid > 0 信号被 投给  哪个它的进程ID 是”pid”的 进程。

pid == 0 : 发给 那些进程(“all processes”),它们的进程组ID 等于调用者的进程组ID , 并且发送者还要有发送权限。 注意 “all processes” 是 排除了  系统进程set。 对大多数unix 系统, 系统进程set 包括了 kernel 进程和 init (pid 为1)

pid  < 0 :  发给 那些进程(“all processes”), 它们的进程组ID 等于 “pid” 的绝对值,并且发送者有权限去发送。 也是,“all processes” 要排除 系统进程set。 就像前面描述的。

pid == -1 : 信号被发送给所有进程,并且发送者要有权限发送。 像前边说的,排除 那些 系统进程。

对超级用户 来说,它可以发送一个 信号给任何 进程。对于其它用户, 基础的规则是:

发送者的 real 或 effective user ID 必须等于 接收者的 real 或 effective user ID。如果 实现 支持 _POSIX_SAVED_IDS, 则会检查 接收者的 saved set-user-ID (而不是检查他的 effective user ID ). 一个 关于权限检查的 特殊的情况  是:被发送的信号是 SIGCONT,一个进程可以发送 给它 给 其它的进程(这些进程在相同的session 即可)。

signal 数字 0 为 null signal .如果 signo 参数是 0,那么调用kill ,会执行正常的错误检查,但不发送信号。这个技术通常 用来去决定 一个 特定的进程 是否 仍然存在。如果我们发送给进程 null signal 并且 它 不存在, 则kill 返回 -1, 并且信号为 ESRCH。 当心!unix 在一段事件后 会回收进程IDs。 所以使用此方法( 给定的ID) 返回了说存在这个进程,那也不意味者 是 你以为的那个进程。

还有一点要理解就是 测试进程存在性 不是原子性的。在kill返回答案给调用者是,进程可能已经exited。 所以答案是 limited value。

10.10 alarm and Pause Functions

当 timer 过期,SIGALRM signal 信号就会产生。如果我们忽略或 不捕获这个信号,它的默认action 就是去终止进程。

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

Returns: 0 or number of seconds until previously set alarm

每个进程 只有一个 alarm clocks。如果 我们调用alarm,  一个先前注册的alarm clock 还没有过期, 那么就会返回 那个alarm clock 剩余的seconds。之前注册的alarm clock 就会被新的 取代。

pause 函数 挂起调用进程直到 一个信号被捕获

        #include <unistd.h>

        int pause(void);

                        Returns: -1 with errno set to EINTR

例子: 使用alarm 和 pause ,我们可以 让 一个进程 sleep 指定的时间。

sleep1.c

1.#include    <signal.h>  
2.#include    <unistd.h>  
3.  
4.static void  
5.sig_alrm(int signo)  
6.{  
7.    /* nothing to do, just return to wake up the pause */  
8.}  
9.  
10.unsigned int  
11.sleep1(unsigned int seconds)  
12.{  
13.    if (signal(SIGALRM, sig_alrm) == SIG_ERR)  
14.        return(seconds);  
15.    alarm(seconds);     /* start the timer */  
16.    pause();            /* next caught signal wakes us up */  
17.    return(alarm(0));   /* turn off timer, return unslept time */  
18.}  

10.7 Simple incomplete implementation of sleep

说明:这个函数有 3个问题:

1. 如果在调用者之前 已经 有一个 alarm 设置, 那这个alarm 就会被 第一个调用sleep1 (内部调用alarm) 的被擦除。我们可以纠正这点,通过看alarm的返回值。如果这个秒数 小于 参数,那么我们应该wait,直到存在的alarm 过期。如果 先前设置的 alarm 将在我们设置的 后边停止,那么在改函数return 之前,我们应该 重新设置这个 alarm 以让它在未来 产生。

2. 我们已经修改了 SIGALRM 的安排(disposition) 。如果我们写的一个函数是为了让别人调用,那么我们应该: 当调用function 时保存 disposition ,当我们做完时,重新存储它。我们可以 纠正这点 通过 保存从signal 返回的值,并且在函数返回前 重新设置下 disposition.

3. 这里有一个竞争条件: 在第一次调用alarm 和 pause 之间。在以busy的系统上,alarm 过期时,signal handler的调用 比 pause的调用早。如果这个情况发生了,那么调用者就会被永远suspended在pause的调用上(假定一些其它的信号没有被捕获)。

第3个问题 有两种方法解决,一是使用 setjmp,   二:使用 sigprocmask 和 sigsuspend

例子 10.8 使用 setjmp 和 longjmp 来避免之前例子中的问题3。这个例子没有处理1,2问题。

sleep2.c

#include	<setjmp.h>
#include	<signal.h>
#include	<unistd.h>

#include    <stdlib.h>
#include    <stdio.h>

static jmp_buf	env_alrm;

static void
sig_alrm(int signo)
{
	printf("sig_alrm of sleep2 starting\n");
	longjmp(env_alrm, 1);
	printf("sig_alrm of sleep2 finished\n");
}

unsigned int
sleep2(unsigned int seconds)
{
	printf("sleep2 starting\n");
	if (signal(SIGALRM, sig_alrm) == SIG_ERR)
		return(seconds);
	if (setjmp(env_alrm) == 0) {
		alarm(seconds);		/* start the timer */
		pause();			/* next caught signal wakes us up */
	}
	printf("sleep2 finished\n");
	return(alarm(0));		/* turn off timer, return unslept time */
}

说明:

1. sleep2 函数避免了10.7 中出现的  race condition 。 即便 pause 是从来不被执行,当SIGALRM信号 发生的时候,sleep2 也能够 return。

2. 这里,然而有另一个 不易察觉和微妙(subtle)的问题, 就是关于和其它信号的交互。

如果SIGALRM 中断了 一些其它 signal handler ,那么 我们调用 longjmp,我们就会abort 其它的signal handler 。图10.9 展示了这种场景。

SIGINT handler 执行的时间超过5秒。我们只是简单的想让它执行的时间超过 sleep2的参数指定的。整型k 被声明为 volatile 来阻止 编译器优化 废弃掉loop.

10.9 一个程序调用sleep2 , 这个程序 还捕获其它信号。(测试sleep2)

tsleep2.c

#include "apue.h"

unsigned int	sleep2(unsigned int);
static void		sig_int(int);

int
main(void)
{
	unsigned int	unslept;

	if (signal(SIGINT, sig_int) == SIG_ERR)
		err_sys("signal(SIGINT) error");
	unslept = sleep2(5);
	printf("sleep2 returned: %u\n", unslept);
	exit(0);
}

static void
sig_int(int signo)
{
	int				i, j;
	volatile int	k;

	/*
	 * Tune these loops to run for more than 5 seconds
	 * on whatever system this test program is run.
	 */
	printf("\nsig_int starting\n");
	for (i = 0; i < 3000010; i++)
		for (j = 0; j < 40020; j++)
			k += i * j;
	printf("sig_int finished\n");
}


/**
 * 
[xingqiji@work78 signals]$ ./tsleep2 
sleep2 starting
^C
sig_int starting
sig_alrm of sleep2 starting
sleep2 finished
sleep2 returned: 0
[xingqiji@work78 signals]$ 

[xingqiji@work78 signals]$ ./tsleep2 
sleep2 starting
sig_alrm of sleep2 starting
sleep2 finished
sleep2 returned: 0
[xingqiji@work78 signals]$ 
 * 
 */

说明:

sleep1, sleep2 展示了 如果天真地处理signals 就会存在陷阱(pitfalls)。下面的部分展示了围绕这些问题的解决办法,以使我们可以 可靠地处理signals(不会有其它的代码片段干扰)。

例子: 一个常见的alarm 使用,除了实现 sleep 函数,也能 放一个上限时间 到那些可能阻塞的操作上。例如,如果我们有一个 read operation 在设备上,它可能阻塞(block), 我们想要 read 在 一些时间后 超时。这个程序10.10就做这些,  从标准输入上读,并且写到标准输出上。

10.10  调用 read ,带超时 ; alarm 的应用之一:设置超时

read1.c

#include "apue.h"

static void	sig_alrm(int);

int
main(void)
{
	int		n;
	char	line[MAXLINE];

	if (signal(SIGALRM, sig_alrm) == SIG_ERR)
		err_sys("signal(SIGALRM) error");

	alarm(180);
	if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
		err_sys("read error");
	alarm(0);

	write(STDOUT_FILENO, line, n);
	exit(0);
}

static void
sig_alrm(int signo)
{
	/* nothing to do, just return to interrupt the read */
	printf("sig_alrm finished\n");
}

/**
 * 
[xingqiji@bogon signals]$ ./read1
^C
[xingqiji@bogon signals]$ make    //注释掉alarm后
gcc -ansi -I../include -Wall -DLINUX -D_GNU_SOURCE  read1.c -o read1  -L../lib -lapue 
[xingqiji@bogon signals]$ ./read1
read error: Interrupted system call
[xingqiji@bogon signals]$ 

ps aux | grep read  查询到进程ID, 使用 kill -USR1 20784 发送 SIGUSR1 信号,则:
[xingqiji@work78 signals]$ ./read1
用户定义信号 1
[xingqiji@work78 signals]$ 
 * 
 */ 

说明:

这些代码在unix applications 中很常见,但是这个程序有两个问题:

1. 注意10.10 中也存在和10.7 相同的  race condition ; 就是在 第一次调用 alarm 和调用read, 如果内核在两个函数之间 长时间block ,这个时间超过了 alarm period, 则read 可能会永远 block。 大多数这种类型的操作 会使用更长的 alarm period, 例如: 一分钟或更长,使这种 坏情况 不可能发生。无论如何,这确实 是个 race condition。

2. 如果系统调用 自动重新 开始,那么当SIGALRM signal handler 返回时,read就不会被中断。在这种情况下,timeout 不起作用了。

在我的work78上测试显然 不会自动重新 开始。

例子:使用longjmp 重新做之前的例子。

10.11 调用read 带超时,使用longjmp;

read2.c

#include "apue.h"  
#include <setjmp.h>  
  
static void     sig_alrm(int);  
static jmp_buf  env_alrm;  
  
int  
main(void)  
{  
    int     n;  
    char    line[MAXLINE];  
  
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)  
        err_sys("signal(SIGALRM) error");  
    if (setjmp(env_alrm) != 0)  
        err_quit("read timeout");  
  
    alarm(10);  
      
    if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)  
        err_sys("read error");  
    alarm(0);  
  
    write(STDOUT_FILENO, line, n);  
    exit(0);  
}  
  
static void  
sig_alrm(int signo)  
{  
    longjmp(env_alrm, 1);  
}  
  
/** 
 *  
[xingqiji@bogon signals]$ ./read2 
read timeout 
[xingqiji@bogon signals]$  
 *  
 */

说明:

这个版本工作是像期望那样的,不用管 是否系统调用是restart interrupted 系统调用。然而,在与其它 signal handlers 的交互上 仍然有问题。

另一个 可选的做法 是 使用 select 或 poll 函数,

10.11 Signal Sets 信号集

我们需要一个数据类型 来代表 众多的信号- - 这就是  信号集。就像前面提到的,信号的数量在各个系统是不同的,这个数量可能超出了integer 的位数。所以不能用 integer来代表信号集。posix.1 定义了 数据类型 sigset_t  来包含信号集,下面的5个函数就是来维护 信号集的。

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

                All four return: 0 if OK, -1 on error

int sigismember(const sigset_t *set, int signo);

                Returns: 1 if true, 0 if false, -1 on error

所有应用不得不调用 要么 sigemptyset 或者 sigfillset  一次 。

一旦 我们初始化了 一个 signal set, 我们可以添加 并且删除 特定的signal 。函数 sigaddset 添加 一个 signal 给 一个已存在的signal set, sigdelset 从一个 signal set 中移除一个signal。

实现:

如果实现 有信号 的数量 少于 一个 整型的bit, 那么 一个 signal set 就可以实现为 一位代表一个信号的方式。下面的部分,假定 有一个实现 它有 31个signals 用 32bit 整型。则 可以用宏实现如下:

#define sigemptyset(ptr) (*(ptr) = 0)

#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)

10.12 sigaddset, sigdelset, sigismember 的实现

10.12 sigprocmask Function

一个进程的 signal mask 就是 当前对进程blocked 的 signals set. 一个进程可以检查她的 signal mask ,改变它的 signal mask , 或者 两步并做一步走(通过下面的函数)。

#include <signal.h>

int sigprocmask(int how, const sigset_t *restrict set,  sigset_t *restrict oset);

                Returns: 0 if OK, −1 on error

首先, oset 是 非空pointer, how 指明了 当前的signal mask 怎么被修改。SIG_BLOCK 是一个 inclusive-OR 操作,  SIG_SETMASK 是一个 赋值操作。注意:SIGKILL  SIGSTOP 不能被 blocked.

如果set 是个 null 指针, 进程的 signal mask 不会被改变,how 会被忽略。

sigprocmask 函数 被定义 仅仅为 单线程的进程对于多线程的进程有另外的函数

例子:10.14 展示了一个函数,它打印调用进程的 signal mask 中的信号名。

10.14  为进程 打印 signal mask

lib/prmask.c

1.#include "apue.h"  
2.#include <errno.h>  
3.  
4.void  
5.pr_mask(const char *str)  
6.{  
7.    sigset_t    sigset;  
8.    int         errno_save;  
9.  
10.    errno_save = errno;     /* we can be called by signal handlers */  
11.    if (sigprocmask(0, NULL, &sigset) < 0) {  
12.        err_ret("sigprocmask error");  
13.    } else {  
14.        printf("%s", str);  
15.        if (sigismember(&sigset, SIGINT))  
16.            printf(" SIGINT");  
17.        if (sigismember(&sigset, SIGQUIT))  
18.            printf(" SIGQUIT");  
19.        if (sigismember(&sigset, SIGUSR1))  
20.            printf(" SIGUSR1");  
21.        if (sigismember(&sigset, SIGALRM))  
22.            printf(" SIGALRM");  
23.  
24.        /* remaining signals can go here  */  
25.  
26.        printf("\n");  
27.    }  
28.  
29.    errno = errno_save;     /* restore errno */  
}

10.13  sigpending Function

sigpending 返回 调用进程 的 被blocked 的且 pending 的信号集。通过信号集通过 set 参数来返回。

#include <signal.h>

int sigpending(sigset_t *set);

                        Returns: 0 if OK, -1 on error

例子:

10.15 展示了许多信号功能

1.#include "apue.h"  
2.  
3.static void sig_quit(int);  
4.  
5.int  
6.main(void)  
7.{  
8.    sigset_t    newmask, oldmask, pendmask;  
9.  
10.    if (signal(SIGQUIT, sig_quit) == SIG_ERR)  
11.        err_sys("can't catch SIGQUIT");  
12.  
13.    /* 
14.     * Block SIGQUIT and save current signal mask. 
15.     */  
16.    sigemptyset(&newmask);  
17.    sigaddset(&newmask, SIGQUIT);  
18.    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)  
19.        err_sys("SIG_BLOCK error");  
20.  
21.    sleep(5);   /* SIGQUIT here will remain pending */  
22.  
23.    if (sigpending(&pendmask) < 0)  
24.        err_sys("sigpending error");  
25.    if (sigismember(&pendmask, SIGQUIT))  
26.        printf("\nSIGQUIT pending\n");  
27.  
28.    /* 
29.     * Restore signal mask which unblocks SIGQUIT. 
30.     */  
31.    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  
32.        err_sys("SIG_SETMASK error");  
33.    printf("SIGQUIT unblocked\n");  
34.  
35.    sleep(5);   /* SIGQUIT here will terminate with core file */  
36.    exit(0);  
37.}  
38.  
39.static void  
40.sig_quit(int signo)  
41.{  
42.    printf("caught SIGQUIT\n");  
43.    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)  
44.        err_sys("can't reset SIGQUIT");  
45.}  
46.  
47.  
48./** 
49. *  
50.  
51. [xingqiji@bogon signals]$ ./critical 
52.^\ 
53. 
54.SIGQUIT pending 
55.caught SIGQUIT 
56.SIGQUIT unblocked 
57.[xingqiji@bogon signals]$  
58.[xingqiji@bogon signals]$ ./critical 
59.^\ 
60. 
61.SIGQUIT pending 
62.caught SIGQUIT 
63.SIGQUIT unblocked 
64.^\退出(吐核) 
65.[xingqiji@bogon signals]$ ./critical 
66.^\ 
67.^\ 
68.^\ 
69. 
70.SIGQUIT pending 
71.caught SIGQUIT 
72.SIGQUIT unblocked 
73.[xingqiji@bogon signals]$  
74. 
1. [xingqiji@bogon signals]$ ./critical 
2.^\ 
3.^\ 
4.^\ 
5.^\ 
6. 
7.SIGQUIT pending 
8.caught SIGQUIT 
9.SIGQUIT unblocked 
10.^\退出(吐核) 
11.[xingqiji@bogon signals]$  
75.
76. *  
77. *  
78. */ 

有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况是通过阻塞信号实现的

2、信号阻塞和忽略信号的区别

操作系统在信号被进程解除阻塞之前不会将信号传递出去,被阻塞的信号也不会影响进程的行为,信号只是暂时被阻止传递。当进程忽略一个信号时,信号会被传递出去但进程会将信号丢弃。

10.14 sigaction Function

sigaction 函数允许我们去 检查  修改 (或both) 特定信号关联的 action

一旦我们对一个给定的signal 安装一个 action, 这个action 就会保持一直安装状态直到 我们通过调用sigaction 显式地改变

#include <signal.h>

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

                        Returns: 0 if OK, -1 on error

这个函数 使用如下的 结构体:

           struct sigaction {

               void     (*sa_handler)(int);   /* addr of signal handler, or SIG_IGN, or SIG_DFL*/

               void     (*sa_sigaction)(int, siginfo_t *, void *);   

               sigset_t   sa_mask;   /* additional signals to block */

               int        sa_flags;   /* signal options, Figure 10.16 */

               void     (*sa_restorer)(void);

           };

sa_sigaction 是一个额外的信号handler。 当SA_SIGINFO flag 被设置时使用

Option

Linux3.2.0

描述

SA_INTERRUPT

支持

System calls interrupted by this signal are not

automatically restarted

SA_NOCLDSTOP

支持

如果signo 是SIGCHLD,  当child进程停止时不会生成这个信号(job control). .....

SA_NOCLDWAIT

支持

如果signo 是SIGCHLD, 这个选项会阻止系统创建zombie processes. 如果它随之调用wait, 调用进程会阻塞直到所有它的子进程终止并且 返回-1 、errno 被设置为ECHLD

SA_NODEFER

支持

SA_ONSTACK

支持

SA_RESETHAND

支持

对于这个信号会被重置为SIG_DFL, 并且SA_SIGINFO flag 会被清除。

SA_RESTART

支持

被这个信号 中断的 系统调用会 自动 重新开始

SA_SIGINFO

支持

这个选项提供额外的信息给signal handler: 一个指针指向 一个siginfo structure, 一个指针指向进程context 的identifier

10.16  sa_flags 选项

正常地, signal handler 被调用 像:

        void handler(int signo)

但是如果 SA_SIGINFO flag 被设置, signal handler 被调用像如下:

        void handler(int signo, siginfo_t *info, void *context);

siginfo 结构体包含了 为什么signal 被生成的 信息。

struct siginfo {

        int si_signo; /* signal number */

        int si_errno; /* if nonzero, errno value from errno.h */

        int si_code; /* additional info (depends on signal) */

        pid_t si_pid; /* sending process ID */

        uid_t si_uid; /* sending process real user ID */

        void *si_addr;  /* address that caused the fault */

        int si_status; /* exit value or signal number */

        union sigval si_value;  /* application-specific value */

        /* possibly other fields also */

};

sigval union 包含如下的字段

        int sival_int;

        void *sival_ptr;

context 结构体 有如下字段:

ucontext_t *uc_link;          /* pointer to context resumed when */

                                        /* this context returns */

sigset_t uc_sigmask;          /* signals blocked when this context */

                                        /* is active */

stack_t uc_stack;             /* stack used by this context */

mcontext_t uc_mcontext; /* machine-specific representation of */

                                                /* saved context */

uc_stack 字段描述了 当前context 使用的 stack。它包含了至少如下的成员:

void    *ss_sp; /* stack base 或者 pointer*/

size_t   ss_size; /* stack size */

int     ss_flags; /* flags  */

siginfo_t  code value 如下表:10.17

signal

Code

原因

SIGILL

ILL_ILLOPC

illegal opcode

ILL_ILLOPN

illegal operand

ILL_ILLADR

illegal addressing mode

ILL_ILLTRP

illegal trap

ILL_PRVOPC

privileged opcode

ILL_PRVREG

privileged register

ILL_COPROC

coprocessor error

ILL_BADSTK

internal stack error

SIGFPE

FPE_INTDIV

integer divide by zero

FPE_INTOVF

integer overflow

FPE_FLTDIV

floating-point divide by zero

FPE_FLTOVF

floating-point overflow

FPE_FLTUND

floating-point underflow

FPE_FLTRES

floating-point inexact result

FPE_FLTINV

invalid floating-point operation

FPE_FLTSUB

subscript out of range

SIGSEGV

SEGV_MAPERR

address not mapped to object

SEGV_ACCERR

invalid permissions for mapped object

SIGBUS

BUS_ADRALN

invalid address alignment

BUS_ADRERR

nonexistent physical address

BUS_OBJERR

object-specific hardware error

SIGTRAP

TRAP_BRKPT

process breakpoint trap

TRAP_TRACE

process trace trap

SIGCHLD

CLD_EXITED

child has exited

CLD_KILLED

child has terminated abnormally(no core)

CLD_DUMPED

child has terminated abnormally with core

CLD_TRAPPED

traced child has trapped

CLD_STOPPED

child has stopped

CLD_CONTINUED

stopped child has continued

ANY

SI_USER

signal sent by kill

SI_QUEUE

signal sent by sigqueue

SI_TIMER

expiration of a timer set by timer_settime

SI_ASYNCIO

completion of asynchronous I/O request

SI_MESGQ

arrival of a message on message queue(real-time extension)

例子:使用 sigaction 来实现 signal 函数。很多平台都这样做。

1.#include "apue.h"  
2.  
3./* Reliable version of signal(), using POSIX sigaction().  */  
4.Sigfunc *  
5.signal(int signo, Sigfunc *func)  
6.{  
7.    struct sigaction    act, oact;  
8.  
9.    act.sa_handler = func;  
10.    sigemptyset(&act.sa_mask);  
11.    act.sa_flags = 0;  
12.    if (signo == SIGALRM) {  
13.#ifdef  SA_INTERRUPT  
14.        act.sa_flags |= SA_INTERRUPT;  
15.#endif  
16.    } else {  
17.        act.sa_flags |= SA_RESTART;  
18.    }  
19.    if (sigaction(signo, &act, &oact) < 0)  
20.        return(SIG_ERR);  
21.    return(oact.sa_handler);  
22.} 

说明:

注意:我们必须使用 sigemptyset 去初始化sa_mask成员。我们不能保证 act.sa_mask=0 能做同样的事情。

我们 打算 对除了SIGALRM外的信号设置SA_RESTART flag , 这样任何系统调用 如果被这些信号(除了SIGALRM) 打断了 都将能自动 restart。 不想让 SIGALRM  restart 的原因是,我们想对I/O 操作 设置超时(可以回顾下10.10的例子)。

一些老的系统,如 SunOS, 定义了 SA_INTERRUPT flag。 这些系统默认就是  restart, 所以设置了这个标志,就会中断(interrupted) 系统调用。Linux 定义 SA_INTERRUPT flag 是为了兼容那些使用这个flag的应用,但是默认地Linux 不会restart 系统调用。

Unix规范指出:sigaction函数不应该 restart 中断的系统调用(除非SA_RESTART 标志被指定)。Linux 符合这个规范

10.19 展示了signal函数的一个版本,这个版本 试着去 阻止 那些被中断的系统调用 被重新开始(restart).

1.#include "apue.h"  
2.  
3.Sigfunc *  
4.signal_intr(int signo, Sigfunc *func)  
5.{  
6.    struct sigaction    act, oact;  
7.  
8.    act.sa_handler = func;  
9.    sigemptyset(&act.sa_mask);  
10.    act.sa_flags = 0;  
11.#ifdef  SA_INTERRUPT  
12.    act.sa_flags |= SA_INTERRUPT;  
13.#endif  
14.    if (sigaction(signo, &act, &oact) < 0)  
15.        return(SIG_ERR);  
16.    return(oact.sa_handler);  
17.} 

10.15 sigsetjmp  and siglongjmp Functions

在调用longjmp 上有问题:  当一个信号被捕获,进入信号捕获函数内, 当前的信号会自动地被添加到进程 的 signal mask。 如果 我们用longjmp 跳出signal handler, 那么对于进程的signal mask 会产生什么影响?

在 freeBSD8.0 和 Mac OS X 10.6.8, setjmp 和 longjmp save 和 restore signal mask。Linux 3.2.0 和 Solaris 10, 然而 不这样做 (尽管Linux 支持一个选择来支持BSD行为)。FreeBSD和 Mac OS X 提供了函数_setjmp 和_longjmp, 它们 不会 save 和 restore signal mask。

POSIX ,在 signal handler 中应该总是 使用 sigsetjmp 和siglongjmp。

#include <setjmp.h>

        int sigsetjmp(sigjmp_buf env, int savemask);

                        Returns: 0 if called directly, nonzero if returning from a call to siglongjmp

        void siglongjmp(sigjmp_buf env, int val);

10.20 说明了 sigsetjmp 和 siglongjmp 的用法。

        signal/mask.c

1.#include "apue.h"  
2.#include <setjmp.h>  
3.#include <time.h>  
4.  
5.static void                     sig_usr1(int);  
6.static void                     sig_alrm(int);  
7.static sigjmp_buf               jmpbuf;  
8.static volatile sig_atomic_t    canjump;  
9.  
10.int  
11.main(void)  
12.{  
13.    if (signal(SIGUSR1, sig_usr1) == SIG_ERR)  
14.        err_sys("signal(SIGUSR1) error");  
15.    if (signal(SIGALRM, sig_alrm) == SIG_ERR)  
16.        err_sys("signal(SIGALRM) error");  
17.  
18.    pr_mask("starting main: ");     /* {Prog prmask} */  
19.  
20.    if (sigsetjmp(jmpbuf, 1)) {  
21.  
22.        pr_mask("ending main: ");  
23.  
24.        exit(0);  
25.    }  
26.    canjump = 1;    /* now sigsetjmp() is OK */  
27.  
28.    for ( ; ; )  
29.        pause();  
30.}  
31.  
32.static void  
33.sig_usr1(int signo)  
34.{  
35.    time_t  starttime;  
36.  
37.    if (canjump == 0)  
38.        return;     /* unexpected signal, ignore */  
39.  
40.    pr_mask("starting sig_usr1: ");  
41.  
42.    alarm(3);               /* SIGALRM in 3 seconds */  
43.    starttime = time(NULL);  
44.    for ( ; ; )             /* busy wait for 5 seconds */  
45.        if (time(NULL) > starttime + 5)  
46.            break;  
47.  
48.    pr_mask("finishing sig_usr1: ");  
49.  
50.    canjump = 0;  
51.    siglongjmp(jmpbuf, 1);  /* jump back to main, don't return */  
52.}  
53.  
54.static void  
55.sig_alrm(int signo)  
56.{  
57.    pr_mask("in sig_alrm: ");  
58.}  
59.  
60.  
61./** 
62.  
63.[xingqiji@work78 signals]$ ./mask & 
64.[1] 46377 
65.[xingqiji@work78 signals]$ starting main:  
66. 
67.[xingqiji@work78 signals]$ kill -USR1 46377 
68.starting sig_usr1:  SIGUSR1 
69.[xingqiji@work78 signals]$ in sig_alrm:  SIGUSR1 SIGALRM 
70.finishing sig_usr1:  SIGUSR1 
71.ending main:  
72. 
73.[1]+  完成                  ./mask 
74.[xingqiji@work78 signals]$  
75. 
76. */ 

给这个程序 画一个 时序图:

10.21

 10.16 sigsuspend Function

我们知道如何 给一个进程 的 signal mask  使用 block 和 unlock  选择的信号。我们可以使用这个技术去 保护 极重要的代码区 ,这个代码区我们不想让信号来中断。

但是,如果我们想unblock 一个信号并且 然后pause 来等待之前被blocked的信号被投递, 这个情况会发生什么?

sigset_t newmask, oldmask;

sigemptyset(&newmask);

sigaddset(&newmask, SIGINT);

                                                                /* block SIGINT and save current signal mask */

if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)

err_sys("SIG_BLOCK error");

                                                                /* critical region of code */

                                                                /* restore signal mask, which unblocks SIGINT */

if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)

err_sys("SIG_SETMASK error");

                                                                /* window is open */

pause();                                                  /* wait for signal to occur */

                                                                /* continue processing */

如果信号 被 blocked , 这个信号将要被发送 给进程,则信号的投递 将被延迟,直到signal 被 unlocked。 对于这个应用, 这看起来 好像 signal 发生在 unblock 和 pause之间,这么我们有个问题。 任何在这个时间窗口来的 signal 都会 丢失,意味着我们可能再也看不到这个信号, 在这种情况下, pause 将无限制 阻塞。另外一个问题就是 早期的 不可靠的信号。

为了纠正这个问题, 我们需要一个方法 来 去 重新存储 signal mask 并且 放进程去sleep ,这两个动作要是原子性操作。 这个功能可以用 sigsuspend 函数来完成。

#include <signal.h>

int sigsuspend(const sigset_t *sigmask);

                                                                Returns: -1 with errno set to EINTR

例子:正确保护极重要代码区域  以防止信号的影响 的方法如下:

10.22

1.#include "apue.h"  
2.  
3.static void sig_int(int);  
4.  
5.int  
6.main(void)  
7.{  
8.    sigset_t    newmask, oldmask, waitmask;  
9.  
10.    pr_mask("program start: ");  
11.  
12.    if (signal(SIGINT, sig_int) == SIG_ERR)  
13.        err_sys("signal(SIGINT) error");  
14.    sigemptyset(&waitmask);  
15.    sigaddset(&waitmask, SIGUSR1);  
16.    sigemptyset(&newmask);  
17.    sigaddset(&newmask, SIGINT);  
18.  
19.    /* 
20.     * Block SIGINT and save current signal mask. 
21.     */  
22.    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)  
23.        err_sys("SIG_BLOCK error");  
24.  
25.    /* 
26.     * Critical region of code. 
27.     */  
28.    pr_mask("in critical region: ");  
29.  
30.    /* 
31.     * Pause, allowing all signals except SIGUSR1. 
32.     *  用wait 信息号集  替代 new 信号集。  过来 SIGUSR1 信号,阻塞掉,程序继续挂起; 过来其他信号(如SIGINT),则会唤醒。 当函数被信息中断要返回前会 恢复调用之前进程的 signal mask
33.     */  
34.    if (sigsuspend(&waitmask) != -1)  
35.        err_sys("sigsuspend error");  
36.  
37.    pr_mask("after return from sigsuspend: ");  
38.  
39.    /* 
40.     * Reset signal mask which unblocks SIGINT. 
41.     */  
42.    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  
43.        err_sys("SIG_SETMASK error");  
44.  
45.    /* 
46.     * And continue processing ... 
47.     */  
48.    pr_mask("program exit: ");  
49.  
50.    exit(0);  
51.}  
52.  
53.static void  
54.sig_int(int signo)  
55.{  
56.    pr_mask("\nin sig_int: ");  
57.}  
58.  
59./** 
60. * 
61.[xingqiji@work78 signals]$ ./suspend1 
62.program start:  
63.in critical region:  SIGINT 
64.^C 
65.in sig_int:  SIGINT SIGUSR1 
66.after return from sigsuspend:  SIGINT 
67.program exit:  
68.[xingqiji@work78 signals]$  
69. 
70. *  
71. */ 

说明:当我们调用sigsuspend 时 参数waitmask 的作用是 设置 SIGUSR1 到 mask,  给这样的参数目的是:当 signal handler 运行时 ,我们可以 告诉/看出 mask 确实已被改变了。

2.可以看到当 sigsuspend returns, 它(sigsuspend会重新 使用调用前的mask(里边有SIGINT) 去设置进程的mask.

10.23 使用sigsuspend 来 等待signal handler 去设置 一个global 变量。

这个程序:我们 捕获 两个信号 interrupt signal 和 quit signal , 但是我们只想捕获到quit signal 时 去唤醒 main routine.

signals/suspend2.c

#include "apue.h"

volatile sig_atomic_t	quitflag;	/* set nonzero by signal handler */

static void
sig_int(int signo)	/* one signal handler for SIGINT and SIGQUIT */
{
	if (signo == SIGINT)
		printf("\ninterrupt\n");
	else if (signo == SIGQUIT)
		quitflag = 1;	/* set flag for main loop */
}

int
main(void)
{
	sigset_t	newmask, oldmask, zeromask;

	if (signal(SIGINT, sig_int) == SIG_ERR)
		err_sys("signal(SIGINT) error");
	if (signal(SIGQUIT, sig_int) == SIG_ERR)
		err_sys("signal(SIGQUIT) error");

	sigemptyset(&zeromask);
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGQUIT);

	/*
	 * Block SIGQUIT and save current signal mask.
	 */
	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
		err_sys("SIG_BLOCK error");

	while (quitflag == 0)
		sigsuspend(&zeromask);

	/*
	 * SIGQUIT has been caught and is now blocked; do whatever.
	 */
	quitflag = 0;

	/*
	 * Reset signal mask which unblocks SIGQUIT.
	 */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");

	exit(0);
}

/**
[xingqiji@bogon signals]$ ./suspend2
^C
interrupt
^C
interrupt
^\[xingqiji@bogon signals]$ 
[xingqiji@work78 signals]$ 
 * 
 * 
 */ 

10.24 例子:展示 信号 如何来同步 parent 和 child.  

TELL_WAIT, TELL_PARENT, TELL_CHILD, WAIT_PARENT, and WAIT_CHILD 用信号来实现。

lib/tellwait.c

#include "apue.h"

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;

static void
sig_usr(int signo)	/* one signal handler for SIGUSR1 and SIGUSR2 */
{
	sigflag = 1;
}

void
TELL_WAIT(void)
{
	if (signal(SIGUSR1, sig_usr) == SIG_ERR)
		err_sys("signal(SIGUSR1) error");
	if (signal(SIGUSR2, sig_usr) == SIG_ERR)
		err_sys("signal(SIGUSR2) error");
	sigemptyset(&zeromask);
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGUSR1);
	sigaddset(&newmask, SIGUSR2);

	/* Block SIGUSR1 and SIGUSR2, and save current signal mask */
	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
		err_sys("SIG_BLOCK error");
}

void
TELL_PARENT(pid_t pid)
{
	kill(pid, SIGUSR2);		/* tell parent we're done */
}

void
WAIT_PARENT(void)
{
	while (sigflag == 0)
		sigsuspend(&zeromask);	/* and wait for parent */
	sigflag = 0;

	/* Reset signal mask to original value */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");
}

void
TELL_CHILD(pid_t pid)
{
	kill(pid, SIGUSR1);			/* tell child we're done */
}

void
WAIT_CHILD(void)
{
	while (sigflag == 0)
		sigsuspend(&zeromask);	/* and wait for child */
	sigflag = 0;

	/* Reset signal mask to original value */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");
}

说明:

在15.7 中,使用pipe 实现了 这5个函数。

当我们 想 sleep 并且等待着信号的发生,那么sigsuspend 函数就完美的实现这个需求。但是如果是 :正在waitting 时要是我们想 调用其它系统函数,我们该怎么办? 不幸的是这个问题没有解决方案,除非我们使用 多线程threads 并且指定一个独立的线程 去处理信号(参考12.8)。

在没有使用线程的情况下, 我们最好的做法就是:在signal handler set一个全局变量.

例子:

我们 捕获 两个信号(SIGINT, SIGALRM) ,并且 用signal_intr函数 安装 signal handler, 信号会中断  任何的 被阻塞的 慢系统调用( slow system call)。当 从 slow device  read 时会被blocked, 这时信号就很可能发生。(典型地  SIGALRM的 做法就很对 :它阻止了我们 永远地 卡在 读取 输入上)。代码看起来如下:(伪代码)

if (intr_flag) /* flag set by our SIGINT handler */ 
    handle_intr(); 
if (alrm_flag) /* flag set by our SIGALRM handler */ 
    handle_alrm(); 
/* signals occurring in here are lost */ 
while (read( ... ) < 0) { 
    if (errno == EINTR) { 
        if (alrm_flag) 
            handle_alrm(); 
        else if (intr_flag) 
            handle_intr(); 
        } else { 
            /* some other error */ 
        } 
    } else if (n == 0) { 
        /* end of file */ 
    } else { 
        /* process input */ 
    }
}

10.17 abort Function

#include <stdlib.h>

void abort(void);

                                                        This function never returns

这个函数 发送 SIGABRT 信号给 caller 。进程不应该忽略此信号。

ISO C 要求 如果信号被 捕获,signal handler返回, abort 仍然不会返回到它的 caller.

让进程捕获SIGABRT的意图是:允许它允许任何的 cleanup。如果进程没有从这signal handler中终止它自己,POSIX.1 说,当signal handler return ,abort 终止 这个进程。

这个函数的 ISO C规范 留给具体的实现来决定 是否 刷新 output streams、 是否删除临时文件。

POSIX.1 允许 abort实现 在终止进程前对 标准I/O streams 调用 fclose

历史的原因, abort 的实现 对如何处理标准I/O 是不同的。所以为了 编程的严谨性 和提高兼容性, 我们想要 标准I/O 被 flushed, 我们可以在调用abort之前做这些。

例子: POSIX.1 abort 函数的一种实现。

10.25  signals/abort.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void
abort(void)			/* POSIX-style abort() function */
{
	sigset_t			mask;
	struct sigaction	action;

	/* Caller can't ignore SIGABRT, if so reset to default */
	sigaction(SIGABRT, NULL, &action);
	if (action.sa_handler == SIG_IGN) {
		action.sa_handler = SIG_DFL;
		sigaction(SIGABRT, &action, NULL);
	}
	if (action.sa_handler == SIG_DFL)
		fflush(NULL);			/* flush all open stdio streams */

	/* Caller can't block SIGABRT; make sure it's unblocked */
	sigfillset(&mask);
	sigdelset(&mask, SIGABRT);	/* mask has only SIGABRT turned off */
	sigprocmask(SIG_SETMASK, &mask, NULL);
	kill(getpid(), SIGABRT);	/* send the signal */

	/* If we're here, process caught SIGABRT and returned */
	fflush(NULL);				/* flush all open stdio streams 
	这里又flush一次,是由于进程可能已经生成了 更多output, 唯一的情况不需要处理是:
	进程捕获到信号且调用了_exit 或_Exit. 在这种情况下在内存中的任何的没刷新的标准I/O 都会被丢弃。
	*/
	action.sa_handler = SIG_DFL;
	sigaction(SIGABRT, &action, NULL);	/* reset to default */
	sigprocmask(SIG_SETMASK, &mask, NULL);	/* just in case ... */
	kill(getpid(), SIGABRT);				/* and one more time */
	exit(1);	/* this should never be executed ... */
}

说明:fflush 不等效于fclose, 它仅仅 flush  open streams,它不关闭 他们,但是 当进程终止,系统关闭所有的文件。

kill: kill 引起生成信号,并且如果 signal 没有被 block,那么signal 会被投递给进程,之后kill 返回。

10.18 system Function

POSIX.1 要求 system 忽略 SIGINT 和 SIGQUIT  并且 block SIGCHLD.

为什么需要 去担心 signal handling

例子:

使用来自8.13 的 system 版本 调用ed(1)  的问题展示在10.26 中。 ed(1) 这个编辑器是 unix 系统的一部分。我们使用它在这,是因为它是一个交互式程序,它捕获interrupt和quit 信号。如果我们 从shell 中调用 ed 并且 键入 interrupt character, 它捕获 interrupt signal 并且 打印 一个 question mark ( ?号)。 ed程序 也会设置 quit signal的disposition,目的是它被忽略)。

8.22 proc/system.c

#include	<sys/wait.h>
#include	<errno.h>
#include	<unistd.h>

#include <stdio.h>

int
system(const char *cmdstring)	/* version without signal handling */
{
	printf("in proc/system.c\n");
	pid_t	pid;
	int		status;

	if (cmdstring == NULL)
		return(1);		/* always a command processor with UNIX */

	if ((pid = fork()) < 0) {
		status = -1;	/* probably out of processes */
	} else if (pid == 0) {				/* child */
		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
		_exit(127);		/* execl error */
	} else {							/* parent */
		while (waitpid(pid, &status, 0) < 0) {
			if (errno != EINTR) {
				status = -1; /* error other than EINTR from waitpid() */
				break;
			}
		}
	}

	return(status);
}

10.26代码:signals/systest2.c

#include "apue.h"

static void
sig_int(int signo)
{
	printf("caught SIGINT\n");
}

static void
sig_chld(int signo)
{
	printf("caught SIGCHLD\n");
}

int
main(void)
{
	if (signal(SIGINT, sig_int) == SIG_ERR)
		err_sys("signal(SIGINT) error");
	if (signal(SIGCHLD, sig_chld) == SIG_ERR)
		err_sys("signal(SIGCHLD) error");
	if (system("/bin/ed") < 0)              /* 使用了同目录下的 system 函数, 需要拿出铁证来,gdb调试下 */
		err_sys("system() error");
	exit(0);
}


/*

修改makefile  system.o 使用../proc/system.o
[root@bogon signals]# ./systest2 
in proc/system.c
a
here is
.
1,$p
here is
w temp.foo
8
q
caught SIGCHLD
[root@bogon signals]# 

修改makefile system.o 使用 ../signals/sytem.o

[root@bogon signals]# ./systest2 
in siganl/system.c
a
hello,world
.
1,$p
hello,world
w temp.foo
12
^C
?                                 // 与书上结果不一样, "caught SIGINT      and so does the parent process "
q
caught SIGCHLD
[root@bogon signals]# 

[root@bogon signals]# ./systest2 
in siganl/system.c
^C
?
^\
?
a
这是here
 .
.
这是here
1,$p
这是here
w aaa.txt
11
q
caught SIGCHLD
[root@bogon signals]# 


*/

使用 ../proc/system.c ,调用10.26程序,我么get

$ ./a.out

a                                                          append text to the editor’s buffer ;a 追加文本

Here is one line of text

.                                                      period on a line by itself stops append mode ;  停止追加模式

1,$p                               print fifirst through last lines of buffer to see what’s there 印最后一行

Here is one line of text

w temp.foo                                  write the buffer to a fifile buffer中内容写到文件中

25                                                  editor says it wrote 25 bytes 编辑器告诉我们它写了25个字节

q                                                  and leave the editor 退出编辑器

caught SIGCHLD

当 编辑器终止,system 发送 SIGCHLD 给 parent( a.out) 进程。

当system函数正在执行,在parent 中的SIGCHLD信号投递 应该被blocked。(这是POSIX.1的要求)。如果不被阻塞,则 当 由system创建的child 结束时, 它(child)将欺骗 system的调用者“ 让调用者认为是它的 一个child 终止了。调用者将使用 wait 类 函数 去获取 the child的终止状态。因此 “不阻塞这种做法”(不好的) 会 阻止system 函数 return  the child的状态。

回想起9.6部分,键入 interrupt character 会引起 interrupt signal 被发送到 在前台进程组重的所有进程。10.27 展示了 当the editor 正在running 时的进程布局。

由于system 的调用者 放弃了 对正在执行程序(ed)的控制,控制指的是 waitting for ed 去结束。所以system的调用者 不应该 接受 这两个 terminal-generated signals.  出于这个原因,POSIX.1 规范说 system 函数应该 ignore 这两个信号(当waitting the command去完成时)。

例子:

10.28 展示了 一个 带必要的signal handling 的 system 系统函数版本。

10.28  正确的 POSIX.1标准的 system函数实现。

1.#include    <sys/wait.h>  
2.#include    <errno.h>  
3.#include    <signal.h>  
4.#include    <unistd.h>  
5.  
6.int  
7.system(const char *cmdstring)   /* with appropriate signal handling */  
8.{  
9.    pid_t               pid;  
10.    int                 status;  
11.    struct sigaction    ignore, saveintr, savequit;  
12.    sigset_t            chldmask, savemask;  
13.  
14.    if (cmdstring == NULL)  
15.        return(1);      /* always a command processor with UNIX */  
16.  
17.    ignore.sa_handler = SIG_IGN;    /* ignore SIGINT and SIGQUIT */  
18.    sigemptyset(&ignore.sa_mask);  
19.    ignore.sa_flags = 0;  
20.    if (sigaction(SIGINT, &ignore, &saveintr) < 0)  
21.        return(-1);  
22.    if (sigaction(SIGQUIT, &ignore, &savequit) < 0)  
23.        return(-1);  
24.    sigemptyset(&chldmask);         /* now block SIGCHLD */  
25.    sigaddset(&chldmask, SIGCHLD);  
26.    if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)  
27.        return(-1);  
28.  /* 在fork 之前 就应该忽略掉SIGINT和 SIGQUIT  放在fork后 会出现race condition*/
29.    if ((pid = fork()) < 0) {  
30.        status = -1;    /* probably out of processes */  
31.    } else if (pid == 0) {          /* child */  
32.        /* restore previous signal actions & reset signal mask */  
33.        sigaction(SIGINT, &saveintr, NULL);  
34.        sigaction(SIGQUIT, &savequit, NULL);  
35.        sigprocmask(SIG_SETMASK, &savemask, NULL);  
36.  
37.        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);   //这里为啥不 直接 execl(cmdstring) ?  
38.        _exit(127);     /* exec error */  
39.    } else {                        /* parent */  
40.        while (waitpid(pid, &status, 0) < 0)  
41.            if (errno != EINTR) {  
42.                status = -1; /* error other than EINTR from waitpid() */  
43.                break;  
44.            }  
45.    }  
46.  
47.    /* restore previous signal actions & reset signal mask */  
48.    if (sigaction(SIGINT, &saveintr, NULL) < 0)  
49.        return(-1);  
50.    if (sigaction(SIGQUIT, &savequit, NULL) < 0)  
51.        return(-1);  
52.    if (sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)    /* 恢复,不再block  SIGCHLD 信号 */  
53.        return(-1);  
54.  
55.    return(status);  
} 

 

来自system 的return value:

从system 返回的值,是 the shell 的终止状态,这不总是command string 的终止状态。

10.19 sleep, nanosleep, and clock_nanosleep Functions

#include <unistd.h>

unsigned int sleep(unsigned int seconds);

                                                                        Returns: 0 or number of unslept seconds

这个函数 引起调用进程 挂起(suspended) 直到 

  1. 被seconds 指定的 时间 到了。
  2. 一个 signal 被 进程 捕获 并且 signal handler 返回了

第一种情况 return 值 是 0; 第二种情况: return 值 是 未sleep的秒数

FreeBSD 8.0 ,Linux 3.2.0, Mac OS X 10.6.8 实现 sleep ,通过使用 nanosleep 函数。

10.29

lib/sleep.c  可靠的sleep 实现

1.#include "apue.h"  
2.  
3.static void  
4.sig_alrm(int signo)  
5.{  
6.    /* nothing to do, just returning wakes up sigsuspend() */  
7.}  
8.  
9.unsigned int  
10.sleep(unsigned int seconds)  
11.{  
12.    struct sigaction    newact, oldact;  
13.    sigset_t            newmask, oldmask, suspmask;  
14.    unsigned int        unslept;  
15.  
16.    /* set our handler, save previous information */  
17.    newact.sa_handler = sig_alrm;  
18.    sigemptyset(&newact.sa_mask);  
19.    newact.sa_flags = 0;  
20.    sigaction(SIGALRM, &newact, &oldact);   /* 设置SIGALRM 信号的 handler */  
21.  
22.    /* block SIGALRM and save current signal mask */  
23.    sigemptyset(&newmask);  
24.    sigaddset(&newmask, SIGALRM);  
25.    sigprocmask(SIG_BLOCK, &newmask, &oldmask);  
26.  
27.    alarm(seconds);  
28.    suspmask = oldmask;  
29.  
30.    /* make sure SIGALRM isn't blocked */  
31.    sigdelset(&suspmask, SIGALRM);  
32.  
33.    /* wait for any signal to be caught */  
34.    sigsuspend(&suspmask);  
35.  
36.    /* some signal has been caught, SIGALRM is now blocked */  
37.  
38.    unslept = alarm(0);  
39.  
40.    /* reset previous action */  
41.    sigaction(SIGALRM, &oldact, NULL);  
42.  
43.    /* reset signal mask, which unblocks SIGALRM */  
44.    sigprocmask(SIG_SETMASK, &oldmask, NULL);  
45.    return(unslept);  
} 

#include <time.h>

int nanosleep(const struct timespec *reqtp, struct timespec *remtp);

                                                Returns: 0 if slept for requested time or -1 on error

因为nanosleep 函数不涉及 任何信号的生成,我们可以使用它,不用担心和其他函数的交互。

随着 多系统clocks 的介绍(回想下6.10),我们需要一种方式 来挂起 调用线程(使用一个 相对于 特定clock 的 delay time )。clock_nanosleep 函数 提供给我们 这样的能力。        

#include <time.h>

int clock_nanosleep(clockid_t clock_id, int flflags, const struct timespec *reqtp, struct timespec *remtp);

                                Returns: 0 if slept for requested time or error number on failure

clock_id 参数 指定了 要延迟的 clock .

flag 通常去控制 是否 延迟是  绝对的还是 相对的。TIMER_ABSTIME 。

然而 当使用 一个绝对时间,remtp 参数 是没用的,因为它不被需要;

一个relative sleep time 可能导致 sleep的比 期望的 更长的时间。

使用 绝对时间,可以提高 精度(precision)

10.20 sigqueue Function

大多数的unix system 不支持queue signals.  对于POSIX.1 的real-time extension ,一些系统开始支持 queueing signals . SUSv4. 已经把real-time extension 移到了base specification.

一般地, 一个信号 使用 一个bit 信息: signal它自己。另外,queueing signals , 这些扩张允许应用 在投递时 传递更多的信息。这些信息被 嵌入到 siginfo structure. 根据系统提供的信息,应用可以 传递 一个 integer 或  pointer (它 执行一个buffer,这个buffer 包含了更多的信息)。

为了使用 queued signal 我们不得不做如下的事:

  1. 指定 SA_SIGINFO flag ,当我们 使用sigaction 函数 install 一个 signal handler  
  2. 提供一个 signal handler 给 sigaction structure的sa_sigaction 成员(而不是 sa_handler).

        实现可能允许我们去使用 sa_handler字段,但是我们不能够 通过sigqueue 函数获取额外的信息。

 3. 使用 sigqueue 函数 去 send signals

#include <signal.h>

int sigqueue(pid_t pid, int signo, const union sigval value)

                                                                                        Returns: 0 if OK, −1 on error

sigqueue 函数与kill 函数很相似。

value 参数 ,那么是 integer 要么是 一个指针(指向signal handler)

signals 不可能无限地来排队。回忆下: SIGQUEUE_MAX , 当限制达到了,sigqueue 可能失败,且 设置errno 为EAGAIN.

10.21 Job-Control Signals

六个作业控制信号

SIGCHLD  child进程 已经stopped 或 terminated

SIGCONT 继续进程 (如果stopped)

SIGSTOP stop信号  不可能被捕获 或忽略

SIGTSTP 交互式 stop signal

SIGTTIN  后台进程组的成员 从控制终端 读

SIGTTOU  后台进程组的成员 写 到 控制终端

除了SIGCHLD, 大多数应用程序不会处理这些信号:交互式shells 通常做一些事情来处理他们。

一个例外 是 这个进程正管理着 终端-- 例如  vi(1) 编辑器。 它需要去知道 何时user 想去 挂起它 以致 它 可以 重新存储 终端的state

例子: 10.31 例子 演示了 当一个进程处理 job control 是 通常的 代码序列。

signals/sigtstp.c  

10.31 如何处理 SIGTSTP

#include "apue.h"  
  
#define BUFFSIZE    1024  
  
static void  
sig_tstp(int signo) /* signal handler for SIGTSTP */  
{  
    sigset_t    mask;  
  
    /* ... move cursor to lower left corner, reset tty mode ... */  
    printf("in sig_tstp\n");  
  
    /* 
     * Unblock SIGTSTP, since it's blocked while we're handling it. 
     */  
    sigemptyset(&mask);  
    sigaddset(&mask, SIGTSTP);  
    sigprocmask(SIG_UNBLOCK, &mask, NULL);  
  
    signal(SIGTSTP, SIG_DFL);   /* reset disposition to default */  
  
    kill(getpid(), SIGTSTP);    /* and send the signal to ourself */  
  
    /* we won't return from the kill until we're continued */  
  
    signal(SIGTSTP, sig_tstp);  /* reestablish signal handler */  
  
    /* ... reset tty mode, redraw screen ... */  
}  
  
int  
main(void)  
{  
    int     n;  
    char    buf[BUFFSIZE];  
  
    /* 
     * Only catch SIGTSTP if we're running with a job-control shell. 
     */  
    if (signal(SIGTSTP, SIG_IGN) == SIG_DFL)  
        signal(SIGTSTP, sig_tstp);  
  
    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)  
        if (write(STDOUT_FILENO, buf, n) != n)  
            err_sys("write error");  
  
    if (n < 0)  
        err_sys("read error");  
  
    exit(0);  
}  
  
/** 
 *  
 
 [xingqiji@bogon signals]$ ./sigtstp  
^Zin sig_tstp 
 
[1]+  已停止               ./sigtstp 
 
[xingqiji@bogon signals]$ ps aux | grep sigtstp 
xingqiji  69899  0.0  0.0   4228   352 pts/1    T    17:33   0:00 ./sigtstp 
xingqiji  70617  0.0  0.0 112832   988 pts/1    S+   17:46   0:00 grep --color=auto sigtstp 
[xingqiji@bogon signals]$ jobs 
[1]+  已停止               ./sigtstp 
[xingqiji@bogon signals]$ fg 1 
./sigtstp 
nihao 
nihao 
^C 
[xingqiji@bogon signals]$  
 
[xingqiji@bogon signals]$ ./sigtstp  
^Zin sig_tstp 
 
[1]+  已停止               ./sigtstp 
[xingqiji@bogon signals]$ jobs 
[1]+  已停止               ./sigtstp 
[xingqiji@bogon signals]$ fg 1 
./sigtstp 
nihaoya 
nihaoya 
^Zin sig_tstp 
 
[1]+  已停止               ./sigtstp 
[xingqiji@bogon signals]$ jobs 
 *  
 */ 

键入 suspend character (control + Z), 进程s  收到SIGTSTP 信号

当10.31的程序开始,

仅当signal的 disposition 是SIG_DFL时,才去安排着去捕获SIGTSTP signal 。这样写的原因是,当是不支持作业控制的shell 执行时,这个signal 的disposition 会被设置为SIG_IGN。

init 设置 3个信号(SIGTSTP, SIGTTIN, and SIGTTOU) 到 SIG_IGN。这种disposition 会被所有的login shells 继承。 仅 job-control shell 应该 重置这3个信号 为 SIG_DFL。

10.22 Signal Names and Numbers

在这个部分,我们描述 signal numbers 和 names 是如何map。

Linux3.2.0, 和 Mac OS X 10.6.8 提供了array  :

extern char *sys_siglist[];

数组的索引是 signal number, 给一个指针,指向signal的字符串name

为了打印 signal number 对应的 字符串,可以使用 psignal 函数

#include <signal.h>

void psignal(int signo, const char *msg);

如果你有一个 来自 signal handler 的 siginfo structure ,你可以用 psiginfo 函数打印 signal 信息。这个函数类似perror(1.7部分)

#include <signal.h>

void psiginfo(const siginfo_t *info, const char *msg);

它的操作 和 psignal 函数很类似,尽管这个函数 访问了更多信息(不仅是signal number), 实际上不同的平台会有额外的信息被打印。

如果你仅需要 signal 的字符描述,且没必要去写到standard error( 你可能想写这些到一个log file) 你可以使用 strsignal 函数。这个函数 类似于 strerror

#include <string.h>

char *strsignal(int signo);

                                                        Returns: a pointer to a string describing the signal

这个函数类似strerror (1.7部分)

10.23 Summary

;