Experimento csapp, un shell simple Tarea de laboratorio L5: Escribir su propio shell Unix

Instrucciones del experimento http://csapp.cs.cmu.edu/3e/shlab.pdf

Todo lo que necesitas saber está en la guía del experimento. Aquí están los puntos importantes sobre este experimento:

  • Tener claros los conceptos de primer plano y fondo. Este es un concepto creado por el shell. Cuando hay un comando externo, lo ejecutamos directamente. Sin embargo, si es un comando de primer plano, esperará a que se complete la ejecución y Al fondo no le importará. Por lo tanto, las funciones de procesamiento de señales son solo para el shell. Al presionar ctrl+z y ctrl+c se envían al shell. (Aquí existe un concepto de grupo de procesos. De forma predeterminada, las señales se envían al grupo de procesos de primer plano, por lo que lo que se envía ahora es el shell que escribimos nosotros mismos. Para evitar que el subproceso creado por comandos externos reciba estas señales, el El subproceso debe crearse antes Al configurar un número de grupo de proceso separado)
  • Sea claro sobre cuándo bloquear qué señales y por qué. Por ejemplo, bloquear SIGCHLD al crear un proceso hijo se debe a que el proceso padre puede agregar un trabajo a los trabajos después de que el proceso hijo haya terminado de ejecutarse. Para evitar esta situación, la señal debe bloquearse y luego desbloquearse después de agregarla. Sin embargo, no se pueden bloquear todas las señales porque se pueden recibir SIGTSTP o SIGINT.
  • En la función de procesamiento SIGCHLD, bloqueé todas las señales. Ahora parece innecesario, porque las otras dos funciones de procesamiento de señales no operan en trabajos globales. Pero eso no importa en este programa. Es mejor escribir la función de procesamiento de señales de una manera más simple y tener menos lugares para escribir waitpid, de lo contrario será muy complicado. Aquí hay una lista de buenos hábitos conservadores mencionados en el libro:
  • El controlador es lo más simple posible. El controlador puede simplemente establecer un indicador global y regresar inmediatamente, y todo el procesamiento relacionado con la recepción de la señal lo realiza el programa principal, que verifica y restablece periódicamente este indicador.
  • Llame únicamente a funciones seguras de señales asíncronas en el controlador. La función asíncrona segura para señales aquí se refiere a: o la función es reentrante (como el acceso a variables locales) o el controlador de señales no puede interrumpirla.
  • Guardar y restaurar error. Prevenir interferencias
  • Bloquea todas las señales. Proteja el acceso a estructuras de datos globales compartidas. Como si solo tuviéramos una función de procesamiento para acceder a los trabajos, que en realidad puede bloquear todas las señales. Pero esto también es un buen hábito, después de todo, ¿qué pasa si un día lo cambias y olvidas que hay un peligro oculto?
  • Utilice volátil para declarar variables globales. La declaración volátil le indicará al compilador que guarde en caché esta variable y siempre la lea desde dentro.
  • Utilice el indicador sig_atomic_t, que garantiza que las lecturas y escrituras en escalares sean ininterrumpibles (atómicas).
  • Ah, y la otra cosa es usar los parámetros de waitpid correctamente, waitpid está bloqueado por defecto. El fotógrafo WNOHANG puede regresar inmediatamente y configurar WUNTRACED puede verificar los procesos secundarios terminados y detenidos, lo cual es útil cuando se espera en primer plano.
  • Además, cuando el shell envía una señal al primer plano, la envía a todo el grupo de procesos del primer plano, así que recuerde usar un número negativo al matar.
  • La función de procesamiento de señales y la función que se estaba ejecutando antes de recibir la señal son concurrentes. Si una operación que requiere relativamente mucho tiempo, como waitpid, no se coloca en la función de procesamiento de señales, no cumplirá con los requisitos de las instrucciones experimentales. Y parece más razonable poner waitpid en la función de procesamiento de señales.

Como la computadora es Windows, escribo el código en sublime y luego lo coloco en CLion para ver si hay una advertencia. Si no, lo subo al servidor en la nube y lo ejecuto en Linux. Si algo sale mal, lo pensaré. sobre esto primero, y luego voy a la depuración de gdb cuando no lo espero. No es tan divertido como IDE. No puedes ver los valores de varias variables al mismo tiempo cuando alcanzas el punto de interrupción (tal vez no No sé cómo usarlos), sólo puedes colocarlos uno por uno. Parece que presionar el teclado no es tan rápido como hacer clic con el mouse. Pero aún necesitas estar más familiarizado con la depuración en Linux. Planeo reinstalar una máquina virtual gráfica y luego usar CLion para depurarla.

Cuando pones el código en sublime, encontrarás que se ve mucho mejor, se siente más cómodo que en CLion y es mucho más fácil de saltar.
Bajo sublime
Escribe aquí la descripción de la imagen.
Bajo CLion
Escribe aquí la descripción de la imagen.

/* 
 * tsh - A tiny shell program with job control
 * 
 * <wrc>
 */
#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 */

