shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容。难度上如果熟读了《CSAPP》的“异常控制流”一章,应该是可以不算困难的写出来。但如果读书不仔细,或者实践的时候忘记了部分细节,那就可能完全不知道怎么下手,或者得改bug改到吐了。我自己写了大概八个小时,其中仅一半的时间都在处理收到SIGTSTP后莫名卡死的问题,最后才发现是课本没看仔细,子进程停止后也会向父进程发送SIGCHLD

在实验中我们需要实现job、fg、bg、kill四个内建命令和对执行本地程序的支持,并且还要处理好SIGCHLDSIGINTSIGTSTP这几个信号。关键要点都在课本的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 实验笔记的更多相关文章

  1. CSAPP shell Lab 详细解答

    Shell Lab的任务为实现一个带有作业控制的简单Shell,需要对异常控制流特别是信号有比较好的理解才能完成.需要详细阅读CS:APP第八章异常控制流并理解所有例程. Slides下载:https ...

  2. 【CSAPP】Cache Lab 实验笔记

    cachelab这节先让你实现个高速缓存模拟器,再在此基础上对矩阵转置函数进行优化,降低高速缓存不命中次数.我的感受如上一节,实在是不想研究这些犄角旮旯的优化策略了. 前期准备 我实验的时候用到了va ...

  3. 【CSAPP】Attack Lab实验笔记

    attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...

  4. 【CSAPP】Performance Lab 实验笔记

    perflab这节的任务是利用书中知识,来对图像处理中的Rotate和Smooth操作函数进行优化.这次没对上电波,觉得学了一堆屠龙之技.于我个人理解,现在计算机配置比以前高多了,连SWAP分区都几近 ...

  5. 【CSAPP】Architecture Lab 实验笔记

    archlab属于第四章的内容.这章讲了处理器体系结构,就CPU是怎样构成的.看到时候跃跃欲试,以为最后实验是真要去造个CPU,配套资料也是一如既往的豪华,合计四十多页的参考手册,一大包的源码和测试程 ...

  6. 【CSAPP】Bomb Lab实验笔记

    bomblab这节搞的是二进制拆弹,可以通俗理解为利用反汇编知识找出程序的六个解锁密码. 早就听闻BOMBLAB的大名,再加上我一直觉得反汇编是个很艰难的工作,开工前我做好了打BOSS心理准备.实际上 ...

  7. 【CSAPP】Data Lab实验笔记

    前天讲到要刚CSAPP,这一刚就是两天半.CSAPP果然够爽,自带完整的说明文档,评判程序,辅助程序.样例直接百万组走起,管饱! datalab讲的是整数和浮点数怎么用二进制表示的,考验的是用基本只用 ...

  8. ChCore Lab3 用户进程和异常处理 实验笔记

    本文为上海交大 ipads 研究所陈海波老师等人所著的<现代操作系统:原理与实现>的课程实验(LAB)的学习笔记的第三篇:用户进程与异常处理.所有章节的笔记可在此处查看:chcore | ...

  9. 深入理解计算机系统项目之 Shell Lab

    博客中的文章均为meelo原创,请务必以链接形式注明本文地址 Shell Lab是CMU计算机系统入门课程的一个实验.在这个实验里你需要实现一个shell,shell是用户与计算机的交互界面.普通意义 ...

随机推荐

  1. javax.net.ssl.sslhandshakeException:sun.security.validator.validatorException:PKIX path buildind failed

    前段时间开发的一个需求,需要通过图片URL获取图片的base64编码,测试的时候使用的是百度图片的url,测试没有问题,但是发布后测试时报如下错: javax.net.ssl.sslhandshake ...

  2. 学习Apache(三)

    对某个目录开启验证登录 <Directory /var/www/html/admin > AllowOverride All Order allow,deny Allow from all ...

  3. 学习openldap01

    Linux 下openldap的详细介绍,搭建,配置管理,备份,案例 Ldap  服务应用指南 兼容(5.X&6.X) 1.1  Ldap 目录服务介绍 1.1.1 什么是目录服务(activ ...

  4. Linux 安装jdk1.8

    Linux安装jdk1.8 总结一句话就是:下载jdk1.8 ==> 解压 ==> 配置环境变量. 一.  jdk的下载,这里有两种方法: 1.去Oracle官网下载. 2.jdk1.8的 ...

  5. jupyter notebook使用技巧

    shift + tab 键可以查看对应源代码(注意:需要先将代码运行才能查看) Jupyter Notebook 的快捷键 Jupyter Notebook 有两种键盘输入模式:1.命令模式,键盘输入 ...

  6. 告别尬聊,解锁秀场+社交新玩法(内含源码+Demo)

    直播已成为用户的生活习惯之一 艾媒咨询数据显示:2021年直播用户规模达到6.35亿人,在线直播用户以年轻群体为主,24岁及以下用户占比49%,30岁以下用户接近8成. 众所周知,Z世代用户是一个社交 ...

  7. kbengine开源分布式游戏服务端引擎

    一款开源的支持多人同时在线实时游戏的服务端引擎,使用简单的约定协议就能够使客户端与服务端进行交互,使用KBEngine插件能够快速与(Unity3D.OGRE.Cocos2d.HTML5,等等)技术结 ...

  8. JS+CSS3 360度全景图插件 - Watch3D.js

    日常闲扯 从上一篇文章到这篇中间快过了一年了,时间真滴过得快.不是在下中间没想过写新的文章,而是自己确实变懒了(体重+1 +1 +1 +1....) ..OTL...不过到最后觉得还是需要写点东西,不 ...

  9. web开发者踏入人工智能的利器_Tensorflow.js

    前言 最近公司向员工搜集公司杂志的文章,刚好最近学习了机器学习相关课程.为了赚取购买课程的费用,所以写了如下文章投稿赚取稿费. 如下文章可能涉及一些我所购买课程的内容,所以不便将所有资源进行展示. 当 ...

  10. python爬虫---爬取网易云音乐

    代码: import requests from lxml import etree text = requests.get("https://music.163.com/discover/ ...