Cómo imprimir la pila después de una falla del proceso y evitar la pérdida de datos
Esta interrupción notificará al proceso en forma de señal y finalizará el proceso por defecto.
Cuando esto sucede, consideramos que el proceso se ha bloqueado.
Cuando un proceso falla, queremos saber por qué falló, qué función y qué línea de código causó el error.
Además, antes de que finalice el proceso, también queremos hacer algo, como almacenar algunos datos en la base de datos, etc.
El siguiente es un ejemplo de un fallo de proceso.
A continuación, presentaré algunas técnicas para lograr estos dos objetivos.
1. Ver la información de la pila en el archivo principal
Si podemos ver la información de la pila cuando el proceso falla, podemos encontrar rápidamente el código incorrecto.
Al usar la opción -g en gcc, la información de depuración se incluirá en el archivo ejecutable. Después de que el proceso falle, se generará un archivo principal.
Podemos usar gdb para ver el archivo principal y comprender el entorno cuando el proceso falló.
Los archivos principales son muy útiles durante la fase de depuración. Sin embargo, en un entorno formal, tiene limitaciones importantes:
1. Los archivos ejecutables que contienen información de depuración pueden ser muy grandes. También funcionará mucho más lento.
2. Los archivos principales suelen ser muy grandes. Si el proceso falla con frecuencia, los recursos del disco duro se agotarán.
Por lo tanto, un programa que se ejecuta en un entorno de producción no contendrá información de depuración.
El tamaño de sus archivos principales lo estableceremos en 0, lo que significa que no se importará ningún archivo principal.
Teniendo esto en cuenta, ¿cómo obtenemos información de la pila de procesos?
2. Obtener dinámicamente la pila del hilo
C proporciona una función de seguimiento inverso que le permite obtener dinámicamente la pila del hilo actual.
Para utilizar la función de retroceso, existen dos requisitos:
1. El programa está en formato binario ELF.
2. El programa se vincula mediante la opción -rdynamic.
-rdynamic se puede utilizar para indicarle al vinculador que agregue todos los símbolos a la tabla de símbolos dinámicos, lo cual es mucho menos informativo que la opción -g.
La siguiente es una descripción de las funciones que se utilizarán:
#include lt;execinfo.hgt;
int backtrace(void **buffer, int size);
Para recuperar la pila de llamadas del hilo actual, la información se almacena en un búfer, que es una lista de punteros.
El parámetro de tamaño se utiliza para especificar cuántos elementos void* se pueden almacenar en el búfer.
El valor de retorno de la función es el número real de punteros obtenidos, que no puede exceder el tamaño
Nota:
Además, las funciones en línea no tienen un marco de pila; eliminar el marco Los punteros también pueden hacer que el contenido de la pila no se analice correctamente;
char ** backtrace_symbols (void *const *buffer, int size)
Convierte la información obtenida de la función de seguimiento en una matriz de cadenas de caracteres.
El parámetro buffer debe ser una matriz de punteros obtenidos de la función de seguimiento inverso,
el tamaño es el número de elementos en la matriz (valor de retorno del seguimiento inverso);
El valor de retorno de la función es un puntero a una matriz de cadenas, cuyo tamaño es el mismo que el del búfer.
Cada cadena contiene información imprimible relativa al elemento correspondiente en el búfer.
Contiene el nombre de la función, la dirección de compensación de la función y la dirección de retorno real.
El valor de retorno de la función es el espacio solicitado por la función malloc, por lo que la persona que llama debe usar la función free para liberar el puntero.
Nota: Si la cadena no tiene suficiente espacio, el valor de retorno será NULL.
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
Esta función es la misma que la función backtrace_symbols,
pero no regresa a la matriz de cadenas de la persona que llama, pero escribe los resultados en un archivo con el descriptor de archivo fd, escribiendo una línea para cada función.
3. Capturar señales
Queremos imprimir la pila cuando el proceso falla, por lo que necesitamos capturar la señal correspondiente. El método es muy sencillo.
#include lt; signal.hgt;
salir(1);
}
void SetSigCatchFun(){
maplt; int, stringgt;::iterador it;
for (it=SIG_LIST.begin(); it!=SIG_ LIST.end(); it ){
señal(it-gt; primero, SaveBackTrace);
}
}
}
void Fun(){
int a = 0;
int b = 1 / a;
}
}
vacío estático * ThreadFun(void* arg){
Diversión();
return NULL
}
int main(){
SetSigList();
SetSigCatchFun();
printf("id del hilo principal = d\n", (pthread_t)pthread_self(); p>
p>
pthread_t pid;
if (pthread_create(amp.pid, NULL, ThreadFun, NULL)){
exit(1); p>
}
if (pthread_create(amp.
printf("id de hilo divertido = d\n", pid);
for( ;;){
dormir(1);
}
devuelve 0; El nombre del archivo es bt.cpp
Compilar: g bt.cpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt
El El hilo principal crea el hilo divertido
El hilo principal creó el hilo divertido y se produjo un error de división por cero en el hilo divertido. El sistema lanzó la señal SIGFPE. La señal interrumpió el hilo divertido y la función SaveBackTrace que registramos lo capturó. Se detecta la señal, se imprime la información relevante y se finaliza el proceso.
En el archivo principal de salida, podemos ver información de pila simple.
5. Seguimiento
En el ejemplo anterior, SIGFPE interrumpe el hilo divertido y se ejecuta la función SaveBackTrace.
En este momento, el hilo principal todavía se está ejecutando normalmente.
Si reemplazamos exit(1); al final de la función SaveBackTrace con for(;) sleep(1);
el hilo principal continuará ejecutándose normalmente.
Podemos hacer muchas otras cosas con esta característica.
Los procesos del servidor de juegos suelen incluir los siguientes subprocesos:
Subprocesos de red, subprocesos de bases de datos y subprocesos de procesamiento empresarial. El código que desencadena el error lógico suele estar ubicado en el subproceso de procesamiento empresarial.
Los subprocesos de bases de datos son muy poderosos debido a sus funciones estables y su lógica simple.
Por lo tanto, si ocurre un error lógico en el hilo de procesamiento de negocios, podemos capturar la señal y cuando finalice la función de señal
notificar al hilo de la base de datos para guardar los datos del juego.
La función de señal no volverá hasta que el hilo de la base de datos haya almacenado toda la información del juego en la base de datos.
De esta forma, el tiempo de inactividad del servidor no provocará una caída y se minimizarán las pérdidas.
Para implementar este mecanismo, es necesario reducir el acoplamiento entre el módulo de base de datos y el módulo de procesamiento de negocios.
Por supuesto, hay muchos detalles a considerar en aplicaciones prácticas.
Por ejemplo, si el subproceso de procesamiento empresarial está procesando datos del jugador y los datos del jugador están dañados debido a un error imprevisto, los datos del jugador no deben almacenarse en la base de datos.