IO básico bajo Linux (requerido para principiantes)

contenido

1. Uso y operación simples de las funciones de biblioteca relacionadas con la operación de archivos en lenguaje C

 二.stdout&&stderr&&stdin

3. Archivo de sistema IO

4. Descriptor de archivo fd

5. Reglas de asignación para descriptores de archivos

5. El principio de redirección


1. Uso y operación simples de las funciones de biblioteca relacionadas con la operación de archivos en lenguaje C

En el lenguaje c, se ha terminado el uso de las funciones de operación de archivos relacionadas con el lenguaje c. Para obtener más información, consulte el artículo del blogger:

Explicación detallada de las operaciones de archivo. Aquí simplemente usamos algunas funciones de biblioteca relacionadas con archivos fopen Por favor vea un fragmento de código a continuación:

 1 #include<stdio.h>
  2 int main()
  3 {
  4    FILE *fp=fopen("log.txt","w");//以写的方式打开这个文件,如果文件不存在会自动创建一个
  5    if(fp==NULL)
  6    {
  7      perror("fopen");
  8    }
  9     
 10    int cnt=5;
 11    const char *str="hello Linux\n";
 12    while(cnt--)
 13    {
 14      fputs(str,fp);//往刚打开的文件中写入数据
 15    }
 16    
 17    fclose(fp);//关闭文件
 18                                                                                                                                                 
 19  
 20 }

Aquí abrimos un archivo escribiendo, cuando estamos aprendiendo lenguaje C, si fopen abre el archivo escribiendo, si el archivo existe, se vaciará el contenido del archivo, si no existe, se creará en el actual ruta.archivo, ¿cuál es la ruta actual?

Ahora vamos a ejecutar este programa.

 Descubrimos que, de hecho, se creó un archivo log.txt en la ruta /home/ksy/BK/, entonces, ¿significa que la ruta actual es la ruta donde se encuentra el programa ejecutable? No se preocupe, usemos una función de biblioteca para leer archivos en lenguaje C:

 1 #include<stdio.h>
  2 int main()
  3 {
  4    FILE *fp=fopen("log.txt","r");//以写的方式打开这个文件,如果文件不存在会自动创建一个
  5    if(fp==NULL)
  6    {
  7      perror("fopen");
  8    }
  9    //由于上次已经往文件写了,所以我们直接可以读了 
 10    char buffer[128];
 11     while(fgets(buffer,sizeof(buffer),fp))
 12     {
 13       printf("%s",buffer);                                                                                                                      
 14     }
 15    return 0;
 16 
 17 }

Ejecute este programa:

 Descubrimos que la lectura fue exitosa Ahora analicemos si la llamada ruta actual es la ruta donde se encuentra el programa ejecutable. Para facilitar la prueba, eliminaremos log.txt en /home/ksy/BK/: use este código

 1 #include<stdio.h>  
  2 int main()  
  3 {  
  4    FILE *fp=fopen("log.txt","w");//以写的方式打开这个文件,如果文件不存在会自动创建一个  
  5    if(fp==NULL)  
  6    {  
  7      perror("fopen");  
  8    }  
  9    const char *str="这是一次测试\n";  
 10    int cnt=5;  
 11    while(cnt--)  
 12    {  
 13      fputs(str,fp);                                                                                                                             
 14    }  
 15    return 0;  
 16   
 17 }  
~                    

Generamos el ejecutable en /home/ksy/BK/, y ejecutamos este ejecutable en el directorio de inicio:

 Nos sorprendió descubrir que se generó un log.txt en la ruta /home/ksy, que muestra completamente que la ruta actual no es la ruta donde se encuentra el programa ejecutable. Pero cuando el programa ejecutable se convierte en un proceso, estamos en ese directorio y se crea en ese directorio.

Demostremos un poco el uso de dos funciones de biblioteca en lenguaje C:

 Aquí hay una pequeña explicación del uso de estas dos funciones:

