前言

计算机系统这学期一共有四个实验,其中的bomblab和buflab都特别有趣,让我学到了很多汇编相关的知识。至于第一次的datalab就,很怪。这次的shelllab也是让人摸不着头脑(结果就是得寻求高人的帮助),但最后还是得做出来(不然实验验收就寄了)。这里就先传一手shelllab的报告,至于bomblab和buflab的就再等什么时候有空再弄了。


一.实验目的

shell Lab的主要目的是为了让我们熟悉进程控制和信号。

二.实验内容

主要是对tsh.c中没有填写的函数进行填写,使得该shell能处理前后台运行程序、能够处理ctrl+z、ctrl+c等信号。需要实现的函数主要有以下七个:

eval: 主要功能是解析cmdline,并且运行.

builtin cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs.

do bgfg: 实现bg和fg命令.

waitfg: 实现等待前台程序运行结束.

sigchld handler: 响应SIGCHLD.

sigint handler: 响应 SIGINT (ctrl-c) 信号.

sigtstp handler: 响应 SIGTSTP (ctrl-z) 信号.


三.实验过程

trace01 -> 正确终止EOF

直接运行就行了。


trace 02,03 ->实现内置的quit

(1)首先观察trace02.txt文件,发现只有quit,WAIT两条命令。执行后可以发现无法正常终止,因为tsh的quit内置命令还未编写,所以不能正常退出。因此需要我们实现终止命令quit()。

(2)实现quit:

目的:补齐文件tsh.c中的函数eval()函数和函数builtin_cmd()与quit相关的部分。

实现思路:首先从命令中提取参数,然后判断是否为内置命令,如果为内置命令,则直接在当前进程执行即可;如果不是内置命令,则需要新建一个子进程,并利用 execve 来通过参数给出的路径寻找出可执行文件并在子进程中执行,如果找不到该可执行文件,则输出命令未找到,并结束子进程。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void eval(char *cmdline) //加载且执行命令
{
char *argv[MAXARGS]; /* 参数列表execve() */
char buf[MAXLINE]; /* 保存修改的命令行 */
pid_t pid; /* 进程id*/
strcpy(buf,cmdline);
parseline(buf,argv); /*提取argv8*/
if(argv[0] == NULL)
return; /* 忽略空命令 */

if(!builtin_cmd(argv)){
if((pid = fork()) == 0){ /* 子程序运行用户作业 */
if(execve(arg[v0],argv,environ)<0){
//判断是否找得到对应可执行文件
printf("%s: 命令未找到.\n",arg[0]);
exit(0);
//找不到就退出,并终止进程
}
}
return;
}
}

(3)然后是判断是否为内置命令的函数builtin_cmd():

1
2
3
4
5
6
int builtin_cmd(char **argv) //判断当前命令是否为内置命令
{
if(!strcmp(argv[0],"quit")) //如果是内置命令quit
exit(0); //就结束当前进程
return 0; /* 不是一个内置命令 */
}

此外,因为tset03功能是运行一个前台job,并且也是以quit终止,因此也同时完成了。


trace04 –>实现eval()的后台作业(BK job)管理功能

(1)思路:

·在原有的eval函数基础之上添加将作业添加至后台作业管理的函数使用(addjobs())。

·加以信号的阻塞和取消阻塞。

(2)具体实现:

1.首先使用一个标记符号 –> bg

2.因为要分析传入指令是否要在后台执行进程,因此要补充分析命令的函数builtin_cmd()

1
2
if(!strcmp(argv[0],"&")) /* 忽略单& */
return 1; //然后返回1,因为如果一个命令以&结尾,shell应该在后台运行它,否则在前台运行;

3.接着在eval中进行判断是否为后台进程

1
2
3
4
5
6
/* 父任务等待前台任务结束 */
if (!bg){ //如果不是后台进程,就等待当前的前台进程
waitfg(pid);
}else{ /* 否则就是后台进程,开始在后台工作 */
printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
}

4.再将waitfg()函数补充完整,让父进程正确地等待。

