Proceso de procesamiento de señales del kernel de Linux

Tabla de contenido

1. ¿Qué es una señal?

2. La operación de respuesta del proceso a la señal

3. Registre la función de procesamiento de señales

4. Mecanismo de procesamiento de señales en Linux

4.1 ¿Cómo descubre y recibe señales el proceso?

4.2. Detección de señales y tiempo de respuesta

4.3, ingrese a la función de procesamiento de señal

4.4 ¿Qué debo hacer después de ejecutar la función de procesamiento de señales?


1. ¿Qué es una señal?

La señal es esencialmente una simulación del mecanismo de interrupción a nivel de software, y tiene principalmente las siguientes fuentes:

Error de programa: división por cero, acceso ilegal a la memoria ...

Señal externa: el terminal Ctrl-C genera la señal SGINT y el temporizador expira para generar SIGALRM ...

Solicitud explícita: la función kill permite que un proceso envíe cualquier señal a otros procesos o grupos de procesos.

Bajo Linux, puede ver todas las señales del sistema a través del siguiente comando: kill -l

Puede enviar una señal a un proceso explícitamente a través de un comando como el siguiente: kill -2 pid / tid

El comando anterior envía la señal número 2 al proceso cuyo ID de proceso es pid. No hay señal numerada 0. ¿Pero podemos enviar una señal numerada 0 a un proceso o subproceso y juzgar si el proceso existe o no por el valor de retorno? Esto es  más común en la programación de Linux C, la función kill.

Actualmente, Linux admite 64 tipos de señales. La señal se divide en dos tipos: señal no en tiempo real (señal no confiable) y señal en tiempo real (señal confiable) El valor de la señal correspondiente a Linux es 1-31 y 34-64. La señal es asíncrona, un proceso no necesita esperar a que llegue la señal a través de ninguna operación, de hecho, el proceso no sabe cuándo llega la señal. Este artículo se centra en el mecanismo de procesamiento de señales de Linux. Para obtener más información sobre las señales, consulte aquí.

En general, después de que un proceso recibe una señal, se comportará de la siguiente manera:

2. La operación de respuesta del proceso a la señal

Ignorar señales: la mayoría de las señales se pueden ignorar, excepto las señales SIGSTOP y SIGKILL (este es un medio para que el superusuario elimine o detenga cualquier proceso).

Capturar señal: registra la función de procesamiento de la señal, que procesa la señal específica generada.

Deje que la acción predeterminada de la señal funcione: La acción predeterminada definida por el kernel de Unix, hay 5 situaciones:

a) Aborto: Termina el proceso y genera un archivo core.

b) Parada de terminación: finaliza el proceso sin generar el archivo principal.

c) Ignorar: ignora la señal.

d) Suspender: Suspender el proceso.

e) Continuar continuar: Si el proceso está suspendido, reanude el proceso, de lo contrario ignore esta señal.

3. Registre la función de procesamiento de señales

Si desea que el proceso capture una determinada señal y luego realice el procesamiento correspondiente, debe registrar la función de procesamiento de señal. De manera similar a las interrupciones, el kernel también prepara una tabla de vectores de señales para cada proceso. La tabla de vectores de señales registra el mecanismo de procesamiento correspondiente a cada señal. Por defecto, se llama al mecanismo de procesamiento predeterminado. Cuando el proceso registra un manejador de señales para una señal, cuando ocurre la señal, el kernel llamará a la función registrada.

La función de procesamiento de la señal de registro se realiza a través de la llamada al sistema signal (), sigaction (). Entre ellos, signal () se implementa sobre la base de llamadas al sistema de señales confiables y es una función de biblioteca. Tiene solo dos parámetros y no admite información de transmisión de señales. Se usa principalmente para la instalación de las primeras 32 señales en tiempo no real; y sigaction () es una función más nueva (implementada por dos llamadas al sistema: sys_signal y sys_rt_sigaction), con tres parámetros , Información de transmisión de señales de soporte, utilizada principalmente junto con la llamada al sistema sigqueue (). Por supuesto, sigaction () también admite la instalación de señales en tiempo no real. sigaction () es superior a signal () principalmente en la señal de soporte con parámetros. Si desea obtener más información sobre esto, también puede consultar aquí.

4. Mecanismo de procesamiento de señales en Linux

4.1 ¿Cómo descubre y recibe señales el proceso?