fwrite: el primer parámetro es el contenido que desea escribir, el segundo parámetro es cuántos bytes escribir a la vez, el tercer parámetro es el número máximo de veces para escribir, el cuarto parámetro es escribir en la transmisión y el return El valor se refiere al número de escrituras reales. Vamos a demostrar con un simple fragmento de código:

    1 #include<stdio.h>
    2 #include<string.h>
    3 int main()
    4 {
    5    FILE *fp=fopen("log.txt","w");//以写的方式打开这个文件,如果文件不存在会自动创建一个
E>  6    const char str="hello Linux\n";
    7     int cnt=5;
    8     while(cnt--)
    9     {
   10       fwrite(str,strlen(str),1,fp);
   11     }
   12     return 0;                                                                                                                                 
   13 }

Ejecutemos este programa:

 Expliquemos el miedo:

El primer parámetro de fread es poner el contenido leído aquí, el segundo parámetro se refiere a cuántos bytes leer, el tercer parámetro se refiere a la cantidad máxima de veces que se lee, y el cuarto parámetro se refiere a dónde leer, devuelve el número. de veces que se leyó realmente el código de valor.

  1 #include<stdio.h>
  2 #include<string.h>
  3 int main()
  4 {
  5    FILE *fp=fopen("log.txt","r");//以写的方式打开这个文件,如果文件不存在会自动创建一个
  6    char buffer[128];
  7    while(fread(buffer,13,1,fp))
  8    {
  9      printf("%s",buffer);
 10    }
 11    return 0;                                                                                                                                    
 12 }
~      

resultado de la operación:

 二.stdout&&stderr&&stdin

A menudo escuchamos que todo en Linux es un archivo, es decir, cualquier cosa en Linux puede considerarse un archivo, por lo que, por supuesto, el teclado y el monitor también pueden considerarse un archivo. Podemos ver los datos en la pantalla porque escribimos datos en el "archivo de pantalla", y la computadora puede obtener los caracteres correspondientes cuando escribimos en el teclado porque la computadora lee los datos del "archivo de teclado".

Cuando se ejecuta un programa en lenguaje c (tenga en cuenta que el archivo debe abrirse cuando el programa se está ejecutando), se abren tres flujos de forma predeterminada, a saber, stdout (flujo de salida estándar), stdin (flujo de entrada estándar) y stderr (flujo de error estándar) . Los dispositivos correspondientes son: monitor, teclado, monitor. Vamos a comprobarlo a través del manual del hombre:

 A través del manual de man, podemos encontrar que todos son de tipo FILE*, que son punteros a archivos. Cuando se ejecuta nuestro programa c, el sistema abrirá estos tres flujos de entrada y salida.Después de abrir, podemos usar scanf e printf para realizar operaciones relacionadas en el teclado y la pantalla. Es decir, stdin, stdout y stderr son el mismo concepto que el puntero de archivo obtenido cuando abrimos un archivo.Imagine que cuando usamos la función fputs, establecemos el segundo parámetro en stdout.En este momento, la función fputs ¿Qué pasa con la visualización de los datos en el monitor en el medio? Vamos a verificarlo con un trozo de código:

1 #include<stdio.h>
  2 #include<string.h>
  3 int main()
  4 {
  5   const char*str="hello ksy\n";                                                                                                                 
  6   fputs(str,stdout);
  7    return 0;
  8 }
~
~

Ejecutemos este programa:

 Descubrimos que la cadena se imprimió con éxito en la pantalla. Por supuesto, no solo el lenguaje C tiene flujo de entrada estándar, flujo de salida estándar, flujo de error estándar, y también hay cin, cout, cerr en C++, otros lenguajes también tienen conceptos similares.

3. Archivo de sistema IO

La capa inferior del sistema operativo en realidad nos proporciona una interfaz de llamada del sistema de E/S de archivo. Algunas escritura, lectura, cierre y búsqueda tienen un conjunto de interfaces de llamada del sistema. Los diferentes idiomas se alinearán y encapsularán en un conjunto de bibliotecas para operar archivos en el idioma correspondiente.Las funciones no necesitan conocer la relación de llamada subyacente, lo que reduce el costo de aprendizaje de los usuarios.