1
2
3
4
5
6
7
8
9
10
void waitfg(pid_t pid)
{
/* 唯一的前台作业结束后,被sigchld_handler回收,deletejob()后,jobs列表中就没有前台作业了,
循环fpgid(..)
*/
while(pid==fgpid(jobs)){
sleep(0);
}
return;
}

5.接下来实现信号的控制,这里使用sigprocmask()函数显式地阻塞和取消阻塞

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
if(!builtin_cmd(argv)){
sigfillset(&mask_all); /* 保存当前的阻塞信号集合(blocked位向量) */
sigemptyset(&mask_one); //初始化mask_one为空集
sigaddset(&mask_one,SIGCHLD);//添加SIGCHLD到mask_one中
//以上三句保存了当前的已阻塞信号集合

sigprocmask(SIG_BLOCK,&mask_one,&prev_one); /* 添加mask_one中的信号到信号集合(blocked位向量),从而父进程保持SIGCHLD的阻塞*/
if((pid = fork()) == 0){ /* 子程序运行用户作业 */

sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 因为子进程继承了它们父进程的被阻塞集合,所以在调用execve之前,必须解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/


if(setpgid(0,0) < 0){
/* 把子进程放到一个新进程组中,该进程组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即shell进程。*/
printf("setpgid error");
exit(0);
}
if(execve(argv[0],argv,environ) < 0){
printf("%s: Command not found.\n",argv[0]);
//第一个错误处理,直接在这里进行提示信息输出
exit(0);
}
}

sigprocmask(SIG_BLOCK,&mask_all,NULL); /* 恢复信号集合(blocked位向量) */
addjob(jobs,pid,bg==1 ? BG : FG,cmdline); /* 将子任务添加到任务列表中 */
sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 解除子进程对SIGCHLD的阻塞 */
/* 这样子sigchld_handler处理程序在我们将其添加到工作队列
中之前是不会运行的。因为直到addjob()之后,我们才解除对SIGCHLD的阻塞
*/

6.接下来实现对应的sigcld_handler()以释放僵尸的子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 
* sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
* 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
* 但不等待任何其他当前运行的子进程终止。
*/
void sigchld_handler(int sig)
{
int olderrno = errno;
sigset_t mask_all,prev_all;
pid_t pid;

sigfillset(&mask_all); /* 保存当前的信号集合(blocked位向量) */
while((pid = waitpid(-1,NULL,WNOHANG)) > 0){
sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
deletejob(jobs,pid); /释放僵尸进程/
sigprocmask(SIG_SETMASK,&prev_all,NULL); //解除阻塞
}
errno = olderrno;

return;
}

7.综上,我们的addjobs就成功实现了!


trace05 –>处理jobs内置命令

(1)思路:直接调用自带的listjobs()方法,就是在原有builtin_cmd函数中添加一个判断函数,如果参数是jobs,则执行listjobs函数的功能(即将所有的作业打印出来)。

(2)实现:

1
2
3
4
if(!strcmp(argv[0],"jobs")){    /* jobs内置指令 */
listjobs(jobs);
return 1;
}

trace06、trace07 ->处理SIGINT信号

(1)目的:

要实现的功能是:

trace06->将SIGINT信号转发到前台作业;

trace07->仅仅将SIGINT信号转发到前台作业;

因此这里放在一起实现。

(2)实现:

根据文档中的解决方法,我们来一步步实现。

1.首先更改一下eval函数,在其中调用setpgid(0,0):

1
2
3
4
5
6
7
8
9
10
11
       if(setpgid(0,0) < 0){               
/* 把子进程放到一个新进程组中,该进程组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即shell进程。*/
printf("setpgid error");
exit(0);
}
if(execve(argv[0],argv,environ) < 0){
printf("%s: Command not found.\n",argv[0]);
//第一个错误处理,直接在这里进行提示信息输出
exit(0);
}
}

2.更改信号处理函数sigint_handler(),实现转发到前台作业的操作(包含前台作业的进程组)

