本文将分为不同的Part,分别实现Shell
的一部分功能。
msh
从CSAPP
的SHLAB
出发,逐渐完善SHELL
功能,并移植到自己的OS上。
Github: https://github.com/He11oLiu/msh
Part1
Part1 目标
- 首先,
tsh
需要支持内嵌指令功能,使用int builtin_cmd(char **argv)
实现。 再,
tsh
需要支持前后台执行程序的功能,shell
需要接收SIGCHLD
进程,回收僵死进程或处理暂停进程。在给出的
handout
中已经把语义分析写好了,直接用即可。
命令求值函数
/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline) {
char *argv[MAXARGS];
int bg;
pid_t pid;
sigset_t mask;
bg = parseline(cmdline,argv);
if(argv[0] == NULL)
return ;
if(!builtin_cmd(argv)){
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigaddset(&mask,SIGINT);
sigaddset(&mask,SIGTSTP);
sigprocmask(SIG_BLOCK,&mask,NULL);
/* child proc */
if((pid = Fork()) == 0){
setpgid(0, 0);
if(verbose){
pid = getpid();
printf("Child proc started with pid %d\n",(int)pid);
}
sigprocmask(SIG_UNBLOCK,&mask,NULL);
if(execve(argv[0],argv,environ)<0){
printf("%s: Command not found.\n",argv[0]);
exit(0);
}
}
/* parent proc */
else{
addjob(jobs,pid,bg?BG:FG,cmdline);
sigprocmask(SIG_UNBLOCK,&mask,NULL);
if(!bg){
/* Use waitfg to wait until proc(pid) is no longer a frontgroud proc. */
waitfg(pid);
}
else{
printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
}
}
}
return ;
}
在第一阶段的目标下,不需要在课本的基础上改多少内容,一个是对于前台的子进程不再使用waitpid
来阻塞,而是使用更加优雅的waitfg
来阻塞。再一个是添加了SIGCHLD
的处理函数,用于处理回收僵死或暂停进程。
前台阻塞处理
对于前台,最初实现是直接waitpid
。但是一旦使用SIGCHLD
的处理函数来回收,两个waitpid
会导致结构不好,全局FLAG
之类也不够优雅。
发现其提供了waitfg
的接口,思路一用以下方法实现。
void waitfg(pid_t pid){
struct job_t *cur = getjobpid(jobs,pid);
while(cur != NULL && cur->state == FG){
cur = getjobpid(jobs,pid);
}
/* 2 cases:
BG : switch to BG from FG
NULL : delete from jobs
*/
return;
}
性能不佳,且不够优雅。每次都观察前台的proc
是不是pid
即可,再利用pause
暂停,直到有下一个信号来临。
void waitfg(pid_t pid){
while(pid == fgpid(jobs))
pause();
return;
}
这样就优雅多了。
回收僵死进程
需要使用sigchld_handler
来接收SIGCHLD
信号对僵死进程进行回收或者处理被暂停的进程。
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn