Aione's blog

一起快乐摸鱼吧🐳

  1. 1. main
  2. 2. eval
  3. 3. waitfg
  4. 4. sigchld_handler
  5. 5. do_bgfg

记录一下 shell lab 流程

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, char **argv) 
{
...

/* Execute the shell's read/eval loop */
while (1) {

...

/* Evaluate the command line */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}

exit(0); /* control never reaches here */
}

main 函数里跑了个死循环,不断读取内容并送到 eval() 处理。

eval

eval() 函数是 shell 主要的处理函数,处理流程如下

1
built-in func[jobs, quit, fg ,bg] ? func: fork--> bg ? return : wait child process terminate

代码如下,有两个点需要注意一下。

  • 在创建新的线程处理 cmdline 之前,阻塞 chld 防止新线程结束之后,父进程将已经结束的子进程的 pid 加入 job list
  • 防止由于 <C-z> <C-c> 而导致子进程未添加到 job list 而成为孤儿进程
    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
    void eval(char *cmdline) 
    {
    pid_t pid;

    char *argv[MAXARGS];
    int bg = parseline(cmdline, argv);
    if(argv[0]==NULL){
    return;
    }
    if(!builtin_cmd(argv)){
    sigset_t mask_all, mask_chld, prev;
    Sigfillset(&mask_all);
    Sigemptyset(&mask_chld);
    Sigaddset(&mask_chld, SIGCHLD);

    //ensure addjob() before deletejob().
    Sigprocmask(SIG_BLOCK, &mask_chld, &prev);
    if((pid = Fork())== 0){
    //in child process
    Sigprocmask(SIG_SETMASK, &prev, NULL);
    if(execve(argv[0], argv, environ) < 0){
    printf("%s: Command not found.\n",argv[0]);
    exit(0);
    }
    }
    //in parent process
    int state = bg ? BG:FG;
    //if parent process meet <c-z> or <c-c> ensure the child process add to job list.
    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;
    }

waitfg

这个函数要等待前台函数返回

1
2
3
4
5
6
7
8
9
10
11
void waitfg(pid_t pid)
{
sigset_t empt_set, prev;
sigemptyset(&empt_set);
while(pid==fgpid(jobs)){
sigprocmask(SIG_BLOCK,&empt_set,&prev);
sigsuspend(&empt_set);
}
sigprocmask(SIG_SETMASK, &prev, NULL);
return;
}

我们用 sigsuspend(sigset_t *) 函数直接挂起父进程,等待子进程结束,然后控制交由父进程。

sigsuspend(sigset_t *)
可以理解为,挂起当前进程直到出现了 mask 内没有屏蔽的信号。

sigchld_handler

这个函数用于处理 sig_chld 信号,也即是说子进程结束时的处理。

主要注意子进程退出的三种情况

  • 程序正常和由于信号退出,这时候字节把子进程从 job_list 中移除即可
  • 程序停止,更新一下状态即可。
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
void sigchld_handler(int sig) 
{
int status;
int old_errno = errno;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all);

while((pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0){
if(WIFEXITED(status)){
//normal exit
sigprocmask(SIG_BLOCK, &mask_all, &prev);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
} else if(WIFSIGNALED(status)) {
struct job_t* job_ptr = getjobpid(jobs,pid);
printf("Job [%d] (%d) terminated by signal %d\n",job_ptr->jid,job_ptr->pid,WTERMSIG(status));
sigprocmask(SIG_BLOCK, &mask_all, &prev);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
} else {
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 = old_errno;
return;
}

do_bgfg

这是程序处理前后台进程的主要函数,这个函数的处理逻辑也很简单,我们只需要找到进程的 pid,让他继续运行即可,bg/fg 区别只在父进程等待。

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
void do_bgfg(char **argv) 
{
pid_t pid;
int jid;
struct job_t* job_ptr;
if(argv[1] == NULL){
printf("%s command requires PID or %%jobid argument\n",argv[0]);
return;
}

if(sscanf(argv[1],"%d", &pid) > 0){
job_ptr = getjobpid(jobs,pid);
if(job_ptr == NULL){
printf("(%d): No such process\n", pid);
return;
}
}
else if(sscanf(argv[1],"%%%d", &jid) > 0){
job_ptr = getjobjid(jobs,jid);
if(job_ptr == NULL){
printf("%%%d: No such job\n",jid);
return;
}
} else {
printf("%s: argument must be a PID or %%jobid\n",argv[0]);
return;
}
if(!strcmp(argv[0],"bg")){
//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;
}

Author : Aione
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
Link to this article : https://aione.me/2019/10/28/CSAPP-shell-lab/

This article was last updated on days ago, and the information described in the article may have changed.