1
2
3
4
5
6
7
8
9
10
11
/* 
* sigint_handler - 当用户在键盘上键入ctrl+c时,内核向shell发送一个SIGINT。抓住它并把它发送到前台工作。
*/
void sigint_handler(int sig)
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */
if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}

3.还要修改sigchld_handler()函数:为了区分进程终止的原因(符合测试文件)(后边也会用到)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1.是正常终止(exit或return)
//2.还是因为收到其他信号如:SIGINT而终止。(这里我们是收到SIGINT信号终止的)
/* 通过调用exit或者一个返回(return)正常终止 */
if(WIFEXITED(status)){
sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
deletejob(jobs,pid);
sigprocmask(SIG_SETMASK,&prev_all,NULL);
}
/* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
if(WIFSIGNALED(status)){
int jid = pid2jid(pid);
printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
deletejob(jobs,pid);/* 终止就删除pid的job */
}

6、Trace08 -> 仅仅将SIGSTP(ctrl+z)转发到前台作业(与上一题实现大同小异)

(1)因此我们就直接实现其信号处理函数sigtstp_handler():

1
2
3
4
5
6
7
8
9
10
11
12
/*
* sigtstp_handler - 每当用户在键盘上键入ctrl-z时,内核就向shell发送一个SIGTSTP。捕获它并通过向它发送SIGTSTP来挂起前台作业。
*/
void sigtstp_handler(int sig)
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */

if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}

(2)依旧来修改一下sigchld_handler()函数。区分终止/停止。

思路:因此在上一题的基础上加上对于SIGTSTP(ctrl+z)的判断和信息显示。如下:

1
2
3
4
5
6
7
/* 引起返回的子进程当前是停止的(SIGTSTP) */
if(WIFSTOPPED(status)){
struct job_t * job = getjobpid(jobs,pid);
int jid = pid2jid(pid);
printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
job->state = ST; /* 状态设为停止(ST) */
}

并改变while的判断条件:

1
while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED)) > 0)

WNOHANG:挂起调用进程,直到有子进程终止。

WUNTRACED:挂起调用进程,直到等待集合中的一个进程变成已终止或者被停止。

WNOHANG | WUNTRACED:等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。

两个合在一起就是接收终止和停止(ctrl+z和ctrl+c)。


trace 09 —> 实现进程内置命令bg

bg < job >:命令会向一个已经停止的job发送SIGCNOT信号来重启这个job,并作为后台作业运行,参数可以是PID或JID。

(1)首先是完成识别命令:

要将bg命令添加到识别命令的函数builtin_cmd()中:

1
2
3
4
  if(!strcmp(argv[0],"bg")){      /* bg内置指令 */
do_bgfg(argv);
return 1;
}

(2)接下来实现其处理函数:

修改do_bgfg()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void do_bgfg(char **argv) 
{

pid_t pid; /* 进程id */
int jid; /* job的id */
struct job_t * job;
if (argv[1][0] == '%'){ /* 如果输入的是jid(作业) */
jid = atoi(argv[1]+1);
job = getjobjid(jobs,jid);//通过jid找到需要执行的job
pid = job->pid;
}else{ /* 给的是pid */
pid = atoi(argv[1]);
job = getjobpid(jobs,pid);
jid = job->jid;
}
if(pid > 0){
if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
printf("[%d] (%d) %s",jid,pid,job->cmdline);
job->state = BG; /* 更改状态 */
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */
}
}
return;
}

trace 10 —> 实现进程内置命令fg(与上一题差不多)

fg < job >:将一个已停止或正在运行的后台作业更改为正在前台运行的作业。

(1)老方法,先往builtin_cmd()函数添加内容:

1
2
3
4
 if(!strcmp(argv[0],"fg")){      /* fg内置指令 */
do_bgfg(argv);
return 1;
}

(2)然后往do_bgfg()函数中加入相关处理:

1
2
3
4
5
6
7
8
else
if(!strcmp(argv[0],"fg")){ /* fg内置指令 */
if(job->state = ST){
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */
}
job->state = FG; /* 更改状态 */
waitfg(pid); /* 等待前台job完成 */
}

