Programación del sistema Linux C (06) entorno de proceso de gestión de procesos

1 Proceso de inicio y salida

1.1 Proceso

Inicio del programa-> Carga del programa y asignación de direcciones-> Salida del programa

@ 1 Inicio del programa: para archivos binarios:

  1. Si el archivo está en la dirección especificada en la variable de entorno PATH bajo la carpeta usr / bin o / bin, simplemente ingrese el nombre del archivo binario directamente
  2. Si no está bajo la variable de entorno PATH, la forma de usar la ruta del programa y el nombre del programa (por ejemplo, ./hello) también es aceptable.
  3. Si desea ingresar directamente el nombre binario para ejecutar el archivo si no está bajo la variable de entorno PATH, agregue la ruta del programa a la variable de entorno PATH y mueva el archivo binario al directorio de la variable de entorno PATH.

@ 2 Carga del programa, asignación de dirección:

El proceso simple de carga es el siguiente:

  1. Lea suficiente información del encabezado del archivo de destino para saber cuánto espacio de direcciones se necesita.
  2. Asigne el espacio de direcciones. Si el formato del código de destino tiene segmentos independientes, divida el espacio de direcciones en segmentos independientes.
  3. Lea el programa en un segmento del espacio de direcciones.
  4. Rellene el espacio del segmento bss al final del programa con 0 (si el departamento del sistema de memoria virtual lo hace automáticamente).
  5. Cree una pila (si la arquitectura lo requiere).
  6. Establezca información de funcionamiento, como parámetros de programa, variables de entorno, etc.
  7. Comience a ejecutar el programa, busque main desde la entrada _start y comience a ejecutar el programa secuencialmente.

@ 3 Salida del programa:

Hay tres formas de salir:

  1. El proceso se cierra voluntariamente. Esto se refleja en la función de salida y la función de retorno. Al salir, debe reclamar los recursos asignados por el proceso (como el espacio de direcciones, los descriptores de archivos, etc.), y el sistema operativo tratará cada recurso posteriormente.    
  2. El proceso recibió una señal para salir. Esta situación es muy común, y a menudo es el proceso de terminación del proceso padre en su proceso hijo. Esta operación es en realidad el proceso padre envía una señal de terminación al proceso hijo, el proceso hijo también saldrá voluntariamente después de recibir la señal.
  3. El proceso finalizó después de realizar una operación que causó una excepción. Los dos casos anteriores salieron como se esperaba por el programa, y ​​la operación anormal salió sin que el programa estuviera preparado. En este momento, el sistema operativo recupera sus recursos, pero es posible que no se ocupe de las consecuencias de estos recursos. La excepción es en realidad una señal especial enviada al proceso, pero no es el proceso sino el propio sistema operativo el que envía la señal.

1.2 Función de procesamiento de terminación de proceso

En el entorno Linux, se permite llamar a algunas funciones definidas por el usuario cuando finaliza el proceso, que se denominan funciones de procesamiento de terminación. Linux estipula que se pueden configurar hasta 32 funciones de procesamiento de terminación de proceso. En Linux, use la función atexit para establecer la función de procesamiento de terminación de proceso. Prototipo de la función atexit:

#include <stdlib.h>
int atexit(void (*function)(void));

Consulte el manual de referencia de la función de Linux para más detalles . Nota:

  1. La ejecución exitosa de la función devuelve 0, y la falla devuelve un valor distinto de cero. (Tenga en cuenta que si la función atexit falla, no devuelve -1)
  2. La secuencia de llamada de la función de procesamiento de terminación de proceso se invierte cuando se establece. (La última llamada finaliza primero, similar a la estructura de la pila)         
  3. De hecho, la función de terminación del proceso es una operación auxiliar realizada al final del proceso.

2 gestión de memoria de proceso de Linux

2.1 Big endian y little endian