Sabemos que las señales son asincrónicas, un proceso no puede esperar la llegada de la señal ni sabe que llegará la señal, entonces, ¿cómo descubre y recibe el proceso la señal? De hecho, la recepción de la señal no la realiza el proceso del usuario, sino el agente del kernel . Cuando un proceso P2 envía una señal a otro proceso P1, el kernel recibe la señal y la coloca en la cola de señales de P1. Cuando P1 vuelve al estado de kernel, comprobará la cola de señales y llamará a la función de procesamiento de señales correspondiente de acuerdo con la señal correspondiente. Como se muestra abajo:


Entre ellos, acción c: encontrar y capturar la señal

4.2. Detección de señales y tiempo de respuesta

Solo dijimos que cuando P1 ingrese al kernel nuevamente, verificará la cola de señales. Entonces, ¿cuándo volverá a caer P1 en el kernel? ¿Cuándo es el momento de verificar la cola de señales después de ingresar al kernel?

Después de que el proceso actual ingresa al espacio del sistema debido a una llamada, interrupción o excepción del sistema, la víspera de regresar del espacio del sistema al espacio del usuario .

Cuando el proceso actual acaba de despertarse después de irse a dormir en el kernel (debe estar en una llamada al sistema), o debido a la existencia de una señal no ignorable, regresa al espacio de usuario antes.

Cabe señalar aquí que cuando el proceso se ha bloqueado en el modo kernel, la señal lanzada por el kernel se puede recibir en este momento, la llamada de bloqueo finaliza en este momento y se pasa al modo de usuario para procesar la señal. Una vez completado el procesamiento, no se realiza ninguna llamada de bloqueo. El experimento es el siguiente:

//   注册信号11,线程收到该信号打印用户态堆栈信息。
   ....
   socklen_t len=sizeof(client);
//进程阻塞在此处,在此处时发送信号量,给其线程,线程结束系统调用处理信号,处理完成后不再阻塞在系统调用中。
   ssize_t lenx=recvfrom(sockfd,buf,sizeof(buf)-1,0,(sockaddr*)&client,&len);
   printf("recvfrom over\n");
   ....

Los resultados son los siguientes:

[root@localhost stack_dump]# cat /proc/20254/task/20255/stack
[<ffffffff81432637>] __skb_recv_datagram+0x237/0x290
[<ffffffff8149861b>] udp_recvmsg+0x8b/0x310
[<ffffffff8149f85a>] inet_recvmsg+0x5a/0x90
[<ffffffff81426693>] sock_recvmsg+0x133/0x160
[<ffffffff81426d5e>] sys_recvfrom+0xee/0x180
[<ffffffff8100b0d2>] system_call_fastpath+0x16/0x1b
[<ffffffffffffffff>] 0xffffffffffffffff



[root@localhost stack_dump]# kill -11 20255
[root@localhost stack_dump]# Stack trace (non-dedicated):
./funstack() [0x400c16]
/lib64/libpthread.so.0() [0x31cf40f7e0]
/lib64/libpthread.so.0(recvfrom+0x33) [0x31cf40eca3]
./funstack(func1+0xd8) [0x400de5]
./funstack(test_func+0xe) [0x400e11]
./funstack(task_entry+0x16) [0x400e29]
/lib64/libpthread.so.0() [0x31cf407aa1]
/lib64/libc.so.6(clone+0x6d) [0x31ce8e893d]
End of stack trace
recvfrom over

A través de experimentos, se puede ver que el kernel mencionado en el artículo no detecta la señal en vísperas del retorno de la llamada al sistema, pero verifica si tiene una señal para ser procesada cuando el hilo está programado.

4.3, ingrese a la función de procesamiento de señal

Después de encontrar la señal, conozca la función de procesamiento de acuerdo con el vector de señal, luego, ¿cómo ingresar al programa de procesamiento de señal y cómo regresar?

Sabemos que la función de procesamiento de señal proporcionada por el proceso de usuario está en el modo de usuario, y cuando encontramos la señal, estamos en el modo de kernel cuando encontramos la función de procesamiento de señal, por lo que debemos ejecutar desde el modo de kernel al modo de usuario para ejecutar el programa de procesamiento de señal, y ejecutar Después de terminar, regrese al modo kernel. Este proceso se muestra en la siguiente figura:

Como puede ver en la figura, todo el proceso de procesamiento de señales es así: el proceso ingresa al kernel debido a llamadas o interrupciones del sistema, completa las tareas correspondientes y regresa al espacio de usuario en la víspera, verifique la cola de señales , si hay una señal, busque la función de procesamiento de señales de acuerdo con la tabla de vectores de señales , Después de configurar el "marco" (marco de pila) , salte al modo de usuario para ejecutar la función de procesamiento de señales. Después de ejecutar la función de procesamiento de señales, regrese al modo kernel, configure "frame" y luego regrese al modo de usuario para continuar ejecutando el programa.

