Как написать программу c использованием библиотеки Procps




Сайт создан в системе uCoz
Вернуться в Доки-Токи

 

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_FILLMEM производить чтение файла /proc/<pid>/statm и заполнять следующие поля структуры proc_t, связанных с использованием процессом памяти:
 
sizeитоговое количество страниц памяти;
resident количество несвопированных страниц резидентного набора (4k);
shareколичество страниц разделяеммой (маппированной) памяти;
trsразмер текствого резидентного набора (text resident set size);
lrsразмер резидентного набора разделяемых библиотек (shared-lib), которые были слинкованы с исполняемым образом;
drsразмер резидентного набора данных;
dtразмер резидентного набора страниц, которые могут быть своппированы.
флаг PROC_FILLCOMпроизводить чтение файла /proc/<pid>/cmdline и заполнять поле cmdline структуры proc_t
флаг PROC_FILLENVпроизводить чтение файла /proc/<pid>/environ и заполнять поле environ структуры proc_t.
флаг PROC_FILLUSRпроизводить разрешение имени пользователя к уникальному идентификатору UID с заполнением поля euser структуры proc_t. При установленном флаге PROC_FILLSTATUS, так же производить разрешение имен и заполнение следующих полей структуры proc_t:
ruser имя реального пользователя процесса к полю ruid структуры proc_t, содержащей уникальный индентификатор реального пользователя;
suserимя сохраненного пользователя процесса вычисляется по полю suid структуры proc_t, содержащей уникальный индентификатор реального пользователя;
fuserимя пользователя файловой системы; вычисляется по полю fuid структуры proc_t, содержащей уникальный индентификатор пользователя файловой системы данного объекта;

Идентификаторы реального пользователя, сохраненного пользователя устанавливаются системным вызовом setresuid(2) вызывающего процесса.

В тоже время, идентификатор пользователя файловой системы обычно равен идентификатору эффективного пользователя EID, который устанавливается setuid(2) или устанавливается setfsuid(2) вызывающего процесса.

Разрешение имен пользователя, реального пользователя, сохраненного пользователя, пользователя файловой системы производится путем обращения к базе данных passwd Службы переключения имён NSS.

флаг PROC_FILLGRP

производить разрешение имени группы к её уникальному идентификатору GID и заполнять поле euser структуры proc_t.

При установленном флаге PROC_FILLSTATUS, так же производить разрешение и заполнение полей структуры proc_t:

rgroupимя группы реального пользователя;
sgroupимя группы сохраненного пользователей;
fgroup имя группы пользователя файловой системы;

Идентификаторы реального пользователя, сохраненного пользователя устанавливаются системным вызовом setresgid(2) вызывающего процесса.

Идентификатор группы пользователя файловой системы FUID в сочетании с дополнительным идентификатором группы используются для определения прав доступа к файловой системе.

Разрешение имен групп пользователя, реального пользователя, сохраненного пользователя, пользователя файловой системы производится путем обращения к базе данных group Службы переключения имён NSS.

флаг PROC_FILLSTATUSпроизводить чтение файла /proc/<pid>/status состояния процесса и заполнять поля ruser, suser, fuser, rgroup, sgroup, fgroup описанных ранее. Так же заполнять следующие поля структуры proc_t: ;
ppid идентификатор родительского процесса PID;
ruid идентификатор реального пользователя RUID;
euid эффективный идентификатор пользователя EUID;
suid сохраненный идентификатор пользователя SUID;
fuid идентификатор пользователя файловой системы FUID;
rgid идентификатор группы реального пользователя RGID;
egid эффективный индентификатор группы пользователя EGID;
sgid сохраненный идентификатор группы пользователя SGID;
fgid идентификатор группы пользователя файловой системы FGID;
vm_data размер сегмента "данные" процесса в виртуальной памяти;
vm_exe размер сегмента "код" процесса в виртуальной памяти, содержащей исполнительную часть;
vm_lib размер подгружаемых библиотек в виртуальной памяти;
vm_lock блокированные страницы в Кбайтах системным вызом mmap;
vm_rss размер резидентного набора сегмента "кучи" процесса в виртуальной памяти(!!!НУЖНО ПРВЕРИТЬ!!!)
vm_size размер виртуальной памяти;
vm_stack размер сегмента "стек" процесса в виртуальной памяти;

