Знакомство с процессами в Linux
By Как увидеть ссылки? | How to see hidden links?, LinuxGazette, 133, декабрь 2006.
Перевод на русский язык: В.А.Костромин,
Как увидеть ссылки? | How to see hidden links?.
Оригинал статьи:
Как увидеть ссылки? | How to see hidden links?
Итак, что такое "процесс"?
Цитирую книгу Роберта Лава (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 здесь ничего не означает - подробнее смотрите
Как увидеть ссылки? | How to see hidden links?.
Связанные списки
Прежде чем перейти к рассмотрению того, как задачи/процессы (мы будем использовать эти два термина как синонимы) сохраняются в ядре, нам нужно понять, как используются в ядре циклические связанные списки. Приведенная ниже реализация является стандартной и используется во всех исходных кодах ядра. Связанные списки объявлены в
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;
Следующий рисунок иллюстрирует представление процессов в памяти в виде связанного списка:
Имеется несколько макросов и функций, которые помогают нам перемещаться по этому списку:
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
Опубликовано в
Как увидеть ссылки? | How to see hidden links? выпуске
Как увидеть ссылки? | How to see hidden links?, декабрь 2006