[Linux] Compréhension approfondie du tampon

Table des matières

qu'est-ce que le tampon

Pourquoi y a-t-il un tampon

stratégie de vidage du tampon

où est le tampon

 Concevoir manuellement un tampon userland


qu'est-ce que le tampon

Un tampon est essentiellement une zone de mémoire utilisée pour stocker des données temporaires. Les tampons sont utilisés dans une grande variété de tâches informatiques, y compris les opérations d'entrée/sortie, les communications réseau, le traitement d'image, le traitement audio, etc.

Qui a fourni cette zone mémoire et où se trouve le tampon ? Vous pouvez continuer à regarder vers le bas.

Ici, pour donner la réponse en premier, elle est fournie par la bibliothèque standard C .

Pourquoi y a-t-il un tampon

La mémoire tampon est utilisée pour résoudre le problème de la vitesse de transmission des données incompatible ou instable, et pour améliorer l'efficacité du traitement des données.

Lors de la lecture d'une grande quantité de données à partir du disque dur, le transfert des données directement vers la mémoire peut entraîner une inadéquation des vitesses de lecture et d'écriture (la mémoire est rapide et le disque dur est lent à lire, ce qui est relativement parlant), ce qui entraîne dans les goulots d'étranglement des performances. Afin d'atténuer ce problème, un tampon peut être introduit pour lire d'abord une partie des données dans le tampon , puis lire progressivement les données du tampon dans la mémoire pour équilibrer la vitesse de transmission des données.

Voici un bon exemple pour expliquer :

Par exemple, vous et votre ami êtes dans deux universités différentes, avec une différence d'environ 500 kilomètres. Un jour, vous souhaitez envoyer des livres à votre ami. À ce moment, vous pouvez choisir de faire du vélo pour livrer ces livres en personne. . Le cadeau est léger et l'affection est lourde. Eh bien, avec une pause au milieu, et en raison de la lenteur de la vitesse, il a fallu environ une semaine pour arriver, et après l'avoir envoyé, je suis retourné à mon école, qui a pris une autre semaine, et il a fallu un total de deux semaines pour achever le travail .

En supposant que vous soyez intelligent à ce moment-là, car il est si lent, vous pouvez prendre le train à grande vitesse pour l'envoyer directement, mais l'aller-retour coûtera plus de 500, ce qui est plus que la valeur de ces livres, c'est-à-dire , le coût est trop élevé .

Les livres ci-dessus peuvent être considérés comme des ressources, et ce mode est appelé mode d'écriture continue.

À ce moment-là, vous pensiez pouvoir envoyer ces livres par exprès, le prix est bon marché et il arrivera dans deux ou trois jours, ce qui est très abordable, alors vous avez remis ces livres à SF Express. Laissez-moi vous dire que je j'ai reçu ces livres, puis j'ai réussi à remettre les ressources à l'autre partie. Le rôle joué par SF Express ici est la zone tampon. 

SF Express ne livre pas votre courrier immédiatement après avoir reçu votre courrier, mais attend que la quantité soit suffisante, puis recommence à expédier, ce qui équivaut à une stratégie de rafraîchissement de la mémoire tampon.

stratégie de vidage du tampon

Il existe trois principales stratégies de rafraîchissement :

1. Actualiser maintenant

2. Rafraîchissement de ligne ( mise en mémoire tampon de ligne), rencontre \n rafraîchissement

3. Rafraîchissement complet (tampon complet), ce qui signifie que les données d'entrée ou de sortie sont entièrement stockées dans le tampon, puis transmises ou traitées.

Il y a bien sûr des cas particuliers :

1. Rafraîchissement forcé par l'utilisateur (fflush)

2. Le processus se termine

Lorsque vous rencontrez les deux situations ci-dessus, les données dans la mémoire tampon doivent être actualisées immédiatement au lieu de continuer à attendre selon la stratégie d'actualisation précédente.

Donc stratégie tampon = cas général + cas particulier.


D'une manière générale, le fichier de périphérique pour la mise en mémoire tampon de ligne --- affichage

