Forum Linux.noyau taille du "fichier" /proc/meminfo

Posté par . Licence CC by-sa
Tags :
1
17
août
2015

Bonjour.
J'ai créé un module pour i3status qui affiche des infos sur la RAM, disponible dans /proc/meminfo. Pour le moment j'ai utilisé la méthode bourrin: wc -c /proc/meminfo et je copie/colle le résultat dans un #define BUFFER_SIZE. Bourrin, et surtout, pas pérenne.
Je me demandais s'il y avais moyen de récupérer quelque part la taille de ce fameux "fichier"? J'ai cherché sur le net, mais je n'ai pas du trouver le terme adéquat puisque mes recherches ont été complètement stériles…

  • # rassures moi vite

    Posté par . Évalué à 2.

    j'espere que tu ne prends pas wc -c /proc/meminfo pour determiner la quantité de RAM utilisée/libre sur ta machine quand meme ?

    parce que wc -c ca donne la taille en octets du fichier,
    mais le fichier contient des trucs comme :

    cat /proc/meminfo
    MemTotal: 8044820 kB
    MemFree: 5281224 kB
    MemAvailable: 6387704 kB
    Buffers: 148744 kB
    Cached: 1291760 kB
    SwapCached: 0 kB
    Active: 1829020 kB
    Inactive: 690436 kB
    Active(anon): 1080244 kB
    Inactive(anon): 220396 kB
    Active(file): 748776 kB
    Inactive(file): 470040 kB
    Unevictable: 64 kB
    Mlocked: 64 kB
    SwapTotal: 4063228 kB
    SwapFree: 4063228 kB
    Dirty: 4 kB
    Writeback: 0 kB
    AnonPages: 1079068 kB
    Mapped: 314976 kB
    Shmem: 221692 kB
    Slab: 144780 kB
    SReclaimable: 113212 kB
    SUnreclaim: 31568 kB
    KernelStack: 7472 kB
    PageTables: 27672 kB
    NFS_Unstable: 0 kB
    Bounce: 0 kB
    WritebackTmp: 0 kB
    CommitLimit: 8085636 kB
    Committed_AS: 4305908 kB
    VmallocTotal: 34359738367 kB
    VmallocUsed: 553100 kB
    VmallocChunk: 34359114396 kB
    HardwareCorrupted: 0 kB
    AnonHugePages: 466944 kB
    CmaTotal: 0 kB
    CmaFree: 0 kB
    HugePages_Total: 0
    HugePages_Free: 0
    HugePages_Rsvd: 0
    HugePages_Surp: 0
    Hugepagesize: 2048 kB
    DirectMap4k: 119296 kB
    DirectMap2M: 2895872 kB
    DirectMap1G: 6291456 kB

    c'est peut-etre plus utile que la taille en octets du fichiers, non ?

    • [^] # Re: rassures moi vite

      Posté par . Évalué à 2.

      Tu m'as mal compris: j'utilise wc pour déterminer la taille de ce fichier, afin d'allouer, dans un programme (C++ en l'occurrence) la taille du buffer que je dois utiliser pour lire ce fichier (et ensuite traiter les informations qui m'intéressent).

      • [^] # Re: rassures moi vite

        Posté par (page perso) . Évalué à 2.

        Pourquoi veux-tu stocker tout le fichier dans la mémoire ?
        Je dirais qu'il serait préférable dans ce genre de cas de lire le fichier ligne par ligne à la recherche du champ que tu veux.

        Le problème est traité plus simplement ainsi.

        • [^] # Re: rassures moi vite

          Posté par . Évalué à 2.

          Parce que ce n'est pas un vrai fichier. En fait, ce que tu décris est la première approche que j'ai utilisée: une bonne vieille while( !feof( f ) ). Sauf que ça marche pas, /proc étant en fait, je l'ai découvert à mes dépends (l'expérience aura été utile au moins), un peu comme un accès à une partie de la mémoire du kernel accessible en read-only (et peut-être rw pour certaines parties, je ne connais pas assez).
          Du coup, FILE* ne mets pas à jour l'information de position. Conséquence, on lis en permanence la même ligne.

          • [^] # Re: rassures moi vite

            Posté par (page perso) . Évalué à 3.

            Je suspecte ton code d'être faux, la commande wc ne passe pas 6 mois chez le psychanalyste avant de savoir comment ouvrir le fichier que tu lui donnes. Ensuite puisque tu programmes en C++, pourquoi est-ce que tu n'utilises les possibilités du C++ au lieu de traîner des FILE* et des buffers de taille fixe… c'est du masochisme.

            • [^] # Re: rassures moi vite

              Posté par . Évalué à 2.

              Je suspecte ton code d'être faux, la commande wc ne passe pas 6 mois chez le psychanalyste avant de savoir comment ouvrir le fichier que tu lui donnes.

              Je ne sais pas, j'ai pas embauché de détective privé pour la filer. Blague à part, je n'ai pas été lire son code, je devrais peut-être, mais je trouve le code gnu en général pas trop lisible (mention spéciale pour la libstdc++ d'ailleurs, ou les tabs et espaces sont mélangés dans un joyeux foutoir).

              Ensuite puisque tu programmes en C++, pourquoi est-ce que tu n'utilises les possibilités du C++ au lieu de traîner des FILE* et des buffers de taille fixe… c'est du masochisme.

              Je suis peut-être masochiste, mais je trouve aussi le code à base de FILE* plus lisible que les fstream. Ceci étant dit, tu as raison, j'aurai du tester avec ifstream, cela fonctionne sans buffer fixe.

  • # man fstat

    Posté par . Évalué à 2. Dernière modification le 17/08/15 à 15:05.

    1) Pour connaître la taille d’un fichier utile fstat ou stat si le fichier n’est pas ouvert. Le champ st_size devrait faire ton bonheur.

    Au delà de ta question :
    2) Si ton but est de charger le fichier en mémoire pour l’étudier : man mmap pourra te simplifier la vie.

    3) Si ton but est de chercher certains champs, autant ouvrir le fichier et le lire ligne par ligne : man getline.

    • [^] # Re: man fstat

      Posté par . Évalué à 2.

      1) Pour connaître la taille d’un fichier utile fstat ou stat si le fichier n’est pas ouvert. Le champ st_size devrait faire ton bonheur.

      En temps normal, j'aurai utilisé ls -l, mais en l'occurrence, il ne s'agit pas d'un vrai fichier, les méthodes traditionnelles ne fonctionnent donc pas.
      Je ne connaissais cependant pas ces commandes. fstat n'est pas installé sur mon système, et statrenvoie 0 pour /proc/meminfo.

      2) Si ton but est de charger le fichier en mémoire pour l’étudier : man mmap pourra te simplifier la vie.

      Je viens de lire très brièvement le man de mmap. Je ne suis pas sûr d'en voir l'intérêt, sauf pour écrire un chargeur de binaire d'un format différent de ELF, ou écrire un programme métamorphe.

      3) Si ton but est de chercher certains champs, autant ouvrir le fichier et le lire ligne par ligne : man getline

      J'ai essayé de lire ligne par ligne, mais la encore, la nature virtuelle du fichier fait que ce n'est pas possible, il faut le lire d'un bloc sinon les instructions ne retournent tout le temps que le début du fichier (qui, en fait, n'est pas un fichier, et du coup FILE* ne contiens pas d'information de positionnement, à ce que j'ai compris).

      • [^] # Re: man fstat

        Posté par . Évalué à 2.

        Je n’avais pas tilté que le fichier était virtuel…

        #include <stdio.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <unistd.h>
        #include <fcntl.h>
        
        
        #define File "/proc/meminfo"
        
        int main()
        {
          int fd = open(File,O_RDONLY);
        
          if (fd == -1)
          {
             fprintf(stderr,"Ouverture de "File" impossible avec open\n");
          }
          else
          {
            struct stat l_stat;
            int rc;
            rc = fstat(fd,&l_stat);
            if (rc == -1)
            {
               perror("fstat error");
            }
            else
            {
               printf("Fichier : "File" : taille : %d\n",l_stat.st_size);
            }
          }
          return 0;
        }
        $ ./a.out
        Fichier : /proc/meminfo : taille : 0
        $ 

        :-(

        La fonction read renvoie le nombre d’octets réellement lus. En définissant un grand buffer ça devrait marcher. En plus, comme Linux n’alloue les pages que quand elles sont utilisées, on peut allouer 1M sans avoir peur.

        #include <stdio.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <unistd.h>
        #include <fcntl.h>
        
        
        #define File "/proc/meminfo"
        
        int main()
        {
          int fd = open(File,O_RDONLY);
        
          if (fd == -1)
          {
             fprintf(stderr,"Ouverture de "File" impossible avec open\n");
          }
          else
          {
            char *l_buffer;
            /* 1M octet est sufisant et pas réellement alloué par linux */
            l_buffer = malloc(1000000);
            ssize_t size;
            size = read(fd,l_buffer,1000000);
            /* size contient la taille réellement lue */
            printf("Fichier : "File" : taille : %d\n",size);
            free(l_buffer);
          }
          return 0;
        }
        $ ./a.out
        Fichier : /proc/meminfo : taille : 1198
        $ 
        • [^] # Re: man fstat

          Posté par . Évalué à 3.

          Je n’avais pas tilté que le fichier était virtuel…

          Sinon j'aurai utilisé le classique combo fseek/ftell/fseek :)

          La fonction read renvoie le nombre d’octets réellement lus. En définissant un grand buffer ça devrait marcher. En plus, comme Linux n’alloue les pages que quand elles sont utilisées, on peut allouer 1M sans avoir peur.

          Ça, ça dépend de la configuration du kernel si je ne dis pas de conneries (/proc/sys/overcommit_memory).
          Personnellement, je trouve que c'est plus une gêne qu'une fonctionnalité: l'overcommit fait qu'on ne peut pas faire confiance au système quant au fait que les ressources qu'il nous à confiées sont réellement utilisables.
          Je ne vais pas m'étendre la-dessus, mais je préfère éviter de me fier à ce type de mécanisme, et ce, particulièrement pour les applications que je fais pour ma pomme (si le chef me demande de faire le goret après tout, ainsi soit-il, hein, mais j'essaierai quand même d'éviter).

          Enfin, certes, 1M, ce n'est pas grand chose, et c'est vrai que ta méthode marche, surtout qu'il suffit de faire d'abord une allocation de bourrin pour récupérer la véritable taille puis ajuster. Je n'y avais pas pensé je le reconnais.

          M'enfin, dommage tout de même de ne pas avoir un mécanisme plus efficace (alloc, read, free, alloc, free, 5 appels système, pour quelque chose qui devrait pouvoir se faire plus facilement je pense, et si on doit faire ça de façon répétitive pour les 35K fichiers la fragmentation mémoire risque d'être conséquente à force).

          • [^] # Re: man fstat

            Posté par . Évalué à 2.

            Ça, ça dépend de la configuration du kernel si je ne dis pas de conneries (/proc/sys/overcommit_memory).

            Ben, j’aime pas non plus, mais il existe.

            Sinon, tu peux allouer un buffer sur la pile, et boucler en sommant les retours du read. Ça bouffe moins de RAM, même si tu la libère juste après. Et puis comme c’est de l’accès RAM, ce n’est pas très long.

            Sinon, si c’est de l’analyse de champ, une solution à base de fscanf peut être bien comme proposé en dessous.

    • [^] # Re: man fstat

      Posté par . Évalué à 2. Dernière modification le 17/08/15 à 17:56.

      Arg… double post, oups!

  • # Doc

    Posté par (page perso) . Évalué à 3.

    man proc

    et aussi /usr/src/linux-source-${kernel_version}/Documentation/filesystems/proc.txt

    ou

    http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/proc.txt?id=HEAD

    ou

    https://github.com/torvalds/linux/blob/master/Documentation/filesystems/proc.txt

    qui dit justement :

    "meminfo: Provides information about distribution and utilization of memory. This varies by architecture and compile options. (…) You may not have all of these fields."

    (ce qui ne répond pas à la question : quelle est la taille (max ?) pour mon architecture et mes options sur mon noyau donné ?)

    • [^] # Re: Doc

      Posté par (page perso) . Évalué à 3. Dernière modification le 17/08/15 à 15:25.

      Sur le /proc/meminfo.c du dernier noyau :

          seq_printf(m,
              "MemTotal:       %8lu kB\n"
              "MemFree:        %8lu kB\n"
              "MemAvailable:   %8lu kB\n"
              "Buffers:        %8lu kB\n"
              "Cached:         %8lu kB\n"
              "SwapCached:     %8lu kB\n"
              "Active:         %8lu kB\n"
              "Inactive:       %8lu kB\n"
              "Active(anon):   %8lu kB\n"
              "Inactive(anon): %8lu kB\n"
              "Active(file):   %8lu kB\n"
              "Inactive(file): %8lu kB\n"
              "Unevictable:    %8lu kB\n"
              "Mlocked:        %8lu kB\n"
      #ifdef CONFIG_HIGHMEM
              "HighTotal:      %8lu kB\n"
              "HighFree:       %8lu kB\n"
              "LowTotal:       %8lu kB\n"
              "LowFree:        %8lu kB\n"
      #endif
      #ifndef CONFIG_MMU
              "MmapCopy:       %8lu kB\n"
      #endif
              "SwapTotal:      %8lu kB\n"
              "SwapFree:       %8lu kB\n"
              "Dirty:          %8lu kB\n"
              "Writeback:      %8lu kB\n"
              "AnonPages:      %8lu kB\n"
              "Mapped:         %8lu kB\n"
              "Shmem:          %8lu kB\n"
              "Slab:           %8lu kB\n"
              "SReclaimable:   %8lu kB\n"
              "SUnreclaim:     %8lu kB\n"
              "KernelStack:    %8lu kB\n"
              "PageTables:     %8lu kB\n"
      #ifdef CONFIG_QUICKLIST
              "Quicklists:     %8lu kB\n"
      #endif
              "NFS_Unstable:   %8lu kB\n"
              "Bounce:         %8lu kB\n"
              "WritebackTmp:   %8lu kB\n"
              "CommitLimit:    %8lu kB\n"
              "Committed_AS:   %8lu kB\n"
              "VmallocTotal:   %8lu kB\n"
              "VmallocUsed:    %8lu kB\n"
              "VmallocChunk:   %8lu kB\n"
      #ifdef CONFIG_MEMORY_FAILURE
              "HardwareCorrupted: %5lu kB\n"
      #endif
      #ifdef CONFIG_TRANSPARENT_HUGEPAGE
              "AnonHugePages:  %8lu kB\n"
      #endif
      #ifdef CONFIG_CMA
              "CmaTotal:       %8lu kB\n"
              "CmaFree:        %8lu kB\n"
      #endif

      ça nous ferait sauf erreur entre 14 et 44 lignes de 20 char + un "%8lu" (soit min 8 max-ça-dépend-ça-dépasse octets) sauf une avec un %5lu. Probablement entre 14*(20+8) et 44*(20+19) octets, mais s'il y a un peu plus je vous le même quand même.

      • [^] # Re: Doc

        Posté par . Évalué à 2.

        C'est vrai que je pourrai prendre la taille maximum actuelle du fichier.
        Le problème étant, dans ce cas, que j'ai toujours un truc hardcodé, dédié à une une partie particulière d'un système particulier.
        Si un jour je veux évoluer le programme pour accéder à d'autres informations, je devrais refaire la même recherche de la taille maximale d'un autre fichier… bof bof.

        Non pas que je m'attende à ce que tous les systèmes nomment leurs "fichiers"/variables de la même façon bien sûr, voire même je doute qu'il soit possible d'accéder à ces informations via un simple fopen (en fait je sais que c'est impossible, l'OS de microsoft n'exposant pas son interface de cette façon, mais m'en cogne de celui-la)… mais je trouve du coup que c'est quand même dommage de devoir utiliser une valeur au pifomètre pour tenter de récupérer toutes les informations.

        Même si mon patch à i3status ne fait que +/- 80 LoC, je trouve ça dommage de mettre des choses en dur dedans (autrement dit, définies à la main, peu importe si c'est au travers d'une constante ou de valeurs numériques, le résultat est le même au final).

        Il me reste toujours la piste de mmap() suggérée plus haut. J'y répondrai quand j'aurai fini de lire le man et quand j'aurai vérifié que c'est utilisable (ou pas, je le préciserai dans ce cas).

  • # Petites précisions.

    Posté par . Évalué à 3.

    Il semble que ma question ne soit pas assez claire (ce qu'en fait je comprend, c'est juste un bloc de texte… mea culpa!), donc voici quelques précisions/reformulations.

    Je cherche à savoir s'il est possible de connaître la taille des éléments de /proc par programmation C (C++ en fait, mais l'accès à ces ressources se fait de la même façon de toute manière).
    À l'heure actuelle, mon code (qui fonctionne) procède en allouant à la compilation un buffer de taille fixe (je vous ai menti en plus, vue la constante je n'ai même pas utilisé la taille exacte de /proc/meminfo…) déterminé de façon empirique pour pouvoir tout contenir.
    L'inconvénient de cette façon de faire est que si un jour je veux accéder à d'autres informations de /proc (ou soyons fous, de /sys?) il me faudra alors effectuer la mesure manuellement afin de déterminer la taille à allouer au buffer sur lequel je vais travailler.
    Pire, si je serre de trop près la taille réelle du fichier, un jour ça pètera et on ne pourra savoir pourquoi qu'en regardant dans le code (même si pour le moment il n'y a que 200LoC, rien ne prouve que ça restera trivial dans le temps). Et si on voulait changer de système cible (genre, pour aller sur du BSD), idem, gros risque que ça casse (ok, les noms des variables diffèrent certainement de toute façon, mais devoir ajuster des textes à la main me suffit, pourquoi y ajouter des tailles de buffer?)

    La solution évidente est de lire progressivement le fichier. J'ai testé, ça ne marche pas (franchement, ça m'a valu pas mal d'incompréhension, je n'ai pas toujours l'esprit très rapide et je ne me suis résolu qu'a contre-cœur d'utiliser la même méthode que le source de xosview), la raison semblant être que dans le cas de ces pseudos-fichiers, aucune information de position du curseur n'est stockée (ou mise à jour, pour ce que ça change).

    En espérant que ce soit plus clair…

  • # En C

    Posté par . Évalué à 3. Dernière modification le 17/08/15 à 19:26.

    Exemple sans gestion d'erreur en C:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    static int meminfo(char *key)
    {
        FILE *file;
        char _key[32];
        int val;
    
        file = fopen("/proc/meminfo", "r");
    
        while (fscanf(file, "%[^:]: %d%*[^\n]\n", _key, &val) == 2)
            if (!strcmp(key, _key))
                return fclose(file), val;
    
        return fclose(file), -1;
    }
    
    int main(void)
    {
        printf("meminfo(SwapFree) = %d\n", meminfo("SwapFree"));
        printf("meminfo(Invalid_Key) = %d\n", meminfo("Invalid_Key"));
        return EXIT_SUCCESS;
    }

    Compile avec gcc -o test -ansi -pedantic -Wall -Werror -O test.c.

    Exemple de résultat sur ma machine :

    $ ./test 
    meminfo(SwapFree) = 265068
    meminfo(Invalid_Key) = -1
    

    Voici un peu d'explications concernant le fscanf("%[^:]: %d %*[^\n]\n") :

    • "%[^:]:": Lis tant que tu ne trouves pas : et stockes ce que tu lis dans le char* donné en paramètre. Enlève le : restant.
    • "%*[^\n]\n" : Lis tant que tu ne trouves pas \n et ignores ce que tu lis. Enlève le \n restant. (En gros « vas à la ligne suivante »)

    En C++ tu devrais pouvoir t'en sortir avec << et l'API de stream.h, mais je connais pas très bien comment ça fonctionne ;)

    • [^] # Re: En C

      Posté par . Évalué à 2. Dernière modification le 17/08/15 à 20:14.

      Je m'auto-réponds pour préciser encore une chose.

      J'utilise %d pour obtenir la valeur mais comme le montre Benoît Sibaud plus haut, il faut lire un long unsigned au lieu d'un simple int.

      Puisque je renvois -1 pour signaler la non-éxistence d'une clé je dois en fait retourner un long long unsigned. Seulement long long unsigned n'est pas possible en C89 et donc il faut trouver un autre moyen.

      Ici j'utilise une structure pour stocker le résultat.

      #include <stdlib.h>
      #include <stdio.h>
      #include <string.h>
      
      struct meminfo_entry {
          char key[32];
          long unsigned value;
      };
      
      static int meminfo(char *key, struct meminfo_entry *entry)
      {
          FILE *file;
      
          file = fopen("/proc/meminfo", "r");
      
          while (fscanf(file, " %31[^:]: %lu%*[^\n]\n",
                        entry->key, &entry->value) == 2) {
              if (!strcmp(entry->key, key))
                  return fclose(file), 1;
          }
      
          return fclose(file), 0;
      }
      
      int main(void)
      {
          struct meminfo_entry entry;
      
          if (meminfo("HugePages_Free", &entry))
              printf("meminfo(%s) = %lu\n", entry.key, entry.value);
          if (meminfo("VmallocTotal", &entry))
              printf("meminfo(%s) = %lu\n", entry.key, entry.value);
          if (!meminfo("Invalid_Key", &entry))
              printf("\"Invalid_Key\" doesn't exist\n");
      
          return EXIT_SUCCESS;
      }

      Et une dernière pour la route :

      fscanf("%*[^\n]\n") ne marche dans le cas ou il reste juste \n, car %*[^\n] vas échouer et donc le \n restant ne sera pas vidé. L'astuce ici consiste à mettre un espace devant le premier spécificateur de conversion: " %31[^:]: %lu%*[^\n]", de cette manière tout les espaces, saut de lignes et autre tabulations sont passé.

      On peut cependant laisser le \n restant pour bien montrer ce que l'on souhaite faire : aller à la ligne suivante.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.