[linux] E/S básica

Directorio de artículos

E/S básica

1. Revisando el archivo

1.1 Preparativos

1.1.1 Hacer preguntas

Algunas preguntas antes de hablar de archivos:

  1. ¿Realmente entiendes el principio y el funcionamiento de los archivos? No es un problema de idioma, sino un problema del sistema.
  2. ¿Es solo C/C++ el que tiene operaciones con archivos? python, java, go... sus métodos de operación de archivos son diferentes, ¿cómo entender este fenómeno? ¿Existe una perspectiva unificada para observar todas las operaciones de archivos de idioma?
  3. Al operar un archivo, lo primero que debe hacer es abrir el archivo.¿Cuál es el propósito de abrir el archivo? ¿Cómo entenderlo?

1.1.2 Consenso alcanzado

  • Archivo = contenido + atributo => operaciones en archivos incluyen: operaciones en contenido y operaciones en atributos
  • Cuando el archivo no está siendo operado, ¿dónde se encuentra generalmente el archivo? disco
  • Cuando operamos en archivos, ¿dónde deben estar los archivos? ¿Por qué? memoria, porque el sistema de von Neumann
  • Cuando operamos sobre un archivo, es necesario cargar previamente el archivo en la memoria ¿Se carga el contenido o el atributo? al menos tener atributos
  • Cuando operamos en el archivo, el archivo debe cargarse en la memoria con anticipación. ¿Eres tú quien carga el archivo en la memoria (lo abres solo)? No, debe haber muchos archivos diferentes en la memoria. propiedades de
  • Entonces, en resumen, la esencia de abrir un archivo es cargar los atributos de archivo requeridos en la memoria . Debe haber una gran cantidad de archivos abiertos dentro del sistema operativo al mismo tiempo, entonces, ¿debe el sistema operativo administrar estos archivos abiertos? Para, primero describir, luego organizar (cómo administrar)

Primero describa: el archivo de estructura de estructura de archivos integrado en la memoria {atributo de archivo (puede provenir del disco), archivo de estructura * siguiente} indica el archivo que se abrirá

Para cada archivo abierto, se debe crear una estructura de estructura correspondiente al objeto de archivo en el sistema operativo, y todas las estructuras de archivos de estructura se pueden vincular con una determinada estructura de datos => dentro del sistema operativo, el archivo abierto se administra, se convierte en agregar , borrando, revisando y modificando la lista enlazada

=> Conclusión: el archivo se abre y el sistema operativo necesita crear una estructura de datos del kernel correspondiente para el archivo abierto

inserte la descripción de la imagen aquí

  • Los archivos se pueden dividir en dos categorías: archivos de disco, archivos abiertos (archivos de memoria) [la primera mitad de este artículo habla de archivos abiertos y la segunda mitad habla de archivos de disco]
  • El archivo está abierto, ¿quién lo está abriendo? OS, pero ¿quién abrió el OS? Usuario (representado por un proceso [el código escrito se compila y ejecuta para convertirse en un proceso])
  • Todas nuestras operaciones de archivos anteriores son la relación entre el proceso y el archivo abierto.
  • Es la relación entre el proceso y el archivo abierto: la relación entre struct task_struct y struct file

1.2 Recuperación de operaciones de archivos en lenguaje C

El siguiente contenido también se puede llamar el esquema de lenguaje de operación de archivos

1.2.1 Escribir archivos

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Discriminación

inserte la descripción de la imagen aquí

fprintf

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

snprintf

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.2.2 Lectura de archivos

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.2.3 Añadir a un archivo

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.3 Llamadas al sistema para operaciones con archivos

El siguiente contenido también se puede llamar el esquema del sistema de operación de archivos

1.3.1 Introducción de la interfaz OS abierta (bandera de bit)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1

Con respecto a las banderas del segundo parámetro, es una estructura de mapa de bits:

inserte la descripción de la imagen aquí

Pequeña demostración :

El siguiente código:

inserte la descripción de la imagen aquí

resultado de la operación:

inserte la descripción de la imagen aquí

1.3.2 Operación de escritura

int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.3.3 Añadir operación

int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.3.4 Operaciones de solo lectura

int fd = open(FILE_NAME, O_RDONLY);

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.4 Contesta las preguntas

Para responder a las preguntas planteadas anteriormente:

  • ¿Es solo C/C++ el que tiene operaciones con archivos? python, java, go... sus métodos de operación de archivos son diferentes, ¿cómo entender este fenómeno? ¿Existe una perspectiva unificada para observar todas las operaciones de archivos de idioma?

No solo C/C++ tiene operaciones de archivos, sino también otros idiomas; los métodos de operación de archivos de cada idioma son diferentes porque los diferentes idiomas tienen operaciones de archivos personalizadas para cumplir con el paradigma gramatical de diferentes idiomas ; mire todas las operaciones de archivos de idiomas desde un punto de vista unificado. perspectiva: todo Para llamar a la interfaz del sistema para completar la operación de archivo

inserte la descripción de la imagen aquí

  • Al operar un archivo, lo primero que debe hacer es abrir el archivo.¿Cuál es el propósito de abrir el archivo? ¿Cómo entenderlo?

La esencia de abrir un archivo es cargar los atributos de archivo requeridos en la memoria. Para cada archivo abierto, se debe crear una estructura de estructura correspondiente al objeto de archivo en el sistema operativo. Todas las estructuras de archivo de estructura se pueden vincular con una determinada estructura de datos. Levantarse => Dentro del sistema operativo, la gestión de los archivos abiertos se convierte en agregar, eliminar, verificar y modificar la lista vinculada

2. Comprensión profunda de las llamadas al sistema de archivos

2.1 Descriptores de archivos

  • Al aprender a abrir la función de llamada del sistema de archivos, descubrimos que la función abrir tendrá un valor de retorno, este valor de retorno es el descriptor de archivo, y el descriptor de archivo es un número entero pequeño

Abra el archivo LOG (archivo log.txt) en modo de escritura y reciba el valor de retorno, imprima el descriptor del archivo y observe los resultados de ejecución

inserte la descripción de la imagen aquí

resultado de la operación:

inserte la descripción de la imagen aquí

Encontraremos que el resultado de la impresión comienza desde 3 y aumenta sucesivamente, por lo que nos lleva a dos preguntas:

  1. ¿Por qué el resultado comienza desde 3 en lugar de 0, en secuencia 0, 1, 2...
  2. ¿Qué tipo de estructura aumentará continua y secuencialmente como el resultado anterior?

Resolvamos estos dos problemas

2.1.1 entrada estándar, salida estándar y derr estándar

C abrirá tres flujos de entrada y salida de forma predeterminada, a saber, stdin, stdout, stderr

Una observación cuidadosa revela que los tipos de estos tres flujos son todos FILE , el tipo de valor de retorno de fopen y el puntero de archivo *

inserte la descripción de la imagen aquí

Pequeña demostración:

Escribe el siguiente código:
inserte la descripción de la imagen aquí

Observaciones en ejecución encontradas:

En este momento, se imprimen 0, 1, 2

inserte la descripción de la imagen aquí

