Как написать программу c использованием библиотеки Procps
Вернуться в Доки-Токи
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1.ЧТО ТАКОЕ PROCPSЯвляется пакетом, который основан на одноименной библиотеке procps, предоставляющей информацию о запущенных процессах и их потоков, использования оперативной памяти и другой системной информации, хранящейся в файловой системе /proc. Этот пакет так же включает программы PS, TOP, VMSTAT, W, KILL, FREE, SLABTOP, SKILL. Выше перечисленные программы являются системными утилитами, использующие API-функции(далее, по тексту функции) библиотеки procps. Функции библиотеки procps мы будем использовать для создания списка запущенных процессов и их потоков с последующим построением дерева на примере консольного приложения procps_ptree, на которое буду ссылаться постоянно в тексте этой статьи. С одним исключением, при указании использования заглавных файлов библиотеки procps будет использоваться запись, например как <proc/readproc.h>. 2.ЧТЕНИЕ СПИСКА ЗАПУЩЕННЫХ ПРОЦЕССОВДля чтения списка запущенных процессов используются функции openproc(), closeproc(), readproctab2() библиотеки procps. 2.1.ФУНКЦИЯ OPENPROC()Создает указатель на структуру PROCTAB, содержащей неизменяемые данные, передаваемые от вызова к вызову(call-to-call) ниже перечисленных функций библиотеки procps. Прототип: #include <proc/readproc.h> PROCTAB* openproc(int flags,... ); В первом аргументе flags передается вид информации, которую мы хотим получить. Флаги передаются c использованием логического сложения ИЛИ("|") и сохраняются в поле flags структуры PROCTAB. Они указываются для:
При указании флагов PROC_FILLUSR и PROC_FILLGRP для разрешения имен пользователей и групп к их идентификаторам используются базы passwd и group Службы переключения имён, Name Switch Service(NSS). Функция возвращает:
Пример использования функции openproc() можно посмотреть в файле main.c, строке 223 проекта procps_ptree. А назначение флагов в файле main.c, строки 188, 191, 195. 2.2.ФУНКЦИЯ CLOSEPROC()Функция производит закрытие файловой системы /proc c освобождением указателя на структуру PROCTAB. Прототип: #include <proc/readproc.h> void closeproc(PROCTAB* PT); В единственном аргументе PT данной функции передается указатель на структуру PROCTAB, который был ранее создан функцией openproc(). Пример вызова функции closeproc() приведен в файле main.c, строка 272, проекта procps_ptree. 2.3.ФУНКЦИЯ READPROCTAB2()Производит чтение всего массива информации о процессах, в т.ч. запущенных ими потоков, если функции openproc() был передан флаг PROC_LOOSE_TASKS. Функция имеет следующий прототип: #include <proc/readproc.h> static int want_this_proc_special ( proc_t *dummy ) {
return 1;
} static int want_this_task_special ( proc_t *dummy ) {
return 1;
} proc_data_t *readproctab2( int(*want_proc)(proc_t *buf), int(*want_task) (proc_t *buf), PROCTAB *restrict const PT); В первом и втором аргументе передаются функции-обработчики, обеспечивающие специальную обработку полей структуры proc_t, алгоритм обработки которых будет позднее описан в этой статье. В третьем аргументе PT передается указатель на структуру PROCTAB, который был ранее создан функцией openproc(). Для получение информации о запущенных процессах функция-обработчик want_this_proc_special() должна быть обязательна передана и возвращать не нулевое значение. Для получения сведений о запущенных потоков, по-мимо флага PROC_LOOSE_TASKS, должна быть передана функция want_this_task_special() и возвращать не нулевое значение. В третьем аргументе передается указатель структуры PROCTAB, созданный функцией openproc(). Функция readproctab2() будет всегда возвращать указатель на структуру proc_data_t, которая определена в typedef struct proc_data_t { proc_t **tab; /* Таблица указателей на информацию о всех процессах ( в т.ч. запущенных ими потоков) */ proc_t **proc; /* Выборка процессов из tab */ proc_t **task; /* Выборка потоков из tab */ int n; /* Количество всех процессов (в т.ч. запущенных ими потоков )*/ int nproc; /* Количество потоков */ int ntask; /* Количество процессов */ } proc_data_t; даже, если запущенных процессов/потоков нет в помине, что конечно же абсурдно, но в этом случае, поле n структуры proc_data_t будет равно нулю. Когда начал работать над это статьей, ошибочно считал, что На самом деле, перед завершением работы программы, нужно освобождать память, выделенную под структуру proc_data_t функцией readproctab2(), как это делает функция procps_tab_close() в файле main.c, строке 242, проекта procps_ptree. Для чего, начиная с строки 251 по 257 определяется начальный адрес массива структур proc_t, на который указывает указатель-на-указатель tab структуры proc_data_t. Именно он будет передан в строке 269 для высвобождения ранее выделенной памяти.
Не все поля структуры proc_t могут быть заполнены непосредственно прямым чтением из файлов директории /proc/<pid>, потому что требуют специальной обработки. Хотя многие из них, как мы увидим ниже, не требуют такой обработки и используются непосредственно. PS(1) позволяет выводить в определенном пользователе формате информацию о запущенных процессах и их потоках. В таблице, приведенной ниже, дается соответствие ключевых слов, заголовка формата вывода PS(1) к полям структуры proc_t:
3.2.ПОЛУЧЕНИЕ ИДЕНТИФИКАТОРА ПРОЦЕССАПоле tgid структуры proc_t содержит индентификатор процесса, но название это поле берет свое начало от идентификатора группы задач(task group ID) из других стандартов, которые применяли эту терминологию ранее чем стандарт POSIX. Значение tgid берется из названия директории /proc/<pid> во время выполнения readproctab2() и не требует какой-либо специальной обработки. static void pr_pid (char *restrict const outbuf,const proc_t *restrict const pp ) { (void)snprintf(outbuf, ROWLENGTH, "%6u", pp->tgid); } Пример использования функции pr_pid() можно посмотреть в файле output.c проекта procps_ptree. 3.3.ПОЛУЧЕНИЕ ИДЕНТИФИКАТОРА РОДИТЕЛЬСКОГО ПРОЦЕССАПоле ppid структуры proc_t содержит индентификатор родительского процесса. Заполняется при выполнении readproctab2() и не требует какой-либо специальной обработки. (void)snprintf(outbuf, ROWLENGTH, "%6u", pp->ppid); Пример использования функции pr_ppid() можно посмотреть в файле output.c проекта procps_ptree. 3.4.ПОЛУЧЕНИЕ ЭФФЕКТИВНОГО ИМЕНИ ПОЛЬЗОВАТЕЛЯПоле euser структуры proc_t содержит идентификатор родительского процесса. Заполняется при выполнении readproctab2() и не требует какой-либо специальной обработки. int rightward = 12;/* количество символов pp->euser escape_str() */ if ( flag & PROC_FILLUSR ) { /* При заполнении поля euser функция readproctab2() выполняет обращение к базе данных passwd службы NSS эквивалентно следующему: #include <pwd.h> struct passwd *restrict pwd; setpwent(); while ( (pwd=getpwent ()) ) { if( pwd->pw_uid == pp->euid ) {
escape_str(outbuf, pwd->pw_name, ROWLENGTH, &rightward);
}
}
endpwent();
*/
escape_str(outbuf, pp->euser, ROWLENGTH, &rightward);
return;
}
(void)snprintf ( outbuf, ROWLENGTH, "%12u", pp->euid );
} Пример использования функции pr_user() можно посмотреть в файле output.c проекта procps_ptree. 3.5.ПОЛУЧЕНИЕ ИДЕНТИФИКАТОРА ГРУППЫ ПРОЦЕССОВПоле pgid структуры proc_t содержит идентификатор группы процессов. Процессы объединяются в группы по принципу использования каналов межпроцессного взаимодействия. Поэтому у процессов одной группы могут быть разные PPID. Заполняется при выполнении readproctab2() и не требует какой-либо специальной обработки. (void)snprintf(outbuf, ROWLENGTH, "%6u", pp->pgid); Пример использования функции pr_pgid() можно посмотреть в файле output.c проекта procps_ptree. 3.6.ВЫЧИСЛЕНИЕ ЗАГРУЗКИ ПРОЦЕССОРА В ПРОЦЕНТАХНе смотря на то, что поле pcpu определено в структуры proc_t, оно не заполняется функцией readproctab2(), рассчитывается по следующему алгоритму: 1) рассчитать суммарное время использование ЦП: total_jiffies = (*proc_t)->utime + (*proc_t)->stime; utime суммарное время использование ЦП на пользовательском уровне в тиках, соотв. полю utime структуры proc_t; stime суммарное время использование ЦП на уровне ядра в тиках, соотв. полю stime структуры proc_t; 2) получить время в секундах с момента запуска системы: seconds_since_boot = uptime(NULL,NULL); функция uptime(NULL,NULL) библиотеки procps возвращает время в секундах с момента старта системы, время запуска процесса в тиках; 3) рассчитать время жизни процесса/потока в секундах, используя для этого поле start_time структуры proc_t: seconds = seconds_since_boot - (*proc_t)->start_time / Hertz; значение start_time приводится к секундам, которое равно jiffies/Hertz секунд, где jiffies - количество прерываний таймера, прошедших с момента запуска ядра ОС, а Hertz -количество прерываний системного таймера, которое содержит глобальная переменная Hertz библиотеки procps ( требует предварительного выполнения функции meminfo() библиотеки procps ); 4) расcчитать в процентном соотношении использование процессора: pcpu = (total_jiffies * 1000ULL / Hertz) / seconds; загрузка ЦП в процентах получается с точностью до двух цифр после запятой умножением на значение равным 1000ULL. Для заполнения полей utime, stime структуры proc_t при вызове функции readproctab2() необходимо передать флаг PROC_FILLSTAT при вызове openproc(). static voidpr_pcpu(char *restrict const outbuf, const proc_t *restrict const pp){ /* время в секундах жизни процесса */ unsigned long long seconds = seconds_since_boot - pp->start_time / Hertz; /* суммарное время использвание ЦП в тактах прерываний таймера */ unsigned long long total_jiffies = pp->utime + pp->stime; /* scaled %cpu, 999 means 99.9% */ unsigned int pcpu; if ( !seconds ) { /* исключая ошюку деления на нуль */ (void)snprintf(outbuf, ROWLENGTH, "0.0" ); return; } pcpu = (total_jiffies * 1000ULL / Hertz) / seconds; if (pcpu > 999U ) { (void)snprintf(outbuf, ROWLENGTH, "%u", pcpu/10U); return; } (void)snprintf(outbuf, ROWLENGTH, "%u.%u", pcpu/10U, pcpu%10U); } Пример использования функции pr_pcpu() можно посмотреть в файле output.c проекта procps_ptree. 3.7. ВЫЧИСЛЕНИЕ ОТНОШЕНИЕ РЕЗИДЕТНОГО НАБОРА ПРОЦЕССА К ФИЗИЧЕСКОЙ ПАМЯТИРассчитывается путем деления размера резидентного набора процесса, содержащегося в поле vm_rss структуры proc_t, на размер физической памяти, содержащейся в глобальной переменной kb_main_total библиотеки procps, которая требует предварительного вызова функции meminfo() библиотеки procps: %MEM = VM_RSS * 1000ULL / kb_main_total; полученное значение будет иметь точность до двух цифр после запятой, после выполнения умножения на значение равное 1000ULL. Для заполнения полей vm_rss структуры proc_t требуется передать флаг PROC_FILLSTAT при вызове openproc().
static voidpr_pmem(char *restrict const outbuf, const proc_t *restrict const pp){ /* scaled %mem, 999 means 99.9% */ unsigned long pmem = pp->vm_rss * 1000ULL / kb_main_total; if ( pmem > 999) pmem = 999;
(void)snprintf(outbuf, ROWLENGTH, "%2u.%u",(unsigned)(pmem/10),(unsigned)(pmem%10)); } Пример использования функции pr_pmem() можно посмотреть в файле output.c проекта procps_ptree. 3.8. ФОРМИРОВАНИЕ ВРЕМЕНИ ЗАПУСКАПоле start_time структуры proc_t содержит время запуска процесса в секундах, начиная с 1 января 1970 года(эпоха-Unix), поэтому целесообразно приводить к более удобному формату вывода, а именно: ЧЧ-MM-CC , где ЧЧ - часы, ММ - минуты, СС - секунды; или, если процесс был запущен более одного дня: мм-дд ЧЧ-MM-CC , где мм - месяц, дд - день; или, если процесс был запущен более одного года: гг-мм-дд ЧЧ-MM-CC , где гг - год;
static voidpr_stime(char *restrict const outbuf, const proc_t *restrict const pp){ struct tm *tm; /* Объявить стурктуру календарного времени */ int our_yday; /* Объявить для сохранения в стеке нынешний день года */ int our_year; /* Объявить для сохранения в стеке нынешний год */ const char *fmt = NULL; /* Объявить формат вывода для strftime() */ /* получить секунды в формате UNIX( с начало 1 января 1970 года), прошедщие с начало запуска системы. Выполняется вычитанием секунд прошедших с начало загрузки системы из текущего времени в формате UNIX( с начало 1 января 1970 года); для исключения больших временных задержек на выполнение time(NULL) разумно ввести глобальную переменную seconds_since_1970 (тип объявления time_t) с инициализацией значения, возвращаемого time(NULL), в точке входа в программу. */ time_t seconds_since_1970_of_boot = seconds_since_1970 - seconds_since_boot; /* время старта процесса в секундах в формате UNIX( с начало 1 января 1970 года) */ time_t seconds_since_1970_of_life = seconds_since_1970_of_boot + pp->start_time / Hertz; tm = localtime(&seconds_since_1970); /* сохранить нынешний день года и нынешний год, т.к. struct tm при последующем вызове localtime() будет перезаписана */ our_yday = tm->tm_yday; our_year = tm->tm_year; /* получить календарное время старта процесса */ tm = localtime(&seconds_since_1970_of_life); /* процесс был запущен более одного дня */ if( our_yday != tm->tm_yday ) fmt = "%m-%d %H:%M:%S"; /* мм-дд ЧЧ-MM-CC */ if( our_year != tm->tm_year ) fmt = "%Y-%m-%d %H:%M:%S"; /* гг-мм-дд ЧЧ-MM-CC */ if( fmt == NULL ) fmt = "%H:%M:%S"; /* мм-дд ЧЧ-MM-CC */ (void)strftime(outbuf, 42, fmt, proc_time ); } Пример использования функции pr_stime() можно посмотреть в файле output.c проекта procps_ptree. 3.9.ВЫЧИСЛЕНИЕ СУММАРНОГО ВРЕМЕНИ ИСПОЛЬЗОВАНИЯ ЦПФормируется исходя из суммарного времени использвание ЦП в тактах прерываний таймера, которое рассчитывали ранее в п.1.4.2.8 приведенного к секундам: cpu_seconds = ((*proc_t)->utime + pp->stime) / Hertz Для приведения к следующему формата вывода [ДД-]ЧЧ-ММ-СС требуется выполнить: 1) получить дни (ДД) делением cpu_seconds на произведения 3600 секунд, умноженных на 24 часа; 2) получить часы (ЧЧ) арифиметической операцией (%) взятие остатка от деления cpu_seconds на произведение 24 часов и 3600 секунд; 3) получить минуты (ММ) арифиметической операцией (%) взятие остатка от деления cpu_seconds на 3600 секунд; 4) получить секунды (СС) арифиметической операцией (%) взятие остатка от деления cpu_seconds на 60 секунд; Но самый простой метод, производить последовательно деление cpu_seconds начиная от секунд к суткам, как показано ниже в pr_time().
Пример использования функции pr_time() можно посмотреть в файле output.c проекта procps_ptree. 3.10 ОПРЕДЕЛЕНИЕ ИМЕНИ ИСПОЛНЯЕМОЙ КОМАНДЫИмя команды(исполняемое имя) может быть получено с помощью функции escape_command() библиотеки procps с передачей ей: в первом аргументе, выходной буфер, содержащий имя команды; во втором аргументе, указатель на структуру proc_t; в третьем аргументе, максимальный размер выходного буфера; в четвертом аргументе, количество символов для обрезания имя команды справа; в пятом аргументе, флаг ESC_DEFUNCT.
Пример использования функции pr_comm() можно посмотреть в файле output.c проекта procps_ptree. 3.11 ОПРЕДЕЛЕНИЕ СОСТОЯНИЯ ПРОЦЕССА/ПОТОКАСостояние процесса представляет собой составной набора кодов-символов, которые будут добавлены: поле stat структуры proc_t открывает набор и устанавливает один из следующих кодов-символов: 'D' – ожидает ввода/вывода (или другого недолгого события), не прерываемый;'R' – выполняется в данный момент;'S' – ожидает (т.е. спит менее 20 секунд);'T' – остановлен;'X' – в глубокой коме, т.е ни когда не проснется;'Z' – zombie или defunct процесс, то есть завершившийся процесс,код возврата которого пока не считан родителем; следующие коды символов будут добавляться, если: '<' – процесс находится в приоритетном режиме, если поле nice структуры proc_t меньше нуля;'N' – процесс имеет имеет низкий паритет , если поле nice структуры proc_t больше нуля; 'L' – real-time процесс, имеются страницы, заблокированные в памяти, если поле vm_lock структуры proc_t не равно нулю;'s' – процесс является лидером, если поле tgid равно полю session структуры proc_t;'l' – процесс является мулти-поточным, если в поле nlwp структуры proc_t указано более одного запущенных потоков;'+' – является приоритетным процессом в группе, если идентификатор группы процесса терминала ( поле tpgid структуры proc_t ) равен идентификатору группы процесса ( поле pgrp структуры proc_t ).
static void pr_stat(char *restrict const outbuf, const proc_t *restrict const pp )
Пример использования функции pr_stat() можно посмотреть в файле output.c проекта procps_ptree. 4. ЧТЕНИЕ ИНФОРМАЦИИ О ПОТОКЕ ЗАПУЩЕННЫМ ПРОЦЕССОМЧтения информации о потоке, запущенным процессом, выполняется c использованием функции readtask() библиотеки procps. Функция имеет следующей прототип:
В первом аргументе PT передается указатель на структуру PROCTAB, память под которую должна быть ранее выделена функцией openproc(). Во втором аргументе p должен быть передан указатель на структуру proc_t самого процесса. Функция readtask() возвращает указатель на структуру proc_t, память под которую выделяется, если в третьем аргументе t был передан нулевой указатель. При этом с каждым последующим вызовом, данная функция будет возвращать информацию о следующем запущенном потоке из директории /proc/<pid>/task . Помните, что выделенная память под структуру proc_t, возвращаемая readtask() не добавляется к общему списку процессов tab структуры proc_data_t. Поэтому, лучше использовать статический способ выделения памяти под структуру proc_t для получения информации о запущенном потоке, как это делается в файле main.c, строке 330 проекта procps_ptree, а сам пример применения функции readtask() можно посмотреть в строке 342. 5. ОБРАБОТКА И ВЫВОД ДОПОЛНИТЕЛЬНОЙ ИНФОРМАЦИИ О ЗАПУЩЕННОМ ПОТОКЕ(ПРОЦЕССЕ)5.1. ПОЛУЧЕНИЕ ИДЕНТИФИКАТОР ПОТОКАПоле tid структуры proc_t содержит идентификатор потока(thread id), в то время как tgid содержит PID-процесса, запустивший поток. Значение tid берется из названия директории /proc/<pid>/task/<tid> при выполнении readtask() и не требует какой-либо специальной обработки.
static voidpr_thread (char *restrict const outbuf, const proc_t *restrict const pp ) {
(void)snprintf(outbuf, ROWLENGTH, "%6u", pp->tid);
}
5.2. ПОЛУЧЕНИЕ ПРИОРИТЕТА ПЛАНИРОВАНИЯ ПОТОКА(ПРОЦЕССА)Поле priority структуры proc_t содержит приоритет планирования потока, а для процесса так называемое nice-значение или фактор уступчивости (nice value), который используется для вычисления уровня приоритета вызывающего процесса. При выводе приоритета нам нужно помнить, что действительный диапазон приоритетов варируется от версии к версии ядра Linux: -- до версии ядра 1.3.36 Linux диапазон составлял: −∞ .. 15; -- начиная с версии ядра 1.3.43 Linux диапазон составлял: −20..19; -- сегодня, если верить комментариям к исходному procps диапазон составляет: −100..39
5.3. ПОЛУЧЕНИЕ ПОЛИТИКИ ПЛАНИРОВАНИЯ ПОТОКА(ПРОЦЕССА)Поле sched структуры proc_t содержит политику планирования как для потока, так и для процесса. Сегодня Linux поддерживает "нормальные"(т.е. не реального времени) политики планирования:
и политики "реального времени", используемые в приложениях, являющиеся критическими ко времени исполнения:
static intpr_policy(char *restrict const outbuf, const proc_t *restrict const pp)
{
switch(pp->sched){
case -1: return snprintf(outbuf, COLWID, "--"); // Не сообщается
case 0: return snprintf(outbuf, COLWID, "TS"); // SCHED_OTHER
case 1: return snprintf(outbuf, COLWID, "FF"); // SCHED_FIFO
case 2: return snprintf(outbuf, COLWID, "RR"); // SCHED_RR
case 3: return snprintf(outbuf, COLWID, "B"); // SCHED_BATCH
case 5: return snprintf(outbuf, COLWID, "IDL"); // SCHED_IDLE
default: return snprintf(outbuf, COLWID, "?"); // Неизвестная
}
}
Библиография1) Проект PROCPS 2) Listing Processes with libproc 3) Работа с процессами: системные вызовы и атрибуты 4) Что обозначают буквы в поле STAT при запуске ps -aux или top ? 5) What does this process STAT indicates? 7) setresuid(2) 8) Name Switch Service(NSS): учётные записи пользователей, групп 9) How Much Linux Memory is Used by a Process? , eHow.com 10) Процессы и их приоритеты в ОС Unix 11) Issue 1214: __WORDSIZE is defined as 64 in x86-64 glibc headers 13) Установка и получение приоритета процесса
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Вернуться в Доки-Токи |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © 2010 rjaan as Andrey Rjavskov(Rzhavskov) <rjaan@yandex.ru> <arjavskov@gmail.com> |