CSAPP-异常

今天csapp学异常的相关概念。

程序内的执行流切换无非两种,一种是分支和跳转;另一种是调用和返回。但是为了响应某些的事件,例如键盘输入和ctrl+c终止程序;以及程序的缺页错误或者段错误,就需要另一种执行流切换的方式。这种特殊处理叫做异常。

异常是将事件交给内核处理的过程,实现的层级从低到高包含异常、进程切换、信号和非本地跳转。

首先异常的分类。异常事件按照是否出现于当前执行的程序内来区分同步异常和异步异常。同步异常是程序内的操作导致的,包括内陷、故障和错误。内陷是程序故意操作的,例如系统调用以及中断。而故障和错误则是无意的。故障出现后,内核可能恢复程序的故障状态,再次执行程序。但是错误出现了,程序会被终止。

另一个则是异常的处理过程。每一个异常都有一个异常号,内核的异常处理函数,会根据出现的异常号来做相应的操作。

进程是一种抽象,在运行的程序看来,自己拥有CPU和内存的使用权。进程切换的时候,需要保存当前程序的上下文,以便未来恢复程序的状态。这里介绍了几个函数:

  • 获取进程信息:getpid()getppid()
  • 创建子进程:fork()
  • 终止进程:exit()
  • 在此进程上直接执行另一个进程execve

这里也介绍了进程图,但是需要配合fork()函数的行为来理解。

两个部分介绍完了,下一个异常就是信号了。信号是后台程序执行完毕后,内核给当前进程的一种通知。信号可以被进程忽略。进程收到信号后,有三种操作:

  • 忽略
  • 终止
  • 交给信号处理函数处理信号

另一个异常就是非本地跳转。本地跳转就是程序在一个函数内的goto语句。非本地跳转实现了两个函数之间的跳转。这里讲了两个函数以及行为。

  • setjmp():执行后,会保存当前函数的状态
  • longjmp():执行后,会恢复由setjmp保存的状态,实现栈的切换。

还讲了伪并行-并发和真正的并行区别,就是同时运行的进程,如果只有一个CPU来处理,那就是并发;如果多个核心分别运行多个进程,则为并行。

晚上再学习下,进程里面相关函数的API以及进程图的概念

  1. fork():创建新进程,返回两次。对于父进程,返回子进程的PID,对于子进程,返回0.

    if ((pid = fork()) < 0) {
        fprintf(stderr, "fork error: %s\n", strerror(errno));
        exit(0);
    }
    

    strerror(errno)strerror(errno) 返回与当前错误码 errno 相关的错误信息字符串。errno 是一个全局变量,它存储的是上一个系统调用或库函数调用失败时的错误码。

    void unix_error(char *msg) /* Unix-style error */
    {
        fprintf(stderr, "%s: %s\n", msg, strerror(errno));
        exit(0);
    }
    
    // 上面的片段可以写为
    if ((pid = fork()) < 0)
        unix_error("fork error");
    

    进一步包装fork(),使之自带错误处理

    pid_t Fork(void) {
      pid_t pid;
      if ((pid = fork()) < 0) {
        unix_error("Fork error");
      }
      return pid;
    }
    

    fork()是系统调用,系统调用都有返回值。可以根据系统调用的返回值是否大于0判断系统调用是否成功。具体到C语言中,<string.h>库的strerror()函数可以将错误码包装为字符串输出。

    <errno.h>中的errno保存了最近一次系统调用或库函数调用的错误码。

    通过strerror(errno)可以很优雅地将系统调用的错误码转换为字符串输出。

  2. 进程图

    • 关键指令用点表示
    • 变量值标记边
    • a->b表示a事件出现在b之前
  3. int setjmp(jmp_buf env)void longjmp(jmp_buf env, int val)

    • setjmp():第一次调用返回0。如果从longjmp跳转回来,则返回longjmp()的第二个参数val
    jmp_buf env;
    
    void func1() {
      printf("Inside func1\n");
      longjmp(env,  1);
    }
    
    void func2() {
      printf("Inside func2\n");
      func1();
    }
    
    void test_jmp() {
      int ret = setjmp(env);
      if (ret == 0) {
        // 第一次调用返回值为0
        printf("setjmp returned 0, calling func2\n");
        func2();
      } else {
        // 通过 longjmp 跳转回来时,ret 将为非零值
        printf("Returned from longjmp, ret = %d\n", ret);
      }
    }
    
posted @ 2024-12-09 20:09  上山砍大树  阅读(12)  评论(0编辑  收藏  举报