Skip to content

Latest commit

 

History

History
355 lines (320 loc) · 20.7 KB

Linux Process.md

File metadata and controls

355 lines (320 loc) · 20.7 KB

Процессы Linux

Процесс - исполняемый экземпляр программы. Процессы могут быть порождены другими процессами, породить ещё процессы, умереть или быть убитыми. Когда процесс создаётся, он почти идентичен своему родителю: получает копию адресного пространства процесса-родителя на момент создания ребёнка. У отца и ребёнка свои собственные данные(стек, куча, структуры данных, код(возможно) - копия), потому изменение ребёнком своих данных не видно родителю и наоборот.

Во время эволюции Unix систем появились пользовательские POSIX потоки - pthread, которые совместно используют большое количество структур данных. Однако первоначальная реализация потоков была не очень удачна, что связано с тем что их планировкой занимались пользователи. Тогда были созданы облегчённые процессы. По сути те же потоки, только их планирование осуществляется ядром и, как следствие, их проще синхронизировать. Сейчас c каждым потоком ассоциирован облегчённый процесс.

В Linux есть такое понятие, как группа потоков - набор облегчённых процессов, которые действуют, как единое целое по отношению к системам вызовам: getpid(), kill(), _exit().

Каждый процесс(облегчённый тоже) описывается с помощью структуры struct task_struct(task_t). Структура полностью описывает всё состояние, атрибуты процесса и многое другое, и она ОЧЕНЬ большая, её описание можно найти в файле include/linux/sched.h.

task_struct
thread_info
state /*-1 unrunnable, 0 runnable, >0 stopped */
*stack
usage
flags
sched_info sched_info
mm_struct mm / Указатели на дескрипторы областей памяти*/
mm_struct *active_mm
pid_t pid /* Идентификатор процесса*/
pid_t tgid /* Идентификатор группы процесса*/
parent / recipient of SIGCHLD, wait4() reports */
real_parent / real parent process */
list_head children /* list of my children */
fs_struct fs / filesystem information */
files_struct files / open file information */
/* signal handlers */
list_head sibling; /* linkage in my parent's children list */
list_head tasks /* Общий список процессов*/
……………

(include/linux/sched.h)

Рассмотрим некоторые поля:

  1. @parent -- это тот task, которому надо отправлять уведомления (например, сигнал SIGCHLD) о данном task_struct.
  2. @real_parent -- всегда тот task_struct, который породил данный task. В большинстве случаев parent == real_parent. Но если мы подцепились к task_struct-у отладчиком, то parent-ом будет отладчик.
  3. @children -- список из детей данного task_struct-а.
  4. @sibling -- list_head-узел в списке (см. описание списков list_head) children твоего родителя
  5. @tasks -- list_head-узел в списке всех процессов (голова находится в idle-таске). В ядре иногда возникают задачи, когда нужно перебрать все процессы в системе (например, чтобы найти жертву, когда памяти не хватает, см. mm/oom_kill.c) соответственно, для данного перебора можно написать поиск в глубину (в ширину), зная родственные отношения процессов в ядре, однако в ядре особо не заморачиваются, поэтому проходятся по списку tasks.

Процесс может находится в различных состояниях во время своего существования. Например:

#define TASK_RUNNING    0

Процесс либо выполняется, либо готов в любой момент выполняться и ждёт своего времени.

#define TASK_INTERRUPTIBLE  1

Процесс приостановлен, пока не произойдёт определённое событие, которое должно его разбудить (например, пришли данные в pipe, а объект pipe знает, кто его ждет, pipe и занимается пробуждением процесса из состояния INTERRUPTIBLE). Также из этого состояния процесс пробуждается, если приходит сигнал.

#define TASK_UNINTERRUPTIBLE    2

Это состояние похоже на предыдущее, но приход сигнала не пробуждает процесс (даже SIGKILL не разбудит), процесс в этом состоянии прервать нельзя. Используется редко, когда ожидаемое событие долго ждать, но оно гарантированно конечно. Например, чтение файлов устройств(IO), времени, взятие mutex.

#define __TASK_STOPPED      4

Процесс переходит в это состояние после получения сигнала SIGSTOP и похожих.

#define __TASK_TRACED 8 // выполнение приостановлено отладчиком ptrace().

