Deep understanding of computer systems CSAPP_TshLab

TshLab

Start date: 22.1.20

Operating system: linux

Debugging tool: linux terminal

Preknowledge

  • tshlab means tiny shell lab
  • In this chapter of EOF (Chapter 8), you must see 8.6. The author knocked down all the running source codes in it. For convenience, please refer to the following two links: source codecsapp. Use of H
  • Be sure to read the write up carefully. The hint part is very important.
  • trace01.txt - Properly terminate on EOF tells us that tsh's main task is to properly terminate exceptions

Functions

Attention

  • During the formal experiment, seven functions should be written according to the task requirements of trace01 ~ 16. It's normal to have no way to start at the beginning. Don't worry. Check more data and think more. This paper mainly draws lessons from these two articles: CS:APP3e deep understanding of computer system_ 3e ShellLab(tsh) experiment myk's CS learning journey

  • We should learn from the correlation function given in the textbook.

  • There are one or more pg (processes) in a pg (process group). A pg is also called a job (work). jobs is the collection of all work

  • jid is the sequence of a job in jobs [1, 2,...]

  • pid: if it is child (subroutine), it represents the process number of child;
    If it is a parent (parent program), it represents both the process number of the parent and the program group number (pgid) of the parent

  • The shell will be divided into two work lists: foreground and background. There can only be one work in the foreground, but there can be multiple work in the background

  • Yes & stands for background task, and no & stands for foreground task

  • % represents the process group and% does not represent the process

  • Additionally, the global variable flag is set to indicate whether the foreground work is terminated (or stopped). (use flag when encountering foreground)

    volatile  sig_atomic_t flag; 
    /* equal 1 => jobs terminated or stopped in fg; equal 0 => jobs running in fg */
    
  • handler series functions need to set error recovery

  • In main(), the signal has been install ed

  • Use the & get address operator to get the address of data and status

eval()

  • Main functions:

      • 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.

    • As you can see, eval() is used to evaluate the instructions entered by the user. If it is an embedded instruction, it will be executed immediately;
      Otherwise, a subroutine must be created and run. For this subroutine, if it runs in the foreground, it must wait for it to terminate or return;
      If it runs in the background, it cannot receive stop / stop signals (SIGINT/SIGTSTP), which can be sent by the user typing ctrl-c/ctrl-z

  • trace04.txt - Run a background job. To call and run a background work, and then printf() gives out the relevant information (Jid, PID, process name), you will use the provided function: pid2jid()

  • trace06.txt - Forward SIGINT to foreground job;
    trace07.txt - Forward SIGINT only to foreground job;
    trace08.txt - Forward SIGTSTP only to foreground job;
    trace11.txt - Forward SIGINT to every process in foreground process group;
    trace12.txt - Forward SIGTSTP to every process in foreground process group.

    • When trace06, 07, 08, 11 and 12 involve signals, you must create and run a child (you have to write sigchld_handler()), which will generate a race between the child and the parent. You must solve it. At the same time, because SITINT and SIGTSTP are involved, sign must be written_ handler(),sigtstp_handler().

    • For the race between child and parent, according to the way in the book, you only need to block sigcld before creating the child, and unblock after creating and running the child.

    • Note the prompt given in write up:

      • After the fork, but before the execve, the child process should call setpgid(0, 0), which puts the child in a new process group whose group ID is identical to the child's PID. This ensures that there will be only one process, your shell, in the foreground process group. When you type ctrl-c, the shell should catch the resulting SIGINT and then forward it to the appropriate foreground job (or more precisely, the process group that contains the foreground job).

      • Therefore, we call setpgid(0, 0) to set the pgid of the process group in the foreground to the pid of the child.

    • For parent, before add/delete job, we must block all sigs to prevent the signal from interfering with the addition and deletion of job

      • If it is the foreground, first set flag = 1, then addjob(), and finally call waitfg() to explicitly wait for the work to terminate
      • If it is the background, you can directly add job () and then unblock.
  • Print Command not found (trace14)

