计算机系统:异常控制流

从给处理器加电开始,直到你断电为止,程序计数器假设一个值的序列

a_{0},a_{1},\cdots ,a_{n-1}

其中,每个a_{k}是某个相应的指令I_{k}的地址。每次从a_{k}a_{k+1}的过渡称为控制转移(control transfer)。这样的控制转移序列叫做处理器的控制流(flow of control或control flow)。

最简单的一种控制是一个“平滑的”序列,其中每个I_{k}I_{k+1}在内存中都是相邻的。这种平滑流的突变通常是由诸如跳转、调用和返回这样一些程序指令造成的。我们把这些突变称为异常控制流(exceptional control flow,ECF)。

 

异常


异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。

异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。

异常刨析图:

在图中,当处理器状态中发生一个重要变化时,处理器正在执行某个当前指令I_{curr}。在处理器中,状态被编码为不同的位和信号。状态变化称为事件(event)。事件可能和当前指令的执行直接相关。比如,发生虚拟内存缺页、算术溢出,或者一条指令试图除以零。另一方面事件也可能和当前指令的执行没有关系。比如,一个系统定时器产生信号或者一个I/O请求完成。

在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序(exception handler))。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下3中情况中的一种:

  • 处理程序将控制返回给当前指令I_{curr},即当事件发生时正在执行的指令。
  • 处理程序将控制返回给I_{next},如果没有发生异常将会执行的下一条指令。
  • 处理程序终止被中断的程序。

异常处理

系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号(exception number)。其中一些号码是由处理器的设计者分配的,其它号码是由操作系统内核(操作系统常驻内存的部分)的设计者分配的。

在系统启动时,操作系统分配和初始化一张称为异常表的跳转表,使得表目k包含异常k的处理程序的地址。

在运行时,处理器检测到发生一个事件,并且确定了相应的异常号k。随后,处理器触发异常,方法是执行间接过程调用,通过异常表的表目k,转到相应的处理程序。

异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常基址寄存器(exception table base register)的特殊CPU寄存器里。

异常的类别

  • 中断(interrupt):中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序常常称为中断处理程序(interrupt handler)。总是返回到下一条指令。
  • 陷阱和系统调用(trap):陷阱是有意的异常,是执行一条之后零的结果。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。总是返回到下一条指令。
  • 故障(fault):故障是由错误情况引起的,它可能能够被故障处理程序修正。可能返回到当前指令。
  • 终止(abort):终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。不会返回。

 

进程


异常是允许操作系统内核提供进程(process)概念的基本构造块,进程是计算机科学中最深刻、最成功的概念之一。

进程的经典定义就是一个执行中程序的实例。

进程提供给应用程序的关键抽象:

  • 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占的使用处理器。
  • 一个私有的地址空间,它提供一个假象,好像我们的程序都炸的使用内存系统。

逻辑控制流

即使在系统中通常有许多其它程序在运行,进程也可以向每个程序提供一种假象,好像它在独占的使用处理器。用调试器单步执行程序,我们会看到一系列的程序计数器的值,这些值唯一的对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称逻辑流。

并发流

一个逻辑流的执行在时间上与另一个流重叠,称为并发流(concurrent flow),这两个流被称为并发的运行。

多个流并发的执行的一般现象被称为并发(concurrent)。一个进程和其它进程轮流运行的概念称为多任务(multitasking)。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)。因此,多任务也叫做时间分片(time slicing)。

注意:并发流的思想与流运行的处理器核数或者计算机数无关。如果两个流在时间上重叠,那么它们就是并发的,即使它们是运行在同一个处理器上。

并行流是并发流的一个真子集。如果两个流并发的运行在不同的处理器核或者计算机上,那么我们称它们位并行流(parallel flow),它们并行的运行(running in parallel),且并行的执行(parallel execution)。

私有地址空间

进程也为每个程序提供一种假象,好像它独占地使用系统地址空间。在一台n位地址的机器上,地址空间是2^{n}个可能地址的集合,0,1,\cdots ,2^{n}-1。进程为每个程序提供它自己的私有地址空间。一般而言,和这个空间中某个地址相关联的那个内存字节是不能被其它继承读或者写的,从这个意义上说,这个地址空间是私有的。