Entonces, esto también explica por qué el descriptor de archivo comienza desde 3 de manera predeterminada, porque 0, 1 y 2 están ocupados de manera predeterminada. Estas interfaces de nuestro lenguaje C encapsulan la interfaz de llamada predeterminada del sistema. Al mismo tiempo, la estructura FILE del lenguaje C también encapsula el descriptor de archivos del sistema.

Entendiendo FIEL

inserte la descripción de la imagen aquí

Resumir
  • De forma predeterminada, un proceso de Linux tiene tres descriptores de archivos abiertos, a saber, entrada estándar 0, salida estándar 1 y error estándar 2.
  • Los dispositivos físicos correspondientes a 0,1,2 son generalmente: teclado, monitor, monitor

inserte la descripción de la imagen aquí

2.1.2 La naturaleza de los descriptores de archivo

Para resolver el segundo problema, ¿qué tipo de estructura aumentará continua y secuencialmente como el resultado anterior?

Como se muestra abajo:

El PCB contiene un puntero de archivos, que apunta a una estructura que pertenece a la relación correspondiente entre el proceso y el archivo: struct files_struct, y esta estructura contiene una struct file* fd _array[]matriz llamada matriz de puntero, por lo que los tres primeros 0, 1 y 2 en la figura son reemplazado por el teclado y La pantalla llama, razón por la cual los descriptores de archivo subsiguientes comienzan desde 3, y luego completan la dirección del archivo en el descriptor de archivo No. 3, y el descriptor de archivo No. 3 ahora apunta al archivo recién abierto. archivo.

inserte la descripción de la imagen aquí

Luego devuelva el descriptor No. 3 al usuario a través de la llamada al sistema para obtener un número llamado 3, de modo que cuando un proceso acceda a un archivo, debe pasar 3, busque la tabla de descriptores de archivo correspondiente a través de la llamada al sistema y encuéntrela. a través de la dirección almacenada Si se encuentra el archivo correspondiente, se puede operar en el archivo. Por lo tanto, la esencia de un descriptor de archivo es un subíndice de matriz.

Entonces, ¿cuáles son los beneficios de hacer esto?

Como se muestra en la figura a continuación, la gestión de procesos está a la izquierda y el sistema de archivos está a la derecha, lo que completa el desacoplamiento entre módulos.

inserte la descripción de la imagen aquí

2.1.3 Reglas de asignación para el archivo fd

Mira directamente el código:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    
    
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
    
    
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

La salida encontrada esfd: 3

Apague 0 o 2, luego mire

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    
    
    close(0);
    //close(2);
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
    
    
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

El hallazgo es que el resultado es: fd: 0ofd 2

Reglas de asignación para descriptores de archivo: en la matriz files_struct, busque el subíndice más pequeño que no se esté utilizando actualmente como un nuevo descriptor de archivo.

3. Redirigir

3.0 ¿Qué es la redirección?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
    
    
    close(1);
    int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
    if(fd < 0){
    
    
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);

    close(fd);
    return 0;
}

De acuerdo con las reglas de asignación de descriptores de archivos mencionadas anteriormente, explicaremos este código en orden:

Primero cierre el stdout (salida estándar: salida a la pantalla) correspondiente al descriptor de archivo 1, y luego asígnelo a través de f, el fd de este archivo escaneará de pequeño a grande y encontrará que la posición de 1 no se usa, entonces el archivo recién creado será myfile está conectado con el puntero correspondiente:

inserte la descripción de la imagen aquí

Por lo tanto, cuando nuestro printf se imprime en stdout, dado que el descriptor de archivo superior stdout corresponde a 1, el archivo correspondiente en array[1] se encontrará en el núcleo para la operación, pero en este momento 1 ya no corresponde a la salida estándar de The monitor, sino el archivo myfile, por lo que no veremos el valor de fd en el monitor al imprimir, sino en el archivo myfile.

inserte la descripción de la imagen aquí

  • La esencia de la redirección: en el caso de que la capa superior no pueda percibir, dentro del sistema operativo, cambie el subíndice específico en la tabla de descriptores del archivo correspondiente al proceso

3.1 3 tipos de redirección

3.1.1 Redirección de salida

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

En este momento, encontramos que el contenido que debería haberse mostrado en la pantalla se envía al archivo log.txt, donde fd=1. Este fenómeno se denomina redirección de salida.

3.1.2 Redirección de entrada

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

En este momento, encontramos que el contenido debe ingresarse a través del teclado, y el contenido se lee directamente desde el archivo log.txt, donde fd=0. Este fenómeno se llama redirección de entrada.

3.1.3 Agregar redirección

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

En este momento, encontramos que el contenido que debería haberse agregado a la pantalla se adjunta al archivo log.txt, donde fd=1. Este fenómeno se llama agregar redirección.

3.1.4 Suplemento

inserte la descripción de la imagen aquí

resultado de la operación:

inserte la descripción de la imagen aquí

Juntos imprimen en la pantalla:

inserte la descripción de la imagen aquí

Los mensajes normales se imprimen en log.normal, los mensajes anormales se imprimen en log.error

Solución:

  1. escribir el código:

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

  1. Algunas instrucciones simples de redirección:

inserte la descripción de la imagen aquí

3.2 Redirección de llamadas al sistema dup2

Ya sea que se trate de reglas de asignación o redirección demostradas anteriormente, es muy problemático cerrar directamente la operación, porque tal operación de cierre no es lo suficientemente flexible, por lo que ahora presentamos una interfaz de redirección de llamadas al sistema:dup2

int dup2(int oldfd, int newfd);//newfd的内容最终会被oldfd指向的内容覆盖

Manifestación:

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

4. Cómo entender que todo es un archivo bajo Linux

inserte la descripción de la imagen aquí

5. Amortiguador

5.1 Introducción al conocimiento

5.1.1 Introducción de fenómenos

Use la función de lenguaje C y la interfaz de llamada del sistema para imprimir cadenas en la pantalla

#include <stdio.h>
#include <unistd.h>
#include<string.h> 

int main()
{
    
    
    //C库
   fprintf(stdout, "hello fprintf\n");
    
    //系统调用
   const char *msg = "hello write\n";
   write(1, msg, strlen(msg)); //+1?
                                                                                         
   return 0;
}

resultado:

inserte la descripción de la imagen aquí

Después de agregar un tenedor sobre la base de este código

#include <stdio.h>
#include <unistd.h>
#include<string.h> 

int main()
{
    
    
    //C库
   fprintf(stdout, "hello fprintf\n");
    
    //系统调用
   const char *msg = "hello write\n";
   write(1, msg, strlen(msg)); //+1?
          
   //代码结束之前,进行创建子进程
   fork();
   return 0;
}

resultado:
inserte la descripción de la imagen aquí

Todavía es un fenómeno normal ejecutar directamente, pero cuando se redirige a log.txt, la interfaz C se imprime dos veces, ¿por qué ocurre este fenómeno?

5.1.2 Comprender los búferes

inserte la descripción de la imagen aquí

5.1.2.1 Por qué hay un búfer

Como ejemplo:

A y B son amigos entre sí. Un día, A en Xi'an quiere enviar algo a B en Beijing. Puede optar por andar en bicicleta de Xi'an a Beijing para entregar el artículo a B. Es teóricamente posible , pero le tomará mucho tiempo a A; así que elige entregar las cosas a la empresa de mensajería y le pide a la empresa de mensajería que lo ayude a enviar las cosas, y B las recibe pronto.

inserte la descripción de la imagen aquí

En la vida real, el significado de la industria de entrega urgente es ahorrarle tiempo al remitente. Para este ejemplo, Xi'an es equivalente a la memoria, el remitente A es equivalente al proceso, el paquete es la información que el proceso necesita enviar , Beijing es equivalente al disco, y B es un archivo en el disco, por lo que se puede ver de la siguiente manera:

inserte la descripción de la imagen aquí

En el sistema de von Neumann, sabemos que la velocidad de acceso directo a la memoria del disco y otros periféricos es relativamente lenta, es decir, al igual que el ejemplo que dimos, A tardará mucho en entregar el paquete en persona, por lo que SF Express también pertenece a la memoria Copie los datos existentes en la memoria a este espacio, y la función de copia regresará directamente, es decir, A se irá después de recibir la notificación de SF Express. Durante la ejecución de su código, los datos en el espacio de memoria correspondiente de SF Express, es decir, el paquete, se enviarán continuamente a la otra parte, es decir, al disco. En este proceso, el espacio abierto por SF Express equivale a una zona de amortiguamiento.

Entonces, ¿cuál es el punto del búfer? - Guardar el tiempo de la persona que llama . Las llamadas al sistema también toman tiempo

5.1.2.2 Estrategia de lavado de búfer
  1. Sin almacenamiento en búfer, actualización instantánea
  2. almacenamiento en búfer de filas, lavado de filas
  3. Almacenamiento en búfer completo, actualización cuando el búfer está lleno

(1) La pantalla utiliza una estrategia de actualización: almacenamiento en búfer de línea

(2) Los archivos ordinarios adoptan una estrategia de actualización: búfer completo

5.1.2.3 ¿Dónde está el búfer?

Cuando abre el archivo con fopen, obtendrá la estructura de ARCHIVO, y el búfer está en esta estructura de ARCHIVO

5.1.3 Explique por qué se imprime dos veces

  1. write es una llamada al sistema, no hay búfer y la llamada directa se escribe en el sistema operativo, por lo que en ambos casos solo se imprimirá una vez

  2. Al imprimir en la pantalla, el esquema de actualización es el almacenamiento en búfer de línea, la cadena se ha vaciado del búfer antes de la bifurcación y la bifurcación no hace nada

  3. Al redireccionar a un archivo, fprintf escribe datos en el búfer, y el esquema de actualización cambia de almacenamiento en búfer de línea a almacenamiento en búfer completo. en el búfer y no hay Después de actualizar, se crea un proceso secundario después de la bifurcación, y stdout pertenece al proceso principal. Cuando se crea un proceso secundario, ¡el proceso sale inmediatamente! No importa quién salga primero, el búfer debe actualizarse (es decir, el búfer se modifica). Una vez modificado, debido a la independencia del proceso, se producirá una copia en escritura, por lo que los datos finalmente se imprimirán en dos copias. .

5.2 ARCHIVO autoencapsulado

La siguiente es una versión de demostración, centrándose en el principio de presentación

Déjame explicarte de antemano: cuando cerramos el archivo, fclose(FILE*), el lenguaje C nos ayudará a vaciar el búfer

mystdio.h

#pragma once

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<malloc.h>
#include<fcntl.h>
#include<unistd.h>
#include<assert.h>


#define NUM 1024
#define BUFF_NONE 0x1    //无缓冲
#define BUFF_LINE 0x2    //行缓冲
#define BUFF_ALL  0x4    //全缓冲



typedef struct _MY_FILE
{
    
    
   int fd;
   char outputffer[NUM];   //输出缓冲区
   int flags;              //刷新方式     
   int current;            //outputbuffer下一次要写入的位置
}MY_FILE;


MY_FILE *my_fopen(const char *path, const char *mode);

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream);

int my_fclose(MY_FILE *fp);

int my_fflush(MY_FILE *fp);

mystdio.c

#include "mystdio.h"

// fopen("/a/b/c.txt", "a");
// fopen("/a/b/c.txt", "r");
// fopen("/a/b/c.txt", "w"); 

MY_FILE *my_fopen(const char *path, const char *mode)
{
    
        
    //1. 识别标志位 --- 判断文件打开方式
    int flag=0;  
    if(strcmp(mode, "r")==0)  flag |= O_RDONLY;  
    else if(strcmp(mode, "w")==0)  flag |= (O_CREAT | O_WRONLY | O_TRUNC);  
    else if(strcmp(mode, "a")==0)  flag |= (O_CREAT | O_WRONLY | O_APPEND);  
    else{
    
    
          //其他的一些打开方式
    }
  
    //2. 尝试打开文件
    mode_t m = 0666;  //设置创建文件的默认权限 
    int fd=0;
    if(flag & O_CREAT)  fd = open(path, flag, m);
    else fd = open(path, flag); 

   if(fd<0) return NULL;
  
   //3. 给用户返回MY_FILE对象, 需要先进行构建
   MY_FILE* mf = (MY_FILE*)malloc(sizeof(MY_FILE));
   if(mf == NULL)    //打开文件失败
   {
    
    
     close(fd);
     return NULL;
   }

   //4. 初始化MY_FILE对象
   mf->fd=fd;
   mf->flags=BUFF_LINE;       //默认刷新方式: 行刷新
   memset(mf->outputffer, '\0', sizeof(mf->outputffer));   
    //或: my->outputbuffer[0]=0;    //初始化缓冲区
   mf->current=0;             //开始时, 缓冲区中数据为0

   //5. 返回打开的文件
   return mf;
} 


// 我们今天返回的就是一次实际写入的字节数,我就不返回个数了
size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{
    
    
  //1. 缓冲区如果已经满了,就直接刷新
  if(stream->current == NUM)  my_fflush(stream);       

  //2. 根据缓冲区剩余情况, 进行数据拷贝即可
  size_t  user_size = size*nmemb;               //用户想写的字节
  size_t  my_size=NUM - stream->current;        //我还剩余的字节

  size_t writen = 0;       //实际所写字节数

  if(my_size >= user_size)            //足以容纳用户想写的数据
  {
    
    
    memcpy(stream->outputffer+stream->current, ptr, user_size);
    //3. 更新计数器字段
    stream->current+=user_size;
    writen=user_size;   
  }
  else                 //空间不够
  {
    
    
    memcpy(stream->outputffer+stream->current, ptr, my_size);
    //3. 更新计数器字段
    stream->current+=my_size;
    writen = my_size;   
  }


  //4. 开始计划刷新, 他们高效体现在哪里  --- TODO
  //不发生刷新的本质, 不进行写入, 就是不进行IO, 不进行系统调用, 所以my_write函数调用会非常快, 数据会暂时保存在缓冲区中
  // 可以在缓冲区中挤压多份数据, 统一进行刷新写入, 本质: 就是一次IO可以IO更多的数据, 提高IO效率
  if(stream ->flags & BUFF_ALL)
  {
    
    
    if(stream->current ==NUM)  my_fflush(stream);
  }
  else if(stream ->flags & BUFF_LINE)
  {
    
    
     if(stream->outputffer[stream->current-1] =='\n')  my_fflush(stream);
  }  
  else
  {
    
    
    //TODO
  }

  return writen;
}
 