Introducción a la interfaz de llamada del sistema: abrir, escribir, leer y cerrar:

1.abierto

Acción: abrir un archivo

El prototipo de la función es el siguiente:

int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mode);

El primer parámetro de open: pathname

El primer parámetro de open significa abrir o crear el archivo de destino. Las cosas a tener en cuenta aquí son:

1. Si se proporciona en forma de ruta, cuando sea necesario crear un archivo, se creará en la ruta que proporcione.

2. Si solo se proporciona el nombre del archivo, se creará en la ruta actual (encima de la ruta actual y se mencionará su significado).

El segundo parámetro de abierto: banderas

El segundo parámetro de open indica cómo se abre el archivo. Las opciones comunes son las siguientes:

 A la hora de abrir un archivo, podemos utilizar múltiples opciones separadas por |. Por ejemplo: queremos abrir un archivo solo para escritura y crear O_WRONLY|O_CREAT si el archivo no existe. Entonces, ¿qué son exactamente las banderas? En realidad es un número entero. Un número entero tiene 32 bits. Cada bit se usa como una opción. En la función correspondiente, verifique si ese bit es 1 para determinar si hemos pasado en esta opción. Entonces también significa que O_WRONLY corresponde a un número entero en el que sólo uno de los 32 bits es 1. ¿Es eso cierto? Usemos vim para abrir los archivos en el directorio /usr/include/asm-generic/fcntl.h y echemos un vistazo:

 Encontramos que estas opciones definidas por macros tienen en común que hay uno y solo un bit en su secuencia binaria es 1 (O_RDONLY) La secuencia binaria de la opción es todo 0, lo que indica que la opción O_RDONLY es la opción predeterminada). En la función abierta, use un número específico para juzgar y luego escriba solo la función específica.

El tercer parámetro de abierto:

El tercer parámetro es establecer el permiso para crear el archivo.En Linux, el archivo tiene permiso. Al abrir un archivo en modo de solo escritura, si el archivo no existe, debe crearse, pero debemos configurar los permisos del archivo al crearlo. Para preguntas sobre permisos, consulte los artículos relacionados del blogger. (Tenga en cuenta que cuando no se crea el archivo, el tercer parámetro se puede dejar en blanco)

El valor de retorno de abierto significa que el descriptor de archivo que abrimos no se abre y devuelve -1.   A continuación demostramos con un fragmento de código:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9   int fd1=open("./log1.txt",O_WRONLY|O_CREAT,0644);
 10   int fd2=open("./log2.txt",O_WRONLY|O_CREAT,0644);
 11   int fd3=open("./log3.txt",O_WRONLY|O_CREAT,0644);
 12   int fd4=open("./log4.txt",O_WRONLY|O_CREAT,0644);
 13   int fd5=open("./log5.txt",O_WRONLY|O_CREAT,0644);
 14   int fd6=open("./log6.txt",O_WRONLY|O_CREAT,0644);
 15   printf("%d\n",fd1);
 16   printf("%d\n",fd2);
 17   printf("%d\n",fd3);
 18   printf("%d\n",fd4);
 19   printf("%d\n",fd5);
 20   printf("%d\n",fd6);
 21   close(fd1);                                                                                                                                   
 22   close(fd2);
 23   close(fd3);
 24   close(fd4);
 25   close(fd5);
 26   close(fd6);
 27    return 0;
 28 }

Ejecutamos este programa:

 Encontramos que los descriptores de archivo comienzan en 3 y se incrementan continuamente. Si abrimos un archivo que no existe y no lo crea como de solo lectura, no podrá abrirse y devolverá -1.

El llamado descriptor de archivo es esencialmente un subíndice de una matriz de punteros.Cada subíndice en la matriz apunta a una estructura que almacena información de archivo abierto, por lo que podemos encontrar el archivo abierto correspondiente a través de fd (descriptor de archivo). En Linux, se abren tres archivos por defecto, entrada estándar (0) salida estándar (1) error estándar (2). Es por eso que abrimos un archivo y por qué los descriptores de archivo comienzan en 3.

 2.cerrar