Fichiers de périphérique entièrement mis en mémoire tampon --- fichiers de disque

Mais tous les périphériques ont toujours tendance à être entièrement tamponnés --> le tampon est plein puis actualisé --> moins d'opérations d'E/S sont nécessaires --> moins d'accès aux périphériques (ce qui équivaut à améliorer l'efficacité de l'ensemble de la machine).

Certains étudiants peuvent avoir des doutes, comme 10 lignes de données, chaque ligne a 100 octets, bien que les 10 lignes soient rafraîchies ensemble à la fin, un seul accès périphérique est effectué, mais la quantité de données est importante, 1000 octets, et le rafraichissement ligne par ligne Bien que le rafraîchissement soit rafraîchi 10 fois, la quantité de données est à chaque fois faible, alors pourquoi vaut-il mieux avoir le moins d'accès périphériques possible ?

En effet, lors des E/S avec des périphériques externes, la taille des données n'est pas la principale contradiction, et le processus de préparation des E/S avec des périphériques prend le plus de temps.

Par exemple, lorsque vous empruntez de l'argent à d'autres, le processus de communication prend souvent beaucoup de temps, mais le processus de transfert ne prend que quelques secondes, pour la même raison.

Ensuite, nous pouvons passer directement à la mise en mémoire tampon complète, n'est-ce pas ? N'est-ce pas efficace ? Quel type de mise en mémoire tampon de ligne est nécessaire ?

En fait, ces stratégies sont toutes des compromis basés sur la situation réelle :

Par exemple, la mise en mémoire tampon des lignes est destinée à l'affichage et doit être vue par les utilisateurs. D'une part, l'efficacité doit être prise en compte et, d'autre part, l'expérience utilisateur doit également être prise en charge.

Habituellement, certains fichiers texte que nous ouvrons sont entièrement mis en mémoire tampon et ils seront enregistrés une fois que l'utilisateur aura fini d'écrire .

Avec ces tampons et ces stratégies, l'efficacité du traitement des données peut être améliorée.

où est le tampon

Pour résoudre ce problème, nous pouvons d'abord écrire le code suivant :

int main()    
{    
    //C语言提供的接口    
    printf("hello,printf\n");    
    fprintf(stdout,"hello,fprintf\n");    
    const char* s = "hello,fputs\n";    
    fputs(s,stdout);    
    
    //系统接口    
    const char* ss = "hello,write\n";    
    write(1,ss,strlen(s));    
    //注意这里有一个创建子进程
    fork();  
}

Ici, nous mettons la fonction fork de création d'un processus enfant à la fin. Quel est l'intérêt de le mettre à la fin du processus fils sans rien exécuter ?

Exécutons d'abord le code :

 Comme vous pouvez le voir, ceux-ci sont tous sortis normalement sans aucun problème.

Mais nous créons un fichier log.txt à ce moment, puis redirigeons la sortie vers celui-ci, puis cat pour voir le contenu de la carte :

Phénomène très étrange : nous avons constaté qu'à l'exception de l'interface système , qui n'est sortie qu'une seule fois , les fonctions fournies par le langage C sont sorties deux fois.

Quelle est la raison?


Selon la stratégie de rafraîchissement du tampon que nous avons mentionnée ci-dessus :

Nous exécutons directement le programme pour imprimer sur l'écran, en utilisant la stratégie de rafraîchissement de ligne , et nous redirigeons vers le fichier et imprimons dans le fichier, ce qui devient une stratégie de tampon complet .

1. S'il imprime sur l'écran, la stratégie de rafraîchissement de ligne est adoptée, puis lorsque le fork est exécuté à la fin, toutes les données ont été actualisées et il est inutile d'exécuter le fork à ce moment.

2. Si le programme est redirigé, c'est-à-dire pour imprimer dans le fichier à ce moment, la stratégie de rafraîchissement deviendra implicitement une mise en mémoire tampon complète à ce moment .

Le saut de ligne \n n'a pas de sens.