void eval(char *cmdline) {
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;   
    pid_t pid;
    sigset_t mask_all, mask_one, prev;
	
    sigfillset(&mask_all); //fill all sigs to mask
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD); //mask_one has one sig: SIGCHLD
    //initjobs(jobs); already use in main(), recalled will empty new lists of jobs

    strcpy(buf, cmdline);
    bg = parseline(buf, argv); //return true, if it is background 
    if(argv[0] == NULL) //if not any cmd
        return;
    //exeluate buliltin_cmd, if not buliltin_cmd(filepath, ./exe and so on), shell will create a child process and run it
    if(!builtin_cmd(argv)) { 
    	sigprocmask(SIG_BLOCK, &mask_one, &prev); //block SIGCHLD to avoid race
        if((pid = fork()) == 0){ //create a child process
        	setpgid(0, 0); //or call setpgrp()
        	sigprocmask(SIG_SETMASK, &prev, NULL); //unblock SIGCHLD
            if(execve(argv[0], argv, environ) < 0) { //run it and if it can't run
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
     	
     	//before add/delete job, we must block all sigs
     	sigprocmask(SIG_BLOCK, &mask_all, NULL);  /* Parent: block all sigs */   
     	
     	//background: 
    	if(bg) {
     		addjob(jobs, pid, BG, cmdline); // add job to jobs(list) in background
     		sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock all sig */
     		// get jid by pid
     		printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);      	
     	}
     	
     	//foreground: If the job is running in the foreground, wait for it to terminate and then return.
     	else { 
    		flag = 0; //job running in foreground
     		addjob(jobs, pid, FG, cmdline);
     		//Still block until process pid is no longer the foreground process
     		waitfg(pid);
     	}
   }
   return;
}

do_bgfg()

  • Main functions: Execute the builtin bg and fg commands
  • trace09.txt - Process bg builtin command
    trace10.txt - Process fg builtin command.
    trace13.txt - Restart every stopped process in process group;
    trace14.txt - Simple error handling.
  • Add job to jobs(list) in bg will builds new jobs, so we can only change the state of job
    BG part: change the state to BG and then print;
    FG part: before changing the state to FG, pay attention to the blocking of the signal and the setting of the flag. At the same time, the foreground job must wait for termination or stop (waitfg)
  • When printing error messages, note that (job_ptr == NULL) must precede (job_ptr - > state = = undef)
  • If restart is satisfied, send SIGCONT. Pay attention to distinguish whether it is sent to a single process or the whole working process (process group)
void do_bgfg(char **argv) 
{	
	if (argv[1] == NULL){
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
	}
	
	//define argument, if argv[1] has value
	struct job_t *job_ptr = NULL;
	int isjob;
	int bg = 0;
	pid_t jid, pid;

	//printf error message
	if (sscanf(argv[1], "%d", &pid) > 0){
		isjob = 0;
		job_ptr = getjobpid(jobs, pid);
		//printf error if state of job is undefine or tan90°(non-being)
		//Note: (job_ptr == NULL) Before (job_ptr->state == UNDEF)
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("(%d): No such process\n", pid);
			return;
		}	
	}
	else if (sscanf(argv[1], "%%%d", &jid) > 0){
		isjob = 1;
		job_ptr = getjobjid(jobs, jid);
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("%%%d: No such job\n", jid);
			return;
		}	
	}
	else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
	}
	
	if(!strcmp(argv[0], "bg"))
		bg = 1;
	// bg command			
	if (bg){
		if(job_ptr->state == BG){
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);	
		}
		//restart
		if(job_ptr->state == ST){
			if (isjob)
				kill(-(job_ptr->pid), SIGCONT);
			else
				kill((job_ptr->pid), SIGCONT);
			job_ptr->state = BG;
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);
		}
	}
	//fg command
	else { 
			//block all sig, after return or terminated in fg
			sigset_t mask_all;
			sigfillset(&mask_all);
			sigprocmask(SIG_BLOCK, &mask_all, NULL);
			flag = 0; //job running in foreground
    
			//when "fg %1 "is typed a second time, will sents SIGCONT to restart job/process
			if (job_ptr -> state == ST){
				if (isjob)
					kill(-(job_ptr->pid), SIGCONT);
				else
					kill((job_ptr->pid), SIGCONT);
			}
			job_ptr->state = FG;
			waitfg(job_ptr->pid);
			return;
	}
	return;
}

