shell lab
在尝试完成这个 shell lab 之前,先看看官方给了什么代码吧,一个是书上有的 shllex.c 另外还发现了神仙代码 shell.c ,这个shell.c在官方给的 code 页面中 complete set 中能找到,感觉这个很靠近我们要实现的结果。
Here is a tarfile that contains the complete set of source files, header files, and Makefiles used to produce the manuscript.
打算在此基础上更改。
然后搞清楚 jobs, bg, fg 的用法:
Unix或Linux中&、jobs、fg、bg等命令的使用方法
WriteUp
然后发现还有极其重要的 WriteUp 要读,读 pdf 文档摘取重要信息
大概函数长度
我们一共需要写7个函数,每个函数的大概长度有告知(包括注释):
- eval: 70
- builtin_cmd: 25
- do_bgfg: 50
- waitfg: 20
- sigchild_handler: 80
- sigint_handler: 15
- sigtstp_handler: 15
Ctrl - C 导致 SIGIN, 终止 process, ctrl - z 导致 SIGSTP,暂停, SIGCONT 可以唤醒。
- jobs: list
- bg <job>: stopped background job running in background
- fg <job>: stopped/running background job run in foreground
- kill <job>: terminate a job
别的提示
- ctrl - c / ctrl - z 应该发到目前的 这个foreground job, 同时包括它的孩子,如果没有 foreground job, 则无效
- 每个 job 都有 pid 和 jid, jid 是 %5 的格式, pid 就是 5
- bg/fg <job> 这个 <job> 接收 pid 和 jid
Hints
- 熟悉 waitpid, kill, fork, execve, setpid, sigprocmask 函数和 waipid 中的参数 WUNTRACED/ WNOHANG
- 需要发送给整个foreground, 用 '-pid' 而非 'pid'
- waitfg 和 sigchld_handler 建议如下处理方式: 在 waitfg 中,使用包含 sleep 的 loop; 在sigchld_handler 中,用一个 waitpid。 当然也说了别的方式也ok。比如在 waitfg 和 sigchld_handler 中都使用 waipid。
- eval 中需要注意 race condtion的
- fork 之后,执行之前,我们需要用 setpgid(0, 0)这个避免 ctrl-c 发送到每个process,包括shell.
同时发现了这个网站有助于理解 WUNTRACED/ WNOHANG
测试
模仿改了代码之后,开始做一些测试,做测试参考了这里的办法,用了一个bash file
#! /bin/bash
for file in $(ls trace*)
do
./sdriver.pl -t $file -s ./tshref > tshref_$file
./sdriver.pl -t $file -s ./tsh > tsh_$file
done
for file in $(ls trace*)
do
diff tsh_$file tshref_$file > diff_$file
done
for file in $(ls diff_trace*)
do
echo $file " :"
cat $file
echo -e "-------------------------------------\n"
- [x] trace01
- [x] trace02
- [x] trace03
- [x] trace04
- [x] trace05
- [x] trace06
- [x] trace07
- [x] trace08
- [x] trace09
- [ ] trace10
- [x] trace11
- [x] trace12
- [x] trace13
- [ ] trace14
- [ ] trace15
- [x] trace16
遗憾的发现到 trace10, trace14, trace15 有不对的地方,都是在出现类似 trace10.txt 的这样的状况下出错,调用 fg %1, 然后测试 TSTP, 看之后的进程状态,应该是正确接收到了 SIGTSTP, 但是我们并没有打印出来,o(╯□╰)o
或者我尝试把打印的部分放到 sigtstp 中,但是发现会出现打印两次的问题,不知道这个是因为什么o(╯□╰)o,不管了
代码
所以最终代码长这样,还是没有让所有的例子全对 (⊙﹏⊙)b,然后里面借鉴了很多之前提到的地方:
eval
/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline)
{
char *argv[MAXARGS]; /* argv for execve() */
int bg; /* should the job run in bg or fg */
pid_t pid; /* process id */
/* parse command line */
bg = parseline(cmdline, argv);
if (argv[0] == NULL)
return; /* ignore empty lines */
if(!strcmp(argv[0],"quit"))
exit(0); /* terminate shell */
sigset_t mask_all, mask_one, prev_one;
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);
if (!builtin_cmd(argv)) {
// Block SIGCHLD
sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
if ((pid = fork()) == 0) { /* child */
// Unblock SIGCHLD
sigprocmask(SIG_SETMASK, &prev_one, NULL);
setpgid(0,0);
/* Background jobs should ignore SIGINT (ctrl-c) */
/* and SIGTSTP (ctrl-z) */
if(bg){
Signal(SIGINT, SIG_IGN);
Signal(SIGTSTP, SIG_IGN);
}
if (execve(argv[0], argv, environ) < 0 ){
printf("%s: Command not found.\n", argv[0]);
fflush(stdout);
exit(0);
}
}
/* parent waits for foreground job to terminate or stop */
sigprocmask(SIG_BLOCK, &mask_all, NULL);
addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);
sigprocmask(SIG_SETMASK, &prev_one, NULL);
if (!bg)
waitfg(pid);
else
/* background job, we need to print out jid,pid,cmd */
printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline);
}
return;
}
builtin_cmd
/*
* builtin_cmd - If the user has typed a built-in command then execute
* it immediately.
*/
int builtin_cmd(char **argv)
{
char *cmd = argv[0];
/* job command */
if (!strcmp(cmd,"jobs")){
listjobs(jobs);
return 1;
}
/* bg and fg commands */
if (!strcmp(cmd, "bg") || !strcmp(cmd, "fg")) {
do_bgfg(argv);
return 1;
}
/* ignore singleton & */
if (!strcmp(argv[0], "&")){
return 1;
}
/* not a builtin command */
return 0;
}
do_bgfg
/*
* do_bgfg - Execute the builtin bg and fg commands
*/
void do_bgfg(char **argv)
{
char *cmd = argv[0];
int pid;
struct job_t *jobp;
/* ignore command if no argument */
if (argv[1] == NULL) {
printf("%s: command requires PID or %%jobid argument\n", cmd);
return;
}
if (argv[1][0] == '%'){
char *t = &argv[1][1];
int jid = atoi(t);
jobp = getjobjid(jobs, jid);
if (jobp == NULL){
printf("%s: No such job\n",argv[1]);
return;
}
pid = jobp->pid;
} else {
pid = atoi(argv[1]);
if (pid == 0) {
printf("%s argument must be a PID or %%jobid\n",cmd);
return;
}
jobp = getjobpid(jobs, pid);
if(jobp == NULL){
printf("(%d): No such process\n", pid);
return;
}
}
if (!strcmp(cmd,"bg")) {
kill(-pid, SIGCONT);
updatejob(jobs, pid, BG);
printf("[%d] (%d) %s", pid2jid(pid), pid, jobp->cmdline);
}
if(!strcmp(cmd,"fg")) {
kill(-pid,SIGCONT);
updatejob(jobs, pid, FG);
waitfg(pid);
}
return;
}
waitfg
/*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)
{
struct job_t *job = getjobpid(jobs, pid);
while(job->state == FG){
sleep(1);
}
return;
}
sigchld_handler
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/
void sigchld_handler(int sig)
{
pid_t pid;
int status;
if (verbose)
printf("sigchild_handler: entering \n");
while ((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0){
/* FG job has stopped. Change its state in jobs list */
if (WIFSTOPPED(status)){
sprintf(sbuf,"Job [%d] (%d) stopped by signal %d", pid2jid(pid) ,pid , WSTOPSIG(status));
printf("%s\n",sbuf);
updatejob(jobs, pid, ST);
}
/*
* Reap any zombie jobs.
* The WNOHANG here is important, Without it, the
* handler would wait for all running or stopped BG jobs
* to terminate, during which time the shell would not
* be able to accept input.
*/
/* FG job has terminated. Remove it from jobs list */
else {
/* check if job was terminated by an uncaught signal */
if (WIFSIGNALED(status)){
sprintf(sbuf,"Job [%d] (%d) terminated by signal %d", pid2jid(pid),pid,WTERMSIG(status));
printf("%s\n",sbuf);
fflush(stdout);
}
sigset_t mask_all, prev_all;
sigfillset(&mask_all);
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
if (verbose)
printf("sigchld_handler: job %d deleted\n",pid);
}
}
/*
* Check for normal loop termination.
* This is a little tricky. For our purposes,
* the waitpid loop terminates normally for one of
* two reasons: (a) there are no children left
* (pid == -1 and errno = ECHILD) or (b) there are
* still children left, but none of them are zombies (pid == 0).
*/
if (!((pid == 0) || (pid == -1 && errno == ECHILD)))
unix_error("sigchld_handler wait error");
if (verbose)
printf("sigchld_handler: exiting\n");
return;
}
sigint_handler
/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig)
{
pid_t pid;
pid = fgpid(jobs);
if (pid) {
kill(-pid, sig);
//updatejob(jobs, pid, FG);
}
return;
}
sigtstp_handler
/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig)
{
pid_t pid;
pid = fgpid(jobs);
if (pid){
kill(-pid, SIGTSTP);
updatejob(jobs, pid, ST);
}
return;
}
updatejob
另外还补充了这个 updatejob 的函数,虽然感觉这个函数也就一句话可以完成的事|||
/* updatejob - update the state of a job with PID=pid */
void updatejob(struct job_t *jobs, pid_t pid, int state)
{
int i;
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) {
//DEBUG_PRINT("job %d updated state %d\n", pid, state);
jobs[i].state = state;
return;
}
}
printf("Job %d not found\n", pid);
return;
}
结束
🔚, 这个 lab 还是很难的,而且是看了各种参考最终拼凑出来,而且也不全对的状况,希望哪天能解决遗留的这个问题吧。