В MemSQL мы стремимся создавать потрясающее программное обеспечение и всегда пытаемся решить сложные проблемы. Несколько дней назад я с коллегами раскрыл интересную загадку Linux и исправил ее. Мы подумали, что обмен этим опытом может принести пользу другим.

Место преступления

При разработке внутреннего инструмента для получения трассировки стека мы решили использовать системный вызов SYS_tgkill Linux для отправки сигналов в определенные потоки. Системный вызов tgkill отправляет сигнал определенному потоку на основе его идентификатора «группы потоков» (tgid) и идентификатора потока (tid). Мы храним идентификатор потока для каждого потока, поэтому его было легко получить, но идентификатор «группы потоков» был для меня новой концепцией.

Поиск в Google показал, что простой способ получить tgid — прочитать его из псевдофайла Linux /proc/self/status, в котором содержится некоторая информация о процессе:

cat /proc/self/status
Name:   cat
State:  R (running)
Tgid:   26473
Ngid:   0
Pid:    26473
PPid:   26378
... <snip> ...

В первом прототипе этого внутреннего инструмента использовался системный вызов Linux SYS_getpid для получения идентификатора процесса, поиска правильного псевдофайла статуса и рудиментарного чтения непосредственно из этого файла. В прототипе предполагалось, что tgid всегда находится в третьей строке.

Это сработало для большинства разработчиков в MemSQL, но некоторые разработчики использовали среды с более новыми дистрибутивами Linux, и в этих случаях это не сработало.

Недавняя фиксация Linux добавила новое поле в /proc/self/status before Tgid, что сломало прототип:

commit 3e42979e65dace1f9268dd5440e5ab096b8dee59
Author: Richard W.M. Jones <[email protected]>
Date:   Fri May 20 17:00:05 2016 -0700
   procfs: expose umask in /proc/<PID>/status
... <snip> ...
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -162,6 +176,10 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
       ngid = task_numa_group_id(p);
       cred = get_task_cred(p);
+       umask = get_task_umask(p);
+       if (umask >= 0)
+               seq_printf(m, "Umask:\t%#04o\n", umask);
+
       task_lock(p);
       if (p->files)
               max_fds = files_fdtable(p->files)->max_fds;
       task_unlock(p);
       rcu_read_unlock();
       seq_printf(m,
               "State:\t%s\n"
               "Tgid:\t%d\n"
               "Ngid:\t%d\n"

Мотив

Чтобы стабилизировать инструмент, мы решили узнать больше об идентификаторах групп потоков, чтобы попытаться найти более стабильный способ их чтения. Мои коллеги заметили, что tgid всегда соответствует pid процесса (идентификатор потока исходного родительского потока), поэтому мы начали смотреть на взаимосвязь между атрибутами. Действительно, в Справочнике Linux по группам потоков говорится:

Группы потоков — это функция, добавленная в Linux 2.4 для поддержки понятия потоков POSIX как набора потоков с общим PID. Внутри этот общий PID является так называемым идентификатором группы потоков (TGID) для группы потоков. Начиная с Linux 2.4, вызовы getpid(2) возвращают TGID вызывающей стороны.

Мы задались вопросом, почему /proc/self/status сообщил и о Tgid, и о Pid?

Виновник

Я посмотрел на реализацию /proc/self/status в fs/proc/array.c, чтобы понять разницу между Tgid и Pid:

tgid = task_tgid_nr_ns(p, ns);
... <snip> ...
seq_put_decimal_ull(m, "\nTgid:\t", tgid);
seq_put_decimal_ull(m, "\nNgid:\t", ngid);
seq_put_decimal_ull(m, "\nPid:\t", pid_nr_ns(pid, ns));

task_tgid в include/linux/sched.h делает именно так, как я и ожидал, просто читая pid ведущего процесса:

static inline struct pid *task_tgid(struct task_struct *task)
{
   return task->group_leader->pids[PIDTYPE_PID].pid;
}

Элементарно, мой дорогой Ватсон

Оказалось, что нам вообще не нужно было читать псевдофайл status, а вместо этого можно было напрямую использовать getpid. Это изменение заставило его работать во всех средах, которые мы тестировали, и значительно упростило код.

В MemSQL у меня была возможность исследовать такие маленькие загадки систем, как эта, а также проектировать и работать над самыми современными системами. Если это звучит как то, что вам понравится, мы в настоящее время ищем инженеров. Подайте заявку и присоединяйтесь к команде http://www.memsql.com/jobs/.

Первоначально опубликовано на blog.memsql.com.