8.26

learn

  • origin shell code in pic 8-23, pic 8-24, pic 8-25
  • how to use waitpid in pic 8-18
  • how to wait child process in pic 8-42

and write a job management module

compose them and the little shell is born.

add bg fg jobs command to origin shell program

#include <assert.h>
#include "../csapp.h"
#include "shell.h"
#include "job.h"

void eval(char *cmdline)
{
  char *argv[MAXARGS]; /* Argument list execve() */
  char buf[MAXLINE];   /* Holds modified command line */
  int bg;              /* Should the job run in bg or fg? */
  pid_t pid;           /* Process id */

  strcpy(buf, cmdline);
  bg = parse_line(buf, argv);
  if (argv[0] == NULL)
    return;   /* Ignore empty lines */

  if (!builtin_command(argv)) {
    sigset_t mask_one, prev_one;
    Sigemptyset(&mask_one);
    Sigaddset(&mask_one, SIGCHLD);

    /* block signal child */
    Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
    if ((pid = Fork()) == 0) {
      /* unblock in child process */
      Sigprocmask(SIG_SETMASK, &prev_one, NULL);

      /* set gid same like pid */
      Setpgid(0, 0);

      if (execve(argv[0], argv, environ) < 0) {
        printf("%s: Command not found.\n", argv[0]);
        exit(0);
      }
    }

    sigset_t mask_all, prev_all;
    Sigfillset(&mask_all);
    // save job info
    Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
    Jid new_jid = new_job(pid, cmdline, !bg);
    Sigprocmask(SIG_SETMASK, &prev_all, NULL);

    if (!bg) {
      set_fg_pid(pid);
      while(get_fg_pid())
        sigsuspend(&prev_one);
    }
    else
      printf("[%d] %d %s \t %s\n", new_jid, pid, "Running", cmdline);

    /* unblock child signal */
    Sigprocmask(SIG_SETMASK, &prev_one, NULL);
  }
  return;
}

/*
 * If first arg is a builtin command, run it and return true;
 * else return false.
 */
int builtin_command(char **argv)
{
  if (!strcmp(argv[0], "quit")) /* quit command */
    exit(0);
  if (!strcmp(argv[0], "&"))    /* Ignore singleton & */
    return 1;
  if (!strcmp(argv[0], "jobs")) {
    print_jobs();
    return 1;
  }
  // > fg
  if (!strcmp(argv[0], "fg")) {
    int id;
    // right format: fg %ddd or fg ddd
    if ((id = parse_id(argv[1])) != -1 && argv[2] == NULL) {
      sigset_t mask_one, prev_one;
      Sigemptyset(&mask_one);
      Sigaddset(&mask_one, SIGCHLD);
      Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);

      pid_t pid = id;
      // if param is jid
      if (argv[1][0] == '%') {
        JobPtr jp = find_job_by_jid(id);
        pid = jp->pid;
      }
      Kill(pid, SIGCONT);
      set_fg_pid(pid);
      while(get_fg_pid())
        sigsuspend(&prev_one);

      Sigprocmask(SIG_SETMASK, &prev_one, NULL);

    } else {
      printf("format error, e.g. fg %%12 || fg 1498\n");
    }

    return 1;
  }
  // > bg
  if (!strcmp(argv[0], "bg")) {
    int id;
    // right format: bg %ddd or bg ddd
    if ((id = parse_id(argv[1])) != -1 && argv[2] == NULL) {
      pid_t pid = id;
      // jid param
      if (argv[1][0] == '%') {
        JobPtr jp = find_job_by_jid(id);
        pid = jp->pid;
      }
      Kill(pid, SIGCONT);
    } else {
      printf("format error, e.g. bg %%12  or  bg 1498\n");
    }
    return 1;
  }

  return 0;                     /* Not a builtin command */
}

/* parse_line - Parse the command line and build the argv array */
int parse_line(char *buf, char **argv)
{
  char *delim;         /* Points to first space delimiter */
  int argc;            /* Number of args */
  int bg;              /* Background job? */

  buf[strlen(buf)-1] = ' ';  /* Replace trailing '\n' with space */
  while (*buf && (*buf == ' ')) /* Ignore leading spaces */
    buf++;

  /* Build the argv list */
  argc = 0;
  while ((delim = strchr(buf, ' '))) {
    argv[argc++] = buf;
    *delim = '\0';
    buf = delim + 1;
    while (*buf && (*buf == ' ')) /* Ignore spaces */
      buf++;
  }
  argv[argc] = NULL;

  if (argc == 0)  /* Ignore blank line */
    return 1;

  /* Should the job run in the background? */
  if ((bg = (*argv[argc-1] == '&')) != 0)
    argv[--argc] = NULL;

  return bg;
}

static int is_number_str(char* s) {
  int len = strlen(s);
  for (int i = 0; i < len; i++)
    if (!isdigit(s[i]))
      return 0;

  return 1;
}

int parse_id(char* s) {
  int error = -1;
  if (s == NULL)
    return error;

  /* format: %ddddd */
  if (s[0] == '%') {
    if (!is_number_str(s+1))
      return error;

    return atoi(s+1);
  }
  /* format: dddddd */
  if (is_number_str(s))
    return atoi(s);

  /* not right */
  return error;
}

