Shell Lab
在这个 tsh.c
文件里面,我们需要完成7个函数的编写:
1 | void eval(char *cmdline); |
eval(char * cmdline)
在调用parseline
解析输出后,我们首先判断这是一个内置命令(shell实现)还是一个程序(本地文件)。
fg %jobnumber 将后台的任务拿到前台来处理
bg %jobnumber 将任务放到后台中去处理
setpgid的用法:
1.如果pgid设置为0,该进程的进程组ID会被设置为该进程的ID
2.如果pid和pgid都指向同一个进程,那么就会重新创建一个进程组,该进程会成为该进程组的首进程
3.如果pid与pgid不一样,那么就会把pid指向的进程移动到另外一个进程组
4.如果pid设置为0,那么调用进程的进程组ID就会发生变化
1 | void eval(char *cmdline)//evaluation |
另外要注意一个线程并行竞争(race)的问题:fork
以后会在job列表里添加job,信号处理函数sigchld_handler
回收进程后会在job列表中删除,如果信号来的比较早,那么就可能会发生先删除后添加的情况。这样这个job永远不会在列表中消失了(内存泄露),所以我们要先block SIGCHLD
这个信号,添加以后再还原。
builtin_cmd(char **argv,char *cmdline)
这个函数分情况判断是哪一个内置命令。如果用户仅仅按下回车键,那么argv
的第一个变量将是一个空指针。如果用这个空指针去调用 strcmp
函数会引发 segmentation fault
1 | int builtin_cmd(char **argv, char *cmdline) |
do_bgfg(char *argv, char cmdline);
这个函数单独处理了bg
和fg
这两个内置命令。这个函数的核心就两点:
区分bg和fg命令,以及传入pid或者jid参数对应的进程的状态。前者if,后者switch就可以包括所用的情况
注意用户输入错误处理,比如参数数量不够或者参数传入错误的情况
要注意fg
有两个对应的情况:
后台程序是stopped的状态,这时我们需要设置相关变量,然后发送继续的信号。
如果这个进程本身就在运行,我们就只需要改变job的状态,设置相关变量,然后进入
waitfg
等待这个新的前台进程执行完毕。
1 | void do_bgfg(char **argv, char *cmdline) |
void waitfg(pid_t pid)
之前声明了一个volatile sig_atomic_t
的全局变量fg_pid_reap
,只要信号处理函数回收了前台进程,它就会将fg_pid_reap
置1,这样我们的waitfg
函数就会退出,接着读取用户的下一个输入。在这里我们使用busy sleep,会导致一定的延迟
1 | void waitfg(pid_t pid) |
void sigchld_handler(int sig)
从这里开始,是3个信号处理程序函数。
首先是 sigchld_handler ,也就是当一个子进程终止或者结束的时候,父进程会发送一个SIGCHLD给子进程,然后回收它
1 | void sigchld_handler(int sig) |
void sigint_handler (int sig)
这个函数是说 当我们使用 ctrl+c 的时候,kernel会使用kill函数给shell发一个 SIGINT信号给前台进程组。注意,这里是群发,因此要使用 killpg
1 | void sigint_handler(int sig) |
void sigtstp_handler(int sig)
和 sigint一样,这次发送的信号是 SIGTSTP
输入Ctrl+Z
会发送一个SIGTSTP信号到前台进程组中的每个进程。默认情况下,结果是停止(挂起suspend)前台作业。在博客的这一章节 从键盘发送信号 有说过
1 | void sigtstp_handler(int sig) |