Basic IO sous Linux (nécessaire pour les débutants)

teneur

1. Utilisation et fonctionnement simples des fonctions de la bibliothèque liées au fonctionnement des fichiers en langage C

 二.stdout&&stderr&&stdin

3. Fichier système IO

4. Descripteur de fichier fd

5. Règles d'attribution des descripteurs de fichiers

5. Le principe de redirection


1. Utilisation et fonctionnement simples des fonctions de la bibliothèque liées au fonctionnement des fichiers en langage C

Dans le langage c, l'utilisation des fonctions d'opération de fichier liées au langage c a été supprimée. Pour plus de détails, veuillez vous référer à l'article du blogueur :

Explication détaillée des opérations sur les fichiers Ici, nous utilisons simplement quelques fonctions de bibliothèque liées aux fichiers fopen. Veuillez voir un morceau de code ci-dessous :

 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 }

Ici nous ouvrons un fichier en écrivant. Lorsque nous apprenons le langage C, si fopen ouvre le fichier en écrivant, si le fichier existe, le contenu du fichier sera vidé. S'il n'existe pas, il sera créé dans le fichier courant chemin.fichier, quel est le chemin actuel ?

Exécutons maintenant ce programme.

 Nous avons constaté qu'un fichier log.txt a bien été créé sous le chemin /home/ksy/BK/, cela signifie-t-il donc que le chemin actuel est le chemin où se trouve le programme exécutable ? Ne vous inquiétez pas, utilisons une fonction de bibliothèque pour lire les fichiers en langage 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 }

Exécutez ce programme :

 Nous avons constaté que la lecture était réussie. Voyons maintenant si le chemin dit courant est le chemin où se trouve le programme exécutable ? Pour faciliter les tests, nous supprimerons log.txt sous /home/ksy/BK/ : utilisez ce code

 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 }  
~                    

On génère l'exécutable dans /home/ksy/BK/, et on lance cet exécutable dans le répertoire home :

 Nous avons été surpris de constater qu'un log.txt a été généré sous le chemin /home/ksy, ce qui montre bien que le chemin actuel n'est pas le chemin où se trouve le programme exécutable. Mais lorsque le programme exécutable devient un processus, nous sommes dans ce répertoire et il est créé dans ce répertoire.

Démontrons un peu l'utilisation de deux fonctions de bibliothèque en langage C :

 Voici une petite explication de l'utilisation de ces deux fonctions :

fwrite : le premier paramètre est le contenu que vous voulez écrire, le deuxième paramètre est le nombre d'octets à écrire à la fois, le troisième paramètre est le nombre maximum de fois à écrire, le quatrième paramètre est l'écriture dans le flux, et le return La valeur fait référence au nombre d'écritures réelles. Démontrons avec un simple morceau de code :

    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 }

Exécutons ce programme :

 Expliquons-nous:

Le premier paramètre de fread est de mettre le contenu lu ici, le deuxième paramètre fait référence au nombre d'octets à lire, le troisième paramètre fait référence au nombre maximum de fois à lire, et le quatrième paramètre fait référence à l'origine de la lecture, renvoie le nombre de fois où le code de valeur a été réellement lu.

  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 }
~      

résultat de l'opération :

 二.stdout&&stderr&&stdin

Nous entendons souvent dire que tout sous Linux est un fichier, c'est-à-dire que tout sous Linux peut être considéré comme un fichier, donc bien sûr le clavier et le moniteur peuvent également être considérés comme un fichier. Nous pouvons voir les données sur l'écran car nous écrivons des données dans le "fichier d'affichage", et l'ordinateur peut obtenir les caractères correspondants lorsque nous tapons sur le clavier car l'ordinateur lit les données du "fichier clavier".