Когда процесс стартует, операционная система выделяет некоторое количество памяти процессу, требуемую для его выполнения(vm_size). Каждый процесс имеет четыре сегмента для исполняемого кода(vm_exe) и данных(vm_data). Сегмент "код" или "программы" содержит исполняемые инструкции. Сегмент "данных" содержит статические и глобальные переменные. Сегмент стек содержит локальные переменные. Сегмент "Кучи" содержит динамически выделенные объекты. Процессами могут использоваться разделяемые библиотеки(vm_lib), которые загружаются в память только один раз, а процессы, использующие часть разделяемой библиотеки, будут использовать её копию.

флаг PROC_FILLSTATпроизводить чтение /proc/<pid>/stat и заполнять следующие поля структуры proc_t:
stateодно символьный код статуса;
pgrp идентификатор группы процесса PGID;
session идентификатор сессии;
tty номер устройства контролирующего терминала;
tpgid идентификатор группы процесса терминала;
utime суммарное использование ЦПУ процессом на пользовательском уровне;
stime суммарное использование ЦПУ процессом на уровне ядра;
cutime суммарное utime и потомками;
cstime суммарное stime и потомками;
start_time время запуска процесса в секундах, начиная с 1 января 1970 года(эпоха-Unix);
priority приоритет планирования ядра;
nice значением nice "уровень приятности";
nlwp количество запущенных потоков процессом;
vsize размер виртуальной памяти процесса тоже самое, что и vm_size;
rss резидентный размер процесса то же самое, что и vm_rss;
rss_rlim предел резидентного набора;
rtprio приоритет реального времени;
флаг ROC_FILLARG выделять память и заполнять поле cmdline структуры proc_t.
флаг PROC_LOOSE_TASKS считать потоки так, если бы они были бы процессами.
флаг PROC_PID читать информацию по списку PID, переданного после первого аргумента flag функции openproc(). Список передается по указателю типа pid_t* . Исключает возможность совместного использования с флагом PROC_UID.
флаг PROC_UIDчитать информацию по списку UID, переданного после первого аргумента flag функции openproc(). Список передается по указателю типа uid_t* c передачей за ним количества UID в этом списке. Исключает возможность совместного использования с флагом PROC_PID.
флаг PROC_FILLUSRприменяет разрешение имен пользователей к их идентификаторам для заполнения следующих полей структуры proc_t:
euser имя пользователя по эффективному идентификатору пользователя EUID, требует флага PROC_FILLSTATUS;
ruser имя пользователя по идентификатору реального пользователя RUID, требует флага PROC_FILLSTATUS;
suser имя пользователя по сохраненному идентификатору пользователя SUID, требует флага PROC_FILLSTATUS;
fuser имя пользователя по идентификатору пользователя файловой системы FUID, требует флага PROC_FILLSTATUS;
флаг PROC_FILLGRPприменяет разрешение имен групп к их идентификаторам для заполнения следующих полей структуры proc_t:
egroupимя группы по эффективному идентификатору группы пользователя EGID, требует флага PROC_FILLSTATUS;
rgroup имя группы по идентификатору группы реального пользователя RGID, требует флага PROC_FILLSTATUS;
sgroup имя группы по сохраненному идентификатору группы пользователя SGID, требует флага PROC_FILLSTATUS;
fgroup имя группы по идентификатору группы пользователя файловой системы FGID, требует флага PROC_FILLSTATUS;

При указании флагов PROC_FILLUSR и PROC_FILLGRP для разрешения имен пользователей и групп к их идентификаторам используются базы passwd и group Службы переключения имён, Name Switch Service(NSS).

Функция возвращает:

      В случае успешного завершения, возвращает указатель на структуру PROCTAB в случае открытия директории /proc;
      Иначе, указатель NULL, если открыть директории /proc невозможно.

Пример использования функции 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 будет равно нулю.

Когда начал работать над это статьей, ошибочно считал, что возвращаемый указатель функцией readproctab2() ни в коим образом не должен быть освобожден free(3), т.к. освобождается closeproc(). Поэтому любые попытки самостаятельно освободить указатель на структуру proc_data_t приведут к ошибке двойного освобождения памяти (double free).

На самом деле, перед завершением работы программы, нужно освобождать память, выделенную под структуру proc_data_t функцией readproctab2(), как это делает функция procps_tab_close() в файле main.c, строке 242, проекта procps_ptree.

Для чего, начиная с строки 251 по 257 определяется начальный адрес массива структур proc_t, на который указывает указатель-на-указатель tab структуры proc_data_t. Именно он будет передан в строке 269 для высвобождения ранее выделенной памяти.

2.4 ПРИМЕР ИСПОЛЬЗОВАНИЯ БИБЛИОТЕКИ PROCPS