trace11,12

trace 11 —> 将SIGINT转发给前台进程组中的每个进程

trace 12 —> 将SIGSTP转发给前台进程组中的每个进程

这两个实验在之前的trace06-trace07的分析中已经实现了,因此我们直接执行即可:

(1)sigint_handler()函数:

1
2
3
4
5
6
7
8
void sigint_handler(int sig) 
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */
if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}

(2)sigtstp_handler()函数:

1
2
3
4
5
6
7
8
9
void sigtstp_handler(int sig) 
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */

if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}

trace13 –>重新启动进程组中每个已经停止的进程

(1)分析:

· 因为此时需要唤醒所有停止的进程,因此要将唤醒函数kill(pid,SIGCONT)的第一个参数改为-pid,因为当其第一个参数<0时,kill就会将SIGCONT信号传递给整个进程组。

· 因为在FG中,有一步是需要等待当前的前台进程完成之后,才会唤醒进程组中的进程,所以为了保证唤醒所有进程,就要去掉FG中,job->state == ST才传递SIGCONT信号的判断,因为当前运行进程可能没有停止(ST),但是进程组中是有停止的,进程组中停止的这些也需要被唤醒。

(2)综上,我们得到以下实现do_bgfg()更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
if(pid > 0){
if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
printf("[%d] (%d) %s",jid,pid,job->cmdline);
job->state = BG; /* 更改状态 */
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */

}else
if(!strcmp(argv[0],"fg")){ /* fg内置指令 */
job->state = FG; /* 更改状态 */
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */
waitfg(pid); /* 等待前台job完成 */
}
}

trace14 简单的错误处理(就是处理输入未实现的命令、fg、bg参数不正确等错误情况)

(1)又直接运行make rtest14可知有五种处理方式,因此我们在do_bgfg()中进行对应的处理即可。

(2)处理:第一个错误:Command not found,未实现的命令。

我们再次回顾一下shell的执行流程:程序会首先执行 eval(),在 eval中进行判断(使用buildin_cmp()函数),如果发现命令不是内置命令,则会调用 fork()函数来新建一个子进程,在子进程中调用 execve()函数通过 argv[0]来寻找路径,并在子进程中运行路径中的可执行文件,如果找不到可执行文件,则说明命令为无效命令。因此我们在此处加入输出语句即可,如下:

1
2
3
4
5
if(execve(argv[0],argv,environ) < 0){
printf("%s: Command not found.\n",argv[0]);
//第一个错误处理,直接在这里进行提示信息输出
exit(0);
}

第二个错误是:fg command requires PID or %jobid argument,fg命令时没有传入pid或jid。因此在do_bgfg()中实现:

1
2
3
4
5
if (argv[1] == NULL){
printf("%s command requires PID or %%jobid argument\n",argv[0]);
return;
}
//第二个错误是没有传入pid或者jid(为空),就报错并返回

第三个错误是:fg: argument must be a PID or %jobid,传入了pid或jid,但是不符合规范(pid或jid必须为数字)。处理:

1
2
3
4
5
6
7
 if (argv[1][0] == '%'){   /* 如果输入的是jid(作业) */
if(argv[1][1] < '0' || argv[1][1] >'9'){
printf("fg: argument must be a PID or %%jobid\n");
return;
}
}
//第三个错误命令是传入了,但是传入的数据不是不符合pid或jid的规范(输入必须为数字)

第四个错误是:No such process,通过传入的pid找不到对应的作业(job=null)处理:

1
2
3
4
5
6
     job = getjobjid(jobs,jid);//通过jid找到需要执行的job
if(job == NULL){
printf("%%%d: No such job\n",jid);
return;
}
//第四个错误就是通过jid找到的job==null,因此“NO such job”

第五个错误:No such job,通过传入的jid找不到对应的job(job=null)

1
2
3
4
5
6
        job = getjobpid(jobs,pid);
