【CSAPP】Shell Lab 实验笔记
shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容。难度上如果熟读了《CSAPP》的“异常控制流”一章,应该是可以不算困难的写出来。但如果读书不仔细,或者实践的时候忘记了部分细节,那就可能完全不知道怎么下手,或者得改bug改到吐了。我自己写了大概八个小时,其中仅一半的时间都在处理收到SIGTSTP
后莫名卡死的问题,最后才发现是课本没看仔细,子进程停止后也会向父进程发送SIGCHLD
。
在实验中我们需要实现job、fg、bg、kill四个内建命令和对执行本地程序的支持,并且还要处理好SIGCHLD
、SIGINT
、SIGTSTP
这几个信号。关键要点都在课本的534页有说过了:
处理程序尽可能简单
处理程序中只用异步信号安全的函数
保存恢复errno
访问共享全局变量时阻塞所有信号
volatile声明全局变量
sig_atiomic_t声明标志
验收标准这一块因为是在实际操作系统上跑的,不能保证进程号相同,但要保证处理进程号意外所有指令的顺序和信息都要与参考程序的输出完全相同。这点可以用linux上的各种diff工具进行结果比较。
eval
eval函数在课本P525页有一个缺陷版,我们要做的就是以此为蓝本加上点信号处理。
void eval(char* cmdline)
{
char* argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL) {
return;
}
if (!builtin_cmd(argv)) {
sigset_t mask_chld, prev_mask, mask_all;
sigemptyset(&mask_chld);
sigaddset(&mask_chld, SIGCHLD);
sigfillset(&mask_all);
/*因为子进程可能在addjob前就结束并调用deleltejob,所以我们要先阻塞掉SIGCHLD,
保证addjob操作成功*/
sigprocmask(SIG_BLOCK, &mask_chld, &prev_mask);
if ((pid = fork()) == 0) {
//子进程默认继承父进程的mask,所以这里要恢复一下
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
setpgid(0, 0); //令进程组号等于进程号
if (execve(argv[0], argv, environ) <= 0) {
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
// addjob涉及到全局变量的操作,需要保证操作的原子性,故这里阻塞掉所有信号
sigprocmask(SIG_SETMASK, &mask_all, NULL);
addjob(jobs, pid, bg?BG:FG, cmdline);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
// 在线程终止前需要打印些相关信息,所以addjob完还要阻塞一会儿SIGCHLD
sigprocmask(SIG_BLOCK, &mask_chld, NULL);
if (!bg) {
waitfg(pid);
} else {
// 同上,操作全局变量时阻塞
sigprocmask(SIG_SETMASK, &mask_all, NULL);
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
// 操作结束后解除阻塞
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
}
return;
}
waitfg
waitfg负责等待前台进程结束。每次都调用fgpid有点低效了,我们直接用一个全局标志fg_child_flag
代表前台进程是否异常,默认为0,如果切换到停止或退出状态就置1。
void waitfg(pid_t pid)
{
sigset_t mask_empty;
sigemptyset(&mask_empty);
fg_child_flag = 0;
while(!fg_child_flag){
// 参考课本545页,挂起进程直到任意信号到达
sigsuspend(&mask_empty);
}
return;
}
sigchld_handler
要注意子进程终止或停止都可能触发SIGCHLD
,所以我们得分类讨论。
void sigchld_handler(int sig)
{
int olderrno=errno;
sigset_t mask_all, prev_mask;
pid_t pid;
int status;
sigfillset(&mask_all);
// 这里一定要设置成WUNTRACED,否则在子进程处于停止状态时会卡死
if((pid=waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){
// 涉及到对全局变量jobs的访问,阻塞所有信号
sigprocmask(SIG_SETMASK, &mask_all, &prev_mask);
struct job_t *job = getjobpid(jobs, pid);
if(job->state == FG){ // 子进程为前台进程,打开标志
fg_child_flag=1;
}
if(WIFEXITED(status)){ // 正常退出,删除任务即可
deletejob(jobs, pid);
}
else if(WIFSIGNALED(status)){ // 收到信号非正常退出,打印消息后删除任务
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, pid, WTERMSIG(status));
deletejob(jobs, pid);
}
else if(WIFSTOPPED(status)){ // 子进程处于停止状态,切换对应的任务状态
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d\n", job->jid ,pid, WSTOPSIG(status));
}
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
}
errno=olderrno;
return;
}
sigint_handler
因为进程在终止时会自动向父进程发送SIGCHLD
信号,所以部分逻辑放在了sigchld_handler,这里只要对子进程发出SIGINT
信号就行
void sigint_handler(int sig)
{
sigset_t mask_all, prev_mask;
sigfillset(&mask_all);
// 访问全局变量,阻塞所有信号
sigprocmask(SIG_SETMASK, &mask_all, &prev_mask);
int pid = fgpid(jobs);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
if(pid > 0){
kill(-pid, SIGINT); // 对子进程及其后代发送,故加负号
}
return;
}
sigtstp_handler
设计思路同上
void sigtstp_handler(int sig)
{
sigset_t mask_all, prev_mask;
sigfillset(&mask_all);
// 访问全局变量,阻塞所有信号
sigprocmask(SIG_SETMASK, &mask_all, &prev_mask);
int pid = fgpid(jobs);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
if(pid > 0){
kill(-pid, SIGTSTP); // 对子进程及其后代发送,故加负号
}
return;
}
builtin_cmd
仍旧参考课本525页,挨个命令strcmp
就行
int builtin_cmd(char** argv)
{
if (!strcmp(argv[0], "quit")) {
exit(0);
}
if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) {
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0], "jobs")) {
//访问全局变量,阻塞所有信号
sigset_t mask_all, prev_mask;
sigfillset(&mask_all);
sigprocmask(SIG_SETMASK, &mask_all, &prev_mask);
listjobs(jobs);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
return 1;
}
if(!strcmp(argv[0], "kill")){
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0], "&")){
return 1;
}
return 0; /* not a builtin command */
}
do_bgfg
这个也没啥难度,对着参考输出慢慢地添加判断细节就行
void do_bgfg(char** argv)
{
sigset_t mask_all, prev_mask;
sigfillset(&mask_all);
// 访问全局变量jobs,阻塞所有信号
sigprocmask(SIG_SETMASK, &mask_all, &prev_mask);
struct job_t *job;
int pid;
if(argv[1] == NULL){
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}
else if(argv[1][0] == '%'){
int jid = atoi(argv[1] + 1);
job = getjobjid(jobs, jid);
if(job == NULL) {
printf("%%%d: No such job\n", jid);
return;
}
pid = job->pid;
}
else {
pid = atoi(argv[1]);
if(pid <= 0){
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
job = getjobpid(jobs, pid);
if(job == NULL){
printf("(%d): No such process\n", pid);
return;
}
}
if(!strcmp(argv[0], "bg")){
job->state = BG;
printf("[%d] (%d) %s", job->jid, pid, job->cmdline);
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
kill(-pid, SIGCONT); // 对子进程及其后代发送,故加负号
return;
}
else if(!strcmp(argv[0], "fg")){
job->state = FG;
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
kill(-pid, SIGCONT); // 对子进程及其后代发送,故加负号
waitfg(pid); // 子进程切换到了前台,故要等待它执行完
return;
}
else if(!strcmp(argv[0], "kill")){
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
kill(-pid,SIGQUIT); // 对子进程及其后代发送,故加负号
return;
}
return;
}
到这所有的实现都捋完一遍了。做这个实验的缘由是在看数据库网课,讲到缓存管理的时候老师说这一块儿知识和你们学操作系统文件系统管理的知识一个样,只不过我们为了效率得另写一套。然后我发现这块知识快忘光了,得补补操作系统,刚好CSAPP还剩下几个实验当初不屑做,干脆一块搞了吧。
【CSAPP】Shell Lab 实验笔记的更多相关文章
- CSAPP shell Lab 详细解答
Shell Lab的任务为实现一个带有作业控制的简单Shell,需要对异常控制流特别是信号有比较好的理解才能完成.需要详细阅读CS:APP第八章异常控制流并理解所有例程. Slides下载:https ...
- 【CSAPP】Cache Lab 实验笔记
cachelab这节先让你实现个高速缓存模拟器,再在此基础上对矩阵转置函数进行优化,降低高速缓存不命中次数.我的感受如上一节,实在是不想研究这些犄角旮旯的优化策略了. 前期准备 我实验的时候用到了va ...
- 【CSAPP】Attack Lab实验笔记
attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...
- 【CSAPP】Performance Lab 实验笔记
perflab这节的任务是利用书中知识,来对图像处理中的Rotate和Smooth操作函数进行优化.这次没对上电波,觉得学了一堆屠龙之技.于我个人理解,现在计算机配置比以前高多了,连SWAP分区都几近 ...
- 【CSAPP】Architecture Lab 实验笔记
archlab属于第四章的内容.这章讲了处理器体系结构,就CPU是怎样构成的.看到时候跃跃欲试,以为最后实验是真要去造个CPU,配套资料也是一如既往的豪华,合计四十多页的参考手册,一大包的源码和测试程 ...
- 【CSAPP】Bomb Lab实验笔记
bomblab这节搞的是二进制拆弹,可以通俗理解为利用反汇编知识找出程序的六个解锁密码. 早就听闻BOMBLAB的大名,再加上我一直觉得反汇编是个很艰难的工作,开工前我做好了打BOSS心理准备.实际上 ...
- 【CSAPP】Data Lab实验笔记
前天讲到要刚CSAPP,这一刚就是两天半.CSAPP果然够爽,自带完整的说明文档,评判程序,辅助程序.样例直接百万组走起,管饱! datalab讲的是整数和浮点数怎么用二进制表示的,考验的是用基本只用 ...
- ChCore Lab3 用户进程和异常处理 实验笔记
本文为上海交大 ipads 研究所陈海波老师等人所著的<现代操作系统:原理与实现>的课程实验(LAB)的学习笔记的第三篇:用户进程与异常处理.所有章节的笔记可在此处查看:chcore | ...
- 深入理解计算机系统项目之 Shell Lab
博客中的文章均为meelo原创,请务必以链接形式注明本文地址 Shell Lab是CMU计算机系统入门课程的一个实验.在这个实验里你需要实现一个shell,shell是用户与计算机的交互界面.普通意义 ...
随机推荐
- IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低.它使应用容易测试,单元测试不再需要单例和JNDI查找机制.最小的代价和最小的侵入性使松散耦合得以实现.IOC容器支持加载服务时的饿汉式初始化和懒加载.
- 什么是RabbitMQ?RabbitMQ的使用场景是什么?
参考链接:RabbitMQ 简介以及使用场景
- synchronized 关键字的用法?
synchronized 关键字可以将对象或者方法标记为同步,以实现对对象和方法的互 斥访问,可以用 synchronized(对象) { - }定义同步代码块,或者在声明方法时 将 synchron ...
- 什么是redis的缓存雪崩与缓存穿透?如何解决?
一.缓存雪崩 1.1 什么是缓存雪崩? 首先我们先来回答一下我们为什么要用缓存(Redis): 1.提高性能能:缓存查询是纯内存访问,而硬盘是磁盘访问,因此缓存查询速度比数据库查询速度快 2.提高并发 ...
- mysql 的INNODB引擎和MYISAM引擎的区别、索引相关
两个引擎都是使用B+tree 数据结构作为索引 不同点: 1.INNODB的主键必须要有,同时也是聚集索引,INNODB的数据文件本身就是索引文件:而MYISAM则是存储了数据的地址 2.INNODB ...
- Python - 字符串基础知识
- 解决Mui中popover 顶部弹出菜单弹出位置不准确以及无法收回的问题
前言 最近公司的项目转向使用Hbuilder开发移动端项目,其中想要通过在顶部标题栏加入弹出菜单的方式,来定位长列表的位置,如图所示. 问题 Mui的功能貌似还不是很完善,在使用这个弹出菜单的时候,发 ...
- 前端面试题整理——VUE双向绑定原理
VUE2.0和3.0数据双向绑定的原理,并说出其区别. 代码: <!DOCTYPE html> <html lang="en"> <head> ...
- Anaconda安装tensorflow和keras(gpu版,超详细)
本人配置:window10+GTX 1650+tensorflow-gpu 1.14+keras-gpu 2.2.5+python 3.6,亲测可行 一.Anaconda安装 直接到清华镜像网站下载( ...
- java中为什么把Checked Exception翻译成受检的异常?
6.Checked Exception(受检的异常) 马克-to-win:为什么我大胆的把Checked Exception翻译成受检的异常?因为这类异常,编译器检查发现到它后会强令你catch它或t ...