Lorsqu'un programme en langage c est en cours d'exécution (notez que le fichier doit être ouvert lorsque le programme est en cours d'exécution), trois flux sont ouverts par défaut, à savoir stdout (flux de sortie standard), stdin (flux d'entrée standard) et stderr (flux d'erreur standard) . Les appareils correspondants sont : moniteur, clavier, moniteur. Vérifions-le à travers le manuel de l'homme:

 Grâce au manuel de l'homme, nous pouvons constater qu'ils sont tous de type FILE *, qui sont des pointeurs de fichiers. Lorsque notre programme c s'exécute, le système ouvre ces trois flux d'entrée et de sortie. Après l'ouverture, nous pouvons utiliser scanf et printf pour effectuer des opérations connexes sur le clavier et l'affichage. C'est-à-dire que stdin, stdout et stderr sont le même concept que le pointeur de fichier obtenu lorsque nous ouvrons un fichier.Imaginez que lorsque nous utilisons la fonction fputs, nous définissons le deuxième paramètre sur stdout.A ce moment, la fonction fputs va pas Qu'en est-il de l'affichage des données sur le moniteur entre les deux ? Vérifions-le avec un morceau de code :

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 }
~
~

Exécutons ce programme :

 Nous avons constaté que la chaîne a été imprimée avec succès sur l'écran. Bien sûr, non seulement le langage C a un flux d'entrée standard, un flux de sortie standard, un flux d'erreur standard, et il y a aussi cin, cout, cerr en C++ D'autres langages ont également des concepts similaires.

3. Fichier système IO

La couche inférieure du système d'exploitation nous fournit en fait une interface d'appel système d'E/S de fichiers.Certaines écritures, lectures, fermetures et recherches ont un ensemble d'interfaces d'appel système.Différentes langues seront alignées et encapsulées dans un ensemble de bibliothèques pour l'exploitation fichiers dans la langue correspondante. Les fonctions n'ont pas besoin de connaître la relation d'appel sous-jacente, ce qui réduit le coût d'apprentissage des utilisateurs.

Présentation de l'interface d'appel système : ouvrir, écrire, lire et fermer :

1.ouvrir

Action : ouvrir un fichier

Le prototype de fonction est le suivant :

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

Le premier paramètre de open : pathname

Le premier paramètre de open signifie ouvrir ou créer le fichier cible. Les choses à noter ici sont :

1. S'il est donné sous la forme d'un chemin, lorsqu'un fichier doit être créé, il sera créé sous le chemin que vous fournissez.

2. Si seul le nom du fichier est donné, il sera créé sous le chemin courant (au-dessus du chemin courant et mentionnant sa signification).

Le second paramètre de open : flags

Le deuxième paramètre de open indique comment le fichier est ouvert. Les options courantes sont les suivantes :

 Lors de l'ouverture d'un fichier, nous pouvons utiliser plusieurs options séparées par |. Par exemple : nous souhaitons ouvrir un fichier en écriture uniquement et créer O_WRONLY|O_CREAT si le fichier n'existe pas. Alors, que sont exactement les drapeaux ? En fait, c'est un entier. Un entier a 32 bits. Chaque bit est utilisé comme une option. Dans la fonction correspondante, vérifiez si ce bit vaut 1 pour déterminer si nous avons passé dans cette option. Ensuite, cela signifie également que O_WRONLY correspond en un entier dont un seul des 32 bits vaut 1. Est-ce vrai ? Utilisons vim pour ouvrir les fichiers dans le répertoire /usr/include/asm-generic/fcntl.h et regardons :

 Nous avons constaté que ces options définies par macro ont en commun qu'il y a un et un seul bit dans leur séquence binaire est 1 (O_RDONLY) La séquence binaire de l'option est tout 0, indiquant que l'option O_RDONLY est l'option par défaut). Dans la fonction ouverte, utilisez un nombre spécifique pour juger, puis écrivez uniquement la fonction spécifique.

Le troisième paramètre de open :

Le troisième paramètre est de définir l'autorisation de créer le fichier. Sous Linux, le fichier a l'autorisation. Lors de l'ouverture d'un fichier en mode écriture seule, si le fichier n'existe pas, il doit être créé, mais nous devons définir les autorisations du fichier lors de sa création. Pour les questions sur les autorisations, veuillez vous référer aux articles connexes du blogueur. (Notez que lorsque le fichier n'est pas créé, le troisième paramètre peut être laissé vide)

La valeur de retour de open signifie que le descripteur de fichier que nous ouvrons ne parvient pas à s'ouvrir et renvoie -1.   Ci-dessous, nous démontrons avec un morceau de code:

  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 }