Use cerrar para cerrar un archivo en el sistema. Prototipo de función correspondiente

int cerrar(int fd);

Para cerrar el archivo, solo necesita pasar el descriptor de archivo correspondiente. Si el archivo se cierra con éxito, devolverá 0 y devolverá -1 si falla.

3.escribir

La función de escritura se utiliza en la interfaz del sistema para escribir información relevante en el archivo. El prototipo de función de la función de escritura es el siguiente:

 El primer parámetro: el descriptor de archivo del archivo correspondiente. El segundo parámetro: lo que quieres escribir. El tercer parámetro: cuántos bytes quieres escribir. Valor devuelto: el número de bytes realmente escritos.

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9   int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);
 10   const char*str="hello word\n";
 11    int cnt=5;
 12    while(cnt--)
 13    {
 14      write(fd,str,strlen(str));
 15    }
 16     close(fd);
 17                                                                                                                                                 
 18    return 0;
 19 }
~

Ejecutamos este programa:

 4.leer

La función de lectura se utiliza en la interfaz del sistema para leer información de un archivo. El prototipo de función de la función de lectura es el siguiente:

ssize_t read(int fd, void *buf, size_t count);

El primer parámetro es el descriptor de archivo correspondiente al archivo, el segundo parámetro es poner el contenido leído aquí, el tercer parámetro lee unos pocos bytes y el valor de retorno es el número real de bytes leídos -1.

1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9      int fd=open("./log.txt",O_RDONLY);
 10       char ch;
 11       while(1)
 12       {
 13         ssize_t ret=read(fd,&ch,1);
 14         if(ret<=0)                                                                                                                              
 15         {
 16           break;
 17         }
 18         else
 19         {
 20           write(1,&ch,1);
 21         }
 22       }
 23 
 24 
 25     close(fd);
 26 
 27    return 0;
 28 }
~

4. Descriptor de archivo fd

Los procesos abren los archivos y un proceso puede abrir varios archivos. También hay una gran cantidad de procesos en el sistema, lo que significa que puede haber una gran cantidad de procesos en el sistema en cualquier momento. Cuando abrimos un archivo, necesitamos cargar los atributos relevantes del archivo en la memoria.El sistema operativo es el software que realiza el trabajo de administración. Entonces, ¿cómo necesita el sistema operativo administrar estos datos? Primero describa la organización. El sistema operativo creará una estructura struct_file para cada archivo abierto y lo organizará en una lista de doble enlace. La gestión de archivos abiertos del sistema operativo también se ha convertido en operaciones como agregar, eliminar, verificar y modificar listas vinculadas.

Entonces, ¿cómo sabe el proceso que yo abro esos archivos? Para distinguir por qué proceso se abre el expediente, también es necesario establecer la relación correspondiente entre el proceso y el expediente. Cuando estamos aprendiendo el proceso, cuando nuestro programa se ejecuta, cargará el código y los datos correspondientes en la memoria y creará estructuras de datos relacionadas (task_struct, mm_struct, tabla de página) para él. Y la relación de mapeo entre direcciones virtuales y direcciones físicas se establece a través de la tabla de páginas.

 De hecho, task_struct tiene un puntero a una estructura. Esta estructura se llama files_struct. Hay una matriz fd_array en la estructura, y el subíndice de esta matriz es lo que llamamos fd. Cuando el proceso abre el archivo log.txt, necesitamos al primero El archivo se carga desde el disco a la memoria para formar el archivo de estructura correspondiente, el archivo de estructura se conecta a la lista de archivos con doble enlace, y la primera dirección de la estructura se completa en la posición del subíndice 3 en el matriz fd_array, de modo que en la matriz fd_array El puntero con el subíndice 3 apunta al archivo de estructura y, finalmente, devuelve el descriptor de archivo del archivo al proceso de llamada.

 Entonces solo necesitamos usar el descriptor de archivo para obtener la información relevante del archivo abierto y realizar una serie de operaciones en él. Anteriormente verificamos que los descriptores de archivos comienzan en 3 de manera predeterminada, lo que significa que 0, 1 y 2 se abren de manera predeterminada. 0 representa el flujo de entrada estándar y el dispositivo de hardware correspondiente es el teclado; 1 representa el flujo de salida estándar y el dispositivo de hardware correspondiente es la pantalla; 2 representa el flujo de error estándar y el dispositivo de hardware correspondiente es la pantalla. Cuando se crea un proceso, el sistema operativo formará su propio archivo de estructura de acuerdo con el teclado, la pantalla y la pantalla, vinculará los tres archivos de estructura a la lista de archivos con doble enlace y completará las direcciones de los tres archivos de estructura en el matriz fd_array respectivamente Los subíndices son 0, 1 y 2, por lo que el flujo de entrada estándar, el flujo de salida estándar y el flujo de error estándar se abren de forma predeterminada.