En el pasaje anterior, mencioné " marco ". ¿Qué es un marco? Entonces, ¿por qué establecer marco? ¿Por qué volver al estado del núcleo después de ejecutar la función de procesamiento de señales?

¿Qué es Frame?

Al llamar a una subrutina, la pila debe estirarse hacia abajo (en el sentido lógico hacia arriba). Esto se debe a que la dirección de retorno de la subrutina debe guardarse en la pila y también a que la subrutina a menudo tiene variables locales, que también ocupan la pila. espacio. Además, los parámetros al llamar a la subrutina también están en la pila. Cuanto más profundo sea el anidamiento de las llamadas de subrutina, más niveles de estiramiento de la pila. Cada uno de estos niveles en la pila se denomina "marco" o marco.

En términos generales, cuando la subrutina y el programa que la llama se encuentran en el mismo espacio, el estiramiento de la pila, es decir, el establecimiento del marco en la pila, es principalmente el siguiente:

  • La instrucción de llamada empuja la dirección de retorno a la pila (automáticamente)
  • Utilice el comando push para enviar los parámetros de llamada
  • Ajuste el puntero de la pila para asignar variables locales

¿Por qué y cómo configurar el marco?

Sabemos que cuando el proceso entra en modo kernel, guardará la escena de interrupción en la pila. Debido a que el modo de usuario y el modo de kernel son dos niveles de ejecución, se utilizan dos pilas diferentes. Cuando el proceso del usuario simplemente ingresa al kernel a través de una llamada al sistema, la CPU automáticamente insertará el contenido que se muestra en la siguiente figura en la pila del kernel del proceso: (La figura es de "Notas completas del kernel de Linux")

Después de procesar la llamada al sistema, es necesario llamar a la función do_signal () para configurar el marco y así sucesivamente. En este momento, el estado de la pila del kernel debería ser similar a la mitad izquierda de la figura siguiente (las llamadas al sistema empujan cierta información a la pila):

Después de encontrar la función de procesamiento de señales, la función do_signal primero guarda el eip del punto de ejecución de retorno en la pila del kernel como old_eip, luego reemplaza eip con la dirección de la función de procesamiento de señales y luego reemplaza el "ESP original" (es decir, el modo de usuario) guardado en el kernel. Dirección de la pila) menos un cierto valor, el propósito es expandir la pila del modo de usuario y luego guardar el contenido de la pila del kernel en la pila del usuario, este proceso es para establecer el marco. Vale la pena señalar los siguientes dos puntos:

La razón por la que el valor de EIP se establece en la dirección de la función de procesamiento de señales es porque una vez que el proceso vuelve al modo de usuario, el programa de procesamiento de señales debe ejecutarse, por lo que EIP debe apuntar al programa de procesamiento de señales en lugar de la dirección que se debe ejecutar.

La razón para copiar el marco de la pila del kernel a la pila de usuario es porque el proceso regresa del modo de kernel al modo de usuario para limpiar la pila de kernel usada en esta llamada (similar a una llamada de función), y la pila de kernel es demasiado pequeña para estar simplemente en la pila Guarde otro cuadro (imagine el procesamiento de señales anidado), y necesitamos EAX (valor de retorno de la llamada al sistema) e información EIP para que el programa pueda continuar después de que se ejecute la función de procesamiento de señales, así que cópielos en la pila del modo de usuario para guardar .

Después de aclarar lo anterior, las siguientes cosas son mucho más suaves. En este momento, el proceso vuelve al espacio del usuario y la función de procesamiento de señales se ejecuta de acuerdo con el valor EIP en la pila del núcleo. Luego, después de que se ejecuta el programa de procesamiento de señales, ¿cómo regresar al programa para continuar con la ejecución?

4.4 ¿Qué debo hacer después de ejecutar la función de procesamiento de señales?

Después de que se ejecute el programa de procesamiento de señales, el proceso llamará activamente a la llamada al sistema sigreturn () para regresar al kernel nuevamente (puede usar strace para verificar) para verificar si hay otras señales para ser procesadas. De lo contrario, el kernel hará un trabajo posterior. Restaure el marco previamente guardado en la pila del kernel, restaure el valor de eip a old_eip y luego regrese al espacio de usuario, y el programa puede continuar ejecutándose. Hasta ahora, el núcleo ha completado el procesamiento de la señal una vez (o varias veces).

Supongo que te gusta

Origin blog.csdn.net/wangquan1992/article/details/108511628
Recomendado
Clasificación