experimento csapp explicación detallada del experimento 7-shelllab

laboratorio de 7 conchas

Bienvenido al blog del autor sakura sin ropa de pera pintada.

1 Breve análisis

Propósito del experimento: Familiarizado con las señales y la gestión de procesos.

Espacio de trabajo: tsh.c

Contenido del experimento: implemente una pequeña versión Unix del shell, principalmente implementando

  1. Función de evaluación: analiza y traduce la línea de comando de entrada
  2. Función incorporada_cmd: reconoce y traduce los comandos integrados quit, fg, bg, jobs
  3. Función do_bgfg: implementa los comandos integrados fg, bg
  4. Función waitfg: espera a que se complete el comando de primer plano
  5. Función sigchld_handler: captura la señal SIGCHILD (cuando un proceso finaliza o se detiene, el proceso padre del proceso recibirá la señal SIGCHLD)
  6. Función sigint_handler: captura la señal SIGINT (ctrl-c)
  7. Función sigtstp_handler: captura la señal SIGTSTP (ctrl-z)

Método experimental: familiarícese con las señales y el shell de Unix y simplemente implemente las siete funciones anteriores.

2 Implementación específica

1. Revisa varias funciones importantes

  1. waitpid: se utiliza para pausar el proceso principal y esperar a que finalice el proceso secundario
    • pid>0: espera el proceso hijo de este pid
    • pid=-1: espera cualquier proceso hijo
    • pid <-1: espera el grupo de procesos cuyo pid es un valor absoluto
  2. sigprocmask: se utiliza para blindar las señales para que estas no sean procesadas cuando se reciben, tiene los siguientes parámetros:
  • SIG_BLOCK: agrega la señal en el conjunto a la palabra de máscara actual
  • SIG_UNBLOCK: elimina la señal en el conjunto de la palabra de máscara actual
  • SIG_SETMASK: establece la palabra de máscara de señal actual para configurar
  1. sigfillset: carga todas las señales en el conjunto
  2. sigemptyset: inicializa un conjunto vacío
  3. sigaddset: Agrega una señal al conjunto

2. Analizar varias funciones dadas por el autor.

  1. La estructura de cada tarea, incluyendo
    • pid: identificación del proceso
    • jid: identificación del trabajo

Nota: Los usuarios pueden iniciar uno o más procesos para realizar un trabajo. Por ejemplo, yo uso varios procesos para ejecutar una función de conteo. Este experimento debe ser que un proceso corresponda a un trabajo.

  • estado: estado del trabajo, UNDEF significa que el trabajo no se ha creado o ha finalizado, BG significa ejecutándose en segundo plano, FG significa ejecutándose en primer plano, ST significa que el trabajo se ha detenido, como recibir una señal SIGSTOP o SIGTSTP.
  • cmdline: declaración de línea de comando correspondiente

jobs contiene un total de 16 espacios libres de empleo

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 */
  1. parseline : analiza los parámetros de la línea de comando, como python echo.py hello &, divide la línea de comando en tres segmentos: python, echo.py, hello y los almacena en la matriz argv, luego verifica si hay un indicador de ejecución en segundo plano &, y si es así, devuelve 1; de lo contrario, se devuelve 0, lo que indica que es un comando de primer plano.
  2. initjobs (clearjobs): inicializa el grupo de trabajos
  3. maxjid : Devuelve el ID de trabajo más grande del grupo de trabajos.
  4. addjob : agrega un trabajo al grupo de trabajos
  5. eliminartrabajo : eliminar un trabajo
  6. fgpid : Devuelve el pid del trabajo en primer plano actual; si no, devuelve 0
  7. getjobpid : Encuentra un trabajo por su pid
  8. getjobjid : Encuentra trabajos por su jid
  9. pid2jid : asigna el pid del trabajo al jid del trabajo.
  10. listjobs : imprime todos los trabajos
  11. uso : imprimir las reglas de uso

Estas son las funciones existentes. Las funciones restantes son todas para el procesamiento de errores y se pueden omitir.

2. función de evaluación

Función: analizar si el comando es un comando integrado u otro comando, ya sea un trabajo en primer plano o un trabajo en segundo plano.