5. Reglas de asignación para descriptores de archivos

Abrimos 6 archivos seguidos antes y descubrimos que los descriptores de archivo comienzan en 3 y tienen direcciones consecutivas. ¿Eso realmente comienza en 3 todo el tiempo? Veamos un trozo de código:

  1 #include<stdio.h>  
  2 #include<string.h>                                                                                                                              
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {              
  9         close(0);
 10      int fd1=open("./log1.txt",O_WRONLY|O_CREAT,0644);
 11      int fd2=open("./log2.txt",O_WRONLY|O_CREAT,0644);
 12      int fd3=open("./log3.txt",O_WRONLY|O_CREAT,0644);
 13      int fd4=open("./log4.txt",O_WRONLY|O_CREAT,0644);
 14      printf("%d\n",fd1);
 15      printf("%d\n",fd2);
 16      printf("%d\n",fd3);
 17      printf("%d\n",fd4);
 18      close(fd1);
 19      close(fd2);
 20      close(fd3);
 21      close(fd4);
 22 
 23    return 0;
 24 }

A continuación ejecutamos el programa:

 Descubrimos cómo fd comienza desde 0 y luego comienza desde 3 nuevamente. Ahora que estamos cerrando 2, veamos cuál será el resultado.

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {              
  9         close(0);
 10         close(2);                                                                                                                               
 11      int fd1=open("./log1.txt",O_WRONLY|O_CREAT,0644);
 12      int fd2=open("./log2.txt",O_WRONLY|O_CREAT,0644);
 13      int fd3=open("./log3.txt",O_WRONLY|O_CREAT,0644);
 14      int fd4=open("./log4.txt",O_WRONLY|O_CREAT,0644);
 15      printf("%d\n",fd1);
 16      printf("%d\n",fd2);
 17      printf("%d\n",fd3);
 18      printf("%d\n",fd4);
 19      close(fd1);
 20      close(fd2);
 21      close(fd3);
 22      close(fd4);
 23    
 24    return 0;
 25 }
~           
~           

resultado de la operación:

 Encontramos que 0 y 2 también se usaron. Ahora entendemos que las reglas de asignación para los descriptores de archivos comienzan desde el subíndice más pequeño no utilizado

5. El principio de redirección

Con la base anterior, podemos estudiar profundamente la redirección que hemos aprendido antes. Comprenda cuál es su principio. Primero, veamos el término de retargeting de entrada.

1. Introduzca el elemento de reinicio.

La redirección de salida que aprendimos anteriormente es redirigir los datos que debemos mostrar en la pantalla a otro archivo. Entonces, ¿cuál es su razón de ser?

Por ejemplo: si queremos que los datos que se deben enviar al "archivo de visualización" se envíen al archivo log.txt, podemos cerrar el archivo con el descriptor de archivo 1 antes de abrir el archivo log.txt, es decir, el "display file" "Cerrar, de modo que cuando abramos el archivo log.txt más tarde, el descriptor de archivo asignado sea 1.

  1 #include<stdio.h>  
  2 #include<string.h>  
  3 #include<unistd.h>  
  4 #include<sys/types.h>  
  5 #include<sys/stat.h>  
  6 #include<fcntl.h>  
  7 int main()  
  8 {                
  9        close(1);  
 10      int fd=open("./log1.txt",O_WRONLY|O_CREAT,0644);  
 11      printf("hello ksy\n");
 12      printf("hello ksy\n");
 13      printf("hello ksy\n");
 14      printf("hello ksy\n");
 15      close(fd);
 16 
 17    return 0;
 18 }                                                                                                                                               
