Solution
注:使用的大写字母开头的函数,都是封装后的函数
例如:
pid_t Fork(void) {
pid_t pid;
if ((pid = fork()) < 0) {
unix_error("Fork error");
}
return pid;
}
这里全部使用的都是代码中提供的unix风格error函数
function eval
→ void
Parameters:
char * cmdline
实现:
void eval(char *cmdline) {
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
int pid;
sigset_t child_mask, prev_mask;
Sigemptyset(&child_mask);
Sigaddset(&child_mask, SIGCHLD);
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL) {
return;
}
if (builtin_cmd(argv)) {
return;
}
int valid = 1;
Sigprocmask(SIG_SETMASK, &child_mask, &prev_mask);
if ((pid = Fork()) == 0) {
Setpgrp();
if (execve(argv[0], argv, environ) < 0) {
valid = 0;
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
if (!valid) {
return;
}
if (!bg) {
addjob(jobs, pid, FG, cmdline);
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);
waitfg(pid);
} else {
addjob(jobs, pid, BG, cmdline);
Sigprocmask(SIG_SETMASK, &prev_mask, NULL);
printf("[%d] (%d) %s", pid2jid(pid), pid, buf);
}
}
思路:
对应解析命令行输入的操作
用一个buf数组来存命令行输入,并且有长度限制
接着使用了提供的帮助函数 parseline
来把参数分离到argv指针数组中,并且用 bg
来接收返回值判断是否后台运行
特判一下无效指令后判断是否是内置指令,如果是的话就立刻执行,不需要添加到joblist中
如果都不是的话就是可执行文件,这里新建一个进程并将其添加到父进程的进程组中
在执行命令之前首先要设置SIGCHILD的信号掩码,防止父进程在添加joblist之前收到信号导致一直存在僵尸进程,并且保存之前的信号掩码用来恢复状态。
Hints里面这样说:
In eval, the parent must use sigprocmask to block SIGCHLD signals before it forks the child,
and then unblock these signals, again using sigprocmask after it adds the child to the job list by
calling addjob. Since children inherit the blocked vectors of their parents, the child must be sure
to then unblock SIGCHLD signals before it execs the new program.
The parent needs to block the SIGCHLD signals in this way in order to avoid the race condition where
the child is reaped by sigchld handler (and thus removed from the job list) before the parent
calls addjob.
这里所有的定义和初始化操作都放在最开头了方便查看
如果不合法设置 valid = 0
,直接返回即可,其实不设置的话也会收到一个SIGCHILD信号来最后删除这个进程,这里直接不添加到joblist中
接下来只需要添加到joblist中就行了,如果在foreground运行就执行 waitfg
函数,否则打印进程信息
function builtin_cmd
→ int
Parameters:
char ** argv
实现:
int builtin_cmd(char **argv) {
if (!strcmp(argv[0], "quit")) {
exit(0);
}
if (!strcmp(argv[0], "jobs")) {
listjobs(jobs);
return 1;
}
if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
do_bgfg(argv);
return 1;
}
if (!strcmp(argv[0], "&")) {
return 1;
}
return 0; /* not a builtin command */
}
思路:
对应执行内部命令的操作
- quit:直接退出即可
- jobs:打印joblist
- do_bgfg:将停止进程调度到前后台
- &:什么也不干
这里quit不会杀子进程,参考了tshref和其他shell中退出也没有杀进程,我的理解是本来的用意就是让shell成为来执行后台操作的一个交互程序
如果满足以上操作,就返回$1$代表是内部命令,否则返回$0$
function do_bgfg
→ void
Parameters:
char ** argv
实现:
void do_bgfg(char **argv) {
struct job_t *job;
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("%s: No such job\n", argv[1]);
return;
}
} else {
int 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;
}
}
kill(-job->pid, SIGCONT);
if (!strcmp(argv[0], "bg")) {
job->state = BG;
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
} else {
job->state = FG;
waitfg(job->pid);
}
return;
}
思路:
如果没有提供id就直接退出
接着判断是jobid还是pid,如果都不是就退出
再用提供的函数来获取对应的job,向进程发送SIGCONT信号继续执行
设置进程相关的信息(foreground or background),如果是后台则打印信息,否则继续调用waitfg等待结束
function waitfg
→ void
Parameters:
pid_t pid (aka int)
实现:
void waitfg(pid_t pid) {
while (fgpid(jobs) == pid) {
sleep(1);
}
}
思路:
根据Hints中的提示:
One of the tricky parts of the assignment is deciding on the allocation of work between the waitfg
and sigchld handler functions. We recommend the following approach:
– In waitfg, use a busy loop around the sleep function.
– In sigchld handler, use exactly one call to waitpid.
使用sleep来实现,课本上标注了使用sigsuspend的实现更好
function sigchld_handler
→ void
Parameters:
int sig
实现:
void sigchld_handler(int sig) {
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
int status;
struct job_t *job;
Sigfillset(&mask_all);
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
job = getjobpid(jobs, pid);
if (WIFEXITED(status)) {
deletejob(jobs, pid);
} else if (WIFSIGNALED(status)) {
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid,
WTERMSIG(status));
deletejob(jobs, pid);
} else if (WIFSTOPPED(status)) {
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid,
WSTOPSIG(status));
}
Sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
errno = olderrno;
}
思路:
保存错误码常规操作
waitpid中使用WNOHANG和WUNTRACED的参数,表示立刻返回,如果等待集合中的子进程都没有被停止或终止,则返回值为0,否则为pid
配合循环效果是不断处理停止或终止的进程,处理的过程需要阻止所有信号,防止冲突导致没有成功设置子进程的状态
接着通过pid获取job,通过WIFEXITED等宏来检测状态,如果运行完成终止就调用deletejob删除job,如果被信号终止了就额外打印一条信号信息,如果收到停止信号,则设置job状态并打印信息
最后再恢复信号掩码和错误码
function sigint_handler
→ void
Parameters:
int sig
实现:
void sigint_handler(int sig) {
int olderrno = errno;
pid_t pid = fgpid(jobs);
if (pid != 0) {
Kill(-pid, SIGINT);
}
errno = olderrno;
return;
}
思路:
收到终止信号,直接获取pid,使用Kill函数发送SIGINT信号即可
function sigtstp_handler
→ void
Parameters:
int sig
实现:
void sigtstp_handler(int sig) {
int olderrno = errno;
pid_t pid = fgpid(jobs);
if (pid != 0) {
Kill(-pid, SIGTSTP);
}
errno = olderrno;
return;
}
思路:
收到停止信号,直接获取pid,使用Kill函数发送SIGTSTP信号即可