waitfg()

  • Main function: Block until process pid is no longer the foreground process
  • eval() and do_ This function is required in the foreground part of bgfg()
void waitfg(pid_t pid)
{
	sigset_t empty;
	sigemptyset(&empty); //an empty set
	while (!flag) //flag == 0 => job running in foreground => !flag == 1
		sigsuspend(&empty); //suspend, we must wait job terminated in foreground explicitly 
	sigprocmask(SIG_SETMASK, &empty, NULL); /* Unblock all sig */	
	return;
}

builtin_cmd()

  • Main function: judge whether it is a builtin_command, execute the corresponding instruction if it is, otherwise return 0
  • trace02.txt - Process builtin quit command.
    Execute the quit instruction, which is used to exit the tsh program and call exit(0). This instruction is a foreground task (trace03.txt - Run a foreground job.)
  • strcmp()=> if equal, strcmp() return 0, so !strcmp() return 1
  • trace05.txt - Process jobs builtin command.
    Execute the jobs instruction. The function of this instruction is to list all jobs. Just call listjobs().
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0], "quit")) // if equal, strcmp() return 0, so !strcmp() return 1
        exit(0);
    if(!strcmp(argv[0], "jobs")){
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")){
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0], "&")) // ignore alone '&'
        return 1;
    return 0;     /* not a builtin command */
}

sigchld_handler()

  • SIGCHLD is called when a child is created_ Handler (), which causes the shell to send SIGCHLD signals.

  • Because sigchld_handler() is called according to the signal, so the block signal is not required when delete() reclaims zombie child

  • The following is the content of the textbook:

    • WNOHANG | WUNTRACED: Return immediately, with a return value of 0, if none of the children in the wait set has stopped or terminated, or with a return value equal to the PID of one of the stopped or terminated children.

      • WIFEXITED(status). Returns true if the child terminated normally, via a call to exit or a return.
      • WIFSIGNALED(status). Returns true if the child process terminated because of a signal that was not caught.
      • WTERMSIG(status). Returns the number of the signal that caused the child process to terminate. This status is only defined if WIFSIGNALED() returned true.
      • WIFSTOPPED(status). Returns true if the child that caused the return is currently stopped.
      • WSTOPSIG(status). Returns the number of the signal that caused the child to stop. This status is only defined if WIFSTOPPED() returned true.
    • The description of wifsignled (status) is wrong. It should be: returns true if the child process terminated because of a signal that was thought

  • Call waitpid() to wait for the child pid, then recycle the zombie child (the experiment requires if judgment here), then print the information according to the status, and finally delete (i.e. recycle) the child or change the child's state to ST. (Note: it is invalid to use jid to modify. You must use address)

  • if pid == fg_pid, job terminated or stopped in foreground
    Because wnohang | untrained: or with a return value equal to the PID of one of the stopped or terminated children

void sigchld_handler(int sig) 
{
        int olderrno = errno;
        int status;
        pid_t pid, fg_pid = fgpid(jobs); //Note: only one job in fg
	
        if ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 
	    if(pid == fg_pid)
		flag = 1;
	    if (WIFEXITED(status)) //normally terminated (return/exit)
	    	deletejob(jobs, pid);
	    else if (WIFSIGNALED(status)){  //child terminated because SIGINT that was caught.
		printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
		deletejob(jobs, pid);
	    }
    	    else if (WIFSTOPPED(status)){
   	        struct job_t *job_ptr = getjobpid(jobs, pid);//getjobpid() return address of job
                job_ptr->state = ST; //stopped the job   			
		printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
    	    }          
         }
    
        errno = olderrno;
        return;
}

sigint_handler()

  • Processing signal SIGINT
  • kill(pid, SIG) is to send signals only to a single process, and kill(-pgid, SIG) is to send signals to the whole process group
void sigint_handler(int sig) 
{	
  int olderrno = errno;
  //get pid from jobs in 'fg'
  pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
  //sent SIGINT to pgid in order to terminate the whole fg ground  
  if (pgid) {
      kill(-pgid, SIGINT); //or call killpg(-pgid, SIGINT)
  }
  errno = olderrno;
  return;
}