~          

resultado de la operación:

 Descubrimos que los datos se imprimieron en log.txt. Explícalo aquí:

1. printf imprime datos en stout de forma predeterminada, y stdout también es un puntero FILE*, que apunta a un cuerpo de resultado FILE, que encapsula un número entero, que es un descriptor de archivo, y stdout apunta a la estructura FILE. es 1, por lo que printf envía datos al archivo cuyo descriptor de archivo es 1.

2. Los datos de salida en el lenguaje c no se escriben inmediatamente en el sistema operativo, sino que se almacenan temporalmente en el búfer del lenguaje c y se descargan en el búfer cuando se dan las condiciones.

2. Agregar redirección

La diferencia entre la redirección para agregar y la redirección de salida es que la redirección para agregar no sobrescribe los datos.

 Echemos un vistazo al principio. De hecho, solo hay una opción O_APPEND más que la redirección de salida.

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9        close(1);
 10      int fd=open("./log1.txt",O_WRONLY|O_CREAT|O_APPEND,0644);//追加重定向和输出重定向的区别就只是多了一个O_APPEND选项                          
 11      printf("hello ksy\n");
 12      printf("hello ksy\n");
 13      printf("hello ksy\n");
 14      printf("hello ksy\n");
 15      close(fd);
 16 
 17    return 0;
 18 }

 3. Redirección de entrada

La redirección de entrada significa que deberíamos leer datos de un teclado, pero ahora se redirige para leer datos de otro archivo.

 Por ejemplo, nuestra función scanf lee datos de entrada estándar, y ahora dejamos que lea datos de log1.txt, cerramos (0) antes de que scanf lea datos. De esta manera, el archivo de teclado se cierra, como log1. El descriptor de archivo de txt es 0

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9        close(0);
 10      int fd=open("./log1.txt",O_RDONLY);//追加重定向和输出重定向的区别就只是多了一个O_APPEND选项:
 11     char buffer[128];
 12      while(~scanf("%s",buffer))
 13      {
 14        printf("%s\n",buffer);                                                                                                                   
 15      }
 16      close(fd);
 17    return 0;
 18 }
~

resultado de la operación:

El principio es similar a stdout, por lo que no hablaré de eso aquí. 

 Considere una pregunta: el flujo de salida estándar y el flujo de error estándar corresponden a la pantalla, ¿cuál es la diferencia entre ellos?

Vamos a verificarlo con un trozo de código:

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9    fprintf(stdout,"hello stdout");
 10    fprintf(stderr,"hello stderr");
 11                                                                                                                                                 
 12    return 0;
 13 }

 Descubrimos que solo las reubicaciones impresas en stdout iban a log1.txt. De hecho, cuando usamos la redirección, el flujo de salida estándar con el descriptor de archivo 1 se redirige y el flujo de error estándar con el descriptor de archivo 2 no se redirige. Esta es la diferencia entre los dos

llamada al sistema dup2

Descubrimos que solo podemos cerrar la redirección de salida correspondiente y la redirección de salida de la práctica del descriptor de archivo correspondiente a través del cierre, entonces, ¿no podemos cerrarlo? Para completar la redirección, solo necesitamos copiar los elementos en la matriz fd_array. Por ejemplo, si copiamos el contenido de fd_array[3] a fd_array[1], porque la salida estándar en lenguaje C es enviar datos al archivo cuyo descriptor de archivo es 1, redirigimos la salida al archivo log.txt. En Linux, contamos con esta llamada al sistema:

 Función: dup2 copiará el contenido de fd_array[oldfd] a fd_array[newfd]. Valor de retorno de la función: se devuelve 0 si la llamada es exitosa y se devuelve -1 si falla.

