Control de procesos de Linux (2) --- proceso en espera

Tabla de contenido

¿Qué es el proceso en espera?

¿Por qué espera el proceso?

esperar()

esperarpid()

Uso de estado★

opciones★

Pregunta: Dado que el proceso es independiente, el código de salida del proceso no es también los datos del proceso secundario, ¿por qué debería obtenerlo el proceso principal? ¿Qué hace exactamente wait/waitpid?

¿Qué es el proceso en espera?

El proceso en espera es un mecanismo por el cual un proceso principal espera a que finalice su proceso secundario . En un sistema multiproceso, un proceso principal crea un proceso secundario y es posible que deba esperar a que el proceso secundario termine de ejecutarse para obtener el estado de salida del proceso secundario o realizar otras operaciones.

Cabe señalar que: el proceso principal que espera que el proceso secundario finalice es una operación de bloqueo , es decir, el proceso principal suspenderá su propia ejecución hasta que finalice el proceso secundario.

¿Por qué espera el proceso?

1. Proceso zombie de reciclaje

2. Obtenga el estado de salida del proceso secundario.

Aquí hay una explicación detallada de las dos oraciones anteriores:

1. Como se mencionó anteriormente, si el proceso secundario finaliza, si se ignora el proceso principal, puede causar el problema de un 'proceso zombi', que a su vez provocará una pérdida de memoria.
Además, una vez que un proceso se convierte en zombi, es invulnerable y kill -9 es impotente, porque nadie puede matar un proceso muerto.
2. Finalmente, necesitamos saber cómo se completan las tareas asignadas por el proceso padre al proceso hijo. Por ejemplo, si el proceso hijo termina de ejecutarse, si el resultado es correcto o no, o si sale normalmente.
Conclusión: El proceso padre reclama los recursos del proceso hijo y obtiene la información de salida del proceso hijo por medio de procesos en espera

A continuación vamos a crear un estado zombi.

Introduce el siguiente código:

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 int main()
  6 {
  7   pid_t id = fork();
  8   if(id < 0)
  9   {
 10     perror("fork");
 11     exit(1);//标识进程运行完毕,结果不正确
 12   }
 13   else if(id == 0)
 14   {
 15     //子进程
 16     int cnt = 5;
 17     while(cnt)
 18     {
 19       printf("cnt: %d, 我是子进程,pid: %d,ppid: %d\n",cnt,getpid(),getppid());
 20       sleep(1);
 21       cnt--;
 22     }
 23     exit(0);
 24 
 25   }
 26   else
 27   {
 28     while(1)
 29     {
 30       printf("我是父进程,pid: %d,ppid: %d\n",getpid(),getppid());
 31       sleep(1);
 32     }
 33   }
 34 
 35   return 0;
 36 }     

De esta manera, el proceso secundario realiza un bucle 5 veces y sale, mientras que el proceso principal sigue realizando un bucle y no puede salir, lo que da como resultado una situación en la que el proceso secundario no puede reciclarse.

Luego salga de vim, haga compilaciones y ejecuciones, y al mismo tiempo crea una ventana, detecta el estado del proceso, e ingresa en la nueva ventana:

while :; do ps axj | head -1 && ps ajx | grep myproc | grep -v grep;sleep 1; echo "-------------------------------"; done

 

Después de 5 segundos, el proceso secundario finaliza y solo se ejecuta el proceso principal. 

 Observe el estado del proceso en este momento y sepa que después de 5 segundos, el estado del proceso secundario pasa a ser Z+, estado zombi.

Entonces, ¿cómo reciclamos el proceso zombie en este momento ? Usamos la interfaz de espera.

esperar()

Verifiquemos su uso en man 2 wait .

Lo que hace es esperar a que un proceso cambie de estado. 

Este estado del parámetro se usa para obtener el resultado del subproceso, de esto hablará waitpid más adelante, y es lo mismo, por ahora solo escribe NULL.

Luego mira el valor de retorno:

 Chen Gong, devuelva el pid del proceso secundario; de lo contrario, devuelva -1.

En este punto, lo usamos y hacemos los siguientes cambios en el código en el módulo de proceso principal:

Primero emita una vez y luego espere el proceso secundario. Si la espera es exitosa, generará "esperando que el proceso secundario tenga éxito".

Salir de vim, hacer para compilar Al mismo tiempo, la ventana derecha todavía está monitoreada.

