- předchozí článek - následující článek - obsah - úvodní stránka -

Linuxové noviny Duben 1998

Programujeme v C s libproc

Karel Žák, 10. dubna 1998

V následujícím článku se pokusím vás seznámit s jednou velmi jednoduchou knihovnou pro vytváření programů pracujících s daty o procesech a systému. Často je slyšet hlasy, které tvrdí, že céčko je složité a v jiných programovacích jazycích lze programovat daleko rychleji atd. Osobně jsem toho názoru, že obtížnost, s jakou lze naprogramovat to či ono, je více než vlastním jazykem dána existencí knihoven, které řeší obtížné nebo (na naprogramování) zdlouhavé úkoly za programátora. Jednou takovou (řešící) knihovnou je i libproc.

O libproc se stará v současné době Helmut Geye Helmut.Geyer@iwr.uni-heidelberg.de. Knihovna je ve většině distribucí ve verzi 1.2.6. Dále je dostupná i experimentální verze této knihovny a to verze 1.12.2. Osobně se mi líbí více 1.12.2. Ta obsahuje daleko více maker a i po stránce množství dat o jednotlivých procesech je daleko obsažnější. Doporučuji dát si velký pozor na rozdílnost těchto knihoven při psaní programů. Stejnými verzemi jako knihovny jsou značeny i programy ps, top, uptime, pstree, free a to proto, že vše je distribuováno v balíku procps. Zajímavostí může být, že v Debianu v unstable distribution je verze 1.2.6 a ve stable distribution je verze 1.12.2. Vývojářské knihovny jsou v balíku libproc-dev odpovídající verze.

Ale zpět k vlastnímu programování. Začneme tím jednodušším. Pokusíme se udělat prográmek, který nám vypíše na standardní výstup aktuální uptime a obsazení paměti a swapu našeho počítače. Pokud bychom tento úkol chtěli splnit bez libproc znamenalo by to přečíst soubory /proc/uptime a /proc/meminfo a obojí analyzovat (a to v případě uptimu není zas taková banalita). Řešení pomocí libproc je uvedeno v příkladu Použití knihovny libproc. Knihovna umožňuje u uptimu přímo tisk uptime stringu na standardní výstup a to pomocí print_uptime(void), takto je ostatně udělán i program uptime(1). S údaji o paměti je to trochu složitější. Zde je pomocí funkce meminfo vráceno pole typu unsigned (**mem). To obsahuje dvě řádky: meminfo_main a meminfo_swap. A každá řádka několik sloupců: meminfo_free, meminfo_total atd. Kombinací sloupců a řádek se lze dobrat jednotlivých údajů. Ty jsou v našem případě ještě před vypsáním převedeny z b na kB (bitová operace >> 10). A to je vše na cca 30 řádcích. Snad ještě poznámka pro ty co by nevěděli jak kompilovat:

gcc -Wall -O2 prog.c -o prog -lproc 


/* ----- příklad 1. ----- */
#include <stdio.h>
#include <proc/sysinfo.h>       /* meminfo()      */
#include <proc/whattime.h>      /* print_uptime() */

#define RE_ERROR        1
#define RE_OK           0

   int main()
   {
      unsigned **mem;   

      puts("\nUPTIME:");                           
      print_uptime();   

      if (!(mem = meminfo())) 
         exit(RE_ERROR);

      puts("\nSYSTEM MEMORY (kB):");                           
      printf("\tTotal  : %u\n",  (mem[meminfo_main][meminfo_total]   >> 10));
      printf("\tFree   : %u\n",  (mem[meminfo_main][meminfo_free]    >> 10));
      printf("\tUsed   : %u\n",  (mem[meminfo_main][meminfo_used]    >> 10));
      printf("\tShared : %u\n",  (mem[meminfo_main][meminfo_shared]  >> 10));
      printf("\tBuffers: %u\n",  (mem[meminfo_main][meminfo_buffers] >> 10));
      printf("\tCached : %u\n",  (mem[meminfo_main][meminfo_cached]  >> 10));
      puts("\nSYSTEM SWAP (kB):");                           
      printf("\tTotal  : %u\n",  (mem[meminfo_swap][meminfo_total]   >> 10));
      printf("\tFree   : %u\n",  (mem[meminfo_swap][meminfo_free]    >> 10));   
      printf("\tUsed   : %u\n\n",(mem[meminfo_swap][meminfo_used]    >> 10));

      exit(RE_OK);
   }