Al usarlo, debe prestar atención:

  1. Si oldfd no es un descriptor de archivo válido, la llamada dup2 falla y el archivo con el descriptor de archivo newfd no se cierra en este momento.
  2. Si oldfd es un descriptor de archivo válido, pero newfd y oldfd tienen el mismo valor, dup2 no hace nada y devuelve newfd.

Demostremos la redirección de salida anterior a través de dup2:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/stat.h>
  4 #include<unistd.h>
  5 #include<fcntl.h>                                                                                                                               
  6 int main()
  7 {
  8   int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);
  9    dup2(fd,1);
 10  printf("hello world\n");
 11  printf("hello world\n");
 12 
 13 }

resultado de la operación:

ARCHIVO en lenguaje c

Debido a que la función de la biblioteca es la encapsulación de la interfaz de llamadas al sistema, el acceso al archivo se realiza esencialmente a través del descriptor de archivo fd, por lo que la estructura FILE en la biblioteca C debe encapsular el descriptor de archivo fd. Podemos usar vim para abrir el archivo usr/include/stdio.h para ver ARCHIVO

 El contenido completo es el siguiente:


struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */

 char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 struct _IO_marker *_markers;
 struct _IO_FILE *_chain;
 int _fileno; //封装的文件描述符
#if 0
 int _blksize;
#else
 int _flags2;
#endif
 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 /* char* _save_gptr; char* _save_egptr; */
 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

Del código fuente de FILE, encontramos que la estructura FILE encapsula fd, que es _fileno adentro. No es difícil ver que también vemos amortiguadores en el interior. El búfer aquí se refiere al búfer en el lenguaje c. Las estrategias de actualización del búfer son las siguientes:

1. Sin almacenamiento en búfer: sin almacenamiento en búfer

2. Almacenamiento en búfer de línea: cuando se encuentra /n, los datos correspondientes impresos en la pantalla se actualizan utilizando esta estrategia

3. Almacenamiento en búfer completo: el búfer se actualiza cuando el búfer está lleno, o el búfer se actualiza cuando finaliza el proceso.Esta estrategia se usa para archivos.

Entonces, debemos entender que la redirección cambiará la estrategia de vaciado del búfer. Por ejemplo, la redirección de salida, la estrategia original para enviar a la pantalla es el almacenamiento en búfer de línea, y ahora la estrategia para enviarlo a un archivo es el almacenamiento en búfer completo: echemos un vistazo a un ejemplo:

  1 #include<stdio.h>
    2 #include<sys/types.h>
    3 #include<sys/stat.h>
    4 #include<unistd.h>
    5 #include<fcntl.h>
    6 #include<string.h>
    7 int main()
    8 {
    9   close(1);
   10   int fd=open("./log.txt",O_WRONLY|O_CREAT,0644);
   11   if(fd<0)
   12   {
   13     perror("open");
   14     return -2;
   15   }
   16   const char*str1="hello write\n";
   17   const char*str2="hello printf\n";
   18   const char*str3="hello fwrite\n";
   19   write(fd,str1,strlen(str1));
W> 20   printf(str2);
   21   fwrite(str3,strlen(str3),1,stdout);                                                                                                         
   22   fork();
   23   fflush(stdout);//刷新
   24   close(fd);
   25   return 0;
   26 }
  ~

 Descubrimos por qué solo la llamada al sistema fwrite imprime solo una vez, mientras que tanto printf como fwrite imprimen dos veces. ¿Que es esto? . Esto se debe a que para la escritura de la llamada al sistema, se escribe directamente en el sistema operativo, mientras que printf y fwrite se escribirán en el búfer proporcionado por el lenguaje C y no se descargarán en el sistema operativo inmediatamente. proceso Cuando la copia en escritura se realiza con el proceso principal, los datos en el búfer del proceso principal serán copiados por el proceso secundario, y los procesos principal e secundario actualizarán los suyos. Entonces descubrimos que la función de biblioteca se imprime dos veces.

Supongo que te gusta

Origin blog.csdn.net/qq_56999918/article/details/124221285
Recomendado
Clasificación