异常部分代码赏析

8章异常部分代码赏析

本节内容可以通过三个程序综合起来帮助理解。

第一个是帮助理解创建进程和回收进程的shellex.c。

第二个是帮助理解信号阻塞的signal.c

第三个是帮助理解并发导致的竞争的procmask.c

下面分别讲一下其中的业务逻辑组块,帮助自己更好地理解代码

shellex.c

业务逻辑

实现获取用户输入命令并执行的shell程序

  1. 要获取输入命令,然后执行命令
  2. 要获取输入命令,就需要在标准输入中读取输入用户输入的命令字符串cmdline
  3. 要执行命令,就需要先将空格分隔的字符串cmdline字符串转换成参数列表argv,然后根据参数执行相应的操作。

执行操作API:

  • 判断命令是否为内置命令
    • 是内置命令,则在当前进程执行即可
    • 如果是外部命令,则需要创建子进程并执行外部指令。
  • 判断是否在后台执行命令
    • 如果后台执行命令,则简单返回后台执行命令的PID和命令字符串
    • 如果前台执行命令,则需要父进程挂起当前进程等待子进程结束

代码组块

这部分是通过将代码继续分块,来帮助理解的。

  1. 创建参数字符串

    char *delim;
    buf[strlen(buf) - 1] = ' ';	// 替换最后一个字符为空格
    while(*buf && (*buf == ' ')) {
      buf++;
    }
    while(delim = strchr(buf, ' ')) {
      argv[argc++] = buf;
      *delim = '\0';
      buf = delim + 1;
      while(*buf && (*buf == ' ')) {
        buf++;
      }
    }
    argv[argc] = NULL;
    
  2. 判断是否为内置命令

    if (!builtin_command(argv))
    if (strcmp(argv[0], "quit") == 0)
    
  3. 创建子进程并加载执行新程序

    if ((pid = Fork()) == 0) {
      if (execve(argv[0], argv, environ) < 0) {
        printf("%s: Command not found.\n", argv[0]);
        exit(0);
      }
    }
    
  4. 前后台执行

    if(!bg) {
      int status;
      if (waitpid(pid, &status, 0) < 0) {
        unix_eror("waitfg: waitpid error");
      }
    } else {
      printf("%d %s", pid, cmdline);
    }
    
  5. 参数为空处理:

    在eval,如果参数为空,则不做处理。

    if (argv[0] == NULL) {
      return;
    }
    

    在parseline中,如果参数为空,则直接返回

    if (argc == 0) {
      return 1;
    }
    

signal.c

业务逻辑

父进程设置一个SIGCHLD处理程序,然后创建三个输出后立即返回的子进程,随后等待用户输入。用户输入完毕,则进入无限循环,等待处理程序回收子进程。

  • 子进程执行完毕后立刻返回
  • 父进程等待回收子进程

要想避免信号丢失,就需要获取到第一个子进程结束信号的时候,就需要尽可能多地回收子进程。

代码组块

  1. 安全的信号处理

    int olderrno = errno;
    ...
    errno = olderrno;
    
  2. 尽可能多地回收子进程

    while (waitpid(-1, NULL, 0) > 0) {
      Sio_puts("Handler heap child\n");
    }
    if (errno != ECHILD) {
      Sio_error("waitpid error");
    }
    
  3. 设置信号处理程序

    if (signal(SIGCHLD, handler2) == SIG_ERR) {
      unix_error("signal error");
    }
    
  4. 创建三个立即结束的子进程

    for (int i = 0; i < 3; i++) {
      if (Fork() == 0) {
        printf("Hello from child %d\n", (int)getpid());
        exit(0);
      } 
    }
    

procmask2.c

避免父进程和子进程对于全局资源的竞争

  1. 安全的信号处理

    int olderrno = errno;
    ...
    errno = olderrno;
    
  2. 删除job的原子化

    Sigfillset(&mask_all);
    while ((pid = waitpid(-1, NULL, 0)) > 0) {
      Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
      deletejob(pid);
      Sigprocmask(SIG_SETMASK, &prev_all, NULL);
    }
    
    if (errno != ECHILD) {
      Sio_error("waitpid error");
    }
    
  3. 信号初始化

    sigset_t mask_all, mask_one, prev_one;
    
    Sigfillset(&mask_all);
    Sigemptyset(&mask_one);
    Sigaddset(&mask_one, SIGCHLD);
    Signal(SIGCHLD, handler);
    
  4. 添加job的原子化

    while(1) {
      Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
      if ((pid = Fork()) == 0) {
        Sigprocmask(SIG_SETMASK, &prev_one, NULL);
        execve(....);
      }
      Sigprocmask(SIG_BLOCK, &mask_all, NULL);
      addjob(pid);
      Sigprocmask(SIG_SETMASK, &prev_one, NULL);
    }
    
posted @ 2024-12-19 20:56  上山砍大树  阅读(6)  评论(0编辑  收藏  举报