Idea:

  1. Primero, divida el comando en argv mediante la función parseline y determine si es un comando en primer plano o en segundo plano.
  2. Luego use la función buildin_cmd implementada más adelante para determinar si es un comando integrado u otro comando.
  3. Si está integrado, buildin_cmd se puede implementar directamente
  4. De lo contrario, primero debe proteger la señal SIGCHLD: porque si el proceso hijo fork finalizó antes de que se ejecutara execve, pero el proceso padre no ha tenido tiempo de procesar la señal SIGCHLD, el nuevo programa puede comenzar a ejecutarse en un estado "ya finalizado". proceso hijo. , lo que conducirá a confusión estatal
  5. Después del blindaje, bifurque un proceso secundario y use execve para ejecutar el comando.
  6. Luego, el proceso principal también debe determinar si se trata de un comando de primer plano. Si es así, debe esperar con waitpid; de lo contrario, generará información de fondo.
  7. Finalmente, tenga en cuenta que todas las señales deben bloquearse al llamar a la función addjob para evitar que el proceso secundario se complete cuando se ejecuta addjob.
void eval(char *cmdline) 
{
    char *argv[MAXARGS];
    char buf[MAXLINE];
    int bg;
    int state;
    pid_t pid;
    sigset_t mask_all, mask_one, prev_one;

    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    state = bg? BG : FG;

    if (argv[0] == NULL)
    {
        return;
    }

    if (!builtin_cmd(argv))
    {
        sigfillset(&mask_all);
        sigemptyset(&mask_one);
        sigaddset(&mask_one, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
        
        if ((pid = fork()) == 0)
        {
            sigprocmask(SIG_SETMASK, &prev_one, NULL);
            setpgid(0, 0);
            execve(argv[0], argv, environ);
            exit(0);
        }
        if (state == FG)
        {
            sigprocmask(SIG_BLOCK, &mask_all, NULL);
            addjob(jobs, pid, state, cmdline);
            sigprocmask(SIG_SETMASK, &mask_one, NULL);
            waitfg(pid);
        }
        else
        {
            sigprocmask(SIG_BLOCK, &mask_all, NULL);
            addjob(jobs, pid, state, cmdline);
            sigprocmask(SIG_SETMASK, &mask_one, NULL);
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
        }
        sigprocmask(SIG_SETMASK, &prev_one, NULL);
    }

    return;
}

4. función buildin_cmd

Función: Determine si es un comando integrado. En caso afirmativo, ejecútelo inmediatamente y devuelva 1. Si no, devuelva 0.

Idea: simplemente juzgue argv[0]

int builtin_cmd(char **argv) 
{
    if (!strcmp(argv[0], "quit"))
    {
        exit(0);
    }
    else if (!strcmp(argv[0], "jobs"))
    {
        listjobs(jobs);
        return 1;
    }
    else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
    {
        do_bgfg(argv);
        return 1;
    }
    else if (!strcmp(argv[0], "&"))
    {
        return 1;
    }
    return 0;     /* not a builtin command */
}

5. función do_fgbg

Función: implementar funciones fg y bg

Idea:

  1. Distinguir si es una función fg o una función bg
  2. Determine si se utiliza jid o pid y analice la identificación
  3. Utilice la función getjob para encontrar el trabajo.
  4. Envía una señal SIGCONT al trabajo para permitir que un proceso o grupo de procesos que se detuvo (por ejemplo, porque se recibió una señal SIGSTOP, SIGTSTP o SIGTTIN SIGTTOU ) continúe ejecutándose.
  5. Luego determine si es el comando fg. En caso afirmativo, use waitfg para implementarlo para esperar a que el proceso termine de ejecutarse. Si es bg, genere la información.
void do_bgfg(char **argv) 
{
    struct job_t *job = NULL;
    int state;
    int id;

    if (!strcmp(argv[0], "bg"))
    {
        state = BG;
    }
    else
    {
        state = FG;
    }

    if (argv[1] == NULL)
    {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }

    if (argv[1][0] == '%')
    {
        if (sscanf(&argv[1][1], "%d", &id) > 0)
        {
            job = getjobjid(jobs, id);
            if (job == NULL)
            {
                printf("%%%d: No such job\n", id);
                return;
            }
        }
    }
    else if (!isdigit(argv[1][0]))
    {
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    else
    {
        id = atoi(argv[1]);
        job = getjobpid(jobs, id);
        if (job == NULL)
        {
            printf("(%d): No such process\n", id);
            return;
        }
    }
    kill(-(job->pid), SIGCONT);
    job->state = state;
    if (state == BG)
    {
        printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
    }
    else
    {
        waitfg(job->pid);
    }

    return;
}

6. función de espera

Función: esperar a que se complete el trabajo en primer plano

Idea: siga esperando en un bucle hasta que finalice el proceso secundario. En este momento, fgpid devolverá 0

void waitfg(pid_t pid)
{
    sigset_t mask;
    sigemptyset(&mask);
    while (fgpid(jobs) != 0)
    {
        sigsuspend(&mask);
    }
    return;
}

7. sigchld_handler

Función: Señal de señal de proceso. Esta señal es una señal enviada por el sistema operativo al proceso principal para notificarle que el estado de su proceso secundario ha cambiado. Los cambios de estado pueden incluir que el proceso secundario finalice, se detenga o continúe ejecutándose. Aquí solo necesitamos ocuparnos de la suspensión y la terminación.

Idea:

  1. Utilice waitpid para esperar todos los procesos secundarios. Si el estado de algún proceso secundario cambia, procéselo.
  2. Determinar si salió normalmente, terminó debido a una señal o se detuvo debido a una señal
    • La opción WNOHANG hace que waitpid() no se bloquee cuando ningún proceso secundario finalice, sino que devuelva 0 inmediatamente. La opción WUNTRACED hace que waitpid() también regrese cuando se detiene el proceso hijo.
    • WIFEXITED: una definición de macro para comprobar si el proceso secundario finaliza normalmente
    • WIFSIGNALED: una definición de macro utilizada para verificar si el proceso secundario finalizó debido a que recibió una señal no detectada.
    • WIFSTOPPED: una definición de macro utilizada para comprobar si el proceso secundario está detenido
void sigchld_handler(int sig) 
{
    int olderrno = errno;
    int status;
    pid_t pid;
    struct job_t *job;
    sigset_t mask_all, prev;
    sigfillset(&mask_all);

    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) 
    {
        sigprocmask(SIG_BLOCK, &mask_all, &prev);
        if (WIFEXITED(status))
        {
            deletejob(jobs, pid);
        }
        else if (WIFSIGNALED(status))
        {
            printf ("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
            deletejob(jobs, pid);
        }
        else if (WIFSTOPPED(status))
        {
            printf ("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
            job = getjobpid(jobs, pid);
            job->state = ST;
        }
        sigprocmask(SIG_SETMASK, &prev, NULL);          
    }
    errno = olderrno;
    return;
}

8. sign_handler

Función: manejar ctrl-c, la señal SIGINT

Idea: para determinar si todavía hay una tarea en primer plano, use kill para enviarle una señal SIGINT para finalizar el proceso.

void sigint_handler(int sig) 
{
    int olderrno = errno;
    pid_t pid;
    sigset_t mask_all, prev;
    sigfillset(&mask_all);
    sigprovmask(SIG_BLOCK, &mask_all, &prev);
    
    if ((pid = fgpid(jobs)) != 0)
    {
        sigprocmask(SIG_SETMASK, &prev, NULL);
        kill(-pid, SIGINT);
    }
    errno = olderrno;
    return;
}

9. sigststp_handler

Función: manejar ctrl-z, la señal SIGTSTP

Idea: Igual que sigint_handler

void sigtstp_handler(int sig) 
{
    int olderrno = errno;
    pid_t pid;
    sigset_t mask_all, prev;
    sigfillset(&mask_all);
    sigprovmask(SIG_BLOCK, &mask_all, &prev);
    
    if ((pid = fgpid(jobs)) != 0)
    {
        sigprocmask(SIG_SETMASK, &prev, NULL);
        kill(-pid, SIGSTOP);
    }
    errno = olderrno;
    return;
}

Nota: Todos los códigos provienen de CSAPP | Análisis en profundidad de Lab7-Shell Lab - Zhihu (zhihu.com) , esta persona realmente escribe bien

Supongo que te gusta

Origin blog.csdn.net/m0_65591847/article/details/132922429
Recomendado
Clasificación