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还剩下几个实验当初不屑做,干脆一块搞了吧。

最新文章

  1. is_null, empty, isset, unset对比
  2. Windows 10 Build 14997中Edge浏览器已默认阻止Flash运行
  3. iOS应用程序生命周期(前后台切换,应用的各种状态)详解
  4. (分享)Paxos在大型系统中常见的应用场景
  5. Python isdigit()方法
  6. 收藏本网站兼容火狐IE
  7. [转]如何利用ndk-stack工具查看so库的调用堆栈【代码示例】?
  8. Codevs 1138 聪明的质监员 2011年NOIP全国联赛提高组
  9. iOS网络通信类库
  10. PHP判断访客是否移动端浏览器访问
  11. Oracle中decode函数 列变成行
  12. 201521123076 《Java程序设计》第11周学习总结
  13. Python实现翻译功能
  14. 回收 PV - 每天5分钟玩转 Docker 容器技术(152)
  15. mysql 表结构及基本操作
  16. MD5 Hashing in Java
  17. 发生服务器错误: Error loading MySQLdb module: libmysqlclient.so.18: cannot open shared object file: No such file or directory
  18. python二叉树练习
  19. weblogic安装部署war包——windows
  20. Imgproc.findContours函数

热门文章

  1. ServletConfig对象和ServletContext对象有什么区别?
  2. Canvas将图片转换成base64形式展示的常见问题及解决方案
  3. vue使用svg,animate事件绑定无效问题及解决方法
  4. 为什么使用 Executor 框架?
  5. Redis 集群方案什么情况下会导致整个集群不可用?
  6. 抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被 synchronized 修饰?
  7. 基于redis实现未登录购物车
  8. 调用高德地图web api 规划路线
  9. git 泄露(Log、Stash、Index)svn泄露
  10. 纯css模拟电子钟