Какая связь между `task_struct` и `pid_namespace`?

Я изучаю некоторый код ядра и пытаюсь понять, как структуры данных связаны друг с другом. Я знаю основную идею того, как работает планировщик, и что такое PID. Тем не менее, я понятия не имею, что такое пространство имен в этом контексте, и не могу понять, как все это работает вместе.

Я прочитал некоторые пояснения (в том числе части O'Reilly «Понимание ядра Linux») и понимаю, что может случиться так, что один и тот же PID попал в два процесса, потому что один из них завершился, а идентификатор был перераспределен. Но я не могу понять, как все это делается.

So:

  1. Что такое пространство имен в этом контексте?
  2. Какая связь между task_struct и pid_namespace? (Я уже понял, что это связано с pid_t, но не знаю как)

Некоторые ссылки:

  • Определение pid_namespace
  • Определение task_struct
  • Определение upid (см. также pid просто под этим)

person Ramzi Khahil    schedule 06.11.2014    source источник


Ответы (1)


Возможно, эти ссылки могут помочь:

  1. пространства имен PID в работе
  2. Краткое введение в пространства имен PID (эта один исходит от системного администратора)

После прохождения второй ссылки становится понятно, что пространства имен — отличный способ изолировать ресурсы. И в любой ОС, включая Linux, процессы являются одним из самых важных ресурсов. По его собственным словам

Да, все, с этим пространством имен можно перезапустить нумерацию PID и получить свой процесс «1». Это можно рассматривать как «chroot» в дереве идентификаторов процессов. Это очень удобно, когда вам нужно иметь дело с PID в повседневной работе и застряли с 4-значными числами…

Таким образом, вы как бы создаете собственное частное дерево процессов, а затем назначаете его конкретному пользователю и/или конкретной задаче. Внутри этого дерева процессам не нужно беспокоиться о конфликте PID с PID за пределами этого «контейнера». Следовательно, это так же хорошо, как передать это дерево другому «корневому» пользователю. Этот прекрасный парень проделал замечательную работу по объяснению вещей с помощью небольшого милого примера в довершение всего, поэтому я не буду повторяться здесь.

Что касается ядра, я могу дать вам несколько советов для начала. Я не эксперт здесь, но я надеюсь, что это должно помочь вам в некоторой степени.

В этой статье LWN описывается старый и новый взгляд на PID. Своими словами:

Все PID, которые могут быть у задачи, описаны в файле struct pid. Эта структура содержит значение идентификатора, список задач, имеющих этот идентификатор, счетчик ссылок и узел хешированного списка, который необходимо сохранить в хеш-таблице для более быстрого поиска. Еще несколько слов о списках задач. По сути, задача имеет три идентификатора PID: идентификатор процесса (PID), идентификатор группы процессов (PGID) и идентификатор сеанса (SID). PGID и SID могут совместно использоваться задачами, например, когда две или более задач принадлежат одной группе, поэтому каждый идентификатор группы относится к более чем одной задаче. С пространствами имен PID эта структура становится гибкой. Теперь у каждого PID может быть несколько значений, каждое из которых допустимо в одном пространстве имен. То есть задача может иметь PID 1024 в одном пространстве имен и 256 в другом. Итак, бывший struct pid меняется. Вот как выглядело struct pid до введения пространств имен PID:

struct pid {
 atomic_t count;                          /* reference counter */
 int nr;                                  /* the pid value */
 struct hlist_node pid_chain;             /* hash chain */
 struct hlist_head tasks[PIDTYPE_MAX];    /* lists of tasks */
 struct rcu_head rcu;                     /* RCU helper */
};

А вот как это выглядит сейчас:

struct upid {
   int nr;                            /* moved from struct pid */
   struct pid_namespace *ns;          /* the namespace this value
                                       * is visible in */
   struct hlist_node pid_chain;       /* moved from struct pid */
};

struct pid {
   atomic_t count;
   struct hlist_head tasks[PIDTYPE_MAX];
   struct rcu_head rcu;
   int level;                     /* the number of upids */
   struct upid numbers[0];
};

Как видите, struct upid теперь представляет значение PID -- оно хранится в хеше и имеет значение PID. Чтобы преобразовать struct pid в PID или наоборот, можно использовать набор помощников, таких как task_pid_nr(), pid_nr_ns(), find_task_by_vpid() и т. д.

Хотя эта информация немного устарела, она достаточно справедлива, чтобы вы могли начать. Здесь нужно упомянуть еще одну важную структуру. Это struct nsproxy. Эта структура является фокусом всего пространства имен по отношению к процессам, с которыми она связана. Он содержит указатель на пространство имен PID, которое будут использовать дочерние процессы этого процесса. Пространство имен PID для текущего процесса находится с помощью task_active_pid_ns.

Внутри struct task_struct у нас есть прокси-указатель пространства имен с метким названием nsproxy, который указывает на структуру struct nsproxy этого процесса. Если вы проследите шаги, необходимые для создания нового процесса, вы сможете найти отношения между task_struct, struct nsproxy и struct pid.

Новый процесс в Linux всегда разветвляется из существующего процесса, а его образ позже заменяется с помощью execve (или аналогичных функций из семейства exec). Таким образом, как часть do_fork вызывается copy_process.

В рамках копирования родительского процесса происходят следующие важные вещи:

  1. task_struct сначала дублируется с помощью dup_task_struct.
  2. пространства имен родительского процесса также копируются с использованием copy_namespaces. Это также создает новую структуру nsproxy для дочернего элемента, и его указатель nsproxy указывает на эту вновь созданную структуру.
  3. Для процесса, отличного от INIT (исходный глобальный PID, также известный как первый процесс, созданный при загрузке), структура PID выделяется с использованием alloc_pid, что фактически выделяет новую структуру PID для нового процесса forked. Короткий фрагмент этой функции:

    nr = alloc_pidmap(tmp);
    if(nr<0)
       goto out_free;
    pid->numbers[i].nr = nr;
    pid->numbers[i].ns = tmp;
    

Это заполняет структуру upid, присваивая ей новый PID, а также пространство имен, которому она в настоящее время принадлежит.

Кроме того, как часть функции copy process, этот вновь выделенный PID затем связывается с соответствующим task_struct через функцию pid_nr, т. е. его глобальный идентификатор (который является исходным номером PID, как видно из пространства имен INIT) сохраняется в поле pid в task_struct.

На заключительных этапах copy_process устанавливается связь между task_struct и этой новой структурой pid через поле pid_link внутри task_struct через функцию attach_pid.

Это еще не все, но я надеюсь, что это даст вам хоть какой-то старт.

ПРИМЕЧАНИЕ. Я имею в виду последнюю (на данный момент) версию ядра, а именно. 3.17.2.

person HighOnMeat    schedule 10.11.2014