int my_fflush(MY_FILE *fp)
{
    
    
  assert(fp);

  write(fp->fd, fp->outputffer, fp->current);
  fp->current=0;

  fsync(fp->fd);    //强制刷新

  return 0;
}


int my_fclose(MY_FILE *fp)
{
    
    
  assert(fp);

  //1. 冲刷缓冲区
  if(fp->current > 0) my_fflush(fp);

  //2. 关闭文件
  close(fp->fd);     //关闭文件描述符

  //3. 释放堆空间
  free(fp);

  //4. 指针置为NULL  --- 可以设置
  fp=NULL;

  return 0;
}

main.c

#include "mystdio.h"

#define MYFILE "log.txt"

int main()
{
    
    
  MY_FILE *fp = my_fopen(MYFILE, "w");
  if(fp == NULL) return 1;

  const char*str="hello my fwrite";

  int cnt=5;

  //操作文件
  while(cnt)
  {
    
    
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%s: %d\n", str, cnt--);
    size_t size = my_fwrite(buffer,strlen(buffer), 1,fp);
    sleep(1);

    printf("当前成功写入: %lu个字节\n", size);
  }

  my_fclose(fp);

  return 0;
}

5.3 La relación entre el búfer y el sistema operativo

inserte la descripción de la imagen aquí

Las cadenas de caracteres que escribimos en el disco se escriben de acuerdo con la actualización de la fila, pero no se escriben directamente en el disco, sino que primero se escriben en el búfer correspondiente al archivo en el sistema operativo.La estructura del archivo, además de algunas interfaces, también tiene un búfer del kernel, y nuestros datos corresponden al descriptor del archivo a través de la estructura del archivo, y luego se escriben en el búfer del kernel, y finalmente el sistema operativo los actualiza en el disco, y actualiza Este proceso lo determina el sistema operativo de forma independiente, en lugar de algo de almacenamiento en búfer de línea, almacenamiento en búfer completo y sin almacenamiento en búfer que acabamos de discutir, porque los búferes que mencionamos se basan en la estructura de ARCHIVO del lenguaje de la capa de aplicación C. La estrategia de actualización y la estrategia de actualización automática del sistema operativo son mucho más complicado que la estrategia que mencionamos, porque el sistema operativo necesita considerar su propia situación de almacenamiento, por lo que el proceso de escribir datos del sistema operativo al periférico no tiene nada que ver con el usuario.

Por lo tanto, se necesita un ciclo tan largo para que un dato se escriba en el hardware (periféricos): primero, los datos escritos por el usuario ingresan al búfer correspondiente a FILE, que está en el nivel de idioma del usuario, y luego a través del estrategia de actualización que mencionamos Actualizar en el búfer del kernel en el sistema operativo guiado por el descriptor de archivo del archivo de estructura* en el sistema operativo, y finalmente escribir en el dispositivo periférico a través de la estrategia de actualización determinada independientemente por el sistema operativo. Si el sistema operativo no funciona, los datos pueden perderse, por lo que si queremos actualizar los datos en el periférico a tiempo, necesitamos algunas otras interfaces para obligar al sistema operativo a actualizar el periférico, es decir, una nueva interfaz: llame int fsync(int fd)esto Después de la función, los datos en el búfer del núcleo se ven obligados a descargarse en el periférico

5.4 Resumen

Por lo tanto, hay dos tipos de búfer que mencionamos anteriormente: búfer de usuario y búfer de núcleo. Los búfer de usuario son búfer de nivel de idioma. Para el lenguaje C, los búfer de usuario están en la estructura FILE. Otros idiomas También es similar; mientras que el búfer del núcleo pertenece al nivel del sistema operativo, su estrategia de actualización se actualiza de acuerdo con la situación real del sistema operativo y no tiene nada que ver con el nivel del usuario.

6. Archivos de disco

En la primera mitad del estudio, todo lo que estudiamos son archivos abiertos ¿Qué pasa si no hay ningún archivo abierto? Si un archivo no se abre, ¿cómo lo gestiona el sistema operativo? (La segunda mitad del artículo habla principalmente de archivos de disco)

  • Los archivos que no se han abierto solo se pueden almacenar en silencio en dispositivos periféricos como discos

Hay una gran cantidad de archivos en el disco, y la mayoría de ellos no se abren.Estos archivos también necesitan ser administrados estáticamente para que podamos encontrarlos y abrirlos en cualquier momento.¿Cómo se administra el disco?

6.1 Estructura del disco

El disco es la única estructura mecánica en nuestra computadora. Para comprender cómo el sistema operativo administra los archivos sin abrir en el disco, primero debemos comprender la estructura física, la estructura de almacenamiento y la estructura lógica del dispositivo de disco, y luego comprender el método de administración del sistema operativo para el disco sobre esta base.

6.1.1 La estructura física del disco

En términos generales, la estructura del disco duro incluye: disco, cabezal magnético, eje del disco, motor de control, controlador del cabezal, convertidor de datos, interfaz, caché, etc.

inserte la descripción de la imagen aquí

Platos: los discos están apilados, lo que significa que un disco tiene muchos platos.
Superficie: Un plato tiene dos superficies.
Cabezas: Hay una cabeza en cada lado. Es decir, si el disco tiene cinco piezas, entonces hay diez lados y también hay diez cabezas. No hay contacto entre la cabeza magnética y la superficie del disco, y está suspendida en la superficie del disco.Una vez que la superficie del disco gira a alta velocidad, la cabeza magnética flotará, por lo que se debe evitar que el disco se agite, de lo contrario, el magnético la cabeza se moverá hacia arriba y hacia abajo, rayando la superficie del disco, lo que resultará en la pérdida de datos binarios en el disco.
Motor: después de encender el disco, el motor hará que el disco gire en sentido contrario a las agujas del reloj a alta velocidad, y la cabeza magnética se balanceará hacia adelante y hacia atrás en este momento. Por lo tanto, el dispositivo de motor puede controlar la oscilación del cabezal magnético y la rotación del plato.

inserte la descripción de la imagen aquí

Se puede encontrar que el disco también tiene su propio circuito de hardware.El circuito de hardware tiene lógica de hardware, que se puede llamar el servosistema del propio disco , es decir, el servosistema puede estar compuesto de circuitos de hardware, para enviar instrucciones binarias al disco, para que el disco pueda ser posicionado o direccionado Un área específica para leer los datos correspondientes en el disco.

extensión:

La computadora portátil no está cargada con un disco mecánico, sino con un SSD. El disco es el único dispositivo mecánico en nuestra computadora. En comparación con otros dispositivos, dado que el disco es una estructura de hardware + periféricos, el acceso al disco duro es relativamente lento (solo relativo), por lo que el sistema operativo necesita manejar mucho trabajo. Aunque los discos rara vez se ven en la actualidad, los discos siguen siendo la corriente principal en el lado empresarial. Debido a que los SSD son demasiado caros y tienen restricciones de lectura y escritura, son fáciles de descomponer. Incluso si la velocidad de acceso supera con creces la de los discos, no puede reemplazar completamente los discos.

Nota: El disco es altamente hermético y se puede desmontar, pero se desechará una vez que se desmonte en condiciones externas normales.

6.1.2 Estructura de almacenamiento en disco

Comprenda la estructura de almacenamiento en disco. Intente comprender la lectura y escritura de datos una vez en el hardware.

plato

Un disco se compone de varios platos (disco 0 en la figura siguiente).

La superficie del plato está cubierta con sustancias magnéticas, que se utilizan para registrar datos binarios. Debido a que los lados frontal y posterior se pueden recubrir con sustancias magnéticas, un disco puede tener dos superficies de disco.

inserte la descripción de la imagen aquí

vía, sector

Cada plato se divide en pistas, y cada pista se divide en sectores. Como se muestra abajo:

inserte la descripción de la imagen aquí

cilindro

Cada superficie del disco corresponde a una cabeza magnética. Todos los cabezales magnéticos están conectados al mismo brazo magnético, por lo que todos los cabezales magnéticos solo pueden "avanzar y retroceder juntos". Las pistas con la misma posición relativa en todos los discos forman un cilindro. Como se muestra abajo

inserte la descripción de la imagen aquí

De lo anterior, ** puede usar (número de cilindro, número de disco, número de sector) para ubicar cualquier "bloque de disco". ** A saber: método de posicionamiento CHS .

6.1.3 Estructura lógica del disco

premisa:

El contenido anterior explica principalmente que si el sistema operativo puede conocer cualquier dirección CHS, puede acceder a cualquier sector.

Entonces, ¿la dirección CHS se usa directamente dentro del sistema operativo?

¿Por qué no usar el método de posicionamiento CHS para ubicar la dirección dentro del sistema operativo?

  1. El sistema operativo es software y el disco es hardware. El hardware localiza una dirección, CHS, pero si el sistema operativo usa directamente esta dirección, ¿qué pasa si el hardware cambia? ¿El sistema operativo tiene que cambiar en consecuencia? El sistema operativo debe desacoplarse del hardware
  2. Incluso para un sector, 512 bytes, el volumen de datos básico por IO es muy pequeño Hardware: 512 bytes, el sistema operativo realmente realiza IO, y la unidad básica es de 4 KB (ajustable) — disco: dispositivo de bloque. Por lo tanto, el sistema operativo debe tener un nuevo conjunto de direcciones para el acceso a nivel de bloque.

La cinta de la figura siguiente es una analogía de un disco.Después de que se desmonta la caja de la cinta y se extrae la cinta, su estructura es lineal, es decir, los datos de la cinta se leen de forma lineal.

inserte la descripción de la imagen aquí

Expanda la superficie del disco de forma lineal:

inserte la descripción de la imagen aquí

Una pista se puede considerar como una matriz, en este momento, al ubicar un sector, solo necesita usar el subíndice de la matriz para completar el posicionamiento.

El sistema operativo realiza IO en unidades de 4 KB, por lo que un bloque de archivos a nivel de sistema operativo debe incluir 8 sectores

Método convencional de acceso a la computadora: dirección de inicio + desplazamiento, también podemos considerar el bloque de datos como un tipo, de manera similar, solo necesitamos saber la dirección de inicio del bloque de datos (dirección de subíndice del primer sector) + 4 KB (tipo de bloque) puede ser accedido

Por lo tanto, la dirección de un bloque es esencialmente un subíndice de la matriz En el futuro, cuando representemos un bloque, podemos usar subíndices lineales para ubicar un bloque.

En este momento, si desea encontrar el sector especificado, siempre que conozca el subíndice de este sector, puede ubicar el sector especificado en el disco. Dentro del sistema operativo, llamamos a esta dirección una dirección LBA (Dirección de bloque lógico), es decir, una dirección de bloque lógico.

Pero nuestro disco solo reconoce direcciones CHS, por lo que podemos realizar la conversión entre direcciones CHS y direcciones LBA de alguna manera.

como:

La fórmula del número de sector lógico LBA:
LBA (número de sector lógico) = número de cabezales × número de sectores por pista × número de cilindro actual + número de sectores por pista × número de cabezal actual + número de sector actual – 1 Por ejemplo: CHS =
0 /0/1, luego según la fórmula LBA=255 × 63 × 0 + 63 × 0 + 1 – 1= 0

6.2 Comprender el sistema de archivos

6.2.1 Partición y agrupación de discos

Por ejemplo, para un disco de 500 GB, la unidad de E/S del sistema de archivos obviamente no es lo suficientemente grande cuando la unidad de E/S del sistema de archivos es de 4 KB, lo dividimos en cada área y no es conveniente administrar cuando cada 100 GB es una partición, entonces necesitamos agrupar estos 100 GB en grupos de 5 GB para administrar

inserte la descripción de la imagen aquí

Después de dividir el grupo, si un grupo se puede administrar bien, entonces otros grupos solo necesitan copiar el método de administración de este grupo para administrar esta partición de la misma manera; y luego copiar el modo de administración de una partición a otras particiones para administrar Bueno, todo el disco se ha ido . Usando la idea de divide y vencerás, siempre que el espacio en disco de 5 GB se administre bien, todo el espacio en disco se puede administrar bien. De la forma anterior, la parte que necesita ser gestionada después de la agrupación se convierte en la siguiente estructura:

inserte la descripción de la imagen aquí

  • Grupo de bloques: el sistema de archivos ext2 se dividirá en varios grupos de bloques según el tamaño de la partición. Y cada Grupo de Bloques tiene la misma estructura. Ejemplos de distritos administrados por el gobierno

  • La primera parte de los datos en cada partición es el Bloque de arranque, seguido de cada grupo, que está relacionado con el arranque de la computadora, no es necesario que nos preocupemos por eso.

6.2.2 Método de gestión de grupo