sigstp_handler()

  • Processing signal SIGTSTP
void sigtstp_handler(int sig) 
{	
    int olderrno = errno;
    pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
    //sent SIGTSTP to pgid in order to stopped the whole fg ground 
    if (pgid) {
	kill(-pgid, SIGTSTP); 
    }
    errno = olderrno;
    return;
}

Put all together

/* 
 * tsh - A tiny shell program with job control
 * 
 * <duile programme>
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* Misc manifest constants */
#define MAXLINE    1024   /* max line size */
#define MAXARGS     128   /* max args on a command line */
#define MAXJOBS      16   /* max jobs at any point in time */
#define MAXJID    1<<16   /* max job ID */

/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1    /* running in foreground */
#define BG 2    /* running in background */
#define ST 3    /* stopped */

/* 
 * Jobs states: FG (foreground), BG (background), ST (stopped)
 * Job state transitions and enabling actions:
 *     FG -> ST  : ctrl-z
 *     ST -> FG  : fg command
 *     ST -> BG  : bg command
 *     BG -> FG  : fg command
 * At most 1 job can be in the FG state.
 */

/* Global variables */
extern char **environ;      /* defined in libc */
char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */
int verbose = 0;            /* if true, print additional output */
int nextjid = 1;            /* next job ID to allocate */
char sbuf[MAXLINE];         /* for composing sprintf messages */

struct job_t {              /* The job struct */
    pid_t pid;              /* job PID */
    int jid;                /* job ID [1, 2, ...] */
    int state;              /* UNDEF, BG, FG, or ST */
    char cmdline[MAXLINE];  /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
volatile  sig_atomic_t flag; /* equal 1 => jobs terminated or stopped in fg; equal 0 => jobs running in fg */

/* End global variables */


/* Function prototypes */

/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv); 
void sigquit_handler(int sig);

void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs); 
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid); 
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid); 
int pid2jid(pid_t pid); 
void listjobs(struct job_t *jobs);

void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);

/*
 * main - The shell's main routine 
 */
int main(int argc, char **argv) {
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1; /* emit prompt (default) */

    /* Redirect stderr to stdout (so that driver will get all output
     * on the pipe connected to stdout) */
    dup2(1, 2);

    /* Parse the command line */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
        case 'h':             /* print help message */
            usage();
        break;
        case 'v':             /* emit additional diagnostic info */
            verbose = 1;
        break;
        case 'p':             /* don't print a prompt */
            emit_prompt = 0;  /* handy for automatic testing */
        break;
        default:
            usage();
        }
    }

    /* Install the signal handlers */

    /* These are the ones you will need to implement */
    Signal(SIGINT,  sigint_handler);   /* ctrl-c */
    Signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */
    Signal(SIGCHLD, sigchld_handler);  /* Terminated or stopped child */

    /* This one provides a clean way to kill the shell */
    Signal(SIGQUIT, sigquit_handler); 

    /* Initialize the job list */
    initjobs(jobs);

    /* Execute the shell's read/eval loop */
    while (1) {
        /* Read command line */
        if (emit_prompt) {
            printf("%s", prompt);
            fflush(stdout);
        }
        if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
            app_error("fgets error");
        if (feof(stdin)) { /* End of file (ctrl-d) */
            fflush(stdout);
            exit(0);
        }

        /* Evaluate the command line */
        eval(cmdline);
        fflush(stdout);
        fflush(stdout);
    } 

    exit(0); /* control never reaches here */
}
  