x86-64 Linux进程的地址空间组织结构:

计算机系统:异常控制流_第1张图片

用户模式和内核模式

为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。

处理器通常是用某个寄存器中的一个模式位(mode bit)来提供这种功能的,该寄存器描述了进程当前享有的特权。

当设置了模式位时,进程就运行在内核模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。

没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,比如停止处理器、改变模式位,或者发起一个I/O操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。任何这样的尝试都会导致致命的保护故障。反之,用户程序必须通过系统调用接口间接的访问内核代码和数据。

运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法时通过诸如中断、故障或者陷入系统调用这样的异常。当异常发生时,控制转递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式改回到用户模式。

Linux提供了一种聪明的机制,叫做/proc文件系统,它允许用户模式进程访问内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。比如,你可以使用/proc文件系统找出一般的系统属性,比如CPU类型(/proc/cpuinfo),或者某个特殊的进程使用的内存段(/proc//maps)。2.6版本的Linux内核引入/sys文件系统,它输出关于系统总线和设备的额外的底层信息。

上下文切换

操作系统内核使用一种称为上下文切换(context switch)的较高层形式的异常控制流来实现多任务。

内核为每个进程维持一个上下文(context)。

上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

在进程执行的某些时刻,内核可以决定抢占当前进程,并从新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler)的代码处理的。

上下文切换工作:

  1. 保存当前进程的上下文。
  2. 恢复某个先前被抢占的进程被保存的上下文。
  3. 将控制传递给这个新恢复的进程。

 

进程控制


Unix提供了大量从C程序中操作进程的系统调用。下面将描述这些重要的函数。

获取进程ID

每个进程都有一个唯一的整数(非零)进程ID(PID)。

getpid函数返回调用进程的PID。

getppid函数返回它的父进程的PID(创建调用进程的进程)。

#include 
#include 

pid_t getpid(void);
pid_t getppid(void);

getpid和getppid函数返回一个类型为pid_t的整数值,在Linux系统上它在types.h中被定义为int。

创建和终止进程

从程序员的角度,我们可以认为进程总是处于下面三种状态之一:

  • 运行。进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
  • 停止。进程的执行被挂起(suspended),且不会被调度。当收到SIGSTOP\SIGTSTP\SIGTTIN或者SIGTTOU信号时,进程就停止,并且保持停止直到它收到一个SIGCONT信号,在这个时刻,进程再次开始运行。
  • 终止。进程永远的停止了。进程会因为三种原因终止:1.收到一个信号,该信号的默认行为是终止进程,2.从主程序返回,3.调用exit函数。

创建新进程
父进程通过调用fork函数创建一个新的运行的子进程。

#include 
#include 

pid_t fork(void);

fork函数创建的子进程特点:

  • 调用一次,返回两次。fork函数被父进程调用一次,但是却返回两次(一次是返回到父进程(返回子进程的PID),一次是返回到新创建的子进程(返回0,因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨进程是在父进程还是在子进程中执行)。)。
  • 并发执行。父进程和子进程是并发运行的独立进程。
  • 相同但是独立的地址空间。父进程和子进程的地址空间都是相同的。每个进程由相同的用户栈、相同的本地变量值、相同的堆、相同的全局变量,以及相同的代码。然而,父进程和子进程都是独立的进程,它们都有自己的私有地址空间。(子进程得到的是与父进程用户级地址空间相同的一份副本)
  • 共享文件。子进程会继承父进程打开的所有文件。

终止进程

exit函数以status退出状态来终止进程(另一种设置退出状态的方法是从主程序中返回一个整数值)。

#include 

void exit(int status);

回收子进程

当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。

当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。

一个终止了但还未被回收的进程称为僵死进程(zombie)。

如果一个父进程终止了,内核会安排init进程成为这个父进程的子进程的父进程(换种说法,一个父进程终止了,内核就会安排init称为这个父进程下的子进程的养父)。init进程的PID为1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核就会安排init进程去回收它们。不过,长时间运行的程序总是应该及时回收它们的僵死子进程的,因为即使僵死进程没有运行,也是会消耗系统的内存资源的。

