Библиотека сайта rus-linux.net
...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;
};
В том же файле определены еще несколько макросов и функций, которые вы можете использовать для того, чтобы манипулировать связанными списками. Тем самым стандартизовано применение связанных списков, что избавляет людей от необходимости "изобретать велосипед" и вносить в свой код новые ошибки.
Приведем несколько ссылок на источники, имеющие отношение к связанным спискам ядра:
- Великолепный очерк дал в своем блоге 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.