inserte la descripción de la imagen aquí

  • Super Block (Super Block) : almacena la información estructural del propio sistema de archivos. La información registrada incluye principalmente: la cantidad total de bolck e inodo, la cantidad de bloques e inodos no utilizados, el tamaño de un bloque e inodo, la hora del último montaje, la hora de la última escritura de datos y la hora de la última inspección del disco y otra información relacionada del sistema de archivos. La información del Super Block se destruye, se puede decir que se destruye toda la estructura del sistema de archivos . El Súper Bloque no se coloca en la partición como el Bloque de Arranque, pero cada grupo tiene uno como respaldo. Una vez que otros Súper Bloques sean anormales, se copiarán otros Súper Bloques normales.

  • GDT , Tabla de descriptores de grupos: descriptor de grupos de bloques, que describe información de atributos de grupos de bloques

  • Archivo de inodo
    = contenido + atributo, el contenido y el atributo del archivo en el sistema operativo Linux están separados , y el inodo se usa para almacenar el atributo del archivo. El inodo tiene un tamaño fijo, un inodo por archivo. Dentro del grupo, puede haber varios inodos, y los inodos deben distinguirse. Cada inodo tiene su propio número de inodo, por lo que el número de inodo también pertenece al atributo id del archivo correspondiente.


  • En una partición, habrá una gran cantidad de archivos en la tabla de inodos y habrá una gran cantidad de nodos de inodos. Un grupo debe tener un área para almacenar los nodos de inodos de todos los archivos del grupo. Esta área es la tabla de inodos .

  • Bloques de datos
    , el contenido de un archivo cambia, usamos bloques de datos para guardar el contenido del archivo, por lo que un archivo válido necesita [1, n] bloques de datos para guardar el contenido, luego, si hay varios archivos, se necesitan más bloques de datos: Bloques de datos

  • mapa de bits de inodo
    La estructura de mapa de bits correspondiente al inodo. Cada bit indica si un inodo está libre y disponible


  • Cada bit de Block Bitmap indica si el bloque de datos está libre y disponible

6.2.3 Comprensión profunda de los inodos

(1) inodo frente a nombre de archivo

El sistema Linux solo reconoce el número de inodo y el nombre del archivo no existe en el atributo de inodo del archivo . El nombre del archivo es para el usuario.

(2) Vuelva a comprender el catálogo

¿Los directorios son archivos? Sí. ¿Los directorios tienen números de inodo? ls - ilPuede ver el inodo del archivo por

¿El catálogo tiene contenido?, sí, ¿cuál es el contenido?

Cualquier archivo debe estar dentro de un directorio, por lo que un directorio necesita un bloque de datos para tener contenido. El bloque de datos del directorio almacena la relación de mapeo entre el nombre del archivo y el número de inodo del archivo en el directorio, y en el directorio, el nombre del archivo . e inodo son mutuamente el valor clave

inserte la descripción de la imagen aquí

(3) Proceso básico de acceso a archivos

Cuando accedemos a un archivo, lo hacemos en un directorio específico. Por ejemplo, el proceso básico de cat log.txt (imprimir contenido del archivo):

  1. Primero, en el directorio actual, busque el número de inodo de log.txt
  2. Un directorio también es un archivo y debe pertenecer a una partición Combinado con inodo, busque el grupo en la partición y busque el inodo del archivo en la tabla de inodos en el grupo
  3. A través de la relación de mapeo entre el inodo y el bloque de datos correspondiente, encuentre el bloque de datos del archivo, cárguelo en el sistema operativo y complete la visualización en la pantalla

6.2.4 Cómo entender la adición, eliminación y modificación de archivos

Borrar archivos

  1. Primero construya la relación de mapeo entre el bloque de datos y el inodo de acuerdo con el nombre del archivo y encuentre el número de inodo de este archivo
  2. De acuerdo con la relación de mapeo entre el número de inodo y el atributo de inodo, establezca la posición de bit correspondiente al mapa de bits del bloque en 0
  3. Establezca el bit correspondiente al mapa de bits de inodo en 0 de acuerdo con el número de inodo

Entonces, eliminar un archivo solo necesita modificar el mapa de bits

crear un archivo

Encuentre el número de bit de 0 en el mapa de bits de inodo, configúrelo en 1 y luego complete todos los atributos del archivo en el espacio del subíndice correspondiente al número en la tabla de inodo; luego busque uno o más números de bit de 0 en el mapa de bits del bloque, configúrelo en 1 y luego complete todo el contenido del archivo en el espacio de subíndice correspondiente a la cantidad de bloques de datos; finalmente modifique el superbloque y, al mismo tiempo, debe escribir la relación de mapeo entre el nombre del archivo y el inodo del nuevo archivo en el bloque de datos del directorio.

6.2.5 Detalles adicionales

  1. ¿Qué debo hacer si un archivo se elimina por error?

Teóricamente hablando, 1. Conozca el número de inodo del archivo eliminado, primero tome el número de inodo y configure el bit correspondiente al mapa de bits de inodo de 0 a 1 en el grupo del archivo eliminado específico, luego el archivo no se sobrescribirá ;2 Luego, lea la tabla de inodos de acuerdo con el inodo, extraiga el bloque de datos ocupado por el archivo actual de la tabla de inodos y establezca el mapa de bits correspondiente al bloque de datos en 1 nuevamente. En este punto, se restauran los atributos y el contenido correspondiente del archivo.

Pero la premisa de todo esto es que el nuevo archivo no usa el inodo del archivo original: si el nuevo archivo usa el inodo del archivo original, la tabla de inodos correspondiente y los datos en el bloque de datos se sobrescribirán, por lo que la mejor práctica después de que el archivo se elimine accidentalmente Simplemente no haga nada, para no ocupar el inodo del archivo original al crear un nuevo archivo.

Cuando se trata de recuperar archivos, lo mejor es usar las herramientas relevantes para la recuperación.

  1. El número de inodo es únicamente válido dentro de una partición y no puede cruzar particiones (por lo general, una partición es un sistema de archivos), por lo que el número de inodo se puede usar para determinar la agrupación

  2. ¿Quién hizo la partición y el agrupamiento que aprendimos, completando las propiedades del sistema? ¿Cuándo lo hiciste?

Es hecho por el sistema operativo. Una vez que se completa la partición, para que la partición funcione normalmente, necesitamos formatear la partición. El proceso de formateo es en realidad la información de atributos de administración del sistema de archivos escrita por el sistema operativo en la partición. .

  1. Si el inodo solo usa una matriz para establecer una relación de mapeo con el bloque de datos, suponiendo que un bloque de datos almacena 4 kb de datos y tiene 15 matrices, ¿significa que un archivo puede contener como máximo 15 * 4 = 60 kb de datos?

inserte la descripción de la imagen aquí

  1. ¿Es posible que para un paquete, el bloque de datos no se agote, el inodo desaparezca, o el inodo no se agote y el bloque de datos se agote? Es posible, pero esta situación básicamente no sucederá.

7. Enlaces blandos y duros

7.1 Enlace flexible (enlace simbólico)

ls -n 文件名 所创建软链接名

inserte la descripción de la imagen aquí

usar

La forma general en que queremos ejecutar un programa ejecutable en un directorio profundo:

inserte la descripción de la imagen aquí

De hecho, podemos crear un enlace suave a este programa para que se ejecute

inserte la descripción de la imagen aquí

Se puede ver que los enlaces blandos son equivalentes a los accesos directos de Windows

7.2 Enlaces duros

ln 文件名 所创建硬链接名

inserte la descripción de la imagen aquí

Si se elimina myfile.txt, aún se puede acceder al contenido del archivo a través del enlace físico, porque solo se elimina una relación de asignación, el contador se reduce en 1 y todavía hay una relación de asignación a la que se puede acceder. Por lo tanto, se puede ver que solo cuando el contador de enlaces duros es 0, un archivo se elimina realmente.