procps_ptree повторяет вывод ps -eHo pid,ppid,euser,%cpu,%mem,stime,time,comm; PS(1) вместо имени пользователя выводит его EUID , если его длинна больше 8-ми символов.
procps_ptree   --loose-taskприменяет флаг PROC_LOOSE_TASKS.
procps_ptree   --show-only-pidsвыводит только PID запущенных процессов, что соответствует выполнению ps -eHo pid
procps_ptree   --not-resolve-nameне приводит имена пользователей к их UID, повторяет вывод: ps -eHo pid,ppid,euid,%cpu,%mem,stime,time,comm
procps_ptree   --show-with-pid PID отображает дерево запущенных процессов и их потоков начиная с указанного PID

3. СПЕЦИАЛЬНАЯ ОБРАБОТКА ПОЛЕЙ СТРУКТУРЫ PROC_T

3.1. ТАБЛИЦА СООТНОШЕНИЯ КЛЮЧЕВЫХ СЛОВ ФОРМАТА ВЫВОДА PS(1) И ПОЛЕЙ СТРУКТУРЫ PROC_T

Не все поля структуры proc_t могут быть заполнены непосредственно прямым чтением из файлов директории /proc/<pid>, потому что требуют специальной обработки. Хотя многие из них, как мы увидим ниже, не требуют такой обработки и используются непосредственно.

PS(1) позволяет выводить в определенном пользователе формате информацию о запущенных процессах и их потоках. В таблице, приведенной ниже, дается соответствие ключевых слов, заголовка формата вывода PS(1) к полям структуры proc_t:

КЛЮЧ.СЛОВО ФОРМАТА ВЫВОДАНАЗВАНИЕ В ЗАГОЛОВКЕ ВЫВОДА PS(1)НАЗНАЧЕНИЕПОЛЕ СТРУКТУРЫ PROC_T
pidPID Идентификатор процесса/потокаСМ.ПОЛУЧЕНИЕ PID
ppidPPID идентификатор родительского процессовСМ.ПОЛУЧЕНИЕ PPID
pgidPGID идентификатор группы процессовСМ.ПОЛУЧЕНИЕ PGID
userUSERэффективное имя пользователяСМ. ПОЛУЧЕНИЕ EUSER
%cpu %CPU Загрузка ЦП в процентах СМ. ВЫЧИСЛЕНИЕ ЗАГРУЗКИ ПРОЦЕССОРА В ПРОЦЕНТАХ
%mem%MEMОтношение размера резидентного набора процесса к физической памяти, выраженного в процентахСМ. ОТНОШЕНИЕ РЕЗИДЕТНОГО НАБОРА ПРОЦЕССА К ФИЗИЧЕСКОЙ ПАМЯТИ
timeTIMEСуммарное время использование ЦП c начало запуска процессаСМ.ВЫЧИСЛЕНИЕ СУММАРНОГО ВРЕМЕНИ ИСПОЛЬЗОВАНИЯ ЦП
start_timeSTARTВремя запуска процесса/POSIX-потокаСМ.ФОРМИРОВАНИЕ ВРЕМЕНИ ЗАПУСКА
commCOMMANDИмя команды(только исполняемое имя)СМ. ОПРЕДЕЛЕНИЕ ИМЕНИ КОМАНДЫ
statSTATСостояние процесса, отображаемое наборомСМ. ОПРЕДЕЛЕНИЕ СОСТОЯНИЯ ПРОЦЕССА

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() и не требует какой-либо специальной обработки.

static void
pr_ppid (char *restrict const outbuf, const proc_t *restrict const pp ) {

(void)snprintf(outbuf, ROWLENGTH, "%6u", pp->ppid);

}

Пример использования функции pr_ppid() можно посмотреть в файле output.c проекта procps_ptree.

3.4.ПОЛУЧЕНИЕ ЭФФЕКТИВНОГО ИМЕНИ ПОЛЬЗОВАТЕЛЯ

Поле euser структуры proc_t содержит идентификатор родительского процесса. Заполняется при выполнении readproctab2() и не требует какой-либо специальной обработки.

