Tengo un controlador de señal registrado en mi programa. Al recibir una señal no deseada (SIGABRT), llamo a ‘salir (-1)’ en el controlador de señales para salir del proceso. Pero como se notó en algunas ocasiones, llama a exit () pero no puede terminar el proceso.
El problema se generó aleatoriamente y sospecho fuertemente de la ejecución de exit().
¿Puede haber alguna razón o caso en el que exit() no pueda terminar el proceso?
Gracias.
Estás llamando exit()
del controlador de señal?
En man 7 signal
sección Funciones seguras de señal asíncrona puede ver todas las funciones que están garantizadas para funcionar cuando se las llama desde un controlador de señales:
Una función de manejo de señales debe tener mucho cuidado, ya que el procesamiento en otro lugar puede interrumpirse en algún punto arbitrario de la ejecución del programa. POSIX tiene el concepto de “función segura”. Si una señal interrumpe la ejecución de una función no segura y el controlador llama a una función no segura, entonces el comportamiento del programa no está definido.
POSIX.1-2004 (también conocido como POSIX.1-2001 Technical Corrigendum 2) requiere una implementación para garantizar que las siguientes funciones se puedan llamar de forma segura dentro de un controlador de señales:
Ahí puedes ver funciones _Exit()
, _exit()
y abort()
pero notablemente no exit()
. Por lo tanto, no debe llamarlo desde un controlador de señal.
Lo desagradable es que incluso si llama a una función insegura desde un controlador de señal (printf()
alguno?) funcionará la mayor parte del tiempo… pero no siempre.
-
Llamar a una función no segura de señal asíncrona desde un controlador de señal es perfectamente legal a menos que la señal interrumpa otra función no segura de señal asíncrona. Esto explica la razón por la que funciona “la mayor parte del tiempo”, pero también significa que puede usar funciones asíncronas de señales inseguras en controladores de señales, incluso en código correcto y robusto, si se asegura de que el controlador de señales no pueda interrumpir una señal insegura. función (por ejemplo, dejando la señal enmascarada la mayor parte del tiempo y solo desbloqueándola mientras se realizan llamadas de biblioteca seguras de señal asincrónica o aritmética pura como descriptor de archivo IO y
select
).– R.. GitHub DEJA DE AYUDAR A ICE
12 de enero de 2012 a las 13:41
-
@R.. Tienes razón, por supuesto. Pero en la práctica es difícil asegurar eso, salvo en casos muy particulares. Personalmente, encuentro mucho más conveniente simplemente evitar todas las funciones inseguras.
– rodrigo
12 de enero de 2012 a las 13:47
-
Ver también: pubs.opengroup.org/onlinepubs/009695399/functions/…
– William Pursell
12 de enero de 2012 a las 17:22
-
@rodrigo: en realidad, es bastante común si solo está usando señales para hacer que su programa sea interrumpible por el usuario mientras espera IO, siempre que haga el esfuerzo de bloquear y desbloquear
SIGINT
o lo que sea en los momentos adecuados. También es común en programas de subprocesos múltiples si deja la señal bloqueada en todos menos en un subproceso de procesamiento de señal dedicado.– R.. GitHub DEJA DE AYUDAR A ICE
12 de enero de 2012 a las 23:41
-
@R.. Un poco tarde aquí, pero nadie mencionó el estándar C en sí. “Llamar a una función no segura de señal asíncrona desde un controlador de señal es perfectamente legal …” Yo no lo caracterizaría como “perfectamente legal”. 7.1.4 Uso de funciones de bibliotecapárrafo 4: “No se garantiza que las funciones en la biblioteca estándar sean reentrantes y pueden modificar objetos con duración de almacenamiento estático o de subprocesos”. Y nota al pie 188: “Por lo tanto, un controlador de señales no puede, en general, llamar a funciones de biblioteca estándar”.
–Andrew Henle
11 de enero de 2019 a las 18:39
jeff fomentar
Sí, hay algunas circunstancias, tales como:
La función exit() primero llamará a todas las funciones registradas por atexit(), en el orden inverso de su registro, excepto que una función se llame después de cualquier función previamente registrada que ya se haya llamado en el momento en que se registró. Cada función se llama tantas veces como se registró. Si, durante la llamada a cualquiera de estas funciones, se realiza una llamada a la función longjmp() que terminaría la llamada a la función registrada, el comportamiento no está definido.
Si una función registrada por una llamada a atexit() no regresa, las funciones registradas restantes no se llamarán y el resto del procesamiento de exit() no se completará. Si se llama a exit() más de una vez, el comportamiento no está definido.
Consulte la página POSIX en salida.
Para obtener más información, adjunte un depurador cuando llegue a la situación y eche un vistazo a la pila de llamadas.
jarzec
Tuve un problema análogo al descrito por Madar. Necesitaba realizar una acción para cada señal y salir correctamente. Me pregunté a través de un par de respuestas a problemas similares y se me ocurrió la siguiente explicación/solución.
Explicación:
Un problema es que exit()
no debe usarse en controladores de señales porque no es una de las funciones seguras de señales asíncronas (ver man signal-safety
). Esto quiere decir que puede, pero no está garantizado, que funcione en controladores de señales. Como resultado, tendría que llamar _exit()
/_Exit()
(que son async-sign-safe). Sin embargo, estos terminan el proceso instantáneamente, sin llamar al atexit
devoluciones de llamada y destructores estáticos. Tengo entendido que para algunas señales se puede hacer un poco más de limpieza que la que proporcionan esas funciones.
Solución: La solución que se me ocurrió es registrar su controlador de señales para todas las señales y realizar los pasos adicionales. Luego puede restablecer el controlador predeterminado y llamar raise(signal_number)
que es async-signal-safe, para volver a enviar la señal y así ejecutar el controlador predeterminado.
Aquí hay un ejemplo de trabajo que ejecuta el controlador predeterminado solo en SIGINT
. Creo que esto es demasiado simple para experimentar el “fallo” exit()
si lo usaste en el controlador. Probé un código similar con una pila alternativa para manejar también SIGSEGV
.
Nota Si desea que esto funcione correctamente en un contexto de subprocesos múltiples (por ejemplo, subprocesos múltiples que causan SIGSEGV
al mismo tiempo) debe tener cuidado con la sincronización. Los subprocesos comparten el mismo controlador pero tienen un enmascaramiento de señal separado.
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <unistd.h>
// The actual signal handler
extern "C" void handleSignal(int sig, siginfo_t *siginfo, void *) {
// Cannot use printf() - not async-signal-safe
// For simplicity I use a single call to write here
// though it is not guaranteed to write the whole message
// You need to wrap it in a loop
// Die only on Ctrl+C
if(sig == SIGINT) {
const char *msg = "Die\n";
write(STDERR_FILENO, msg, ::strlen(msg));
// Reset to use the default handler to do proper clean-up
// If you want to call the default handler for every singal
// You can avoid the call below by adding SA_RESETHAND to sa_flags
signal(sig, SIG_DFL);
raise(sig);
return;
}
// Here we want to handle the signal ourselves
// We have all the info available
const char *msg = "Continue\n";
write(STDERR_FILENO, msg, ::strlen(msg));
}
int main() {
// You might want to setup your own alternative stack
// eg. to handle SIGSEGV correctly
// sigaltstack() + SA_ONSTACK flag in sa_flag
// Prepare a signal action for handling any signal
struct sigaction signal_action;
signal_action.sa_sigaction = ::handleSignal;
signal_action.sa_flags = SA_SIGINFO;
::sigfillset(&signal_action.sa_mask);
// A vector of all signals that lead to process termination by default
// (see man -s 7 signal)
const int TERM_SIGNALS[] = {
SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV,
SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGBUS, SIGPOLL,
SIGPROF, SIGSYS, SIGTRAP, SIGVTALRM, SIGXCPU, SIGXFSZ};
// Register the signal event handler for every terminating signal
for (auto sig : TERM_SIGNALS) {
::sigaction(sig, &signal_action, 0);
}
while(true);
return 0;
}
Sin duda, le aconsejo que busque otra causa del error que una función como
exit
. Casi siempre, cuando cree que hay un error en el compilador o en la biblioteca estándar, etc., es su propio error el que está causando el error.– Shahbaz
12 de enero de 2012 a las 10:24
@Shahbaz: Mandar no pregunta sobre un error en la implementación ni sugiere que exista. La pregunta es si
exit
se especifica para terminar siempre el programa, y la respuesta es no (y especialmente no si se llama desde un controlador de señal).–Steve Jessop
12 de enero de 2012 a las 11:10
¿Por qué no simplemente configurar el controlador para que SIGABRT salga ()? Cualquier otra cosa que esté haciendo en el controlador de señal, hágalo en una llamada atexit().
– William Pursell
12 de enero de 2012 a las 17:24
@William Pursell: si una señal va a terminar un proceso, las funciones registradas por atexit() no se llamarán a menos que alguien sea lo suficientemente tonto como para llamar a exit() desde el controlador de señales. El reingreso es una píldora.
– Josué
14 de junio de 2013 a las 16:21