/* 
 * 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];
    char buf[MAXLINE];
    int bg;   
    pid_t pid;
	sigset_t mask_all, mask_one, prev;
	
	sigfillset(&mask_all); //fill all sigs to mask
	sigemptyset(&mask_one);
	sigaddset(&mask_one, SIGCHLD); //mask_one has one sig: SIGCHLD
	//initjobs(jobs); already use in main(), recalled will empty new lists of jobs

    strcpy(buf, cmdline);
    bg = parseline(buf, argv); //return true, if it is background 
    if(argv[0] == NULL) //if not any cmd
        return;
    //exeluate buliltin_cmd, if not buliltin_cmd(filepath, ./exe and so on), shell will create a child process and run it
    if(!builtin_cmd(argv)) { 
    	sigprocmask(SIG_BLOCK, &mask_one, &prev); //block SIGCHLD to avoid race
        if((pid = fork()) == 0){ //create a child process
        	setpgid(0, 0); //or call setpgrp()
        	sigprocmask(SIG_SETMASK, &prev, NULL); //unblock SIGCHLD
            if(execve(argv[0], argv, environ) < 0) { //run it and if it can't run
                printf("%s: Command not found.\n", argv[0]);
                exit(0);
            }
        }
     	
     	//before add/delete job, we must block all sigs
     	sigprocmask(SIG_BLOCK, &mask_all, NULL);  /* Parent: block all sigs */   
     	
     	//background: 
    	if(bg) {
     		addjob(jobs, pid, BG, cmdline); // add job to jobs(list) in background
     		sigprocmask(SIG_SETMASK, &prev, NULL); /* Unblock all sig */
     		// get jid by pid
     		printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);      	
     	}
     	
     	//foreground: If the job is running in the foreground, wait for it to terminate and then return.
     	else { 
    		flag = 0; //job running in foreground
     		addjob(jobs, pid, FG, cmdline);
     		//Still block until process pid is no longer the foreground process
     		waitfg(pid);
     	}
   }
   return;
}

/* 
 * parseline - Parse the command line and build the argv array.
 * 
 * Characters enclosed in single quotes are treated as a single
 * argument.  Return true if the user has requested a BG job, false if
 * the user has requested a FG job.  
 */
int parseline(const char *cmdline, char **argv) 
{
    static char array[MAXLINE]; /* holds local copy of command line */
    char *buf = array;          /* ptr that traverses command line */
    char *delim;                /* points to first space delimiter */
    int argc;                   /* number of args */
    int bg;                     /* background job? */

    strcpy(buf, cmdline);
    buf[strlen(buf)-1] = ' ';  /* replace trailing '\n' with space */
    while (*buf && (*buf == ' ')) /* ignore leading spaces */
		buf++;

    /* Build the argv list */
    argc = 0;
    if (*buf == '\'') {
		buf++;
		delim = strchr(buf, '\'');
    }
    else {
		delim = strchr(buf, ' ');
    }

    while (delim) {
		argv[argc++] = buf;
		*delim = '\0';
		buf = delim + 1;
		while (*buf && (*buf == ' ')) /* ignore spaces */
	       	buf++;

		if (*buf == '\'') {
	    	buf++;
	    	delim = strchr(buf, '\'');
		}
		else {
	    	delim = strchr(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;
}

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0], "quit")) // if equal, strcmp() return 0, so !strcmp() return 1
        exit(0);
    if(!strcmp(argv[0], "jobs")){
        listjobs(jobs);
        return 1;
    }
    if(!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")){
        do_bgfg(argv);
        return 1;
    }
    if(!strcmp(argv[0], "&")) // ignore alone '&'
        return 1;
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{	
	if (argv[1] == NULL){
		printf("%s command requires PID or %%jobid argument\n", argv[0]);
		return;
	}
	
	//define argument, if argv[1] has value
	struct job_t *job_ptr = NULL;
	int isjob;
	int bg = 0;
	pid_t jid, pid;

	//printf error message
	if (sscanf(argv[1], "%d", &pid) > 0){
		isjob = 0;
		job_ptr = getjobpid(jobs, pid);
		//printf error if state of job is undefine or tan90°(non-being)
		//Note: (job_ptr == NULL) Before (job_ptr->state == UNDEF)
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("(%d): No such process\n", pid);
			return;
		}	
	}
	else if (sscanf(argv[1], "%%%d", &jid) > 0){
		isjob = 1;
		job_ptr = getjobjid(jobs, jid);
		if ((job_ptr == NULL) || (job_ptr->state == UNDEF)) {
			printf("%%%d: No such job\n", jid);
			return;
		}	
	}
	else {
		printf("%s: argument must be a PID or %%jobid\n", argv[0]);
		return;
	}
	
	if(!strcmp(argv[0], "bg"))
		bg = 1;
	// bg command			
	if (bg){
		if(job_ptr->state == BG){
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);	
		}
		//restart
		if(job_ptr->state == ST){
			if (isjob)
				kill(-(job_ptr->pid), SIGCONT);
			else
				kill((job_ptr->pid), SIGCONT);
			job_ptr->state = BG;
			printf("[%d] (%d) %s", jid, job_ptr->pid, job_ptr->cmdline);
		}
	}
	//fg command
	else { 
			//block all sig, after return or terminated in fg
			sigset_t mask_all;
			sigfillset(&mask_all);
			sigprocmask(SIG_BLOCK, &mask_all, NULL);
			flag = 0; //job running in foreground
    
			//when "fg %1 "is typed a second time, will sents SIGCONT to restart job/process
			if (job_ptr -> state == ST){
				if (isjob)
					kill(-(job_ptr->pid), SIGCONT);
				else
					kill((job_ptr->pid), SIGCONT);
			}
			job_ptr->state = FG;
			waitfg(job_ptr->pid);
			return;
	}
	return;
}
/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	sigset_t empty;
	sigemptyset(&empty); //an empty set
	while (!flag) //flag == 0 => job running in foreground => !flag == 1
		sigsuspend(&empty); //suspend, we must wait job terminated in foreground explicitly 
	sigprocmask(SIG_SETMASK, &empty, NULL); /* Unblock all sig */	
	return;
}

