CSAPP:shell Lab笔记

CSAPP:shell Lab笔记 CSAPP:shell Lab 笔记该篇文章用于记录我在做shell实验的心得intstrcmp(constchar*__s1,constchar*__s2);该函数是在builtin_cmd函数用到用于比较内置指令与输出指令的常见的实现方式是s1的字典序-s2的字典序只有为0时表示两字符串相等而不是返回布尔值。pid_twaitpid(pid_tpid,int*status,intoption);阻塞等待option0不阻塞optionWNOHANGpid-1等待所有子进程终止option含义0阻塞等待WNOHANG不阻塞等待没有等待到返回0WUNTRACED除了等待子进程正常退出如果子进程因为收到信号如SIGSTOP而暂停waitpid也会立刻返回WCONTINUE如果子进程之前是暂停状态现在收到SIGCONT信号恢复执行waitpid也会返回WNOHANG | WNUTRACED立刻返回检查子进程是否正常退出或暂停由于option均是位掩码故可以使用|进行任意组合status含义WIFEXITED子进程通过exit或者return退出为真WIFSIGNALED子进程是因为一个未被捕获的信号终止的如CTRLCWIFSTOPPED子进程当前停止不阻塞轮询pid_tret;intstatus;while(1){retwaitpid(pid,status,WNOHANG);if(retpid){printf(等待完成);break;}elseif(ret0){// 避免CPU空转sleep(1);}else{fprintf(stderr,出现错误);}}保护操作不被信号打断的示例代码sigset_tmask,prev_mask;sigfillset(mask);// 位掩码对所有位填充1,表示拦截所有信号// 与他相反的是sigemptyset对所有位置0// 配合sigaddset(mask,SIGINT),添加信号到集合中// sigdelset(mask,SIGINT),删除在集合中的指定信号// SIGINT 是一个例子sigprocmask(SIG_BLOCK,mask,prev_mask);// 临界区代码保证临界区代码不会被信号打断除了SIGKILL,SIGSEGV(段错误)sigprocmask(SIG_SETMASK,prev,NULL);// 上面所有函数成功返回0,失败返回-1// 查找集合内是否有该信号有返回1,没有0,出错-1sigismember(mask,intsignum)为了保证父进程添加jobs时子进程在父进程后面执行完即父进程在这一时刻不会处理SIGCHILD信号使用异步信号安全的函数在信号处理函数中无须使用信号屏蔽函数sigprocmask是信号安全的,但是直接在信号处理函数中直接修改信号可能会导致死锁,直接使用waitpidtrace04 BUG输入命令# # trace04.txt - Run a background job. # /bin/echo -e tsh ./myspin 1 \046 ./myspin 1 正确结果polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make rtest04 ./sdriver.pl -t trace04.txt -s ./tshref -a -p # # trace04.txt - Run a background job. # tsh ./myspin 1 [1] (6380) ./myspin 1 我的结果polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make test04 ./sdriver.pl -t trace04.txt -s ./tsh -a -p # # trace04.txt - Run a background job. # tsh ./myspin 1 [2] (6119) ./myspin 1 可以看到jid分配错误所以我怀疑执行echo进程没有被回收通过dedug进入sigchld_handler发现waitpid返回值始终是-1而errno的值是ECHILD。源码如下voidsigchld_handler(intsig){intstatus;intoldernoerrno;pid_tpid;// char err_msg[] sigchild_handler\n;do{pidwaitpid(-1,status,0);deletejob(jobs,pid);}while(pid0);// while((pid waitpid(-1,status,WNOHANG | WUNTRACED))0){// deletejob(jobs,pid);// }if(errno!ECHILD){// write(STDOUT_FILENO,err_msg,sizeof(err_msg));_exit(1);}errnoolderno;}本质原因是因为waitfg中调用了一次waitpid而且还是阻塞调用导致了信号处理函数在调用waitpid时自然拿不到pid但又必须等待前台任务返回后执行故修改后采用了busy loop with sleepvoidwaitfg(pid_tpid){// 如果相等表示当前前台任务没有被回收while(fgpid(jobs)pid){sleep(1);}}trace05 BUG输入命令# # trace05.txt - Process jobs builtin command. # /bin/echo -e tsh ./myspin 2 \046 ./myspin 2 /bin/echo -e tsh ./myspin 3 \046 ./myspin 3 /bin/echo tsh jobs jobs正确结果polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make rtest05 ./sdriver.pl -t trace05.txt -s ./tshref -a -p # # trace05.txt - Process jobs builtin command. # tsh ./myspin 2 [1] (16002) ./myspin 2 tsh ./myspin 3 [2] (16004) ./myspin 3 tsh jobs [1] (16002) Running ./myspin 2 [2] (16004) Running ./myspin 3 我的结果polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make test05 ./sdriver.pl -t trace05.txt -s ./tsh -a -p # # trace05.txt - Process jobs builtin command. # tsh ./myspin 2 [1] (13301) ./myspin 2 tsh ./myspin 3 会发现在执行./myspin 3 时shell进程被杀死了其原因在sigchld_handler函数在回收进程时由于对全局变量errno判断导致了错误触发了_exit(1)下面是修改后的代码voidsigchld_handler(intsig){intstatus;intoldernoerrno;pid_tpid;while((pidwaitpid(-1,status,WNOHANG|WUNTRACED))0){deletejob(jobs,pid);}// if(errno ! ECHILD){// write(STDERR_FILENO,err_msg,sizeof(err_msg));// char buf[64];// int len sprintf(buf, errno %d\n, errno);// write(STDERR_FILENO, buf, len);// _exit(1);// }errnoolderno;}在多进程中不轻易对全局变量如errno判断下面是导致trace05 BUG流程图子进程 (myspin 3)子进程 (myspin 2)Shell (父进程)测试驱动 (sdriver.pl)子进程 (myspin 3)子进程 (myspin 2)Shell (父进程)测试驱动 (sdriver.pl)脚本模式极速并发触发 EINTRaddjob() 将 JID 设为 1就在 fork() 系统调用内部复制内存、分配 PID 时中断发生fork() 被信号打断返回 -1内核设置 errno EINTR (4)C3 变成孤儿进程被 init 收养1. 发送 ./myspin 2 fork() 创建后台任务2. 几乎同时发送 ./myspin 3 开始 fork() 创建 C33. myspin 2 运行结束内核发送 SIGCHLD 信号4. 立即跳转执行 sigchld_handlerwaitpid() 尝试回收因 C2 已死返回 -1, errnoECHILD 或保持 EINTRShell 进程崩溃测试中断无 [2] 输出每次子进程状态的变化都会发送SIGCHLD进入sigchld_handler程序 但是SIG_CHLD只占有一个bit位也就是说不体现次数这也是使用while回收子进程的原因一次性回收。更完善处理函数如下voidsigchld_handler(intsig){intstatus;intoldernoerrno;pid_tpid;while((pidwaitpid(-1,status,WNOHANG|WUNTRACED))0){if(WIFEXITED(status)||WIFSIGNALED(status))deletejob(jobs,pid);elseif(WIFSTOPPED(status)){structjob_t*jobgetjobpid(jobs,pid);job-stateST;}}errnoolderno;}trace11 BUG输入# # trace11.txt - Forward SIGINT to every process in foreground process group # /bin/echo -e tsh ./mysplit 4 ./mysplit 4 SLEEP 2 INT /bin/echo tsh /bin/ps a /bin/ps a正确答案polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make rtest11 ./sdriver.pl -t trace11.txt -s ./tshref -a -p # # trace11.txt - Forward SIGINT to every process in foreground process group # tsh ./mysplit 4 Job [1] (13795) terminated by signal 2 tsh /bin/ps a PID TTY STAT TIME COMMAND 2675 tty2 Ssl 0:00 /usr/libexec/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODEubuntu /usr/bin/gnome-session --sessionubuntu 2677 tty2 Sl 3:58 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -nolisten tcp -background none -noreset -keeptty -novtswitch -verbose 3 2811 tty2 Sl 0:00 /usr/libexec/gnome-session-binary --sessionubuntu 3888 pts/0 Ss 0:00 bash 4135 pts/1 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 4389 pts/2 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 4616 pts/3 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 5319 pts/4 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 6257 pts/5 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 13790 pts/5 S 0:00 make rtest11 13791 pts/5 S 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tshref -a -p 13792 pts/5 S 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tshref -a -p 13793 pts/5 S 0:00 ./tshref -p 13813 pts/5 R 0:00 /bin/ps a我的答案polpol-Legion-Y7000P-IRX9:~/桌面/shlab-handout$ make test11 ./sdriver.pl -t trace11.txt -s ./tsh -a -p # # trace11.txt - Forward SIGINT to every process in foreground process group # tsh ./mysplit 4 Job [1] (13932) terminated by signal 2 tsh /bin/ps a PID TTY STAT TIME COMMAND 2675 tty2 Ssl 0:00 /usr/libexec/gdm-x-session --run-script env GNOME_SHELL_SESSION_MODEubuntu /usr/bin/gnome-session --sessionubuntu 2677 tty2 Sl 4:02 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -nolisten tcp -background none -noreset -keeptty -novtswitch -verbose 3 2811 tty2 Sl 0:00 /usr/libexec/gnome-session-binary --sessionubuntu 3888 pts/0 Ss 0:00 bash 4135 pts/1 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 4389 pts/2 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 4616 pts/3 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 5319 pts/4 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 6257 pts/5 Ss 0:00 /usr/bin/bash --init-file /usr/share/code/resources/app/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh 13927 pts/4 S 0:00 make test11 13928 pts/4 S 0:00 /bin/sh -c ./sdriver.pl -t trace11.txt -s ./tsh -a -p 13929 pts/4 S 0:00 /usr/bin/perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p 13930 pts/4 S 0:00 ./tsh -p 13933 pts/4 S 0:00 ./mysplit 4 13950 pts/4 R 0:00 /bin/ps a可以看到我的答案./mysplit 4没有被回收原因是我只是杀了一个进程而不是一个进程组进程组setgpid:0,0表示由Parent fork出来的子进程均属于同一个进程组内部 fork内部 fork内部 forkShell进程组长PID101子进程1PID102PGID101子进程1PID102PGID101子进程1PID102PGID101对进程组操作发送信号kill(-pid,sig)信号处理函数职责单一原则/***************** * Signal handlers *****************//* * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever * a child job terminates (becomes a zombie), or stops because it * received a SIGSTOP or SIGTSTP signal. The handler reaps all * available zombie children, but doesnt wait for any other * currently running children to terminate. */voidsigchld_handler(intsig){intstatus;intoldernoerrno;pid_tpid;while((pidwaitpid(-1,status,WNOHANG|WUNTRACED))0){if(WIFEXITED(status)){deletejob(jobs,pid);}elseif(WIFSIGNALED(status)){chars[]Job [] () terminated by signal 2\n;inti[]{pid2jid(pid),pid};sio_putsif(s,i);deletejob(jobs,pid);if(write(STDOUT_FILENO,s,sio_strlen(s))sio_strlen(s))_exit(0);}elseif(WIFSTOPPED(status)){chars[]Job [] () stopped by signal 20\n;inti[]{pid2jid(pid),pid};sio_putsif(s,i);if(write(STDOUT_FILENO,s,sio_strlen(s))sio_strlen(s))_exit(0);structjob_t*jobgetjobpid(jobs,pid);job-stateST;}}errnoolderno;}/* * sigint_handler - The kernel sends a SIGINT to the shell whenver the * user types ctrl-c at the keyboard. Catch it and send it along * to the foreground job. */voidsigint_handler(intsig){pid_tpidfgpid(jobs);if(pid0)return;kill(-pid,SIGINT);}/* * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever * the user types ctrl-z at the keyboard. Catch it and suspend the * foreground job by sending it a SIGTSTP. */voidsigtstp_handler(intsig){pid_tpidfgpid(jobs);if(pid0)return;kill(-pid,SIGTSTP);}参考资料教材Randal E. Bryant, David R. O’Hallaron.Computer Systems: A Programmer’s Perspective(Third Edition). 第 8 章《异常控制流》Exceptional Control Flow.实验来源Carnegie Mellon University (CMU) 15-213 / 18-213 / 15-513:Introduction to Computer Systems. Lab Assignment L5:Writing Your Own Unix Shell (shlab).环境Ubuntu 24.04 / GCC / VSCode GDB 调试.