proceso-exec/zombies y huérfanos/espera/tuberías

familia de funciones ejecutivas

        Después de que fork crea un proceso hijo, ejecuta el mismo programa que el proceso padre (pero puede ejecutar una rama de código diferente). El proceso hijo a menudo llama a una función ejecutiva para ejecutar otro programa. Cuando un proceso llama a una función ejecutiva, el código y los datos del espacio de usuario del proceso son completamente reemplazados por el nuevo programa, comenzando desde la rutina de inicio del programa principal. Llamar a exec no crea un nuevo proceso, por lo que la identificación del proceso no cambia antes y después de llamar a exec

función execlp

Cargue un proceso con la variable de entorno PATH
        int execlp(cosnt char* file, const char* arg,...); Éxito: no hay retorno Fallo: -1
        Parámetro 1: El nombre del programa a cargar. Esta función debe usarse con la variable de entorno PATH. Cuando no hay ningún parámetro 1 después de buscar en todos los directorios en PATH, se devolverá un error.
        Esta función se usa generalmente para llamar programas del sistema. Tales como: ls, date, cp, cat y otros comandos

Execlp implementa ls -l -a en el proceso hijo

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void){
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent\n");
    }else{
        execlp("ls","ls","-l","-a",NULL);
    }

    return 0;
}

función de ejecución

Cargue un proceso por ruta + nombre del programa
        int execl(const char* ruta, const char* arg,...); éxito: sin retorno, falla: -1
vs. execlp, como cargar el comando "ls" con parámetros -l -F
execl("/bin/ls","ls","-l","-F",NULL);

Utilice execl para lograr

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void){
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent\n");
    }else{
        execl("bin/ls","ls","-l","-a",NULL);
    }

    return 0;
}

La función execvp
carga un proceso utilizando una variable de entorno personalizada env
        int execvp(const char* file, const char* argv[]);

dup2

Imprimir información del proceso en un archivo

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void){
    int fd;

    fd = open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(fd<0){
        perror("open ps.out error");
        exit(1);
    }
    dup2(fd, STDOUT_FILENO);

    execlp("ps","ps","ax",NULL);

    return 0;
}

Procesos zombies y huérfanos

Proceso huérfano: cuando el proceso padre finaliza antes que el proceso hijo, el proceso hijo se convierte en un proceso huérfano y el proceso padre del proceso hijo se convierte en el proceso init, que se denomina proceso init y adopta el proceso huérfano.

Proceso zombie: el proceso finaliza, el proceso principal no se ha reciclado y los recursos residuales (PCB) del proceso secundario se almacenan en el kernel y se convierten en un proceso zombie.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
    pid_t pid;

    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(1);
    }else if(pid > 0){
        sleep(1);
        printf("parent pid = %d, parentID = %d\n",getpid(),getppid());
    }else if(pid == 0){
        printf("child pid = %d, parentID = %d\n",getpid(),getppid());
        sleep(3);
        printf("child pid = %d, parentID = %d\n",getpid(),getppid());
    }
    return 0;
}

 Aquí podemos ver que la primera salida del proceso padre y el proceso hijo no son un problema, pero después de dormir durante 3 segundos, el proceso padre ha finalizado y la segunda salida del proceso hijo entrará al orfanato, por lo que el segundo hijo proceso El parentID de salida es 1
y el directorio correspondiente es /sbin/init. El proceso de inicio aquí finalmente recicla el proceso huérfano.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main(void){
    pid_t pid;
    pid = fork();

    if(pid == 0){
        printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
        sleep(10);
        printf("-------child die-------");
    }else if(pid > 0){
        while(1){
            printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
            sleep(1);
        }
        
    }else{
        perror("fork");
        return 1;
    }
    return 0;
}

Después de compilar y ejecutar, puede encontrar que hay un archivo [] más en ps aux, que es el archivo zombie.

función de espera

Cuando un proceso finaliza, cerrará todos los descriptores de archivos y liberará la memoria asignada en el espacio del usuario, pero su PCB aún está reservada y el núcleo guarda cierta información en él; si finaliza normalmente, guarda el estado de salida, y si termina anormalmente Almacena qué señal causó que el proceso terminara. El proceso principal de este proceso puede llamar a esperar o esperarpid para obtener esta información y luego borrar completamente el proceso.

