前言
《深入理解计算机系统》实验六Shell Lab下载和官方文档机翻请看:
https://blog.youkuaiyun.com/weixin_43362650/article/details/122331406
我觉得这个文档对整个实验很有帮助。
这个实验我是按照文档的这个提示来做的,本文也是这样写的,16个文件+1个代码总结。
使用跟踪文件来指导shell的开发。从trace01.txt开始,确保您的shell产生与参考shell相同的输出。然后继续跟踪文件trace02.txt,等等。
还有一个提示
阅读课本第八章(异常控制流)的每一个字。
实验任务
打开tsh.c文件可以看到里面有我们需要实现的函数(有些注释非自带,自己整理的)
/* 下面是您将实现的函数 */
void eval(char *cmdline); /* eval:解析和解释命令行的主例程。[70行] */
int builtin_cmd(char **argv); /* 识别并解释内置命令:quit、fg、bg和jobs。[25行] */
void do_bgfg(char **argv); /* 实现bg和fg内置命令. [50行] */
void waitfg(pid_t pid); /* 等待前台任务完成。[20行] */
void sigchld_handler(int sig); /* 捕获SIGCHILD信号。[80行] */
void sigtstp_handler(int sig); /* 捕获SIGINT (ctrl-c)信号。[15行] */
void sigint_handler(int sig); /* 捕获SIGTSTP (ctrl-z)信号。[15行] */
还有以下的辅助函数
/* 下面是我们为您提供的辅助程序 */
int parseline(const char *cmdline, char **argv); /* 解析命令行并构建argv数组。 */
void sigquit_handler(int sig); /* */
void clearjob(struct job_t *job); /* 清除job结构中的条目 */
void initjobs(struct job_t *jobs); /* 初始化作业列表 */
int maxjid(struct job_t *jobs); /* 返回已分配的最大作业ID */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline); /* 向job列表中添加job */
int deletejob(struct job_t *jobs, pid_t pid); /* 从job列表中删除PID= pid的作业 */
pid_t fgpid(struct job_t *jobs); /* 返回当前前台job的PID,如果没有该job则返回0 */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid); /* 在job列表中找到一个job(通过PID) */
struct job_t *getjobjid(struct job_t *jobs, int jid); /* 在job列表中找到一个job(通过JID) */
int pid2jid(pid_t pid); /* 将进程ID映射到job ID */
void listjobs(struct job_t *jobs); /* 打印job列表 */
void usage(void); /* 打印帮助信息 */
void unix_error(char *msg); /* unix-style 错误程序 */
void app_error(char *msg); /* application-style 错误程序 */
typedef void handler_t(int); /* */
handler_t *Signal(int signum, handler_t *handler); /* sigaction函数的包装器 */
过程
trace01 - 正确地终止EOF。
先熟悉一下环境
正确的输出如下:
我们编译tsh.c文件运行看看
会发现一模一样,第一个文件就这样啥都没动就解决了,熟悉了环境。
trace02 - 进程内置quit命令。
主要修改的就是eval()函数和builtin_cmd()函数。
这里《CS:APP3e》P525页有段代码和P543页有关于job的操作的代码。
trace03 - 运行一个前台job。
在前台运行job,要等待job运行完毕。可以在waitfg()方法中循环fgpid()方法
trace04 - 运行后台job。
结合trace2-4编写出以下代码。
/*
* eval - 计算用户刚刚输入的命令行
*
* 如果用户请求了一个内置命令(quit, jobs, bg或fg),
* 然后立即执行它。否则,fork一个子进程并在该子进程的上下文中运行该作业。
* 如果作业正在前台运行,则等待它终止然后返回。
* 注意:每个子进程必须有一个唯一的进程组ID,这样当我们在键盘上输入ctrl-c (ctrl-z)时,
* 我们的后台子进程就不会从内核接收到SIGINT (SIGTSTP)。
*/
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* 参数列表execve() */
char buf[MAXLINE]; /* 保存修改的命令行 */
int bg; /* 这个作业应该在后台进行? */
pid_t pid; /* 进程id*/
sigset_t mask_all,mask_one,prev_one;
strcpy(buf,cmdline);
bg = parseline(buf,argv);
if(argv[0] == NULL)
return; /* 忽略空命令 */
if(!builtin_cmd(argv)){
sigfillset(&mask_all); /* 保存当前的信号集合(blocked位向量) */
sigemptyset(&mask_one);
sigaddset(&mask_one,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask_one,&prev_one); /* 父进程保持SIGCHLD的阻塞 */
if((pid = fork()) == 0){ /* 子程序运行用户作业 */
sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 解除子进程对SIGCHLD的阻塞,避免子进程fork出来的进程无法被回收*/
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 - 如果用户输入了一个内置的命令,那么立即执行它。
*/
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0],"quit")) /* 退出命令 */
exit(0);
if(!strcmp(argv[0],"&")) /* 忽略单& */
return 1;
return 0; /* 不是一个内置命令 */
}
/*
* waitfg - 阻塞,直到进程pid不再是前台进程
*/
void waitfg(pid_t pid)
{
/* 唯一的前台作业结束后,被sigchld_handler回收,deletejob(..)后,jobs列表中就没有前台作业,
循环fpgid(..)
*/
while(pid==fgpid(jobs)){
sleep(0);
}
return;
}
/*
* 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){ /* WNOHANG:非阻塞的 */
sigprocmask(SIG_BLOCK,&mask_all,&prev_all); /* 恢复信号集合(blocked位向量) */
deletejob(jobs,pid);
sigprocmask(SIG_SETMASK,&prev_all,NULL);
}
errno = olderrno;
return;
}
编译运行(左边我们编写的tsh,右边标准答案tshref,下同)
trace02
trace03
trace04
trace05 - 处理jobs内置命令
这个简单,直接调用自带的listjobs()方法
/*
* builtin_cmd - 如果用户输入了一个内置的命令,那么立即执行它。
*/
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0],"quit")){ /* 退出命令 */
exit(0);
}
if(!strcmp(argv[0],"jobs")){ /* jobs内置指令 */
listjobs(jobs);
return 1;
}
if(!strcmp(argv[0],"&")){ /* 忽略单& */
return 1;
}
return 0; /* 不是一个内置命令 */
}
trace06 - 将SIGINT转发到前台作业。
trace07 - 仅将SIGINT转发给前台作业。
SIGINT信号是终止。
文档中有这么一个提示。
大概就是下图所示
所以要更改eval()函数
sigprocmask(SIG_BLOCK,&mask_one,&prev_one); /* 父进程保持SIGCHLD的阻塞 */
if((pid = Fork()) == 0){ /* 子程序运行用户作业 */
sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 解除子进程对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的阻塞 */
修改sigint_handler()函数,转发到前台作业(包含前台作业的进程组)。
/*
* 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;
}
还要修改sigchld_handler()函数。区分终止原因
- 调用exit或者一个返回(return)正常终止。(原本的写法)
- 接收到其他信号如:SIGINT。终止的。(修改后要加上的)
/*
* sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
* 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
* 但不等待任何其他当前运行的子进程终止。
*/
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)) > 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 */
}
}
errno = olderrno;
return;
}
编译运行
trace06
trace07
trace08 - 仅将SIGTSTP转发到前台作业。
SIGTSTP信号是停止直到下一个SIGCONT。
和singint_handler()差不多
/*
* 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;
}
还要修改sigchld_handler()函数。区分终止/停止
- 调用exit或者一个返回(return)正常终止。(原本的写法)
- 接收到其他信号如:SIGINT。终止的。(原本的写法)
- 引起返回的子进程当前是停止的如:SIGTSTP。(修改后要加上的)
还要加多一个WUNTRACED,变成WNOHANG | WUNTRACED
WNOHANG:挂起调用进程,直到有子进程终止。
WUNTRACED:挂起调用进程,直到等待集合中的一个进程变成已终止或者被停止。
WNOHANG | WUNTRACED:等待集合中的子进程都没有被停止或终止,则返回值为0;如果有一个停止或终止,则返回值为该子进程的PID。
可以理解为WNOHANG接收终止,WUNTRACED接收停止。两个合在一起就是接收终止和停止。
/*
* sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
* 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
* 但不等待任何其他当前运行的子进程终止。
*/
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;
}
编译运行
trace08
trace09 - 进程bg内置命令
bg <job>:将一个已停止的后台作业修改为正在运行的后台作业。
往builtin_cmd()方法添加内容
if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
do_bgfg(argv);
return 1;
}
修改do_bgfg()方法
void do_bgfg(char **argv)
{
pid_t pid; /* 进程id */
int jid; /* job的id */
struct job_t * job;
if (argv[1][0] == '%'){ /* 给的是%x,即jid */
jid = atoi(argv[1]+1);
job = getjobjid(jobs,jid);
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;
}
编译运行
trace09
trace10 - 进程fg内置命令
fg <job>:将一个已停止或正在运行的后台作业更改为正在前台运行的作业。
往builtin_cmd()方法添加内容
if(!strcmp(argv[0],"fg")){ /* bg内置指令 */
do_bgfg(argv);
return 1;
}
修改do_bgfg()方法
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内置指令 */
if(job->state == ST){ /* ST:已停止 */
kill(pid,SIGCONT); /* 传递SIGCONT信号 */
}
job->state = FG; /* 更改状态 */
waitfg(pid); /* 等待前台job完成 */
}
}
编译运行
trace10
trace11 - 将SIGINT转发给前台进程组中的每个进程
在trace06-trace07中写的就是转发给进程组
void sigint_handler(int sig)
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */
if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}
运行结果(太长了,就不放全了,一个一个对着看就好)
trace11
trace12 - 将SIGTSTP转发到前台进程组中的每个进程
在trace08中写的就是转发给进程组
void sigtstp_handler(int sig)
{
pid_t pid = fgpid(jobs); /* 获取前台进程id */
if(pid > 0){
kill(-pid,sig); /* 转发信号sig给进程组|pid|中的每个进程 */
}
return;
}
运行结果(太长了,就不放全了,一个一个对着看就好)
trace12
trace13 - 重新启动进程组中的每个已停止的进程
运行标准答案可以发现主要就是这两个进程在一组,先给停止,然后重新启动。
修改do_bgfg()方法
- 传递给kill的pid修改为-pid,传递给整个进程组。
- 去掉job->state == ST才传递SIGCONT信号的判断,因为当前进程可能没有停止(ST),但是进程组有停止的,要重新启动。
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完成 */
}
}
运行结果(太长了,就不放全了,一个一个对着看就好)
trace13
trace14 - 简单的错误处理
运行看看效果,就是处理一些错误的命令。
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;
}
if (argv[1][0] == '%'){ /* 给的是%x,即jid */
if(argv[1][1] < '0' || argv[1][1] >'9'){
printf("fg: argument must be a PID or %%jobid\n");
return;
}
jid = atoi(argv[1]+1);
job = getjobjid(jobs,jid);
if(job == NULL){
printf("%%%d: No such job\n",jid);
return;
}
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->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;
}
编译运行
trace14
trace15 - 把它们放在一起
这个就是把命令放在一起测试。
trace16 - 测试shell是否能够处理来自其他进程而不是终端的SIGTSTP和SIGINT信号。
测试一波,也可以了。
总结
想说的都在注解和上面的过程中了。
void eval(char *cmdline);
/*
* eval - 计算用户刚刚输入的命令行
*
* 如果用户请求了一个内置命令(quit, jobs, bg或fg),
* 然后立即执行它。否则,fork一个子进程并在该子进程的上下文中运行该作业。
* 如果作业正在前台运行,则等待它终止然后返回。
* 注意:每个子进程必须有一个唯一的进程组ID,这样当我们在键盘上输入ctrl-c (ctrl-z)时,
* 我们的后台子进程就不会从内核接收到SIGINT (SIGTSTP)。
*/
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* 参数列表execve() */
char buf[MAXLINE]; /* 保存修改的命令行 */
int bg; /* 这个作业应该在后台进行? */
pid_t pid; /* 进程id*/
sigset_t mask_all,mask_one,prev_one;
strcpy(buf,cmdline);
bg = parseline(buf,argv);
if(argv[0] == NULL)
return; /* 忽略空命令 */
if(!builtin_cmd(argv)){
sigfillset(&mask_all); /* 保存当前的信号集合(blocked位向量) */
sigemptyset(&mask_one);
sigaddset(&mask_one,SIGCHLD);
sigprocmask(SIG_BLOCK,&mask_one,&prev_one); /* 父进程保持SIGCHLD的阻塞 */
if((pid = fork()) == 0){ /* 子程序运行用户作业 */
sigprocmask(SIG_SETMASK,&prev_one,NULL); /* 解除子进程对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;
}
int builtin_cmd(char **argv);
/*
* builtin_cmd - 如果用户输入了一个内置的命令,那么立即执行它。
*/
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0],"quit")){ /* 退出命令 */
exit(0);
}
if(!strcmp(argv[0],"jobs")){ /* jobs内置指令 */
listjobs(jobs);
return 1;
}
if(!strcmp(argv[0],"bg")){ /* bg内置指令 */
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0],"fg")){ /* bg内置指令 */
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0],"&")){ /* 忽略单& */
return 1;
}
return 0; /* 不是一个内置命令 */
}
void do_bgfg(char **argv);
/*
* do_bgfg - 执行bg和fg命令
*/
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;
}
if (argv[1][0] == '%'){ /* 给的是%x,即jid */
if(argv[1][1] < '0' || argv[1][1] >'9'){
printf("fg: argument must be a PID or %%jobid\n");
return;
}
jid = atoi(argv[1]+1);
job = getjobjid(jobs,jid);
if(job == NULL){
printf("%%%d: No such job\n",jid);
return;
}
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->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;
}
void waitfg(pid_t pid);
/*
* waitfg - 阻塞,直到进程pid不再是前台进程
*/
void waitfg(pid_t pid)
{
/* 唯一的前台作业结束后,被sigchld_handler回收,deletejob(..)后,jobs列表中就没有前台作业,
循环fpgid(..)
*/
while(pid==fgpid(jobs)){
sleep(0);
}
return;
}
void sigchld_handler(int sig);
/*
* sigchld_handler - 每当子作业终止(变成僵尸),或者因为收到SIGSTOP或SIGTSTP信号而停止时,
* 内核就向shell发送SIGCHLD。该处理程序获取所有可用的僵尸子进程,
* 但不等待任何其他当前运行的子进程终止。
*/
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:非阻塞的 WUNTRACED:等待集合的停止*/
/* 通过调用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;
}
void sigint_handler(int sig)
/*
* 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;
}
void sigint_handler(int sig);
/*
* 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;
}