Nous exécutons ce programme :

 Nous avons constaté que les descripteurs de fichiers commencent à 3 et sont continuellement incrémentés. Si nous ouvrons un fichier qui n'existe pas et ne le créons pas en lecture seule, il ne parviendra pas à s'ouvrir et renverra -1.

Le soi-disant descripteur de fichier est essentiellement un indice d'un tableau de pointeurs. Chaque indice du tableau pointe vers une structure qui stocke les informations sur les fichiers ouverts, de sorte que nous pouvons trouver le fichier ouvert correspondant via fd (descripteur de fichier). Sous Linux, trois fichiers sont ouverts par défaut, entrée standard (0) sortie standard (1) erreur standard (2). C'est pourquoi nous ouvrons un fichier et pourquoi les descripteurs de fichiers commencent à 3.

 2.fermer

Utilisez close pour fermer un fichier dans le système. Prototype de fonction correspondant

int close(int fd);

Pour fermer le fichier, il vous suffit de passer le descripteur de fichier correspondant. Si le fichier est fermé avec succès, il renverra 0 et renverra -1 en cas d'échec.

3. écrire

La fonction d'écriture est utilisée dans l'interface système pour écrire des informations pertinentes dans le fichier. Le prototype de la fonction d'écriture est le suivant :

 Le premier paramètre : le descripteur de fichier du fichier correspondant. Le deuxième paramètre : ce que vous voulez écrire. Le troisième paramètre : combien d'octets vous voulez écrire. Valeur de retour : nombre d'octets réellement écrits.

  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 }
~

Nous exécutons ce programme :

 4.lire

La fonction de lecture est utilisée dans l'interface système pour lire des informations à partir d'un fichier. Le prototype de la fonction de lecture est le suivant :

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

Le premier paramètre est le descripteur de fichier correspondant au fichier, le deuxième paramètre est de mettre le contenu lu ici, le troisième paramètre lit quelques octets, et la valeur de retour est le nombre réel d'octets lus. -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. Descripteur de fichier fd

Les fichiers sont ouverts par des processus et un processus peut ouvrir plusieurs fichiers. Il existe également un grand nombre de processus dans le système, ce qui signifie qu'il peut y avoir un grand nombre de processus dans le système à tout moment. Lorsque nous ouvrons un fichier, nous devons charger les attributs pertinents du fichier dans la mémoire.Le système d'exploitation est le logiciel qui effectue le travail de gestion. Alors, comment le système d'exploitation doit-il gérer ces données ? Décrivez d'abord l'organisation. Le système d'exploitation créera une structure struct_file pour chaque fichier ouvert et l'organisera dans une liste à double lien. La gestion des fichiers ouverts du système d'exploitation est également devenue des opérations telles que l'ajout, la suppression, la vérification et la modification des listes liées.

Alors, comment le processus sait-il que ces fichiers sont ouverts par moi ? Afin de distinguer par quel processus le fichier est ouvert, il est également nécessaire d'établir la relation correspondante entre le processus et le fichier. Lorsque nous apprenons le processus, lorsque notre programme s'exécute, il charge le code et les données correspondants en mémoire et crée des structures de données associées (task_struct, mm_struct, page table) pour cela. Et la relation de mappage entre les adresses virtuelles et les adresses physiques est établie via la table des pages.

 En fait, task_struct a un pointeur vers une structure. Cette structure est appelée files_struct. Il y a un tableau fd_array dans la structure, et l'indice de ce tableau est ce que nous appelons fd. Lorsque le processus ouvre le fichier log.txt, nous avons besoin au premier Le fichier est chargé du disque dans la mémoire pour former le fichier struct correspondant, le fichier struct est connecté à la liste à double liaison de fichiers et la première adresse de la structure est remplie à la position de l'indice 3 dans le tableau fd_array, de sorte que dans le tableau fd_array Le pointeur avec l'indice 3 pointe vers le fichier struct et renvoie enfin le descripteur de fichier du fichier au processus appelant.

 Il nous suffit donc d'utiliser le descripteur de fichier pour obtenir les informations pertinentes du fichier ouvert et effectuer une série d'opérations dessus. Nous avons précédemment vérifié que les descripteurs de fichiers commencent à 3 par défaut, ce qui signifie que 0, 1 et 2 sont ouverts par défaut. 0 représente le flux d'entrée standard et le périphérique matériel correspondant est le clavier ; 1 représente le flux de sortie standard et le périphérique matériel correspondant est l'affichage ; 2 représente le flux d'erreur standard et le périphérique matériel correspondant est l'affichage. Lorsqu'un processus est créé, le système d'exploitation forme son propre fichier de structure en fonction du clavier, de l'affichage et de l'affichage, lie les trois fichiers de structure à la liste de fichiers à double liaison et remplit les adresses des trois fichiers de structure dans le tableau fd_array respectivement. Les indices sont 0, 1 et 2, donc le flux d'entrée standard, le flux de sortie standard et le flux d'erreur standard sont ouverts par défaut.

