Análisis en profundidad de "todo es un archivo" en el sistema Linux

0 Implementar MiniShell

** Observamos ingresar comandos en el shell y el conocimiento descrito en el último artículo, podemos escribir un shell simple ** Para
escribir un shell, necesitamos repetir el siguiente proceso

  1. Obtener la línea de comando
  2. Analizar la línea de comando
  3. Use fork para crear un proceso hijo (fork)
  4. Reemplazar proceso hijo (execvp)
  5. El proceso padre espera a que salga el proceso hijo (esperar / waitpid)

Código

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

#define SIZE 256
#define NUM 16
// 自己实现了一个命令行解析器:MiniShell
// 需要的知识:1、多进程编程 2、程序替换 3、C字符串操作函数 4、进程的理解
int main()
{
    
    
  // 对输入命令行字符串的解析
  char cmd[SIZE];
  const char *cmd_line = "[temp@腾讯云 MyMiniShell]# ";
  while (1)
  {
    
    
    cmd[0] = 0;
    printf("%s",cmd_line);
    fgets(cmd,SIZE,stdin);
    cmd[strlen(cmd) - 1] = '\0';//为了避免把命令的换行读进去
    char *args[NUM];           // 字符串数组  保存解析后的命令行参数,
    args[0] = strtok(cmd," ");
    int i = 1;
    do {
    
    
       args[i] = strtok(NULL," ");
       if (args[i] == NULL) break;
       ++i;
    }while(1);
    int size = sizeof(args)/sizeof(args[0]);
    args[size - 1] = NULL;

    //多进程和程序替换
    pid_t id = fork();
    if (id < 0) {
    
    
      perror("fork error!\n");
      continue;
    }
    if (id == 0) {
    
    
      // child process
      execvp(args[0],args);
      exit(1);
    }
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if (ret > 0) {
    
    
      printf("status code : %d\n",(status >> 8) & 0xff);//这是进程退出码的底层实现,当然也可以用宏
    }else {
    
    
      printf("process wait error!\n");
    }
  }
  return 0;
}

Resultados presentados
Inserte la descripción de la imagen aquí

1 archivo C IO

  • C abrirá tres flujos de entrada y salida de forma predeterminada, a saber , stdin, stdout, stderr
  • Los tipos de estas tres secuencias son ARCHIVO * punteros de archivo
    , consulte : https://blog.csdn.net/CZHLNN/article/details/110238501

2 Interfaz de llamada al sistema relacionada con archivos

Para manipular archivos, además de la interfaz C anterior , podemos usar la interfaz de llamada al sistema, abrir, cerrar, leer , escribir , C ++, java u otros lenguajes también tienen interfaces IO similares, y las implementaciones subyacentes son todas interfaces de llamada al sistema.

2.1 Introducción a la interfaz abierta

Inserte la descripción de la imagen aquí
nombre de ruta : el archivo de destino que se abrirá o creará
banderas : al abrir el archivo, puede pasar múltiples opciones de parámetros y usar una o más de las siguientes constantes para realizar la operación "o" para formar el
parámetro banderas :
O_RDONLY: leer -sólo abrir
O_WRONLY: escribir solo Abrir
O_RDWR: abrir para lectura y escritura
Las primeras tres constantes, se debe especificar una y solo se puede especificar una.
O_CREAT: Si el archivo no existe, créelo y use la opción de modo para especificar el permiso de acceso del nuevo archivo.
O_APPEND: agregar
valor de retorno de escritura :
archivo abierto correctamente: el descriptor de archivo
abierto o creado no pudo abrir el archivo: -1

usar
Inserte la descripción de la imagen aquí

3 Descriptor de archivo

3.1 ¿Qué es un descriptor de archivo?