waitpid函数

一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。

#include 
#include 

pid_t waitpid(pid_t pid, int *statusp, int options);

在默认情况下(当options=0时),waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止。如果等待集合中的一个进程中在刚调用的时刻就已经终止了,那么waitpid就立即返回。在这两种情况中,waitpid返回导致waitpid返回的已终止子进程的PID。此时,已终止的子进程已经被回收,内核会从系统中删除掉它的所有痕迹。

pid:pid用来确定等待集合的成员。

  • 如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid。
  • 如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。

*statusp:检查已回收子进程的退出状态。如果statusp参数是非空的,那么waitpid就会在status中放上关于导致返回的子进程的状态信息,status是statusp指向的值。wait.h头文件定义了解释status参数的几个宏:

  • WIFEXITED(status):如果子进程通过调用exit或者一个返回(return)正常终止,就返回真。
  • WEXITSTATUS(status):返回一个正常终止的子进程的退出状态。只有在WIFEXITED()返回为真时,才定义这个状态。
  • WIFSIGNALED(status):如果子进程是因为一个未被捕获的信号终止的,那么就返回真。
  • WTERMSIG(status):返回导致子进程终止的信号的编号。只有在WIFSIGNALED()返回为真时,才定义这个状态。
  • WIFSTOPPED(status):如果引起返回的子进程当前是停止的,那么就返回真。
  • WSTOPSIG(status):返回引起子进程停止的信号的编号。只有在WIFSTOPPED()返回为真时,才定义这个状态。
  • WIFCONTINUED(status):如果子进程收到SIGCONT信号重新启动,则返回真。

options:修改默认行为。

  • WNOHANG:如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。默认的行为时挂起调用进程,直到子进程终止。
  • WUNTRACED:挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的PID为导致返回的已终止或被停止子进程的PID。默认的行为是只返回已终止的子进程。
  • WCONTINUED:挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到SIGCONT信号重新开始执行。
  • WNOHANG | WUNTRACED:立即返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。

错误条件:如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR。

wait函数

wait函数是waitpid函数的简单版本。

#include 
#include 

pid_t wait(int *statusp);

调用wait(&status)等价于调用waitpid(-1,&status,0)。

让进程休眠

sleep函数

sleep函数将一个进程挂起一段指定的时间。

#include 

unsigned int sleep(unsigned int secs);

如果请求的时间量已经到了,sleep返回0,否则返回还剩下的要休眠的秒数。后一种情况是可能的,如果因为sleep函数被一个信号中断而过早的返回。

pause函数

pause函数让调用函数休眠,直到该进程收到一个信号

#include 

int pause(void);

加载并运行程序

execve函数中在当前进程的上下文中加载并运行一个新程序。

#include 

int execve(const char *filename, const char *argv[], const char *envp[]);

execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并从不返回。

 

信号


Linux信号,它允许进程和内核中断其它进程。

一个信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。

序号 名称 默认行为 相应事件
1 SIGHUP 终止 终端线挂断

2

SIGINT 终止 来自键盘的中断
3 SIGOUIT 终止 来自键盘的退出
4 SIGILL 终止 非法指令
5 SIGTRAP 终止并转储内存 跟踪陷阱
6 SIGABRT 终止并转储内存 来自abort函数的终止信号
7 SIGBUS 终止 总线错误
8 SIGFPE 终止并转储内存 浮点异常
9 SIGKILL 终止 杀死程序
10 SIGUSR1 终止 用户定义的信号1
11 SIGSEGV 终止并转储内存 无效的内存引用(段故障)
12 SIGUSR2 终止 用户定义的信号2
13 SIGPIPE 终止 向一个没有读用户的管道做写操作
14 SIGALRM 终止 来自alarm函数的定时器信号
15 SIGTERM 终止 软件终止信号
16 SIGSTKFLT 终止 协处理器上的栈故障
17 SIGCHLD 忽略 一个子进程停止或者终止
18 SIGCONT 忽略 继续进程如果该进程停止
19 SIGSTOP 停止直到下一个SIGCONT 不是来自终端的停止信号
20 SIGTSTP 停止直到下一个SIGCONT 来自终端的停止信号
21 SIGTTIN 停止直到下一个SIGCONT 后台进程从终端读
22 SIGTTOU 停止直到下一个SIGCONT 后台进程向终端写
23 SIGURG 忽略 套接字上的紧急情况
24 SIGXCPU 终止 CPU时间限制超出
25 SIGXFSZ 终止 文件大小限制超出
26 SIGVTALRM 终止 虚拟定时器期满
27 SIGPROF 终止 剖析定时器期满
28 SIGWINCH 忽略 窗口大小变化
29 SIGIO 终止 在某个描述上可执行I/O操作
30 SIGPWR 终止 电源故障