5. Règles d'attribution des descripteurs de fichiers

Nous avons déjà ouvert 6 fichiers à la suite et nous avons constaté que les descripteurs de fichiers commencent à 3 et ont des adresses consécutives. Est-ce que ça commence vraiment à 3h tout le temps ? Regardons un morceau de code :

  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 }

Ci-dessous, nous exécutons le programme :

 Nous avons découvert comment fd commence à partir de 0, puis recommence à partir de 3. Maintenant que nous fermons 2, voyons quel sera le résultat.

  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 }
~           
~           

résultat de l'opération :

 Nous avons constaté que 0 et 2 étaient également utilisés. Maintenant, nous comprenons que les règles d'allocation pour les descripteurs de fichiers commencent à partir du plus petit indice inutilisé

5. Le principe de redirection

Avec la base ci-dessus, nous pouvons étudier en profondeur la redirection que nous avons apprise auparavant. Comprenez quel est son principe.D'abord, regardons le terme de retargeting d'entrée.

1. Saisissez l'élément de réinitialisation.

La redirection de sortie que nous avons apprise précédemment consiste à rediriger les données que nous devrions afficher sur l'écran vers un autre fichier. Quelle est donc sa justification ?

Par exemple : si nous voulons que les données qui doivent être sorties dans le "fichier d'affichage" soient sorties dans le fichier log.txt, nous pouvons fermer le fichier avec le descripteur de fichier 1 avant d'ouvrir le fichier log.txt, c'est-à-dire le "fichier d'affichage" "Fermer, de sorte que lorsque nous ouvrirons le fichier log.txt plus tard, le descripteur de fichier attribué est 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 }                                                                                                                                               
~          

résultat de l'opération :

 Nous avons constaté que les données étaient effectivement imprimées dans log.txt. Expliquez-le ici :

1. printf imprime les données sur stout par défaut, et stdout est également un pointeur FILE*, qui pointe vers un corps de résultat FILE, qui encapsule un entier, qui est un descripteur de fichier, et stdout pointe vers la structure FILE. est 1, donc printf génère des données dans le fichier dont le descripteur de fichier est 1.

2. Les données de sortie en langage c ne sont pas immédiatement écrites dans le système d'exploitation, mais sont temporairement stockées dans le tampon du langage c et sont vidées dans le tampon lorsque les conditions se présentent.

2. Ajouter la redirection

La différence entre la redirection d'ajout et la redirection de sortie est que la redirection d'ajout n'écrase pas les données.

 Voyons le principe, en fait, il n'y a qu'une option O_APPEND de plus que la redirection de sortie.

  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. Redirection d'entrée

La redirection d'entrée signifie que nous devrions lire les données d'un clavier, mais maintenant il est redirigé pour lire les données d'un autre fichier.

 Par exemple, notre fonction scanf lit les données de l'entrée standard, et maintenant nous la laissons lire les données de log1.txt, nous fermons (0) avant que scanf ne lise les données. De cette façon, le fichier du clavier est fermé, comme log1. Le descripteur de fichier de txt est 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 }
~

résultat de l'opération :

Le principe est similaire à stdout, donc je n'en parlerai pas ici 

 Considérez une question : le flux de sortie standard et le flux d'erreur standard correspondent à l'affichage, quelle est la différence entre eux ?