Quiere eliminar un enlace duro: puede eliminarlo directamente con rm o eliminarlo unlink 硬链接名con

7.3 Comprensión.

En la ruta actual, creamos un archivo normal y un directorio vacío

inserte la descripción de la imagen aquí

La observación encontró que la cantidad de enlaces duros entre los dos es diferente, la cantidad de archivos ordinarios es 1 y la cantidad de directorios es 2. De hecho, muestra indirectamente que todavía hay un archivo que es el mismo que el Inodo de dir, formando un enlace duro. Ingrese al directorio dir para observar:

inserte la descripción de la imagen aquí

Descubrimos que el .implícito es en realidad un enlace fijo de dir, y el archivo .se genera automáticamente, por lo que la razón por la cual el enlace fijo inicial del directorio es 2 es debido al enlace fijo de .

Entonces, en un directorio, .hay un enlace fijo al directorio actual

7.4 Entender...

Podemos verificar que en el directorio actual, el número de inodo se ve en el modo de ruta absoluta y el modo, y los dos son exactamente iguales;

De la misma manera: ruta absoluta y ruta relativa... Verifique los números de inodo del directorio de nivel superior y descubra que los dos son exactamente iguales, hay 3 enlaces duros debajo del directorio de nivel superior lección19, que son: lección19 en sí, . , . . .

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Esto también verifica que el archivo en Linux es una estructura de árbol de múltiples bifurcaciones

inserte la descripción de la imagen aquí

Entonces, en el directorio, ..es un enlace fijo al directorio principal

Entonces, ¿puedes crear activamente un enlace duro para el directorio?

inserte la descripción de la imagen aquí

Descubrimos que esto no está permitido, entonces, ¿por qué Linux no permite a los usuarios comunes vincular directorios?

Problema de ruta de bucle fácil de causar

inserte la descripción de la imagen aquí

.Los enlaces duros hacia y ..son establecidos por el propio sistema operativo.

7.5 Suplemento

acm
explica los tres tiempos del archivo a continuación:

Acceso: Hora del último acceso
Modificar: Hora de la última modificación del contenido del archivo
Cambiar: Hora de la última modificación del atributo

8. Biblioteca dinámica y biblioteca estática

8.1 Conoce la Biblioteca

inserte la descripción de la imagen aquí

Lo anterior es la biblioteca bajo el sistema Linux.

Conclusión :

  1. El sistema tiene archivos de encabezado y archivos de biblioteca C/C++ preinstalados. Los archivos de encabezado proporcionan descripciones de métodos y la biblioteca proporciona implementaciones de métodos. Existe una relación correspondiente entre el encabezado y la biblioteca, y deben usarse juntos.
  2. El archivo de encabezado se introduce en la etapa de preprocesamiento y la esencia del enlace es en realidad la biblioteca de enlaces.

Entender el fenómeno :

  1. Por lo tanto, instalamos el entorno de desarrollo bajo VS2022: instalar el software del compilador es en realidad instalar las bibliotecas y los archivos que admiten el lenguaje que se va a desarrollar.
  2. Cuando usamos el compilador, habrá una función de recordatorio de sintaxis automática, que primero debe incluir el archivo de encabezado. La función de recordatorio automático depende del archivo de encabezado.
  3. Cuando escribimos código, ¿cómo sabe nuestro entorno dónde hay errores de sintaxis en nuestro código y si hay problemas para definir variables en esos lugares? No subestimes al compilador, hay modos de línea de comandos y otros modos automatizados para ayudarnos a verificar constantemente la sintaxis.

8.2 Por qué hay una biblioteca

En pocas palabras, es mejorar la eficiencia del desarrollo.

8.3 Diseño de una biblioteca

8.3.1 Conocimientos preliminares

Biblioteca estática (.a) : el programa vincula el código de la biblioteca en el archivo ejecutable al compilar y vincular. Cuando el programa se está ejecutando, la biblioteca estática
y la biblioteca dinámica (.so) ya no son necesarias : el código de la biblioteca dinámica solo se vincula cuando el programa se está ejecutando y varios programas comparten el código de la biblioteca.

Nombre de la biblioteca: el nombre real de la biblioteca es eliminar el prefijo lib, después de eliminar el punto (o número de versión) encontrado por primera vez

Por ejemplo, estas dos bibliotecas:libstdc++.so.6 libc-2.17.so

Los nombres reales son:stdc++ c

Por lo general, los servidores en la nube solo tienen bibliotecas dinámicas de forma predeterminada y no hay bibliotecas estáticas. Las bibliotecas estáticas deben instalarse por separado.

Cuando una biblioteca dinámica y una biblioteca estática existen al mismo tiempo, el sistema utiliza la vinculación dinámica de forma predeterminada

Compilador, al vincular, si se proporcionan una biblioteca dinámica y una biblioteca estática, la biblioteca dinámica se usa primero; cuando solo hay una biblioteca estática, solo se puede usar la biblioteca estática

8.3.2 Diseño de una biblioteca estática

Por ejemplo, necesita implementar una operación simple de suma y resta:

inserte la descripción de la imagen aquí

main.c implementado por otra persona

inserte la descripción de la imagen aquí

Ahora hay un directorio otherPerson que representa a otros usuarios que necesitan la implementación de operaciones de suma y resta, pero no queremos darle los archivos fuente directamente, sino darle los archivos .o generados al compilar los archivos de encabezado y los archivos .c , y escribe un archivo main.c y compila el archivo .c para generar un archivo .o, y vincula estos archivos .o para realizar la operación

inserte la descripción de la imagen aquí

Pero este método de dar archivos .o directamente a otra Persona es demasiado problemático, si hay demasiados archivos .o;

Adoptamos un nuevo método, ahora todos los archivos .o ar -rc libmymath.a *.ose escriben en bibliotecas estáticas mediante comandos

Cree dos directorios nuevos, coloque los archivos .o en el directorio lib, coloque los archivos .h en el directorio de inclusión y empaquete estos dos directorios en un paquete comprimido para otra persona, y otra persona puede obtener los dos descomprimiendo archivos en directorios

inserte la descripción de la imagen aquí

Después de vincular el archivo main.c, el archivo .o, el archivo .h y especificar el nombre de la biblioteca estática en este momento, se puede generar el programa ejecutable mytest, simplemente ejecute mytest

inserte la descripción de la imagen aquí

Uso de bibliotecas de terceros
  1. Necesidad de especificar archivos de encabezado y archivos de biblioteca
  2. Si no está instalado en la ruta de búsqueda predeterminada del sistema gcc y g ++, el usuario debe especificar la opción correspondiente para informar al compilador:
    a. ¿Dónde está el archivo de encabezado?
    b. ¿Dónde está el archivo de biblioteca?
    c. ¿Quién es el archivo de biblioteca?
  3. Copie los archivos de la biblioteca y los archivos de encabezado que descargamos a la ruta predeterminada del sistema; de hecho, es para instalar la biblioteca en Linux, entonces, ¿qué pasa con la desinstalación? Para cualquier software, la esencia
    de la instalación y desinstalación es copiar al sistema -ruta específica
  4. Si la biblioteca que instalamos es una biblioteca de terceros (idioma, la interfaz del sistema operativo es la primera y la segunda), debemos usarla normalmente, incluso si se ha instalado en el sistema, gcc, g ++ debe -lespecificar el nombre de la biblioteca especifica

