前置知识
Exception Control FLow
Signal 与 Signal 阻塞
下面我们常用的一个函数 sigprocmask
的原型是这样定义的
1
| int sigprocmask(int how, const sigset_t *set, sigset_t *oldest)
|
其中参数 how 是这样的
SIG_BLOCK
:将 set
中的信号添加到 blocked
中
SIG_UNBLOCK
:从 blocked
中删除 set
中的信号
SIG_SETMASK
: block=set
进程与进程组
一个进程的进程组 ID 是其父进程的 PID
1
| int setpgid(pid_t pid, pid_t pgid);
|
setpgid()
函数可以修改进程的进程组 ID
kill
函数
kill 定义如下
1
| int kill(pid_t pid, int sig);
|
其中参数 pid 有以下几种
- pid 大于0,pid是信号欲送往的进程的标识
- pid 等于0时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程
- pid 等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)
- pid 小于-1时,信号将送往以 -pid 为组标识的进程
目标
这次是要实现一个简单的 Shell,叫做 tsh,tsh 应该有这些 feature
- 提示符以
>tsh
开始
- 这个 shell 应该处理 name 和 argv,如果 name 是一个内置的命令,tsh 应该立刻运行并且等待下一个命令;如果 name 是一个可执行文件的目录,则 tsh 需要在子进程中运行
- tsh 并不需要管道(|)和输入输出重定向(<和>)
- 输入 Ctrl-C 的时候需要引发一个
SIGINT
信号发送给当前的前台任务;如果没有任何前台任务,则 Ctrl-C 没有作用
- 如果命令行以
&
结尾,则 tsh 需要在后台运行这个任务
- 每一个任务可以由 process ID(PID) 和 job ID(JID) 区分,这两者是由 tsh 所制定的正整数。JID 需要由
%
开头
- tsh 应该支持
quit
jobs
bg <job>
fg <job>
指令
quit
退出 shell
jobs
列出所有任务
bg <job>
给后台 job
发送 SIGCONT 信号, job
可以是 PID 或 JID
fg <jog>
给前台 job
发送 SIGCONT 信号, job
可以是 PID 或 JID
- tsh 应该 reap(收割?) 僵尸子进程
目录下的 sldriver.pl
是可以检验的,可以通过 ./sldriver.pl -h
查看使用方法。同时 tshref
是一个参考样例,可以用来验证
任务
我们需要完善 tsh.c
的 7 个函数,分别是
1 2 3 4 5 6 7 8
| void eval(char *cmdline); int builtin_cmd(char **argv); void do_bgfg(char **argv); void waitfg(pid_t pid);
void sigchld_handler(int sig); void sigtstp_handler(int sig); void sigint_handler(int sig);
|
Code
eval
在编写 eval 的时候要考虑到阻塞的设计,原则有二
- 在访问全局变量的时候需要阻塞所有信号
- 在执行有前后顺序的函数时需要阻塞(如
addjob
与deletejob
)
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
| void eval(char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int state; int argc; pid_t curr_pid; sigset_t mask_all, mask_child, mask_prev;
sigemptyset(&mask_child); sigaddset(&mask_child, SIGCHLD); sigfillset(&mask_all);
strcpy(buf, cmdline); state = parseline(buf, argv) ? BG : FG;
if (!builtin_cmd(argv)) { sigprocmask(SIG_BLOCK, &mask_child, &mask_prev); if ((curr_pid = fork()) == 0) { sigprocmask(SIG_SETMASK, &mask_prev, NULL); setpgid(0,0); if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found.\n", argv[0]); } exit(EXIT_SUCCESS); }
sigprocmask(SIG_BLOCK, &mask_all, NULL); addjob(jobs, curr_pid, state, cmdline); sigprocmask(SIG_SETMASK, &mask_child, NULL);
if (state == FG) { waitfg(curr_pid); } else { sigprocmask(SIG_BLOCK, &mask_all, NULL); struct job_t *curr_bgjob = getjobpid(jobs, curr_pid); printf("[%d] (%d) %s", curr_bgjob->jid, curr_bgjob->pid, curr_bgjob->cmdline); }
sigprocmask(SIG_SETMASK, &mask_prev, NULL); }
return; }
|
builtin_cmd
这个函数比较简单,只需要比较下字符串然后执行对应的函数就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int builtin_cmd(char **argv) { char *cmd = argv[0]; sigset_t mask_all, mask_prev;
sigfillset(&mask_all);
if (!strcmp(cmd, "quit")) { exit(EXIT_SUCCESS); } else if (strcmp(cmd, "fg") == 0 || strcmp(cmd, "bg") == 0) { do_bgfg(argv); return 1; } else if (!strcmp(cmd, "jobs")) { sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); listjobs(jobs); sigprocmask(SIG_SETMASK, &mask_prev, NULL); return 1; } return 0; }
|
do_bgfg
在 Linux 中,一个进程可以接受 SIGSTP
后停止,知道接收到 SIGCONT
后再继续执行
而 bg
就是给一个进程发送 SIGCONT
,使之在后台继续执行;同理,fg
就是发送 SIGCONT
并且在前台执行
了解了 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| void do_bgfg(char **argv) { char *cmd = argv[0]; char *para = argv[1]; struct job_t *curr_job; sigset_t mask_all, mask_prev; int curr_jid;
sigfillset(&mask_all);
if (para[0] == '%') { curr_jid = atoi(&(para[1])); if (curr_jid == 0) { printf("%s:argument must be a PID or %%jobid\n", cmd); fflush(stdout); return; } } else { curr_jid = atoi(para); if (curr_jid == 0) { printf("%s:argument must be a PID or %%jobid\n", cmd); fflush(stdout); return; } sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); curr_jid = pid2jid(curr_jid); sigprocmask(SIG_SETMASK, &mask_prev, NULL); }
sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); curr_job = getjobjid(jobs, curr_jid);
if (curr_job == NULL) { printf("(%s): No such process\n", para); fflush(stdout); sigprocmask(SIG_SETMASK, &mask_prev, NULL); return; }
if (!strcmp(cmd, "bg")) { switch(curr_job->state) { case ST: curr_job->state = BG; kill(-(curr_job->pid), SIGCONT); printf("[%d] (%d) %s", curr_job->jid, curr_job->pid, curr_job->cmdline); break; case BG: break; case FG: case UNDEF: unix_error("bg error\n"); break; } } else { switch(curr_job->state) { case ST: curr_job->state = FG; kill(-(curr_job->pid), SIGCONT); waitfg(curr_job->pid); break; case BG: curr_job->state = FG; waitfg(curr_job->pid); break; case FG: case UNDEF: unix_error("fg error\n"); break; } }
sigprocmask(SIG_SETMASK, &mask_prev, NULL);
return; }
|
waitfg
首先我们需要定义一个全局变量 fg_stop_or_exit
来判断前台进程是否停止,但是在声明此变量时,应该使用特殊的标志
1
| volatile sig_atomic_t fg_stop_or_exit;
|
其中
volatile
告诉编译器不要缓存此变量,避免变量在信号 handler 中改变后 main 函数看不到改变的变量
sig_atomic_t
是一个 atomic 的整形变量
1 2 3 4 5 6 7 8 9 10 11
| void waitfg(pid_t pid) { sigset_t mask_empty; sigemptyset(&mask_empty); fg_stop_or_exit = 0; while(!fg_stop_or_exit) { sigsuspend(&mask_empty); } return; }
|
(可以看 CSAPP 上545页
sigchld_handler
sigchld_handler
的核心是 waitpid
函数,该函数的具体使用方法在这里和这里
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
| void sigchld_handler(int sig) { int olderrno = errno; sigset_t mask_all, mask_prev; pid_t child_pid; struct job_t *child_job; int status;
sigfillset(&mask_all);
while ((child_pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); child_job = getjobpid(jobs, child_pid); if (child_pid == fgpid(jobs)) { fg_stop_or_exit = 1; } if (WIFSTOPPED(status)) { child_job->state = ST; printf("Job [%d] (%d) terminated by signal %d\n", child_job->jid, child_job->pid, WSTOPSIG(status)); } else { if (WIFSIGNALED(status)) { printf("Job [%d] (%d) terminated by signal %d\n", child_job->jid, child_job->pid, WTERMSIG(status)); } deletejob(jobs, child_pid); } fflush(stdout); sigprocmask(SIG_SETMASK, &mask_prev, NULL); }
errno = olderrno; return; }
|
sigint_handler & sigtstp_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 35 36 37 38 39 40 41
| void sigint_handler(int sig) { int olderrno = errno; sigset_t mask_all, mask_prev; pid_t curr_fg_pid; sigfillset(&mask_all); sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); curr_fg_pid = fgpid(jobs); sigprocmask(SIG_SETMASK, &mask_prev, NULL); if(curr_fg_pid != 0){ kill(-curr_fg_pid, SIGINT); } errno = olderrno;
return; }
void sigtstp_handler(int sig) { int olderrno = errno; sigset_t mask_all, mask_prev; pid_t curr_fg_pid; sigfillset(&mask_all); sigprocmask(SIG_BLOCK, &mask_all, &mask_prev); curr_fg_pid = fgpid(jobs); sigprocmask(SIG_SETMASK, &mask_prev, NULL); if(curr_fg_pid != 0){ kill(-curr_fg_pid, SIGTSTP); } errno = olderrno;
return; }
|
最后的原始代码在这里
总结
Linux 编程还是挺有意思的,以后有机会可以看看 Advanced Programming in the UNIX® Environment