La PC general usa una estructura little-endian, mientras que el servidor generalmente usa una estructura big-endian. Esta diferencia en el almacenamiento de datos no es causada por el sistema operativo, big-endian y little-endian se reflejan en la arquitectura de la CPU. Generalmente, al programarlo, primero determine si es big endian o little endian, y luego actívelo.

Little endian: los valores altos y bajos almacenan bits altos, las direcciones bajas almacenan bits bajos. Big endian: lo contrario de little endian.

2.2 Sección de código, sección de datos y sección de búfer

@ 1 Segmento de código: en general, las operaciones de escritura no están permitidas y el atributo es de solo lectura. Un programa no necesita cambiar el segmento de código en la mayoría de los casos, excepto en un caso, que es actualizar el programa. Para el servidor, es necesario completar la sustitución de parte del segmento de código sin detener el programa. En el pasado, el segmento de código generalmente se escribía y reemplazaba directamente, pero el riesgo también era grande. Actualmente, este problema generalmente se resuelve mediante el uso de una biblioteca compartida.

@ 2 Segmento de datos:

  1. Sección de datos de inicialización (.data): Contiene variables globales y variables estáticas que claramente tienen valores iniciales en el programa.
  2. Segmento de almacenamiento en bloque (.bss): los datos almacenados en este segmento suelen ser variables globales y variables estáticas que no tienen un valor inicial claro.

El contenido de la sección @ 3 bss no forma parte del archivo del programa, es decir, no está incluido en el archivo binario, sino que está almacenado en la memoria externa. El sistema solo marca cierta información de la sección bss en la memoria (tamaño variable de inicialización , Atributos, etc.); para encontrar el contenido en la sección bss al ejecutar el programa. Si la variable global / variable estática tiene un valor dado, y este valor es 0 / NULL, el compilador escribirá su contenido en la sección bss, no en la sección de datos.

2.3 Pila y montón

Hay 3 formas de almacenar variables automáticas:

  1. sección bss: variables locales estáticas
  2. En el registro: variable de registro
  3. Pila: variables automáticas generales

El error más común en la programación es devolver un puntero a una variable local como el valor de retorno de la función. Dado que el contenido señalado por el puntero todavía está en el marco de la pila, la función simplemente devuelve su dirección. Por lo tanto, si el marco de la pila se sobrescribe con otra función, el valor del área de memoria señalado por el puntero devuelto no será válido.

El espacio de almacenamiento dinámico es generalmente el espacio de memoria para almacenar aplicaciones de usuario, y la operación en el almacenamiento dinámico es a menudo malloc. La ubicación de la pila y el montón a menudo son relativos, pero la asignación específica depende de la estructura de almacenamiento del procesador, y la diferencia entre big endian y little endian es similar.

2.4 Almacenamiento constante

Para una constante simple, se almacena en el segmento de código porque la longitud de la variable simple es fija. Esto puede acelerar la velocidad de obtención de instrucciones, y también puede mejorar la eficiencia del programa. Pero esa es una constante compleja, como una cadena, y su longitud es indefinida. Si la cadena se almacena en el segmento de código, el segmento de código será muy grande y no es propicio para que el procesador lea el código en el procesamiento del búfer, lo que afecta en gran medida al programa Eficiencia de ejecución. Entonces, al final, se almacena un segmento separado para almacenar la cadena.

2.5 Gestión de memoria dinámica

El sistema utiliza la estructura mem_control_block para administrar todos los bloques de memoria asignados. La estructura es la siguiente:

    struct mem_control_block{
      int is_available;     //该块是否可用
      int size;               //块的大小
    }

A través de esta estructura, la función malloc se puede implementar de manera simple: todo el proceso es el siguiente:

La función malloc agrega primero el número de bytes que debe asignar el usuario al tamaño de un "bloque de control de memoria" para obtener el número real de bytes que deben asignarse.

  1. Luego, repita todos los bloques de memoria en el montón en secuencia, si el bloque está disponible y es mayor que el número real de bytes requerido, entonces se devuelve la primera dirección del bloque de memoria y el bloque está configurado para estar disponible; de ​​lo contrario, intente con el siguiente bloque de memoria.
  2. Si todos los bloques de memoria no satisfacen la condición, se llama a la función sbrk (si la función sbrk falla, no hay memoria disponible en el sistema y la función malloc devuelve NULL), y se asigna un bloque de memoria a través del sistema operativo. La función malloc expande esta memoria en el montón, que es equivalente a un crecimiento de montón.
  3. Omita la "estructura de control de memoria" de este bloque de memoria y restablezca la última dirección del último bloque de memoria.

Algunas notas sobre la función libre: El trabajo principal de la función libre es configurar el bloque de control de memoria para que esté disponible. Cuando se llama a la función malloc la próxima vez, el bloque de memoria se puede asignar como un bloque asignable. Por lo tanto, después de llamar a la función libre, el contenido del bloque de memoria no desaparecerá de inmediato, pero eso se debe a que este contenido ya no está protegido por el sistema operativo, por lo que el tiempo efectivo también es aleatorio.


Entorno de 3 conchas

Tanto los parámetros de la línea de comandos como las variables de entorno se obtienen del proceso padre y se obtienen de diferentes maneras.
Los parámetros de la línea de comandos se transfieren al nuevo proceso como parámetros de la función principal, y el nuevo proceso utiliza las variables de entorno como una variable global.

3.1 Parámetros de línea de comando y aplicaciones

argc:命令行参数的个数;
argv:指向参数的各个指针所构成的数组;

Argv [0] aquí representa el nombre completo de la ruta del programa ejecutable, no solo el nombre del archivo del programa ejecutable. (Para obtener el nombre del archivo a través del nombre de la ruta, se requiere el procesamiento de caracteres correspondiente); argv [argc] debe ser NULL.

3.2 Variables de entorno

Cada programa tendrá una tabla de variables de entorno, al igual que los parámetros de la línea de comandos, la tabla de variables de entorno también es una matriz de punteros. Incluya el archivo de encabezado correspondiente, escriba extern char ** entorno en el programa; leyendo el entorno [i] y el bucle hasta el entorno [i] = NULL, puede obtener la tabla de variables de entorno, es decir, el valor de cada variable de entorno.

Nota: No tiene sentido modificar las variables de entorno en este proceso, ya que no afectará a otros procesos.

Los prototipos de configuración, obtención y eliminación de variables de entorno son los siguientes:

#include <stdlib.h>
char *getenv(const char *name);//获取环境变量,成功则返回环境变量的值,失败则返回NULL。
int put(char* str);            //将 name==value的字符串放进环境表,如果原来有值则覆盖。
int setenv(const char *name, const char *value, int overwrite);//设置环境变量,这里第3个参数rewrite的值为0则:不修改原来的值;非0值则:修改原来的值。
int unsetenv(const char *name);//删除一个环境变量的值,成功返回0,失败返回-1。
int clearenv();         //此函数会将整个environ这个指针置为NULL,成功返回0,失败返回-1。

Consulte el manual de referencia de la función de Linux para más detalles . Las funciones anteriores que operan en estas variables de entorno solo afectan sus propios procesos y procesos secundarios, y no tienen ningún efecto en el proceso primario.

3.3 Obtener el estado final del proceso

$? Es una variable incorporada en el shell de Linux, que contiene el valor de retorno del programa ejecutado más recientemente. Hay 3 situaciones:

  1. La función principal del programa finaliza y el valor de retorno de la función principal se guarda en $?.
  2. Llame a la función de salida para finalizar la operación mientras se ejecuta el programa, $? Guarde los parámetros de la función de salida.
  3. El programa sale anormalmente y el número de error del error anormal se guarda en $?.

