Csapp - Shelllab

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 : stopped background job running in background
  • fg : stopped/running background job run in foreground
  • kill : terminate a job

别的提示

  • ctrl - c / ctrl - z 应该发到目前的 这个foreground job, 同时包括它的孩子,如果没有 foreground job, 则无效
  • 每个 job 都有 pid 和 jid, jid 是 %5 的格式, pid 就是 5
  • bg/fg 这个 接收 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 还是很难的,而且是看了各种参考最终拼凑出来,而且也不全对的状况,希望哪天能解决遗留的这个问题吧。

你可能感兴趣的:(Csapp - Shelllab)