El proceso padre llama a la función de espera para reciclar la información de terminación del proceso hijo. Esta función tiene tres funciones:
1. Bloquear y esperar a que salga el subproceso
2. Reciclar los recursos residuales del subproceso
3. Obtener el estado final del subproceso (motivo de salida)

pid_t wait(int *status); Éxito: ID del proceso hijo limpiado Fallo: -1 (sin proceso hijo)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main(void){
    pid_t pid,wpid;
    pid = fork();

    if(pid == 0){
        printf("i am child, my parent = %d,going to sleep 10s\n",getppid());
        sleep(10);
        printf("-------child die-------");
    }else if(pid > 0){
        wpid = wait(NULL);
        if(wpid == -1){
            perror("wait error");
            exit(1);
        }
        while(1){
            printf("i am parent, pid= %d, myson = %d\n",getppid(),pid);
            sleep(1);
        }
        
    }else{
        perror("fork");
        return 1;
    }
    return 0;
}

Después de compilar y ejecutar, verifique a través de ps aux, no hay ningún proceso de archivo [], lo que indica que el proceso zombie se ha reciclado mediante espera

función de espera

Una llamada a la función de espera solo puede reciclar un proceso hijo. Si hay 5 procesos hijos, se debe utilizar la función waitpid.

La función es la misma que esperar, pero puede especificar el proceso pid a limpiar y no puede bloquear
pid_t waitpid(pid_t pid, int* status, en opciones); Éxito: devolver el ID del proceso hijo limpio Error: -1 ( sin proceso hijo)
parámetro pid:
        >0 Reciclar el proceso hijo con el ID especificado
        -1 Reciclar cualquier proceso hijo (equivalente a esperar)
        0 Reciclar todos los procesos hijo en el mismo grupo que la llamada actual a waitpid
        <-1 Reciclar cualquier proceso hijo en el grupo de procesos especificado

tubería

Método IPC:
        en el entorno Linux, los espacios de direcciones de los procesos son independientes entre sí y cada proceso tiene un espacio de direcciones de usuario diferente. Las variables globales de cualquier proceso no se pueden ver en otro proceso, por lo que los procesos no pueden acceder entre sí. Para intercambiar datos, se debe crear un búfer en el kernel a través del kernel. El proceso 1 copia los datos del espacio del usuario al búfer del kernel, y El proceso 2 lee los datos del búfer del kernel. Este mecanismo proporcionado por el kernel se llama comunicación entre procesos.

        La finalización de la transferencia de datos entre procesos requiere métodos especiales proporcionados por el sistema operativo, como: archivos, canalizaciones, señales, contenido compartido, colas de mensajes, sockets, canalizaciones con nombre, etc. Con el vigoroso desarrollo de las computadoras, algunos métodos han sido eliminados o abandonados por sus propios defectos. Los métodos de comunicación entre procesos comúnmente utilizados en la actualidad son:
1. Tubería (más fácil de usar)
2. Señal (gastos generales mínimos)
3. Área de mapeo compartida (sin parentesco consanguíneo)
4. Enchufe local (más estable)

El concepto de canalización:
        la canalización es el mecanismo de IPC más básico, que actúa entre procesos relacionados con la sangre para completar la transmisión de datos. Se puede crear una tubería llamando a la función del sistema de tuberías. Tiene las siguientes características:
1. Su esencia es un pseudoarchivo (en realidad, un búfer del núcleo)
2. Se hace referencia a él mediante dos descriptores de archivo, uno indica el final de lectura y el otro indica el final de escritura
3. Estipula el flujo de datos en la tubería desde el extremo de escritura de la tubería, desde el lado de lectura.

El principio de la canalización: la canalización en realidad utiliza el mecanismo de cola en anillo para el kernel y se implementa con la ayuda del búfer del kernel (4k).

Limitaciones de la tubería:
1. Los datos no se pueden escribir por sí solos
2. Una vez que se leen los datos, no existen en la tubería y no se pueden leer repetidamente 3.
Porque la tubería adopta el modo de comunicación semidúplex. Por lo tanto, los datos solo pueden fluir en una dirección
4. Las tuberías solo se pueden usar entre procesos que tienen un ancestro común

