原文链接
目标
补全tsh.c
中剩余的代码:
-
void eval(char *cmdline)
:解析并执行命令。 -
int builtin_cmd(char **argv)
:检测命令是否为内置命令quit
、fg
、bg
、jobs
。 -
void do_bgfg(char **argv)
:实现bg
、fg
命令。 -
void waitfg(pid_t pid)
:等待前台命令执行完成。 -
void sigchld_handler(int sig)
:处理SIGCHLD
信号,即子进程停止或终止。 -
void sigint_handler(int sig)
:处理SIGINT
信号,即来自键盘的中断ctrl-c
。 -
void sigtstp_handler(int sig)
:处理SIGTSTP
信号,即终端停止信号ctrl-z
。
使用make testn
用来测试你编写的shell
执行第n
组测试数据的输出。
使用make rtestn
用来测试参考shell
程序第n
组测试数据的输出(共16
组测试数据)。
tshref.out
包含参考shell
程序的所有测试数据的输出结果,先看完该文件了解命令格式在开始编码。
说明
可用辅助函数:
-
int parseline(const char *cmdline,char **argv)
:获取参数列表char **argv
,返回是否为后台运行命令(true
)。 -
void clearjob(struct job_t *job)
:清除job
结构。 -
void initjobs(struct job_t *jobs)
:初始化jobs
链表。 -
void maxjid(struct job_t *jobs)
:返回jobs
链表中最大的jid
号。 -
int addjob(struct job_t *jobs,pid_t pid,int state,char *cmdline)
:在jobs
链表中添加job
-
int deletejob(struct job_t *jobs,pid_t pid)
:在jobs
链表中删除pid
的job
。 -
pid_t fgpid(struct job_t *jobs)
:返回当前前台运行job
的pid
号。 -
struct job_t *getjobpid(struct job_t *jobs,pid_t pid)
:返回pid
号的job
。 -
struct job_t *getjobjid(struct job_t *jobs,int jid)
:返回jid
号的job
。 -
int pid2jid(pid_t pid)
:将pid
号转化为jid
。 -
void listjobs(struct job_t *jobs)
:打印jobs
。 -
void sigquit_handler(int sig)
:处理SIGQUIT
信号。
eval
- 为避免子进程在未加入到
jobs
链表中就发送信号(SIGINT
、SIGCHLD
、SIGTSTP
)去处理jobs
链表(竞争),在子进程创建前需要将SIGCHLD
阻塞,在加入jobs
链表后解锁该阻塞。 -
fork
后的子进程会继承父进程的信号屏蔽字并且在exec
后仍会继承,所以需要在执行可执行文件前复原信号屏蔽字。 -
fork
后的子进程会继承父进程的信号处理设置,而exec
后不会继承。 - 参考《深入理解计算机第三版》
p543
,如何解决竞争问题。
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_all,mask_one,prev;
Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one,SIGCHLD);
Sigprocmask(SIG_BLOCK,&mask_one,&prev);
if((pid=Fork()) == 0){
setpgid(0,0);
Sigprocmask(SIG_SETMASK,&prev,NULL);
if(execve(argv[0],argv,environ) < 0){
printf("%s: Command not found.\n",argv[0]);
exit(0);
}
}
int state = bg ? BG:FG;
//parent
Sigprocmask(SIG_BLOCK,&mask_all,NULL);
addjob(jobs,pid,state,cmdline);
Sigprocmask(SIG_SETMASK,&prev,NULL);
if(!bg){
waitfg(pid);
}else{
printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
}
}
return;
}
builtin_cmd
- 参考《深入理解计算机第三版》
p525
。
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)
{
if(argv[1] == NULL){
printf("%s command requires PID or %%jobid argument\n",argv[0]);
return;
}
int bg = !strcmp(argv[0],"bg");
struct job_t *job_ptr;
pid_t pid;
int jid;
if(sscanf(argv[1],"%d",&pid) > 0){
// pid
job_ptr = getjobpid(jobs,pid);
if(job_ptr == NULL || job_ptr->state == UNDEF){
printf("(%d): No such process\n",pid);
return;
}
}else if(sscanf(argv[1],"%%%d",&jid) > 0){
// jid
job_ptr = getjobjid(jobs,jid);
if(job_ptr == NULL || job_ptr->state == UNDEF){
printf("%%%d: No such job\n",jid);
return;
}
}else{
printf("%s: argument must be a PID or %%jobid\n",argv[0]);
return;
}
// get the job_ptr;
if(bg){
printf("[%d] (%d) %s",job_ptr->jid,job_ptr->pid,job_ptr->cmdline);
job_ptr->state = BG;
kill(-job_ptr->pid,SIGCONT);
}else{
// "fg"
job_ptr->state = FG;
kill(-job_ptr->pid,SIGCONT);
waitfg(job_ptr->pid);
}
return;
}
waitfg
- 使用循环检查,简单实现。
- 参考《深入理解计算机第三版》
p545
,显示等待信号。
void waitfg(pid_t pid)
{
while(pid == fgpid(jobs))
sleep(1);
return;
}
sigchld_handler
- 参考《深入理解计算机第三版》
p539
,如何回收尽可能多的僵尸子进程。
void sigchld_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all,prev;
pid_t pid;
int status;
Sigfillset(&mask_all);
while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED)) > 0){
if(WIFEXITED(status)){
// normally exit
Sigprocmask(SIG_BLOCK,&mask_all,&prev);
deletejob(jobs,pid);
Sigprocmask(SIG_SETMASK,&prev,NULL);
}else if(WIFSIGNALED(status)){
// exit by signal
struct job_t *job_ptr = getjobpid(jobs,pid);
Sigprocmask(SIG_BLOCK,&mask_all,&prev);
printf("Job [%d] (%d) terminated by signal %d\n",job_ptr->jid,job_ptr->pid,WTERMSIG(status));
deletejob(jobs,pid);
Sigprocmask(SIG_SETMASK,&prev,NULL);
}else{ // stop
struct job_t *job_ptr = getjobpid(jobs,pid);
Sigprocmask(SIG_BLOCK,&mask_all,&prev);
printf("Job [%d] (%d) stopped by signal %d\n",job_ptr->jid,job_ptr->pid,WSTOPSIG(status));
job_ptr->state= ST;
Sigprocmask(SIG_SETMASK,&prev,NULL);
}
}
errno = olderrno;
return;
}
sigint_handler
- 最后两个函数,均向子进程发送信号。
void sigint_handler(int sig)
{
int olderrno = errno;
pid_t pid = fgpid(jobs);
if(pid != 0){
kill(-pid,SIGINT);
}
errno = olderrno;
return;
}
sigtstp_handler
void sigtstp_handler(int sig)
{
int olderrno = errno;
pid_t pid = fgpid(jobs);
if(pid != 0){
kill(-pid,SIGTSTP);
}
errno = olderrno;
return;
}