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

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

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

Так:

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

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

  • Значение pid_namespace
  • Значение task_struct
  • Значение upid (смотрите также pid просто под ним)

1 ответ

Решение

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

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

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

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

Таким образом, вы создаете свое собственное частное дерево процессов, а затем назначаете его конкретному пользователю и / или конкретной задаче. Внутри этого дерева процессам не нужно беспокоиться о том, что 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 для нового fork процесс ред. Краткий фрагмент этой функции:

    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.

Другие вопросы по тегам