/*****************
 * Signal handlers
 *****************/

/* 
 * 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) 
{
	int olderrno = errno;
	int status;
	pid_t pid, fg_pid = fgpid(jobs); //Note: only one job in fg
	
  	if ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { 
		if(pid == fg_pid)
			flag = 1;
	    if (WIFEXITED(status)) //normally terminated (return/exit)
	    	deletejob(jobs, pid);
	    else if (WIFSIGNALED(status)){  //child terminated because SIGINT that was caught.
			printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
			deletejob(jobs, pid);
	    }
    	else if (WIFSTOPPED(status)){
   			struct job_t *job_ptr = getjobpid(jobs, pid);//getjobpid() return address of job
        	job_ptr->state = ST; //stopped the job   			
			printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
    	}          
 	}
    
    errno = olderrno;
    return;
}
/* 
 * 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) 
{	
	int olderrno = errno;
	//get pid from jobs in 'fg'
	pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
	//sent SIGINT to pgid in order to terminate the whole fg ground  
	if (pgid) {
		kill(-pgid, SIGINT); //or call killpg(-pgid, SIGINT)
	}
  errno = olderrno;
  return;
}

/*
 * 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) 
{	
	int olderrno = errno;
	pid_t pgid = fgpid(jobs);  //process ground in fg: fg_pid == pgid == pid != 0
  
	//sent SIGTSTP to pgid in order to stopped the whole fg ground 
	if (pgid) {
		kill(-pgid, SIGTSTP); 
	}
  errno = olderrno;
  return;
}

/*********************
 * End signal handlers
 *********************/

/***********************************************
 * Helper routines that manipulate the job list
 **********************************************/

/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job) {
    job->pid = 0;
    job->jid = 0;
    job->state = UNDEF;
    job->cmdline[0] = '\0';
}

/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs) {
    int i;

    for (i = 0; i < MAXJOBS; i++)
	clearjob(&jobs[i]);
}

/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs) 
{
    int i, max=0;

    for (i = 0; i < MAXJOBS; i++)
	if (jobs[i].jid > max)
	    max = jobs[i].jid;
    return max;
}

/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
    int i;
    
    if (pid < 1)
	return 0;

    for (i = 0; i < MAXJOBS; i++) {
	if (jobs[i].pid == 0) {
	    jobs[i].pid = pid;
	    jobs[i].state = state;
	    jobs[i].jid = nextjid++;
	    if (nextjid > MAXJOBS)
			nextjid = 1;
	    strcpy(jobs[i].cmdline, cmdline);
  	    if(verbose){
	        printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
        }
        return 1;
	}
    }
    printf("Tried to create too many jobs\n");
    return 0;
}