Výpis č. 2: Použití knihovny libproc

Teď o něco těžší prográmek, který zjistí jaký proces používá nejvíce rezistentní paměti (tedy té, která není swapována) a jaký proces nejvíce zatěžuje procesor. Řešení je uvedeno na výpise Použití knihovny libproc podruhé. Na počátku je nutné nastavit verzi Linuxu (set_linux_version()), protože pokud to neuděláte, můžete se dočkat i poněkud zkreslených údajů (mě se to stalo např. u ttyc). Pak je nutné nastavit časové údaje, které budeme potřebovat pro výpočet zatížení procesoru. Zde se pozastavím - funkce get_pcpu() je původní funkce, kterou používá program ps(1) po výpočet %CPU (proto ty podivné proměnné...). Tato funkce umožňuje buď počítat zatížení CPU pro vlastní proces nebo i včetně potomků (cumulative). Proto i možný parametr -c u našeho programu. A pak už následuje vlastní načtení dat o procesech. Libproc umožňuje načíst informace buď o jednom procesu (readproc()) nebo pro více procesů (readproctab()). Informace o procesech jsou těmito funkcemi vráceny v pointeru na strukturu typu proc_t, ta je definována a okomentována v hlavičkové souboru readproc.h. Struktura obsahuje asi 60 různých údajů o daném procesu a to od UID (verze 1.12.2 rozlišuje několik typů UID, GID, USER, GROUP) až po takové věci jako detailní rozlišení kolik paměti je použito knihovnami procesu, kolik je sdíleno, jaký je paměťový limit procesu a mnoho a mnoho dalšího. Nahlédnou do readproc.h nebo alespoň do manuálu k /proc doporučuji i (ne)programátorům, budete příjemně překvapeni kolik mnoho se lze o procesech dozvědět (zde má jistý komerčně "úspěšný" operační systém ještě co dohánět). Pokud získáváme data pomocí readproctab() je nám vráceno pole pointerů na struktury, kde jedna řádka pole je jedna struktura typu proc_t a reprezentuje právě jeden proces - poslední řádka pole je pointer NULL. Funkce readproc() a readproctab() umožňují velice efektivně definovat co o procesu chceme načíst a je možné i definovat filtr, podle kterého jsou vybrány jen naší podmínce vyhovující procesy. (Toto je hlavně u verze 1.12.2.) Více už makra PROC_NĚCO v souboru readproc.h. Funkce readproctab() je vlastně celé kouzlo našeho programu a asi i nejdůležitější částí libproc. Nyní už je jen naší starostí co s daty, které nám tato funkce vrátila. (A vše se v podstatě odbylo na jednom řádku - a pak že je céčko složité).

/* ----- příklad 2. ----- */
#include <stdio.h>
#include <string.h>		/* strcmp()            */
#include <proc/readproc.h>	/* readproctab         */
#include <proc/version.h>	/* set_linux_version() */
#include <proc/sysinfo.h>	/* uptime()            */
#include <sys/param.h>	/* HZ                  */
#include <time.h>		/* time()              */

#define RE_ERROR	1
#define RE_OK		0

   int	GL_current_time, CL_Sum;
   long	GL_time_now;