Cree una tubería sin nombre:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 int main(void){
  6     int fds[2];
  7     int ret = -1;
  8 
  9     //create a pipe
 10     ret = pipe(fds);
 11     if(-1 == ret){
 12         perror("pipe");
 13         return 1;
 14     }
 15 
 16     //fds[0] is for reading fds[1] is for writing
 17     printf("fds[0]: %d  fds[1]: %d\n",fds[0],fds[1]);
 18 
 19     close(fds[0]);
 20     close(fds[1]);
 21 
 22     return 0;
 23 }

Resultado de salida:

fds[0]: 3 fds[1]: 4

La salida aquí es 3 y 4 porque 0 1 2 está ocupado por la entrada estándar, la salida estándar y el error estándar.
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SIZE 64

//父进程使用无名管道进行通信
//父进程写管道 子进程读管道
int main(void){
    pid_t pid = -1;
    int fds[2];
    char buf[SIZE];
    int ret = -1;
    
    //创建无名管道
    ret = pipe(fds);
    if(-1 == ret){
        perror("pipe");
        return 1;
    }
    
    //创建进程
    pid = fork();
    if(-1 == pid){
        perror("fork");
        return 1;
    }

    //子进程
    if(0 == pid){
        //关闭写端
        close(fds[1]);

        memset(buf, 0, SIZE);
        //读管道的内容
        ret = read(fds[0], buf, SIZE);
        if(ret < 0){
            perror("read");
            exit(-1);
        }

        printf("child process buf: %s\n",buf);

        //关闭读端
        close(fds[0]);
        exit(0);
    }
    
    //父进程
    //关闭读端
    close(fds[0]);

    ret = write(fds[1],"ABCDEGHIJK",10);
    if(ret < 0){
        perror("write");
        return 1;
    }

    printf("parent process write len: %d\n",ret);

    //关闭写端
    close(fds[1]);

    return 0;
}

Resultado de salida: 

proceso padre escribir len: 10
proceso hijo buf: ABCDEGHIJK

Características de lectura y escritura de tuberías.

 

 Resumir:

Tubería de lectura:
hay datos en la tubería, la lectura devuelve el número de bytes realmente leídos.
No hay datos en la tubería:
        todos los extremos de escritura de la tubería están cerrados y la lectura devuelve 0 (equivalente a leer hasta el final del archivo). Si
        no se cierran todos los extremos de escritura, la lectura no se bloquea Espere (es posible que se entreguen datos en el futuro cercano y la CPU se liberará en este momento)

Tubería de escritura:
todos los extremos de lectura de la tubería están cerrados y el proceso finaliza de manera anormal (el proceso también puede finalizar capturando la señal SIGPIPE). No todos los extremos de lectura de la tubería están cerrados:
la
        tubería está llena, la escritura La tubería del bloque
        no está llena, la escritura escribe los datos y devuelve los bytes reales escritos.

 El tamaño de la tubería en ulimit -a    es el tamaño del búfer de la tubería

famoso oleoducto

Las tuberías, dado que no tienen nombre, solo se pueden utilizar para la comunicación entre procesos con afinidad. Para superar esta deficiencia, se propone una tubería con nombre (FIFO), también llamada tubería con nombre, archivo FIFO.

FIFO y pipe tienen algunas características en común, la diferencia es:
1. FIFO existe como un archivo especial en el sistema de archivos, pero el contenido de FIFO se almacena en la memoria
2. Cuando el proceso que utiliza FIFO sale, los archivos FIFO seguirán siendo guardado en el sistema de archivos para uso futuro
3. FIFO tiene un nombre y los procesos no relacionados pueden comunicarse abriendo canalizaciones con nombre

mkfifo fifo  crea una tubería conocida mediante el comando y descubre que el tamaño es 0 porque su contenido se coloca en la memoria

Nota:
1. Un proceso que abre una tubería para solo lectura se bloqueará hasta que otro proceso abra la tubería para solo escritura.
2. Un proceso que abre una tubería para solo escritura se bloqueará hasta que otro proceso abra la tubería para solo lectura. abre la tubería

Supongo que te gusta

Origin blog.csdn.net/weixin_43754049/article/details/126083999
Recomendado
Clasificación