У каждого процесса есть поле - int exit_state, которое содержит состояние, с которым процесс завершил своё выполнение. После того, как процесс завершает работу ядро переводит его в состояние "Зомби":

#define EXIT_ZOMBIE 32

В состояние "Зомби" процесс будет находиться до тех пор, пока его родитель не прочитает его состояние с помощью wait4(), waitpid() и т.д.

#define EXIT_DEAD 16

Последнее состояние жизни процесса, в которое он переходит после того, как кто-нибудьвыполнит к нему wait4, waitpid().

Схема состояний и переходов между ними процесса; о состоянии процесса можно узнать с помощью утилиты ps:

_____                     _______
| R | <—————————————————> | r/q | <—————————————————————————\
————— ———\______________  ———————    /\                      \
  | exit/kill           \_____________|________________      |
  \/                  read \/         |      IO/mutex |      |
_____                   ________ _____|               |      |
| Z | <————————kill()———|  W/S | interruptible        \/     |
—————       /\          ————————                    _____ ___/
  | wait()   | ———————————————— NO_WAY ———————————— | D | uninterruptible
  \/                                                —————
________
| dead |
-———————

Состояния R (running), r/q (ready, queued), W/S(wait/sleep) -- для userspace В ядре состояния R, r/q объединяются в RUNNING.

Максимальное количество процессов обычно 2^15 = 32768. По умолчанию можно получить из макроса #define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000) (include/linux/threads.h). Так же количество доступных процессов может быть изменено системным администратором в /proc/sys/kernel/pid_max/. На 64 разрядных машинах максимальное значение можно увеличить до 4 * 1024 * 1024.

Для повторного использования pid’ов ядро поддерживает pidmap(include/linux/ pid_namespace.h)

struct pidmap {
       atomic_t nr_free;
       void *page;
};

task_struct существует для каждого процесса, даже облегчённого, pid у них у всех разный. Однако с потоками надо работать, как с единой сущностью, например, посылая сигнал. Потому у каждого потока в группе есть общий идентификатор - pid лидера группы и хранится он в поле tgid (thread group id), и системный вызов getpid() возвращает именно его.

Для каждого процесса ядро хранила struct thread_info, содержащую указатель на task_struct, и стэк режима ядра. thread_info - платформозависимая структура, потому искать её в <asm/thread_info.h>. Для x86 для ядра 2.6 выглядела вот так:

struct thread_info {
        struct task_struct          *task;
        struct exec_domain      *exec_domain;
        unsigned long               flags;
        unsigned long               status;
        __u32                           cpu;
        __s32                           preempt_count;
        mm_segment_t            addr_limit;
        struct restart_block    restart_block;
        unsigned long               previous_esp;
        __u8                            supervisor_stack[0];
};

Раньше размер стэка ядерного режима был 4Кб и task_struct хранился в нём.

LOW                             HIGH
_________________________________
|             |        |        |
| task_struct |  <———— | stack  |
|             |        |        |
—————————————————————————————————

Для 32битной системы sizeof(task_struct) ~ 1.7 Kb, что было довольно критично, поэтому ввели thread_info, который указывает на task_struct, также увеличили стэк ядерного режима до 8Kb.

LOW                                                   HIGH
__________________________________________________________
|             |                                 |        |
| thread_info |            <———————————————————-| stack  |
|             |                                 |        |
——————————————————————————————————————————————————————————

Однако в процессе эволюции thread_info переместилось в окончательно в task_struct, а на структуру указывает регистр gs.

LOW                                                   HIGH   ______
__________________________________________________________   | gs |
|                                               |        |   ——————
|                          <———————————————————-| stack  |      \-> current
|                                               |        |
——————————————————————————————————————————————————————————

Текущий процесс, запущенный на процессоре можно получить с помощью макроса current() <asm/current.h>:

#define current get_current()
static __always_inline struct task_struct *get_current(void)
{
    return this_cpu_read_stable(current_task);
}
#define this_cpu_read_stable(var)   percpu_stable_op("mov", var)

Так же надо заметить, что thread_info должна быть самой первой в task_struct, чтобы имея адрес одной в регистре fs, иметь адрес двух структур сразу, что можно заметить в макросе:

#define current_thread_info() ((struct thread_info *)current)

(include/linux/thread_info.h)

