CSAPP:第8章 异常控制流 系统管理员 2022-10-29 09:17 206阅读 0赞 ### CSAPP:第8章 异常控制流 ### ### 文章目录 ### * * * CSAPP:第8章 异常控制流 * * 8.1 异常 * * 8.1.1 异常处理 * 8.1.2 异常的类别 * 8.1.3 Linux/x86-64 系统中的异常 * 8.2 进程 * * 8.2.1 逻辑控制流 * 8.2.2 并发流 * 8.2.3 私有地址空间 * 8.2.4 用户模式和内核模式 * 8.2.5 上下文切换 * 8.3 系统调用错误处理 * 8.4 进程控制 * * 8.4.1 获取进程ID * 8.4.2 创建和终止进程 * 8.4.3 回收子进程 * 8.4.4 让进程休眠 * 8.4.5 加载并行程序 * 8.4.6 利用fork和execve运行程序 * 8.5 信号 * * 8.5.1 信号术语 * 8.5.2 发送信号 * 8.5.3 接收信号 * 8.5.4 阻塞和解除阻塞信号 * 8.5.5 编写信号处理程序 * 8.5.6 同步流以避免讨厌的并发错误 * 8.5.7 显示地等待信号 * 8.6 非本地跳转 * 8.7 操作进程的工具 #### 8.1 异常 #### * 异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。 * 异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。 * 在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序(exception handler ))。 * 调用完毕后: * 返回当前指令 * 返回下一条指令 * 终止被中断的程序 ##### 8.1.1 异常处理 ##### * 异常表的起始地址放在一个叫做异常表基址寄存器(exception table base register)的特殊 CPU 寄存器里。 * 异常与过程调用的区别 * 异常调用结束后的三种可能(见上文) * 处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始执行被中断的程序会需要这些状态。 * 如果控制从用户程序转移到内核,所有这些项目都被压到内核桟中,而不是压到用户栈中。 * 异常处理程序运行在内核模式下,这意味着它们对所有的系统资源都有完全的访问权限。 ##### 8.1.2 异常的类别 ##### * ![image-20210209150618788][] * 1、中断 * 在当前指令完成执行之后,处理器注意到中断引脚的电压变高了,就从系统总线读取异常号,然后调用适当的中断处理程序。 * 当处理程序返回时,它就将控制返回给下一条指令(也即如果没有发生中断,在控制流中会在当前指令之后的那条指令)。结果是程序继续执行,就好像没有发生过中断一样。 * ![image-20210209151042031][] * 2、陷阱和系统调用 * 陷阱是有意的异常(比如调用内核请求服务),是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。 * ![image-20210209151218886][] * 从程序员的角度来看,系统调用和普通的函数调用是一样的。然而,它们的实现非常不同。 * 其实就是权限不同(即系统调用在内核模式)。 * 3、故障 * 故障由错误情况引起,它可能能够被故障处理程序修正,如:缺页异常 * 如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。 * 否则,处理程序返回到内核中的 abort 例程,abort 例程会终止引起故障的应用程序。 * ![image-20210209152219156][] * 4、终止 * 终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如 DRAM 或者SRAM 位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。 * ![image-20210209152311452][] ##### 8.1.3 Linux/x86-64 系统中的异常 ##### * 1、Linux/x86-64 故障和终止 * ![image-20210209152501719][] * 机器异常:机器检查(异常 18)是在导致故障的指令执行中检测到致命的硬件错误时发生的。机器检查处理程序从不返回控制给应用程序。 * 2、Linux/x86-64 系统调用 * C 程序用 syscall 函数可以直接调用任何系统调用。 * 在 X86-64 系统上,系统调用是通过一条称为 syscall 的陷阱指令来提供的。 * 寄存器% rax 包含系统调用号,参数可依次存在%rdi、%rdi、%rsi、%rdx、%r10、%r8、%r9中,最多留个 * ![image-20210209152724437][] * 如下面函数: * ![image-20210209153803161][]![image-20210209153930994][] * ![image-20210209153930994][image-20210209153930994 1] #### 8.2 进程 #### * 进程的经典定义就是一个执行中程序的实例。 * 进程提供给应用程序的关键抽象: * 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。 * 一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。 ##### 8.2.1 逻辑控制流 ##### * 一个进程中的PC值的序列——逻辑控制流(多个进程有多个逻辑流) ##### 8.2.2 并发流 ##### * 一个逻辑流的执行在时间上与另一个流重叠,称为并发流(concurrent flow),这两个流被称为并发地运行。 * 并发=同个**时间段**内同时运行。注意与并行的区分。 ##### 8.2.3 私有地址空间 ##### * 进程为每个程序提供它自己的私有地址空间。一般而言,和这个空间中某个地址相关联的那个内存字节是不能被其他进程读或者写的,从这个意义上说,这个地址空间是私有的。 * 结构: * ![image-20210209155315485][] ##### 8.2.4 用户模式和内核模式 ##### * 就权限不同嘛,按我个人理解,就好比普通用户权限和root权限 ##### 8.2.5 上下文切换 ##### * 操作系统内核使用一种称为上下文切换(context switch)的较高层形式的异常控制流来实现多任务。上下文切换机制是建立在 8.1 节中已经讨论过的那些较低层异常机制之上的。 * 个人理解为通过内核中的某些代码(内核模式下)实现进程切换(上下文切换),也就实现了多任务。 * ![image-20210209160011855][] #### 8.3 系统调用错误处理 #### * 错误处理包装函数——个人理解成:类似Java的Exception类的概念,调用下就输出错误的Track,只不过眼下这玩意儿是系统级的。 #### 8.4 进程控制 #### ##### 8.4.1 获取进程ID ##### * 进程ID即PID * ![image-20210209160919075][] ##### 8.4.2 创建和终止进程 ##### * 从程序员角度: * running: * 进程要么在 CPU 上执行,要么在等待被执行且最终会被内核调度。 * stopped: * 进程的执行被挂起(suspended ), 且不会被调度。当收到 SIGSTOP、SIGTSTP、SIGTTIN 或者 SIGTTOU 信号时,进程就停止,并且保持停止直到它收到一个 SIGCONT 信号,在这个时刻,进程再次开始运行。 * terminated: * 进程永远地停止了。进程会因为三种原因终止: * 1)收到一个信号,该信号的默认行为是终止进程 * 2)从主程序返回 * 3)调用 exit 函数 * ![image-20210209161226103][] * 创建进程:fork * ![image-20210209161313707][] * 当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。 * ![image-20210209162951317][] * 基于上面的的例子说明fork()的几个特点: * 调用一次,返回两次。 * fork 函数被父进程调用一次,但是却返回两次 次是返回到父进程,一次是返回到新创建的子进程。 * 并发执行。 * 相同但是独立的地址空间。 * 即子进程复制了一份父进程的地址空间 * 共享文件。 * 原因是子进程继承了父进程所有的打开文件。即,对于控制台的输出是同一个文件,而不是子进程再打开一个控制台输出。 * 该函数运行流程如下,注意fork之后的变化(一般来说fork赋予子进程的PID总是0,所以子进程进入了if中,这才导致输出不同) * ![image-20210209163012983][] ##### 8.4.3 回收子进程 ##### * 当一个进程由于某种原因终止时,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。 * 当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。 * 一个终止了但还未被回收的进程称为僵死进程(zombie)。 * 如果一个父进程终止了,内核会安排 init 进程成为它的孤儿进程的养父。init 进程的 PID 为 1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排 init 进程去回收它们。 * 这里说明下僵死进程:假如有父进程A和A的子进程B * 1、当A一直运行,B exit()的时候,在A未回首之前,B都会被标为僵死进程,当A结束,如果还没把B回收,则由init回收 * 2、当A终止,B一直运行,则B不被判定为僵死进程。 * 一个进程可以通过调用 waitpid 函数来等待它的子进程终止或者停止。 * ![image-20210210153322037][] * 默认情况下(当 options=0 时),waitpid 挂起调用进程的执行,直到它的等待集合(wait set )中的一个子进程终止。 * 描述: * 1、判定等待集合的成员——根据参数pid * if(pid>0) 等待集合就是一个单独的紫禁城,PID=pid * else if(pie==-1) 等待集合是父进程所有的子进程组成 * else 其他 * 2、修改默认行为 * WNOHANG:如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为 0)。默认的行为是挂起调用进程,直到有子进程终止。 * WUNTRACED:挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的 PID 为导致返回的已终止或被停止子进程的 PID。默认的行为是只返回已终止的子进程。 * WCONTINUED:挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到 SIGCONT 信号重新开始执行。 * 上述组合 * 3、检查已回收子进程的退出状态:如果 statusp 参数是非空的,那么 waitpid 就会在 status 中放上关于导致返回的子进程的状态信息,status 是 statusp 指向的值。wait .h 头文件定义了解释 status 参数的几个宏: * WIFEXITEDC status):如果子进程通过调用 exit 或者一个返回(return)正常终止,就返回真。 * WEXITSTATUS( status):返回一个正常终止的子进程的退出状态。只有在 WIFEXITED() 返回为真时,才会定义这个状态。 * WIFSIGNALED(status):如果子进程是因为一个未被捕获的信号终止的,那么就返回真。 * WTERMSIG(status):返回导致子进程终止的信号的编号。只有在 WIFSIGNALED() 返回为真时,才定义这个状态。 * WIFSTOPPED(status):如果引起返回的子进程当前是停止的,那么就返回真。 * WSTOPSIG(status):返回引起子进程停止的信号的编号。只有在 WIFSTOPPED() 返回为真时,才定义这个状态。 * W1FCONTINUED(status):如果子进程收到 SIGCONT 信号重新启动,则返回真。 * 4、错误条件:如果调用进程没有子进程,那么 waitpid 返回一1,并且设置 errno 为 ECHILD。如果 waitpid函数被一个信号中断,那么它返回一1,并设置 errno 为 EINTR。 * 5、wait函数 * ![image-20210210161113941][] ##### 8.4.4 让进程休眠 ##### * sleep 函数将一个进程挂起一段指定的时间。 * ![image-20210210162257446][image-20210210161113941] * pause 函数,该函数让调用函数休眠,直到该进程收到一个信号。 * ![Image-20210210162424004][image-20210210161113941] ##### 8.4.5 加载并行程序 ##### * execve 函数在当前进程的上下文中加载并运行一个新程序。 * execve 函数加载并运行可执行目标文件 filename, 且带参数列表 argv 和环境变量列表 erwp。 * \[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uktbXsMv-1612975812767)(/Users/chchen/Library/Application Support/typora-user-images/image-20210210162501564.png)\] * 参数和环境变量格式——经典key:value * ![image-20210 210162814425][image-20210210161113941] * 一些对环境数组进行操作的函数 * ![image-20210210163539567][image-20210210161113941] * ![image-20210210163549844][image-20210210161113941] ##### 8.4.6 利用fork和execve运行程序 ##### * fork和execve的区别 * The exec() family of functions replaces the current process image with a new process image exec是没有创建新进程的,而是把当前进程对应的应用换成新的应用。因此,它里头当前不会去fork了。这个进程就是执行exec的进程,举个例,如果PID=1000的进程A, 执行ecec B, 那就PID=1000的进程就会变为B,A的资源会被系统回收。对Exec函数来说,没所谓父子进程,只有当前进程,当前执行exec函数的进程。 * fork和exec不一样,它的作用是复制一个进程,但两个进程都运行相同的程序。task\_struct也是fork的时间新建的。一般这两函数是联用的,先fork,再在子进程里exec。在内核态并没有相互调用关系。 * 具体实现时,exec函数将堆栈中存储的返回指令和栈指针修改为新的程序的头指令和新的堆栈地址。 * 这样,当调用返回时,系统弹出新的返回地址和堆栈。系统开始执行新程序。 #### 8.5 信号 #### * \[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ar2YDRJQ-1612975812768)(/Users/chchen/Library/Application Support/typora-user-images/image-20210210164506503.png)\] ##### 8.5.1 信号术语 ##### * 发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。 * 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。 * 待处理信号(pending signal):一个发出而没有被接收的信号,在任何时刻,一种类型至多只会有一个待处理信号(其他的会被丢弃)。 * 一个待处理信号最多只能被接收一次。 * 当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。 ##### 8.5.2 发送信号 ##### * **进程组** * 每个进程都只属于一个进程组,进程组是由一个正整数进程组 ID 来标识的。getpgrp函数返回当前进程的进程组 ID,一个子进程和它的父进程同属于一个进程 * ![im age-20210210204255164][] * 一个进程可以通过使用 setpgid 函数来改变自己或者其他进程的进程组: * ![image-20210210204408598][] * setpgid 函数将进程 pid 的进程组改为 pgid。如果 pid 是 0 , 那么就使用当前进程的 PID。如果 pgid 是 0 , 那么就用 pid 指定的进程的 PID 作为进程组 ID。即:setpgid(0, 0);=使用当前进程的PID当新建进程组的ID。 * **kill命令**——杀(任何)进程用 * **从键盘发送信号** * **用kill函数发送信号** * 进程通过调用 kill 函数发送信号给其他进程(包括它们自己)。 * ![image-20210210210450232][] \- 如果 pid 大于零,那么 kill 函数发送信号号码 sig 给进程 pid。 - 如果 pid 等于零,那么kill 发送信号 sig 给调用进程所在进程组中的每个进程,包括调用进程自己。 - 如果 pid小于零,kill 发送信号 sig 给进程组|Pid|(Pid 的绝对值)中的每个进程。 * **用 alarm 函数发送信号** * 进程可以通过调用 alarm 函数向它自己发送 SIGALRM 信号。 * ![image-20210210210757034][] \- alarm 函数安排内核在 secs 秒后发送一个 SIGALRM 信号给调用进程。 - 如果 secs是零,那么不会调度安排新的闹钟(alarm)。 - 在任何情况下,对 alarm 的调用都将取消任何待处理的(pending)闹钟,并且返回任何待处理的闹钟在被发送前还剩下的秒数(如果这次对 alarm 的调用没有取消它的话); - 如果没有任何待处理的闹钟,就返回零。 ##### 8.5.3 接收信号 ##### * 每次从内核模式切换回用户模式,将处理所有信号(个人理解为中断)。 * ![image-20210210232148082][] * 每个信号类型都有一个预定义的默认行为 ,是下面中的一种: * 进程终止。 * 进程终止并转储内存。 * 进程停止(挂起)直到被 SIGCONT 信号重启。 * 进程忽略该信号。 * ![image-20210210231804452][] * `signum`为信号编号,可以直接输入信号名称 * `handler`为我们想要对信号`signum`采取的行为 * * 当`handler`为`SIG_IGN`,表示要进程忽略该信号 * 当`handler`为`SIG_DFL`,表示要恢复该信号的默认行为 * 当`handler`为用户自定义的**信号处理程序**地址,则会调用该函数来处理该信号,该函数原型为`void signal_handler(int sig);`。调用信号处理程序称为**捕获信号**,置信信号处理程序称为**处理信号**。当信号处理程序返回时,会将控制传递回逻辑流中的下一条指令。\*\*注意:\*\*信号处理程序可以被别的信号处理程序中断。 * 当`signal`函数执行成功,则返回之前`signal handler`的值,否则返回`SIG_ERR` * ![image-20210210232909922][] * 这张图是不是像极了计组里面的多级中断~~ ##### 8.5.4 阻塞和解除阻塞信号 ##### * Linux 提供阻塞信号的隐式和显式的机制: * 隐式阻塞机制。 * 内核默认阻塞 任何 当前处理程序 正在处理信号类型 的 待处理 的 信号。 * 显式阻塞机制。 * 应用程序可以使用 sigprocmask 函数和它的辅助函数,明确地阻塞和解除阻塞选定的信号。 * ![image-20210210233512700][] \- sigprocmask 函数改变当前阻塞的信号集合。具体的行为依赖于 how 的值: - SIG\_BLOCK: 把 set 中的信号添加到 blocked 中(blocked=blocked | set)。 - SIG\_UNBLOCK: 从 blocked 中删除 set 中的信号(blocked=blocked S set)。 - SIG\_SETMASK:block=set0 - 如果 oldset 非空,那么 blocked 位向量之前的值保存在 oldset 中。 - ![image-20210210233849201][] \- sigenptyset 初始化 set 为空集合。 - sigfillset函数把每个信号都添加到 set 中。 - sigaddset 函数把 signum 添加到 set - sigdelset 从 set 中删除 signum,如果 signum是 set 的成员,那么 sigismember返回 1,否则返回 0。 ##### 8.5.5 编写信号处理程序 ##### * 信号处理程序的特点: * 1)处理程序与主程序并发运行,共享同样的全局变量,因此可能与主程序和其他处理程序互相干扰; * 2)如何以及何时接收信号的规则常常有违人的直觉; * 3)不同的系统有不同的信号处理语义。 * 1、信号的安全处理——俺的理解就是类似锁机制? * GO. 处理程序要尽可能简单。避免麻烦的最好方法是保持处理程序尽可能小和简单。 * G1. 在处理程序中只调用异步信号安全的函数。所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用 * **注意:**`printf`,`sprintf`,`malloc`和`exit`是不安全的,而`write`是安全的。 * ![image-20210210234542542][] * 一些安全的函数——SIO(安全的IO)包,可重入(**reentrant**) * ![image-20210210234717405][] * sio\_put1和 sio\_puts 函数分别向标准输出传送一个 long 类型数和一个字符串。sio\_error打印一条错误消息并终止。 * G2. 保存和恢复 errno。许多 Linux 异步信号安全的函数都会在出错返回时设置errno.在处理程序中调用这样的函数可能会干扰主程序中其他依赖于 errno 的部分。解决方法是在进人处理程序时把 errno 保存在一个局部变量中,在处理程序返回前恢复它。 * **注意**,只有在处理程序要返回时才有此必要。如果处理程序调用\_exit终止该进程,那么就不需要这样做了。 * G3. 阻塞所有的信号,保护对共享全局数据结构的访问。 * 如果处理程序和主程序或其他处理程序共享一个全局数据结构,那么在访问(读或者写)该数据结构时,你的处理程序和主程序应该暂时阻塞所有的信号。 * 这条规则的原因是从主程序访问一个数据结构 d 通常需要一系列的指令,如果指令序列被访问 d 的处理程序中断,那么处理程序可能会发现 的状态不一致,得到不可预知的结果。 * 在访问d时暂时阻塞信号保证了处理程序不会中断该指令序列。 * G4. 用 volatile 声明全局变量。 * 考虑一个处理程序和一个 main 函数,它们共享一个全局变量 g。处理程序更新 g,main 周期性地读 g。对于一个优化编译器而言,main 中 g的值看上去从来没有变化过,因此使用缓存在寄存器中 g 的副本来满足对 g 的每次引用是很安全的。如果这样,main 函数可能永远都无法看到处理程序更新过的值。 * 可以用 volatile 类型限定符来定义一个变量,告诉编译器不要缓存这个变量。例如:volatile int g; * volatile 限定符强迫编译器每次在代码中引用 g 时,都要从内存中读取 g 的值。 * G5. 用 Sig\_atomic\_t 声明标志。 * 在常见的处理程序设计中,处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志,响应信号,再清除该标志。 * 对于通过这种方式来共享的标志,C 提供一种整型数据类型 sig\_atomic\_t, 对它的读写是原子的:\_volatile sig\_atomic\_t flag; * 2、正确的信号处理 * 信号的一个与直觉不符的方面是未处理的信号是不排队的——因为第二个被丢了(同种的)——即同种待处理信号同一时间只能有一个。 * 3、可移植的信号处理 * Unix 信号处理的另一个缺陷在于不同的系统有不同的信号处理语义。例如: * signal 函数的语义各有不同 。有些老的 Unix 系统在信号k 被处理程序捕获之后就把对信号k的反应恢复到默认值。在这些系统上,每次运行之后,处理程序必须调用 signal 函数,显式地重新设置它自己 。 * 系统调用可以被中断。像 read、write 和 accept 这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。在某些较早版本的 Unix 系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用在信号处理程序返回时不再继续,而是立即返回给用户一个错误条件,并将 errno 设置为 EINTR。在这些系统上,程序员必须包括手动重启被中断的系统调用的代码。 * Posix 标准定义了 sigaction 函数,它允许用户在设置信号处理时,明确指定他们想要的信号处理语义。 * ![image-20210211001345365][] * sigaction 函数运用并不广泛,因为它要求用户设置一个复杂结构的条目。一个更简洁的方式——Signal,它调用 sigaction。 * Signal 包装函数设置了一个信号处理程序,其信号处理语义如下: * 只有这个处理程序当前正在处理的那种类型的信号被阻塞。 * 和所有信号实现一样,信号不会排队等待。 * 只要可能,被中断的系统调用会自动重启。 * 一旦设置了信号处理程序,它就会一直保持,直到 Signal 带着 handler 参数为SIG\_IGN 或者 SIG\_DFL 被调用。 ##### 8.5.6 同步流以避免讨厌的并发错误 ##### * 一个例子用signal来解决由于并发而导致的同步问题。 ##### 8.5.7 显示地等待信号 ##### * ![image-20210211002144973][] \- sigsuspend 函数暂时用 mask 替换当前的阻塞集合,然后挂起该进程,直到收到一个信号,其行为要么是运行一个处理程序,要么是终止该进程。(我的理解:类似于wait机制) - 如果它的行为是终止,那么该进程不从 sigsuspend 返回就直接终止。 - 如果它的行为是运行一个处理程序,那么sigsuspend 从处理程序返回,恢复调用 sigsuspend 时原有的阻塞集合。 #### 8.6 非本地跳转 #### * C 语言提供了一种用户级异常控制流形式,称为非本地跳转(nonlocal jump),它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。 * 非本地跳转是通过 setjmp 和 longjmp 函数来提供的。 * ![image-20210211002435129][] \- setjmp 函数在 env 缓冲区中保存当前调用环境,以供后面的 longjmp 使用,并返回0。调用环境包括程序计数器、栈指针和通用目的寄存器。\*\*setjmp 返回的值不能被赋值给变量\*\*:rc = setjmp(env); /\* Wrong! \*/ * ![image-20210211002445167][] \- longjmp 函数从 env 缓冲区中恢复调用环境,然后触发一个从最近一次初始化 env的 setjmp 调用的返回。然后 setjmp 返回,并带有非零的返回值 retval。 * **应用1**——无需解析调用栈,直接从深层嵌套函数中返回 * ![image-20210211003518256][] * ![image-20210211003504559][] * main函数中switch里有个setjmp(buf)函数将当前调用环境保存到`buf`中并返回0,所以就调用`foo`函数和`bar`函数,当这两个函数中出现错误,则通过`longjmp(buf, retval)`恢复调用环境,并跳转回第13行,然后让`setjmp`函数返回`retval`的值,由此就无需解析调用栈了。但是该方法可能存在内存泄露问题。 * **应用2**——控制信号处理程序结束后的位置 * 在信号处理中也有对应的两个非本地跳转的函数 #include <setjmp.h> int sigsetjmp(sigjmp_buf env, int savesigs); void siglomgjmp(sigjmp_buf env, int retval); * ![image-20210211004420436][] * 在程序第一次启动时,对 sigsetjmp 函数的初始调用保存调用环境和信号的上下文(包括待处理的和被阻塞的信号向量)。随后,主函数进人一个无限处理循环。 * 当用户键人Ctrl+C 时,内核发送一个 SIGINT 信号给这个进程,该进程捕获这个信号。不是从信号处理程序返回,如果是这样那么信号处理程序会将控制返回给被中断的处理循环,反之,处理程序完成一个非本地跳转,回到 main 函数的开始处。 * 当我们在系统上运行这个程序时,得到以下输出: * ![image-20210211004713762][] #### 8.7 操作进程的工具 #### * Linux 系统提供了大量的监控和操作进程的有用工具。 * STRACE: 打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹。 * PS:列出当前系统中的进程(包括僵死进程)。 * TOP:打印出关于当前进程资源使用的信息。 * PMAP:显示进程的内存映射。 * /proc:一个虚拟文件系统,以 ASCII 文本格式输出大量内核数据结构的内容,用户程序可以读取这些内容。比如,输入 “cat /proc/loadavg”,可以看到你的 Linux 系统上当前的平均负载。 [image-20210209150618788]: /images/20221024/14a6a50abb674e5c8835b961e807bcd1.png [image-20210209151042031]: /images/20221024/ff14413fd4ff4e80aade77aed62fd1d2.png [image-20210209151218886]: /images/20221024/951a96ece3ee454cb985f7ce6c5efe23.png [image-20210209152219156]: /images/20221024/1175beca0c81443aa4f6aba1f53b2f93.png [image-20210209152311452]: /images/20221024/fa26e607841b41588eb0cb901c94ae57.png [image-20210209152501719]: /images/20221024/7dc1da7bc5df4f9585f5da87093e4a9f.png [image-20210209152724437]: /images/20221024/ceef27ace5bb4f1382718f4c93c16d1f.png [image-20210209153803161]: https://tva1.sinaimg.cn/large/008eGmZEly1gnhbni5iqcj30ka06cdgc.jpg [image-20210209153930994]: /images/20221024/18cdff3f887441f8813c5460c8159075.png [image-20210209153930994 1]: /images/20221024/4c7ffded542f4fe59daf79fb25dc603c.png [image-20210209155315485]: /images/20221024/17763f9db0a84366a247f598fc7d0a41.png [image-20210209160011855]: /images/20221024/bb6f09a7c06a4ea5887db7b982e16002.png [image-20210209160919075]: /images/20221024/66aae35f30f8434daa90aff44049df41.png [image-20210209161226103]: /images/20221024/8075a1c8784b412c98e6c522ab42ed3a.png [image-20210209161313707]: /images/20221024/fd4a5444a93641b5b222190b25c71171.png [image-20210209162951317]: /images/20221024/2c16e485174242508d457cc542750dc9.png [image-20210209163012983]: /images/20221024/1635c8a33a3c4199bcffcd4c7aff3d7e.png [image-20210210153322037]: /images/20221024/9ebb6f4ecd374179bb1da4a59ce84592.png [image-20210210161113941]: [im age-20210210204255164]: /images/20221024/c3f73b2110eb43ccb84d349efaef5095.png [image-20210210204408598]: /images/20221024/876df74b82ff4bb5bdd396b35bc6d201.png [image-20210210210450232]: /images/20221024/ed692fd99b7a4220a97911f0e7c1c51a.png [image-20210210210757034]: /images/20221024/14e1a1014595421f9f0081b038b34653.png [image-20210210232148082]: /images/20221024/0c7ee66870e44f378e63470bc921e33e.png [image-20210210231804452]: /images/20221024/0fb68eeaa83940c887a235a52a270ad6.png [image-20210210232909922]: /images/20221024/d2de74a9a8e54c8bb9eaf35f88b6472b.png [image-20210210233512700]: http://docker.aliyouth.cn/img/image-20210210233512700.png [image-20210210233849201]: /images/20221024/6eb0c85eb4ef44a1b63c3595d7797b3e.png [image-20210210234542542]: /images/20221024/52abe4d4ae0f4709935483a7a232ea57.png [image-20210210234717405]: /images/20221024/d33f274031c94968a90ba744ebb1d6af.png [image-20210211001345365]: /images/20221024/2919b8beace54c7c9206a8e013a19dd5.png [image-20210211002144973]: /images/20221024/ee4d24290f7a4a5bb837db3df60938c8.png [image-20210211002435129]: /images/20221024/a73f84a3d46b4b16a130db6735fddd7f.png [image-20210211002445167]: /images/20221024/866770de67fb48a9bef6b7038c0af899.png [image-20210211003518256]: /images/20221024/628a2778a45b4ee1aa62468fd02ae921.png [image-20210211003504559]: /images/20221024/549ea21592fa49b0a64c989639b63d5b.png [image-20210211004420436]: /images/20221024/47450bc6b1b14dbdaf56dcb82e4f684d.png [image-20210211004713762]: /images/20221024/c950562b5e564c7d8930d1a1abeceffc.png
相关 CSAPP:第1章 计算机系统漫游 文章目录 1.1 information=bit+context 1.2 程序翻译 1 以你之姓@/ 2023年01月02日 03:24/ 0 赞/ 215 阅读
相关 CSAPP:第12章 并发编程 CSAPP:第12章 并发编程 文章目录 CSAPP:第12章 并发编程 12.1 基于进程的并发编程(Process 傷城~/ 2022年10月31日 01:47/ 0 赞/ 23 阅读
相关 CSAPP:第11章 网络编程 CSAPP:第11章 网络编程 文章目录 CSAPP:第11章 网络编程 11.1 客户端-服务器编程模型 待我称王封你为后i/ 2022年10月30日 05:24/ 0 赞/ 261 阅读
相关 CSAPP:第10章 系统级IO CSAPP:第10章 系统级IO 文章目录 CSAPP:第10章 系统级IO 10.1 Unix IO 水深无声/ 2022年10月29日 15:22/ 0 赞/ 231 阅读
相关 CSAPP:第9章 虚拟内存 CSAPP:第9章 虚拟内存 文章目录 CSAPP:第9章 虚拟内存 9.1 物理和虚拟寻址 谁践踏了优雅/ 2022年10月29日 11:27/ 0 赞/ 335 阅读
相关 CSAPP:第6章 存储器层次结构 CSAPP:第6章 存储器层次结构 文章目录 CSAPP:第6章 存储器层次结构 6.1 存储技术 Dear 丶/ 2022年10月28日 10:09/ 0 赞/ 244 阅读
相关 CSAPP:第4章 处理器体系结构 CSAPP:第4章 处理器体系结构 文章目录 CSAPP:第4章 处理器体系结构 Y86-84指令集体系结构 - 日理万妓/ 2022年10月24日 13:30/ 0 赞/ 355 阅读
相关 第8章——《进程控制》 实验环境介绍 gcc:4.8.5 glibc:glibc-2.17-222.el7.x86\_64 os:Centos7.4 kernel:3.1 谁践踏了优雅/ 2022年05月16日 00:14/ 0 赞/ 297 阅读
还没有评论,来说两句吧...