Библиотека сайта rus-linux.net
Знакомство с процессами в 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; };
В том же файле определены еще несколько макросов и функций, которые вы можете использовать для того, чтобы манипулировать связанными списками. Тем самым стандартизовано применение связанных списков, что избавляет людей от необходимости "изобретать велосипед" и вносить в свой код новые ошибки.
Приведем несколько ссылок на источники, имеющие отношение к связанным спискам ядра:
- Великолепный очерк дал в своем блоге Suman Adak.
- Исходные коды ядра Linux: <include/linux/list.h>, <include/linux/sched.h>
- "Linux Kernel Development", by Robert Love (Appendix A)
- http://www.win.tue.nl/~aeb/linux/lk/lk-2.html
Список задач ядра
Теперь давайте посмотрим, как ядро 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!
Другие ресурсы:
- The Linux Kernel Module Programming Guide 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
Опубликовано в 133-ем выпуске Linux Gazette, декабрь 2006
Добавление от переводчика: Если хотите подробнее познакомиться с понятиями процесса и потока в Linux (UNIX), прочитайте следующие статьи:
- Глава 2 в книге: Тигран Айвазян (Tigran Aivazian), "Внутреннее устройство Ядра Linux 2.4", 23 August 2001 г., перевод: Андрей Киселев.
- В. Хименко Процессы, задачи, потоки и нити В статье подробно рассказывается о том, что такое процессы, задачи, потоки и нити в UNIX (Linux)...
- Лев Пяхтин, "Процессы и нити в ОС Linux"
- В.А.Костромин, "Процессы и демоны в Linux".
- А. Калинин, Потоки Что такое потоки в Unix и каких типов они бывают
- "Процессы в Linux"
- Б.А. Державец, "Реализация многопотокового "асинхронного сервера TCP" и RPC для ОС Linux" Пример реализации многопотокового (LinuxThreads) эхо-сервера под Linux, основанного на использовании неблокирующего ввода вывода (мультиплексирование через select()).
- Sandeep Grover, перевод : Pukhlyakov Kirill, "О файловой системе /proc"
- "Содержимое /proc, связанное с настройкой Firewall в Linux " Из этой статьи можно почерпнуть полезные сведения о файловой системе /proc.
- Д.Колисниченко, "Процессы в Linux" Рассказ о состояниях процесса и программе top.
- Владимир Калюжный, Владимир Тарасенко, "Многопотоковые вычисления в системе Linux" Linux поддерживает многозадачность и многопотоковость, т.е. в системе одновременно может работать несколько задач (процессов), и каждая из задач может выполнятся в несколько потоков.
- Raghu J Menon, Перевод: Андрей Киселев, "Очереди сообщений, часть 1."
- Перевод LogRus, "Родная Библиотека Потоков POSIX для Linux"
- Dru Lavigne, перевод Станислава Лапшанского, "Исследуем процессы", часть 1, часть 2 часть 3. В этих статьях рассматриваются процессы в ОС FreeBSD.