El descriptor de archivo es en realidad un número positivo.Cada vez que llama a open para abrir un nuevo archivo, el fd devuelto siempre comienza desde 3. Entonces, ¿por qué sucede esto?
De hecho, el proceso de Linux tendrá tres descriptores de archivos abiertos predeterminados por defecto, que son stdin (entrada estándar) 0, stdout (salida estándar) 1, stderr (error estándar) 2, 0, 1, 2 Los dispositivos físicos correspondientes son generalmente : Teclado, monitor, monitor. Entonces, ¿qué hizo la capa inferior cuando abrimos el archivo?
Cuando abrimos un archivo, el sistema operativo debe crear una estructura de datos correspondiente en la memoria para describir el archivo de destino, por lo que hay una estructura de archivo, que representa un objeto de archivo abierto, y el proceso ejecuta la llamada al sistema abierto, por lo que el proceso y el archivo debe estar asociado, hay un puntero * archivos en la pcb de cada proceso, que apunta a una tabla files_struct. La parte más importante de la tabla es una matriz de punteros de estructura. Cada puntero apunta a la estructura de archivo abierta, archivo Hay punteros de función en la estructura, el puntero de función encontrará el controlador correspondiente y luego el controlador encontrará el disco duro o la pantalla.

Echemos un vistazo al código fuente del kernel de Linux.

Inserte la descripción de la imagen aquí
La misma interfaz de llamada al sistema apunta a diferentes controladores de hardware en diferentes estructuras de archivos. En realidad, esto es polimorfismo en OOP. El tipo grande que escribe SO usa C para implementar el polimorfismo. El
resumen: como se muestra en la figura
Inserte la descripción de la imagen aquí

3.2 Reglas de asignación de descriptores de archivos

Aquí hacemos un pequeño experimento, vea el siguiente código
Inserte la descripción de la imagen aquí
fd como

Inserte la descripción de la imagen aquí

Apague 0 y 2 y mire el
Inserte la descripción de la imagen aquí
resultado nuevamente. El
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
resultado es
Inserte la descripción de la imagen aquí
visible. La regla de asignación del descriptor de archivo es: en la matriz files_struct, busque el subíndice más pequeño que no se usa actualmente como un nuevo descriptor de archivo.

3 ¿Cómo se entiende la redirección (desde la perspectiva del sistema operativo)?

Mira el siguiente código: el
Inserte la descripción de la imagen aquí
resultado es
Inserte la descripción de la imagen aquí

En este momento, el contenido que debería haberse enviado a la pantalla se envía al archivo myfile1 y fd = 1. Este fenómeno se denomina redirección de salida. Los redireccionamientos comunes incluyen>, >>, <
¿Qué hizo la capa inferior?

Inserte la descripción de la imagen aquí
Con la reserva de conocimientos anterior, no debería ser difícil comprender esta imagen.

Mira el siguiente código

Inserte la descripción de la imagen aquí

printf es una función en la biblioteca C , generalmente se envía a stdout , pero cuando stdout accede al archivo subyacente, encuentra fd: 1, pero en este momento, el contenido representado por fd: 1 se ha convertido en la dirección de log.txt. ya no es la dirección del archivo de visualización , por lo que cualquier mensaje de salida se escribirá en el archivo, completando así la redirección de salida.
Las funciones relacionadas con IO corresponden a la interfaz de llamada del sistema y las funciones de la biblioteca encapsulan las llamadas del sistema, por lo que, en esencia, todo el acceso a los archivos se accede a través de fd.
Por lo tanto, la estructura FILE en la biblioteca C debe encapsular fd, que es el caso en Linux y Windows. Mira la foto de abajo.

Linux bajo
Inserte la descripción de la imagen aquí
Windows
Inserte la descripción de la imagen aquí

4 Habla de búfer de nuevo

Inserte la descripción de la imagen aquí

De la figura anterior, sabemos que hay búferes y descriptores de archivo dentro del ARCHIVO C / C ++. Fd
0 corresponde a stdin / cin 1 corresponde a stdout / cout 2 corresponde a stderr / cerr

Mira el siguiente código: el
Inserte la descripción de la imagen aquí
resultado es

Inserte la descripción de la imagen aquí
Pero, ¿qué pasa si se implementa la redirección de salida para el proceso? ./myexe> log, encontramos que el resultado se convierte en:

Inserte la descripción de la imagen aquí

