话不多说,直接上代码(顺序从易到难,和原题顺序不一致)
1.sigint_handler
一句话概括实现功能:实现一个 SIGINT 信号处理函数,将信号传送给前台进程。即判断如果还有前台任务,则用kill向其发送一个SIGINT信号终止该进程
void sigint_handler(int sig)
{
int olderrno = errno;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all);
sigprovmask(SIG_BLOCK, &mask_all, &prev);
if ((pid = fgpid(jobs)) != 0)
{
sigprocmask(SIG_SETMASK, &prev, NULL);
kill(-pid, SIGINT);//注意是给进程组发送信号,因为可能会产生子程序
}
errno = olderrno;
return;
}
PS:注意保存和恢复 errno。很多函数会在出错返回式设置 errno,在处理程序中调用这样的函数可能会干扰主程序中其他依赖于 errno 的部分,解决办法是在进入处理函数时用局部变量保存它,运行完成后再将其恢复
2.sigtstp_handler
一句话概括实现功能:和上题思路一致
void sigint_handler(int sig)
{
int olderrno = errno;
pid_t pid;
sigset_t mask_all, prev;
sigfillset(&mask_all);
sigprovmask(SIG_BLOCK, &mask_all, &prev);
if ((pid = fgpid(jobs)) != 0)
{
sigprocmask(SIG_SETMASK, &prev, NULL);
kill(-pid, SIGINT);//注意是给进程组发送信号,因为可能会产生子程序
}
errno = olderrno;
return;
}
3.waitfg
一句话概括实现功能:等待前台作业完成
void waitfg(pid_t pid)
{
sigset_t mask;
sigempty(&mask);
while(fgpid(jobs)!=0)
{
sigsuspend(&mask);
}
return;
}
PS:不用“pause”用“sigsuspend”函数的原因是考虑了竞争的情况,假定父进程在进入循环后,而执行pause
前,子进程终止,由于pause
仅在捕捉到信号后返回,而之后不会再有任何信号抵达,那么父进程就会永远休眠。
而"sigsuspend"相当于如下代码,1,2行代码同时执行
sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL);
5.builtin_cmd
一句话概括功能:判断是否为内置命令,是的话立即执行并返回1,不是的话返回0
if(!strcmp(argv[0],"quit"))
{
exti(0);
}
if((!strcmp(argv[0],"fg"))||(!strcmp(argv[0],"fg")))
{
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0],"jobs"))
{
listjobs(jobs);
return 1;
}
if(!strcmp(argv[0],"&"))
{
return 1;
}
return 0;
6.do_fgbg
一句话概括功能:实现fg和bg函数
void do_bgfg(char **argv)
{
struct job_t *job = NULL; //要处理的job
int state; //输入的命令
int id; //存储jid或pid
if(!strcmp(argv[0], "bg")) state = BG;
else state = FG;
if(argv[1]==NULL){ //没带参数
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return;
}
if(argv[1][0]=='%'){ //说明是jid
if(sscanf(&argv[1][1], "%d", &id) > 0){
job = getjobjid(jobs, id); //获得job
if(job==NULL){
printf("%%%d: No such job\n", id);
return;
}
}
}
else if(!isdigit(argv[1][0])) { //其它符号,非法输入
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
else{ //pid
id = atoi(argv[1]);
job = getjobpid(jobs, id);
if(job==NULL){
printf("(%d): No such process\n", id);
return;
}
}
Kill(-(job->pid), SIGCONT); //重启进程, 这里发送到进程组
job->state = state;
if(state==BG)
printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
else
waitfg(job->pid);
return;
}
7.eval
一句话概括功能:解析命令是内置命令还是其他命令,是前台作业还是后台作业
void eval(char *cmdline)
{
char *argv[MAXARGS]; //存放解析的参数
char buf[MAXLINE]; //解析cmdline
int bg; //判断程序是前台还是后台执行
int state; //指示前台还是后台运行状态
pid_t pid; //执行程序的子进程的pid
strcpy(buf, cmdline);
bg = parseline(buf, argv); //解析参数
state = bg? BG:FG;
if(argv[0] == NULL) //空行,直接返回
return;
sigset_t mask_all, mask_one, prev_one;
Sigfillset(&mask_all);
Sigemptyset(&mask_one);
Sigaddset(&mask_one, SIGCHLD);
if(!builtin_cmd(argv)) { //判断是否为内置命令
Sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //fork前阻塞SIGCHLD信号
if((pid = Fork()) == 0) { //创建子进程
Sigprocmask(SIG_SETMASK, &prev_one, NULL); //解除子进程的阻塞
Setpgid(0, 0); //创建新进程组,ID设置为进程PID
Execve(argv[0], argv, environ); //执行
exit(0); //子线程执行完毕后一定要退出
}
if(state==FG){
Sigprocmask(SIG_BLOCK, &mask_all, NULL); //添加工作前阻塞所有信号
addjob(jobs, pid, state, cmdline); //添加至作业列表
Sigprocmask(SIG_SETMASK, &mask_one, NULL);
waitfg(pid); //等待前台进程执行完毕
}
else{
Sigprocmask(SIG_BLOCK, &mask_all, NULL); //添加工作前阻塞所有信号
addjob(jobs, pid, state, cmdline); //添加至作业列表
Sigprocmask(SIG_SETMASK, &mask_one, NULL);
printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline); //打印后台进程信息
}
Sigprocmask(SIG_SETMASK, &prev_one, NULL); //解除阻塞
}
return;
}
主要借鉴了知乎大佬“潜龙勿用”的思路
附上链接