Shell

熟悉过程控制和信号


简介

编写一个支持工作控制的Unix Shell程序,使自己更熟悉进程控制和信号

需要实现的功能点

  • 能运行几个内置的命令,如fg bg
  • 能运行指定的程序,如/bin/ls
  • 能区分前后台程序,包括切换前后台程序
  • 能接受从程序内部和外部分别发送的信号,如SIGINT SIGTSTOP
  • 对非法命令的错误处理和提示

注意点

  1. 使用测试文件来推动代码的完善,正确结果可以参考tshref
  2. 在kill函数中发送SIGINT和SIGTSTP信号的时候,记得使用-pid,表示给整个进程组发送信号。
  3. 关于在eval里面要使用sigprocmask函数在addjob之前阻塞住SIGCHLD,不然在子进程执行完的时候如果还没有执行addjob,就会给父进程shell发送SIGCHLD,这时候调用sigchld_handler在addjob之前deletejob一个根本不存在的job,容易出现竞争条件。同时,阻塞之后要及时释放锁,释放锁的地方有三个地方,
    一是:在子进程执行execve,即子进程执行其他程序的时候需要把锁还原。
    二是:在父进程addjob之后要及时归还锁。
    三是:不能把sigprocmask阻塞进程在build_cmd判断外面,不然会出现锁住了没有归还锁的情况。
  4. 当按下ctrl-c或者ctrl-z的时候,内核会发送SIGINT或者SIGTSTP信号给所有前台进程组,因为shell是前台进程,那么从shell中fork出的子进程都属于shell这个前台进程组的,那么我们每次SIGINT或者SIGTSTP信号都会给我们的所有进程,这明显是不合理的。所以当我们在每次fork出子进程的时候,都要利用setpgid(0,0)把新加的进程添加到新的进程组中。
  5. 为了实现前后台功能,shell引入了job这个概念,表示为了一条命令行求值而创建的进程。
  6. test16要求把子进程自己给自己发送SIGINT信号和SIGSTSP信号,我们之前只讨论过通过ctrl-z和ctrl-c向shell前台发送。对于子进程自己给自己发送SIGINT信号和SIGSTSP信号的唯一改变就是,shell外壳不会主动调用sigint_handler和sigstsp_handler,只有在子进程终止或者停止的时候,会发送信号给父进程,此时父进程通过调用sigchld_handler回收僵尸进程。那么,在sigchld_handler根据得到信号调用来判断不同的情况,并选择调用sigint_handler和sigstsp_handler。

代码讲解

大部分函数都已经完成,需要特别完善的函数有eval(char *cmdline) do_bgfg(char **argv) builtin_cmd(char **argv)以及三个信号处理程序void sigchld_handler(int sig) void sigtstp_handler(int sig) void sigint_handler(int sig)

eval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void eval(char *cmdline) 
{
char buf[MAXLINE]; /* holds modified command line */
char *argv[MAXARGS]; /* argument list execve */
int bg; /* should the job run in bg or fg */
pid_t pid; /* process id */

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGCHLD);
sigaddset(&mask,SIGINT);
sigaddset(&mask,SIGTSTP);

strcpy(buf,cmdline);
bg = parseline(buf,argv);

if(argv[0] == NULL)
return; /* ignore empty lines */

if(!builtin_cmd(argv)){

sigprocmask(SIG_BLOCK,&mask,NULL);

if((pid = fork()) == 0){ /* child runs user jobs */
sigprocmask(SIG_UNBLOCK,&mask,NULL);
setpgid(0,0);/* set new process to new process group */

if(execve(argv[0],argv,environ) < 0){
printf("%s:Command not found.\n",argv[0]);
exit(0);
}
}

// sigprocmask(SIG_BLOCK,&mask_all,NULL);

// add job to jobs list
//if(strcmp(argv[0],"/bin/echo")){
if(bg)
addjob(jobs,pid,BG,cmdline);
else
addjob(jobs,pid,FG,cmdline);
//}


sigprocmask(SIG_UNBLOCK,&mask,NULL);
sigemptyset(&mask);

/* Parent wait for foreground job to terminate */
if(!bg)
waitfg(pid);
else
printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
}

return;
}

该函数首先判断命令是否是内嵌命令;如果不是,则产生一个新的进程来执行命令,并将这个命令对应的job加入工作列表;如果是前台进程,则要显式等待该程序结束

sigchld_handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void sigchld_handler(int sig) 
{
int state,chld_sig;
pid_t ret_pid;//pid of child job which has returned

// wait for any child process to return
while((ret_pid = waitpid(-1,&state,WNOHANG | WUNTRACED)) > 0){
if(WIFSTOPPED(state))
sigtstp_handler(WSTOPSIG(state));

/* handle child process interrupt,like test16 */
else if(WIFSIGNALED(state)){
chld_sig = WTERMSIG(state);
if(chld_sig == SIGINT)
sigint_handler(chld_sig);
}
else
deletejob(jobs,ret_pid);
}

/*
if(errno != ECHILD)
unix_error("error");
*/

return;
}