CSAPP:第8章 异常控制流

2024-03-05 18:08
文章标签 异常 csapp 控制流

本文主要是介绍CSAPP:第8章 异常控制流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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-20210209153803161image-20210209153930994
      • image-20210209153930994
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
  • pause 函数,该函数让调用函数休眠,直到该进程收到一个信号。
    • Image-20210210162424004
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-20210210163539567
    •  image-20210210163549844
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采取的行为

    • handlerSIG_IGN,表示要进程忽略该信号
    • handlerSIG_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. 在处理程序中只调用异步信号安全的函数。所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用
      • 注意:printfsprintfmallocexit是不安全的,而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 系统上当前的平均负载。

这篇关于CSAPP:第8章 异常控制流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/777364

相关文章

Spring Boot统一异常拦截实践指南(最新推荐)

《SpringBoot统一异常拦截实践指南(最新推荐)》本文介绍了SpringBoot中统一异常处理的重要性及实现方案,包括使用`@ControllerAdvice`和`@ExceptionHand... 目录Spring Boot统一异常拦截实践指南一、为什么需要统一异常处理二、核心实现方案1. 基础组件

Python中异常类型ValueError使用方法与场景

《Python中异常类型ValueError使用方法与场景》:本文主要介绍Python中的ValueError异常类型,它在处理不合适的值时抛出,并提供如何有效使用ValueError的建议,文中... 目录前言什么是 ValueError?什么时候会用到 ValueError?场景 1: 转换数据类型场景

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

Python中的异步:async 和 await以及操作中的事件循环、回调和异常

《Python中的异步:async和await以及操作中的事件循环、回调和异常》在现代编程中,异步操作在处理I/O密集型任务时,可以显著提高程序的性能和响应速度,Python提供了asyn... 目录引言什么是异步操作?python 中的异步编程基础async 和 await 关键字asyncio 模块理论

详解Python中通用工具类与异常处理

《详解Python中通用工具类与异常处理》在Python开发中,编写可重用的工具类和通用的异常处理机制是提高代码质量和开发效率的关键,本文将介绍如何将特定的异常类改写为更通用的ValidationEx... 目录1. 通用异常类:ValidationException2. 通用工具类:Utils3. 示例文

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

Thymeleaf:生成静态文件及异常处理java.lang.NoClassDefFoundError: ognl/PropertyAccessor

我们需要引入包: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>sp

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存