static void
pr_user(char *restrict const outbuf, const proc_t *restrict const pp){

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() и не требует какой-либо специальной обработки.

static void
pr_pgid (char *restrict const outbuf, const proc_t *restrict const pp ) {

(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 void
pr_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 void
pr_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 void
pr_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().

static void
pr_time ( char *restrict const outbuf, const proc_t *restrict const pp )

{

unsigned int dd_chars, dd, mm, hh, ss;

unsigned long cpu_seconds;

if ( !Hertz ) {

snprintf(outbuf, ROWLENGTH, "DIV:ZERO:ERR" );

return;

}

cpu_seconds = (pp->utime + pp->stime) / Hertz;

ss = cpu_seconds % 60; /* секунды */

cpu_seconds /= 60;

mm = cpu_seconds % 60; /* минуты */

cpu_seconds /= 60;

hh = cpu_seconds % 24; /* часы */

dd = cpu_seconds / 24;

dd_chars = ( dd ? snprintf(outbuf, 240, "%u-", dd) : 0 ); /* Сутки */

(void)snprintf ( outbuf+dd_chars, ROWLENGTH, "%02u:%02u:%02u",
hh, mm, ss );

}

Пример использования функции pr_time() можно посмотреть в файле output.c проекта procps_ptree.

3.10 ОПРЕДЕЛЕНИЕ ИМЕНИ ИСПОЛНЯЕМОЙ КОМАНДЫ

Имя команды(исполняемое имя) может быть получено с помощью функции escape_command() библиотеки procps с передачей ей:

в первом аргументе, выходной буфер, содержащий имя команды;
во втором аргументе, указатель на структуру proc_t;
в третьем аргументе, максимальный размер выходного буфера;
в четвертом аргументе, количество символов для обрезания имя команды справа;
в пятом аргументе, флаг ESC_DEFUNCT.

static void
pr_comm ( char *restrict const outbuf, const proc_t *restrict const pp )

{

int rightward = 15;

static char cmdbuf[PATH_MAX] = { 0 };

escape_command ( cmdbuf, processes[self], PATH_MAX, &rightward, ESC_DEFUNCT );

fprintf ( stdout,"%15s", cmdbuf );

}

Пример использования функции 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 )

{

int offset = 0;

*(outbuf+offset++) = pp->state;

if(pp->nice < 0) *(outbuf+offset++) = '<';

if(pp->nice > 0) *(outbuf+offset++) = 'N';

if(pp->vm_lock != 0 ) *(outbuf+offset++) = 'L';

if(pp->session == pp->tgid) *(outbuf+offset++) = 's';

if(pp->nlwp > 1) *(outbuf+offset++) = 'l';

if(pp->pgrp == pp->tpgid) *(outbuf+offset++) = '+';

*(outbuf+offset) = '\0';

}

Пример использования функции pr_stat() можно посмотреть в файле output.c проекта procps_ptree.

4. ЧТЕНИЕ ИНФОРМАЦИИ О ПОТОКЕ ЗАПУЩЕННЫМ ПРОЦЕССОМ

Чтения информации о потоке, запущенным процессом, выполняется c использованием функции readtask() библиотеки procps.

Функция имеет следующей прототип:

#include <proc/readproc.h>

proc_t* readtask ( PROCTAB *restrict const PT,
const proc_t *restrict const p, proc_t *restrict t);

В первом аргументе 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 void
pr_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

static int
pr_priority(char *restrict const outbuf, const proc_t *restrict const pp)
{
if ( pp->priority < -100 && pp->priority > 39 ) {
(void)snprintf(outbuf, ROWLENGTH, "EPRIO" ); /* Priority is not */
}
(void)snprintf(outbuf, ROWLENGTH, "%ld", pp->priority);
}

5.3. ПОЛУЧЕНИЕ ПОЛИТИКИ ПЛАНИРОВАНИЯ ПОТОКА(ПРОЦЕССА)

Поле sched структуры proc_t содержит политику планирования как для потока, так и для процесса. Сегодня Linux поддерживает "нормальные"(т.е. не реального времени) политики планирования:
SCHED_OTHERстандартная политика по круговому алгоритму с разделением времени;
SCHED_BATCHполитика batch-планирования, предназначена для задач, интенсивно использующих процессор;
SCHED_IDLEпланирование политики, направленной на очень низкие приоритетные задачи;

и политики "реального времени", используемые в приложениях, являющиеся критическими ко времени исполнения:

SCHED_FIFOполитика "первым пришел, первым ушел";
SCHED_RRциклическая политика.
static int
pr_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?

6) Process memory usage

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

12) How to use the VRTSexplorer utility to gather information useful for Symantec Corporation Technical Support

13) Установка и получение приоритета процесса

 

Вернуться в Доки-Токи

Copyright © 2010 rjaan as Andrey Rjavskov(Rzhavskov) <rjaan@yandex.ru> <arjavskov@gmail.com>