De esta forma, el proceso padre esperará a que el proceso hijo deje de ejecutarse hasta que muera, y cuando cambie el estado, se reciclará.

 Descubrimos que en este momento, el proceso secundario no generaba un estado zombie después de 5 segundos, sino que desaparecía directamente, dejando solo el proceso principal para continuar ejecutándose.

En este momento, el proceso principal recicla el proceso secundario y espera hasta que cambie el estado.

Entonces, ¿cuál es la diferencia entre waitpid y esta espera?

esperarpid()

Del mismo modo, mantenemos 2 waitpid para ver el uso.

El primer parámetro, pid, se usa para esperar el proceso hijo de un pid específico.

Hay dos opciones:

        1.Pid=-1, espere cualquier proceso secundario. Equivale a esperar.
        2.Pid> 0. Esperando un proceso hijo cuyo ID de proceso sea igual a pid.

Las opciones del tercer parámetro tienen un valor predeterminado de 0 , lo que significa bloqueo y espera.

El segundo estado del parámetro es un tipo de puntero, que es un parámetro de salida.

Por ejemplo, definimos un estado int = 0 fuera de la función,

Luego pase este estado al segundo parámetro en waitpid.

Luego, cuando finaliza la función, el resultado de salida del proceso secundario se completará automáticamente en el estado, que es el parámetro de salida.

Entonces, waitpid(-1,NULL,0) es equivalente a wait(NULL).

Cambiamos la espera en el proceso padre a waitpid:

 hacer compilaciones y ejecuciones

 El resultado es el mismo.

Uso de estado★

Entonces usemos el estado.

Agregamos manualmente un código de salida 69 en el módulo de proceso secundario, y luego definimos la variable de estado en el módulo de proceso principal primero, luego lo pasamos a waitpid y finalmente lo generamos.

Salir, compilar y ejecutar.

 Descubrimos que el código de salida no es 69, sino un número tan grande, ¿cuál es la situación?

De hecho, el estado no se usa como un número entero, sino que se divide en 32 bits según la forma de los bits.

Solo aprendemos los 16 bits inferiores Los 16 bits inferiores básicamente satisfacen nuestras necesidades.

Entre ellos, esta vez los 8 bits inferiores identifican el código de salida del subproceso, es equivalente a los bits 8-15, si queremos obtenerlo primero debemos desplazarlo 8 bits a la derecha y luego & 0xFF , 0xFF significa que solo los últimos 8 bits son 1, los bits restantes son todos 0, y cualquier &1 es el mismo, y &0 es 0, por lo que obtiene los 8 bits.

 

 En este punto obtenemos el resultado que queremos 69.

Se siente muy problemático escribir esto cada vez, entonces, ¿hay una manera más rápida?

La biblioteca estándar de C proporciona algunas macros:

WIFEXITED(estado) : Verdadero si este es el estado devuelto por un proceso secundario normalmente terminado . (Verifique para ver si el proceso salió normalmente)

WEXITSTATUS(status) : si WIFEXITED no es cero, extraiga el código de salida del proceso secundario. (Consultar el código de salida del proceso)

Echemos un vistazo a cómo usarlo:

 correr:

 Se puede ver que el proceso secundario finaliza normalmente en este momento y obtenemos el código de salida que devolvimos

Si todavía está agregando un error de división por 0 en el proceso secundario en este momento, vuelva a generarlo.

Puede ver que el proceso secundario finalizó de manera anormal y  WIFEXITED devolvió un valor de 0.

También es relativamente simple.

señal de parada

Hablemos de la salida anormal o caída del proceso, la esencia es que el sistema operativo mata el proceso.

¿Cómo lo mató el sistema operativo? La esencia es matar el proceso enviando una señal .

Aquí están todas las señales bajo Linux.

 Entonces, los 7 bits más bajos, es decir, la señal de terminación, indican la señal recibida por el proceso.

Entre ellos, el volcado de código se explicará más adelante al explicar la señal.

También mostramos el valor de los últimos 7 bits.

En este momento, debe obtener los últimos 7 bits, luego debe agregar 0111 1111, lo que significa 0x7 F en hexadecimal.

 correr:

 La última señal recibida por el proceso hijo es 0, lo que indica que ha terminado de ejecutarse con normalidad.

El código de salida se utiliza para juzgar si el resultado es correcto o incorrecto sobre la base del funcionamiento normal .

Entonces escribimos un error de división por 0 en este momento, para que el proceso hijo finalice de manera anormal.

correr:

 

 Encontramos que la señal recibida por el proceso secundario es 8, y la señal 8 es SIGFPE, lo que significa que el error de cálculo de punto flotante.