if(job == NULL){
printf("(%d): No such process\n",pid);
return;
}
//第五个错误就是通过jid找到的job==null,因此“NO such job”

此外,我们发现还有一行(如下),这里我们和trace15一起解决。所以接下来看一下trace15.

1
2
tsh>fg %1
job [1] (3104) stopped by signal 20

trace15–>所有命令一起运行

(1)经make rtest15可知是缺失两条消息的处理。那么首先查看一下文件trace15.txt,看看是因为什么信号出现这种情况:经查看发现一是INT信号将job10终止,二是TSTP信号将job1中断。因此我们就在终止信号处理函数sihchld_handler()中进行判断处理,并输出上述错误信息:

(2)处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
if(WIFSIGNALED(status)){
int jid = pid2jid(pid);
printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
deletejob(jobs,pid);/* 终止就删除pid的job */
}
/* 引起返回的子进程当前是停止的(SIGTSTP) */
if(WIFSTOPPED(status)){
struct job_t * job = getjobpid(jobs,pid);
int jid = pid2jid(pid);
printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
job->state = ST; /* 状态设为停止(ST) */
}

trace16 –>测试shell是否能够处理来自其他进程而不是终端的SIGTSTP和SIGINT信号

emmm,好像没有要做的事了,直接执行就行了。


完整代码

最后给出7个函数的完整代码

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
void eval(char *cmdline) //加载且执行命令
{
char *argv[MAXARGS]; /* 参数列表execve() */
char buf[MAXLINE]; /* 保存修改的命令行 */
int bg; /* 这个作业应该在后台进行? */
pid_t pid; /* 进程id*/
strcpy(buf,cmdline);
bg = parseline(buf,argv);
if(argv[0] == NULL)
return; /* 忽略空命令 */

sigset_t mask_all,mask_one,prev_one;
if(!builtin_cmd(argv)){
sigfillset(&mask_all); /* 保存当前的阻塞信号集合(blocked位向量) */
sigemptyset(&mask_one); //初始化mask_one为空集
sigaddset(&mask_one,SIGCHLD);//添加SIGCHLD到mask_one中
//以上三句保存了当前的已阻塞信号集合

sigprocmask(SIG_BLOCK,&mask_one,&prev_one); /* 添加mask_one中的信号到信号集合(blocked位向量),从而父进程保持SIGCHLD的阻塞*/
if((pid = fork()) == 0){ /* 子程序运行用户作业 */

sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 因为子进程继承了它们父进程的被阻塞集合,所以在调用execve之前,必须解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/


if(setpgid(0,0) < 0){
/* 把子进程放到一个新进程组中,该进程组ID与子进程的PID相同。这将确保前台进程组中只有一个进程,即shell进程。*/
printf("setpgid error");
exit(0);
}
if(execve(argv[0],argv,environ) < 0){
printf("%s: Command not found.\n",argv[0]);
//第一个错误处理,直接在这里进行提示信息输出
exit(0);
}
}

sigprocmask(SIG_BLOCK,&mask_all,NULL); /* 恢复信号集合(blocked位向量) */
addjob(jobs,pid,bg==1 ? BG : FG,cmdline); /* 将子任务添加到任务列表中 */
sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 解除子进程对SIGCHLD的阻塞 */
/* 这样子sigchld_handler处理程序在我们将其添加到工作队列
中之前是不会运行的。因为直到addjob()之后,我们才解除对SIGCHLD的阻塞
*/


/* 父任务等待前台任务结束 */
if (!bg){ //如果不是后台进程,就等待当前的前台进程
waitfg(pid);
}else{ /* 否则就是后台进程,开始在后台工作 */
printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline);
}
}

return;

}

builtin_cmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int builtin_cmd(char **argv) //判断当前命令是否为内置命令
{
if(!strcmp(argv[0],"quit")) //如果是内置命令quit
exit(0); //就结束当前进程
if(!strcmp(argv[0],"jobs")){ /* jobs内置指令 */
listjobs(jobs);
return 1;
}
if(!strcmp(argv[0],"&")) /* 忽略单& */
return 1; //然后返回1,因为如果一个命令以&结尾,shell应该在后台运行它,否则在前台运行;

if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0],"fg")){ /* fg内置指令 */
do_bgfg(argv);
return 1;
}