Списки процессов: Для дальнейшего понимания происходящего необходимо разобраться со списками Linux и технологией RCU. Вы сможете это сделать прочитав, например, соседние файлы: Linux Lists.txt и Linux Synchronization.txt

Все процессы выстроены в двунаправленный список, для этого каждая структура task_struct включает в себя поле — struct list_head tasks. В голове списка находится дескриптор init_task с pid = 0.

Пройтись по списку процессов можно с помощью макроса for_each_process(p) (include/linux/sched.h):

#define next_task(p) \
    list_entry_rcu((p)->tasks.next, struct task_struct, tasks)

#define for_each_process(p) \
    for (p = &init_task ; (p = next_task(p)) != &init_task ; )

(include/linux/rculist.h)
/**
 * list_entry_rcu - 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_head within the struct.
 *
 * This primitive may safely run concurrently with the _rcu list-mutation
 * primitives such as list_add_rcu() as long as it's guarded by rcu_read_lock().
 */
#define list_entry_rcu(ptr, type, member) \
    container_of(lockless_dereference(ptr), type, member)

При создании процесса, новый добавляется в конец списка процессов (kernel/fork.c —> copy_procces):

list_add_tail_rcu(&p->tasks, &init_task.tasks);

Изучив структуру task_struct, мы можем отыскать ещё списки в её составе:

    struct list_head children;  /* list of my children */
    struct list_head sibling;   /* linkage in my parent's children list */

Данные списки описывают какие отношения связывают наш процесс с другими процессами. Список — children содержит детей нашего процесса, а sibling — братьев — других процессов созданных нашим родителем.

Процесс 0 — предок всех процессов. Его описывает статическая структура: struct task_struct init_task = INIT_TASK(init_task); (/init/init_task.c и макрос в/linux/init_task.h)

Структура инициализируется при старте функцией — start_kernel(void) (/init/main.c). После чего процесс 0 инициализирует подсистемы, создаёт процесс с pid == 1 — init.

Функция kernel_thread(kernel_init, NULL, CLONE_FS) - создаст клона, который будет исполнять функцию kernel_init. После этого процесс 0 вызовет cpu_idle() и будет работать вхолостую, просыпаясь только если другие процессы не могут быть исполнены. Созданный поток ядра завершает инициализацию ядра, а после загружает исполняемую программу init.

Управление процессами

Как мы видели раньше у каждого процесса может быть группа потоков исполнения. У каждого потока свой собственный pid, но все потоки должны реагировать на событие, как одна сущность потому есть tgid = pid лидера группы потоков.

Различные процессы объединяются в группы процессов, для того чтобы взаимодействовать с ними, как с единой сущностью есть pgid = pid процесса лидера. В группу процессов входят не только родственники, но и процессы объединённые с помощью конвейера process | process …

Группы процессов в свою очередь объединяются в сессию, сессии связаны с терминалом. И, как можно догадаться, sid = pid лидера сессии.

            _________session________
           /            |           \
 process groupe(pg)     pg          pg
    /       |      \
process(p)  p       p
          / | \
 thread(t)  t  t

Для удобства и скорости управления процессами на этапе инициализации ядра создаётся 4 хэш-таблицы: для каждого потока (различные pid), для каждого лидера группы(tgid = pid), для каждого лидера группы процессов(pgid = pid), для каждого лидера сессии(sid = pid).

Хэш таблица эффективнее массива работает с памятью, так как количество процессов в системе зачастую меньше чем их максимальное количество.

pid_hash(hlist_head)
__________
|        |    pid                    pid
——————————    ______________         ______________
|        | —> |            |    / —> |            |
——————————    ——————————————   /     ——————————————
|        |    | hlist_node | —/      | hlist_node |
| …      |    ——————————————         ——————————————
——————————    | hlist_head | ———\
              ——————————————     \————> список task_struct tgid = nr
                                  \ ————> список task_struct pgid = nr
                                   \ ————> список task_struct sid = nr

(/include/linux/pid.h)

enum pid_type
{
    PIDTYPE_PID,
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

struct upid {
    /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
    int nr;
    struct pid_namespace *ns;
    struct hlist_node pid_chain;
};

struct pid
{
    atomic_t count;                 // счётчик ссылок на структуру
    unsigned int level;
    /* lists of tasks that use this pid */
    struct hlist_head tasks[PIDTYPE_MAX];
    struct rcu_head rcu;
    struct upid numbers[1];
};