【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是用户与计算机的交互界面.普通意义 ...
随机推荐
- Linux Centos7使用ping命令ping不通网络的解决方案
本解决方案不配置dns,都是ping的IP地址,所以如果想ping域名,则加上DNS项的配置后自行尝试吧 我使用的虚拟机系统信息: Linux:Centos7 Network:虚拟机设置的桥接模式(自 ...
- String、StringBuiler、StringBuffer的区别
一.三者的区别概述 1.可变与不可变:String底层使用final修饰的字符数组来存储字符串,它属于不可变类,对String对象的任何改变操作都不会改变原对象,而是生成一个新对象.StringBui ...
- hdu 1175 连连看 DFS_字节跳动笔试原题
转载至:https://www.cnblogs.com/LQBZ/p/4253962.html Problem Description "连连看"相信很多人都玩过.没玩过也没关系, ...
- 怎么将 byte 转换为 String?
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用 的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也 可能不同.
- windows编写sh脚本在linux上不能执行
报错:/bin/sh^M:bad interpreter: 编码没有被识别, vi *.sh Esc 输入 :set fileformat 查看文件格式(显示 fileformat=dos) Esc ...
- BMZCTF 端午节就该吃粽子
端午节就该吃粽子 题目如下让我们访问login.php 然后就一个登录界面查看源码发现index.php 我们直接访问发现没有结果使用伪协议读取 然后我们使用base64解密 <?php err ...
- ESD@TVS选型
一.工作原理 ESD ESD静电保护元件,又称静电抑制二极管.ESD是多个TVS晶粒或二极管采用不同的布局做成具有特定功能的多路或单路ESD保护器件,主要应用于各类通信接口静电保护,如USB.HDMI ...
- CSS揭秘之《多重边框》
1.box-shadow还接受第四个参数(称作"扩张半径"), 通过指定正值或负值, 可以让投影面积加大或者减小2.如果我们想要一道实线边框其实也是可以通过box-shadow来模 ...
- Vue-cli的打包初体验
前言:我司是一个教育公司,最近要做一个入学诊断的项目,领导让我开始搭建一套基于vue的H5的开发环境.在网上搜集很多的适配方案,最终还是选定flexible方案.选择它的原因很简单: 它的github ...
- javaweb之删除功能
对数据库的删除,主要是通过表中的一个数据查询来进行逐个删除,否则会清空整张表. 一.dao层 在dao层加入删除方法 public boolean delete(Course n) { boolean ...