void test_shell() {
  // parse id
  assert(-1 == parse_id("ns"));
  assert(-1 == parse_id("%%"));
  assert(0 == parse_id("%0"));
  assert(0 == parse_id("0"));
  assert(98 == parse_id("%98"));
  assert(98 == parse_id("98"));
}

job management module. signal handlers are the key part

/*
 * job.c
 */
#include <stdio.h>
#include <assert.h>
#include "job.h"
#include "../csapp.h"

static volatile sig_atomic_t fg_pid;
static Job jobs[MAXJOBS];

int is_fg_pid(pid_t pid) {
  return fg_pid == pid;
}
pid_t get_fg_pid() {
  return fg_pid;
}
void set_fg_pid(pid_t pid) {
  fg_pid = pid;
}

/* SIGCONT signal */
void sigchild_handler(int sig) {
  int old_errno = errno;
  int status;
  pid_t pid;

  sigset_t mask_all, prev_all;
  Sigfillset(&mask_all);

  /* exit or be stopped or continue */
  while ((pid = waitpid(-1, &status, WNOHANG|WUNTRACED|WCONTINUED)) > 0) {
    /* exit normally */
    if (WIFEXITED(status) || WIFSIGNALED(status)) {
      if (is_fg_pid(pid)) {
        set_fg_pid(0);
      } else {
        Sio_puts("pid "); Sio_putl(pid); Sio_puts(" terminates\n");
      }
      Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
      del_job_by_pid(pid);
      Sigprocmask(SIG_SETMASK, &prev_all, NULL);
    }

    /* be stopped */
    if (WIFSTOPPED(status)) {
      if (is_fg_pid(pid)) {
        set_fg_pid(0);
      }
      // set pid status stopped
      Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
      JobPtr jp = find_job_by_pid(pid);
      set_job_status(jp, Stopped);
      Sigprocmask(SIG_SETMASK, &prev_all, NULL);

      Sio_puts("pid "); Sio_putl(pid); Sio_puts(" be stopped\n");
    }

    /* continue */
    if(WIFCONTINUED(status)) {
      set_fg_pid(pid);
      // set pid status running
      Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
      JobPtr jp = find_job_by_pid(pid);
      set_job_status(jp, Running);
      Sigprocmask(SIG_SETMASK, &prev_all, NULL);

      Sio_puts("pid "); Sio_putl(pid); Sio_puts(" continue\n");
    }
  }

  errno = old_errno;
}

void sigint_handler(int sig) {
  /* when fg_pid == 0, stop shell itself, it'll be a dead loop */
  if (is_fg_pid(0)) {
    Signal(SIGINT, SIG_DFL);
    Kill(getpid(), SIGINT);
  } else {
    Kill(get_fg_pid(), SIGINT);
  }
}

void sigstop_handler(int sig) {
  /* same like int handler */
  if (is_fg_pid(0)) {
    Signal(SIGTSTP, SIG_DFL);
    Kill(getpid(), SIGTSTP);
  } else {
    Kill(get_fg_pid(), SIGTSTP);
  }
}

JobPtr find_job_by_jid(Jid jid) {
  return &(jobs[jid]);
}

JobPtr find_job_by_pid(pid_t pid) {
  for (int i = 0; i < MAXJOBS; i++) {
    Job j = jobs[i];
    if (j.using && j.pid == pid) {
      return &(jobs[i]);
    }
  }
  /* no such job */
  return NULL;
}

void set_job_status(JobPtr jp, enum JobStatus status) {
  if (jp)
    jp->status = status;
}


// seek a spare place for new job
static int find_spare_jid() {
  Jid jid = -1;
  for (int i = 0; i < MAXJOBS; i++) {
    if (jobs[i].using == 0) {
      jid = i;
      break;
    }
  }
  return jid;
}

int new_job(pid_t pid, char* cmdline, int fg) {
  // find a jid
  Jid jid = find_spare_jid();
  if (jid == -1)
    unix_error("no more jid to use");

  // save process info
  jobs[jid].jid = jid;
  jobs[jid].pid = pid;
  jobs[jid].status = Running;
  strcpy(jobs[jid].cmdline, cmdline);
  jobs[jid].using = 1;

  return jid;
}

void del_job_by_pid(pid_t pid) {
  // search job whose pid is pid
  for (int i = 0; i < MAXJOBS; i++) {
    if (jobs[i].using && jobs[i].pid == pid) {
      // delete job
      jobs[i].using = 0;
    }
  }
}

void print_jobs() {
  for (int i = 0; i < MAXJOBS; i++) {
    Job j = jobs[i];
    if (j.using) {
      printf("[%d] %d %s \t %s\n", j.jid, j.pid,
          j.status == Running ? "Running" : "Stopped", j.cmdline);
    }
  }
}

void init_jobs() {
  memset(jobs, 0, sizeof(jobs));
}

void test_job() {

}

test it

(cd chapter8/code/shell; make && ./shell)

ps: ./loop is a dead loop program, ./sleep sleeps 5 secs and exit.

comments powered by Disqus