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 <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 还是很难的,而且是看了各种参考最终拼凑出来,而且也不全对的状况,希望哪天能解决遗留的这个问题吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,359评论 0 5
  • linux中ctrl+z、ctrl+d和ctrl+c的区别 fg、bg、jobs、&、ctrl+z都是跟系统任务有...
    happut阅读 2,149评论 0 1
  • 实验介绍 完成一个简单的shell程序,总体的框架和辅助代码都已经提供好了,我们需要完成的函数主要以下几个: ev...
    leon4ever阅读 8,363评论 1 4
  • #########################################################...
    半斋阅读 3,729评论 0 3
  • 一个程序被加载到内存当中运行,那么在内存内的那个数据就被称为进程(process)。进程是操作系统上非常重要的概念...
    Zhang21阅读 1,915评论 0 12