Начиная с версии 2.6.8, ядро Linux поддерживает альтернативную версию учетного файла (известную как версия 3), лишенную ограничений традиционной версии. Чтобы воспользоваться ею, перед сборкой ядра следует включить параметр CONFIG_BSD_PROCESS_ACCT_V3.

Единственным отличием версии 3 является формат записей, хранящихся в учетном файле. Его можно описать следующим образом:

struct acct_v3 {

char ac_flag; /* Флаги учета */

char ac_version; /* Версия учетного файла (3) */

u_int16_t ac_tty; /* Управляющий терминал процесса */

u_int32_t ac_exitcode; /* Код завершения процесса */

u_int32_t ac_uid; /* 32-битный пользовательский идентификатор процесса */

u_int32_t ac_gid; /* 32-битный групповой идентификатор процесса */

u_int32_t ac_pid; /* Идентификатор процесса */

u_int32_t ac_ppid; /* Идентификатор родительского процесса */

u_int32_t ac_btime; /* Начальное время (time_t) */

float ac_etime; /* Прошедшее (реальное) время (такты системного времени) */

comp_t ac_utime; /* Пользовательское процессорное время

(такты системного времени) */

comp_t ac_stime; /* Системное процессорное время

(такты системного времени) */

comp_t ac_mem; /* Среднее потребление памяти (килобайты) */

comp_t ac_io; /* Количество прочитанных/записанных байтов

(не используется) */

comp_t ac_rw; /* Количество прочитанных/записанных блоков

(не используется) */

comp_t ac_minflt; /* Незначительные отказы страницы */

comp_t ac_majflt; /* Значительные отказы страницы */

comp_t ac_swaps; /* Количество сбросов в файл подкачки

(не используется; существует лишь в Linux) */

#define ACCT_COMM 16

char ac_comm[ACCT_COMM]; /* Имя команды */

};

Ниже перечислены главные отличия новой структуры acct_v3 от традиционной acct.

• Добавлено поле ac_version, содержащее номер версии этого формата записей. Для acct_v3 оно всегда равно 3.

• Добавлены поля ac_pid и ac_ppid, содержащие идентификаторы процесса и его родителя.

• Поля ac_uid и ac_gid расширены с 16 до 32 бит, чтобы вместить 32-битные пользовательские и групповые идентификаторы, появившиеся в Linux 2.4 (удлиненные идентификаторы не могут быть корректно представлены в традиционном файле формата acct).

• Тип поля ac_etime поменялся с comp_t на float, чтобы можно было записывать более длинные периоды прошедшего времени.

Аналог программы из листинга 28.2 с поддержкой версии 3 доступен в файле procexec/acct_v3_view.c внутри архива с исходными кодами, прилагающемся к этой книге.

28.2. Системный вызов clone()

В Linux доступен уникальный для этой системы вызов clone(), предназначенный для создания новых процессов. От fork() и vfork() он отличается тем, что позволяет более точно контролировать этапы создания процесса. Его в основном применяют в библиотеках, отвечающих за работу с потоками выполнения. Поскольку вызов clone() не является переносимым, вам следует избегать его непосредственного использования в прикладных программах. Мы упоминаем его здесь, потому что он послужит хорошим фоном для описания потоков выполнения POSIX в главах 29–33, а также потому, что он помогает осветить дополнительные аспекты вызовов fork() и vfork().

#define _GNU_SOURCE

#include

int clone(int (*func) (void *), void *child_stack, int flags,

void *func_arg

/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */);

Возвращает идентификатор потомка при успешном завершении или –1 в результате ошибки

Как и в случае с fork(), новый процесс, создаваемый вызовом clone(), является почти полной копией своего родителя.

Отличие от fork() состоит в том, что клонированный потомок не продолжает работу с момента вызова, а сначала вызывает функцию, указанную в аргументе func; мы будем называть ее дочерней функцией. Итак, при вызове дочерней функции ей передается значение, указанное в func_arg. Этот аргумент можно свободно интерпретировать как целое число или, например, указатель на структуру, используя подходящее приведение типов (интерпретация аргумента в качестве указателя становится возможным благодаря тому, что клонированный потомок либо получает копию памяти вызывающего процесса, либо разделяет ее с ним).

Клонированный дочерний процесс завершается либо при возврате функции func (в этом случае возвращаемое значение становится кодом завершения процесса), либо при вызове exit() (или _exit()). Родительский процесс может ожидать завершения потомка как обычно — с помощью вызова wait() или аналогичного ему.

Поскольку клонированный потомок может разделять память с родителем (как в случае с vfork()), он не может использовать родительский стек. Вместо этого он должен выделить для своего стека блок памяти подходящего размера и передать указатель на него в аргумент child_stack. В большинстве аппаратных архитектур стек растет сверху вниз, поэтому аргумент child_stack должен указывать на верхний конец выделенного блока.

Перейти на страницу:

Похожие книги