//是否正在等待的前台完成
volatile sig_atomic_t waiting =0;
volatile sig_atomic_t waitingPid =-1;//正在等待的前台的pid,仅当waiting为1时有效

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 */
/* 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, sigset_t *set);

void sigchld_handler(int sig);

void sigtstp_handler(int sig);

void sigint_handler(int sig);

//包装函数
void Sigfillset(sigset_t *);

void Sigemptyset(sigset_t *);

void Sigaddset(sigset_t *, int);

void Sigprocmask(int how, const sigset_t *set, sigset_t *oldsest);


/* 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 + 1];
    int bg = parseline(cmdline, (char **) &argv);
    if (!builtin_cmd((char **) &argv)) {
        pid_t pid;
        sigset_t mask_one, prev_one;
        //其实好像不需要阻塞全部信号
        //Sigfillset(&mask_all);
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one, SIGCHLD);
        //这里要显示阻塞CHILD信号,防止子进程提早执行完但父进程还未addjob
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
        if ((pid = fork()) < 0) {
            unix_error("Fork error");
        }
        if (pid == 0) {
   
   //Child runs here
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);//unblock 子进程的SIGCHLD
            setpgid(pid, 0);
            if (execve(argv[0], argv, environ) < 0) {
                printf("%s: Command not found\n", argv[0]);
                exit(1);
            }
        }
        //Sigprocmask(SIG_BLOCK, &mask_all, NULL);
        addjob(jobs, pid, ((bg == 1) ? BG : FG), cmdline);
        //Sigprocmask(SIG_SETMASK, &mask_one, NULL);
        //这里是不是有点多此一举?应该不是,毕竟ctrl+z和ctrl+c的信号不应该在这里阻塞的,不然就死锁了
        if (!bg) {
            waiting=1;
            waitingPid=pid;
            waitfg(pid, &prev_one);

        } else {
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
        }
        Sigprocmask(SIG_SETMASK, &prev_one, NULL);
    }


    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")) {
        //是不是还需要回收子进程?
        while (waitpid(-1, NULL, 0) > 0);
        exit(0);
    }
    if (!strcmp(argv[0], "jobs")) {
        listjobs(jobs);
        return 1;
    }
    if (!strncmp(argv[0], "bg", 2) || !strncmp(argv[0], "fg", 2)) {
        do_bgfg(argv);
        return 1;
    }
    return 0;     /* not a builtin command */
}

/*
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) {


    int bOrf, isJob, id;
    char *temp;
    if (!strncmp(argv[0], "bg", 2)) {
        bOrf = 0;
    } else {
        bOrf = 1;
    }
    if(argv[1]==NULL){
        if(!bOrf){
            printf("bg command requires PID or %%jobid argument\n");
        }else{
            printf("fg command requires PID or %%jobid argument\n");
        }
        return;
    }
    struct job_t *job;
    if (!strncmp(argv[1], "%%", 1)) {
        isJob = 1;
        temp = argv[1]+1;
        id = atoi(temp);
        job = getjobjid(jobs, id);
    } else {
        isJob = 0;
        temp = argv[1];
        id = atoi(temp);
        job = getjobpid(jobs, id);
    }
    if(id==0){
        if(bOrf){
            printf("fg: argument must be a PID or %%jobid\n");
        }else{
            printf("bg: argument must be a PID or %%jobid\n");
        }
        return;
    }
    if (job == NULL) {
        if (!isJob) {
            printf("(%d): No such process\n",id);
        } else {
            printf("%%%d: No such job\n",id);
        }
        return;
    } else {
        if (0 == bOrf) {
   
   //bg
            job->state = BG;
            kill(-job->pid, SIGCONT);
            printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
        } else {
   
   //fg
            //waitfg
            job->state = FG;
            sigset_t mask, prev;
            Sigemptyset(&mask);
            Sigaddset(&mask, SIGCHLD);
            //这里要显示阻塞CHILD信号,防止子进程唤醒,执行完,回收完了,父进程才去判断,就会一直等
            Sigprocmask(SIG_BLOCK, &mask, &prev);
            waiting=1;
            waitingPid=job->pid;
            kill(-job->pid, SIGCONT);
            waitfg(job->pid, &prev);
            Sigprocmask(SIG_SETMASK, &prev, NULL);
        }
    }
    return;
}

/*
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid, sigset_t *set) {
    while (waiting==1) {
        sigsuspend(set);
    }
    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 temp = errno;
    pid_t cpid;
    int status;
    sigset_t mask_all, prev_one;
    Sigfillset(&mask_all);
    while ((cpid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) {
        //这里应该判断是挂起还是终止,根据不同来更新或删除job
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_one);
        if(cpid==waitingPid)
            waiting=0;
        if (WIFEXITED(status)) {
            deletejob(jobs, cpid);
        } else if (WIFSIGNALED(status)) {
            printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(cpid), cpid, WTERMSIG(status));
            deletejob(jobs, cpid);
        } else if (WIFSTOPPED(status)) {
            struct job_t *fjob = getjobpid(jobs, cpid);
            fjob->state = ST;
            printf("Job [%d] (%d) stopped by signal 20\n", pid2jid(cpid), cpid);
        }
        Sigprocmask(SIG_SETMASK, &prev_one, NULL);
    }
    errno = temp;
    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 temp = errno;
    pid_t fpid;
    fpid = fgpid(jobs);
    if (fpid > 0) {
        kill(-fpid, SIGINT);//向整个进程组发
    }
    errno = temp;
    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) {
    pid_t fpid;
    fpid = fgpid(jobs);
    if (fpid > 0) {
        kill(-fpid, SIGTSTP);//向整个进程组发
    }
    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);
}

void Sigfillset(sigset_t *set) {
    if (sigfillset(set) < 0) {
        unix_error("sigfillset error");
    }
}

void Sigemptyset(sigset_t *set) {
    if (sigemptyset(set) < 0) {
        unix_error("sigemptyset error");
    }
}

void Sigaddset(sigset_t *set, int num) {
    if (sigaddset(set, num) < 0) {
        unix_error("sigaddset error");
    }
}

void Sigprocmask(int how, const sigset_t *set, sigset_t *oldsest) {
    if (sigprocmask(how, set, oldsest) < 0) {
        unix_error("sigprocmask error");
    }
}

Supongo que te gusta

Origin blog.csdn.net/ZhaoBuDaoFangXia/article/details/72853812
Recomendado
Clasificación