Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

Linux Gazette ...making Linux just a little more fun!

Знакомство с процессами в Linux

By Amit Saha, LinuxGazette, 133, декабрь 2006.
Перевод на русский язык: В.А.Костромин, http://rus-linux.net.
Оригинал статьи: "Learning about Linux Processes"

Итак, что такое "процесс"?

Цитирую книгу Роберта Лава (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 здесь ничего не означает - подробнее смотрите http://www.kcomputing.com/volatile.html.

Связанные списки

Прежде чем перейти к рассмотрению того, как задачи/процессы (мы будем использовать эти два термина как синонимы) сохраняются в ядре, нам нужно понять, как используются в ядре циклические связанные списки. Приведенная ниже реализация является стандартной и используется во всех исходных кодах ядра. Связанные списки объявлены в linux/list.h и их структура очень проста:

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

В том же файле определены еще несколько макросов и функций, которые вы можете использовать для того, чтобы манипулировать связанными списками. Тем самым стандартизовано применение связанных списков, что избавляет людей от необходимости "изобретать велосипед" и вносить в свой код новые ошибки.

Приведем несколько ссылок на источники, имеющие отношение к связанным спискам ядра:

Список задач ядра

Теперь давайте посмотрим, как ядро Linux использует дву-связные списки для хранения записей о процессах. Поиск struct list_head внутри определения struct task_struct дает нам следующую строку:

struct list_head tasks;

Эта строка показывает, что ядро использует циклический связанный список для хранения задач. Это означает, что мы можем использовать стандартные макросы и функции для работы со связанными списками с целью просмотра полного списка задач.

Как известно, "отцом всех процессов" в системе Linux является процесс init. Так что он должен стоять в начале списка, хотя, строго говоря, начала не существует раз речь идет о циклическом списке. Дескриптор процесса init задается статично (is statically allocated):

extern struct task_struct init_task;

Следующий рисунок иллюстрирует представление процессов в памяти в виде связанного списка:

Linked List Figure

Имеется несколько макросов и функций, которые помогают нам перемещаться по этому списку:

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!

Другие ресурсы:

     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

Опубликовано в 133-ем выпуске Linux Gazette, декабрь 2006

Copyright (C) 2006, Amit Saha. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.


Добавление от переводчика: Если хотите подробнее познакомиться с понятиями процесса и потока в Linux (UNIX), прочитайте следующие статьи: