首先是eval函数:
void eval(char *cmdline)
{
char *argv[MAXARGS];
char buf[MAXLINE];
int bg;
pid_t pid;
sigset_t mask;
strcpy(buf, cmdline);
bg = parseline(buf, argv);
if(argv[0] == NULL)
return;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, NULL); //阻塞SIGCHLD信号
if(!builtin_cmd(argv)){
if((pid = fork()) == 0){
sigprocmask(SIG_UNBLOCK, &mask, NULL);
if(setpgid(0, 0) < 0) //将当前进程组ID改为当前进程ID
unix_error("eval: setgpid failed.\n");
if(execve(argv[0], argv, environ) < 0 ){
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
if(!bg)
addjob(jobs, pid, FG, cmdline);
else
addjob(jobs, pid, BG, cmdline);
sigprocmask(SIG_UNBLOCK, &mask, NULL); //如果不阻塞SIGCHLD信号,可能会发生先deletejob再addjob
if(!bg)
waitfg(pid);
else
printf("[%d] (%d) %s\n", pid2jid(pid), pid, cmdline);
}
return;
}
可以参考书本P525 eval函数,但不同的是这里加了一个addjob的操作,所以需要阻塞信号,因为如果不阻塞,可能发生系统先调度子进程执行,子进程执行完后发送信号给父进程,父进程收到信号先deletejob再addjob,这样就会出错。
然后是builtin_cmd函数,这个函数内容比较简单,主要是调用do_bgfg函数:
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0], "quit"))
exit(0);
if(!strcmp(argv[0], "jobs")){
listjobs(jobs);
return 1;
}
if(!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")){
do_bgfg(argv);
return 1;
}
return 0;
}
然后是do_bgfg函数:
void do_bgfg(char **argv)
{
char *id = argv[1];
struct job_t *job;
int jobid;
if(id[0] == '%')
jobid = atoi(id+1);
else
jobid = pid2jid(atoi(id));
if((job = getjobjid(jobs, jobid)) == NULL){
printf("Job is not exist.\n");
return;
}
if(!strcmp(argv[0], "bg")){
job->state = BG;
kill(-1* job->pid, SIGCONT); //*-1是发送给整个进程组
}
if(!strcmp(argv[0], "fg")){
job->state = FG;
kill(-1 * job->pid, SIGCONT);
waitfg(job->pid); //前台运行就等待他结束
}
return;
}
这里作业要求有点乱,只能根据书本P555要求一条一条来,首先拆分命令,第一个是bg或者fg,第二个是JID或者PID,首先统一把前面的全部换成JID,寻找任务返回指针,根据fg判断是前台运行还是后台运行。
waitfg很简单,就是等待前台任务执行完:
void waitfg(pid_t pid)
{
while(pid == fgpid(jobs));
return;
}
然后是SIGCHLD信号的处理,即当一个子进程停止或终止时需要使用的handler函数:
void sigchld_handler(int sig)
{
pid_t pid;
int status, child_sig;
while((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0 ){ //等待集合中的子进程有一个返回或终止,返回他的pid,否则返回零
printf("Handling child process %d\n", (int)pid);
if( WIFSTOPPED(status) )
sigtstp_handler( WSTOPSIG(status) );
else if( WIFSIGNALED(status) ) {
child_sig = WTERMSIG(status); //是什么信号导致中止
if(child_sig == SIGINT) //如果SIGINT处理这个信号
sigint_handler(child_sig);
}
else
deletejob(jobs, pid);
}
return;
}
WUNTRACED | WNOHANG 在书本P517,子进程有一个停止或者终止,返回他的PID,有三种可能,一种是ctrl-z,一种是ctrl-c,一种是完成后返回,那么分别处理,前两种主要是处理函数在做,最后一种直接删除任务即可。
针对前两种,有两个处理函数,首先是sigint_handler:
void sigint_handler(int sig)
{
pid_t pid = fgpid(jobs); //前台任务是什么
int jid = pid2jid(pid);
if(pid != 0){
printf("Job [%d] terminated by SIGINT.\n", jid);
deletejob(jobs, pid); //结束他
kill(-pid, sig);
}
return;
}
按下ctrl-c就是要前台任务结束执行,那么只要deletejob,另外还要将信号转发给进程组中所有进程,所以要写一句kill(-pid, sig),-pid就是进程组中所有进程,因为他可能在执行过程中有子进程,要一起杀死。
然后是sigtstp_handler函数:
void sigtstp_handler(int sig)
{
pid_t pid = fgpid(jobs);
int jid = pid2jid(pid);
if(pid != 0){
printf("Job[%d] stopped by SIGTSTP", jid);
(*getjobpid(jobs, pid)).state = ST;
kill(-pid, sig);
}
return;
}
是类似的,只不过不能deletejob,而是把状态改为ST。
这么做下来,test到08的时候就出现了问题,问题大致是前台的还没有结束就有新的命令输入进来这样的问题,另外有时候jobs指令也会出错,因此我参考了博客:https://blog.csdn.net/KopM1/article/details/73307213?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control
前三个函数不做改动与博客中的大致相同,不做改动。
后面函数改动如下:
void waitfg(pid_t pid)
{
int status;
sigset_t mask_all,prev_all;
sigfillset(&mask_all);
waitpid(pid,&status,WUNTRACED);
struct job_t *job = getjobpid(jobs,pid);
if(WIFSIGNALED(status)){
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
}
if(WIFSTOPPED(status)){
printf("Job [%d] (%d) stopped by signal 20\n",job->jid,job->pid);
job->state = ST;
return;
}
if(!WIFSTOPPED(status)) {
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL); //在deletejob时阻塞信号
return;
}
}
void sigchld_handler(int sig)
{
sigset_t mask_all,prev_all;
sigfillset(&mask_all);
pid_t pid;
while((pid = waitpid(-1,NULL,WNOHANG))!=0){
if(pid == -1)
break;
sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
deletejob(jobs,pid);
sigprocmask(SIG_SETMASK,&prev_all,NULL);
}
return;
}
void sigint_handler(int sig)
{
pid_t pid;
pid = fgpid(jobs);
if(pid!=0) {
kill(-pid, SIGINT);
}
return;
}
void sigtstp_handler(int sig)
{
pid_t pid;
pid = fgpid(jobs);
if(pid!=0) {
kill(-pid,SIGTSTP);
}
return;
}
sigtstp_handler和sigint_handler里面的内容变少了,这符合handler函数要尽量短小的要求,原先的函数中有printf,delete这样的函数存在,有风险!这些操作全都应该交给waitfg做,他不是一个handler函数,所以安全。另外,由于在子进程终止时候deletejob工作必须交给sigchld_handler做,所以这里deletejob使用了阻塞信号保证他在执行过程中不会受到干扰。
这样所有的make test 都和make rtest 一样了。