这次的实验要求我们实现一个简化版的shell,需要能够实现子程序的前台与后台运行以及切换,同时实现几条内建命令。
需要我们实现的内建命令包括以下4个:
- quit: 退出shell
- jobs: 打印子进程列表与运行状态
- fg: 切换进程到前台运行
- bg: 切换进程到后台运行
**实验之前一定要认真读[手册](http://csapp.cs.cmu.edu/3e/shlab.pdf)和课本**,在进行实验的时候也要时刻翻看课本,想清楚每一步之的前后关联。下面放上代码与思路。
eval()
void eval(char *cmdline)
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
sigset_t mask_one, mask_all, pre_mask;
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL)
return;
if (!builtin_cmd(argv))
{
sigprocmask(SIG_BLOCK, &mask_one, &pre_mask);//阻断SIGCHLD
if ((pid = fork()) == 0)
{
setpgid(0, 0); //*******很重要
sigprocmask(SIG_SETMASK, &pre_mask, NULL);
if (execve(argv[0], argv, environ) < 0)
{
printf("%s: Command not found
", argv[0]);
exit(0);
}
}
sigprocmask(SIG_BLOCK, &mask_all, NULL);//addjob涉及全局变量访问
if (bg)
addjob(jobs, pid, BG, buf);
else
addjob(jobs, pid, FG, buf);
if (!bg)
waitfg(pid);
else
{
struct job_t *curr_bgmask = getjobpid(jobs, pid);
printf("[%d] (%d) %s", curr_bgmask->jid, curr_bgmask->pid, curr_bgmask->cmdline);
}
sigprocmask(SIG_SETMASK, &pre_mask, NULL);
}
return;
}
这个函数本身比较简单,书上的代码拿来改改就能用,但是有一个特别重要的问题需要注意
大体意思就是在Unix shell上运行我们自己的shell时,需要在创建出一个子进程之后调用setpgid(0, 0)
将他们放到一个新的进程组,否则我们的ctrl-c
与ctrl-z
会把信号发送到包括我们的shell在内的全部进程中,就是因为没注意这个使得我的shell产生的很多迷幻的错误,以至于调试很久却不知道原因(其实只能怪自己没认真看手册)。
waitfg()
对于waitfg()函数,我想过几个实行方案:
waitfg1()
# This is a buggy code.
void waitfg(pid_t pid)
{
int status;
if (waitpid(pid,&status,0) < 0)
unix_error("waitfg: waitpid error
");
printf("wait %d ok
",pid);
return;
}
void sigchld_handler(int sig)
{
pid_t pid;
int old_errno = errno;
while ((pid = waitpid(-1, NULL, 0)) > 0) //这里必须是while,书上讲的很明白了
printf("Handler reaped child %d
", (int)pid);
if (errno != ECHILD)
unix_error("waitpid error");
errno = old_errno;
return;
}
代码是之前的,省略了一些不相关的细节。
我们直接在waitfg
里面调用 waitpid
函数,但是这样做有个很明显的问题,就是在回收后台进程时,如果在sigchld_handler
函数的执行阶段前台进程结束了,会被一并回收掉,从而导致waitfg
中的waitpid
回收不到前台进程,从而产生一个错误。
waitfg2()
# This is a buggy code.
void waitfg(pid_t pid)
{
int status;
sigset_t mask_chld,pre_mask;
sigemptyset(&mask_chld);
sigaddset(&mask_chld,SIGCHLD);
sigprocmask(SIG_SETMASK,&mask_chld,&pre_mask);
if (waitpid(pid,&status,0) < 0)
unix_error("waitfg: waitpid error
");
printf("wait %d ok
",pid);
sigprocmask(SIG_SETMASK,&pre_mask,NULL);
return;
}
加入我们再waitfg中加入对SIGCHLD信号的阻塞,这样可以保证前台进程能被正确回收,但是问题在于我们执行前台进程时不会处理SIGCHLD信号,这样子进程就不能被回收,如果前台进程一直运行,就会导致后台进程退出后全部变成僵死进程,浪费系统资源。
所以我们最终还是要按照书上的思路来实现
waitfg()
void waitfg(pid_t pid)
{
sigset_t mask_empty, mask_all, mask_pre;
sigemptyset(&mask_empty);
sigfillset(&mask_all);
sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //fgpid涉及全局变量的访问
while (pid == fgpid(jobs))
sigsuspend(&mask_empty);
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
return;
}
sigchld_handler()
void sigchld_handler(int sig)
{
pid_t pid;
int status;
int old_errno = errno;
sigset_t mask_empty, mask_all, mask_pre;
sigemptyset(&mask_empty);
sigfillset(&mask_all);
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
sigprocmask(SIG_BLOCK, &mask_all, &mask_pre);
if (WIFSTOPPED(status)) //暂停
{
struct job_t *job = getjobpid(jobs, pid);
job->state = ST;
printf("Job [%d] (%d) stopped by signal %d
",job->jid, pid, WSTOPSIG(status));
}
else
{
if (WIFSIGNALED(status)) //被信号退出
{
struct job_t *job = getjobpid(jobs, pid);
printf("Job [%d] (%d) terminated by signal %d
", job->jid, job->pid, WTERMSIG(status));
}
deletejob(jobs, pid);
}
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
}
errno = old_errno;
return;
}
有了前面的基础,这个函数也很简单了。
sigint_handler() 与 sigtstp_handler()
void sigint_handler(int sig)
{
pid_t pid;
int old_errno = errno;
if ((pid = fgpid(jobs)) != 0)
{
kill(-pid, SIGINT);
}
errno = old_errno;
return;
}
void sigtstp_handler(int sig)
{
pid_t pid;
int old_errno = errno;
pid = fgpid(jobs);
if (pid)
{
kill(-pid, sig);
}
errno = old_errno;
return;
}
这两个也很简单
builtin_cmd()
int builtin_cmd(char **argv)
{
if (!strcmp(argv[0], "quit"))
exit(0);
else if (!strcmp(argv[0], "jobs"))
{
listjobs(jobs);
return 1;
}
else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
{
do_bgfg(argv);
return 1;
}
return 0; /* not a builtin command */
}
这个函数与书上的几乎无异。
do_bgfg()
void do_bgfg(char **argv)
{
struct job_t *job;
if(argv[1] == NULL){
printf("%s command requires PID or %%jobid argument
",argv[0]);
return;
}
char * idstring = argv[1];
int isjid = 0;
int id;
if(idstring[0]=='%'){ //以jobid作为目标
isjid = 1;
id = atoi(idstring+1);
if(!id){
printf("%s: argument must be a PID or %%jobid
",argv[0]);
return;
}
}
else{ //以pid作为目标
id = atoi(idstring);
if(!id){
printf("%s: argument must be a PID or %%jobid
",argv[0]);
return;
}
}
sigset_t mask_empty, mask_all, mask_pre;
sigemptyset(&mask_empty);
sigfillset(&mask_all);
sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //与job相关操作涉及全局变量的访问
if (isjid) //寻找目标job
{
job = getjobjid(jobs, id);
if (!job){
printf("%%%d: No such job
", id);
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
return;
}
}
else
{
job = getjobpid(jobs, id);
if (!job){
printf("(%d): No such process
", id);
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
return;
}
}
pid_t pid = job->pid;
if (!strcmp(argv[0], "bg")) //bg fg切换
{
job->state = BG;
printf("[%d] (%d) %s",job->jid,job->pid,job->cmdline);
kill(-pid, SIGCONT);
}
else if (!strcmp(argv[0], "fg"))
{
job->state = FG;
kill(-pid, SIGCONT);
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
waitfg(pid);
return;
}
sigprocmask(SIG_SETMASK, &mask_pre, NULL);
return;
}
主要是要对参数是否符合要求进行判断。