/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid) 
{
    int i;

    if (pid < 1)
	return 0;

    for (i = 0; i < MAXJOBS; i++) {
	if (jobs[i].pid == pid) {
	    clearjob(&jobs[i]);
	    nextjid = maxjid(jobs)+1;
	    return 1;
	}
    }
    return 0;
}

/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs) {
    int i;

    for (i = 0; i < MAXJOBS; i++)
	if (jobs[i].state == FG)
	    return jobs[i].pid;
    return 0;
}

/* getjobpid  - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
    int i;

    if (pid < 1)
	return NULL;
    for (i = 0; i < MAXJOBS; i++)
	if (jobs[i].pid == pid)
	    return &jobs[i];
    return NULL;
}

/* getjobjid  - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid) 
{
    int i;

    if (jid < 1)
	return NULL;
    for (i = 0; i < MAXJOBS; i++)
	if (jobs[i].jid == jid)
	    return &jobs[i];
    return NULL;
}

/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid) 
{
    int i;

    if (pid < 1)
	return 0;
    for (i = 0; i < MAXJOBS; i++)
	if (jobs[i].pid == pid) {
            return jobs[i].jid;
        }
    return 0;
}

/* listjobs - Print the job list */
void listjobs(struct job_t *jobs) 
{
    int i;
    
    for (i = 0; i < MAXJOBS; i++) {
	if (jobs[i].pid != 0) {
	    printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
	    switch (jobs[i].state) {
		case BG: 
		    printf("Running ");
		    break;
		case FG: 
		    printf("Foreground ");
		    break;
		case ST: 
		    printf("Stopped ");
		    break;
	    default:
		    printf("listjobs: Internal error: job[%d].state=%d ", 
			   i, jobs[i].state);
	    }
	    printf("%s", jobs[i].cmdline);
	}
    }
}
/******************************
 * end job list helper routines
 ******************************/


/***********************
 * Other helper routines
 ***********************/

/*
 * usage - print a help message
 */
void usage(void) 
{
    printf("Usage: shell [-hvp]\n");
    printf("   -h   print this message\n");
    printf("   -v   print additional diagnostic information\n");
    printf("   -p   do not emit a command prompt\n");
    exit(1);
}

/*
 * unix_error - unix-style error routine
 */
void unix_error(char *msg)
{
    fprintf(stdout, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

/*
 * app_error - application-style error routine
 */
void app_error(char *msg)
{
    fprintf(stdout, "%s\n", msg);
    exit(1);
}

/*
 * Signal - wrapper for the sigaction function
 */
handler_t *Signal(int signum, handler_t *handler) 
{
    struct sigaction action, old_action;

    action.sa_handler = handler;  
    sigemptyset(&action.sa_mask); /* block sigs of type being handled */
    action.sa_flags = SA_RESTART; /* restart syscalls if possible */

    if (sigaction(signum, &action, &old_action) < 0)
	unix_error("Signal error");
    return (old_action.sa_handler);
}

/*
 * sigquit_handler - The driver program can gracefully terminate the
 *    child shell by sending it a SIGQUIT signal.
 */
void sigquit_handler(int sig) 
{
    printf("Terminating after receipt of SIGQUIT signal\n");
    exit(1);
}

trace15 & 16

Conclusion

  • Completion date: 22.1.25
  • The effective time is about 12 hours. Except that the 24th is in the afternoon and in front of the computer in the evening, other times are at noon, because you have to learn c + + in the evening. After debug ging on the 24th, you will finish writing your blog on the 25th
  • This experiment has a great relationship with the textbook, so it is mainly difficult to understand (process, anomaly, signal)
  • c language is indeed the best language to communicate with the bottom
  • We should finish learning c + + quickly and then use it in the algorithm!
  • "Bedtime fairy tale" sounds good. You can watch the ending of "beginning" in the evening! (English Name: reset, restart? Send SIGCONT?)

Keywords: C

Added by stanleyg on Thu, 27 Jan 2022 13:02:25 +0200