Descubrimos que tanto printf como fwrite (funciones de biblioteca) generan dos veces, mientras que la escritura solo genera una vez (llamada al sistema). ¿Por qué? ¡Debe estar relacionado con la
bifurcación!

  • Generalmente, las funciones de la biblioteca C se almacenan en búfer completo cuando se escribe en un archivo, mientras que la escritura en la pantalla es un búfer de línea.
  • La función de biblioteca printf fwrite tiene su propio búfer.Cuando ocurre la redirección a un archivo normal, el modo de almacenamiento en búfer de datos cambia de búfer de línea a búfer completo.
  • Entonces, la salida de datos de las dos funciones está en el búfer y no se actualizará inmediatamente, hasta el final del proceso, se actualizará de manera uniforme y se escribirá en el archivo.
  • Pero cuando se bifurca, los datos padre-hijo se copiarán en la escritura, por lo que el padre y el hijo tendrán una copia privada de los datos en el búfer. Cuando finalice el proceso padre-hijo, verá dos copias de la función de biblioteca imprimir resultados en el registro.
  • Pero la escritura no cambia, lo que indica que no existe el llamado almacenamiento en búfer.
    Resumen: La función de la biblioteca printf fwrite tendrá su propio búfer y la llamada al sistema de escritura no tiene búfer. Además, los búferes mencionados aquí son búfer de nivel de usuario. De hecho, para mejorar el rendimiento, el sistema operativo también proporcionar búferes de nivel de kernel relacionados. Entonces, ¿quién proporcionó la zona de amortiguación por la que nos hemos estado molestando? printf fwrite es una función de biblioteca, write es una llamada al sistema, la función de biblioteca es la "superior" de la llamada al sistema y es la "encapsulación" de la llamada al sistema, pero write no tiene búfer, mientras que printf fwrite tiene, que es suficiente para mostrar que el búfer es un Plus secundario, porque es C, lo proporciona la biblioteca estándar de C.

5 llamada al sistema dup2

Inserte la descripción de la imagen aquí
El uso de dup2
Inserte la descripción de la imagen aquí
** Copiamos y sobrescribimos el contenido de la estructura de archivo donde se encuentra fd en la estructura de archivo donde se encuentra la pantalla de salida estándar, luego fd es 1 y ya no es una pantalla, sino el archivo que especificamos czh .TXT **

6 Entender el sistema de archivos

Lo que vimos cuando usamos ls -l además del nombre del archivo, pero también los metadatos del archivo
Inserte la descripción de la imagen aquí

Cada fila incluye 7 columnas

  • modo
  • Número de enlaces físicos
  • Propietario del archivo
  • El grupo al que pertenece el archivo
  • Tamaño del archivo
  • Hora de la última modificación del archivo
  • nombre del archivo

ls -l lee la información del archivo almacenada en el disco y luego la muestra.
De hecho, además de leer esta información de esta manera, también hay un comando stat para ver más información
Inserte la descripción de la imagen aquí

7 Bibliotecas dinámicas y estáticas

Biblioteca estática y biblioteca dinámica

  • Biblioteca estática (.a): el código de la biblioteca está vinculado al archivo ejecutable cuando el programa se compila y vincula. La biblioteca estática ya no será necesaria cuando el programa se esté ejecutando.
  • Biblioteca dinámica (.so): el código de la biblioteca dinámica está vinculado cuando el programa se está ejecutando y el código de la biblioteca es compartido por varios programas.
  • Un archivo ejecutable vinculado con una biblioteca dinámica solo contiene una tabla de direcciones de entrada de función que utiliza, en lugar de todo el código de máquina del archivo objeto donde se encuentra la función externa.
  • Antes de que el archivo ejecutable comience a ejecutarse, el sistema operativo copia el código de máquina de la función externa de la biblioteca dinámica en el disco a la memoria, este proceso se denomina vinculación dinámica.
  • Las bibliotecas dinámicas se pueden compartir entre varios programas, por lo que la vinculación dinámica reduce el tamaño de los archivos ejecutables y ahorra espacio en el disco. El sistema operativo utiliza un mecanismo de memoria virtual para permitir que una biblioteca dinámica en la memoria física sea compartida por todos los procesos que necesitan usar la biblioteca, ahorrando memoria y espacio en disco.

Crea tu propia biblioteca dinámica y estática

Puede consultar mi código de GitHub. También es posible eliminar la biblioteca estática después de formar un programa ejecutable
https://github.com/CZH214926/Linux_C_Cpp

Supongo que te gusta

Origin blog.csdn.net/CZHLNN/article/details/115111647
Recomendado
Clasificación