Nota:

  1. Si el programa se ejecuta incorrectamente, el valor en la variable incorporada $? Es 1. Por lo tanto, al escribir código, si no hay ningún problema con el código, no devuelva 1 (exit (1) o return (1)). Para no causar confusión innecesaria.
  2. Si la función principal no devuelve un valor especificado, entonces el valor en $? No es aleatorio, ¡recuerde!
  3. Dado que el valor de la variable integrada en $? En el shell de Linux es en realidad el valor del registro eax después de que finaliza el proceso (solo bajo la arquitectura X86), se ve que el sistema Linux usa eax para guardar cada función en esta arquitectura Valor de retorno. Este valor es diferente para diferentes sistemas.

3.4 Depuración de programas con errno

Hay varias formas de depurar un programa:

  • Usar depurador
  • Utilice la función de salida directamente en el programa para generar información de depuración
  • Ver archivo de error estándar
  • Registros escritos cuando el programa es anormal

Se producirán algunos errores al ejecutar llamadas del sistema en Linux. No es suficiente verificar el valor de retorno de estas llamadas del sistema. Los desarrolladores a menudo necesitan información más detallada. El lenguaje C proporciona una variable global errno. Cuando se usa, se agrega el archivo de encabezado <errno.h>. Esta variable global compensa las deficiencias de la información de valor de retorno insuficiente.
Si errno es 0, no hay error. Si se produce un error, se emite el número de error. Al usarlo, primero se debe borrar a 0, porque es una variable global.

3.5 Causas de errores de salida

errno es solo un valor entero, debe buscar la tabla para saber, para encontrar errores más fácilmente, puede usar dos funciones, estas dos funciones proporcionan el número de error a la conversión de información: strerror y perror. Prototipo de la función strerror:

#include <string.h>
char *strerror(int errnum);

Consulte el manual de referencia de la función de Linux para más detalles . Prototipo de función perror:

#include <stdio.h>
void perror(const char *s);

Consulte el manual de referencia de la función de Linux para más detalles .

Nota: No agregue '\ n' a esta cadena, el sistema la agregará automáticamente. La ventaja de esto es que se puede pasar un parámetro menos, la desventaja es que el perror no tiene búfer y es una función con efectos secundarios, cuya función es generar la causa del error de una función del sistema más cercana a la llamada a la función.    

4 salto global

La declaración goto es una declaración que solo se puede saltar dentro de la función, es decir, tal salto es local, y para un salto global, la declaración goto no tiene poder. Para hacer un salto global, necesita ese tipo de declaración de salto global.

En Linux, use la función setjump y la función longjump para lograr el salto global. La idea de este salto es establecer primero un punto de salto y guardar el marco de la pila de llamadas de función actual. Cuando el programa realiza un salto global y regresa al punto de salto, el marco de la pila del informe se utiliza para sobrescribir el marco de la pila existente, y así realizar la restauración del marco de la pila de funciones. En Linux, use la estructura jmp_buf para guardar el marco de la pila actual y luego restaure el marco de la pila en la estructura al saltar. En Linux, use la función setjmp para establecer un punto de salto global. El prototipo de la función es el siguiente:

#include <setjmp.h>
int setjmp(jmp_buf env);

Consulte el manual de referencia de la función de Linux para más detalles . En Linux, parece utilizar la función longjmp para realizar un salto global. El prototipo de la función es el siguiente:

#include <setjmp.h>
void longjmp(jmp_buf env, int val);

Consulte el manual de referencia de la función de Linux para más detalles . Con saltos globales, la estructura del programa se controla mejor y el código se vuelve compacto. El uso del salto global es una aplicación relativamente avanzada. El salto global requiere la asistencia del sistema operativo, mientras que el salto local no. Los saltos locales se implementan a nivel de idioma, y ​​tenga en cuenta que goto es solo una palabra clave en C.

Publicado 289 artículos originales · elogiados 47 · 30,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/vviccc/article/details/105152197
Recomendado
Clasificación