Parlando di Processi in Linux

By Amit Saha - trad. Cardelli Sandro

Allora, cosa e' un processo?

Riporto dal libro di Robert Love Linux Kernel Development, "Il Processo e' una delle astrazioni fondamentali nei sistemi operativi Unix, essendo i file l'altra fondamemtale astrazione." Un processo e' un programma in esecuzione. Consiste nell'esecuzione di codice di programma, un insieme di risorse come i file aperti, dati interni del kernel, e spazio indirizzato, uno o piu; thread di esecuzione e una sezione di dati contenenti le variabili globali.

Descrittori di processo

Ogni processo ha associati a se dei descrittori di processo. Questi tengono le informazioni usate per tenere traccia di un processo nella memoria. Tra le vari parti delle informazioni memorizzate per un processo sono: il suo PID, lo stato, i processi parenti, i figli, fratelli, i valore dei registri del processore, la lista dei file aperti e altre informazioni sullo spazio indirizzato.

Il Kernel Linux usa una lista circolare concatenata doppiamente della struttura struct task_struct per memorizzare questi descrittori di processi. Questa struttura e' dichiarata in linux/sched.h. Ecco alcuni campi dal kernel 2.6.15-1.2054_FC5, iniziando dalla riga 701:

    701 struct task_struct {
    702         volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    703         struct thread_info *thread_info;
     .
     .
    767         /* PID/PID hash table linkage. */
    768         struct pid pids[PIDTYPE_MAX];
     .
     .
    798         char comm[TASK_COMM_LEN]; /* executable name excluding path

La prima riga della struttura definisce il campo state come volatile long. Questa variabile e' usata per tenere traccia dello stato dell'esecuzione del processo, definito dalle seguenti macro:

#define TASK_RUNNING            0
#define TASK_INTERRUPTIBLE      1
#define TASK_UNINTERRUPTIBLE    2
#define TASK_STOPPED            4
#define TASK_TRACED             8
/* in tsk->exit_state */
#define EXIT_ZOMBIE             16
#define EXIT_DEAD               32
/* in tsk->state again */
#define TASK_NONINTERACTIVE     64

In caso la parolachiave volatile non sia chiara - vedere http://www.kcomputing.com/volatile.html per maggiori informazioni.

Liste collegate

Prima di guardare a come i task/processi (useremo le due parole come sinonimi) sono memorizzati dal kernel, occorre capire come il kernel implementa le liste circolari collegate. L'implementazione che segue e' uno standard che e' usato in tutti i sorgenti del kernel. La lista collegata e' dichiarata in linux/list.h e la struttura dei dati e' semplice:

 struct list_head {
         struct list_head *next, *prev;
 };

Questo file definisce anche altre macro gia' pronte e funzioni che e' possibile usare per manipolare le liste collegate. Questo standardizza l'implementazione delle liste di collegamenti per prevenire che le persone "reinventno la ruote" e introducano nuovi bug.

Ecco qui alcuni riferimenti a liste collegate del kernel:

La lista dei task del Kernel

Guardiamo ora come il kernel utiliza le liste circolari doppiamente collegate per memorizzare la struttura dei processi. Cercando la struttura list_head dentro la definizione della struttura task_struct abbiamo:

struct list_head tasks;

Questa riga ci mostra che il kernel usa una lista collegata circolare per memorizzare i task. Questo significa che e' possibile usare le macro e funzioni standard della lista collegata del kernel per passare attraverso la lisya dei task completa.

Init e' il padre di tutti i processi su in sistema Linux. Pertanto esso e' rappresentato all'inizio della lista, anche se in senso stretto non c'e' una testa poiche' questa e' una lista circolare. Il descrittore del processo del task di init e' allocato staticamente:

extern struct task_struct init_task;

Di seguito si mostra la rappresentazione della lista collegata dei processi in memoria:

Linked List Figure

Sono disponibili diverse altre macro e funzioni per aiutare a scorrere questa lista:

for_each_process() e' una macro che itera sull'intera lista dei task. E' definita come segue in linux/sched.h:

#define for_each_process(p) \
        for (p = &init_task ; (p = next_task(p)) != &init_task ; )

next_task() e' una macro definita in linux/sched.h la quale ritorna il prossimo task nella lista:

#define next_task(p)    list_entry((p)->tasks.next, struct task_struct, tasks)

list_entry() e' una macro definita in linux/list.h:

/*
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
        container_of(ptr, type, member)

La macro container_of() e' definita come segue::

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

Pertanto se possiamo passare attraverso l'intera lista dei task e' possibile avere l'esecuzione di tutti i processi nel sistema. Questo puo' essere fatto con la macro for_each_process(task) , dove il task e' un puntatore della struttura tipo task_struct. Ecco un esempio di modulo del kernel, dal Linux Kernel Development:

    /* ProcessList.c 
    Robert Love Chapter 3
    */
    #include < linux/kernel.h >
    #include < linux/sched.h >
    #include < linux/module.h >

    int init_module(void)
    {
    struct task_struct *task;
    for_each_process(task)
    {
    printk("%s [%d]\n",task->comm , task->pid);
    }
   
    return 0;
    }
   
    void cleanup_module(void)
    {
    printk(KERN_INFO "Cleaning Up.\n");
    }

L'attuale macro e' un link al descrittore di processo (un puntatore a un task_struct) del processo in esecuzione corrente. Come l'attuale ottiene i suoi e' dipendente dall'architettura. Su un x86 questo e' fatto dalla funzione current_thread_info() dentro asm/thread_info.h

   /* how to get the thread information struct from C */
   static inline struct thread_info *current_thread_info(void)
   {
           struct thread_info *ti;
           __asm__("andl %%esp,%0; ":"=r" (ti) : "0" (~(THREAD_SIZE - 1)));
           return ti;
   }

In fine le attuali dereferenze il membro del task della struttura thread_info che e' riprodotta sotto da asm/thread_info.h dal current_thread_info()->task;

   struct thread_info {
           struct task_struct      *task;          /* main task structure */
           struct exec_domain      *exec_domain;   /* execution domain */
           unsigned long           flags;          /* low level flags */
           unsigned long           status;         /* thread-synchronous flags */
           __u32                   cpu;            /* current CPU */
           int                     preempt_count;  /* 0 => preemptable, <0 => BUG */
   
   
           mm_segment_t            addr_limit;     /* thread address space:
                                                      0-0xBFFFFFFF for user-thread
                                                      0-0xFFFFFFFF for kernel-thread
                                                   */
           void                    *sysenter_return;
           struct restart_block    restart_block;
   
           unsigned long           previous_esp;   /* ESP of the previous stack in case
                                                      of nested (IRQ) stacks
                                                   */
           __u8                    supervisor_stack[0];
   };

Usando l'attuale macro e init_task e' possibile scrivere un modulo del kernel per la tracciatura dall'attuale processo in dietro fino a init.

/*
Traceroute to init
   traceinit.c
Robert Love Chapter 3
*/
  #include < linux/kernel.h >
  #include < linux/sched.h >
  #include < linux/module.h >
 
  int init_module(void)
  {
  struct task_struct *task;
  
   for(task=current;task!=&init_task;task=task->parent)
   //current is a macro which points to the current task / process
   {
   printk("%s [%d]\n",task->comm , task->pid);
   }
  
   return 0;
   }
   
   void cleanup_module(void)
   {
   printk(KERN_INFO "Cleaning up 1.\n");
   }

Bene, abbiamo appena iniziato nella conoscenza di una delle astrazioni fondamentali di un sistema linux — il processo. In un (possibile) futuro verra' ampliato, daremo uno sguardo ad altri aspetti.

'Till then, Happy hacking!

Altre risorse:

     obj-m +=ProcessList.o
     obj-m +=traceinit.o
     all:
             make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
     
     clean:
             make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Talkback: Discuss this article with The Answer Gang


Bio picture

L'autore e' al 3 anno di Computer Engineering Undergraduate in Haldia Institute of Technology, Haldia. Nei suoi interessi include Protocolli di rete, Network Security, Sistemi Operativi, e Microprocessori. E' un fan di Linux e amante hacker del kernel Linux.