/* This function is from ps(1) - ps.c 
 * Copyright (c) 1992 Branko Lankester
 * Changes Copyright (C) 1993, 1994 Michael K. Johnson,
 *   and   Copyright (C) 1995, 1996 Charles Blake,
 *   and   Copyright (C) 1996       Helmut Geyer 
 */
   void get_pcpu(proc_t *p) {
      int total_time, seconds;
      time_t start;
      unsigned int pcpu=0;
   
      seconds = (((GL_current_time * HZ) - p->start_time) / HZ);
      start = GL_time_now - seconds;
      total_time = (p->utime + p->stime  + 
                   (CL_Sum ?   p->cutime + p->cstime : 0));
      pcpu = seconds ? (total_time * 10 * 100/HZ) / seconds : 0;
      if (pcpu > 999) pcpu = 999;
      p->pcpu = pcpu;
   }

   int main(int argc, char **argv)
   {
      proc_t	**pt;	
      int	proc_num=0, mem_max=0, cpu_max=0;
   
      if (argc > 1)
         if (!(strcmp(argv[1], "-c")))
            CL_Sum = 1;
   
      set_linux_version();
      if (!(GL_current_time = uptime(0,0))) 
         exit(RE_ERROR);
      GL_time_now = time(0L);
      pt = readproctab(PROC_FILLTTY | PROC_FILLUSR | PROC_FILLMEM);
   
      while(pt[proc_num] != NULL) {
      /* --- mem --- */
         if (pt[proc_num]->resident > pt[mem_max]->resident)
            mem_max = proc_num;
      /* --- cpu --- */ 
         get_pcpu(pt[proc_num]);    	 	
         if (pt[proc_num]->pcpu > pt[cpu_max]->pcpu)
            cpu_max = proc_num; 	
         ++proc_num;
      }      
      printf("\nPROCESS  : %d\n", proc_num);
      puts("PROCES WITH MAX. RESIDENT MEMORY:");
      printf("\tName     : %s\n",         pt[mem_max]->cmd);
      printf("\tPID      : %d\n",         pt[mem_max]->pid);
      printf("\tSTATE    : %c\n",         pt[mem_max]->state);
      printf("\tUSER     : %s\n",         pt[mem_max]->user); 
      printf("\tTTY      : %s\n",         pt[mem_max]->ttyc);
      printf("\tRESIDENT : %ld (pages)\n", pt[mem_max]->resident);  
      puts("PROCES WITH MAX. %CPU:");
      printf("\tName     : %s\n",         pt[cpu_max]->cmd);
      printf("\tPID      : %d\n",         pt[cpu_max]->pid);
      printf("\tSTATE    : %c\n",         pt[cpu_max]->state); 
      printf("\tUSER     : %s\n",         pt[cpu_max]->user); 
      printf("\tTTY      : %s\n",         pt[cpu_max]->ttyc);
      printf("\t%%CPU     : %.1f", (float) pt[cpu_max]->pcpu/10);    
      if (CL_Sum) 
         puts(" (cumulative)\n");
      else
         puts("\n");
   
      exit(RE_OK);
   }

Výpis č. 3: Použití knihovny libproc podruhé

Ještě malé upozornění pro ty co by rádi něco podnikali s libproc. U polí jako např. **cmdline je dobré před započetím práce testovat jestli vůbec něco obsahuje. A testovat také délku položek v něm uložených. Ona příkazová řádka některých procesů by mohla váš program odeslat kamsi do core...

Pochopitelně výše uvedené je jen částí toho co dovede libproc. Dostupné jsou i funkce převádějící čísla zařízení do stringů (devname.h), speciální outputy (output.h), zacházení s "psdatabází" (psdata.h), jména signálů (signames.h), vytváření stromu procesů dle vztahu rodič - dítě (tree.h) atd.

Aby nikdo nemusel opisovat nebo jinak složitě získávat uvedené příklady (pokud by si je chtěl vyzkoušet), může je nalézt na FTP serverech Linuxových novin nebo na adrese http://home.zf.jcu.cz/~zakkr/LN/.

Všem zájemcům lze jen doporučit nainstalovat balík libproc-dev a začít samostudium s knihovnou, která dokáže usnadnit programátorský život všem, kteří ji dokáží efektivně využít. *


- předchozí článek - následující článek - obsah - úvodní stránka -