信号术语

传送一个信号到目的进程是由两个不同步骤组成的:

  • 发送信号。内核通过更新目的进程上下文中的某个状态,发送(传递)一个信号给目的进程。发送信号可以有如下两种原因:1.内核检测到一个系统事件。2.一个进程调用了kill函数,显示的要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
  • 接收信号。当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。

待处理信号

一个发出而没有被接收的信号叫做待处理信号(pending signal)。

在任何时刻,一种类型至多只会有一个待处理信号。

如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待。它们只是简单的被丢弃。

一个进程可以有选择性的阻塞接收某种信号。当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。

一个待处理信号最多只能被接收一次。内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护着被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收了一个类型为k的信号,内核而就会清除pending中的第k位。

发送信号

Unix系统提供了大量向进程发送信号的机制。所有这个机制都是基于进程组(process group)这个概念的。

进程组

每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。

一个子进程和它的父进程同属于一个进程组。

getpgrp函数返回当前进程的进程组ID:

#include 

pid_t getpgrp(void); //返回:调用进程的进程组ID。

setpgid函数改变自己或者其它进程的进程组:

#include 

int setpgid(pid_t pid, pid_t pgid); //返回:若成功则为0,若失败则为-1。

用/bin/kill程序发送信号

/bin/kill程序可以向另外的进程发送任意的信号。比如,命令

linux> /bin/kill -9 15213

发送信号9(SIGKILL)给进程15213。一个为负的PID会导致信号被发送到进程组PID中的每个进程。

从键盘发送信号

Unix shell使用作业(job)这个抽象概念来表示为对一条命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和0个或多个后台作业。比如,键如

linux> ls / sort

会创建一个由两个进程组成的前台作业,这两个进程是通过Unix管道连接起来的:一个进程运行ls程序,另一个运行sort程序。shell为每个作业创建一个独立的进程组。进程组ID通常取自作业中父进程中的一个。

在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程组中的每个进程。默认情况下,结果是终止前台作业。类似的,输入Ctrl+Z会发送一个SIGTSTP信号到前台进程组中的每个进程。默认情况下,结果是停止(挂起)前台作业。

用kill函数发送信号

进程通过调用kill函数发送信号给其它进程(包括它们自己)。

#include 
#include 

int kill(pid_t pid, int sig); //返回:若成功则为0,若错误则为-1。

如果pid大于零,那么kill函数发送信号号码sig给进程pid。如果pid等于零,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己。如果pid小于零,kill发送信号sig给进程组|pid|(pid的绝对值)中的每个进程。 

用alarm函数发送信号

进程可以通过调用alarm函数向它自己发送SIGALRM信号。

#include 

unsigned int alarm(unsigned int secs); //返回:前一次闹钟剩余的秒数,若以前灭有设定闹钟,则为0。

alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程。如果secs是零,那么不会调度安排新的闹钟(alarm)。在任何情况下对alarm的调用都将取消任何待处理的(pending)闹钟,并且返回任何待处理的闹钟在被发送钱还剩下的秒数(如果这次对alarm的调用没有取消它的话);如果没有任何待处理的闹钟,就返回零。

接收信号

