8章异常部分代码赏析
本节内容可以通过三个程序综合起来帮助理解。
第一个是帮助理解创建进程和回收进程的shellex.c。
第二个是帮助理解信号阻塞的signal.c
第三个是帮助理解并发导致的竞争的procmask.c
第四个则是帮助理解如何让父进程显式地回收子进程的sigsuspend.c
下面分别讲一下其中的业务逻辑组块,帮助自己更好地理解代码
shellex.c
业务逻辑
实现获取用户输入命令并执行的shell程序
- 要获取输入命令,然后执行命令
- 要获取输入命令,就需要在标准输入中读取输入用户输入的命令字符串
cmdline
。 - 要执行命令,就需要先将空格分隔的字符串
cmdline
字符串转换成参数列表argv
,然后根据参数执行相应的操作。
执行操作API:
- 判断命令是否为内置命令
- 是内置命令,则在当前进程执行即可
- 如果是外部命令,则需要创建子进程并执行外部指令。
- 判断是否在后台执行命令
- 如果后台执行命令,则简单返回后台执行命令的PID和命令字符串
- 如果前台执行命令,则需要父进程挂起当前进程等待子进程结束
代码组块
这部分是通过将代码继续分块,来帮助理解的。
-
创建参数字符串
char *delim; buf[strlen(buf) - 1] = '\0'; // fgets会保存最后一个字符为换行符 while(*buf && (*buf == ' ')) {buf++; } while(delim = strchr(buf, ' ')) {argv[argc++] = buf;*delim = '\0';buf = delim + 1;while(*buf && (*buf == ' ')) {buf++;} } argv[argc] = NULL;
-
判断是否为内置命令
if (!builtin_command(argv)) if (strcmp(argv[0], "quit") == 0)
-
创建子进程并加载执行新程序
if ((pid = Fork()) == 0) {if (execve(argv[0], argv, environ) < 0) {printf("%s: Command not found.\n", argv[0]);exit(0);} }
-
前后台执行
if(!bg) {int status;if (waitpid(pid, &status, 0) < 0) {unix_eror("waitfg: waitpid error");} } else {printf("%d %s", pid, cmdline); }
signal.c
业务逻辑
父进程设置一个SIGCHLD处理程序,然后创建三个输出后立即返回的子进程,随后等待用户输入。用户输入完毕,则进入无限循环,等待处理程序回收子进程。
- 子进程执行完毕后立刻返回
- 父进程等待回收子进程
要想避免信号丢失,就需要获取到第一个子进程结束信号的时候,就需要尽可能多地回收子进程。
代码组块
-
安全的信号处理
int olderrno = errno; ... errno = olderrno;
-
尽可能回收子进程
while (waitpid(-1, NULL, 0) > 0) {Sio_puts("Handler heap child\n"); } if (errno != ECHILD) {Sio_error("waitpid error"); }
-
设置信号处理程序
if (signal(SIGCHLD, handler2) == SIG_ERR) {unix_error("signal error"); }
-
创建三个子进程
for (int i = 0; i < 3; i++) {if (Fork() == 0) {printf("Hello from child %d\n", (int)getpid());exit(0);} }
procmask2.c
待完成
sigsuspend.c
待完成