Lors du fork, la fonction doit avoir été exécutée, mais les données n'ont pas été rafraichies ! Ces données sont dans le tampon de la bibliothèque standard C correspondant au processus en cours.Ces données appartiennent au processus parent .

Une fois le code exécuté, cela ne signifie pas que les données ont été actualisées . Lorsque le processus enfant et le processus parent s'exécutent, renvoient 0 après le fork, c'est-à-dire lorsque les données doivent être actualisées, une copie sur écriture se produit , donc qu'il existe deux copies des données, puis les sortir séparément dans le fichier.

Par conséquent, la sortie de la fonction par la bibliothèque standard du langage C est imprimée deux fois et l'interface système est imprimée une fois.

Parce que l'interface système est directement écrite dans le fichier sans passer par le tampon.

A ce stade, nous sommes encore plus convaincus du fait que le buffer ne doit pas être fourni par le système d'exploitation ! Il est fourni par la bibliothèque standard du langage C ! Parce que si elle est fournie par le système d'exploitation, cette interface système doit également être sortie deux fois, pas une seule.


Alors où est-il ?

Ajoutons un autre morceau de code :

    fflush(stdout);

 Exécutons-le à nouveau et publions ceci dans le fichier log.txt :

Nous avons constaté qu'une seule déclaration était sortie à ce moment, au lieu de deux déclarations. 

Après l'explication ci-dessus, je pense que tout le monde peut comprendre que fflush a vidé de force le contenu du tampon.À ce moment, le tampon est vide , puis exécutez fork, les tampons de processus parent et enfant sont vides, il n'y a donc pas d'actualisation des données , donc une seule instruction est imprimée.

Notez ensuite que notre fflush est fourni par le langage C, et que nous ne fournissons qu'un paramètre stdout, alors comment trouve-t-il le tampon ?

En langage C, la fonction correspondante pour ouvrir un fichier est fopen dont le prototype de fonction est le suivant :

Sa valeur de retour de fonction est FILE *, qui est un fichier struct, qui non seulement encapsule fd, mais contient également la structure tampon _IO_FILE de la couche de langage correspondant au fichier fd.

La structure interne de _IO_FILE est à peu près la suivante :

 

 Ainsi, le tampon de langage C existe dans la structure FILE .

Ce qu'il faut expliquer ici, c'est qu'il existe également des tampons dans le noyau , mais le tampon du noyau et le tampon au niveau de l'utilisateur (tel que le tampon fourni par le langage C) sont indépendants et ne s'affectent pas.

 Concevoir manuellement un tampon userland

Nous savons que le tampon est encapsulé dans une structure de fichiers et qu'il existe des descripteurs de fichiers fd, etc., nous devons donc d'abord créer une structure pour encapsuler ces données.

#define NUM 1024                                                                                                                      
struct MyFILE_{    
 int fd;//文件描述符
 char buffer[NUM];//缓冲区
 int end;//当前缓冲区的结尾    
};    
typedef struct MyFILE_ MyFILE;    

De même, pour l'utiliser, il y a quatre interfaces à utiliser, à savoir fopen_, fputs_, fflush_, fclose_ ces quatre interfaces.

Parlons d'abord de fopen_, qui a principalement deux paramètres : le premier paramètre est le chemin du fichier à ouvrir , le second est le mode d'ouverture (r, r+, w, w+, a, a+), et enfin renvoie le structure MonFICHIER.

Ces six modes ne sont pas tous écrits un par un, un seul mode w est écrit, ce qui nous suffit à utiliser.

L'idée de la méthode est la suivante : appelez d'abord l'interface système open(), puis transmettez les paramètres, le mode d'ouverture est O_WRONLY, O_TRUNC, O_CREAT, puis acceptez le fd renvoyé

Si fd est supérieur ou égal à 0, cela signifie qu'il est ouvert, puis ouvrez l'espace pour la structure FILE et initialisez-le à ce moment, et rendez le fd dans FILE égal au fd obtenu en ouvrant tout à l'heure