return 0; /* 不是一个内置命令 */
}

do_bgfg

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
57
void do_bgfg(char **argv) 
{

pid_t pid; /* 进程id */
int jid; /* job的id */
struct job_t * job;
if (argv[1] == NULL){
printf("%s command requires PID or %%jobid argument\n",argv[0]);
return;
}
//第二个错误是没有传入pid或者jid(为空),就报错并返回

if (argv[1][0] == '%'){ /* 如果输入的是jid(作业) */
if(argv[1][1] < '0' || argv[1][1] >'9'){
printf("fg: argument must be a PID or %%jobid\n");
return;
}
//第三个错误命令是传入了,但是传入的数据不是不符合pid或jid的规范(输入必须为数字)
//在这里判断并输出错误信息:fg: argument must be a PID or %%jobid\n
jid = atoi(argv[1]+1);
job = getjobjid(jobs,jid);//通过jid找到需要执行的job
if(job == NULL){
printf("%%%d: No such job\n",jid);
return;
}
//第四个错误就是通过jid找到的job==null,因此“NO such job”
pid = job->pid;
}else{ /* 给的是pid */
if(argv[1][0] < '0' || argv[1][0] >'9'){
printf("bg: argument must be a PID or %%jobid\n");
return;
}
pid = atoi(argv[1]);
job = getjobpid(jobs,pid);
if(job == NULL){
printf("(%d): No such process\n",pid);
return;
}
//第五个错误就是通过jid找到的job==null,因此“NO such job”
jid = job->jid;
}
if(pid > 0){
if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
printf("[%d] (%d) %s",jid,pid,job->cmdline);
job->state = BG; /* 更改状态 */
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */

}else
if(!strcmp(argv[0],"fg")){ /* fg内置指令 */
job->state = FG; /* 更改状态 */
kill(-pid,SIGCONT); /* 传递SIGCONT信号给进程组中的所有进程 */
waitfg(pid); /* 等待前台job完成 */
}
}

return;
}

waitfg

1
2
3
4
5
6
7
8
9
10
void waitfg(pid_t pid)
{
/* 唯一的前台作业结束后,被sigchld_handler回收,deletejob()后,jobs列表中就没有前台作业了,
循环fpgid(..)
*/
while(pid==fgpid(jobs)){
sleep(0);
}
return;
}

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
28
29
30
31
32
33
34
void sigchld_handler(int sig) 
{
int olderrno = errno;
sigset_t mask_all,prev_all;
pid_t pid;
int status;

sigfillset(&mask_all); /* 保存当前的信号集合(blocked位向量) */
while((pid = waitpid(-1,&status,WNOHANG | WUNTRACED)) > 0){ /* WNOHANG:非阻塞的 */
/* 通过调用exit或者一个返回(return)正常终止 */
if(WIFEXITED(status)){
sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
deletejob(jobs,pid);
sigprocmask(SIG_SETMASK,&prev_all,NULL);
}
/* 子进程是因为一个未被捕获的信号终止的(SIGINT) */
if(WIFSIGNALED(status)){
int jid = pid2jid(pid);
printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status));
deletejob(jobs,pid);/* 终止就删除pid的job */
}
/* 引起返回的子进程当前是停止的(SIGTSTP) */
if(WIFSTOPPED(status)){
struct job_t * job = getjobpid(jobs,pid);
int jid = pid2jid(pid);
printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
job->state = ST; /* 状态设为停止(ST) */
}

}
errno = olderrno;

return;
}

sigint_handler

1
2
3
4
5
6
7
8
void sigint_handler(int sig) 
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */
if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}

sigtstp_handler

1
2
3
4
5
6
7
8
9
void sigtstp_handler(int sig) 
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */

if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}