linking的部分看了一半跳过去了....据说你南的ICS讲linking非常不错,于是我就心安理得了
exception的部分还在看,估计这周末可以看完(吧),先留坑
我来填坑辣
终于赶在五一(假期前)写完了第六个lab,书也推完了第八章,四舍五入就是五月前搞定了前八章内容,还是很不戳的。整个四月都非常忙,各种ddl和期中满天飞。五月也不空闲,还有EL、听说读写ddl、马原、银川、大雾期中、军理。希望人没事.jpg
这次lab的一个明显特点就是答案都可以在书上找到,各种详细的例子书上都有,因此写起来只需要多翻书就好了。不过由于这次lab的测试比较弱,而我对自己的代码水平又没有什么信心,因此下面贴的代码看看就好,莫当真
说了不少废话,讲正题吧
前置姿势
这一章主要从硬件和软件(系统)两个层面讲了异常控制流(exceptional control flow)的各种姿势,这次的lab主要关注的是软件方面的东西。软件的控制流交移是通过信号(signal)的发送和接受来进行的,这一点因为写过QT所以不是特别虚。看前面的内容如果不过瘾还可以搭配OS的教材一起看,正好就顺手入了SJTU的书,希望暑假能啃一啃。
本章引入的鲁棒性的问题非常重要,也就是多线程过程中的竞争问题。这种问题常常发生在不同线程对统一数据的修改和读取中。为了解决这个问题本章引入了原子性的概念,大意就是某些操作不可分割、控制流不能从中间被移接。事实上还有锁的概念,大意就是在修改、读取数据的时候阻塞其余信号,也就是给数据“上锁”
本次lab的测试非常之水,毕竟系统方面的测试用用例还是比较难调出问题(我对形式化验证这方面也很感兴趣)。
代码
eval
这一部分可以抄书。具体需要搞清楚fork()
会创建一个新的进程,而execve()
则相当于用新的进程"覆盖"当前进程。因此如果想要实现shell的调用需要先fork()
再在子进程里execve()
根据writeup的提示需要给子进程分配组id,在eval
中新建进程需要调用addjob()
来修改进程表中的内容,因此需要阻塞其它信号以免出现问题。
void eval(char *cmdline)
{
int olderrno = errno;
char *argv[MAXARGS];
int bg = parseline(cmdline, argv);
if (argv[0] == NULL) return ;
if ( builtin_cmd(argv) ) return ;
pid_t pid;
// Child Process
sigset_t mask, prev;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prev);
pid = fork();
if (pid < 0)
unix_error("Fork error");
if ( pid == 0) {
sigprocmask(SIG_BLOCK, &prev, NULL);
setpgid(0, 0);
if ( execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found
", argv[0]);
exit(0);
}
}
if (!bg) {
addjob(jobs, pid, FG, cmdline);
sigprocmask(SIG_SETMASK, &prev, NULL);
waitfg(pid);
} else {
addjob(jobs, pid, BG, cmdline);
struct job_t *job = getjobpid(jobs, pid);
printf("[%d] (%d) %s", job->jid, job->pid, cmdline);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
errno = olderrno;
return;
}
builtin_cmd
这个估计是最好写的,判断一下命令类型就好了。quit
和jobs
都很好写,fg
和bg
的具体操作被封装在do_bgfg()
里面了,在这里直接用就好了
int builtin_cmd(char **argv)
{
char *builtin_args[4] = {"quit", "bg", "fg", "jobs"};
for (int i = 0; i < 4; ++ i) {
if ( strcmp(builtin_args[i], argv[0]) ) continue;
switch (argv[0][0]) {
case 'q': exit(0);
case 'f': {
do_bgfg(argv);
break;
}
case 'b': {
do_bgfg(argv);
break;
}
case 'j': listjobs(jobs);
}
return 1;
}
return 0; /* not a builtin command */
}
waitfg
这一段也很好写。这是用来等待前台进程结束的,那么就用一个死循环来不停地监听pid
进程是否在前台
由于在监听的时候访问了全局数据结构,因此也要阻塞信号
void waitfg(pid_t pid)
{
sigset_t mask, prev;
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev);
struct job_t *job = getjobpid(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
for (; job != NULL && job->state == FG; ) {
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev);
job = getjobpid(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
return;
}
do_bgfg
这个是最后写的,毕竟bg
和fg
我也是上个月才会用的.....
大概就是利用kill
发送信号,同时修改job
对应的state
。在发送信号的时候也要阻塞其余信号因为访问了全局数据结构
这里还要判一下输入是否合法,这个比较麻烦不过对着trace
慢慢搞就好了
void do_bgfg(char **argv)
{
char *id_str = argv[1];
//fg bg error handling
if (id_str == NULL) {
printf("%s command requires PID or %%jobid argument
", argv[0]);
return ;
}
int flag = (id_str[0] == '%'), jid, pid;
id_str += flag;
for (int i = 0, _len = strlen(id_str); i < _len; ++ i) {
if (!isdigit(id_str[i])) {
printf("%s: argument must be a PID or %%jobid
", argv[0]);
return ;
}
}
sigset_t mask, prev;
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev);
if (!flag) {
sscanf(id_str, "%d", &pid);
jid = pid2jid(jid);
} else {
sscanf(id_str, "%d", &jid);
}
struct job_t *job = getjobjid(jobs, jid);
if (job == NULL) {
if (flag) printf("%%%d: No such job
", jid);
else printf("(%d): No such process
", pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
return ;
}
if (argv[0][0] == 'b') {
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
job->state = BG;
kill(-(job->pid), SIGCONT);
} else if (argv[0][0] == 'f') {
job->state = FG;
kill(-(job->pid), SIGCONT);
sigprocmask(SIG_SETMASK, &prev, NULL);
waitfg(job->pid);
}
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}
hanlders
三个放在一起说
sigint_handler()
最好写,只需要利用fgpid()
搭配kill()
就好了
注意要阻塞信号
void sigint_handler(int sig)
{
int olderrno = errno;
sigset_t mask, prev;
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev);
pid_t pid = fgpid(jobs);
if (pid == 0) return ;
struct job_t *job = getjobpid(jobs, pid);
if (kill(-pid, sig) < 0)
unix_error("Sigint error");
sigprocmask(SIG_SETMASK, &prev, NULL);
errno = olderrno;
return;
}
sigstp_handler()
和上面是一样的,就不说了
需要注意的是,在这两个handler中我们没必要更改数据结构,而是可以等sigchld_handler()
一起改,这样写起来可以清晰一些
sigchld_handler()
有几个坑点
- 要注意
waitpid()
默认行为是挂起父进程直至子进程终止(terminate),写到这里的时候记得往回翻书,或者看writeup的提示也行 - 好像就没了....
回收的时候需要对子进程的死因讨论一下,同时记得阻塞就好了
在读这一章的时候深刻体会到了术语明确的重要性,比如说停止(stop)和终止(terminate)和中断(interrupt)这三个,放在中文里感觉就没有区别啊
void sigchld_handler(int sig)
{
int olderrno = errno;
int status;
pid_t pid = waitpid(-1, &status, WNOHANG | WUNTRACED);
if (!pid) return ;
sigset_t mask, prev;
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &prev);
struct job_t *job = getjobpid(jobs, pid);
if (WIFSTOPPED(status)) {
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d
", job->jid, job->pid, WSTOPSIG(status) );
} else {
if (!WIFEXITED(status))
printf("Job [%d] (%d) terminated by signal %d
", job->jid, job->pid, WTERMSIG(status) );
deletejob(jobs, job->pid);
}
sigprocmask(SIG_SETMASK, &prev, NULL);
errno = olderrno;
return;
}
于是就写完辣!
最后的最后我还写了一个小程序来快速检验答案是否正确,大概长这样
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char str1[200];
int main(int argc, char const *argv[])
{
system("make clean; make");
for (int i = 1; i <= 16; ++ i) {
sprintf(str1, "make test%02d > myp%02d.out", i, i);
system(str1);
sprintf(str1, "make rtest%02d > std%02d.out", i, i);
system(str1);
printf("id: %d completed
", i);
}
for (int i = 1; i <= 16; ++ i) {
sprintf(str1, "wc -l std%02d.out", i);
system(str1);
sprintf(str1, "wc -l myp%02d.out", i);
system(str1);
}
return 0;
}
当然这个只是初步的,具体正确性还要自行比对一下....不过这个看起来也是可以自动化的,我太懒了就鸽了吧,毕竟要考大雾了淦