当内核把进程p从内核模式切换到用户模式时(例如,从系统调用返回或是完成了一次上下文切换),它会检查进程p的未被阻塞的待处理信号的集合(pending &~blocked)。如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中的下一条指令。然而,如果集合是非空的,那么内核选择集合中的某个信号k(通常是最小的k),并且强制p接收信号k。收到这个信号会触发进程采取某种行为。一旦进程完成了这个行为,那么控制就传递回p的逻辑控制流中的下一条指令。每个信号类型都有一个预定义的默认行为,是下面中的一种:

  • 进程终止。
  • 进程终止并转储内存。
  • 进程停电(挂起)直到被SIGCONT信号重启。
  • 进程忽略该信号。
#include 

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler); //返回:若成功则为指向前次处理程序的指针,若出错则为SIG_ERR(不设置errno)。

signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:

  • 如果handler是SIG_IGN,那么忽略类型为signum的信号。
  • 如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为。
  • 否则,handler就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序(installing the handler)。调用信号处理程序被称为捕获信号。执行信号处理程序被称为处理信号。

当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序,一个整数参数被设置为k。这个参数允许同一个处理函数捕获不同类型的信号。

当处理程序执行它的return语句时,控制(通常)传递回控制流中进程被信号接收终端位置处的指令。我们说”通常“是因为在某些系统中,被中断的系统调用会立即返回一个错误。

阻塞和解除阻塞信号

Linux提供阻塞信号的隐式和显式的机制:

  • 隐式阻塞机制。内核默认阻塞任何当前处理程序正在处理信号类型的待处理的信号。
  • 显式阻塞机制。应用程序可以使用sigprocmask函数和它的辅助函数,明确的阻塞和解除阻塞选定的信号。
#include 

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); //设定对信号屏蔽集内的信号的处理方式(阻塞或不阻塞)。
int sigemptyset(sigset_t *set); //初始化set为空集合。
int sigfillset(sigset_t *set); //把每个信号都添加到set中。
int sigaddset(sigset_t *set, int signum); //把signum添加到set。
int sigdelset(sigset_t *set, int signum); // 从set中删除signum。
                                    //返回:如果成功则为0,若出错则为-1。
int sigismember(const sigset_t *set, int signum); //测试signum是否在set中。
                                    //返回:若signum是set的成员则为1,如果不是则为0,若出错则为-1。

sigprocmask函数改变当前阻塞的信号集合。具体的行为依赖于how的值:

  • SIG_BLOCK:把set中的信号添加到blocked中(blocked=blocked | set)。
  • SIG_UNBLOCK:从blocked中删除set中的信号(blocked=blocked &~set)。
  • SIG_SETMASK:block=set。

如果oldset非空,那么blocked位向量之前的值保存在oldset中。

例子,展示了如何使用sigprocmask来临时阻塞接收SIGINT信号。

 1    sigset_t mask, prev_mask;
 2
 3    Sigemtyset(&mask);
 4    Sigaddset(&mask, SIGINT);
 5
 6    /* Block SIGINT and save previous blocked set */
 7    Sigprocmask(SIG_BLOCK, &mask, &prev_mask);
      .
 8    .  //Code region that will not be interrupted by SIGINT
      .
 9    /* Restore previous blocked set, unblocking SIGINT */
10    Sigprocmask(SIG_SETMASK, &prev_mask, NULL);
11

编写信号处理程序

处理程序有几个属性使得它们很难推理分析:

  1. 处理程序与主程序并发运行,共享同样的全局变量,因此可能与主程序和其它处理程序相互干扰。
  2. 如何以及何时接收信号的规则常常有违人的直觉。
  3. 不同的系统由不同的信号处理语义。

安全的信号处理

  1. 处理程序要尽可能的简单。
  2. 在处理程序中只调用异步信号安全的函数。
  3. 保存和恢复errno。
  4. 阻塞所有的信号,保护对共享全局数据结构的访问。
  5. 用volatitle声明全局变量。
  6. 用sig_atomic_t声明标志。

正确的信号处理

 

可移植的信号处理

 

同步流避免错误

 

显示的等待信号

 

非本地跳转

 

资料《深入理解计算机系统》

你可能感兴趣的:(操作系统)