Пошаговый анализ кода

Давайте взглянем на основные части исходного кода утилиты командной строки.

Мы не будет обсуждать функциональность main.c — это стандартный код и в большой степени он понятен и без объяснений. Для деталей реализации разбора командной строки посмотрите описание на getopt() из библиотеки C.

Все интересное начинается в procfs.c внутри функции show_tfp_name(). Вот сокращенная версия этой функции, показывающая принцип ее работы:

void
show_tfp_name (char *n)
{
struct dirent *dirent;
DIR *dir;
int fd;
int r;
int pid;

static struct {
procfs_debuginfo info;
char buff [PATH_MAX];
} name;

// 1) открыть директорию /proc
if (!(dir = opendir («/proc»))) {
fprintf (stderr,
«%s: невозможно открыть /proc, errno %d\n», progname, errno);
perror (NULL);
exit (EXIT_FAILURE);
}

// 2) пройтись по всем записям в директории
while (dirent = readdir (dir)) {

// 3) пропустить все нечисловые имена
if (isdigit (*dirent -> d_name)) {
pid = atoi (dirent -> d_name);
if (pid == 1) {
continue; // пропустить менеджер процессов
}

// 4) открыть файл с именем ID процесса
sprintf (paths, «/proc/%d/as», pid);
if ((fd = open (paths, O_RDONLY)) == -1) {
continue; // ничего нельзя с этим поделать
}

// 5) получить имя процесса
if (devctl (fd, DCMD_PROC_MAPDEBUG_BASE, &name;,
sizeof (name), 0) != EOK)
{
strcpy (name.info.path, «(n/a)»);
}

// 6) проверить это ли имя мы ищем (если n != NULL)
if (n) {
if (strlen (name.info.path) > strlen (n)) {
r = strcmp (name.info.path + (strlen (name.info.path)
— strlen (n)), n);
} else {
r = strcmp (name.info.path, n);
}
if (r) { // не совпало
close (fd);
continue; // переходим к следующему
}
}

// 7) выполняем обработку процесса
process_pid (pid, fd, name.info.path);
close (fd);
}
}
closedir (dir);
}

Как вы видите, основная операция это просто обработка записей в директории и сравнение:

Директория /proc для нас такая же как и любая другая — для ее чтения мы используем те же самые функции что и для нормальных директорий. Вызов opendir() дает нам дескриптор, который мы используем в последующих вызовах функций.
Мы используем функцию readdir() для чтения следующей записи директории. Эта функция возвращает NULL, если все записи прочитаны, и мы прерываем наш цикл while.
В файловой системе /proc есть несколько записей с нечисловыми именами (например, boot, dumper, qnetstats, и self). Они нас не интересуют, так что мы пропускаем всё, чьё имя не начинается с цифры. Имена, начинающиеся с цифры, мы преобразуем из ASCII строки в номер (который мы потом используем на шаге 7). Мы пропускаем менеджер процессов, так как в версии Neutrino, на которой я разрабатывал TFP, его обработка давала неправильный результат — число ключей для него было 134511032 с первым ключом в позиции 6. Не одно из этих значений не производило впечатление правильного, так что лучше было просто игнорировать менеджер процессов. Так как файловая система в основном не документирована мы (к несчастью) вынуждены прибегать в коде к обработке таких «специальных случаев».
Записи с числовыми именами являются директориями, в каждой из которых существует файл с именем as (сокращение от «Address Space» — адресное пространство). Так как мы хотим покопаться именно в адресном пространстве процесса, мы открываем этот файл.
Используя devctl() DCMD_PROC_MAPDEBUG_BASE мы можем получить имя процесса. (Этот вызов дает нам много другой полезной информации, но в данном случае она нас не интересует; смотрите «Файловая система /proc» в приложении к книге.)
Мы сравниваем полученное имя с тем, которое передано как параметр. В этом есть немного магии, так как мы пытаемся сравнить только завершающий набор символов полученного имени.
На этом шаге мы имеем подходящее нам имя (или потому что оно совпало с переданным именем или потому что для поиска всех процессов был задан NULL). Мы вызываем функцию process_pid() для выполнения настоящей «работы».
Может показаться, что самое интересное сосредоточено в process_pid(), тоже из файла procfs.c, но все, что она делает — это нахождение количества потоков в процессе. Потом она вызывает process_tid() для каждого потока:

static int printed_header; // флаг для внутреннего использования

static void
process_pid (int pid, int fd, char *n)
{
procfs_status status;

printed_header = 0;

status.tid = 1;
while (1) {

// спросить менеджер процессов о потоке
if (devctl (fd, DCMD_PROC_TIDSTATUS, &status,
sizeof (status), 0) != EOK)
{
break; // выходим, если вызов провалился
} else {
if (status.state != STATE_DEAD) {
if (optv) { // если в режиме подробной печати
// просто отладочная информация
printf («P%d T%d S%d\n», pid, status.tid,
status.state);
}
process_tid (pid, status.tid, &status;, fd, n);
}
status.tid++;// смотри примечания
}
}
}
Единственная мудреная часть в этом коде это прохождение через все потоки процесса. Если вы запустите pidin в Neutrino, вы заметите, что потоки внутри процесса не всегда имеют непрерывную нумерацию. Вы может видеть потоки с 1 по 9, а потом с 11 до 13, с пропуском потока 10. Это все в порядке вещей и вполне нормально — просто поток номер 10 отработал и уже завершился.