En este momento, dado que el programa no ha terminado de ejecutarse normalmente, el código de salida no tiene sentido. Debido a que el programa no ha terminado de ejecutarse, no tiene sentido juzgar que el resultado es correcto en este momento.

Además , la anomalía del programa no es solo un problema con el código interno, sino también una causa externa (como una señal, etc.).

opciones★

En el waitpid que escribimos antes, el proceso padre ha estado bloqueando y esperando hasta el final del proceso hijo, es decir, el proceso padre ha estado esperando hasta el final del proceso hijo y no puede hacer nada. parece un poco extraño, así que podemos hacer que el proceso principal sea muy ¿Qué pasa con el bloqueo de esperas ?

El tercer parámetro de waitpid se usa aquí.

WNOHANG : si el proceso secundario especificado por pid no ha finalizado, la función waitpid() devuelve 0, no espera y vuelve inmediatamente. Si finaliza normalmente, se devuelve el ID del proceso hijo.

Esto es en realidad una macro, y el interior en realidad es #define WNOHANG 1, es decir, puede completar WNOHANG para el tercer parámetro y también puede completar 1.

El siguiente es un pseudocódigo de waitpid. Cuando se detecta que el proceso secundario no ha salido, si las opciones detectadas son 0, el proceso principal se bloqueará directamente. La esencia es bloquear dentro de la función del sistema y esperar al proceso secundario . ser despertado . _

Y si es 1, es decir WNOHANG, regresará directamente sin bloqueos y esperas.

Entonces la pregunta es, cuando options==0, cuando el proceso secundario se despierta, ¿debería continuar ejecutándose desde la parte posterior de if, o volver a ejecutar waitpid?

Pero continúe ejecutando la declaración después de if, porque el registro EIP contiene la dirección de la siguiente línea de código.

 Pasemos al código para entenderlo.

 Si la espera tiene éxito y el proceso secundario finaliza o la espera falla, finaliza. 

Si el proceso secundario no sale, la detección se realizará todo el tiempo, lo que equivale a la detección de sondeo. 

Se puede ver que durante la ejecución del proceso hijo, el proceso padre sondea y lo detecta cada segundo. Cuando finaliza el proceso hijo, waitpid también detecta que el proceso hijo sale. En este momento, res>0, la información de salida de el proceso secundario es la salida Luego, el ciclo del proceso principal también finaliza al mismo tiempo, finalizando el proceso.

Por supuesto, puede dejar que el proceso principal maneje algunas tareas específicamente, como agregar estos códigos al comienzo de la función:

 

Este módulo mientras espera:

 

 Luego ejecutamos de nuevo:

En este momento, el proceso principal puede realizar las tareas correspondientes mientras espera que se inicie el proceso secundario. 

Pregunta: Dado que el proceso es independiente, el código de salida del proceso no es también los datos del proceso secundario, ¿por qué debería obtenerlo el proceso principal? ¿Qué hace exactamente wait/waitpid?

Necesitamos comenzar con el proceso zombie: es un proceso muerto, pero al menos se debe preservar la información de PCB del proceso, que contiene la información del resultado de salida cuando cualquier proceso sale.

¡El propósito del proceso zombie para mantener su propia información de salida es permitir que otros procesos la lean!

wait y waitpid esencialmente leen la estructura task_struct del proceso hijo.

Echemos un vistazo al código fuente del kernel:

 La esencia de wait/waitpid es leer estos dos campos y luego establecer la operación de bits en la variable de salida de estado, y obtenemos el archivo .

Entonces, ¿espera y waitpid tiene permiso para leer el contenido de esta estructura de datos del kernel?

La respuesta es sí, ¡porque wait y waitpid son llamadas al sistema! Las llamadas al sistema son llamadas por el sistema operativo.

Mi proceso principal no tiene permiso para leer, pero puedo llamar a wait y waitpid para que el sistema operativo lo obtenga por mí.

En resumen: el proceso principal no tiene permiso, pero wait/waitpid tiene permiso, y el proceso principal puede llamar a wait/waitpid para obtener el estado de salida del proceso secundario.

wait/waitpid esencialmente establece el estado de salida del subproceso de lectura en el estado de la variable de salida a través de operaciones de bits.

Así que aquí, el proceso de espera ha terminado.

Supongo que te gusta

Origin blog.csdn.net/weixin_47257473/article/details/131802018
Recomendado
Clasificación