Знакомство с процессами в Linux
By Для просмотра ссылки Войди или Зарегистрируйся,  LinuxGazette, 133, декабрь 2006. 
Перевод на русский язык: В.А.Костромин,  
Для просмотра ссылки Войди или Зарегистрируйся. 
Оригинал статьи: 
Для просмотра ссылки Войди или Зарегистрируйся  
Итак, что такое "процесс"?
  Цитирую книгу Роберта Лава (Robert Love) 
"Linux Kernel Development": "Процесс - одно из фундаментальнейших понятий операционной  системы Unix, наряду с другой такой же фундаментальной абстракцией - понятием  файла." 
Процесс - это исполняющаяся программа.  Он состоит из исполняемого программного кода, набора ресурсов (таких, как  открытые файлы), внутренних данных ядра, адресного пространства, одного или  нескольких потоков исполнения (или нитей - threads of execution) и секции  данных, содержащей глобальные переменные. 
Process Descriptors
  С каждым процессом связан (ассоциирован) "описатель процесса" или  
дескриптор процесса. Дескриптор содержит информацию, используемую для того,  чтобы отслеживать процесс в оперативной памяти. В частности, в дескрипторе  содержатся идентификатор процесса (PID), его состояние, ссылки на родительский и дочерние  процессы, регистры процессора, список открытых файлов и информация  об адресном пространстве.
  Ядро Linux использует циклически замкнутый двухсвязный список записей      struct task_struct для хранения дескрипторов процессов. Эта  структура объявлена в файле 
linux/sched.h. Ниже приведены несколько  полей этой структуры из ядра 2.6.15-1.2054_FC5, начиная со строки 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
 Первая строка структуры определяет поле state как   volatile long. Эта переменная используется для того, чтобы  отслеживать состояние выполнения процесса, определяемое одним из следующих  значений:
  #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
 Ключевое слово 
volatile здесь ничего не означает - подробнее  смотрите  
Для просмотра ссылки Войди или Зарегистрируйся.
Связанные списки
  Прежде чем перейти к рассмотрению того, как задачи/процессы (мы будем  использовать эти два термина как синонимы) сохраняются в ядре, нам нужно  понять, как используются в ядре циклические связанные списки. Приведенная ниже  реализация является стандартной и используется во всех исходных кодах ядра. Связанные списки объявлены в 
linux/list.h и их структура очень проста:
   struct list_head {
         struct list_head *next, *prev;
 };
 В том же файле определены еще несколько макросов и функций, которые вы можете  использовать для того, чтобы манипулировать связанными списками. Тем самым  стандартизовано применение связанных списков, что избавляет людей от  необходимости "изобретать велосипед" и вносить в свой код новые ошибки.  
  Приведем несколько ссылок на источники, имеющие отношение к связанным  спискам ядра:
- Для просмотра ссылки Войди или Зарегистрируйся дал в своем блоге Suman Adak.
 
- Исходные коды ядра Linux: <include/linux/list.h>, <include/linux/sched.h>
 
- "Linux Kernel Development", by Robert Love (Appendix A)
 
- Для просмотра ссылки Войди или Зарегистрируйся
 
Список задач ядра
  Теперь давайте посмотрим, как ядро Linux использует дву-связные списки  для хранения записей о процессах. Поиск struct list_head  внутри определения struct task_struct дает нам следующую строку: 
  struct list_head tasks;
 Эта строка показывает, что ядро использует циклический связанный список для  хранения задач. Это означает, что мы можем использовать стандартные  макросы и функции для работы со связанными списками с целью просмотра полного  списка задач. 
  Как известно, "отцом всех процессов" в системе Linux является процесс  
init. Так что  он должен стоять в начале списка, хотя, строго говоря, начала не существует раз  речь идет о циклическом списке. Дескриптор процесса 
init  задается статично (is statically allocated):
  extern struct task_struct init_task;
 Следующий рисунок иллюстрирует представление процессов в памяти  в виде связанного списка:
  
	
	
	
		
		
		
			
		
		
	
	
 
  Имеется несколько макросов и функций, которые помогают нам перемещаться  по этому списку:
  for_each_process() - это макрос, который проходит весь  список задач. Он определен в linux/sched.h следующим образом:
  #define for_each_process(p) \
        for (p = &init_task ; (p = next_task(p)) != &init_task ; )
 next_task() - макрос, определенный в linux/sched.h,  возвращает следующую задачу из списка:
  #define next_task(p)    list_entry((p)->tasks.next, struct task_struct, tasks)
 Макрос list_entry() определен в 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)
 Макрос container_of() определен следующим образом:
  #define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})
   Так что если мы просмотрим весь список задач, мы получим все процессы,  запущенные в системе. Это можно сделать с помощью макроса for_each_process(task),  где task - указатель типа struct task_struct. Вот пример модуля ядра, заимствованный из  
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");
    }
 Макрос current - это ссылка на дескриптор  (указатель на task_struct) текущего исполняющегося процесса. Каким образом  current выполняет свою задачу, зависит от архитектуры. Для x86 это делается с помощью функции current_thread_info(), определенной в  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;
   }
 Наконец, current разименовывает поле task структуры  thread_info, которая представлена ниже из asm/thread_info.h,  посредством 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];
   };
   Используя макрос current и init_task мы можем написать  модуль ядра, который будет прослеживать цепочку от текущего процесса до   
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");
   }
 Ну вот, мы только начали знакомство с одной из фундаментальных абстракций  в Linux-системе - понятием процесса. Возможно в будущем будет продолжение  этих заметок, в котором мы рассмотрим другие важные понятия. 
   До встречи, Happy hacking! 
Другие ресурсы:
- Для просмотра ссылки Войди или Зарегистрируйся for 2.6.x kernels
 
- Makefile, использованный для компиляции модулей ядра:
 
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
   Опубликовано в 
Для просмотра ссылки Войди или Зарегистрируйся  выпуске 
Для просмотра ссылки Войди или Зарегистрируйся, декабрь 2006