Entender el fenómeno:

Ya sea que descargue la biblioteca directamente de la red, o el código fuente (método de compilación), el comando para realizar la instalación de instalación, es cp, instalado en el sistema, necesitamos sudo para instalar la mayoría de las instrucciones, bibliotecas, etc. o superusuario operación

8.3.3 Configuración de la biblioteca dinámica

Aún en el escenario anterior, en este momento queremos construir una biblioteca dinámica para permitir que otra persona ejecute el código

Antes de construir una biblioteca dinámica, primero necesitamos: gcc -fPIC -c myadd.cy gcc -fPIC -c mysub.c formar un archivo .o (fPIC: generar código independiente de la posición)

inserte la descripción de la imagen aquí

Luego gcc -shared -o libmymath.so *.oconviértalo en una biblioteca dinámica

inserte la descripción de la imagen aquí

La misma operación que la biblioteca estática anterior, cree dos directorios, coloque el archivo .h y el archivo de la biblioteca en el directorio respectivamente, luego empaquételo en un paquete comprimido y cópielo en el directorio otherPerson

inserte la descripción de la imagen aquí

Después de descomprimir el directorio otherPerson, vincúlelo como una biblioteca estática para generar un programa ejecutable y luego ejecute el programa, pero descubra que no se puede ejecutar

inserte la descripción de la imagen aquí

Estamos muy confundidos, ¿no le hemos dicho ya al sistema dónde está mi biblioteca y cómo se llama? ¿Por qué no puedo encontrarlo?

De hecho, no le está diciendo al sistema, sino al compilador

Cuando se ejecuta, debido a que su .so no está en la ruta predeterminada del sistema, el sistema operativo aún no puede encontrarlo

Entonces, ¿por qué se puede encontrar la biblioteca estática?

El principio de vinculación de la biblioteca estática: copie directamente el código binario utilizado por el usuario en el programa ejecutable de destino , pero la biblioteca dinámica no lo hará.

Podemos usar las siguientes tres soluciones para permitir que el sistema operativo encuentre la biblioteca dinámica:

Esquema de variables de entorno

LD_LIBRARY_PATH

Agregue la biblioteca dinámica a la variable de entorno, que es conveniente para el sistema operativo y Shell para encontrar

inserte la descripción de la imagen aquí

Sin embargo, debido a la naturaleza temporal de las variables de entorno, la próxima vez que iniciemos sesión, nuestras variables de entorno personalizadas desaparecerán.

esquema de enlace suave

Cree un enlace suave en la ruta de búsqueda predeterminada del sistema

inserte la descripción de la imagen aquí

esquema de perfil

inserte la descripción de la imagen aquí

8.4 Comprensión de carga de bibliotecas dinámicas y estáticas

8.4.1 Carga de bibliotecas estáticas

Para las bibliotecas estáticas, no es necesario cargar las bibliotecas estáticas , pero es necesario cargar los programas. Cuando la biblioteca estática está vinculada, el código (printf) en realidad se copia en el programa, por lo que cuando el programa se ejecuta más tarde, ya no depende de la biblioteca estática.

Una vez que haya muchos programas, la biblioteca estática copiará una gran cantidad de código repetitivo y los distribuirá a diferentes programas . A través del conocimiento del espacio de direcciones del proceso, sabemos que cuando la biblioteca estática copia el código en el programa, en realidad copia el código en el área de código. Por lo tanto, cuando el programa se ejecuta para formar el espacio de direcciones del proceso, el código en la biblioteca estática solo se puede asignar al área de código correspondiente del espacio de direcciones del proceso, y se debe acceder al código futuro a través de una ubicación de dirección relativamente definida .

inserte la descripción de la imagen aquí

8.4.2 Cargar biblioteca dinámica

Pasos específicos:

  1. Para el enlace dinámico, la dirección de un archivo .o específico en la biblioteca dinámica se almacena en el programa ejecutable. Al mismo tiempo, dado que los archivos redireccionables que componen la biblioteca dinámica son generados por el código fPIC independiente de la posición, esta dirección no es la dirección real del archivo o, sino el desplazamiento del archivo .o en la biblioteca dinámica ( igual que la tabla de funciones virtuales en C++)

  2. Luego está el proceso de ejecución del programa: el sistema operativo cargará el programa ejecutable en el disco en la memoria física, luego creará mm_struct, establecerá el mapeo de la tabla de páginas y luego comenzará a ejecutar el código.Cuando se ejecuta la función de biblioteca, el sistema operativo encuentra que el enlace de la función es la dirección de una biblioteca dinámica, y la dirección es una dirección externa, el sistema operativo suspenderá la ejecución del programa y comenzará a cargar la biblioteca dinámica;

  3. Carga de la biblioteca dinámica: el sistema operativo cargará la biblioteca dinámica en el disco en la memoria física y luego la asignará al área compartida del espacio de direcciones del proceso a través de la tabla de páginas, y luego determinará inmediatamente la dirección del biblioteca dinámica en el espacio de direcciones, es decir, la dirección inicial de la biblioteca dinámica, y luego continúa ejecutando el código;

  4. En este momento, el sistema operativo puede obtener la dirección del archivo .o de acuerdo con la dirección almacenada en la función de biblioteca, es decir, el desplazamiento del archivo .o en la biblioteca dinámica, más la dirección inicial de la biblioteca dinámica, y luego salte al área compartida Ejecute la función y salte hacia atrás después de la ejecución para continuar ejecutando el código detrás del segmento de código. Este es el proceso de carga de la biblioteca dinámica completa.

inserte la descripción de la imagen aquí

Por lo tanto, se puede ver que la biblioteca dinámica no es como la biblioteca estática. No necesita copiar una gran cantidad del mismo código. Todos los procesos pueden establecer una relación de mapeo para que múltiples programas puedan compartir el código en la biblioteca dinámica. .

8.4.3 Comprensión de direcciones en bibliotecas

Por ejemplo, en una escena de este tipo, está parado a 50 metros del patio de recreo y su amigo quiere acercarse a usted, puede decirle: 1. Estoy a 50 metros del patio de recreo 2. Estoy en el lado izquierdo del número del patio, a unos 20 metros de distancia; 1 es un direccionamiento absoluto; 2 es un direccionamiento relativo. Si un día la escuela amplía la parte delantera y trasera del patio de recreo, definitivamente cambiará para la dirección 1, pero seguirá siendo la misma para la dirección 2 (siempre y cuando la posición del árbol siga siendo la misma)

inserte la descripción de la imagen aquí

Use el ejemplo anterior para comparar bibliotecas dinámicas y estáticas

inserte la descripción de la imagen aquí

fPIC: genera un código independiente de la posición, que es esencialmente un desplazamiento

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/Ryujianli/article/details/129963955
Recomendado
Clasificación