Vérifions-le avec un morceau de code :

  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 }

 Nous avons constaté que seules les relocalisations imprimées sur stdout allaient dans log1.txt. En fait, lorsque nous utilisons la redirection, le flux de sortie standard avec le descripteur de fichier 1 est redirigé, et le flux d'erreur standard avec le descripteur de fichier 2 n'est pas redirigé. C'est la différence entre les deux

appel système dup2

Nous avons constaté que nous ne pouvons fermer que la redirection de sortie correspondante et la redirection de sortie de la pratique de descripteur de fichier correspondante via la fermeture, alors ne pouvons-nous pas la fermer ? Pour terminer la redirection, nous avons juste besoin de copier les éléments dans le tableau fd_array. Par exemple, si nous copions le contenu de fd_array[3] vers fd_array[1], car stdout en langage C consiste à sortir des données vers le fichier dont le descripteur de fichier est 1, alors nous redirigeons la sortie vers le fichier log.txt. Sous Linux, nous avons cet appel système :

 Fonction : dup2 copiera le contenu de fd_array[oldfd] dans fd_array[newfd]. Valeur de retour de la fonction : 0 est renvoyé si l'appel réussit et -1 est renvoyé en cas d'échec.

Lors de son utilisation, vous devez faire attention:

  1. Si oldfd n'est pas un descripteur de fichier valide, l'appel dup2 échoue et le fichier avec le descripteur de fichier newfd n'est pas fermé pour le moment.
  2. Si oldfd est un descripteur de fichier valide, mais que newfd et oldfd ont la même valeur, dup2 ne fait rien et renvoie newfd.

Démontrons la redirection de sortie précédente via 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 }

résultat de l'opération :

FICHIER en langage c

Étant donné que la fonction de la bibliothèque est l'encapsulation de l'interface d'appel système, l'accès au fichier est essentiellement accessible via le descripteur de fichier fd, de sorte que la structure FILE de la bibliothèque C doit encapsuler le descripteur de fichier fd. Nous pouvons utiliser vim pour ouvrir le fichier usr/include/stdio.h pour afficher FILE

 Le contenu complet est le suivant :


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
};

À partir du code source de FILE, nous avons trouvé que la structure FILE encapsule fd, qui est _fileno à l'intérieur. Il n'est pas difficile de voir que nous voyons aussi des tampons à l'intérieur. Le tampon fait ici référence au tampon du langage c. Les stratégies de rafraîchissement de la mémoire tampon sont les suivantes :

1. Pas de mise en mémoire tampon : pas de mise en mémoire tampon

2. Mise en mémoire tampon de ligne : lorsque /n est rencontré, les données correspondantes imprimées à l'écran sont actualisées à l'aide de cette stratégie

3. Mise en mémoire tampon complète : la mémoire tampon est actualisée lorsqu'elle est pleine, ou la mémoire tampon est actualisée lorsque le processus se termine. Cette stratégie est utilisée pour les fichiers.

Nous devons donc comprendre que la redirection modifiera la stratégie de vidage du tampon. Par exemple, la redirection de sortie, la stratégie d'origine pour la sortie sur l'écran est la mise en mémoire tampon de ligne, et maintenant la stratégie pour la sortir dans un fichier est la mise en mémoire tampon complète : examinons un exemple :

  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 }
  ~

 Nous avons découvert pourquoi seul l'appel système fwrite n'imprime qu'une seule fois, alors que printf et fwrite impriment deux fois ? qu'est-ce que c'est? . En effet, pour l'appel système write, il est écrit directement dans le système d'exploitation, tandis que printf et fwrite seront écrits dans le tampon fourni par le langage C et ne seront pas immédiatement vidés dans le système d'exploitation. Après fork(), l'enfant process Lorsque la copie sur écriture est effectuée avec le processus parent, les données dans le tampon du processus parent seront copiées par le processus enfant, et les processus parent et enfant rafraîchiront les leurs. Nous avons donc constaté que la fonction de bibliothèque imprime deux fois.

Je suppose que tu aimes

Origine blog.csdn.net/qq_56999918/article/details/124221285
conseillé
Classement