MyFILE* fopen_(const char* pathname,const char* mode)
{
  assert(pathname);
  assert(mode);
  
  MyFILE* fp = NULL;
  if(strcmp(mode,"r") == 0)
  {

  }
  else if(strcmp(mode,"r+") == 0)
  {

  }
  else if(strcmp(mode,"w") == 0)
  {
    int fd = open(pathname,O_WRONLY | O_TRUNC | O_CREAT,0666);
    if(fd >= 0)
    {
      fp = (MyFILE*)malloc(sizeof(MyFILE));
      memset(fp,0,sizeof(MyFILE));
      fp->fd = fd;

    }
    
  }
  else if(strcmp(mode,"w+") == 0)
  {

  }
  else if(strcmp(mode,"a") == 0)
  {

  }
  else if(strcmp(mode,"a+") == 0)
  {

  }
  return fp;
}

Vient ensuite fputs_, la fonction principale est d'écrire le contenu dans le descripteur de fichier spécifié fd (essentiellement écrire dans le buffer ). Ainsi, le premier paramètre est le contenu à écrire, et le deuxième paramètre est la structure MyFILE.

L'idée principale est de copier d'abord le contenu entrant dans le tampon tampon de MyFILE, puis de mettre à jour la longueur de end.

À ce stade, nous allons juger si fd est 0, 1, 2 ou d'autres fichiers. Ici, nous ne réalisons que le cas de fd=1

Lorsque fd=1, jugez d'abord si le dernier caractère du tampon est '\0', si c'est le cas, écrivez ensuite à 1, c'est-à-dire actualisez le contenu du tampon et définissez end sur 0.

Si ce n'est pas le cas, aucune action n'est requise.

void fputs_(const char* message, MyFILE* fp)
{
  assert(message);
  assert(fp);

  strcpy(fp->buffer+fp->end,message);                                                                                                 
  fp->end += strlen(message);

  //for debug
  //printf("%s\n",fp->buffer);
  //暂时没有刷新,刷新策略是是来执行的呢? 用户通过执行C标准库中的代码逻辑,来完成刷新动作
  //效率提高体现在哪里呢?因为C提供了缓冲区,那么我们就通过策略,减少了IO次数的执行次数,不是数据量!
  if(fp->fd == 0)
  {
    //标准输入
  }
  else if(fp->fd == 1)
  {
    //标准输出
    if(fp->buffer[fp->end-1] == '\n')
    {
      //fprintf(stderr,"fflush: %s",fp->buffer);
      write(fp->fd,fp->buffer,fp->end);
      fp->end = 0;
    }
  }
  else if(fp->fd == 0)
  {
    //标准错误
  }
  else
  {
    //其他文件
  }
}

Vient ensuite fflush_, la fonction principale de cette fonction est de forcer le vidage du contenu dans le tampon.

L'idée principale est la suivante : jugez d'abord si le contenu du tampon est vide, sinon, écrivez dans le fichier numéroté fd, puis appelez la fonction syncfs pour écrire les données sur le disque, et enfin définissez end sur 0.

void fflush_(MyFILE* fp)
{
  assert(fp);
  if(fp->end != 0)
  {
    //暂且认为刷新了 -- 其实是把数据写到了内核
    write(fp->fd,fp->buffer,fp->end);
    syncfs(fp->fd);//将数据写入到磁盘
    fp->end = 0;
  }
}

Le dernier est fclose_, la fonction de cette fonction est de fermer le fichier et de rafraîchir le contenu du tampon.

C'est relativement simple, il suffit de réutiliser le tampon fflush_ refresh tout à l'heure et d'appeler la fonction close pour fermer le fichier

void fclose_(MyFILE* fp)
{
  assert(fp);
  fflush_(fp);
  close(fp->fd);
  free(fp);

}

 Le contenu de la zone tampon est expliqué ici. Si vous avez des questions ou des erreurs, n'hésitez pas à les faire ou à les corriger dans la zone de commentaires ou en message privé.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_47257473/article/details/131913698
conseillé
Classement