Мобильное программирование приложений реального времени в стандарте POSIX

Опрос и изменение атрибутов потоков управления


Следуя классическому принципу "познай самого себя", описание функций, обслуживающих потоки управления, мы начнем с функции pthread_self(), возвращающей в качестве результата идентификатор вызвавшего ее потока (см. листинг 1.1).
#include pthread_t pthread_self (void);
Листинг 1.1. Описание функции pthread_self(). (html, txt)
Выше мы отмечали, что тип pthread_t трактуется стандартом POSIX-2001 как абстрактный. На уровне языка C он может быть представлен, например, структурой. Для работы со значениями типа pthread_t предусмотрены два метода: присваивание и сравнение на равенство, реализуемое функцией pthread_equal() (см. листинг 1.2).
#include int pthread_equal (pthread_t t1, pthread_t t2);
Листинг 1.2. Описание функции pthread_equal(). (html, txt)
Если значения аргументов t1 и t2 равны, результат функции pthread_equal() отличен от нуля.
Атрибуты потоков управления, используемые при создании последних, сгруппированы в упоминавшиеся выше атрибутные объекты. Для инициализации и разрушения атрибутных объектов служат функции pthread_attr_init() и pthread_attr_destroy() (см. листинг 1.3).
#include
int pthread_attr_init ( pthread_attr_t *attr);
int pthread_attr_destroy ( pthread_attr_t *attr);
Листинг 1.3. Описание функций pthread_attr_init() и pthread_attr_destroy(). (html, txt)
Функция pthread_attr_init() инициализирует атрибутный объект, заданный указателем attr, подразумеваемыми значениями для всех индивидуальных атрибутов потоков управления, предусмотренных реализацией.
Функция pthread_attr_destroy() разрушает заданный атрибутный объект. Впрочем, "разрушает", возможно, слишком сильный термин. Быть может, реализация просто присваивает значениям атрибутов недопустимые значения. Во всяком случае, разрушенный атрибутный объект в дальнейшем может быть вновь инициализирован.
Структура атрибутных объектов скрыта от приложений, но сам набор стандартизованных атрибутов выглядит вполне естественно.
Следуя классическому принципу "познай самого себя", описание функций, обслуживающих потоки управления, мы начнем с функции pthread_self(), возвращающей в качестве результата идентификатор вызвавшего ее потока (см. листинг 1.1).
#include pthread_t pthread_self (void);
Листинг 1.1. Описание функции pthread_self().
Выше мы отмечали, что тип pthread_t трактуется стандартом POSIX-2001 как абстрактный. На уровне языка C он может быть представлен, например, структурой. Для работы со значениями типа pthread_t предусмотрены два метода: присваивание и сравнение на равенство, реализуемое функцией pthread_equal() (см. листинг 1.2).
#include int pthread_equal (pthread_t t1, pthread_t t2);
Листинг 1.2. Описание функции pthread_equal().
Если значения аргументов t1 и t2 равны, результат функции pthread_equal() отличен от нуля.
Атрибуты потоков управления, используемые при создании последних, сгруппированы в упоминавшиеся выше атрибутные объекты. Для инициализации и разрушения атрибутных объектов служат функции pthread_attr_init() и pthread_attr_destroy() (см. листинг 1.3).
#include
int pthread_attr_init ( pthread_attr_t *attr);
int pthread_attr_destroy ( pthread_attr_t *attr);
Листинг 1.3. Описание функций pthread_attr_init() и pthread_attr_destroy().
Функция pthread_attr_init() инициализирует атрибутный объект, заданный указателем attr, подразумеваемыми значениями для всех индивидуальных атрибутов потоков управления, предусмотренных реализацией.
Функция pthread_attr_destroy() разрушает заданный атрибутный объект. Впрочем, "разрушает", возможно, слишком сильный термин. Быть может, реализация просто присваивает значениям атрибутов недопустимые значения. Во всяком случае, разрушенный атрибутный объект в дальнейшем может быть вновь инициализирован.
Структура атрибутных объектов скрыта от приложений, но сам набор стандартизованных атрибутов выглядит вполне естественно. Их описание мы начнем с атрибутов стека – начального адреса и размера – и методов для их опроса и установки (см.


Их описание мы начнем с атрибутов стека – начального адреса и размера – и методов для их опроса и установки (см. листинг 1.4).

#include

int pthread_attr_getstack ( const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);

int pthread_attr_setstack ( pthread_attr_t *attr, void *stackaddr, size_t stacksize);

Листинг 1.4. Описание функций pthread_attr_getstack() и pthread_attr_setstack(). (html, txt)

Размер стека должен составлять не менее PTHREAD_STACK_MIN, начальный адрес – должным образом выровнен. Память, отведенная под стек, должна быть доступна на чтение и запись.

Функция pthread_attr_getstack() помещает атрибуты стека по указателям stackaddr и stacksize. Это – проявление единообразной для семейства функций pthread*(), обслуживающих потоки управления, дисциплины возврата результатов. Содержательные данные помещаются в выходные аргументы. При нормальном завершении результат функции равен нулю; в противном случае выдается код ошибки.

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

Для опроса и изменения размера защитной области, служащей цели обнаружения переполнения стека, предназначены функции pthread_attr_getguardsize() и pthread_attr_setguardsize() (см. листинг 1.5).

#include

int pthread_attr_getguardsize ( const pthread_attr_t *restrict attr, size_t *restrict guardsize);

int pthread_attr_setguardsize ( pthread_attr_t *attr, size_t guardsize);

Листинг 1.5. Описание функций pthread_attr_getguardsize() и pthread_attr_setguardsize(). (html, txt)

Если значение аргумента guardsize функции pthread_attr_setguardsize() равно нулю, при создании потоков управления с атрибутным объектом *attr защитная область отводиться не будет.


Положительные величины guardsize также становятся новыми значениями одноименного атрибута, однако являются лишь указанием операционной системе; реальный размер защитной области может быть больше заданного.

Приложение, соответствующее стандарту POSIX, должно использовать значения guardsize, кратные конфигурационной константе PAGESIZE, которая одновременно является подразумеваемым значением данного атрибута.

Если приложение посредством функции pthread_attr_setstack() взяло на себя управление стеками потоков, атрибут guardsize игнорируется, операционная система не отводит защитную область, а контроль за переполнением стека возлагается на приложение.

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

Стандартом POSIX-2001 предусмотрена группа атрибутов, обслуживающих планирование потоков управления. Соответствующие описания размещены в заголовочном файле . Центральную роль среди них играет структура типа sched_param, которая должна содержать по крайней мере поле int sched_priority; /* Приоритет планирования при выполнении потока */

Реализация может поддерживать политику планирования SCHED_SPORADIC (спорадическое планирование), предусматривающую резервирование определенного количества вычислительной мощности для обработки с заданным приоритетом неких единичных, непериодических (спорадических) событий. В этом случае должны быть определены конфигурационные константы _POSIX_SPORADIC_SERVER и/или _POSIX_THREAD_SPORADIC_SERVER, а в структуре sched_param должны присутствовать следующие дополнительные поля.

int sched_ss_low_priority; /* Нижняя граница приоритета */ /* планирования сервера */ /* спорадических событий */ struct timespec sched_ss_repl_period; /* Период пополнения бюджета */ /* спорадического сервера */ struct timespec sched_ss_init_budget; /* Начальный бюджет */ /* спорадического сервера */ int sched_ss_max_repl; /* Максимальное число */ /* ждущих операций */ /* пополнений бюджета */ /* спорадического сервера */



Для опроса и установки атрибутов планирования в атрибутных объектах служат функции pthread_attr_getschedparam() и pthread_attr_setschedparam() (см. листинг 1.6).

#include

int pthread_attr_getschedparam ( const pthread_attr_t *restrict attr, struct sched_param *restrict param);

int pthread_attr_setschedparam ( pthread_attr_t *restrict attr, const struct sched_param *restrict param);

Листинг 1.6. Описание функций pthread_attr_getschedparam() и pthread_attr_setschedparam(). (html, txt)

Атрибут "политика планирования", способный принимать значения SCHED_FIFO (планирование по очереди), SCHED_RR (циклическое планирование), SCHED_OTHER ("прочее" планирование) и, возможно, SCHED_SPORADIC (спорадическое планирование), можно опросить и установить посредством функций pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy() (см. листинг 1.7).

#include

int pthread_attr_getschedpolicy ( const pthread_attr_t *restrict attr, int *restrict policy);

int pthread_attr_setschedpolicy ( pthread_attr_t *attr, int policy);

Листинг 1.7. Описание функций pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy(). (html, txt)



листинг 1.4).

#include

int pthread_attr_getstack ( const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);

int pthread_attr_setstack ( pthread_attr_t *attr, void *stackaddr, size_t stacksize);

Листинг 1.4. Описание функций pthread_attr_getstack() и pthread_attr_setstack().

Размер стека должен составлять не менее PTHREAD_STACK_MIN, начальный адрес – должным образом выровнен. Память, отведенная под стек, должна быть доступна на чтение и запись.

Функция pthread_attr_getstack() помещает атрибуты стека по указателям stackaddr и stacksize. Это – проявление единообразной для семейства функций pthread*(), обслуживающих потоки управления, дисциплины возврата результатов. Содержательные данные помещаются в выходные аргументы. При нормальном завершении результат функции равен нулю; в противном случае выдается код ошибки.

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

Для опроса и изменения размера защитной области, служащей цели обнаружения переполнения стека, предназначены функции pthread_attr_getguardsize() и pthread_attr_setguardsize() (см. листинг 1.5).

#include

int pthread_attr_getguardsize ( const pthread_attr_t *restrict attr, size_t *restrict guardsize);

int pthread_attr_setguardsize ( pthread_attr_t *attr, size_t guardsize);

Листинг 1.5. Описание функций pthread_attr_getguardsize() и pthread_attr_setguardsize().

Если значение аргумента guardsize функции pthread_attr_setguardsize() равно нулю, при создании потоков управления с атрибутным объектом *attr защитная область отводиться не будет. Положительные величины guardsize также становятся новыми значениями одноименного атрибута, однако являются лишь указанием операционной системе; реальный размер защитной области может быть больше заданного.



Приложение, соответствующее стандарту POSIX, должно использовать значения guardsize, кратные конфигурационной константе PAGESIZE, которая одновременно является подразумеваемым значением данного атрибута.

Если приложение посредством функции pthread_attr_setstack() взяло на себя управление стеками потоков, атрибут guardsize игнорируется, операционная система не отводит защитную область, а контроль за переполнением стека возлагается на приложение.

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

Стандартом POSIX-2001 предусмотрена группа атрибутов, обслуживающих планирование потоков управления. Соответствующие описания размещены в заголовочном файле . Центральную роль среди них играет структура типа sched_param, которая должна содержать по крайней мере поле int sched_priority; /* Приоритет планирования при выполнении потока */

Реализация может поддерживать политику планирования SCHED_SPORADIC (спорадическое планирование), предусматривающую резервирование определенного количества вычислительной мощности для обработки с заданным приоритетом неких единичных, непериодических (спорадических) событий. В этом случае должны быть определены конфигурационные константы _POSIX_SPORADIC_SERVER и/или _POSIX_THREAD_SPORADIC_SERVER, а в структуре sched_param должны присутствовать следующие дополнительные поля.

int sched_ss_low_priority; /* Нижняя граница приоритета */ /* планирования сервера */ /* спорадических событий */ struct timespec sched_ss_repl_period; /* Период пополнения бюджета */ /* спорадического сервера */ struct timespec sched_ss_init_budget; /* Начальный бюджет */ /* спорадического сервера */ int sched_ss_max_repl; /* Максимальное число */ /* ждущих операций */ /* пополнений бюджета */ /* спорадического сервера */



Для опроса и установки атрибутов планирования в атрибутных объектах служат функции pthread_attr_getschedparam() и pthread_attr_setschedparam() (см. листинг 1.6).

#include

int pthread_attr_getschedparam ( const pthread_attr_t *restrict attr, struct sched_param *restrict param);

int pthread_attr_setschedparam ( pthread_attr_t *restrict attr, const struct sched_param *restrict param);

Листинг 1.6. Описание функций pthread_attr_getschedparam() и pthread_attr_setschedparam().

Атрибут "политика планирования", способный принимать значения SCHED_FIFO (планирование по очереди), SCHED_RR (циклическое планирование), SCHED_OTHER ("прочее" планирование) и, возможно, SCHED_SPORADIC (спорадическое планирование), можно опросить и установить посредством функций pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy() (см. листинг 1.7).

#include

int pthread_attr_getschedpolicy ( const pthread_attr_t *restrict attr, int *restrict policy);

int pthread_attr_setschedpolicy ( pthread_attr_t *attr, int policy);

Листинг 1.7. Описание функций pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy().

Описанный выше атрибут "область планирования конкуренции", способный принимать значения PTHREAD_SCOPE_SYSTEM и PTHREAD_SCOPE_PROCESS, обслуживают функции pthread_attr_getscope() и pthread_attr_setscope() (см. листинг 1.8).

#include

int pthread_attr_getscope ( const pthread_attr_t *restrict attr, int *restrict contentionscope);

int pthread_attr_setscope ( pthread_attr_t *attr, int contentionscope);

Листинг 1.8. Описание функций pthread_attr_getscope() и pthread_attr_setscope().

При создании потока управления все рассмотренные выше атрибуты планирования, в зависимости от значения PTHREAD_INHERIT_SCHED или PTHREAD_EXPLICIT_SCHED атрибута inheritsched, могут наследоваться у создающего потока или извлекаться из атрибутного объекта. Для опроса и изменения этого атрибута предназначены функции pthread_attr_getinheritsched() и pthread_attr_setinheritsched() (см.


листинг 1.9).

#include

int pthread_attr_getinheritsched ( const pthread_attr_t *restrict attr, int *restrict inheritsched);

int pthread_attr_setinheritsched ( pthread_attr_t *attr, int inheritsched);

Листинг 1.9. Описание функций pthread_attr_getinheritsched() и pthread_attr_setinheritsched().

Атрибут обособленности потока управления, присутствующий в атрибутном объекте, можно опросить и установить посредством функций pthread_attr_getdetachstate() и pthread_attr_setdetachstate() (см. листинг 1.10).

#include

int pthread_attr_getdetachstate ( const pthread_attr_t *attr, int *detachstate);

int pthread_attr_setdetachstate ( pthread_attr_t *attr, int detachstate);

Листинг 1.10. Описание функций pthread_attr_getdetachstate() и pthread_attr_setdetachstate().

Напомним, что значение этого атрибута (PTHREAD_CREATE_DETACHED или PTHREAD_CREATE_JOINABLE) определяет, будет ли поток управления создан как обособленный или присоединяемый, то есть доступный другим потокам для ожидания завершения. Подразумеваемым является значение PTHREAD_CREATE_JOINABLE.

Значения атрибутов планирования могут задаваться не только при создании потока управления. Стандарт POSIX-2001 предоставляет средства для их динамического изменения и опроса (см. листинг 1.11).

#include

int pthread_getschedparam ( pthread_t thread, int *restrict policy, struct sched_param *restrict param);

int pthread_setschedparam ( pthread_t thread, int policy, const struct sched_param *param);

Листинг 1.11. Описание функций pthread_getschedparam() и pthread_setschedparam().

Отметим две тонкости, связанные с функцией pthread_setschedparam(). Во-первых, возможно, что для ее успешного вызова процесс должен обладать соответствующими привилегиями. Во-вторых, реализация не обязана поддерживать динамический переход к политике спорадического планирования (SCHED_SPORADIC) (как, впрочем, и саму эту политику).

Если требуется изменить лишь приоритет планирования, не меняя политику, проще воспользоваться функцией pthread_setschedprio() (см.


листинг 1.12), которая, правда, является новой и в исторически сложившихся реализациях может отсутствовать.

#include int pthread_setschedprio ( pthread_t thread, int prio);

Листинг 1.12. Описание функции pthread_setschedprio().

Сходную направленность, но более глобальный характер имеют функции pthread_getconcurrency() и pthread_setconcurrency() (см. листинг 1.13), позволяющие опросить и изменить уровень параллелизма выполнения потоков управления.

#include

int pthread_getconcurrency (void);

int pthread_setconcurrency (int new_level);

Листинг 1.13. Описание функций pthread_getconcurrency() и pthread_setconcurrency().

По умолчанию операционная система предоставляет возможность параллельно проявлять активность некоему "достаточному числу" потоков управления в процессе, так, чтобы это не вело к перерасходу системных ресурсов. Некоторым приложениям, однако, может требоваться более высокий уровень параллелизма; это требование они могут передать ОС в виде значения аргумента new_level функции pthread_setconcurrency(). Впрочем, с точки зрения операционной системы это всего лишь просьба или рекомендация; стандарт не специфицирует реально устанавливаемый уровень.

Нулевое значение аргумента new_level означает переход к подразумеваемому уровню параллелизма, как если бы функция pthread_setconcurrency() ранее не вызывалась.

Функция pthread_getconcurrency() в качестве результата возвращает значение уровня параллелизма, установленное предыдущим вызовом pthread_setconcurrency(). Если такового не было, выдается нуль.

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

К числу атрибутов потока управления можно отнести обслуживающие его часы процессорного времени. Для выяснения их идентификатора достаточно обратиться к функции pthread_getcpuclockid() (см. листинг 1.14).

#include #include

int pthread_getcpuclockid ( pthread_t thread_id, clockid_t *clock_id);



Листинг 1.14. Описание функции pthread_getcpuclockid().

Еще один атрибут потока управления – маска блокированных сигналов. Поток может опросить и/или изменить ее посредством вызова функции pthread_sigmask() (см. листинг 1.15) – аналога рассмотренной в курсе [1] функции sigprocmask().

#include int pthread_sigmask ( int how, const sigset_t *restrict set, sigset_t *restrict oset);

Листинг 1.15. Описание функции pthread_sigmask().

На листинге 1.16 приведен пример программы, использующей большинство описанных выше функций для опроса и изменения атрибутов потоков управления. Возможные результаты работы этой программы показаны на листинге 1.17.

/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа опрашивает атрибуты потоков управления */ /* и изменяет некоторые из них */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */

#define _XOPEN_SOURCE 600

#include #include #include #include

int main (void) { pthread_t ct_id; /* Идентификатор текущего потока управления */ pthread_attr_t patob; /* Атрибутный объект для создания потоков управления */ int res; /* Переменная для запоминания результатов "потоковых" функций */ void *stackaddr; /* Начало стека как атрибут потока управления */ size_t atrsize; /* Размеры как атрибуты потока управления */ /* Структура с параметрами планирования */ struct sched_param shdprm; char *spname; /* Названия политики планирования, области */ /* планирования конкуренции и т.п. */

printf ("Идентификатор текущего потока управления: %lx\n", (ct_id = pthread_self ()));

if ((errno = pthread_attr_init (&patob)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); }

printf ("Значения, установленные системой " "в атрибутном объекте\n");

if ((errno = pthread_attr_getstack (&patob, &stackaddr, &atrsize)) != 0) { perror ("PTHREAD_ATTR_GETSTACK"); return (errno); } printf ("Адрес начала стека: %p\n", stackaddr); printf ("Размер стека: %d\n", atrsize);



assert (pthread_attr_getguardsize (&patob, &atrsize) == 0); printf ("Размер защитной области: %d\n", atrsize);

assert (pthread_attr_getschedparam (&patob, &shdprm) == 0); assert (pthread_attr_getschedpolicy (&patob, &res) == 0); switch (res) { case SCHED_FIFO: spname = "Планирование по очереди"; break; case SCHED_RR: spname = "Циклическое планирование"; break; case SCHED_OTHER: spname = "Прочее планирование"; break; default: spname = "Неизвестная политика планирования"; } printf ("Политика планирования: %s\n", spname); printf ("Приоритет планирования: %d\n", shdprm.sched_priority);

assert (pthread_attr_getscope (&patob, &res) == 0); switch (res) { case PTHREAD_SCOPE_SYSTEM: spname = "Система"; break; case PTHREAD_SCOPE_PROCESS: spname = "Процесс"; break; default: spname = "Неизвестная область планирования " "конкуренции"; } printf ("Область планирования конкуренции: %s\n", spname);

assert (pthread_attr_getinheritsched (&patob, &res) == 0); switch (res) { case PTHREAD_INHERIT_SCHED: spname = "Наследуются у родительского потока"; break; case PTHREAD_EXPLICIT_SCHED: spname = "Извлекаются из атрибутного объекта"; break; default: spname = "Устанавливаются неизвестным образом"; } printf ("Атрибуты планирования: %s\n", spname);

assert (pthread_attr_getdetachstate (&patob, &res) == 0); switch (res) { case PTHREAD_CREATE_JOINABLE: spname = "Присоединяемые"; break; case PTHREAD_CREATE_DETACHED: spname = "Обособленные"; break; default: spname = "Неизвестные"; } printf (" Потоки управления создаются как: %s\n", spname);

/* Изменим значения атрибутов планирования и уровня */ /* параллелизма */ shdprm.sched_priority = 1; if ((errno = pthread_setschedparam (ct_id, SCHED_RR, &shdprm)) != 0) { perror ("PTHREAD_SETSCHEDPARAM"); } if ((errno = pthread_setconcurrency (8192)) != 0) { perror ("PTHREAD_SETCONCURRENCY"); } printf ("\nТекущие значения атрибутов потоков управления\n");



assert (pthread_getschedparam (ct_id, &res, &shdprm) == 0); switch (res) { case SCHED_FIFO: spname = "Планирование по очереди"; break; case SCHED_RR: spname = "Циклическое планирование"; break; case SCHED_OTHER: spname = "Прочее планирование"; break; default: spname = "Неизвестная политика планирования"; } printf ("Политика планирования: %s\n", spname); printf ("Приоритет планирования: %d\n", shdprm.sched_priority); printf ("Уровень параллелизма: %d\n", pthread_getconcurrency());

return 0; }

Листинг 1.16. Пример программы, опрашивающей и изменяющей значения атрибутов потоков управления.

Идентификатор текущего потока управления: 400 Значения, установленные системой в атрибутном объекте Адрес начала стека: 0xffe01000 Размер стека: 2093056 Размер защитной области: 4096 Политика планирования: Прочее планирование Приоритет планирования: 0 Область планирования конкуренции: Система Атрибуты планирования: Извлекаются из атрибутного объекта Потоки управления создаются как: Присоединяемые

Текущие значения атрибутов потоков управления Политика планирования: Циклическое планирование Приоритет планирования: 1 Уровень параллелизма: 8192

Листинг 1.17. Возможные результаты работы программы, опрашивающей и изменяющей значения атрибутов потоков управления.

Листинг 1.18 содержит результаты выполнения упрощенного варианта этой же программы (без вызовов функций pthread_attr_getstack(), pthread_attr_getguardsize(), pthread_getconcurrency(), pthread_setconcurrency() и без соответствующих выдач) для операционной системы реального времени oc2000, соответствующей подмножеству требований стандарта POSIX-2001.

Идентификатор текущего потока управления: f31ae0 Значения, установленные системой в атрибутном объекте Политика планирования: Планирование по очереди Приоритет планирования: 100 Область планирования конкуренции: Процесс Атрибуты планирования: Извлекаются из атрибутного объекта Потоки управления создаются как: Присоединяемые

Текущие значения атрибутов потоков управления Политика планирования: Циклическое планирование Приоритет планирования: 1

Листинг 1.18. Возможные результаты работы программы, опрашивающей и изменяющей значения атрибутов потоков управления, для операционной системы реального времени oc2000.


Основные идеи, понятия и объекты


Напомним, уточним и дополним определения, которые были даны в курсе [1] применительно к потокам управления.
Процесс – это адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами, которые этим потокам требуются.
После того, как процесс создан с помощью функции fork(), он считается активным. Сразу после создания в его рамках существует ровно один поток управления – копия того, что вызвал fork().
До завершения процесса в его рамках существуют по крайней мере один поток управления и адресное пространство.
Большинство атрибутов процесса разделяются существующими в его рамках потоками управления. К числу индивидуальных атрибутов относятся идентификатор, приоритет и политика планирования, значение переменной errno, ассоциированные с потоком управления пары ключ/значение (служащие для организации индивидуальных данных потока и доступа к ним), а также системные ресурсы, требующиеся для поддержки потока управления.
Идентификатор потока управления уникален в пределах процесса, но не системы в целом.
Идентификаторы потоков управления представлены значениями типа pthread_t, который трактуется в стандарте POSIX-2001 как абстрактный. В частности, для него определен метод сравнения значений на равенство.
Всем потокам управления одного процесса доступны все объекты, адреса которых могут быть определены потоком. В число таких объектов входят статические переменные, области динамической памяти, полученные от функции malloc(), прямоадресуемая память, полученная от системно-зависимых функций, автоматические переменные и т.д.
По отношению к потокам управления вводится понятие безопасных функций, которые можно вызывать параллельно в нескольких потоках без нарушения корректности их функционирования. К числу безопасных принадлежат "чистые" функции, а также функции, обеспечивающие взаимное исключение перед доступом к разделяемым объектам. Если в стандарте явно не оговорено противное, функция считается потоково-безопасной.
Выполняющимся (активным) называется поток управления, обрабатываемый в данный момент процессором.
В многопроцессорных конфигурациях может одновременно выполняться несколько потоков.

Поток управления считается готовым к выполнению, если он способен стать активным, но не может этого сделать из-за отсутствия свободного процессора.

Поток управления называется вытесненным, если его выполнение приостановлено из-за того, что другой поток с более высоким приоритетом стал готов к выполнению.

Поток управления считается блокированным, если для продолжения его выполнения должно стать истинным некоторое условие, отличное от доступности процессора.

Списком потоков управления называется упорядоченный набор равноприоритетных потоков, готовых к выполнению. Порядок потоков в списке определяется политикой планирования. Множество наборов включает все потоки в системе, готовые к выполнению.

Планированием, согласно стандарту POSIX-2001, называется применение политики выбора процесса или потока управления, готового к выполнению, для его перевода в число активных, а также политики изменения списков потоков управления.

Под политикой планирования понимается набор правил, используемых для определения порядка выполнения процессов или потоков управления для достижения некоторой цели.

Политика планирования воздействует на порядок процессов (потоков управления) по крайней мере в следующих ситуациях:

  • когда активный процесс (поток управления) блокируется или вытесняется;
  • когда блокированный процесс (поток управления) становится готовым к выполнению.


  • Область планирования размещения – это набор процессоров, по отношению к которым в некоторый момент времени может планироваться поток управления.

    Областью планирования конкуренции называется свойство потока управления, определяющее набор потоков, с которыми он конкурирует за ресурсы, например, за процессор. В стандарте POSIX-2001 предусмотрены две подобные области – PTHREAD_SCOPE_SYSTEM (конкуренция в масштабе системы) и PTHREAD_SCOPE_PROCESS (конкуренция в масшабе процесса).

    Пожалуй, общей проблемой всех приложений является контроль переполнения стека.


    Для ее решения стандартом POSIX- 2001 предусмотрено существование так называемой защитной области, расположенной за верхней границей стека. При переполнении и попадания указателя стека в защитную область операционная система должна фиксировать ошибку – например, доставлять потоку управления сигнал SIGSEGV.

    С каждым потоком управления ассоциирован атрибутный объект – собрание атрибутов потока с конфигурируемыми значениями, таких как адрес и размер стека, параметры планирования и т.п. В стандарте POSIX-2001 атрибутные объекты представлены как значения абстрактного типа pthread_attr_t, внутренняя структура значений которого скрыта от приложений. Смысл введения атрибутных объектов – сгруппировать немобильные параметры потоков, чтобы облегчить адаптацию приложений к новым целевым платформам. Использование идеологии абстрактных объектов позволяет безболезненно добавлять новые атрибуты, не теряя обратной совместимости.

    Обратим внимание на следующее обстоятельство, важное для реализации многопотоковых приложений. Иногда потоки управления называют легковесными процессами, поскольку они требуют существенно меньшей, чем обычные процессы, аппаратно-программной поддержки и, кроме того, их функционирование сопряжено с меньшими накладными расходами. С этой точки зрения потоками можно пользоваться более свободно, чем процессами. С другой стороны, потоки одного процесса никак не защищены друг от друга, они разделяют одно адресное пространство, поэтому, в отличие от полноценных процессов, ошибки в программе одного из них могут сказаться на других, породить ситуации, которые трудно воспроизвести, что делает поиск и исправление ошибок крайне сложными. В этом смысле легковесной можно назвать аналогию с процессами, основанную только на возможности параллельной работы. Она обманчива, поскольку из вида упускается очень важный аспект разделения доменов выполнения. Вообще говоря, многопотоковые приложения существенно менее надежны, чем многопроцессные, поэтому потоками управления следует пользоваться осторожно, систематически.



    Операции с потоками управления можно подразделить на две группы:

  • создание, терминирование, выполнение других управляющих операций;
  • синхронизация.


  • В таком порядке они и будут рассматриваться далее. Отметим, что стандарт POSIX-2001 относит их к необязательной части, именуемой, как нетрудно догадаться, "Потоки управления" ("Threads", THR).

    Модель, принятая в стандарте применительно к созданию потоков управления, отличается от соответствующей модели для процессов. При создании нового потока задается функция, с вызова которой начнется его выполнение, то есть вместо пары вида fork()/exec() создающий поток должен обратиться лишь к одной функции – pthread_create(). Впрочем, как мы увидим далее, и для процессов в стандарте POSIX-2001 произошел отход от классических канонов – введены средства (функции семейства posix_spawn()) для порождения процессов "в один ход".

    Потоки управления бывают обособленными (отсоединенными) и присоединяемыми; только последние доступны другим потокам для ожидания завершения и, быть может, утилизации освободившихся ресурсов. Ресурсы, освободившиеся после завершения обособленных потоков управления, утилизирует операционная система.

    Поток управления можно терминировать изнутри и извне (из других потоков того же процесса). Поток может управлять состоянием восприимчивости к терминированию (разрешить/запретить собственное терминирование извне), а также специфицировать тип терминирования (отложенное или немедленное, асинхронное).

    Отложенное терминирование происходит только по достижении потоком управления точек терминирования – мест в оговоренных в стандарте POSIX функциях, где поток должен отреагировать на ждущие запросы на терминирование (если оно разрешено), перед тем как его выполнение будет приостановлено на неопределенное время с сохранением состояния восприимчивости к терминированию.

    Согласно стандарту POSIX-2001, точки терминирования имеются в таких функциях, как accept(), connect(), msgrcv(), msgsnd(), pause(), read(), sleep(), wait(), write() и сходных с ними по поведению.Допускается существование подобных точек и в других, также оговоренных в стандарте POSIX функциях – printf(), scanf(), semop() и т.п.


    pthread_t pthread_self


    #include pthread_t pthread_self (void);
    Листинг 1.1. Описание функции pthread_self().
    Закрыть окно




    #include int pthread_equal ( pthread_t t1, pthread_t t2);
    Листинг 1.2. Описание функции pthread_equal().
    Закрыть окно




    #include
    int pthread_attr_init ( pthread_attr_t *attr);
    int pthread_attr_destroy ( pthread_attr_t *attr);
    Листинг 1.3. Описание функций pthread_attr_init() и pthread_attr_destroy().
    Закрыть окно




    #include
    int pthread_attr_getstack ( const pthread_attr_t * restrict attr, void **restrict stackaddr, size_t *restrict stacksize);
    int pthread_attr_setstack ( pthread_attr_t *attr, void *stackaddr, size_t stacksize);
    Листинг 1.4. Описание функций pthread_attr_getstack() и pthread_attr_setstack().
    Закрыть окно




    #include
    int pthread_attr_getguardsize ( const pthread_attr_t * restrict attr, size_t *restrict guardsize);
    int pthread_attr_setguardsize ( pthread_attr_t *attr, size_t guardsize);
    Листинг 1.5. Описание функций pthread_attr_getguardsize() и pthread_attr_setguardsize().
    Закрыть окно




    #include
    int pthread_attr_getschedparam ( const pthread_attr_t *restrict attr, struct sched_param *restrict param);
    int pthread_attr_setschedparam ( pthread_attr_t * restrict attr, const struct sched_param *restrict param);
    Листинг 1.6. Описание функций pthread_attr_getschedparam() и pthread_attr_setschedparam().
    Закрыть окно




    #include
    int pthread_attr_getschedpolicy ( const pthread_attr_t * restrict attr, int *restrict policy);
    int pthread_attr_setschedpolicy ( pthread_attr_t *attr, int policy);
    Листинг 1.7. Описание функций pthread_attr_getschedpolicy() и pthread_attr_setschedpolicy().
    Закрыть окно




    #include
    int pthread_attr_getscope ( const pthread_attr_t * restrict attr, int *restrict contentionscope);
    int pthread_attr_setscope ( pthread_attr_t *attr, int contentionscope);
    Листинг 1.8. Описание функций pthread_attr_getscope() и pthread_attr_setscope().
    Закрыть окно




    #include
    int pthread_attr_getinheritsched ( const pthread_attr_t * restrict attr, int *restrict inheritsched);
    int pthread_attr_setinheritsched ( pthread_attr_t *attr, int inheritsched);
    Листинг 1.9. Описание функций pthread_attr_getinheritsched() и pthread_attr_setinheritsched().
    Закрыть окно




    #include
    int pthread_attr_getdetachstate ( const pthread_attr_t *attr, int *detachstate);
    int pthread_attr_setdetachstate ( pthread_attr_t *attr, int detachstate);
    Листинг 1.10. Описание функций pthread_attr_getdetachstate() и pthread_attr_setdetachstate().
    Закрыть окно




    #include
    int pthread_getschedparam ( pthread_t thread, int *restrict policy, struct sched_param *restrict param);
    int pthread_setschedparam ( pthread_t thread, int policy, const struct sched_param *param);
    Листинг 1.11. Описание функций pthread_getschedparam() и pthread_setschedparam().
    Закрыть окно




    #include int pthread_setschedprio ( pthread_t thread, int prio);
    Листинг 1.12. Описание функции pthread_setschedprio().
    Закрыть окно




    #include
    int pthread_getconcurrency (void);
    int pthread_setconcurrency (int new_level);
    Листинг 1.13. Описание функций pthread_getconcurrency() и pthread_setconcurrency().
    Закрыть окно




    #include #include
    int pthread_getcpuclockid ( pthread_t thread_id, clockid_t *clock_id);
    Листинг 1.14. Описание функции pthread_getcpuclockid().
    Закрыть окно




    #include int pthread_sigmask ( int how, const sigset_t *restrict set, sigset_t *restrict oset);
    Листинг 1.15. Описание функции pthread_sigmask().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа опрашивает атрибуты потоков управления */ /* и изменяет некоторые из них */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include #include
    int main (void) { pthread_t ct_id; /* Идентификатор текущего потока управления */ pthread_attr_t patob; /* Атрибутный объект для создания потоков управления */ int res; /* Переменная для запоминания результатов "потоковых" функций */ void *stackaddr; /* Начало стека как атрибут потока управления */ size_t atrsize; /* Размеры как атрибуты потока управления */ /* Структура с параметрами планирования */ struct sched_param shdprm; char *spname; /* Названия политики планирования, области */ /* планирования конкуренции и т.п. */
    printf ("Идентификатор текущего потока управления: %lx\n", (ct_id = pthread_self ()));
    if ((errno = pthread_attr_init (&patob)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); }
    printf ("Значения, установленные системой " "в атрибутном объекте\n");
    if ((errno = pthread_attr_getstack (&patob, &stackaddr, &atrsize)) != 0) { perror ("PTHREAD_ATTR_GETSTACK"); return (errno); } printf ("Адрес начала стека: %p\n", stackaddr); printf ("Размер стека: %d\n", atrsize);
    assert (pthread_attr_getguardsize (&patob, &atrsize) == 0); printf ("Размер защитной области: %d\n", atrsize);
    assert (pthread_attr_getschedparam (&patob, &shdprm) == 0); assert (pthread_attr_getschedpolicy (&patob, &res) == 0); switch (res) { case SCHED_FIFO: spname = "Планирование по очереди"; break; case SCHED_RR: spname = "Циклическое планирование"; break; case SCHED_OTHER: spname = "Прочее планирование"; break; default: spname = "Неизвестная политика планирования"; } printf ("Политика планирования: %s\n", spname); printf ("Приоритет планирования: %d\n", shdprm.sched_priority);
    assert (pthread_attr_getscope (&patob, &res) == 0); switch (res) { case PTHREAD_SCOPE_SYSTEM: spname = "Система"; break; case PTHREAD_SCOPE_PROCESS: spname = "Процесс"; break; default: spname = "Неизвестная область планирования " "конкуренции"; } printf ("Область планирования конкуренции: %s\n", spname);
    assert (pthread_attr_getinheritsched (&patob, &res) == 0); switch (res) { case PTHREAD_INHERIT_SCHED: spname = "Наследуются у родительского потока"; break; case PTHREAD_EXPLICIT_SCHED: spname = "Извлекаются из атрибутного объекта"; break; default: spname = "Устанавливаются неизвестным образом"; } printf ("Атрибуты планирования: %s\n", spname);
    assert (pthread_attr_getdetachstate (&patob, &res) == 0); switch (res) { case PTHREAD_CREATE_JOINABLE: spname = "Присоединяемые"; break; case PTHREAD_CREATE_DETACHED: spname = "Обособленные"; break; default: spname = "Неизвестные"; } printf ("Потоки управления создаются как: %s\n", spname);
    /* Изменим значения атрибутов планирования и уровня */ /* параллелизма */ shdprm.sched_priority = 1; if ((errno = pthread_setschedparam (ct_id, SCHED_RR, &shdprm)) != 0) { perror ("PTHREAD_SETSCHEDPARAM"); } if ((errno = pthread_setconcurrency (8192)) != 0) { perror ("PTHREAD_SETCONCURRENCY"); } printf ("\nТекущие значения атрибутов потоков управления\n");
    assert (pthread_getschedparam (ct_id, &res, &shdprm) == 0); switch (res) { case SCHED_FIFO: spname = "Планирование по очереди"; break; case SCHED_RR: spname = "Циклическое планирование"; break; case SCHED_OTHER: spname = "Прочее планирование"; break; default: spname = "Неизвестная политика планирования"; } printf ("Политика планирования: %s\n", spname); printf ("Приоритет планирования: %d\n", shdprm.sched_priority); printf ("Уровень параллелизма: %d\n", pthread_getconcurrency());
    return 0; }
    Листинг 1.16. Пример программы, опрашивающей и изменяющей значения атрибутов потоков управления.
    Закрыть окно




    Идентификатор текущего потока управления: 400 Значения, установленные системой в атрибутном объекте Адрес начала стека: 0xffe01000 Размер стека: 2093056 Размер защитной области: 4096 Политика планирования: Прочее планирование Приоритет планирования: 0 Область планирования конкуренции: Система Атрибуты планирования: Извлекаются из атрибутного объекта Потоки управления создаются как: Присоединяемые
    Текущие значения атрибутов потоков управления Политика планирования: Циклическое планирование Приоритет планирования: 1 Уровень параллелизма: 8192
    Листинг 1.17. Возможные результаты работы программы, опрашивающей и изменяющей значения атрибутов потоков управления.
    Закрыть окно




    Идентификатор текущего потока управления: f31ae0 Значения, установленные системой в атрибутном объекте Политика планирования: Планирование по очереди Приоритет планирования: 100 Область планирования конкуренции: Процесс Атрибуты планирования: Извлекаются из атрибутного объекта Потоки управления создаются как: Присоединяемые
    Текущие значения атрибутов потоков управления Политика планирования: Циклическое планирование Приоритет планирования: 1
    Листинг 1.18. Возможные результаты работы программы, опрашивающей и изменяющей значения атрибутов потоков управления, для операционной системы реального времени oc2000.
    Закрыть окно




    #include
    pthread_once_t once_control = PTHREAD_ONCE_INIT;
    int pthread_once ( pthread_once_t *once_control_ptr, void (*init_routine) (void));
    Листинг 1.19. Описание функции pthread_once().
    Закрыть окно




    #include
    int pthread_key_create ( pthread_key_t *key_ptr, void (*destructor) (void *));
    int pthread_key_delete ( pthread_key_t key);
    Листинг 1.20. Описание функций pthread_key_create() и pthread_key_delete().
    Закрыть окно




    #include
    void *pthread_getspecific ( pthread_key_t key);
    int pthread_setspecific ( pthread_key_t key, const void *value);
    Листинг 1.21. Описание функций pthread_getspecific() и pthread_setspecific().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запоминает в качестве индивидуальных данных */ /* потока управления время начала активных операций */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include static pthread_key_t data_key; static pthread_once_t key_once = PTHREAD_ONCE_INIT;
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Деструктор индивидуальных данных, в роли которых */ /* выступает указатель на структуру типа timeval. */ /* Поскольку она не содержит указателей, достаточно */ /* освободить занимаемую ею память */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void data_destructor (void *p) { free (p); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция создания ключа индивидуальных данных, */ /* ассоциирующая с ним деструктор, освобождающий память */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void create_data_key (void) { (void) pthread_key_create (&data_key, data_destructor); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция инициализации индивидуальных данных. */ /* Запрашивает астрономическое время */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_func (void) { struct timeval *tmvl_ptr;
    /* Запомним астрономическое время начала операций */ /* потока управления */ if ((tmvl_ptr = (struct timeval *) malloc (sizeof (struct timeval))) == NULL) { return (NULL); } (void) gettimeofday (tmvl_ptr, NULL);
    /* Создадим ключ индивидуальных данных, перепоручив */ /* вызов pthread_key_create() функции pthread_once() */ (void) pthread_once (&key_once, create_data_key);
    (void) pthread_setspecific (data_key, tmvl_ptr); return (tmvl_ptr); }
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() вызывает функцию инициализации */ /* и запрашивает индивидуальные данные потока */ /* управления */ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { struct timeval *tmvl_ptr;
    if (start_func () == NULL) { return (1); }
    if ((tmvl_ptr = (struct timeval *) pthread_getspecific (data_key)) != NULL) { printf ("Время начала операций потока управления: " "%ld сек, %ld мсек\n", tmvl_ptr->tv_sec, tmvl_ptr->tv_usec); } else { printf ("Отсутствуют индивидуальные данные потока " "управления.\n"); printf ("Время начала операций неизвестно\n"); return (2); }
    return 0; }
    Листинг 1.22. Пример программы, формирующей и опрашивающей индивидуальные данные потоков управления.
    Закрыть окно




    Время начала операций потока управления: 1075707670 сек, 584737 мсек
    Листинг 1.23. Возможные результаты работы программы, формирующей и опрашивающей индивидуальные данные потоков управления.
    Закрыть окно




    #include int pthread_create ( pthread_t * restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine) (void *), void *restrict arg);
    Листинг 1.24. Описание функции pthread_create().
    Закрыть окно




    #include int pthread_atfork ( void (*prepare) (void), void (*parent) (void), void (*child) (void));
    Листинг 1.25. Описание функции pthread_atfork().
    Закрыть окно




    #include void pthread_exit (void *value_ptr);
    Листинг 1.26. Описание функции pthread_exit().
    Закрыть окно




    #include int pthread_join ( pthread_t thread, void **value_ptr_ptr);
    Листинг 1.27. Описание функции pthread_join().
    Закрыть окно




    #include
    void pthread_cleanup_push ( void (*routine) (void *), void *arg);
    void pthread_cleanup_pop (int execute);
    Листинг 1.28. Описание функций pthread_cleanup_push() и pthread_cleanup_pop().
    Закрыть окно




    #define pthread_cleanup_push (rtn, arg) { \ struct _pthread_handler_rec \ __cleanup_handler, \ **__head; \ __cleanup_handler.rtn = rtn; \ __cleanup_handler.arg = arg; \ (void) pthread_getspecific \ (_pthread_handler_key, &__head); \ __cleanup_handler.next = *__head; \ *__head = &__cleanup_handler;
    #define pthread_cleanup_pop (ex) \ *__head = __cleanup_handler.next; \ if (ex) (*__cleanup_handler.rtn) \ (__cleanup_handler.arg); \ }
    Листинг 1.29. Возможная реализация функций pthread_cleanup_push() и pthread_cleanup_pop() как макросов.
    Закрыть окно




    #include int pthread_cancel (pthread_t thread);
    Листинг 1.30. Описание функции pthread_cancel().
    Закрыть окно




    #include
    int pthread_setcancelstate ( int state, int *oldstate);
    int pthread_setcanceltype ( int type, int *oldtype);
    void pthread_testcancel (void);
    Листинг 1.31. Описание функций pthread_setcancelstate(), pthread_setcanceltype(), pthread_testcancel().
    Закрыть окно




    #include int pthread_kill ( pthread_t thread, int sig);
    Листинг 1.32. Описание функции pthread_kill().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * */ /* Программа демонстрирует генерацию */ /* и доставку сигналов */ /* потокам управления */ /* * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* * * * * * * * * * * * * * / /* Функция обработки сигнала */ /* * * * * * * * * * * * * * / static void signal_handler (int dummy) { printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n", pthread_self ()); }
    /* * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока управления, */ /* которому будет направлен сигнал */ /* * * * * * * * * * * * * * * * * * * */ static void *thread_start (void *dummy) { printf ("Идентификатор нового потока управления: %lx\n", pthread_self ()); while (1) { sleep (1); }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGINT, */ /* создает поток управления и посылает ему сигнал */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t thread_id; struct sigaction act;
    /* Установим реакцию на сигнал SIGINT */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);
    if ((errno = pthread_create (&thread_id, NULL, thread_start, NULL)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } printf ("Идентификатор созданного потока управления: %lx\n", thread_id);
    (void) pthread_kill (thread_id, SIGINT); printf ("После вызова pthread_kill()\n");
    sleep (1); printf ("Выспались...\n");
    return (0); }
    Листинг 1.33. Пример использования механизма сигналов в многопотоковой программе.
    Закрыть окно




    Идентификатор созданного потока управления: 402 После вызова pthread_kill() Идентификатор потока, обрабатывающего сигнал: 402 Идентификатор нового потока управления: 402 Выспались...
    Листинг 1.34. Возможные результаты работы многопотоковой программы, использующей механизм сигналов.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует взаимодействие сигналов */ /* и ожидания завершения потока управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include
    /* * * * * * * * * * * * * * */ /* Функция обработки сигнала */ /* * * * * * * * * * * * * * */ static void signal_handler (int dummy) { printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n", pthread_self ()); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция создаваемого потока управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void *thread_start (void *thread_id) { printf ("Идентификатор нового потока управления: %lx\n", pthread_self ()); (void) pthread_kill ((pthread_t) thread_id, SIGINT);
    return ((void *) pthread_self ()); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGINT, */ /* создает поток управления и ожидает его завершения */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t thread_id; struct sigaction act; void *pv;
    /* Установим реакцию на сигнал SIGINT */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);
    if ((errno = pthread_create (&thread_id, NULL, thread_start, (void *) pthread_self ())) != 0) { perror ("PTHREAD_CREATE"); return (errno); } printf ("Идентификаторы начального и созданного потоков " "управления: " "%lx %lx\n", pthread_self (), thread_id);
    /* Дождемся завершения созданного потока управления */ if ((errno = pthread_join (thread_id, &pv)) != 0) { perror ("PTHREAD_JOIN"); return (errno); } printf ("Статус завершения созданного потока " "управления: %p\n", pv);
    return (0); }
    Листинг 1.35. Пример программы, обрабатывающей сигнал во время ожидания завершения потока управления.
    Закрыть окно




    Идентификаторы начального и созданного потоков управления: 400 402 Идентификатор нового потока управления: 402 Идентификатор потока, обрабатывающего сигнал: 400 Статус завершения созданного потока управления: 0x402
    Листинг 1.36. Возможные результаты работы программы, обрабатывающей сигнал во время ожидания завершения потока управления.
    Закрыть окно




    #include int pthread_detach (pthread_t thread);
    Листинг 1.37. Описание функции pthread_detach().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его серверным), */ /* принимающего запросы на установления соединения и */ /* запускающего потоки управления для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая (и единственная) функция потоков управления, */ /* обслуживающих запросы на копирование строк, */ /* поступающих из сокета */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *srv_thread_start (void *ad) { FILE *fpad; /* Поток данных, соответствующий */ /* дескриптору ad */ char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Структура для записи адреса */ struct sockaddr_in sai; /* Длина адреса */ socklen_t sai_len = sizeof (struct sockaddr_in);
    /* Опросим адрес партнера по общению (передающего сокета) */ if (getpeername ((int) ad, (struct sockaddr *) &sai, &sai_len) < 0) { perror ("GETPEERNAME"); return (NULL); }
    /* По файловому дескриптору ad сформируем */ /* буферизованный поток данных */ if ((fpad = fdopen ((int) ad, "r")) == NULL) { perror ("FDOPEN"); return (NULL); }
    /* Цикл чтения строк из сокета */ /* и выдачи их на стандартный вывод */ while (fgets (line, sizeof (line), fpad) != NULL) { printf ("Вы ввели и отправили с адреса %s, " "порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); fputs (line, stdout); }
    /* Закрытие соединения */ shutdown ((int) ad, SHUT_RD); (void) fclose (fpad);
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* В функции main() принимаются запросы на установление */ /* соединения и запускаются потоки управления для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Буфер для принимаемых строк */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель – выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ pthread_attr_t patob; /* Атрибутный объект для создания */ /* потоков управления */ pthread_t adt_id; /* Идентификатор обслуживающего потока управления */
    /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
    /* Привяжем этот сокет к адресу сервиса spooler */ /* на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (2); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (3); }
    /* Можно освободить память, которую запрашивала */ /* функция getaddrinfo() */ freeaddrinfo (addr_res);
    /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (4); }
    /* Инициализируем атрибутный объект потоков управления */ if ((errno = pthread_attr_init (&patob)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); } /* Потоки управления будем создавать обособленными */ (void) pthread_attr_setdetachstate (&patob, PTHREAD_CREATE_DETACHED);
    /* Цикл приема соединений и запуска */ /* обслуживающих потоков управления */ while (1) { /* Примем соединение. */ /* Адрес партнера по общению нас */ /* в данном случае не интересует */ if ((ad = accept (sd, NULL, NULL)) < 0) { perror ("ACCEPT"); return (6); }
    /* Запустим обслуживающий поток управления */ if ((errno = pthread_create (&adt_id, &patob, srv_thread_start,(void *) ad)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } }
    return (0); }
    Листинг 1.38. Пример многопотоковой программы, обслуживающей запросы на копирование строк, поступающих через сокеты.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа вызывает функции обработки в рамках */ /* порождаемых потоков управления и контролирует время */ /* их выполнения с помощью интервального таймера */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* Период интервального таймера (в секундах) */ #define IT_PERIOD 1
    static pthread_t cthread_id; /* Идентификатор текущего */ /* потока управления, */ /* обрабатывающего данные */
    static int in_proc_data = 0; /* Признак активности */ /* потока обработки данных */
    static double s; /* Результат функций */ /* обработки данных */
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция обработки срабатывания таймера реального */ /* времени (сигнал SIGALRM) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_sigalrm (int dummy) { if (in_proc_data) { /* Не имеет значения, какой поток обрабатывает сигнал */ /* и заказывает терминирование (быть может, себя) */ (void) pthread_cancel (cthread_id); } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Обработчик завершения потока управления. */ /* Сбрасывает признак активности потока обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_cleanup_handler (void *arg) { in_proc_data = (int) arg; }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока управления, обрабатывающего */ /* данные. Аргумент – указатель на функцию обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_func (void *proc_data_func) { /* Поместим в стек обработчик завершения */ pthread_cleanup_push (proc_data_cleanup_handler, 0); in_proc_data = 1; /* Время пошло ... */
    /* На время выполнения функции обработки данных установим */ /* асинхронный тип терминирования, иначе оно не сработает */ (void) pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    /* Выполним функцию обработки данных */ ((void (*) (void)) (proc_data_func)) ();
    /* Установим отложенный тип терминирования, */ /* иначе изъятие обработчика из стека */ /* будет небезопасным действием */ (void) pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, NULL);
    /* Выполним обработчик завершения и удалим его из стека */ pthread_cleanup_pop (1); return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Первая функция обработки данных (вычисляет ln (2)) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_1 (void) { double d = 1; int i;
    s = 0; for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Вторая функция обработки данных (вычисляет sqrt (2))*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_2 (void) { s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s – 2) > 0.000000001); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGALRM, */ /* взводит периодический таймер реального времени */ /* и запускает в цикле потоки обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив указателей на функции обработки данных */ void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL}; /* Указатель на указатель на */ /* текущую функцию обработки данных */ void (**tfptr) (void); void *pstat; /* Статус завершения потока */ /* обработки данных */ struct itimerval itvl; struct sigaction sact; int i;
    /* Установим реакцию на сигнал SIGALRM */ sact.sa_handler = proc_sigalrm; sact.sa_flags = 0; (void) sigemptyset (&sact.sa_mask); if (sigaction (SIGALRM, &sact, NULL) < 0) { perror ("SIGACTION"); return (1); }
    /* Сделаем таймер реального времени периодическим */ itvl.it_interval.tv_sec = IT_PERIOD; itvl.it_interval.tv_usec = 0;
    /* Цикл запуска потоков обработки данных. */ /* Выполним его дважды */ for (i = 0; i < 2; i++) { for (tfptr = fptrs; *tfptr != NULL; tfptr++) { /* Взведем интервальный таймер реального времени */ itvl.it_value.tv_sec = IT_PERIOD; itvl.it_value.tv_usec = 0; if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER"); return (2); }
    /* Создадим поток обработки данных, */ /* затем дождемся его завершения */ if ((errno = pthread_create (&cthread_id, NULL, start_func, (void *) *tfptr)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } if ((errno = pthread_join (cthread_id, &pstat)) != 0) { perror ("PTHREAD_JOIN"); return (errno); }
    if (pstat == PTHREAD_CANCELED) { printf ("Частичный результат функции " "обработки данных: %g\n", s); } else { printf ("Полный результат функции " "обработки данных: %g\n", s); } } }
    return 0; }
    Листинг 1.39. Пример многопотоковой программы, осуществляющей обработку данных с контролем времени.
    Закрыть окно




    Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421 Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421
    Листинг 1.40. Возможные результаты работы многопотоковой программы, осуществляющей обработку данных с контролем времени.
    Закрыть окно




    #include #include #include #include
    #define N 10000
    int main (void) { int i;
    for (i = 0; i < N; i++) { switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: /* Порожденный процесс */ (void) execl ("./dummy", "dummy", (char *) 0); exit (0); default: /* Родительский процесс */ (void) wait (NULL); } }
    return 0; }
    Листинг 1.41. Пример программы, порождающей в цикле практически пустые процессы.
    Закрыть окно




    int main (void) { return 0; }
    Листинг 1.42. Содержимое файла dummy.c
    Закрыть окно




    real 34.97 user 12.36 sys 22.61
    Листинг 1.43. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы (вариант с вызовом execl()).
    Закрыть окно




    real 11.49 user 2.38 sys 9.11
    Листинг 1.44. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы (вариант без вызова execl()).
    Закрыть окно




    #include #include #include #include
    #define N 10000
    static void *thread_start (void *arg) { pthread_exit (arg); }
    int main (void) { pthread_t thread_id; int i;
    for (i = 0; i < N; i++) { if ((errno = pthread_create ( &thread_id, NULL, thread_start, NULL)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } if ((errno = pthread_join ( thread_id, NULL)) != 0) { perror ("PTHREAD_JOIN"); return (errno); } }
    return (0); }
    Листинг 1.45. Пример программы, порождающей в цикле потоки управления.
    Закрыть окно




    real 2.08 user 0.52 sys 1.56
    Листинг 1.46. Возможные результаты измерения времени работы программы, порождающей в цикле потоки управления.
    Закрыть окно



    Работа с индивидуальными данными потоков управления


    Все потоки управления одного процесса разделяют общее адресное пространство и, следовательно, имеют общие данные. Чтобы сделать некоторые данные индивидуальными для потока, нужно принять специальные меры: с помощью функции pthread_key_create() создать ключ и ассоциировать с ним индивидуальные данные, воспользовавшись функцией pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому) ключу разные потоки будут получать доступ к разным данным.
    Создать один ключ, очевидно, нужно один раз. В ситуации, когда несколько потоков управления выполняют одну программу, это сопряжено с определенными проблемами. Стандартный прием, заключающийся во введении статической переменной, хранящей признак инициализированности (в данном случае – признак того, что ключ уже создан) в многопотоковой среде не работает, поскольку проверка и последующее изменение значения подобной переменной не являются атомарным действием. В принципе, манипуляции со статической переменной можно защитить каким-либо средством синхронизации, но его тоже нужно инициализировать!
    Для решения проблемы однократного выполнения инициализирующих действий в многопотоковой среде стандарт POSIX-2001 предлагает функцию pthread_once() (см. листинг 1.19).
    #include
    pthread_once_t once_control = PTHREAD_ONCE_INIT;
    int pthread_once ( pthread_once_t *once_control_ptr, void (*init_routine) (void));
    Листинг 1.19. Описание функции pthread_once(). (html, txt)
    При первом и только при первом обращении к функции pthread_once() с фиксированным значением аргумента once_control_ptr, вне зависимости от того, какой из потоков процесса его выполняет, будет вызвана функция (*init_routine) (), которая по идее должна осуществлять инициализирующие действия, такие как создание ключа индивидуальных данных потоков управления.
    Переменная, на которую указывает аргумент once_control_ptr, должна иметь начальное значение PTHREAD_ONCE_INIT и не должна быть автоматической.

    Все потоки управления одного процесса разделяют общее адресное пространство и, следовательно, имеют общие данные. Чтобы сделать некоторые данные индивидуальными для потока, нужно принять специальные меры: с помощью функции pthread_key_create() создать ключ и ассоциировать с ним индивидуальные данные, воспользовавшись функцией pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому) ключу разные потоки будут получать доступ к разным данным.
    Создать один ключ, очевидно, нужно один раз. В ситуации, когда несколько потоков управления выполняют одну программу, это сопряжено с определенными проблемами. Стандартный прием, заключающийся во введении статической переменной, хранящей признак инициализированности (в данном случае – признак того, что ключ уже создан) в многопотоковой среде не работает, поскольку проверка и последующее изменение значения подобной переменной не являются атомарным действием. В принципе, манипуляции со статической переменной можно защитить каким-либо средством синхронизации, но его тоже нужно инициализировать!
    Для решения проблемы однократного выполнения инициализирующих действий в многопотоковой среде стандарт POSIX-2001 предлагает функцию pthread_once() (см. листинг 1.19).
    #include
    pthread_once_t once_control = PTHREAD_ONCE_INIT;
    int pthread_once ( pthread_once_t *once_control_ptr, void (*init_routine) (void));
    Листинг 1.19. Описание функции pthread_once().
    При первом и только при первом обращении к функции pthread_once() с фиксированным значением аргумента once_control_ptr, вне зависимости от того, какой из потоков процесса его выполняет, будет вызвана функция (*init_routine) (), которая по идее должна осуществлять инициализирующие действия, такие как создание ключа индивидуальных данных потоков управления.
    Переменная, на которую указывает аргумент once_control_ptr, должна иметь начальное значение PTHREAD_ONCE_INIT и не должна быть автоматической.



    За создание и удаление ключа индивидуальных данных потоков управления, согласно стандарту POSIX-2001, отвечают функции pthread_key_create() и pthread_key_delete() (см. листинг 1.20).

    #include

    int pthread_key_create ( pthread_key_t *key_ptr, void (*destructor) (void *));

    int pthread_key_delete ( pthread_key_t key);

    Листинг 1.20. Описание функций pthread_key_create() и pthread_key_delete(). (html, txt)

    Функция pthread_key_create() создает новый ключ, которым могут воспользоваться все входящие в процесс потоки управления, и, в соответствии со "штабной дисциплиной", помещает его по указателю key_ptr. Сразу после создания ключа для всех потоков в качестве индивидуальных данных с ним ассоциируется значение NULL. (Аналогично, после создания потока управления он не имеет индивидуальных данных, поэтому со всеми доступными ему ключами также ассоциированы пустые указатели.)

    С ключом может быть ассоциирован деструктор, который вызывается при завершении потока управления с индивидуальными данными в качестве аргумента. Обычно в роли индивидуальных данных выступает указатель на динамически зарезервированную потоком область памяти, которую деструктор должен освободить.

    Функция pthread_key_delete() удаляет заданный ключ, не заботясь о том, пусты ли ассоциированные значения, и не вызывая каких-либо деструкторов (напротив, ее обычно вызывают из деструктора). Вся ответственность по освобождению памяти и выполнению других необходимых зачисток возлагается на приложение.

    Для выборки и изменения ассоциированных с ключом key индивидуальных данных вызывающего потока управления предназначены функции pthread_getspecific() и pthread_setspecific() (см. листинг 1.21).

    #include

    void *pthread_getspecific ( pthread_key_t key);

    int pthread_setspecific ( pthread_key_t key, const void *value);

    Листинг 1.21. Описание функций pthread_getspecific() и pthread_setspecific(). (html, txt)

    Функция pthread_getspecific() возвращает индивидуальные данные в качестве результата; функция pthread_setspecific() ассоциирует с ключом key значение аргумента value.

    На листинге 1.22 показана программа, использующая стандартную схему создания ключа и манипулирования индивидуальными данными потоков управления.

    Листинг 1.22. Пример программы, формирующей и опрашивающей индивидуальные данные потоков управления. (html, txt)

    Результат работы этой программы может выглядеть так, как показано на листинге 1.23.

    Время начала операций потока управления: 1075707670 сек, 584737 мсек

    Листинг 1.23. Возможные результаты работы программы, формирующей и опрашивающей индивидуальные данные потоков управления. (html, txt)



    За создание и удаление ключа индивидуальных данных потоков управления, согласно стандарту POSIX-2001, отвечают функции pthread_key_create() и pthread_key_delete() (см. листинг 1.20).

    #include

    int pthread_key_create ( pthread_key_t *key_ptr, void (*destructor) (void *));

    int pthread_key_delete ( pthread_key_t key);

    Листинг 1.20. Описание функций pthread_key_create() и pthread_key_delete().

    Функция pthread_key_create() создает новый ключ, которым могут воспользоваться все входящие в процесс потоки управления, и, в соответствии со "штабной дисциплиной", помещает его по указателю key_ptr. Сразу после создания ключа для всех потоков в качестве индивидуальных данных с ним ассоциируется значение NULL. (Аналогично, после создания потока управления он не имеет индивидуальных данных, поэтому со всеми доступными ему ключами также ассоциированы пустые указатели.)

    С ключом может быть ассоциирован деструктор, который вызывается при завершении потока управления с индивидуальными данными в качестве аргумента. Обычно в роли индивидуальных данных выступает указатель на динамически зарезервированную потоком область памяти, которую деструктор должен освободить.

    Функция pthread_key_delete() удаляет заданный ключ, не заботясь о том, пусты ли ассоциированные значения, и не вызывая каких-либо деструкторов (напротив, ее обычно вызывают из деструктора). Вся ответственность по освобождению памяти и выполнению других необходимых зачисток возлагается на приложение.

    Для выборки и изменения ассоциированных с ключом key индивидуальных данных вызывающего потока управления предназначены функции pthread_getspecific() и pthread_setspecific() (см. листинг 1.21).

    #include

    void *pthread_getspecific ( pthread_key_t key);

    int pthread_setspecific ( pthread_key_t key, const void *value);

    Листинг 1.21. Описание функций pthread_getspecific() и pthread_setspecific().

    Функция pthread_getspecific() возвращает индивидуальные данные в качестве результата; функция pthread_setspecific() ассоциирует с ключом key значение аргумента value.



    На листинге 1. 22 показана программа, использующая стандартную схему создания ключа и манипулирования индивидуальными данными потоков управления.

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запоминает в качестве индивидуальных данных */ /* потока управления время начала активных операций */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include static pthread_key_t data_key; static pthread_once_t key_once = PTHREAD_ONCE_INIT;

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Деструктор индивидуальных данных, в роли которых */ /* выступает указатель на структуру типа timeval. */ /* Поскольку она не содержит указателей, достаточно */ /* освободить занимаемую ею память */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void data_destructor (void *p) { free (p); }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция создания ключа индивидуальных данных, */ /* ассоциирующая с ним деструктор, освобождающий память */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void create_data_key (void) { (void) pthread_key_create (&data_key, data_destructor); }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция инициализации индивидуальных данных. */ /* Запрашивает астрономическое время */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_func (void) { struct timeval *tmvl_ptr;

    /* Запомним астрономическое время начала операций */ /* потока управления */ if ((tmvl_ptr = (struct timeval *) malloc (sizeof (struct timeval))) == NULL) { return (NULL); } (void) gettimeofday (tmvl_ptr, NULL);

    /* Создадим ключ индивидуальных данных, перепоручив */ /* вызов pthread_key_create() функции pthread_once() */ (void) pthread_once (&key_once, create_data_key);

    (void) pthread_setspecific (data_key, tmvl_ptr); return (tmvl_ptr); }

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() вызывает функцию инициализации */ /* и запрашивает индивидуальные данные потока */ /* управления */ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { struct timeval *tmvl_ptr;



    if (start_func () == NULL) { return (1); }

    if ((tmvl_ptr = (struct timeval *) pthread_getspecific (data_key)) != NULL) { printf (" Время начала операций потока управления: " "%ld сек, %ld мсек\n", tmvl_ptr->tv_sec, tmvl_ptr->tv_usec); } else { printf ("Отсутствуют индивидуальные данные потока " "управления.\n"); printf ("Время начала операций неизвестно\n"); return (2); }

    return 0; }

    Листинг 1.22. Пример программы, формирующей и опрашивающей индивидуальные данные потоков управления.

    Результат работы этой программы может выглядеть так, как показано на листинге 1.23.

    Время начала операций потока управления: 1075707670 сек, 584737 мсек

    Листинг 1.23. Возможные результаты работы программы, формирующей и опрашивающей индивидуальные данные потоков управления.


    Создание и терминирование потоков управления


    Для создания нового потока управления служит функция pthread_create() (см. листинг 1.24).
    #include int pthread_create ( pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine) (void *), void *restrict arg);
    Листинг 1.24. Описание функции pthread_create(). (html, txt)
    Выполнение созданного потока управления начнется с вызова (*start_routine) (arg); возврат из этой функции приведет к терминированию потока с возвращаемым значением в качестве статуса завершения. Из этого правила стандартом предусмотрено одно, вполне естественное исключение: для потока, выполнение которого началось с функции main(), возврат из нее означает завершение процесса, содержащего поток, со всеми вытекающими отсюда последствиями.
    Аргумент attr задает атрибуты нового потока; если значение attr равно NULL, используются зависящие от реализации подразумеваемые атрибуты.
    От "родительского" вновь созданный поток управления наследует немногое: маску сигналов и характеристики вещественной арифметики.
    К числу средств создания потоков можно отнести и функцию fork(). Правда, здесь нас будет интересовать не она сама, а ассоциированные с ней обработчики, зарегистрированные с помощью функции pthread_atfork() (см. листинг 1.25).
    #include int pthread_atfork ( void (*prepare) (void), void (*parent) (void), void (*child) (void));
    Листинг 1.25. Описание функции pthread_atfork(). (html, txt)
    В каждом обращении к pthread_atfork() фигурируют три обработчика (если, конечно, в качестве значения аргумента не задан пустой указатель). Первый ((*prepare)()) выполняется в контексте потока, вызвавшего fork(), до разветвления процесса; второй ((*parent)()) – в том же контексте, но после разветвления; третий ((*child)()) – в контексте единственного потока порожденного процесса.
    С помощью pthread_atfork() можно зарегистрировать несколько троек обработчиков. Первые элементы троек вызываются в порядке, обратном по отношению к регистрации; вторые и третьи выполняются в прямом порядке.

    Для создания нового потока управления служит функция pthread_create() (см. листинг 1.24).
    #include int pthread_create ( pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine) (void *), void *restrict arg);
    Листинг 1.24. Описание функции pthread_create().
    Выполнение созданного потока управления начнется с вызова (*start_routine) (arg); возврат из этой функции приведет к терминированию потока с возвращаемым значением в качестве статуса завершения. Из этого правила стандартом предусмотрено одно, вполне естественное исключение: для потока, выполнение которого началось с функции main(), возврат из нее означает завершение процесса, содержащего поток, со всеми вытекающими отсюда последствиями.
    Аргумент attr задает атрибуты нового потока; если значение attr равно NULL, используются зависящие от реализации подразумеваемые атрибуты.
    От "родительского" вновь созданный поток управления наследует немногое: маску сигналов и характеристики вещественной арифметики.
    К числу средств создания потоков можно отнести и функцию fork(). Правда, здесь нас будет интересовать не она сама, а ассоциированные с ней обработчики, зарегистрированные с помощью функции pthread_atfork() (см. листинг 1.25).
    #include int pthread_atfork ( void (*prepare) (void), void (*parent) (void), void (*child) (void));
    Листинг 1.25. Описание функции pthread_atfork().
    В каждом обращении к pthread_atfork() фигурируют три обработчика (если, конечно, в качестве значения аргумента не задан пустой указатель). Первый ((*prepare)()) выполняется в контексте потока, вызвавшего fork(), до разветвления процесса; второй ((*parent)()) – в том же контексте, но после разветвления; третий ((*child)()) – в контексте единственного потока порожденного процесса.
    С помощью pthread_atfork() можно зарегистрировать несколько троек обработчиков. Первые элементы троек вызываются в порядке, обратном по отношению к регистрации; вторые и третьи выполняются в прямом порядке.
    Как и процесс, поток управления можно терминировать изнутри и извне. С одним, неявным, но наиболее естественным способом "самоликвидации" – выходом из стартовой функции потока – мы уже познакомились. Тот же эффект достигается вызовом функции pthread_exit() (см. листинг 1.26).
    #include void pthread_exit (void *value_ptr);
    Листинг 1.26. Описание функции pthread_exit().
    Терминирование потока управления в идейном плане существенно сложнее создания. Чтобы осветить все тонкости, необходимо ввести несколько новых понятий и описать целый ряд функций. Пока мы укажем лишь, что терминирование последнего потока в процессе вызывает его (процесса) завершение с нулевым кодом.
    Из общих соображений (например, если исходить из аналогии между процессами и потоками управления) очевидно, что должна существовать возможность дождаться завершения заданного потока управления. Эта возможность реализуется функцией pthread_join() (см. листинг 1.27), напоминающей waitpid().
    #include int pthread_join ( pthread_t thread, void **value_ptr_ptr);
    Листинг 1.27. Описание функции pthread_join().
    Поток управления, вызвавший функцию pthread_join(), приостанавливает выполнение до завершения потока, идентификатор которого задан аргументом thread. При успешном возврате из pthread_join() результат, как и положено, равен нулю, а по указателю value_ptr_ptr (если он не пуст) помещается значение (указатель value_ptr), переданное в качестве аргумента функции pthread_exit(). Тем самым ждуший поток получает данные о статусе завершения ожидаемого.
    Отметим, что трактовка значения value_ptr возлагается на приложение. Например, оно может считать его целым числом, а не указателем; по этой причине операционная система при выполнении функции pthread_exit() не вправе выдавать ошибку типа "неверный адрес" каким бы ни был аргумент value_ptr. Если он все же является указателем, то ему нельзя присваивать адрес автоматической переменной, поскольку к моменту его использования ожидаемый поток уже завершится и состояние его автоматических переменных станет неопределенным.
    Второе общее соображение касается того обстоятельства, что вызов такой функции, как ожидание завершения (pthread_join()) способен приостановить выполнение вызывающего потока управления на неопределенное время, в течение которого ему может быть доставлен обрабатываемый сигнал. Как правило, в подобных ситуациях выполнение функций (таких, например, как read()) завершается с частично достигнутым результатом (например, с числом прочитанных байт, меньшим запрошенного) и кодом ошибки EINTR, нуждающимся в нестандартной обработке, далеко не всегда реализуемой разработчиками приложений. Из-за этого в программах появляются дефекты, которые трудно воспроизвести и, соответственно, исправить.
    Согласно стандарту POSIX-2001, функции, обслуживающие потоки управления, свободны от этого недостатка. Они никогда не завершаются с частичным результатом и не выдают код ошибки EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.
    Третье общее соображение состоит в том, что такое критически важное событие, как завершение потока управления, не может оставаться без функций-обработчиков. Стандартом POSIX-2001 предусмотрено существование не одного, а целого стека подобных обработчиков, ассоциированного с потоком управления. Операции над этим стеком возложены на функции pthread_cleanup_push() и pthread_cleanup_pop() (см. листинг 1.28).
    #include
    void pthread_cleanup_push ( void (*routine) (void *), void *arg);
    void pthread_cleanup_pop (int execute);
    Листинг 1.28. Описание функций pthread_cleanup_push() и pthread_cleanup_pop().
    Функция pthread_cleanup_push() помещает заданный аргументами routine и arg обработчик в стек обработчиков вызывающего потока. Функция pthread_cleanup_pop() извлекает верхний обработчик из этого стека и, если значение аргумента execute отлично от нуля, вызывает его (как (*routine) (arg)).
    Разумеется, все обработчики, начиная с верхнего, извлекаются из стека и вызываются при терминировании потока управления (вне зависимости от того, объясняется ли терминирование внутренними или внешними причинами). В частности, это происходит после того, как поток обратится к функции pthread_exit().
    Напомним, что обработчики завершения существуют и для процессов (они регистрируются с помощью функции atexit()), однако применительно к потокам управления идея стека обработчиков оформлена в более явном и систематическом виде.
    Пару функций pthread_cleanup_push() и pthread_cleanup_pop() можно представлять себе как открывающую и закрывающую скобки, оформленные в виде отдельных инструкций языка C и обрамляющие обслуживаемый обработчиком участок программы. Согласно стандарту POSIX-2001, этот участок должен представлять собой фрагмент одной лексической области видимости (блока), а pthread_cleanup_push() и pthread_cleanup_pop() могут быть реализованы как макросы (см. листинг 1.29).
    #define pthread_cleanup_push (rtn, arg) { \ struct _pthread_handler_rec \ __cleanup_handler, \ **__head; \ __cleanup_handler.rtn = rtn; \ __cleanup_handler.arg = arg; \ (void) pthread_getspecific \ (_pthread_handler_key, &__head); \ __cleanup_handler.next = *__head; \ *__head = &__cleanup_handler;
    #define pthread_cleanup_pop (ex) \ *__head = __cleanup_handler.next; \ if (ex) (*__cleanup_handler.rtn) \ (__cleanup_handler.arg); \ }
    Листинг 1.29. Возможная реализация функций pthread_cleanup_push() и pthread_cleanup_pop() как макросов.
    Обратим внимание на то, что в определении макроса pthread_cleanup_push() открывается внутренний блок, в котором декларируются два необходимых объекта – структура __cleanup_handler, описывающая обработчик, и указатель __head на вершину стека, представленного в виде односвязанного (линейного) списка. В определении pthread_cleanup_pop() этот блок закрывается. Так что даже из соображений синтаксической корректности вызовы pthread_cleanup_push() и pthread_cleanup_pop() должны быть парными и располагаться в одном блоке, но более существенной нам представляется корректность семантическая.
    Если поток управления по внутренним или внешним причинам терминируется при выполнении обслуживаемого обработчиком участка, тот должен обеспечить аккуратное завершение с восстановлением (если это необходимо) целостного, корректного состояния объектов, видимых в блоке, и освобождением ресурсов, лексически доступных из текущей области видимости. Иными словами, характер обработки завершения определяется программным контекстом, в котором прервано выполнение потока управления. Отсюда и привязка стека обработчиков к блочной структуре программы.
    После того, как выполнятся все обработчики завершения, в неспецифицированном порядке вызываются деструкторы индивидуальных данных (если у потока управления таковые имеются). То, что деструкторы вызываются после обработчиков завершения, делает доступными для последних индивидуальные данные потоков управления.
    Отметим, что если поток – не последний в процессе, то при его завершении, разумеется, не происходит автоматического освобождения видимых приложению ресурсов процесса (таких, например, как файловые дескрипторы) и не выполняются другие общепроцессные зачистки (такие, как выполнение функций, зарегистрированных посредством atexit()).
    Заказать терминирование извне потока управления с заданным идентификатором можно, воспользовавшись функцией pthread_cancel() (см. листинг 1.30).
    #include int pthread_cancel (pthread_t thread);
    Листинг 1.30. Описание функции pthread_cancel().
    Напомним, что на выполнение "заказа" влияют состояние восприимчивости и тип терминирования, установленные для потока, а также достижение точки терминирования. Эти атрибуты опрашиваются и изменяются с помощью функций pthread_setcancelstate(), pthread_setcanceltype() и pthread_testcancel() (см. листинг 1.31).
    #include
    int pthread_setcancelstate ( int state, int *oldstate);
    int pthread_setcanceltype ( int type, int *oldtype);
    void pthread_testcancel (void);
    Листинг 1.31. Описание функций pthread_setcancelstate(), pthread_setcanceltype(), pthread_testcancel().
    Функции pthread_setcancelstate() и pthread_setcanceltype() атомарным образом, в рамках неделимой транзакции устанавливают новые значения (state и type) для состояния восприимчивости и типа и помещают по заданным указателям (соответственно, oldstate и oldtype) старые значения. Допустимыми значениями для состояния восприимчивости к терминированию являются PTHREAD_CANCEL_ENABLE (терминирование разрешено – подразумеваемое значение для вновь созданных потоков управления) и PTHREAD_CANCEL_DISABLE, для типа – PTHREAD_CANCEL_DEFERRED (отложенное терминирование – подразумеваемое значение) и PTHREAD_CANCEL_ASYNCHRONOUS (немедленное, асинхронное терминирование).
    Функция pthread_testcancel() создает в вызывающем потоке точку терминирования, то есть проверяет наличие ждущего заказа на терминирование и, при наличии такового, инициирует его обработку (если она разрешена).
    Из общих соображений следует, что манипуляции с атрибутами терминирования необходимы для обеспечения атомарности транзакций и сохранения программных инвариантов. При входе в транзакцию терминирование запрещают, при выходе восстанавливают старое значение. Аналогично, если в пределах критического интервала нарушается некий программный инвариант, на этом интервале поток должен защититься от терминирования. Ресурсы, ассоциированные с потоком, обязаны оставаться в корректном состоянии, а осмысленные действия – или выполняться до конца, или не выполняться вообще.
    Возвращаясь к функции pthread_cancel(), отметим, что иногда обработку заказа на терминирование сравнивают с реакцией на доставку сигнала. На наш взгляд, к подобной аналогии нужно относиться осторожно, поскольку она довольно поверхностна и основана в первую очередь на том, что и сигналы, и заказы на терминирование являются средствами асинхронного программного воздействия на потоки управления, и других механизмов подобной направленности стандарт POSIX-2001 не предусматривает. В частности, доставка как сигналов, так и заказов на терминирование способна вывести поток управления из состояния ожидания, быть может, потенциально бесконечного. Еще одна параллель – запрещение терминирования при входе в обработчик завершения и блокирование сигнала при входе в функцию его обработки.
    Противоречат отмеченной аналогии следующие обстоятельства. Во-первых, обработка сигналов зачастую направлена на продолжение, а не завершение выполнения и, следовательно, носит принципиально иной, чем у обработчика завершения, характер. Во-вторых, для обработки терминирования предоставлено больше средств (не одна функция, как в случае сигналов, а целый стек обработчиков завершения плюс деструкторы индивидуальных данных) и они строже регламентированы (добавление и изъятие обработчиков стандартизованы как парные операции, выполняемые в пределах одной лексической области видимости, отложенное терминирование производится только в определенных точках программы, нелокальные переходы (siglongjmp()), являющиеся стандартным элементом обработки сигналов, применительно к обработчикам завершения допускаются с многочисленными оговорками и т.п.). Конечно, терминирование извне может быть реализовано на основе механизма сигналов, но есть и другие возможности.
    Разумеется, функция pthread_cancel() не ожидает выполнения заказа на терминирование, так что вызывающий и целевой потоки управления продолжают выполняться параллельно, пока последний не осуществит всех специфицированных действий по зачистке и не завершится, выдав ждущим этого события потокам значение PTHREAD_CANCELED.
    Помимо заказа на терминирование, потоку управления можно направить и "честный" сигнал, воспользовавшись средством "внутрипроцессного межпотокового" взаимодействия – функцией pthread_kill() (см. листинг 1.32).
    #include int pthread_kill ( pthread_t thread, int sig);
    Листинг 1.32. Описание функции pthread_kill().
    Как и в случае функции kill(), при нулевом значении аргумента sig проверяется корректность заданного идентификатора потока, но никакой сигнал не генерируется.
    Напомним (см. курс [1]), что сигналы генерируются для конкретного потока управления (так, в частности, поступает функция pthread_kill()) или для процесса в целом (как это делает функция kill()), но доставляются они всегда одному потоку, только во втором случае его выбор определяется реализацией из соображений простоты доставки: обычно берется активный поток, если он не блокирует данный сигнал. Естественно, если для сигнала определена функция обработки, она выполняется в контексте целевого потока управления. Другие возможные действия (терминирование, остановка) всегда применяются к процессу в целом.
    Для иллюстрации изложенного приведем небольшую программу (см. листинг 1.33), в которой создается поток управления с последующей доставкой ему сигнала SIGINT. Возможные результаты работы этой программы показаны на листинге 1.34.
    /* * * * * * * * * * * * * * * * * */ /* Программа демонстрирует генерацию */ /* и доставку сигналов */ /* потокам управления */ /* * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* * * * * * * * * * * * * * / /* Функция обработки сигнала */ /* * * * * * * * * * * * * * / static void signal_handler (int dummy) { printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n", pthread_self ()); }
    /* * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока управления, */ /* которому будет направлен сигнал */ /* * * * * * * * * * * * * * * * * * * */ static void *thread_start (void *dummy) { printf ("Идентификатор нового потока управления: %lx\n", pthread_self ()); while (1) { sleep (1); }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGINT, */ /* создает поток управления и посылает ему сигнал */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t thread_id; struct sigaction act;
    /* Установим реакцию на сигнал SIGINT */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);
    if ((errno = pthread_create (&thread_id, NULL, thread_start, NULL)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } printf ("Идентификатор созданного потока управления: %lx\n", thread_id);
    (void) pthread_kill (thread_id, SIGINT); printf ("После вызова pthread_kill()\n");
    sleep (1); printf ("Выспались...\n");
    return (0); }
    Листинг 1.33. Пример использования механизма сигналов в многопотоковой программе.
    Идентификатор созданного потока управления: 402 После вызова pthread_kill() Идентификатор потока, обрабатывающего сигнал: 402 Идентификатор нового потока управления: 402 Выспались...
    Листинг 1.34. Возможные результаты работы многопотоковой программы, использующей механизм сигналов.
    Обратим внимание на два любопытных (хотя и довольно очевидных) момента. Во-первых, начальный поток управления процесса продолжает выполнение после вызова pthread_kill() и успевает сообщить об этом. Затем он на секунду засыпает, активным становится вновь созданный поток и первым делом приступает к обработке доставленного ему сигнала. Уже после возврата из функции обработки начинается выполнение инструкций стартовой функции потока управления.
    Читателю предлагается самостоятельно проанализировать, как будет вести себя приведенная программа при подразумеваемом способе обработки сигнала SIGINT.
    Отмеченную выше устойчивость ожидания в функциях, обслуживающих потоки управления, к доставке и обработке сигналов проиллюстрируем программой, показанной на листинге 1.35.
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует взаимодействие сигналов */ /* и ожидания завершения потока управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include
    /* * * * * * * * * * * * * * */ /* Функция обработки сигнала */ /* * * * * * * * * * * * * * */ static void signal_handler (int dummy) { printf ("Идентификатор потока, обрабатывающего сигнал: %lx\n", pthread_self ()); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция создаваемого потока управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void *thread_start (void *thread_id) { printf ("Идентификатор нового потока управления: %lx\n", pthread_self ()); (void) pthread_kill ((pthread_t) thread_id, SIGINT);
    return ((void *) pthread_self ()); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGINT, */ /* создает поток управления и ожидает его завершения */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t thread_id; struct sigaction act; void *pv;
    /* Установим реакцию на сигнал SIGINT */ act.sa_handler = signal_handler; (void) sigemptyset (&act.sa_mask); act.sa_flags = 0; (void) sigaction (SIGINT, &act, (struct sigaction *) NULL);
    if ((errno = pthread_create (&thread_id, NULL, thread_start, (void *) pthread_self ())) != 0) { perror ("PTHREAD_CREATE"); return (errno); } printf ("Идентификаторы начального и созданного потоков " "управления: " "%lx %lx\n", pthread_self (), thread_id);
    /* Дождемся завершения созданного потока управления */ if ((errno = pthread_join (thread_id, &pv)) != 0) { perror ("PTHREAD_JOIN"); return (errno); } printf ("Статус завершения созданного потока " "управления: %p\n", pv);
    return (0); }
    Листинг 1.35. Пример программы, обрабатывающей сигнал во время ожидания завершения потока управления.
    Если посмотреть на возможные результаты работы этой программы (см. листинг 1.36), можно сделать вывод, что, несмотря на получение и обработку сигнала, функция pthread_join отрабатывает с нормальным (нулевым) результатом, получая статус завершения "ожидаемого" потока.
    Идентификаторы начального и созданного потоков управления: 400 402 Идентификатор нового потока управления: 402 Идентификатор потока, обрабатывающего сигнал: 400 Статус завершения созданного потока управления: 0x402
    Листинг 1.36. Возможные результаты работы программы, обрабатывающей сигнал во время ожидания завершения потока управления.
    И здесь читателю рекомендуется самостоятельно выяснить, как поведет себя приведенная программа при подразумеваемой реакции на сигнал SIGINT.
    Еще одна полезная операция, связанная с обработкой завершения потока управления, – его динамическое обособление, выполняемое функцией pthread_detach() (см. листинг 1.37).
    #include int pthread_detach (pthread_t thread);
    Листинг 1.37. Описание функции pthread_detach().
    При завершении обособленного потока операционная система может освободить использовавшуюся им память.
    Может показаться, что возможность динамического обособления является излишней (мол, достаточно соответствующего атрибута, принимаемого во внимание при создании потока функцией pthread_create()), однако это не так. Во-первых, начальный поток процесса создается нестандартным образом и обособить его статически невозможно. Во-вторых, если терминируется поток, ждущий в функции pthread_join(), обработчик его завершения должен обособить того, чье завершение является предметом ожидания, иначе с утилизацией памяти могут возникнуть проблемы.
    Если приложение заботится об аккуратном освобождении памяти, то для всех потоков управления, созданных с атрибутом PTHREAD_CREATE_JOINABLE, следует предусмотреть вызов либо pthread_join(), либо pthread_detach().
    В качестве примера многопотоковой программы приведем серверную часть рассматривавшегося в курсе [1] приложения, копирующего строки со стандартного ввода на стандартный вывод с "прокачиванием" их через потоковые сокеты (см. листинг 1.38).
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа процесса (будем называть его серверным), */ /* принимающего запросы на установления соединения и */ /* запускающего потоки управления для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая (и единственная) функция потоков управления, */ /* обслуживающих запросы на копирование строк, */ /* поступающих из сокета */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *srv_thread_start (void *ad) { FILE *fpad; /* Поток данных, соответствующий */ /* дескриптору ad */ char line [LINE_MAX]; /* Буфер для принимаемых строк */ /* Структура для записи адреса */ struct sockaddr_in sai; /* Длина адреса */ socklen_t sai_len = sizeof (struct sockaddr_in);
    /* Опросим адрес партнера по общению (передающего сокета) */ if (getpeername ((int) ad, (struct sockaddr *) &sai, &sai_len) < 0) { perror ("GETPEERNAME"); return (NULL); }
    /* По файловому дескриптору ad сформируем */ /* буферизованный поток данных */ if ((fpad = fdopen ((int) ad, "r")) == NULL) { perror ("FDOPEN"); return (NULL); }
    /* Цикл чтения строк из сокета */ /* и выдачи их на стандартный вывод */ while (fgets (line, sizeof (line), fpad) != NULL) { printf ("Вы ввели и отправили с адреса %s, " "порт %d :", inet_ntoa (sai.sin_addr), ntohs (sai.sin_port)); fputs (line, stdout); }
    /* Закрытие соединения */ shutdown ((int) ad, SHUT_RD); (void) fclose (fpad);
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* В функции main() принимаются запросы на установление */ /* соединения и запускаются потоки управления для их обслуживания */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int sd; /* Дескриптор слушающего сокета */ int ad; /* Дескриптор приемного сокета */ /* Буфер для принимаемых строк */ struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; /* Указатель – выходной аргумент getaddrinfo */ struct addrinfo *addr_res; int res; /* Результат getaddrinfo */ pthread_attr_t patob; /* Атрибутный объект для создания */ /* потоков управления */ pthread_t adt_id; /* Идентификатор обслуживающего потока управления */
    /* Создадим слушающий сокет */ if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { perror ("SOCKET"); return (1); }
    /* Привяжем этот сокет к адресу сервиса spooler */ /* на локальном хосте */ if ((res = getaddrinfo (NULL, "spooler", &hints, &addr_res)) != 0) { fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res)); return (2); } if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { perror ("BIND"); return (3); }
    /* Можно освободить память, которую запрашивала */ /* функция getaddrinfo() */ freeaddrinfo (addr_res);
    /* Пометим сокет как слушающий */ if (listen (sd, SOMAXCONN) < 0) { perror ("LISTEN"); return (4); }
    /* Инициализируем атрибутный объект потоков управления */ if ((errno = pthread_attr_init (&patob)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); } /* Потоки управления будем создавать обособленными */ (void) pthread_attr_setdetachstate (&patob, PTHREAD_CREATE_DETACHED);
    /* Цикл приема соединений и запуска */ /* обслуживающих потоков управления */ while (1) { /* Примем соединение. */ /* Адрес партнера по общению нас */ /* в данном случае не интересует */ if ((ad = accept (sd, NULL, NULL)) < 0) { perror ("ACCEPT"); return (6); }
    /* Запустим обслуживающий поток управления */ if ((errno = pthread_create (&adt_id, &patob, srv_thread_start,(void *) ad)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } }
    return (0); }
    Листинг 1.38. Пример многопотоковой программы, обслуживающей запросы на копирование строк, поступающих через сокеты.
    Многопотоковая реализация в данном случае уместнее многопроцессной: она и выглядит проще (поскольку потоки проще создавать и не обязательно в явном виде ждать их завершения), и ресурсов потребляет меньше.
    Можно надеяться, что и следующая программа (см. листинг 1.39), реализующая идею обработки данных с контролем времени, подтверждает, что применение потоков управления позволяет сделать исходный текст более простым и наглядным по сравнению с "беспотоковым" вариантом (см. курс [1]). Удалось избавиться от такого сугубо "неструктурного" средства, как нелокальные переходы, и от ассоциированных с ними тонкостей, чреватых ошибками.
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа вызывает функции обработки в рамках */ /* порождаемых потоков управления и контролирует время */ /* их выполнения с помощью интервального таймера */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* Период интервального таймера (в секундах) */ #define IT_PERIOD 1
    static pthread_t cthread_id; /* Идентификатор текущего */ /* потока управления, */ /* обрабатывающего данные */
    static int in_proc_data = 0; /* Признак активности */ /* потока обработки данных */
    static double s; /* Результат функций */ /* обработки данных */
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция обработки срабатывания таймера реального */ /* времени (сигнал SIGALRM) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_sigalrm (int dummy) { if (in_proc_data) { /* Не имеет значения, какой поток обрабатывает сигнал */ /* и заказывает терминирование (быть может, себя) */ (void) pthread_cancel (cthread_id); } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Обработчик завершения потока управления. */ /* Сбрасывает признак активности потока обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_cleanup_handler (void *arg) { in_proc_data = (int) arg; }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока управления, обрабатывающего */ /* данные. Аргумент – указатель на функцию обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_func (void *proc_data_func) { /* Поместим в стек обработчик завершения */ pthread_cleanup_push (proc_data_cleanup_handler, 0); in_proc_data = 1; /* Время пошло ... */
    /* На время выполнения функции обработки данных установим */ /* асинхронный тип терминирования, иначе оно не сработает */ (void) pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    /* Выполним функцию обработки данных */ ((void (*) (void)) (proc_data_func)) ();
    /* Установим отложенный тип терминирования, */ /* иначе изъятие обработчика из стека */ /* будет небезопасным действием */ (void) pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, NULL);
    /* Выполним обработчик завершения и удалим его из стека */ pthread_cleanup_pop (1); return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Первая функция обработки данных (вычисляет ln (2)) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_1 (void) { double d = 1; int i;
    s = 0; for (i = 1; i <= 100000000; i++) { s += d / i; d = -d; } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Вторая функция обработки данных (вычисляет sqrt (2))*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static void proc_data_2 (void) { s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s – 2) > 0.000000001); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() задает способ обработки сигнала SIGALRM, */ /* взводит периодический таймер реального времени */ /* и запускает в цикле потоки обработки данных */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив указателей на функции обработки данных */ void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL}; /* Указатель на указатель на */ /* текущую функцию обработки данных */ void (**tfptr) (void); void *pstat; /* Статус завершения потока */ /* обработки данных */ struct itimerval itvl; struct sigaction sact; int i;
    /* Установим реакцию на сигнал SIGALRM */ sact.sa_handler = proc_sigalrm; sact.sa_flags = 0; (void) sigemptyset (&sact.sa_mask); if (sigaction (SIGALRM, &sact, NULL) < 0) { perror ("SIGACTION"); return (1); }
    /* Сделаем таймер реального времени периодическим */ itvl.it_interval.tv_sec = IT_PERIOD; itvl.it_interval.tv_usec = 0;
    /* Цикл запуска потоков обработки данных. */ /* Выполним его дважды */ for (i = 0; i < 2; i++) { for (tfptr = fptrs; *tfptr != NULL; tfptr++) { /* Взведем интервальный таймер реального времени */ itvl.it_value.tv_sec = IT_PERIOD; itvl.it_value.tv_usec = 0; if (setitimer (ITIMER_REAL, &itvl, NULL) < 0) { perror ("SETITIMER"); return (2); }
    /* Создадим поток обработки данных, */ /* затем дождемся его завершения */ if ((errno = pthread_create (&cthread_id, NULL, start_func, (void *) *tfptr)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } if ((errno = pthread_join (cthread_id, &pstat)) != 0) { perror ("PTHREAD_JOIN"); return (errno); }
    if (pstat == PTHREAD_CANCELED) { printf ("Частичный результат функции " "обработки данных: %g\n", s); } else { printf ("Полный результат функции " "обработки данных: %g\n", s); } } }
    return 0; }
    Листинг 1.39. Пример многопотоковой программы, осуществляющей обработку данных с контролем времени.
    Возможные результаты работы приведенной программы показаны на листинге 1.40.
    Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421 Частичный результат функции обработки данных: 0.693147 Полный результат функции обработки данных: 1.41421
    Листинг 1.40. Возможные результаты работы многопотоковой программы, осуществляющей обработку данных с контролем времени.
    К сожалению, там, где есть недетерминированность и асинхронность, без тонкостей все равно не обойтись. Мы обратим внимание на три из них. Во-первых, возможно срабатывание таймера и доставка сигнала SIGALRM до того, как завершится (или даже начнется) первый вызов pthread_create() и будет инициализирована переменная cthread_id. Чтобы не допустить терминирования "неопределенного" потока управления в функции обработки сигнала, введен признак активности потока обработки данных. Если он установлен, переменная cthread_id заведомо инициализирована.
    Во-вторых, не имеет значения, в контексте какого из потоков выполняется функция обработки сигнала – вполне допустимо, чтобы поток заказал собственное терминирование.
    В-третьих, поскольку функции обработки данных не содержат точек терминирования, на время их выполнения необходимо установить асинхронный тип терминирования, иначе оно попросту не сработает. Однако использование этого типа крайне опасно, поэтому при первой возможности следует вернуться к более надежному отложенному типу. Перед манипуляциями со стеком обработчиков завершения установка данного типа или запрет терминирования являются обязательными.
    Как мы уже упоминали, потоки управления иногда называют легковесными процессами. Любопытно оценить, какова степень их легковесности, насколько накладные расходы на их обслуживание меньше, чем для "настоящих" процессов.
    На листингах 1.41 и 1.42 показана программа, которая в цикле порождает практически пустые процессы и дожидается их завершения; на листинге 1.43 приведены данные о времени ее работы, полученные с помощью команды time -p. Даже если сделать процессам послабление и убрать вызов execl(), времена получатся довольно большими (см. листинг 1.44) в сравнении с аналогичными данными для варианта с потоками управления (см. листинги 1.45 и 1.46).
    #include #include #include #include
    #define N 10000
    int main (void) { int i;
    for (i = 0; i < N; i++) { switch (fork ()) { case -1: perror ("FORK"); return (1); case 0: /* Порожденный процесс */ (void) execl ("./dummy", "dummy", (char *) 0); exit (0); default: /* Родительский процесс */ (void) wait (NULL); } }
    return 0; }
    Листинг 1.41. Пример программы, порождающей в цикле практически пустые процессы.
    int main (void) { return 0; }
    Листинг 1.42. Содержимое файла dummy.c
    real 34.97 user 12.36 sys 22.61
    Листинг 1.43. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы (вариант с вызовом execl()).
    real 11.49 user 2.38 sys 9.11
    Листинг 1.44. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы (вариант без вызова execl()).
    #include #include #include #include
    #define N 10000
    static void *thread_start (void *arg) { pthread_exit (arg); }
    int main (void) { pthread_t thread_id; int i;
    for (i = 0; i < N; i++) { if ((errno = pthread_create ( &thread_id, NULL, thread_start, NULL)) != 0) { perror ("PTHREAD_CREATE"); return (errno); } if ((errno = pthread_join ( thread_id, NULL)) != 0) { perror ("PTHREAD_JOIN"); return (errno); } }
    return (0); }
    Листинг 1.45. Пример программы, порождающей в цикле потоки управления.
    real 2.08 user 0.52 sys 1.56
    Листинг 1.46. Возможные результаты измерения времени работы программы, порождающей в цикле потоки управления.
    В первом приближении можно считать, что потоки управления на порядок дешевле процессов. На самом деле, в реальных ситуациях, когда процессы существенно превосходят по размеру приведенные выше "пустышки", различие будет еще больше.
    Можно сделать вывод, что потоки управления допускают довольно свободное использование, накладные расходы на их обслуживание невелики, особенно в сравнении с обслуживанием процессов. Поэтому, проектируя приложение с параллельно выполняемыми компонентами, следует в первую очередь проанализировать возможность многопотоковой реализации. Главное препятствие в осуществлении подобной возможности – разделение потоками одного адресного пространства. Только если это препятствие окажется непреодолимым, целесообразно воспользоваться механизмом процессов. Создание и терминирование потоков управления <


    Как и процесс, поток управления можно терминировать изнутри и извне. С одним, неявным, но наиболее естественным способом "самоликвидации" – выходом из стартовой функции потока – мы уже познакомились. Тот же эффект достигается вызовом функции pthread_exit() (см. листинг 1.26).

    #include void pthread_exit (void *value_ptr);

    Листинг 1.26. Описание функции pthread_exit(). (html, txt)

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

    Из общих соображений (например, если исходить из аналогии между процессами и потоками управления) очевидно, что должна существовать возможность дождаться завершения заданного потока управления. Эта возможность реализуется функцией pthread_join() (см. листинг 1.27), напоминающей waitpid().

    #include int pthread_join ( pthread_t thread, void **value_ptr_ptr);

    Листинг 1.27. Описание функции pthread_join(). (html, txt)

    Поток управления, вызвавший функцию pthread_join(), приостанавливает выполнение до завершения потока, идентификатор которого задан аргументом thread. При успешном возврате из pthread_join() результат, как и положено, равен нулю, а по указателю value_ptr_ptr (если он не пуст) помещается значение (указатель value_ptr), переданное в качестве аргумента функции pthread_exit(). Тем самым ждуший поток получает данные о статусе завершения ожидаемого.

    Отметим, что трактовка значения value_ptr возлагается на приложение. Например, оно может считать его целым числом, а не указателем; по этой причине операционная система при выполнении функции pthread_exit() не вправе выдавать ошибку типа "неверный адрес" каким бы ни был аргумент value_ptr. Если он все же является указателем, то ему нельзя присваивать адрес автоматической переменной, поскольку к моменту его использования ожидаемый поток уже завершится и состояние его автоматических переменных станет неопределенным.



    Второе общее соображение касается того обстоятельства, что вызов такой функции, как ожидание завершения (pthread_join()) способен приостановить выполнение вызывающего потока управления на неопределенное время, в течение которого ему может быть доставлен обрабатываемый сигнал. Как правило, в подобных ситуациях выполнение функций (таких, например, как read()) завершается с частично достигнутым результатом (например, с числом прочитанных байт, меньшим запрошенного) и кодом ошибки EINTR, нуждающимся в нестандартной обработке, далеко не всегда реализуемой разработчиками приложений. Из-за этого в программах появляются дефекты, которые трудно воспроизвести и, соответственно, исправить.

    Согласно стандарту POSIX-2001, функции, обслуживающие потоки управления, свободны от этого недостатка. Они никогда не завершаются с частичным результатом и не выдают код ошибки EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.

    Третье общее соображение состоит в том, что такое критически важное событие, как завершение потока управления, не может оставаться без функций-обработчиков. Стандартом POSIX-2001 предусмотрено существование не одного, а целого стека подобных обработчиков, ассоциированного с потоком управления. Операции над этим стеком возложены на функции pthread_cleanup_push() и pthread_cleanup_pop() (см. листинг 1.28).

    #include

    void pthread_cleanup_push ( void (*routine) (void *), void *arg);

    void pthread_cleanup_pop (int execute);

    Листинг 1.28. Описание функций pthread_cleanup_push() и pthread_cleanup_pop(). (html, txt)

    Функция pthread_cleanup_push() помещает заданный аргументами routine и arg обработчик в стек обработчиков вызывающего потока. Функция pthread_cleanup_pop() извлекает верхний обработчик из этого стека и, если значение аргумента execute отлично от нуля, вызывает его (как (*routine) (arg)).

    Разумеется, все обработчики, начиная с верхнего, извлекаются из стека и вызываются при терминировании потока управления (вне зависимости от того, объясняется ли терминирование внутренними или внешними причинами).


    В частности, это происходит после того, как поток обратится к функции pthread_exit().

    Напомним, что обработчики завершения существуют и для процессов (они регистрируются с помощью функции atexit()), однако применительно к потокам управления идея стека обработчиков оформлена в более явном и систематическом виде.

    Пару функций pthread_cleanup_push() и pthread_cleanup_pop() можно представлять себе как открывающую и закрывающую скобки, оформленные в виде отдельных инструкций языка C и обрамляющие обслуживаемый обработчиком участок программы. Согласно стандарту POSIX-2001, этот участок должен представлять собой фрагмент одной лексической области видимости (блока), а pthread_cleanup_push() и pthread_cleanup_pop() могут быть реализованы как макросы (см. листинг 1.29).

    #define pthread_cleanup_push (rtn, arg) { \ struct _pthread_handler_rec \ __cleanup_handler, \ **__head; \ __cleanup_handler.rtn = rtn; \ __cleanup_handler.arg = arg; \ (void) pthread_getspecific \ (_pthread_handler_key, &__head); \ __cleanup_handler.next = *__head; \ *__head = &__cleanup_handler;

    #define pthread_cleanup_pop (ex) \ *__head = __cleanup_handler.next; \ if (ex) (*__cleanup_handler.rtn) \ (__cleanup_handler.arg); \ }

    Листинг 1.29. Возможная реализация функций pthread_cleanup_push() и pthread_cleanup_pop() как макросов. (html, txt)

    Обратим внимание на то, что в определении макроса pthread_cleanup_push() открывается внутренний блок, в котором декларируются два необходимых объекта – структура __cleanup_handler, описывающая обработчик, и указатель __head на вершину стека, представленного в виде односвязанного (линейного) списка. В определении pthread_cleanup_pop() этот блок закрывается. Так что даже из соображений синтаксической корректности вызовы pthread_cleanup_push() и pthread_cleanup_pop() должны быть парными и располагаться в одном блоке, но более существенной нам представляется корректность семантическая.



    Создание и терминирование потоков управления
    Создание и терминирование потоков управления
    © 2003-2007 INTUIT.ru. Все права защищены.

    Барьеры


    Барьеры – весьма своеобразное средство синхронизации. Идея его в том, чтобы в определенной точке ожидания собралось заданное число потоков управления. Только после этого они смогут продолжить выполнение. (Поговорка "семеро одного не ждут" к барьерам не применима.)
    Барьеры полезны для организации коллективных распределенных вычислений в многопроцессорной конфигурации, когда каждый участник (поток управления) выполняет часть работы, а в точке сбора частичные результаты объединяются в общий итог.
    Функции, ассоциированные с барьерами, подразделяются на следующие группы.
  • инициализация и разрушение барьеров: pthread_barrier_init(), pthread_barrier_destroy() (см. листинг 2.36);
    #include
    int pthread_barrier_init ( pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
    int pthread_barrier_destroy ( pthread_barrier_t *barrier);
    Листинг 2.36. Описание функций инициализации и разрушения барьеров. (html, txt)
  • синхронизация на барьере: pthread_barrier_wait() (см. листинг 2.37);
    #include int pthread_barrier_wait ( pthread_barrier_t *barrier);
    Листинг 2.37. Описание функции синхронизации на барьере. (html, txt)
  • инициализация и разрушение атрибутных объектов барьеров: pthread_barrierattr_init(), pthread_barrierattr_destroy() (см. листинг 2.38);
    #include
    int pthread_barrierattr_init ( pthread_barrierattr_t *attr);
    int pthread_barrierattr_destroy ( pthread_barrierattr_t *attr);
    Листинг 2.38. Описание функций инициализации и разрушения атрибутных объектов барьеров. (html, txt)
  • опрос и установка атрибутов барьеров в атрибутных объектах: pthread_barrierattr_getpshared(), pthread_barrierattr_setpshared() (см. листинг 2.39).
    #include
    int pthread_barrierattr_getpshared (const pthread_barrierattr_t *restrict attr, int *restrict pshared);
    int pthread_barrierattr_setpshared (pthread_barrierattr_t *attr, int pshared);
    Листинг 2.39. Описание функций опроса и установки атрибутов барьеров в атрибутных объектах. (html, txt)


  • Обратим внимание на аргумент count в функции инициализации барьера pthread_barrier_init(). Он задает количество синхронизируемых потоков управления. Столько потоков должны вызвать функцию pthread_barrier_wait(), прежде чем каждый из них сможет успешно завершить вызов и продолжить выполнение. (Разумеется, значение count должно быть положительным.)

    Когда к функции pthread_barrier_wait() обратилось требуемое число потоков управления, одному из них (стандарт POSIX-2001 не специфицирует, какому именно) в качестве результата возвращается именованная константа PTHREAD_BARRIER_SERIAL_THREAD, а всем другим выдаются нули. После этого барьер возвращается в начальное (инициализированное) состояние, а выделенный поток может выполнить соответствующие объединительные действия.

    Описанная схема работы проиллюстрирована листингом 2.40.

    if ((status = pthread_barrier_wait( &barrier)) == PTHREAD_BARRIER_SERIAL_THREAD) { /* Выделенные (обычно – объединительные) */ /* действия. */ /* Выполняются каким-то одним потоком */ /* управления */

    } else { /* Эта часть выполняется всеми */ /* прочими потоками */ /* управления */ if (status != 0) { /* Обработка ошибочной ситуации */ } else { /* Нормальное "невыделенное" */ /* завершение ожидания */ /* на барьере */ } }

    /* Повторная синхронизация – */ /* ожидание завершения выделенных действий */ status = pthread_barrier_wait (&barrier); /* Продолжение параллельной работы */ . . .

    Листинг 2.40. Типичная схема применения функции pthread_barrier_wait(). (html, txt)

    Отметим, что для барьеров отсутствует вариант синхронизации с контролем времени ожидания. Это вполне понятно, поскольку в случае срабатывания контроля барьер окажется в неработоспособном состоянии (требуемое число потоков, скорее всего, уже не соберется). По той же причине функция pthread_barrier_wait() не является точкой терминирования – "оставшиеся в живых" не переживут потери товарища...

    Аналогично, не являются точками терминирования функции pthread_mutex_lock() и pthread_spin_lock().Если бы они были таковыми, то точками терминирования стали бы все функции, в том числе библиотечные, которые их вызывают – malloc(), free() и т.п. Обеспечить в обработчиках завершения корректное состояние объектов, обслуживаемых подобными функциями, довольно сложно; это деятельность, чреватая ошибками, которые трудно не только найти и исправить, но даже воспроизвести. С другой стороны, мьютексы и спин-блокировки предназначены для захвата на короткое время, без длительного ожидания, так что нечувствительность к терминированию в данном случае не составляет большой проблемы.

    Работу с барьерами проиллюстрируем коллективными вычислениями, производимыми двумя потоками (см. листинг 2.41).

    Листинг 2.41. Пример программы, использующей барьеры. (html, txt)

    В данном случае второго ожидания на барьере не понадобилось – вместо этого потоки управления просто завершаются.


    Блокировки чтение-запись


    Блокировки чтение-запись можно назвать интеллектуальным средством синхронизации, поскольку они делают различие между читателями и писателями. В большинстве случаев разделяемые данные чаще читают, чем изменяют (действительно, зачем писать то, что никто не будет читать?), и это делает блокировки чтение-запись весьма употребительными.
    Типичный пример использования блокировок чтение-запись – синхронизация доступа к буферизованным в памяти фрагментам файловой системы, особенно к каталогам (чем ближе к корню, тем чаще их читают и реже изменяют). Важно отметить в этой связи, что, в отличие от блокировок, реализуемых функцией fcntl() (см. курс [1]), блокировки чтение-запись могут применяться и там, где файловая система отсутствует (например, в минимальных конфигурациях, функционирующих под управлением соответствующей подпрофилю стандарта POSIX операционной системы реального времени).
    Применительно к блокировкам чтение-запись предоставляются следующие группы функций.
  • инициализация и разрушение блокировок: pthread_rwlock_init(), pthread_rwlock_destroy() (см. листинг 2.20);
    #include
    int pthread_rwlock_init ( pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy ( pthread_rwlock_t *rwlock);
    Листинг 2.20. Описание функций инициализации и разрушения блокировок чтение-запись. (html, txt)
  • установка блокировки на чтение: pthread_rwlock_rdlock(), pthread_rwlock_tryrdlock(), pthread_rwlock_timedrdlock() (см. листинги 2.21 и 2.22);
    #include
    int pthread_rwlock_rdlock ( pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock ( pthread_rwlock_t *rwlock);
    Листинг 2.21. Описание функций установки блокировки на чтение. (html, txt)
    #include #include int pthread_rwlock_timedrdlock ( pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abstime);
    Листинг 2.22. Описание функции установки блокировки на чтение с ограниченным ожиданием. (html, txt)
  • установка блокировки на запись: pthread_rwlock_wrlock(), pthread_rwlock_trywrlock(), pthread_rwlock_timedwrlock() (см.
    листинги 2.23 и 2.24);

    #include

    int pthread_rwlock_wrlock ( pthread_rwlock_t *rwlock);

    int pthread_rwlock_trywrlock ( pthread_rwlock_t *rwlock);

    Листинг 2.23. Описание функций установки блокировки на запись. (html, txt)

    #include #include int pthread_rwlock_timedwrlock ( pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abstime);

    Листинг 2.24. Описание функции установки блокировки на запись с ограниченным ожиданием. (html, txt)

  • снятие блокировки чтение-запись: pthread_rwlock_unlock() (см. листинг 2.25);

    #include int pthread_rwlock_unlock ( pthread_rwlock_t *rwlock);

    Листинг 2.25. Описание функции снятия блокировки чтение-запись. (html, txt)

  • инициализация и разрушение атрибутных объектов блокировок: pthread_rwlockattr_init(), pthread_rwlockattr_destroy() (см. листинг 2.26);

    #include

    int pthread_rwlockattr_init ( pthread_rwlockattr_t *attr);

    int pthread_rwlockattr_destroy ( pthread_rwlockattr_t *attr);

    Листинг 2.26. Описание функций инициализации и разрушения атрибутных объектов блокировок. (html, txt)

  • опрос и установка атрибутов блокировок в атрибутных объектах: pthread_rwlockattr_getpshared(), pthread_rwlockattr_setpshared() (см. листинг 2.27).

    #include

    int pthread_rwlockattr_getpshared ( const pthread_rwlockattr_t *restrict attr, int *restrict pshared);

    int pthread_rwlockattr_setpshared ( pthread_rwlockattr_t *attr, int pshared);

    Листинг 2.27. Описание функций опроса и установки атрибутов блокировок в атрибутных объектах. (html, txt)



  • Тонким вопросом, связанным с блокировками чтение-запись, является взаимодействие читателей и писателей. Согласно стандарту POSIX-2001, если речь не идет о приложениях реального времени, то от реализации зависит, будет ли приостановлен поток управления при попытке установить блокировку на чтение при наличии ожидающих освобождения той же блокировки писателей. (Как правило, чтобы воспрепятствовать зависанию писателей, читателей в подобной ситуации "тормозят", иначе они так и будут подхватывать блокировку друг у друга.


    Для политик планирования реального времени преимущество получает более приоритетный поток.)

    Естественно, если блокировку установил писатель, читателю придется подождать. Если этим писателем оказался тот же поток, ждать придется долго...

    Параллельно одним или несколькими потоками управления может быть установлено несколько блокировок на чтение. Сколько раз устанавливали такую блокировку, столько же раз ее необходимо снять. (Установка блокировки писателем возможна только после последнего снятия.) Максимально допустимое число одновременно установленных блокировок на чтение зависит от реализации.

    Когда снимают блокировку, на которую претендуют и читатели, и писатели, как правило, преимущество получают писатели, но, строго говоря, решение зависит от реализации.

    Разумеется, снимать блокировку может только тот поток управления, который ее устанавливал; в противном случае поведение неопределено.

    В качестве иллюстрации применения блокировок чтение-запись приведем набор функций для работы со списками в многопотоковой среде. На листинге 2.28 показан заголовочный файл, содержащий необходимые описания, на листинге 2.29 – C-файл с реализацией функций. Листинг 2.30 содержит текст тестовой программы, листинг 2.31 – результаты ее работы. Обратим внимание на использование блокировок без приостановки выполнения и на проверку статуса завершения попыток их установить.

    Листинг 2.28. Заголовочный файл g_list.h для функций работы со списками в многопотоковой среде. (html, txt)

    Листинг 2.29. Исходный текст функций для работы со списками в многопотоковой среде. (html, txt)

    Листинг 2.30. Пример программы, использующей функции для работы со списками в многопотоковой среде. (html, txt)

    Попыток выполнить операцию со списком: 1503 Число попыток, неудачных из-за занятости списка: 0

    Листинг 2.31. Возможные результаты работы программы, использующей функции для работы со списками в многопотоковой среде. (html, txt)

    Можно видеть, что в данном случае оптимистичное применение блокировок чтение-запись без приостановки выполнения в случае невозможности немедленной установки себя полностью оправдало.



    /* Инициализировать список */ extern void g_list_init (g_listP);

    /* Завершить работу со списком */ extern void g_list_destroy (g_listP);

    /* Вставить новый элемент */ /* в конец списка */ extern void g_list_ins_last ( g_listP, void *); /* Вставить новый элемент в */ /* список перед первым, */ /* удовлетворяющим заданному */ /* свойству (или в конец) */ extern void g_list_ins_fun ( g_listP, void *, afun);

    /* Удалить элемент из списка */ extern void g_list_del ( g_listP, void *);

    /* Применить процедуру ко всем */ /* элементам списка */ extern void g_list_forall_x ( g_listP, aproc, void *);

    /* Выбрать подходящий элемент списка */ extern void *g_list_suchas_x ( g_listP, afun, void *);

    /* Выдать элемент по номеру */ extern void *g_list_get_bynum ( g_listP, int);

    #endif

    Листинг 2.28. Заголовочный файл g_list. h для функций работы со списками в многопотоковой среде.

    /* * * * * * * * * * * * * * * * * * * * */ /* Реализация функций для многопотоковой */ /* работы со списками. */ /* Применяются блокировки чтение-запись */ /* без приостановки выполнения */ /* * * * * * * * * * * * * * * * * * * * */

    #define _XOPEN_SOURCE 600

    #include #include #include #include "g_list.h"

    /* * * * * * * * * * * * * */ /* Инициализировать список */ /* * * * * * * * * * * * * */ void g_list_init (g_listP plist) { plist->head = NULL; errno = pthread_rwlock_init ( &plist->lk, NULL); } /* * * * * * * * * * * * * * * */ /* Завершить работу со списком */ /* * * * * * * * * * * * * * * */ void g_list_destroy (g_listP plist) { errno = pthread_rwlock_destroy ( &plist->lk); }

    /* * * * * * * * * * * * * * * * * * * * */ /* Вставить новый элемент в конец списка */ /* * * * * * * * * * * * * * * * * * * * */ void g_list_ins_last (g_listP plist, void *pval) { register g_linkP pt; register g_linkP *p;

    if ((errno = pthread_rwlock_trywrlock ( &plist->lk)) != 0) { return; }

    for (p = &plist->head; *p != NULL; p = &(*p)->pnext) ; if ((pt = (g_linkP) malloc (sizeof (*pt))) != NULL) { pt->pnext = NULL; pt->pvalue = pval; *p = pt; }



    (void) pthread_rwlock_unlock ( &plist->lk); }

    /* * * * * * * * * * * * * * * * * */ /* Вставить новый элемент в список */ /* перед первым, */ /* удовлетворяющим заданному свойству */ /* (или в конец) */ /* * * * * * * * * * * * * * * * */ void g_list_ins_fun (g_listP plist, void *pval, afun fun) { register g_linkP pt; register g_linkP *p;

    if ((errno = pthread_rwlock_trywrlock ( &plist->lk)) != 0) { return; }

    for (p = &plist->head; (*p != NULL) && (fun (pval, (*p)->pnext->pvalue) == 0); p = &(*p)->pnext) ;

    if ((pt = (g_linkP) malloc ( sizeof (*pt))) != NULL) { pt->pnext = NULL; pt->pvalue = pval; *p = pt; }

    (void) pthread_rwlock_unlock ( &plist->lk); }

    /* * * * * * * * * * * * * * */ /* Удалить элемент из списка */ /* * * * * * * * * * * * * * */ void g_list_del (g_listP plist, void *pval) { register g_linkP pt; register g_linkP *p;

    if ((errno = pthread_rwlock_trywrlock (&plist->lk)) != 0) { return; }

    for (p = &plist->head; *p != NULL; p = &(*p)->pnext) { if ((*p)->pvalue == pval) { pt = *p; *p = pt->pnext; free (pt); errno = pthread_rwlock_unlock( &plist->lk); return; } }

    /* Пытаемся удалить */ /* несуществующий элемент */ (void) pthread_rwlock_unlock ( &plist->lk); errno = EINVAL; }

    /* * * * * * * * * * * * * */ /* Применить процедуру ко */ /* всем элементам списка */ /* * * * * * * * * * * * */ void g_list_forall_x (g_listP plist, aproc proc, void *extobj) { register g_linkP pt;

    /* Устанавливаем блокировку на запись, */ /* поскольку, возможно, процедура */ /* модифицирует */ /* объекты, ссылки на которые */ /* хранятся в списке */ if ((errno = pthread_rwlock_trywrlock( &plist->lk)) != 0) { return; }

    for (pt = plist->head; pt != NULL; pt = pt->pnext) { proc (extobj, pt->pvalue); }

    (void) pthread_rwlock_unlock( &plist->lk); }

    /* * * * * * * * * * * * * * * * * * */ /* Выбрать подходящий элемент списка */ /* * * * * * * * * * * * * * * * * * */ void *g_list_suchas_x (g_listP plist, afun fun, void *extobj) { register g_linkP pt;



    /* Устанавливаем блокировку на */ /* чтение, так как */ /* считаем, что функция не */ /* модифицирует объекты, */ /* ссылки на которые хранятся */ /* в списке */ if ((errno = pthread_rwlock_tryrdlock( &plist->lk)) != 0) { return NULL; }

    for (pt = plist->head; pt != NULL; pt = pt->pnext) { if (fun (extobj, pt->pvalue)) { (void) pthread_rwlock_unlock( &plist->lk); return (pt->pvalue); } }

    (void) pthread_rwlock_unlock( &plist->lk); return NULL; }

    /* * * * * * * * * * * * * * */ /* Выдать элемент по номеру */ /* * * * * * * * * * * * * * */ void *g_list_get_bynum (g_listP plist, int n) { register g_linkP pt;

    if ((errno = pthread_rwlock_tryrdlock( &plist->lk)) != 0) { return NULL; }

    for (pt = plist->head; n--, pt != NULL; pt = pt->pnext) { if (n == 0) { (void) pthread_rwlock_unlock( &plist->lk); return (pt->pvalue); } }

    /* Пытаемся извлечь несуществующий */ /* компонент */ (void) pthread_rwlock_unlock( &plist->lk); errno = EINVAL;

    return NULL; }

    Листинг 2.29. Исходный текст функций для работы со списками в многопотоковой среде.

    /* * * * * * * * * * * * * * * * * * * */ /* Тест функций многопотоковой работы */ /* со списками */ /* * * * * * * * * * * * * * * * * * * */

    #define _XOPEN_SOURCE 600

    #include #include #include #include #include #include "g_list.h"

    /* Структура для засыпания на */ /* минимальное время */ static struct timespec nslp = {0, 1};

    /* Структуры для возврата результатов */ /* потоками: */ /* Сколько операций пытались сделать, */ /* сколько из них оказались неудачными */ /* из-за занятости списка */ static struct pt_res { int nops; int nbusy; } ptres [3] = {{0, 0}, {0, 0}, {0, 0}};

    /* * * * * * * * * * * * * * * * * * * * */ /* Этот поток добавляет элементы к списку */ /* * * * * * * * * * * * * * * * * * * * */ static void *start_func_1 (void *plist) { int *p1;

    while (1) { if ((p1 = (int *) malloc ( sizeof (int))) != NULL) { *p1 = rand (); errno = 0; g_list_ins_last (plist, p1); ptres [0].nops++; if (errno == EBUSY) { ptres [0].nbusy++; } } (void) nanosleep (&nslp, NULL); }



    return (NULL); }

    /* * * * * * * * * * * * * * * * * * * * * */ /* Процедура для подсчета суммы элементов */ /* списка */ /* * * * * * * * * * * * * * * * * * * * * */ static void proc_sum (void *sum, void *pval) { *((int *) sum) += *((int *) pval); } /* * * * * * * * * * * * * * * * * * * * * */ /* Этот поток подсчитывает сумму элементов */ /* списка */ /* * * * * * * * * * * * * * * * * * * * * */ static void *start_func_2 (void *plist) { int sum;

    while (1) { sum = 0; errno = 0; g_list_forall_x (plist, &proc_sum, &sum); ptres [1].nops++; if (errno == EBUSY) { ptres [1].nbusy++; } (void) nanosleep (&nslp, NULL); }

    return (NULL); }

    /* * * * * * * * * * * * * * * * * */ /* Этот поток удаляет из списка */ /* элементы со случайными номерами */ /* * * * * * * * * * * * * * * * * */ static void *start_func_3 (void *plist) { int *p1;

    while (1) { errno = 0; p1 = (int *) g_list_get_bynum( plist, rand ()); ptres [2].nops++; if (errno == EBUSY) { ptres[2].nbusy++; }

    if (p1 != NULL) { errno = 0; g_list_del (plist, p1); ptres [2].nops++; if (errno == EBUSY) { ptres [2].nbusy++; } free (p1); } (void) nanosleep (&nslp, NULL); }

    return (NULL); }

    /* * * * * * * * * * * * * * * * * * * * * * */ /* Начальный поток всех запустит, поспит, */ /* потом всех терминирует и выдаст статистику */ /* * * * * * * * * * * * * * * * * * * * * * */ int main (void) { g_listP plist; pthread_t pt1, pt2, pt3;

    g_list_init (plist);

    pthread_create (&pt1, NULL, start_func_1, plist); pthread_create (&pt2, NULL, start_func_2, plist); pthread_create (&pt3, NULL, start_func_3, plist);

    sleep (10);

    pthread_cancel (pt1); pthread_cancel (pt2); pthread_cancel (pt3);

    pthread_join (pt1, NULL); pthread_join (pt2, NULL); pthread_join (pt3, NULL);

    g_list_destroy (plist);

    printf ("Попыток выполнить " "операцию со списком: %d\n", ptres [0].nops + ptres [1].nops + ptres [2].nops); printf ("Число попыток, неудачных" " из-за занятости " "списка: %d\n", ptres [0].nbusy + ptres [1].nbusy + ptres [2].nbusy);

    return (0); }

    Листинг 2.30. Пример программы, использующей функции для работы со списками в многопотоковой среде.

    Попыток выполнить операцию со списком: 1503 Число попыток, неудачных из-за занятости списка: 0

    Листинг 2.31. Возможные результаты работы программы, использующей функции для работы со списками в многопотоковой среде.

    Можно видеть, что в данном случае оптимистичное применение блокировок чтение-запись без приостановки выполнения в случае невозможности немедленной установки себя полностью оправдало.


    Мьютексы


    Функции, обслуживающие мьютексы, можно разбить на следующие группы:
  • инициализация и разрушение мьютексов: pthread_mutex_init(), pthread_mutex_destroy() (см. листинг 2.1);
    #include
    int pthread_mutex_init ( pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destroy ( pthread_mutex_t *mutex);
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    Листинг 2.1. Описание функций инициализации и разрушения мьютексов. (html, txt)
  • захват и освобождение мьютексов: pthread_mutex_lock(), pthread_mutex_trylock(), pthread_mutex_timedlock(), pthread_mutex_unlock() (см. листинги 2.2 и 2.3);
    #include
    int pthread_mutex_lock ( pthread_mutex_t *mutex);
    int pthread_mutex_trylock ( pthread_mutex_t *mutex);
    int pthread_mutex_unlock ( pthread_mutex_t *mutex);
    Листинг 2.2. Описание функций захвата и освобождения мьютексов. (html, txt)
    #include #include int pthread_mutex_timedlock ( pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
    Листинг 2.3. Описание функции захвата мьютексов с ограниченным ожиданием. (html, txt)
  • опрос и установка атрибутов мьютекса: pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling() (см. листинг 2.4);
    #include
    int pthread_mutex_getprioceiling ( const pthread_mutex_t *restrict mutex, int *restrict prioceiling);
    int pthread_mutex_setprioceiling ( pthread_mutex_t *restrict mutex, int prioceiling, int *restrict old_ceiling);
    Листинг 2.4. Описание функций опроса и установки атрибутов мьютекса. (html, txt)
  • инициализация и разрушение атрибутных объектов мьютексов: pthread_mutexattr_init(), pthread_mutexattr_destroy() (см. листинг 2.5);
    #include
    int pthread_mutexattr_init ( pthread_mutexattr_t *attr);
    int pthread_mutexattr_destroy ( pthread_mutexattr_t *attr);
    Листинг 2.5. Описание функций инициализации и разрушения атрибутных объектов мьютексов. (html, txt)
  • опрос и установка атрибутов мьютекса в атрибутных объектах: pthread_mutexattr_gettype(), pthread_mutexattr_settype(), pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared(), pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol(), pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling() (см.
    листинг 2.6).

    #include

    int pthread_mutexattr_gettype ( const pthread_mutexattr_t *restrict attr, int *restrict type);

    int pthread_mutexattr_settype ( pthread_mutexattr_t *attr, int type);

    int pthread_mutexattr_getpshared ( const pthread_mutexattr_t *restrict attr, int *restrict pshared);

    int pthread_mutexattr_setpshared ( pthread_mutexattr_t *attr, int pshared);

    int pthread_mutexattr_getprotocol ( const pthread_mutexattr_t *restrict attr, int *restrict protocol);

    int pthread_mutexattr_setprotocol ( *attr, int protocol);

    int pthread_mutexattr_getprioceiling ( const pthread_mutexattr_t *restrict attr, int *restrict prioceiling); int pthread_mutexattr_setprioceiling ( pthread_mutexattr_t *attr, int prioceiling);

    Листинг 2.6. Описание функций опроса и установки атрибутов мьютекса в атрибутных объектах. (html, txt)



  • Мы не будем детально описывать каждую из перечисленных выше функций (хотя бы потому, что дисциплина работы с атрибутами и атрибутными объектами та же, что и для потоков управления), но коснемся лишь отдельных, специфических аспектов.

    Сразу после инициализации функцией pthread_mutex_init() мьютекс, разумеется, оказывается свободным. Разрушить функцией pthread_mutex_destroy() можно только инициализированный, свободный мьютекс.

    Для инициализации статически описанных мьютексов с подразумеваемыми значениями атрибутов целесообразно воспользоваться макросом PTHREAD_MUTEX_INITIALIZER. Эффект будет тем же, что и после вызова pthread_mutex_init() с пустым указателем в качестве значения аргумента attr, только без накладных расходов на проверку корректности атрибутного объекта.

    В стандарте POSIX-2001 тип pthread_mutex_t трактуется как абстрактный, со скрытой структурой и даже без методов для присваивания и сравнения на равенство, а попытки обойти их отсутствие за счет применения операций с областями памяти, естественно, обречены на неудачу поскольку, согласно стандарту, для синхронизации должны использоваться сами объекты-мьютексы, а не их копии.Это "развязывает руки" операционной системе в использовании доступных аппаратных возможностей при реализации мьютексов, делая ее максимально эффективной.

    У инициализированного мьютекса имеется четыре атрибута:

  • тип (обслуживается функциями pthread_mutexattr_gettype() и pthread_mutexattr_settype());
  • верхняя грань приоритетов выполнения (функции pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling(), pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling());
  • протокол (pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol());
  • признак использования несколькими процессами (pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared()).


  • Мы уже отмечали важность эффективной реализации для средств синхронизации потоков управления. Вероятно, по этой причине в число атрибутов не включен идентификатор потока-владельца, поскольку его нужно устанавливать и хранить.



    У инициализированного мьютекса имеется четыре атрибута:

  • тип (обслуживается функциями pthread_mutexattr_gettype() и pthread_mutexattr_settype());
  • верхняя грань приоритетов выполнения (функции pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling(), pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling());
  • протокол (pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol());
  • признак использования несколькими процессами (pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared()).


  • Мы уже отмечали важность эффективной реализации для средств синхронизации потоков управления. Вероятно, по этой причине в число атрибутов не включен идентификатор потока-владельца, поскольку его нужно устанавливать и хранить.

    В стандарте POSIX-2001 определены четыре типа мьютексов.

    PTHREAD_MUTEX_NORMAL

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

    PTHREAD_MUTEX_ERRORCHECK

    Данный тип обеспечивает выявление ошибочных ситуаций. Упомянутые выше некорректные действия с мьютексами приведут к выдаче кода ошибки. Поскольку, как считается, мьютекс обычно доступен для захвата, а значения атрибутов проверяются лишь тогда, когда поток приходится блокировать, использование атрибутов по сути не влияет на эффективность. В частности, контроль ошибок несколько замедляет нормальное функционирование только при освобождении мьютекса.

    PTHREAD_MUTEX_RECURSIVE

    Мьютекс со счетчиком и выявлением ошибочных ситуаций. Поток управления может многократно захватить мьютекс, но затем должен столько же раз освободить его.

    PTHREAD_MUTEX_DEFAULT

    Подразумеваемое значение атрибута "тип". Некорректные действия приводят к неопределенному эффекту. Реализация может отождествить этот тип с одним из вышеописанных.

    Атрибут "верхняя грань приоритетов выполнения" определяет минимальный приоритет, с которым будет выполняться критический интервал, охраняемый мьютексом.


    Чтобы избежать инверсии приоритетов, значение этого атрибута следует сделать не меньшим, чем максимальный из приоритетов потоков, могущих захватить данный мьютекс (отсюда и название атрибута). Диапазон допустимых значений атрибута тот же, что и для приоритетов политики планирования SCHED_FIFO.

    Атрибут "протокол" влияет на планирование потока управления во время владения мьютексом. Согласно стандарту, возможных протоколов три.

    PTHREAD_PRIO_NONE

    Владение мьютексом не влияет на приоритет и планирование потока управления.

    PTHREAD_PRIO_INHERIT

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

    PTHREAD_PRIO_PROTECT

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

    В стандарте оговаривается, что при изменении приоритета, вызванном операциями с мьютексами, подчиняющимися протоколам PTHREAD_PRIO_INHERIT или PTHREAD_PRIO_PROTECT, поток управления не должен перемещаться в хвост очереди планирования.

    Если поток управления владеет несколькими мьютексами с разными протоколами, ему назначается приоритет, максимальный из предписанных каждым из протоколов.

    Признак использования несколькими процессами мьютекса, в соответствии со стандартом POSIX-2001, может принимать два значения.

    PTHREAD_PROCESS_PRIVATE

    Мьютекс доступен только для потоков управления, выполняющихся в рамках того же процесса, что и поток, инициализировавший мьютекс. Это значение атрибута является подразумеваемым.

    PTHREAD_PROCESS_SHARED

    С мьютексом могут работать все потоки управления, имеющие доступ к памяти, в которой мьютекс расположен, даже если эта память разделяется несколькими процессами.



    На попытки захвата мьютекса, осуществляемые с применением функций pthread_mutex_lock(), pthread_mutex_trylock() или pthread_mutex_timedlock() значения атрибутов влияют так, как описано выше.

    Как правило, поток, вызвавший pthread_mutex_lock(), блокируется, если мьютекс уже захвачен, и ждет его освобождения. В аналогичной ситуации функция pthread_mutex_trylock() немедленно завершается, возвращая код ошибки, а функция pthread_mutex_timedlock() блокируется, пока либо мьютекс не освободится, либо не наступит заданный аргументом abstime момент времени, отсчитываемого по часам CLOCK_REALTIME.

    Обычно функцию pthread_mutex_timedlock() вызывают лишь после того, как с помощью pthread_mutex_trylock() выяснили, что мьютекс захвачен. Ограничение времени ожидания делает программу устойчивой к ошибкам, препятствующим освобождению мьютексов, хотя, если считать, что охраняемый критический интервал невелик, данное свойство не имеет особого значения.

    В соответствии с общим подходом, если во время ожидания освобождения мьютекса потоку доставляется сигнал, после выхода из функции обработки сигнала оно возобновляется, как если бы и не прерывалось.

    Функция pthread_mutex_unlock() обычно освобождает мьютекс. Для мьютексов типа PTHREAD_MUTEX_RECURSIVE вызов pthread_mutex_unlock(), строго говоря, приводит лишь к уменьшению счетчика на единицу, а освобождение происходит только тогда, когда счетчик становится нулевым. Кому достанется мьютекс после освобождения, зависит от политики планирования.

    В качестве примера использования мьютексов приведем упрощенную реализацию динамического выделения памяти в многопотоковой среде. На листинге 2.7 показан заголовочный файл, на листинге 2.8 – исходный текст функций выделения и освобождения памяти.

    #ifndef g_MALLOC #define g_MALLOC

    /* Количество размеров (в словах типа size_t), */ /* для которых поддерживаются */ /* разные списки памяти */ #define DIF_SIZES 8

    /* Размер пула памяти */ #define POOL_SIZE 65536

    /* Указатель на кусок памяти нулевого размера */ #define g_NULL ((void *) (-1))



    /* Первое поле следующей структуры нужно /* для всех кусков памяти, а второе – */ /* только для провязки свободных.*/ /* При отведении памяти адрес второго */ */ поля выдается как результат */ typedef struct listi { size_t length; struct listi *pnext; } *list_of_mem;

    extern void *g_malloc (size_t); extern void g_free (void *);

    #endif

    Листинг 2.7. Заголовочный файл g_malloc.h для функций выделения и освобождения памяти в многопотоковой среде.

    /* * * * * * * * * * * * * * * * * * * * * */ /* Функции выделения и освобождения памяти */ /* * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include "g_malloc.h"

    static char mem_pool [POOL_SIZE] = {0, }; /* Размер занятой части пула памяти */ /* Списки свободного пространства */ /* (по одному на каждый размер */ /* от 1 до DIF_SIZES) */ static size_t cur_pool_size = 0;

    static list_of_mem short_lists [DIF_SIZES] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; /* Список больших */ /* свободных кусков (превосходящих DIF_SIZES) */ /* Разные мьютексы для разных */ /* групп списков свободного пространства */ static list_of_mem big_list = NULL;

    static pthread_mutex_t short_lists_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t big_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;

    /* * * * * * * * * * */ /* Выделение памяти */ /* * * * * * * * * * */ void *g_malloc (size_t size) { /* Указатель для хождения по списку */ /* больших свободных кусков */ list_of_mem *p; /* Указатель для хождения по спискам */ /* свободных кусков */ list_of_mem pt; /* Индекс в массиве short_lists */ size_t ls; size_t ts; /* Временная переменная */

    if (size == 0) { return (g_NULL); /* Важно, чтобы результат был */ /* отличен от NULL, поскольку NULL */ /* – признак ненормального завершения */ }

    /* Округлим запрошенный размер вверх */ /* до кратного размеру size_t и */ /* прибавим слово служебной информации */ size = (size – 1 + 2 * sizeof (size_t)) & ~(sizeof (size_t) – 1);



    /* Вычислим индекс в массиве */ /* short_lists [], соответствующий */ /* запрошенному размеру */ ls = size / sizeof (size_t) – 2;

    if (ls < DIF_SIZES) { /* Попробуем выдать кусок */ /* из списка коротких */ assert ( pthread_mutex_lock( &short_lists_mutex) == 0); if ((pt = short_lists [ls]) != NULL) { /* Есть нужный кусок */ short_lists [ls] = (short_lists [ls])->pnext; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); return (&pt->pnext); } assert (pthread_mutex_unlock( &short_lists_mutex) == 0); }

    /* Попробуем выдать кусок из */ /* списка больших */ assert (pthread_mutex_lock( &big_list_mutex) == 0); for (p = &big_list, pt = *p; pt != NULL; p = &pt->pnext, pt = *p) { if ((signed long) (ts = pt->length – size) >= 0) { /* Нашли подходящий кусок */ if (ts < sizeof (*pt)) { /* Придется выдать кусок целиком – */ /* в остатке не помещается */ /* служебная информация */ *p = pt->pnext; } else { /* Отрежем сколько надо и, */ /* при необходимости, */ /* перецепим остаток в */ /* список коротких */ if ((ls = (pt->length = ts) / sizeof (size_t) – 2) < DIF_SIZES) { *p = pt->pnext; assert (pthread_mutex_lock( &short_lists_mutex) == 0); pt->pnext = short_lists [ls]; short_lists [ls] = pt; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); } pt = (list_of_mem) ((char *) pt + ts); pt->length = size; } assert ( pthread_mutex_unlock( &big_list_mutex) == 0); return (&pt->pnext); } } /* for */ assert (pthread_mutex_unlock ( &big_list_mutex) == 0);

    /* Кусок из большого списка */ /* выдать не удалось. */ /* Попробуем взять прямо из */ /* пула памяти */ assert (pthread_mutex_lock( &pool_mutex) == 0); if (cur_pool_size + size <= POOL_SIZE) { pt = (list_of_mem) (mem_pool + cur_pool_size); pt->length = size; cur_pool_size += size; assert (pthread_mutex_unlock ( &pool_mutex) == 0); return (&pt->pnext); } assert (pthread_mutex_unlock( &pool_mutex) == 0);

    /* Неудача при выделении памяти */ errno = ENOMEM; return (NULL); }



    /* * * * * * * * * * * * * * * * * * */ /* Возврат ранее запрошенной памяти */ /* * * * * * * * * * * * * * * * * * */ void g_free (void *p) { list_of_mem pt; size_t size, ls;

    if ((p == g_NULL) || (p == NULL)) { return; }

    /* Установим указатель на */ /* служебную информацию */ pt = (list_of_mem) ((char *) p – sizeof (size_t)); size = pt->length; ls = size / sizeof (size_t) – 2; memset (p, 0, size – sizeof (size_t));

    /* Не из конца ли пула этот кусок? */ assert (pthread_mutex_lock( &pool_mutex) == 0); if (((char *) pt + size) == (mem_pool + cur_pool_size)) { pt->length = 0; cur_pool_size -= size; assert (pthread_mutex_unlock( &pool_mutex) == 0); return; } assert (pthread_mutex_unlock( &pool_mutex) == 0);

    /* Добавим освободившийся кусок */ /* к одному из списков */ if (ls < DIF_SIZES) { assert (pthread_mutex_lock( &short_lists_mutex) == 0); pt->pnext = short_lists [ls]; short_lists [ls] = pt; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); } else { /* Добавим к большому списку */ assert (pthread_mutex_lock( &big_list_mutex) == 0); pt->pnext = big_list; big_list = pt; assert (pthread_mutex_unlock( &big_list_mutex) == 0); } }

    Листинг 2.8. Исходный текст функций выделения и освобождения памяти в многопотоковой среде.

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

    С каждым из трех источников выделения памяти (пул, списки коротких, список больших кусков) ассоциирован свой мьютекс. Цель подобного разделения – минимизировать время владения мьютексом. Только при операциях с общим списком оно может быть большим.

    Для библиотечных функций удобны предоставляемые стандартом POSIX-2001 средства инициализации статически описанных мьютексов.

    На листинге 2.9 показана тестовая многопотоковая программа, а на листинге 2.10 – возможные результаты ее работы.


    Видно, что выполнение потоков управления чередуется, и потоки влияют друг на друга.

    #include #include #include #include "g_malloc.h"

    static void *start_func (void *dummy) { void *p1, *p2, *p3;

    printf ("g_malloc (65000): %p\n", p1 = g_malloc (65000)); sleep (1); printf ("g_malloc (1): %p\n", p2 = g_malloc (1)); sleep (1); g_free (p1); sleep (1); g_free (p2); sleep (1); printf ("g_malloc (64990): %p\n", p1 = g_malloc (64990)); sleep (1); printf ("g_malloc (1): %p\n", p2 = g_malloc (1)); sleep (1); printf ("g_malloc (5): %p\n", p3 = g_malloc (5)); sleep (1); g_free (p1); sleep (1); g_free (p2); sleep (1); g_free (p3); sleep (1); printf ("g_malloc (100000): %p\n", p3 = g_malloc (100000));

    return (NULL); }

    int main (void) { pthread_t pt1, pt2;

    pthread_create (&pt1, NULL, start_func, NULL); pthread_create (&pt2, NULL, start_func, NULL);

    pthread_join (pt1, NULL); pthread_join (pt2, NULL);

    return (0); }

    Листинг 2.9. Пример программы, использующей функции выделения и освобождения памяти в многопотоковой среде.

    g_malloc (65000): 0x8049024 g_malloc (65000): (nil) g_malloc (1): 0x8058e10 g_malloc (1): 0x8058e18 g_malloc (64990): 0x804902c g_malloc (64990): (nil) g_malloc (1): 0x8049024 g_malloc (1): 0x8058e10 g_malloc (5): 0x8058e18 g_malloc (5): 0x8058e24 g_malloc (100000): (nil) g_malloc (100000): (nil)

    Листинг 2.10. Возможные результаты работы программы, использующей функции выделения и освобождения памяти в многопотоковой среде.

    Мьютексы – весьма подходящее средство для реализации обеда философов. Ниже приведена программа, написанная С.В. Самборским (см. листинг 2.11).

    /* Обедающие философы. Многопотоковая реализация с помощью мьютексов. Запуск: mudrecMutex [-a | -p | -I] [-t число_секунд] имя_философа ...

    Опции: -t число_секунд – сколько секунд моделируется

    Стратегии захвата вилок: -a – сначала захватывается вилка с меньшим номером; -p – сначала захватывается нечетная вилка; -I – некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется.



    Пример запуска: mudrecMutex -p -t 300 A B C D E F G\ H I J K L M N\ O P Q R S T U\ V W X Y Z */

    static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecMutex.c,v 1.14 2003/11/20 16:09:20" "sambor Exp $";

    #include #include #include #include #include #include #include #include #include

    #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))

    struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra;

    /* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */

    /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])

    /* Массив мьютексов для синхронизации доступа */ /* к вилкам */ pthread_mutex_t *mtxFork;

    /* Разные алгоритмы захвата вилок */ static void get_forks_simple ( struct mudrec *this); static void get_forks_odd ( struct mudrec *this); static void get_forks_maybe_infinit_time( struct mudrec *this);

    /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;

    /* Возвращение вилок */ static void put_forks (struct mudrec *this);

    /*Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn;

    while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL);

    bytes = write ( private_pFdIn, buffer, strlen (buffer));

    (*get_forks) (this);

    wait_time = time (NULL) – tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time);

    sprintf( buffer, "%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }

    /* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; }



    /* Ест */ { int eat_time = rand () % 20 + 1;

    sleep (eat_time);

    this->eat_time += eat_time; this->count++; sprintf (buffer, "%s: ел %d сек\n", this->name, eat_time); bytes = write ( private_pFdIn, buffer, strlen (buffer)); }

    /* Отдает вилки */ put_forks (this);

    if (Stop) break;

    /* Размышляет */ { int think_time = rand () % 10 + 1;

    sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */

    sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));

    close (private_pFdIn);

    return (NULL); } /* Поток-философ */

    /* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { pthread_mutex_unlock ( &mtxFork [this->left_fork – 1]); pthread_mutex_unlock ( &mtxFork [this->right_fork – 1]); }

    /* Берет вилки по очереди в порядке номеров */ static void get_forks_simple( struct mudrec *this) { int first = min ( this->left_fork, this->right_fork); int last = max ( this->left_fork, this->right_fork); pthread_mutex_lock ( &mtxFork [first – 1]); pthread_mutex_lock ( &mtxFork [last – 1]); }

    /* Берем сначала нечетную вилку */ /* (если обе нечетные – */ /* то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;

    int first; int last;

    if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); }

    pthread_mutex_lock ( &mtxFork [first – 1]); pthread_mutex_lock ( &mtxFork [last – 1]); }

    /* Берем вилки по очереди, в */ /* произвольном порядке. */ /* Но если вторая вилка не */ /* берется сразу, то кладем */ /* первую. */ /* То есть философ не расходует */ /* вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;



    for (;;) { pthread_mutex_lock ( &mtxFork [left – 1]); if (0 == pthread_mutex_trylock (&mtxFork [right – 1])) return; pthread_mutex_unlock ( &mtxFork [left – 1]); pthread_mutex_lock ( &mtxFork [right – 1]); if (0 == pthread_mutex_trylock (&mtxFork [left – 1])) return; pthread_mutex_unlock ( &mtxFork [right – 1]); } }

    /* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; }

    static void usage (char name []) { fprintf (stderr, "Использование: %s" " [-a | -p | -I] " "[-t число_секунд] " "имя_философа ...\n", name); exit (1); }

    /* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact;

    while ((c = getopt (argc, argv, "apIt:")) != -1) { (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol( optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } }

    nMudr = argc – optind; /* Меньше двух */ /* философов неинтересно ... */ if (nMudr < 2) usage (argv [0]);

    /* Создание канала для протокола */ /* обработки событий */ pipe (protokol); kafedra = calloc (sizeof (struct mudrec), nMudr);

    /* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0);

    /* Укажем новичку, */ /* какими вилками */ /* пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } /* Последний */ /* пользуется вилкой первого */ kafedra [nMudr – 1].right_fork = 1;

    /* Зададим реакцию на сигналы */ /* и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, (struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time);



    /* Создадим мьютексы для охраны вилок */ mtxFork = calloc ( sizeof (pthread_mutex_t), nMudr); for (i = 0; i < nMudr; i++) { pthread_mutex_init ( &mtxFork [i], NULL); }

    /* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create ( &kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]);

    /* Выдача сообщений на стандартный */ /* вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);

    /* Уничтожение мьютексов */ for (i = 0; i < nMudr; i++) { pthread_mutex_destroy ( &mtxFork [i]); }

    /* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i – 1];

    full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time;

    if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count;

    printf ( "%s: ел %d раз в " "среднем: думал=%.1f " "ел=%.1f ждал=%.1f " "(максимум %d)\n", this->name, this->count, think_time,eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */

    { float total_time = ( full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr; printf("Среднее число одновременно" " едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно" " ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */

    free (mtxFork); free (kafedra);

    /* Сообщим об окончании работы. */ printf ("Конец обеда\n");

    return 0; }

    Листинг 2.11.Многопотоковый вариант решения задачи об обедающих философах с использованием мьютексов.


    Особенности синхронизации потоков управления


    По сравнению с процессами, потоки управления характеризуются двумя особенностями:
  • на порядок меньшими накладными расходами на обслуживание;
  • существенно более тесным взаимодействием в общем адресном пространстве.

  • Очевидно, чтобы быть практически полезными, средства синхронизации, специально ориентированные на потоки управления, должны быть оптимизированы с учетом обеих отмеченных особенностей.
    К числу таких средств, присутствующих в стандарте POSIX-2001, принадлежат мьютексы, переменные условия, блокировки чтение-запись, спин-блокировки и барьеры.
    Мьютекс – это синхронизирующий объект, использование которого позволяет множеству потоков управления упорядочить доступ к разделяемым данным. Название этого средства синхронизации отражает его функциональность – взаимное исключение (mutual-exclusion). Поток захватывает мьютекс в монопольное владение и остается владельцем, пока сам же его не освободит.
    Переменная условия в качестве синхронизирующего объекта дает потокам управления возможность многократно приостанавливать выполнение, пока некий ассоциированный предикат (условие) не станет истинным. Говорят, что поток, выполнение которого приостановлено на переменной условия, блокирован на этой переменной.
    Блокировки чтение-запись (много читателей или один писатель) в каждый момент времени позволяют нескольким потокам управления одновременно иметь к данным доступ на чтение или только одному потоку – доступ на запись. Естественно, подобные блокировки обычно применяют для защиты данных, которые читаются чаще, чем изменяются.
    Спин-блокировки представляют собой низкоуровневое средство синхронизации. Как и мьютексы, они предназначены для упорядочения доступа множества потоков управления к разделяемым данным.
    Барьеры предназначены для синхронизации множества потоков управления в определенной точке их выполнения.
    Средства синхронизации могут использоваться для достижения двух существенно разных целей:
  • захват (как правило, на короткое время) разделяемого объекта для защиты критического интервала;
  • ожидание (долгое или даже потенциально неограниченное) наступления некоторого события, выполнения некоторого условия.

  • Мьютексы и блокировки можно отнести к первой из выделенных категорий, переменные условия и барьеры – ко второй.
    Вообще говоря, средства синхронизации потоков управления можно использовать не только в рамках одного процесса, но и среди множества процессов, расположенных в разделяемой, доступной на запись памяти, соответствующим образом инициализированной.
    Со средствами синхронизации потоков управления ассоциируются атрибуты и атрибутные объекты, которые можно опрашивать и/или изменять аналогично тому, как это делается для самих потоков.


    Переменные условия


    Пусть имеется некоторый предикат (условие), зависящий от значений переменных, разделяемых несколькими потоками управления. Совместное использование мьютексов, переменных условия и обслуживающих их функций позволяет организовать экономное ожидание состояния истинности этого предиката.
    Общая схема применения переменных условия выглядит следующим образом. С разделяемыми переменными, фигурирующими в предикате, ассоциируется мьютекс, который необходимо захватить перед началом проверок. Затем поток управления входит в цикл вида
    while (! предикат) { Ожидание на переменной условия с освобождением мьютекса. После успешного завершения ожидания поток вновь оказывается владельцем мьютекса. }
    После нормального выхода из цикла проверяемое условие истинно; можно выполнить требуемые действия и освободить мьютекс.
    Разблокирование потоков управления, ожидающих на переменной условия, должен обеспечить другой поток, изменивший значения разделяемых переменных и отправивший ждущим соответствующее уведомление. Вообще говоря, не гарантируется, что в момент разблокирования проверяемое условие истинно, поэтому ожидание и следует обертывать в цикл, делая его потенциально многократным.
    По логике применения переменные условия напоминают семафоры: одни потоки ждут их перехода в некоторое состояние, другие своими действиями должны этот переход обеспечить и, тем самым, разблокировать ждущих. Есть, однако, и принципиальное отличие. Содержательный предикат, истинность которого является целью ожидания, ассоциируется с семафором неявно и дополнительно не проверяется; считается, что прекращение ожидания само по себе является свидетельством истинности. Это значит, что ответственность за корректность синхронизирующих действий разделяется между оперирующими с семафором потоками управления со всеми вытекающими отсюда последствиями. Для переменных условия прекращение ожидания гарантий истинности предиката не дает, его приходится в явном виде записывать в заголовке цикла, что делает программу устойчивее и упрощает анализ ее корректности.

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

    Переходя к описанию функций, предлагаемых стандартом POSIX-2001 для обслуживания переменных условия, укажем, что их можно разделить на следующие группы:

  • инициализация и разрушение переменных условия: pthread_cond_init(), pthread_cond_destroy() (см. листинг 2.12);

    #include

    int pthread_cond_init ( pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

    int pthread_cond_destroy ( pthread_cond_t *cond);

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    Листинг 2.12. Описание функций инициализации и разрушения переменных условия. (html, txt)

  • блокирование (ожидание) на переменной условия: pthread_cond_wait(), pthread_cond_timedwait() (см. листинг 2.13);

    #include

    int pthread_cond_wait ( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

    int pthread_cond_timedwait ( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, struct timespec *restrict abstime);

    Листинг 2.13. Описание функций блокирования на переменной условия. (html, txt)

  • разблокирование (прекращение ожидания) потоков управления, блокированных на переменной условия: pthread_cond_broadcast(), pthread_cond_signal() (см. листинг 2.14);

    #include

    int pthread_cond_broadcast ( pthread_cond_t *cond);

    int pthread_cond_signal ( pthread_cond_t *cond);

    Листинг 2.14. Описание функций разблокирования потоков управления, блокированных на переменной условия. (html, txt)

  • инициализация и разрушение атрибутных объектов переменных условия: pthread_condattr_init(), pthread_condattr_destroy() (см. листинг 2.15);

    #include

    int pthread_condattr_init ( pthread_condattr_t *attr);



    int pthread_condattr_destroy ( pthread_condattr_t *attr);

    Листинг 2.15. Описание функций инициализации и разрушения атрибутных объектов переменных условия. (html, txt)

  • опрос и установка атрибутов переменных условия в атрибутных объектах: признака использования несколькими процессами (обслуживается функциями pthread_condattr_getpshared(), pthread_condattr_setpshared()) и идентификатора часов реального времени, используемых для ограничения ожидания на переменной условия (функции pthread_condattr_getclock(), pthread_condattr_setclock()) (см. листинг 2.16).

    #include

    int pthread_condattr_getpshared ( const pthread_condattr_t *restrict attr, int *restrict pshared);

    int pthread_condattr_setpshared ( pthread_condattr_t *attr, int pshared);

    int pthread_condattr_getclock ( const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);

    int pthread_condattr_setclock ( pthread_condattr_t *attr, clockid_t clock_id);

    Листинг 2.16. Описание функций опроса и установки атрибутов переменных условия в атрибутных объектах. (html, txt)





  • Для функции pthread_cond_timedwait() к приведенному перечню добавляется еще и срабатывание контроля по времени. Отметим, впрочем, что и в этом случае мьютекс передается во владение вызывающему потоку управления, а предикат может оказаться истинным (пока срабатывал контроль по времени, другой поток изменил значения разделяемых переменных).

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

    rc = 0; while (! predicate && rc == 0) { rc = pthread_cond_timedwait ( &cond, &mutex, &ts); }

    Листинг 2.17. Типичный цикл ожидания на переменной условия с контролем по времени.

    Если бы аргумент ts задавался как интервал, его пришлось бы перевычислять в цикле; кроме того, контроль по времени оказался бы смазанным непредсказуемыми задержками в выполнении потока управления.

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

    Функция pthread_cond_broadcast() разблокирует все потоки управления, ждущие на переменной условия (это полезно, например, когда писатель уступает место читателям), а функция pthread_cond_signal() (не генерирующая, вопреки названию, никаких сигналов) – по крайней мере один из них (если таковые вообще имеются). Порядок разблокирования определяется политикой планирования; борьба за владение мьютексом, заданным в начале ожидания, протекает так же, как и при одновременном вызове pthread_mutex_lock (mutex).



    Вообще говоря, поток управления, вызвавший pthread_cond_broadcast() или pthread_cond_signal(), не обязан быть владельцем упомянутого мьютекса; тем не менее, если требуется предсказуемость решений планировщика, это условие должно быть выполнено.

    Реализация должна гарантировать, что поток управления, ожидавший на переменной условия и разблокированный в результате получения заказа на терминирование, не "потребит" разрешение на разблокирование, сгенерированное функцией pthread_cond_signal(), если имеются другие потоки, ожидающие на той же переменной условия. Без подобной гарантии конкуренция между терминированием и разблокированием может завершиться тупиковой ситуацией.

    Не рекомендуется вызывать pthread_cond_signal() (а также освобождать мьютексы) в (асинхронно выполняемых) функциях обработки сигналов.

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

    /* * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда */ /* философов с использованием */ /* мьютексов и переменных условия */ /* * * * * * * * * * * * * * * * * */

    #include #include #include #include #include

    /* Число обедающих философов */ #define QPH 5

    /* Время (в секундах) на обед */ #define FO 15

    /* Длительность еды */ #define ernd (rand () % 3 + 1)

    /* Длительность разговора */ #define trnd (rand () % 5 + 1)

    /* Состояние вилок */ static int fork_busy [QPH] = {0, };

    /* Синхронизирующая переменная условия */ static pthread_cond_t forks_cond = PTHREAD_COND_INITIALIZER;

    /* Синхронизирующий мьютекс */ static pthread_mutex_t forks_mutex = PTHREAD_MUTEX_INITIALIZER;

    /* * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока-философа. */ /* Аргумент – номер философа */ /* * * * * * * * * * * * * * * * * * */ void *start_phil (void *no) { /* Время до конца обеда */ int fo; /* Время очередного отрезка */ /* еды или беседы */ int t;



    fo = FO; while (fo > 0) { /* Обед */

    /* Философ говорит */ printf ("Философ %d беседует\n", (int) no); t = trnd; sleep (t); fo -= t;

    /* Пытается взять вилки */ (void) pthread_mutex_lock ( &forks_mutex); while (fork_busy [(int) no – 1] || fork_busy [(int) no % QPH] ) { (void) pthread_cond_wait ( &forks_cond, &forks_mutex); } fork_busy [(int) no – 1] = fork_busy [(int) no % QPH] = 1; (void) pthread_mutex_unlock ( &forks_mutex);

    /* Ест */ printf ("Философ %d ест\n", (int) no); t = ernd; sleep (t); fo -= t;

    /* Отдает вилки */ (void) pthread_mutex_lock ( &forks_mutex); fork_busy [(int) no – 1] = fork_busy [(int) no % QPH] = 0; (void) pthread_cond_broadcast (&forks_cond); (void) pthread_mutex_unlock (&forks_mutex); } /* while */

    printf ("Философ %d закончил обед\n", (int) no); return (NULL); }

    /* * * * * * * * * * * * * * * */ /* Создание потоков-философов */ /* и ожидание их завершения */ /* * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов */ /* потоков-философов */ pthread_t pt_id [QPH]; /* Номер философа */ int no; /* Атрибутный объект для */ /* создания потоков */ pthread_attr_t attr_obj;

    if ((errno = pthread_attr_init( &attr_obj)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); }

    /* В очередь, господа */ /* философы, в очередь! */ if ((errno = pthread_attr_setschedpolicy( &attr_obj, SCHED_FIFO)) != 0) { perror ( "PTHREAD_ATTR_SETSCHEDPOLICY"); return (errno); }

    /* Все – к столу */ for (no = 1; no <= QPH; no++) { if ((errno = pthread_create ( &pt_id [no – 1], &attr_obj, start_phil, (void *) no)) != 0) { perror ( "PTHREAD_CREATE"); return (no); } }

    /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) pthread_join ( pt_id [no – 1], NULL); }

    return 0; }

    Листинг 2.18. Пример многопотоковой реализации обеда философов с использованием мьютексов и переменных условия.

    Результаты работы этой программы могут выглядеть так, как показано на листинге 2.19.



    Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 ест Философ 2 ест Философ 4 беседует Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 3 беседует Философ 4 ест Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 1 ест Философ 4 беседует Философ 3 ест Философ 1 беседует Философ 5 ест Философ 5 беседует Философ 3 беседует Философ 4 ест Философ 2 ест Философ 4 беседует Философ 2 беседует Философ 1 ест Философ 3 ест Философ 1 закончил обед Философ 5 ест Философ 3 закончил обед Философ 2 ест Философ 5 закончил обед Философ 4 ест Философ 2 закончил обед Философ 4 закончил обед

    Листинг 2.19. Возможные результаты работы многопотоковой программы, моделирующей обед философов с использованием мьютексов и переменных условия.

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

    В употреблении глобальной (в том же смысле, что и мьютекс) переменной условия и всеобщем разблокировании ждущих (посредством вызова pthread_cond_broadcast()) при освобождении любой пары вилок также нет ничего плохого. Ресурсов это практически не отнимает, а пока до разбуженного философа дойдет очередь, нужные ему вилки на самом деле могут оказаться свободными, даже если ожидание на переменной условия было прервано не из-за них. В то же время, кажущееся предпочтительным создание соответствующего числа переменных условия и избирательное разблокирование соседей насытившегося философа двумя вызовами pthread_cond_signal() на самом деле не позволяет гарантировать отсутствие ложных пробуждений, так как из-за нюансов планирования нужную вилку могут выхватить буквально из-под носа (или из-под руки).

    Обратим внимание на то, что цикл, в который заключено ожидание на переменной условия, в данном случае использован для захвата нескольких ресурсов.Это напоминает описанные в курсе [1] групповые операции с семафорами, если иметь в виду цикл в целом и отвлечься от ложных разблокирований.

    Разумеется, приведенное решение задачи об обедающих философах является нечестным, поскольку во время ожидания на переменной условия, которое длится неопределенно долго, философ "отключается", он не беседует и не ест, что, по условию задачи, запрещено (этим недостатком страдает и программа из курса [1], основанная на групповых операциях с семафорами). Однако как иллюстрация типичных способов работы с мьютексами и переменными условия данная программа имеет право на существование.


    restrict mutex, const pthread_mutexattr_t


    #include
    int pthread_mutex_init ( pthread_mutex_t * restrict mutex, const pthread_mutexattr_t *restrict attr);
    int pthread_mutex_destroy ( pthread_mutex_t *mutex);
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    Листинг 2.1. Описание функций инициализации и разрушения мьютексов.
    Закрыть окно




    #include
    int pthread_mutex_lock ( pthread_mutex_t *mutex);
    int pthread_mutex_trylock ( pthread_mutex_t *mutex);
    int pthread_mutex_unlock ( pthread_mutex_t *mutex);
    Листинг 2.2. Описание функций захвата и освобождения мьютексов.
    Закрыть окно




    #include #include int pthread_mutex_timedlock ( pthread_mutex_t * restrict mutex, const struct timespec *restrict abstime);
    Листинг 2.3. Описание функции захвата мьютексов с ограниченным ожиданием.
    Закрыть окно




    #include
    int pthread_mutex_getprioceiling ( const pthread_mutex_t *restrict mutex, int *restrict prioceiling);
    int pthread_mutex_setprioceiling ( pthread_mutex_t * restrict mutex, int prioceiling, int *restrict old_ceiling);
    Листинг 2.4. Описание функций опроса и установки атрибутов мьютекса.
    Закрыть окно




    #include
    int pthread_mutexattr_init ( pthread_mutexattr_t *attr);
    int pthread_mutexattr_destroy ( pthread_mutexattr_t *attr);
    Листинг 2.5. Описание функций инициализации и разрушения атрибутных объектов мьютексов.
    Закрыть окно




    #include
    int pthread_mutexattr_gettype ( const pthread_mutexattr_t *restrict attr, int *restrict type);
    int pthread_mutexattr_settype ( pthread_mutexattr_t *attr, int type);
    int pthread_mutexattr_getpshared ( const pthread_mutexattr_t *restrict attr, int *restrict pshared);
    int pthread_mutexattr_setpshared ( pthread_mutexattr_t *attr, int pshared);
    int pthread_mutexattr_getprotocol ( const pthread_mutexattr_t *restrict attr, int *restrict protocol);
    int pthread_mutexattr_setprotocol ( *attr, int protocol);
    int pthread_mutexattr_getprioceiling ( const pthread_mutexattr_t *restrict attr, int *restrict prioceiling); int pthread_mutexattr_setprioceiling ( pthread_mutexattr_t *attr, int prioceiling);
    Листинг 2.6. Описание функций опроса и установки атрибутов мьютекса в атрибутных объектах.
    Закрыть окно




    #ifndef g_MALLOC #define g_MALLOC
    /* Количество размеров (в словах типа size_t), */ /* для которых поддерживаются */ /* разные списки памяти */ #define DIF_SIZES 8
    /* Размер пула памяти */ #define POOL_SIZE 65536
    /* Указатель на кусок памяти нулевого размера */ #define g_NULL ((void *) (-1))
    /* Первое поле следующей структуры нужно /* для всех кусков памяти, а второе – */ /* только для провязки свободных.*/ /* При отведении памяти адрес второго */ */ поля выдается как результат */ typedef struct listi { size_t length; struct listi *pnext; } *list_of_mem;
    extern void *g_malloc (size_t); extern void g_free (void *);
    #endif
    Листинг 2.7. Заголовочный файл g_malloc.h для функций выделения и освобождения памяти в многопотоковой среде.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * */ /* Функции выделения и освобождения памяти */ /* * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include "g_malloc.h"
    static char mem_pool [POOL_SIZE] = {0, }; /* Размер занятой части пула памяти */ /* Списки свободного пространства */ /* (по одному на каждый размер */ /* от 1 до DIF_SIZES) */ static size_t cur_pool_size = 0;
    static list_of_mem short_lists [DIF_SIZES] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; /* Список больших */ /* свободных кусков (превосходящих DIF_SIZES) */ /* Разные мьютексы для разных */ /* групп списков свободного пространства */ static list_of_mem big_list = NULL;
    static pthread_mutex_t short_lists_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t big_list_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;
    /* * * * * * * * * * */ /* Выделение памяти */ /* * * * * * * * * * */ void *g_malloc (size_t size) { /* Указатель для хождения по списку */ /* больших свободных кусков */ list_of_mem *p; /* Указатель для хождения по спискам */ /* свободных кусков */ list_of_mem pt; /* Индекс в массиве short_lists */ size_t ls; size_t ts; /* Временная переменная */
    if (size == 0) { return (g_NULL); /* Важно, чтобы результат был */ /* отличен от NULL, поскольку NULL */ /* – признак ненормального завершения */ }
    /* Округлим запрошенный размер вверх */ /* до кратного размеру size_t и */ /* прибавим слово служебной информации */ size = (size – 1 + 2 * sizeof (size_t)) & ~(sizeof (size_t) – 1);
    /* Вычислим индекс в массиве */ /* short_lists [], соответствующий */ /* запрошенному размеру */ ls = size / sizeof (size_t) – 2;
    if (ls < DIF_SIZES) { /* Попробуем выдать кусок */ /* из списка коротких */ assert ( pthread_mutex_lock( &short_lists_mutex) == 0); if ((pt = short_lists [ls]) != NULL) { /* Есть нужный кусок */ short_lists [ls] = (short_lists [ls])->pnext; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); return (&pt->pnext); } assert (pthread_mutex_unlock( &short_lists_mutex) == 0); }
    /* Попробуем выдать кусок из */ /* списка больших */ assert (pthread_mutex_lock( &big_list_mutex) == 0); for (p = &big_list, pt = *p; pt != NULL; p = &pt->pnext, pt = *p) { if ((signed long) (ts = pt->length – size) >= 0) { /* Нашли подходящий кусок */ if (ts < sizeof (*pt)) { /* Придется выдать кусок целиком – */ /* в остатке не помещается */ /* служебная информация */ *p = pt->pnext; } else { /* Отрежем сколько надо и, */ /* при необходимости, */ /* перецепим остаток в */ /* список коротких */ if ((ls = (pt->length = ts) / sizeof (size_t) – 2) < DIF_SIZES) { *p = pt->pnext; assert (pthread_mutex_lock( &short_lists_mutex) == 0); pt->pnext = short_lists [ls]; short_lists [ls] = pt; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); } pt = (list_of_mem) ((char *) pt + ts); pt->length = size; } assert ( pthread_mutex_unlock( &big_list_mutex) == 0); return (&pt->pnext); } } /* for */ assert (pthread_mutex_unlock ( &big_list_mutex) == 0);
    /* Кусок из большого списка */ /* выдать не удалось. */ /* Попробуем взять прямо из */ /* пула памяти */ assert (pthread_mutex_lock( &pool_mutex) == 0); if (cur_pool_size + size <= POOL_SIZE) { pt = (list_of_mem) (mem_pool + cur_pool_size); pt->length = size; cur_pool_size += size; assert (pthread_mutex_unlock ( &pool_mutex) == 0); return (&pt->pnext); } assert (pthread_mutex_unlock( &pool_mutex) == 0);
    /* Неудача при выделении памяти */ errno = ENOMEM; return (NULL); }
    /* * * * * * * * * * * * * * * * * * */ /* Возврат ранее запрошенной памяти */ /* * * * * * * * * * * * * * * * * * */ void g_free (void *p) { list_of_mem pt; size_t size, ls;
    if ((p == g_NULL) || (p == NULL)) { return; }
    /* Установим указатель на */ /* служебную информацию */ pt = (list_of_mem) ((char *) p – sizeof (size_t)); size = pt->length; ls = size / sizeof (size_t) – 2; memset (p, 0, size – sizeof (size_t));
    /* Не из конца ли пула этот кусок? */ assert (pthread_mutex_lock( &pool_mutex) == 0); if (((char *) pt + size) == (mem_pool + cur_pool_size)) { pt->length = 0; cur_pool_size -= size; assert (pthread_mutex_unlock( &pool_mutex) == 0); return; } assert (pthread_mutex_unlock( &pool_mutex) == 0);
    /* Добавим освободившийся кусок */ /* к одному из списков */ if (ls < DIF_SIZES) { assert (pthread_mutex_lock( &short_lists_mutex) == 0); pt->pnext = short_lists [ls]; short_lists [ls] = pt; assert (pthread_mutex_unlock( &short_lists_mutex) == 0); } else { /* Добавим к большому списку */ assert (pthread_mutex_lock( &big_list_mutex) == 0); pt->pnext = big_list; big_list = pt; assert (pthread_mutex_unlock( &big_list_mutex) == 0); } }
    Листинг 2.8. Исходный текст функций выделения и освобождения памяти в многопотоковой среде.
    Закрыть окно




    #include #include #include #include "g_malloc.h"
    static void *start_func (void *dummy) { void *p1, *p2, *p3;
    printf ("g_malloc (65000): %p\n", p1 = g_malloc (65000)); sleep (1); printf ("g_malloc (1): %p\n", p2 = g_malloc (1)); sleep (1); g_free (p1); sleep (1); g_free (p2); sleep (1); printf ("g_malloc (64990): %p\n", p1 = g_malloc (64990)); sleep (1); printf ("g_malloc (1): %p\n", p2 = g_malloc (1)); sleep (1); printf ("g_malloc (5): %p\n", p3 = g_malloc (5)); sleep (1); g_free (p1); sleep (1); g_free (p2); sleep (1); g_free (p3); sleep (1); printf ("g_malloc (100000): %p\n", p3 = g_malloc (100000));
    return (NULL); }
    int main (void) { pthread_t pt1, pt2;
    pthread_create (&pt1, NULL, start_func, NULL); pthread_create (&pt2, NULL, start_func, NULL);
    pthread_join (pt1, NULL); pthread_join (pt2, NULL);
    return (0); }
    Листинг 2.9. Пример программы, использующей функции выделения и освобождения памяти в многопотоковой среде.
    Закрыть окно




    g_malloc (65000): 0x8049024 g_malloc (65000): (nil) g_malloc (1): 0x8058e10 g_malloc (1): 0x8058e18 g_malloc (64990): 0x804902c g_malloc (64990): (nil) g_malloc (1): 0x8049024 g_malloc (1): 0x8058e10 g_malloc (5): 0x8058e18 g_malloc (5): 0x8058e24 g_malloc (100000): (nil) g_malloc (100000): (nil)
    Листинг 2.10. Возможные результаты работы программы, использующей функции выделения и освобождения памяти в многопотоковой среде.
    Закрыть окно




    /* Обедающие философы. Многопотоковая реализация с помощью мьютексов. Запуск: mudrecMutex [-a | -p | -I] [-t число_секунд] имя_философа ...
    Опции: -t число_секунд – сколько секунд моделируется
    Стратегии захвата вилок: -a – сначала захватывается вилка с меньшим номером; -p – сначала захватывается нечетная вилка; -I – некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется.
    Пример запуска: mudrecMutex -p -t 300 A B C D E F G\ H I J K L M N\ O P Q R S T U\ V W X Y Z */
    static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecMutex.c,v 1.14 2003/11/20 16:09:20" "sambor Exp $";
    #include #include #include #include #include #include #include #include #include
    #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))
    struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra;
    /* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */
    /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])
    /* Массив мьютексов для синхронизации доступа */ /* к вилкам */ pthread_mutex_t *mtxFork;
    /* Разные алгоритмы захвата вилок */ static void get_forks_simple ( struct mudrec *this); static void get_forks_odd ( struct mudrec *this); static void get_forks_maybe_infinit_time( struct mudrec *this);
    /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;
    /* Возвращение вилок */ static void put_forks (struct mudrec *this);
    /*Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn;
    while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL);
    bytes = write ( private_pFdIn, buffer, strlen (buffer));
    (*get_forks) (this);
    wait_time = time (NULL) – tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time);
    sprintf( buffer, "%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }
    /* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; }
    /* Ест */ { int eat_time = rand () % 20 + 1;
    sleep (eat_time);
    this->eat_time += eat_time; this->count++; sprintf (buffer, "%s: ел %d сек\n", this->name, eat_time); bytes = write ( private_pFdIn, buffer, strlen (buffer)); }
    /* Отдает вилки */ put_forks (this);
    if (Stop) break;
    /* Размышляет */ { int think_time = rand () % 10 + 1;
    sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */
    sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));
    close (private_pFdIn);
    return (NULL); } /* Поток-философ */
    /* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { pthread_mutex_unlock ( &mtxFork [this->left_fork – 1]); pthread_mutex_unlock ( &mtxFork [this->right_fork – 1]); }
    /* Берет вилки по очереди в порядке номеров */ static void get_forks_simple( struct mudrec *this) { int first = min ( this->left_fork, this->right_fork); int last = max ( this->left_fork, this->right_fork); pthread_mutex_lock ( &mtxFork [first – 1]); pthread_mutex_lock ( &mtxFork [last – 1]); }
    /* Берем сначала нечетную вилку */ /* (если обе нечетные – */ /* то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
    int first; int last;
    if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); }
    pthread_mutex_lock ( &mtxFork [first – 1]); pthread_mutex_lock ( &mtxFork [last – 1]); }
    /* Берем вилки по очереди, в */ /* произвольном порядке. */ /* Но если вторая вилка не */ /* берется сразу, то кладем */ /* первую. */ /* То есть философ не расходует */ /* вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
    for (;;) { pthread_mutex_lock ( &mtxFork [left – 1]); if (0 == pthread_mutex_trylock (&mtxFork [right – 1])) return; pthread_mutex_unlock ( &mtxFork [left – 1]); pthread_mutex_lock ( &mtxFork [right – 1]); if (0 == pthread_mutex_trylock (&mtxFork [left – 1])) return; pthread_mutex_unlock ( &mtxFork [right – 1]); } }
    /* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; }
    static void usage (char name []) { fprintf (stderr, "Использование: %s" " [-a | -p | -I] " "[-t число_секунд] " "имя_философа ...\n", name); exit (1); }
    /* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact;
    while ((c = getopt (argc, argv, "apIt:")) != -1) { (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol( optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } }
    nMudr = argc – optind; /* Меньше двух */ /* философов неинтересно ... */ if (nMudr < 2) usage (argv [0]);
    /* Создание канала для протокола */ /* обработки событий */ pipe (protokol); kafedra = calloc (sizeof (struct mudrec), nMudr);
    /* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0);
    /* Укажем новичку, */ /* какими вилками */ /* пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } /* Последний */ /* пользуется вилкой первого */ kafedra [nMudr – 1].right_fork = 1;
    /* Зададим реакцию на сигналы */ /* и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, (struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time);
    /* Создадим мьютексы для охраны вилок */ mtxFork = calloc ( sizeof (pthread_mutex_t), nMudr); for (i = 0; i < nMudr; i++) { pthread_mutex_init ( &mtxFork [i], NULL); }
    /* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create ( &kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]);
    /* Выдача сообщений на стандартный */ /* вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);
    /* Уничтожение мьютексов */ for (i = 0; i < nMudr; i++) { pthread_mutex_destroy ( &mtxFork [i]); }
    /* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i – 1];
    full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time;
    if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count;
    printf ( "%s: ел %d раз в " "среднем: думал=%.1f " "ел=%.1f ждал=%.1f " "(максимум %d)\n", this->name, this->count, think_time,eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */
    { float total_time = ( full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr; printf("Среднее число одновременно" " едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно" " ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */
    free (mtxFork); free (kafedra);
    /* Сообщим об окончании работы. */ printf ("Конец обеда\n");
    return 0; }
    Листинг 2.11. Многопотоковый вариант решения задачи об обедающих философах с использованием мьютексов.
    Закрыть окно




    #include
    int pthread_cond_init ( pthread_cond_t * restrict cond, const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy ( pthread_cond_t *cond);
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    Листинг 2.12. Описание функций инициализации и разрушения переменных условия.
    Закрыть окно




    #include
    int pthread_cond_wait ( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    int pthread_cond_timedwait ( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, struct timespec *restrict abstime);
    Листинг 2.13. Описание функций блокирования на переменной условия.
    Закрыть окно




    #include
    int pthread_cond_broadcast ( pthread_cond_t *cond);
    int pthread_cond_signal ( pthread_cond_t *cond);
    Листинг 2.14. Описание функций разблокирования потоков управления, блокированных на переменной условия.
    Закрыть окно




    #include
    int pthread_condattr_init ( pthread_condattr_t *attr);
    int pthread_condattr_destroy ( pthread_condattr_t *attr);
    Листинг 2.15. Описание функций инициализации и разрушения атрибутных объектов переменных условия.
    Закрыть окно




    #include
    int pthread_condattr_getpshared ( const pthread_condattr_t *restrict attr, int *restrict pshared);
    int pthread_condattr_setpshared ( pthread_condattr_t *attr, int pshared);
    int pthread_condattr_getclock ( const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
    int pthread_condattr_setclock ( pthread_condattr_t *attr, clockid_t clock_id);
    Листинг 2.16. Описание функций опроса и установки атрибутов переменных условия в атрибутных объектах.
    Закрыть окно




    rc = 0; while (! predicate && rc == 0) { rc = pthread_cond_timedwait ( &cond, &mutex, &ts); }
    Листинг 2.17. Типичный цикл ожидания на переменной условия с контролем по времени.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда */ /* философов с использованием */ /* мьютексов и переменных условия */ /* * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* Число обедающих философов */ #define QPH 5
    /* Время ( в секундах) на обед */ #define FO 15
    /* Длительность еды */ #define ernd (rand () % 3 + 1)
    /* Длительность разговора */ #define trnd (rand () % 5 + 1)
    /* Состояние вилок */ static int fork_busy [QPH] = {0, };
    /* Синхронизирующая переменная условия */ static pthread_cond_t forks_cond = PTHREAD_COND_INITIALIZER;
    /* Синхронизирующий мьютекс */ static pthread_mutex_t forks_mutex = PTHREAD_MUTEX_INITIALIZER;
    /* * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока-философа. */ /* Аргумент – номер философа */ /* * * * * * * * * * * * * * * * * * */ void *start_phil (void *no) { /* Время до конца обеда */ int fo; /* Время очередного отрезка */ /* еды или беседы */ int t;
    fo = FO; while (fo > 0) { /* Обед */
    /* Философ говорит */ printf ("Философ %d беседует\n", (int) no); t = trnd; sleep (t); fo -= t;
    /* Пытается взять вилки */ (void) pthread_mutex_lock ( &forks_mutex); while (fork_busy [(int) no – 1] || fork_busy [(int) no % QPH] ) { (void) pthread_cond_wait ( &forks_cond, &forks_mutex); } fork_busy [(int) no – 1] = fork_busy [(int) no % QPH] = 1; (void) pthread_mutex_unlock ( &forks_mutex);
    /* Ест */ printf ("Философ %d ест\n", (int) no); t = ernd; sleep (t); fo -= t;
    /* Отдает вилки */ (void) pthread_mutex_lock ( &forks_mutex); fork_busy [(int) no – 1] = fork_busy [(int) no % QPH] = 0; (void) pthread_cond_broadcast (&forks_cond); (void) pthread_mutex_unlock (&forks_mutex); } /* while */
    printf ("Философ %d закончил обед\n", (int) no); return (NULL); }
    /* * * * * * * * * * * * * * * */ /* Создание потоков-философов */ /* и ожидание их завершения */ /* * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов */ /* потоков-философов */ pthread_t pt_id [QPH]; /* Номер философа */ int no; /* Атрибутный объект для */ /* создания потоков */ pthread_attr_t attr_obj;
    if ((errno = pthread_attr_init( &attr_obj)) != 0) { perror ("PTHREAD_ATTR_INIT"); return (errno); }
    /* В очередь, господа */ /* философы, в очередь! */ if ((errno = pthread_attr_setschedpolicy( &attr_obj, SCHED_FIFO)) != 0) { perror ( "PTHREAD_ATTR_SETSCHEDPOLICY"); return (errno); }
    /* Все – к столу */ for (no = 1; no <= QPH; no++) { if ((errno = pthread_create ( &pt_id [no – 1], &attr_obj, start_phil, (void *) no)) != 0) { perror ( "PTHREAD_CREATE"); return (no); } }
    /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) pthread_join ( pt_id [no – 1], NULL); }
    return 0; }
    Листинг 2.18. Пример многопотоковой реализации обеда философов с использованием мьютексов и переменных условия.
    Закрыть окно




    Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 ест Философ 2 ест Философ 4 беседует Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 3 беседует Философ 4 ест Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 1 ест Философ 4 беседует Философ 3 ест Философ 1 беседует Философ 5 ест Философ 5 беседует Философ 3 беседует Философ 4 ест Философ 2 ест Философ 4 беседует Философ 2 беседует Философ 1 ест Философ 3 ест Философ 1 закончил обед Философ 5 ест Философ 3 закончил обед Философ 2 ест Философ 5 закончил обед Философ 4 ест Философ 2 закончил обед Философ 4 закончил обед
    Листинг 2.19. Возможные результаты работы многопотоковой программы, моделирующей обед философов с использованием мьютексов и переменных условия.
    Закрыть окно




    #include
    int pthread_rwlock_init ( pthread_rwlock_t * restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy ( pthread_rwlock_t *rwlock);
    Листинг 2.20. Описание функций инициализации и разрушения блокировок чтение-запись.
    Закрыть окно




    #include
    int pthread_rwlock_rdlock ( pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock ( pthread_rwlock_t *rwlock);
    Листинг 2.21. Описание функций установки блокировки на чтение.
    Закрыть окно




    #include #include int pthread_rwlock_timedrdlock ( pthread_rwlock_t * restrict rwlock, const struct timespec *restrict abstime);
    Листинг 2.22. Описание функции установки блокировки на чтение с ограниченным ожиданием.
    Закрыть окно




    #include
    int pthread_rwlock_wrlock ( pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock ( pthread_rwlock_t *rwlock);
    Листинг 2.23. Описание функций установки блокировки на запись.
    Закрыть окно




    #include #include int pthread_rwlock_timedwrlock ( pthread_rwlock_t * restrict rwlock, const struct timespec *restrict abstime);
    Листинг 2.24. Описание функции установки блокировки на запись с ограниченным ожиданием.
    Закрыть окно




    #include int pthread_rwlock_unlock ( pthread_rwlock_t *rwlock);
    Листинг 2.25. Описание функции снятия блокировки чтение-запись.
    Закрыть окно




    #include
    int pthread_rwlockattr_init ( pthread_rwlockattr_t *attr);
    int pthread_rwlockattr_destroy ( pthread_rwlockattr_t *attr);
    Листинг 2.26. Описание функций инициализации и разрушения атрибутных объектов блокировок.
    Закрыть окно




    #include
    int pthread_rwlockattr_getpshared ( const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
    int pthread_rwlockattr_setpshared ( pthread_rwlockattr_t *attr, int pshared);
    Листинг 2.27. Описание функций опроса и установки атрибутов блокировок в атрибутных объектах.
    Закрыть окно




    /* * * * * * * * * * * * * * */ /* Типы, структуры и функции */ /* для многопотоковой работы */ /* со списками */ /* * * * * * * * * * * * * * */
    #ifndef g_LIST #define g_LIST
    /* Указатель на процедуру */ typedef void (*aproc) (void *, void *);
    /* Указатель на целочисленную функцию */ typedef int (*afun) (void *, void *);
    /* Элемент списка указателей */ /* на объекты произвольного типа */ typedef struct g_link { /* Ссылка на следующий элемент списка */ struct g_link *pnext; /* Ссылка на объект */ void *pvalue; } *g_linkP;
    /* Список указателей на объекты */ /* произвольного типа */ typedef struct g_list { /* Голова списка */ g_linkP head; /* Блокировка, ассоциированная */ pthread_rwlock_t lk; /* со списком */ } *g_listP;
    /* Инициализировать список */ extern void g_list_init (g_listP);
    /* Завершить работу со списком */ extern void g_list_destroy (g_listP);
    /* Вставить новый элемент */ /* в конец списка */ extern void g_list_ins_last ( g_listP, void *); /* Вставить новый элемент в */ /* список перед первым, */ /* удовлетворяющим заданному */ /* свойству (или в конец) */ extern void g_list_ins_fun ( g_listP, void *, afun);
    /* Удалить элемент из списка */ extern void g_list_del ( g_listP, void *);
    /* Применить процедуру ко всем */ /* элементам списка */ extern void g_list_forall_x ( g_listP, aproc, void *);
    /* Выбрать подходящий элемент списка */ extern void *g_list_suchas_x ( g_listP, afun, void *);
    /* Выдать элемент по номеру */ extern void *g_list_get_bynum ( g_listP, int);
    #endif
    Листинг 2.28. Заголовочный файл g_list.h для функций работы со списками в многопотоковой среде.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * */ /* Реализация функций для многопотоковой */ /* работы со списками. */ /* Применяются блокировки чтение-запись */ /* без приостановки выполнения */ /* * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include #include "g_list.h"
    /* * * * * * * * * * * * * */ /* Инициализировать список */ /* * * * * * * * * * * * * */ void g_list_init (g_listP plist) { plist->head = NULL; errno = pthread_rwlock_init ( &plist->lk, NULL); } /* * * * * * * * * * * * * * * */ /* Завершить работу со списком */ /* * * * * * * * * * * * * * * */ void g_list_destroy (g_listP plist) { errno = pthread_rwlock_destroy ( &plist->lk); }
    /* * * * * * * * * * * * * * * * * * * * */ /* Вставить новый элемент в конец списка */ /* * * * * * * * * * * * * * * * * * * * */ void g_list_ins_last (g_listP plist, void *pval) { register g_linkP pt; register g_linkP *p;
    if ((errno = pthread_rwlock_trywrlock ( &plist->lk)) != 0) { return; }
    for (p = &plist->head; *p != NULL; p = &(*p)->pnext) ; if ((pt = (g_linkP) malloc (sizeof (*pt))) != NULL) { pt->pnext = NULL; pt->pvalue = pval; *p = pt; }
    (void) pthread_rwlock_unlock ( &plist->lk); }
    /* * * * * * * * * * * * * * * * * */ /* Вставить новый элемент в список */ /* перед первым, */ /* удовлетворяющим заданному свойству */ /* (или в конец) */ /* * * * * * * * * * * * * * * * */ void g_list_ins_fun (g_listP plist, void *pval, afun fun) { register g_linkP pt; register g_linkP *p;
    if ((errno = pthread_rwlock_trywrlock ( &plist->lk)) != 0) { return; }
    for (p = &plist->head; (*p != NULL) && (fun (pval, (*p)->pnext->pvalue) == 0); p = &(*p)->pnext) ;
    if ((pt = (g_linkP) malloc ( sizeof (*pt))) != NULL) { pt->pnext = NULL; pt->pvalue = pval; *p = pt; }
    (void) pthread_rwlock_unlock ( &plist->lk); }
    /* * * * * * * * * * * * * * */ /* Удалить элемент из списка */ /* * * * * * * * * * * * * * */ void g_list_del (g_listP plist, void *pval) { register g_linkP pt; register g_linkP *p;
    if ((errno = pthread_rwlock_trywrlock (&plist->lk)) != 0) { return; }
    for (p = &plist->head; *p != NULL; p = &(*p)->pnext) { if ((*p)->pvalue == pval) { pt = *p; *p = pt->pnext; free (pt); errno = pthread_rwlock_unlock( &plist->lk); return; } }
    /* Пытаемся удалить */ /* несуществующий элемент */ (void) pthread_rwlock_unlock ( &plist->lk); errno = EINVAL; }
    /* * * * * * * * * * * * * */ /* Применить процедуру ко */ /* всем элементам списка */ /* * * * * * * * * * * * */ void g_list_forall_x (g_listP plist, aproc proc, void *extobj) { register g_linkP pt;
    /* Устанавливаем блокировку на запись, */ /* поскольку, возможно, процедура */ /* модифицирует */ /* объекты, ссылки на которые */ /* хранятся в списке */ if ((errno = pthread_rwlock_trywrlock( &plist->lk)) != 0) { return; }
    for (pt = plist->head; pt != NULL; pt = pt->pnext) { proc (extobj, pt->pvalue); }
    (void) pthread_rwlock_unlock( &plist->lk); }
    /* * * * * * * * * * * * * * * * * * */ /* Выбрать подходящий элемент списка */ /* * * * * * * * * * * * * * * * * * */ void *g_list_suchas_x (g_listP plist, afun fun, void *extobj) { register g_linkP pt;
    /* Устанавливаем блокировку на */ /* чтение, так как */ /* считаем, что функция не */ /* модифицирует объекты, */ /* ссылки на которые хранятся */ /* в списке */ if ((errno = pthread_rwlock_tryrdlock( &plist->lk)) != 0) { return NULL; }
    for (pt = plist->head; pt != NULL; pt = pt->pnext) { if (fun (extobj, pt->pvalue)) { (void) pthread_rwlock_unlock( &plist->lk); return (pt->pvalue); } }
    (void) pthread_rwlock_unlock( &plist->lk); return NULL; }
    /* * * * * * * * * * * * * * */ /* Выдать элемент по номеру */ /* * * * * * * * * * * * * * */ void *g_list_get_bynum (g_listP plist, int n) { register g_linkP pt;
    if ((errno = pthread_rwlock_tryrdlock( &plist->lk)) != 0) { return NULL; }
    for (pt = plist->head; n--, pt != NULL; pt = pt->pnext) { if (n == 0) { (void) pthread_rwlock_unlock( &plist->lk); return (pt->pvalue); } }
    /* Пытаемся извлечь несуществующий */ /* компонент */ (void) pthread_rwlock_unlock( &plist->lk); errno = EINVAL;
    return NULL; }
    Листинг 2.29. Исходный текст функций для работы со списками в многопотоковой среде.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * */ /* Тест функций многопотоковой работы */ /* со списками */ /* * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include #include #include #include "g_list.h"
    /* Структура для засыпания на */ /* минимальное время */ static struct timespec nslp = {0, 1};
    /* Структуры для возврата результатов */ /* потоками: */ /* Сколько операций пытались сделать, */ /* сколько из них оказались неудачными */ /* из-за занятости списка */ static struct pt_res { int nops; int nbusy; } ptres [3] = {{0, 0}, {0, 0}, {0, 0}};
    /* * * * * * * * * * * * * * * * * * * * */ /* Этот поток добавляет элементы к списку */ /* * * * * * * * * * * * * * * * * * * * */ static void *start_func_1 (void *plist) { int *p1;
    while (1) { if ((p1 = (int *) malloc ( sizeof (int))) != NULL) { *p1 = rand (); errno = 0; g_list_ins_last (plist, p1); ptres [0].nops++; if (errno == EBUSY) { ptres [0].nbusy++; } } (void) nanosleep (&nslp, NULL); }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * */ /* Процедура для подсчета суммы элементов */ /* списка */ /* * * * * * * * * * * * * * * * * * * * * */ static void proc_sum (void *sum, void *pval) { *((int *) sum) += *((int *) pval); } /* * * * * * * * * * * * * * * * * * * * * */ /* Этот поток подсчитывает сумму элементов */ /* списка */ /* * * * * * * * * * * * * * * * * * * * * */ static void *start_func_2 (void *plist) { int sum;
    while (1) { sum = 0; errno = 0; g_list_forall_x (plist, &proc_sum, &sum); ptres [1].nops++; if (errno == EBUSY) { ptres [1].nbusy++; } (void) nanosleep (&nslp, NULL); }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * */ /* Этот поток удаляет из списка */ /* элементы со случайными номерами */ /* * * * * * * * * * * * * * * * * */ static void *start_func_3 (void *plist) { int *p1;
    while (1) { errno = 0; p1 = (int *) g_list_get_bynum( plist, rand ()); ptres [2].nops++; if (errno == EBUSY) { ptres[2].nbusy++; }
    if (p1 != NULL) { errno = 0; g_list_del (plist, p1); ptres [2].nops++; if (errno == EBUSY) { ptres [2].nbusy++; } free (p1); } (void) nanosleep (&nslp, NULL); }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * */ /* Начальный поток всех запустит, поспит, */ /* потом всех терминирует и выдаст статистику */ /* * * * * * * * * * * * * * * * * * * * * * */ int main (void) { g_listP plist; pthread_t pt1, pt2, pt3;
    g_list_init (plist);
    pthread_create (&pt1, NULL, start_func_1, plist); pthread_create (&pt2, NULL, start_func_2, plist); pthread_create (&pt3, NULL, start_func_3, plist);
    sleep (10);
    pthread_cancel (pt1); pthread_cancel (pt2); pthread_cancel (pt3);
    pthread_join (pt1, NULL); pthread_join (pt2, NULL); pthread_join (pt3, NULL);
    g_list_destroy (plist);
    printf ("Попыток выполнить " "операцию со списком: %d\n", ptres [0].nops + ptres [1].nops + ptres [2].nops); printf ("Число попыток, неудачных" " из-за занятости " "списка: %d\n", ptres [0].nbusy + ptres [1].nbusy + ptres [2].nbusy);
    return (0); }
    Листинг 2.30. Пример программы, использующей функции для работы со списками в многопотоковой среде.
    Закрыть окно




    Попыток выполнить операцию со списком: 1503 Число попыток, неудачных из-за занятости списка: 0
    Листинг 2.31. Возможные результаты работы программы, использующей функции для работы со списками в многопотоковой среде.
    Закрыть окно




    #include
    int pthread_spin_init ( pthread_spinlock_t *lock, int pshared);
    int pthread_spin_destroy ( pthread_spinlock_t *lock);
    Листинг 2.32. Описание функций инициализации и разрушения спин-блокировок.
    Закрыть окно




    #include
    int pthread_spin_lock ( pthread_spinlock_t *lock);
    int pthread_spin_trylock ( pthread_spinlock_t *lock);
    Листинг 2.33. Описание функций установки спин-блокировки.
    Закрыть окно




    #include int pthread_spin_unlock ( pthread_spinlock_t *lock);
    Листинг 2.34. Описание функции снятия спин-блокировки.
    Закрыть окно




    pthread_spin_lock (&ss->lock); /* Восстановим старую маску */ ss->blocked = oldmask; /* Проверим ждущие сигналы */ pending = ss->pending & ~ss->blocked; pthread_spin_unlock (&ss->lock);
    Листинг 2.35. Фрагмент возможной реализации функции sigsuspend().
    Закрыть окно




    #include
    int pthread_barrier_init ( pthread_barrier_t * restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
    int pthread_barrier_destroy ( pthread_barrier_t *barrier);
    Листинг 2.36. Описание функций инициализации и разрушения барьеров.
    Закрыть окно




    #include int pthread_barrier_wait ( pthread_barrier_t *barrier);
    Листинг 2.37. Описание функции синхронизации на барьере.
    Закрыть окно




    #include
    int pthread_barrierattr_init ( pthread_barrierattr_t *attr);
    int pthread_barrierattr_destroy ( pthread_barrierattr_t *attr);
    Листинг 2.38. Описание функций инициализации и разрушения атрибутных объектов барьеров.
    Закрыть окно




    #include
    int pthread_barrierattr_getpshared (const pthread_barrierattr_t *restrict attr, int *restrict pshared);
    int pthread_barrierattr_setpshared (pthread_barrierattr_t *attr, int pshared);
    Листинг 2.39. Описание функций опроса и установки атрибутов барьеров в атрибутных объектах.
    Закрыть окно




    if ((status = pthread_barrier_wait( &barrier)) == PTHREAD_BARRIER_SERIAL_THREAD) { /* Выделенные (обычно – объединительные) */ /* действия. */ /* Выполняются каким-то одним потоком */ /* управления */
    } else { /* Эта часть выполняется всеми */ /* прочими потоками */ /* управления */ if (status != 0) { /* Обработка ошибочной ситуации */ } else { /* Нормальное "невыделенное" */ /* завершение ожидания */ /* на барьере */ } }
    /* Повторная синхронизация – */ /* ожидание завершения выделенных действий */ status = pthread_barrier_wait (&barrier); /* Продолжение параллельной работы */ . . .
    Листинг 2.40. Типичная схема применения функции pthread_barrier_wait().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * */ /* Программа использует барьеры для слияния */ /* результатов */ /* коллективных вычислений ln(2) */ /* * * * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include
    static pthread_barrier_t mbrr;
    /* Ряд для ln(2) будут суммировать */ /* два потока. */ /* Один возьмет на себя положительные */ /* слагаемые, */ /* другой – отрицательные */ static double sums [2] = {0, 0};
    /* * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потоков управления, */ /* участвующих в вычислениях ln(2). */ /* Аргумент ns на самом деле */ /* целочисленный и равен 1 или 2 */ /* * * * * * * * * * * * * * * * * * * * */ static void *start_func (void *ns) { double d = 1; double s = 0; int i;
    /* Вычислим свою часть ряда */ for (i = (int) ns; i <= 100000000; i += 2) { s += d / i; }
    /* Запомним результат в нужном месте */ sums [(int) ns – 1] = s;
    /* Синхронизируемся для получения */ /* общего итога */ if (pthread_barrier_wait (&mbrr) == PTHREAD_BARRIER_SERIAL_THREAD) { sums [0] -= sums [1]; } /* Указатель на итог возвращают оба потока */ return (sums); }
    /* * * * * * * * * * * * * * * * * */ /* Инициализация барьера, */ /* создание и ожидание завершения */ /* потоков управления */ /* * * * * * * * * * * * * * * * * */ int main (void) { pthread_t pt1, pt2; double *pd;
    if ((errno = pthread_barrier_init ( &mbrr, NULL, 2)) != 0) { perror ("PTHREAD_BARRIER_INIT"); return (errno); }
    pthread_create (&pt1, NULL, start_func, (void *) 1); pthread_create (&pt2, NULL, start_func, (void *) 2);
    pthread_join (pt1, (void **) &pd); pthread_join (pt2, (void **) &pd);
    printf ("Коллективно вычисленное" " значение ln(2): %g\n", *pd);
    return (pthread_barrier_destroy( &mbrr)); }
    Листинг 2.41. Пример программы, использующей барьеры.
    Закрыть окно



    Спин-блокировки


    Спин-блокировки представляют собой чрезвычайно низкоуровневое средство синхронизации, предназначенное в первую очередь для применения в многопроцессорной конфигурации с разделяемой памятью. Они обычно реализуются как атомарно устанавливаемое булево значение (истина – блокировка установлена). Аппаратура поддерживает подобные блокировки командами вида "проверить и установить".
    При попытке установить спин-блокировку, если она захвачена кем-то другим, как правило, применяется активное ожидание освобождения, с постоянным опросом в цикле состояния блокировки. Естественно, при этом занимается процессор, так что спин-блокировки следует устанавливать только на очень короткое время и их владелец не должен приостанавливать свое выполнение.
    Для описываемых блокировок стандарт POSIX-2001 не предусматривает установки с ограниченным ожиданием. Это понятно, поскольку накладные расходы по времени на ограничение в типичном случае превысят само время ожидания.
    По сравнению с мьютексами спин-блокировки могут иметь то преимущество, что (активное) ожидание и установка не связаны с переключением контекстов, активизацией планировщика и т.п. Если ожидание оказывается кратким, минимальными оказываются и накладные расходы. Приложение, чувствительное к подобным тонкостям, в каждой конкретной ситуации может выбрать наиболее эффективное средство синхронизации.
    Согласно стандарту POSIX-2001, спин-блокировки обслуживаются следующими группами функций:
  • инициализация и разрушение спин-блокировок: pthread_spin_init(), pthread_spin_destroy() (см. листинг 2.32);
    #include
    int pthread_spin_init ( pthread_spinlock_t *lock, int pshared);
    int pthread_spin_destroy ( pthread_spinlock_t *lock);
    Листинг 2.32. Описание функций инициализации и разрушения спин-блокировок. (html, txt)
  • установка спин-блокировки: pthread_spin_lock(), pthread_spin_trylock() (см. листинг 2.33);
    #include
    int pthread_spin_lock ( pthread_spinlock_t *lock);
    int pthread_spin_trylock ( pthread_spinlock_t *lock);

    Листинг 2.33. Описание функций установки спин-блокировки. (html, txt)

  • снятие спин-блокировки: pthread_spin_unlock() (см. листинг 2.34).

    #include int pthread_spin_unlock ( pthread_spinlock_t *lock);

    Листинг 2.34. Описание функции снятия спин-блокировки. (html, txt)



  • Обратим внимание на то, что применительно к спин-блокировкам было решено не возиться с атрибутными объектами, а единственный поддерживаемый атрибут – признак использования несколькими процессами – задавать при вызове функции pthread_spin_init().

    Поскольку между применением мьютексов и спин-блокировок много общего, мы не будем приводить примеры программ, использующих спин-блокировки. Ограничимся маленьким, слегка модифицированным характерным фрагментом реализации функции sigsuspend() в библиотеке glibc (см. листинг 2.35).

    pthread_spin_lock (&ss->lock); /* Восстановим старую маску */ ss->blocked = oldmask; /* Проверим ждущие сигналы */ pending = ss->pending & ~ss->blocked; pthread_spin_unlock (&ss->lock);

    Листинг 2.35. Фрагмент возможной реализации функции sigsuspend(). (html, txt)

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


    Часы и таймеры


    Стандартом POSIX-2001 предусмотрены средства создания таймеров для процессов, которые будут генерировать уведомления о наступлении заданного момента в виде рассмотренных выше сигналов реального времени. При этом разрешающая способность общесистемных часов реального времени CLOCK_REALTIME и монотонных часов CLOCK_MONOTONIC, а также основанных на них сервисов времени должна быть не хуже, чем заданное конфигурационной константой _POSIX_CLOCKRES_MIN значение 20 мсек (1/50 секунды). Естественно, реализация может обеспечивать более высокую разрешающую способность.
    Если определена конфигурационная константа _POSIX_CPUTIME, значит, реализация дополнительно поддерживает для процессов часы процессорного времени с идентификатором типа clockid_t и значением CLOCK_PROCESS_CPUTIME_ID.
    Если определена константа _POSIX_THREAD_CPUTIME, то аналогичные часы с идентификатором CLOCK_THREAD_CPUTIME_ID поддерживаются и для потоков управления.
    Функция clock_getcpuclockid() позволяет выяснить идентификатор часов процессорного времени для заданного (отличного от вызывающего) процесса, а функция pthread_getcpuclockid() – аналогичный идентификатор для заданного потока управления текущего процесса. В принципе, это позволяет мобильным образом строить системы мониторинга выполнения независимо разработанных приложений, выявляя узкие места и ситуации перерасхода процессорного времени, что очень важно по крайней мере для выполнения требований мягкого реального времени. Правда, возможность создания таймеров на основе полученных идентификаторов объявлена в стандарте POSIX-2001 как зависящая от реализации.
    Для приложений реального времени важна возможность использования не только процессорных, но и монотонных часов. В основном из этих соображений в стандарте POSIX-2001 присутствует расширенный аналог рассмотренной в курсе [1] функции nanosleep() – clock_nanosleep() (см. листинг 3.19).
    #include int clock_nanosleep (clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp);

    Листинг 3.19. Описание функции clock_nanosleep(). (html, txt)

    Аргумент rqtp задает момент времени (по часам с идентификатором clock_id), до наступления которого приостанавливается выполнение текущего потока управления. Если в аргументе flags установлен флаг TIMER_ABSTIME, этот момент трактуется как абсолютный, в противном случае – как относительный.

    Разумеется, "наносон" может быть прерван доставкой обрабатываемого сигнала. Если при этом значение аргумента rmtp отлично от NULL, а момент возобновления выполнения задан как относительный, то в указуемую структуру типа timespec помещается "недоспанное" время.

    Отметим, что функция clock_nanosleep() полностью аналогична nanosleep(), если не устанавливать флаг TIMER_ABSTIME, а в качестве часов использовать общесистемные часы реального времени CLOCK_REALTIME.

    Возможность приостановки выполнения до наступления абсолютного момента времени полезна, например, для периодических процессов, когда, после завершения всех операций текущего периода, нужно заснуть до начала следующего периода. Для nanosleep() в таком случае необходимо узнать текущее время и вычесть его из расчетного времени возобновления выполнения; clock_nanosleep() с флагом TIMER_ABSTIME позволяет сразу задать время возобновления. Более сложное вычисление аргумента функции nanosleep() плохо не столько само по себе, сколько из-за возможного вытеснения потока управления с процессора перед самым вызовом nanosleep(); в результате поток позже заснет и, соответственно, проснется позже запланированного момента времени. (Нетрудно видеть, что это общая проблема относительных таймеров.)

    Понятно также, почему "недоспанное" время возвращается, только если момент возобновления выполнения задан как относительный. Чтобы "доспать" после обработки сигнала, абсолютный момент времени перевычислять не нужно, а вот в качестве относительного как раз пригодится значение, на которое указывает аргумент rmtp.

    Естественно, попытка использования в clock_nanosleep() часов процессорного времени вызывающего потока управления считается ошибкой.



    Следующая программа (см. листинг 3.20) иллюстрирует типичную схему применения функции clock_nanosleep() для организации периодических процессов.

    Листинг 3.20. Пример применения функции clock_nanosleep() для организации периодического процесса. (html, txt)

    На листинге 3.21 показаны возможные результаты выполнения приведенной программы.

    Листинг 3.21. Возможные результаты выполнения программы, использующей функцию clock_nanosleep() для организации периодического процесса. (html, txt)

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

    Для организации периодических процессов можно воспользоваться не только часами, но и таймерами. Чтобы создать для процесса таймер, генерирующий уведомления о наступлении заданного момента в виде сигнала реального времени, следует обратиться к функции timer_create() (см. листинг 3.22).

    #include #include int timer_create (clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid);

    Листинг 3.22. Описание функции timer_create(). (html, txt)

    Таймер создается на основе часов с идентификатором clockid; идентификатор таймера (уникальный в пределах вызывающего процесса) записывается по указателю timerid. Разумеется, сразу после создания таймер оказывается в невзведенном состоянии.

    Аргумент evp, указывающий на структуру типа sigevent, определяет характер уведомлений о срабатывании таймера. Если его значение равно NULL, то в качестве способа уведомления принимается SIGEV_SIGNAL, при срабатывании генерируется сигнал реального времени с подразумеваемым номером и значением, равным идентификатору таймера.

    Если реализация поддерживает часы процессорного времени, то подобные часы для вызывающего процесса (потока управления) могут обслуживать создаваемый таймер.


    Одношаговое порождение процессов


    При стандартизации средств одношагового порождения процессов преследовались две основные цели:
  • возможность использования на аппаратных конфигурациях без устройства управления памятью и без какого-либо специфического оборудования;
  • совместимость с существующими POSIX-стандартами.

  • В качестве дополнительных целей объявлены:
  • возможность эффективной реализации;
  • способность заменить двухшаговое порождение по крайней мере в половине типичных случаев;
  • системы, где имеется только одношаговое порождение процессов, должны быть практически полезны, по крайней мере для приложений реального времени;
  • на системах с двухшаговым порождением процессов должна быть возможна библиотечная реализация одношагового порождения.

  • Еще одна дополнительная цель – простота интерфейса. Семейство exec*() насчитывает шесть членов; для posix_spawn*() хватило двух с единым списком аргументов в духе execve() и небольшими отличиями в трактовке имени файла с образом нового процесса (напоминающими разницу между execve() и execvp()).
    Более точно: для одношагового порождения процессов служат функции posix_spawn() и posix_spawnp() (см. листинг 3.1).
    #include
    int posix_spawn (pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]); int posix_spawnp (pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]);
    Листинг 3.1. Описание функций одношагового порождения процессов. (html, txt)
    Аргументами, отличающими posix_spawn() и posix_spawnp() от функций семейства exec*() являются pid, file_actions, attrp. Опишем их назначение.
    По указателю pid (если он отличен от NULL) возвращается идентификатор успешно порожденного процесса.
    Аргументы file_actions и attrp отвечают за контроль сущностей, наследуемых при одношаговом порождении процессов.
    Чтобы достичь перечисленных выше целей, функции posix_spawn() и posix_spawnp() контролируют шесть видов наследуемых сущностей:

  • файловые дескрипторы;
  • идентификатор группы процессов;
  • идентификаторы пользователя и группы процесса;
  • параметры планирования;
  • маску сигналов;
  • способ обработки сигналов, игнорируемых родительским процессом.


  • Контроль файловых дескрипторов, в первую очередь стандартных ввода, вывода и протокола, который осуществляется с помощью аргумента file_actions, позволяет порожденному процессу получить доступ к потокам данных, открытым или даже порожденным родительским процессом, без встраивания в исходный текст или передачи в качестве аргументов main() конкретных имен файлов или номеров дескрипторов.

    Как правило, все открытые дескрипторы родительского процесса остаются таковыми и в порожденном, за исключением тех, у которых установлен флаг FD_CLOEXEC. Кроме того, если значение аргумента file_actions отлично от NULL, до обработки флагов FD_CLOEXEC принимается во внимание указуемый объект типа posix_spawn_file_actions_t, который содержит действия по закрытию, открытию и/или дублированию файловых дескрипторов. Для формирования объектов типа posix_spawn_file_actions_t служат функции posix_spawn_file_actions_init(), posix_spawn_file_actions_addclose(), posix_spawn_file_actions_addopen() и posix_spawn_file_actions_adddup2(); функция posix_spawn_file_actions_destroy() ликвидирует подобный объект (см. листинг 3.2).

    #include

    int posix_spawn_file_actions_init (posix_spawn_file_actions_t *file_actions);

    int posix_spawn_file_actions_destroy (posix_spawn_file_actions_t *file_actions);

    int posix_spawn_file_actions_addclose (posix_spawn_file_actions_t *file_actions, int fildes);

    int posix_spawn_file_actions_addopen ( posix_spawn_file_actions_t *restrict file_actions, int fildes, const char *restrict path, int oflag, mode_t mode);

    int posix_spawn_file_actions_adddup2 (posix_spawn_file_actions_t *file_actions, int fildes, int newfildes);

    Листинг 3.2.


    Описание функций формирования и ликвидации объектов типа posix_spawn_file_actions_t. (html, txt)

    Функция posix_spawn_file_actions_addclose() добавляет дескриптор fildes к числу закрываемых перед началом выполнения порожденного процесса. Функция posix_spawn_file_actions_addopen() предписывает открыть дескриптор fildes, как если бы был выполнен вызов open (path, oflag, mode). Наконец, функция posix_spawn_file_actions_adddup2() специфицирует дублирование дескриптора fildes в newfildes (close (newfildes); fcntl (fildes, F_DUPFD, newfildes)). Таким образом, функции posix_spawn() и posix_spawnp(), отправляясь от набора открытых дескрипторов родительского процесса, выполняют действия, заданные аргументом file_actions, и получают набор дескрипторов, открытых в порождаемом процессе, то есть родительский процесс берет на себя согласование по файловым дескрипторам с независимо созданным новым образом процесса.

    Отметим, что с помощью функции posix_spawn_file_actions_addopen() удобно перенаправлять ввод/вывод порожденного процесса.

    За контроль других сущностей, наследуемых при одношаговом порождении процессов, отвечает атрибутный объект , заданный аргументом attrp. Для формирования и опроса подобных объектов служат функции, показанные на листингах 3.3, 3.4 и 3.5.

    #include

    int posix_spawnattr_init ( posix_spawnattr_t *attr);

    int posix_spawnattr_destroy ( posix_spawnattr_t *attr);

    int posix_spawnattr_getflags ( const posix_spawnattr_t *restrict attr, short *restrict flags);

    int posix_spawnattr_setflags ( posix_spawnattr_t *attr, short flags);

    int posix_spawnattr_getpgroup ( const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);

    int posix_spawnattr_setpgroup ( posix_spawnattr_t *attr, pid_t pgroup);

    Листинг 3.3. Описание функций формирования и опроса атрибутных объектов порождаемых процессов. (html, txt)

    #include #include

    int posix_spawnattr_getschedparam ( const posix_spawnattr_t *restrict attr, struct sched_param *restrict schedparam);



    int posix_spawnattr_setschedparam ( posix_spawnattr_t * restrict attr, const struct sched_param *restrict schedparam);

    int posix_spawnattr_getschedpolicy ( const posix_spawnattr_t *restrict attr, int *restrict schedpolicy);

    int posix_spawnattr_setschedpolicy ( posix_spawnattr_t *attr, int schedpolicy);

    Листинг 3.4. Описание функций опроса и установки параметров и политики планирования в атрибутных объектах порождаемых процессов. (html, txt)

    #include #include int posix_spawnattr_getsigdefault ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);

    int posix_spawnattr_setsigdefault ( posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault);

    int posix_spawnattr_getsigmask ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);

    int posix_spawnattr_setsigmask ( posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);

    Листинг 3.5. Описание функций опроса и установки подразумеваемой обработки и маски сигналов в атрибутных объектах порождаемых процессов. (html, txt)



    POSIX_SPAWN_SETSIGMASK

    Установить начальную маску сигналов по атрибутному объекту.

    POSIX_SPAWN_SETSCHEDPARAM

    Установить параметры планирования по атрибутному объекту.

    POSIX_SPAWN_SETSCHEDULER

    Установить политику и параметры планирования по атрибутному объекту (независимо от состояния флага POSIX_SPAWN_SETSCHEDPARAM).

    Если значение аргумента attrp равно NULL, используются подразумеваемые значения атрибутов.

    Все характеристики нового процесса, на которые не воздействуют аргументы attrp и file_actions, устанавливаются так, как если бы применялось двухшаговое порождение fork()/exec(). Будут ли при одношаговом порождении выполняться обработчики разветвления процессов, зарегистрированные с помощью функции atfork(), зависит от реализации.

    При библиотечной реализации функций posix_spawn() и posix_spawnp() некоторые ошибки могут быть выявлены только после порождения процесса. В таком случае родительский процесс может узнать о них, анализируя с помощью макросов WIFEXITED, WEXITSTATUS (см. курс [1]) значение stat_val, возвращаемое функциями wait() и/или waitpid(). Предлагается, чтобы статус "аварийного завершения до начала реального выполнения" равнялся 127. Это не очень естественно и удобно, но иного выхода не видно.

    В целом средства одношагового порождения процессов позволяют достичь сформулированных выше целей. Они просты, но обладают достаточной выразительной силой и, несомненно, способны заменить двухшаговое порождение по крайней мере в половине типичных случаев.

    Чтобы лучше понять семантику одношагового порождения процессов, приведем фрагмент возможной библиотечной реализации функции posix_spawn(), представленной в четвертой, информационной части стандарта POSIX-2001 (см. листинги 3.6 и 3.7).

    typedef struct { short posix_attr_flags; pid_t posix_attr_pgroup; sigset_t posix_attr_sigmask; sigset_t posix_attr_sigdefault; int posix_attr_schedpolicy; struct sched_param posix_attr_schedparam; } posix_spawnattr_t;

    typedef char *posix_spawn_file_actions_t;

    int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []);



    Листинг 3.6. Фрагмент возможного содержимого файла spawn.h.

    int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []) {

    /* Создадим новый процесс */ if ((*pid = fork()) == (pid_t) 0) { /* Порожденный процесс */ /* Позаботимся о группе процессов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETPGROUP) { /* Изменим унаследованную группу */ if (setpgid (0, attrp->posix_attr_pgroup) != 0) { /* Неудача */ exit (127); } }

    /* Позаботимся о действующих идентификаторах */ /* пользователя и группы */ if (attrp->posix_attr_flags & POSIX_SPAWN_RESETIDS) { /* В данном случае неудачи быть не может */ setuid (getuid ()); setgid (getgid ()); }

    /* Позаботимся о подразумеваемом способе */ /* обработки сигналов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) { struct sigaction deflt; sigset_t all_signals; int s;

    deflt.sa_handler = SIG_DFL; deflt.sa_flags = 0;

    sigfillset (&all_signals);

    /* Цикл по всем сигналам */ for (s = 0; sigismember (&all_signals, s); s++) { if (sigismember (&attrp->posix_attr_sigdefault, s)) { if (sigaction (s, &deflt, NULL) == -1) { exit (127); } } } }

    /* Проконтролируем остальные атрибуты */ /* . . . */

    /* Подменим образ процесса */ execve (path, argv, envp); exit (127); } else { /* Родительский (вызывающий) процесс */ if (*pid == (pid_t) (-1)) return errno; return 0; } }

    Листинг 3.7. Фрагмент возможной библиотечной реализации функции posix_spawn().

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



    /* Запуск процесса с произвольным идентификатором */ /* пользователя */

    uid_t old_uid; uid_t new_uid = ...;

    old_uid = getuid (); setuid (new_uid);

    posix_spawn (...);

    setuid (old_uid);

    Листинг 3.8. Пример установки характеристики порожденного процесса, не принадлежащей к числу контролируемых стандартными средствами.

    На листинге 3.9 показан пример перенаправления стандартных ввода и вывода порождаемого процесса с помощью формирования и использования объекта типа posix_spawn_file_actions_t. В данном случае стандартный вывод (дескриптор 1) направляется в файл outfile, а стандартный ввод (дескриптор 0) отождествляется с открытым ранее дескриптором socket_pair [1]. Попутно обеспечивается закрытие в новом процессе дескрипторов socket_pair [0] и socket_pair [1].

    posix_spawn_file_actions_t file_actions;

    posix_spawn_file_actions_init ( &file_actions); posix_spawn_file_actions_addopen ( &file_actions, 1, "outfile", ...); posix_spawn_file_actions_adddup2 ( &file_actions, socket_pair [1], 0); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [0]); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [1]);

    posix_spawn (..., &file_actions, ...); posix_spawn_file_actions_destroy ( &file_actions);

    Листинг 3.9. Пример перенаправления стандартных ввода и вывода порождаемого процесса.

    Любопытно сопоставить реальные накладные расходы на одношаговое и двухшаговое порождение процессов. Программа, порождающая с помощью функции posix_spawn() практически пустые процессы, показана на листинге 3.10. На листинге 3.11 приведены данные о времени ее работы, полученные с помощью команды time -p. Полученные результаты практически не отличаются от измеренного ранее времени двухшагового порождения. Это означает, что в используемой нами версии ОС Linux функции posix_spawn() и posix_spawnp() реализованы как библиотечные, а их применение выигрыша в эффективности в данном случае не дает (но по соображениям "мобильной потенциальной эффективности" их все равно есть смысл использовать).

    #include #include #include #include

    #define N 10000

    int main (void) { char *s_argv [] = {"dummy", NULL}; char *s_env [] = {NULL};

    int i;

    for (i = 0; i < N; i++) { if ((errno = posix_spawn ( NULL, "./dummy", NULL, NULL, s_argv, s_env)) != 0) { perror ("POSIX_SPAWN"); return (errno); } (void) wait (NULL); }

    return 0; }

    Листинг 3.10. Пример программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().

    real 34.37 user 12.01 sys 22.07

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


    Основные идеи, понятия и объекты


    В рамках решаемой стандартом POSIX общей задачи обеспечения мобильности приложений на уровне исходных текстов можно выделить подзадачу мобильного программирования систем, к которым предъявляются требования работы в реальном масштабе времени. (Разумеется, стандарт не может и не должен преодолевать зависимость числовых параметров реализации от свойств аппаратной платформы.)
    В качестве средств для решения этой подзадачи в стандарте POSIX-2001 присутствует целый ряд идей, понятий, объектов и функций, которые можно сгруппировать в следующие предметные области:
  • одношаговое порождение процессов;
  • сигналы реального времени;
  • часы и таймеры;
  • средства межпроцессного взаимодействия;
  • объекты в памяти;
  • удержание процессов в памяти;
  • приоритетное планирование;
  • асинхронный ввод/вывод;
  • рекомендательные интерфейсы.

  • Одношаговое порождение процессов (в противоположность традиционной для ОС Unix двухшаговой модели fork()/exec()) основано на применении функций posix_spawn() и posix_spawnp(). Оно в любом случае менее тяжеловесно (хотя и тяжеловеснее порождения потоков управления), но особенно актуально для аппаратных конфигураций, в которых отсутствует поддержка виртуальной памяти и, как следствие, реализация функции fork() проблематична.
    Под сигналами реального времени понимается не нарушающее совместимости расширение общего механизма сигналов, повышающее детерминированность за счет постановки сигналов, асинхронно доставленных приложению, в очередь.
    Основные понятия, ассоциированные с часами и таймерами, были рассмотрены в курсе [1]. В настоящем курсе мы сосредоточимся на необязательных элементах стандарта POSIX-2001, специфичных для реального времени.
    В качестве средств межпроцессного взаимодействия в реальном времени в стандарт POSIX-2001 включены очереди сообщений, семафоры и разделяемые сегменты памяти (см. также курс [1], где детально анализируется другой класс средств межпроцессного взаимодействия с аналогичными названиями).
    Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти.
    Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.

    Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти. Стандартом POSIX-2001 предусмотрено три вида таких объектов:

  • файлы, отображенные в память;
  • объекты в разделяемой памяти (они же – упомянутые выше как средства межпроцессного взаимодействия разделяемые сегменты памяти);
  • объекты в типизированной памяти.


  • С помощью функции mmap() строится отображение объекта в памяти на группу страниц из адресного пространства вызывающего процесса, так что доступ к адресам из заданного диапазона выливается в обращение к ассоциированному объекту (например, к файлу).

    Объекты в разделяемой памяти характеризуются тем, что их можно параллельно отобразить в адресное пространство нескольких процессов.

    Объект в типизированной памяти представляет собой комбинацию типизированных пула и порта памяти. Типизированный пул – это область памяти с однородными операционными характеристиками. Пулы могут быть вложенными. Аппаратный доступ к содержимому пула осуществляется через порт. Объект в типизированной памяти идентифицируется именем из пространства имен типизированной памяти.

    Отметим, что объект в памяти не обязательно должен быть резидентным в физической памяти.

    Согласно стандарту POSIX-2001, приоритет – это неотрицательная целочисленная величина, ассоциированная с процессом или потоком управления. Допустимый диапазон приоритетов определяется применяемой политикой планирования. Большие величины представляют более высокие приоритеты.

    Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.

    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.

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



    Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.

    Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти. Стандартом POSIX-2001 предусмотрено три вида таких объектов:

  • файлы, отображенные в память;
  • объекты в разделяемой памяти (они же – упомянутые выше как средства межпроцессного взаимодействия разделяемые сегменты памяти);
  • объекты в типизированной памяти.


  • С помощью функции mmap() строится отображение объекта в памяти на группу страниц из адресного пространства вызывающего процесса, так что доступ к адресам из заданного диапазона выливается в обращение к ассоциированному объекту (например, к файлу).

    Объекты в разделяемой памяти характеризуются тем, что их можно параллельно отобразить в адресное пространство нескольких процессов.

    Объект в типизированной памяти представляет собой комбинацию типизированных пула и порта памяти. Типизированный пул – это область памяти с однородными операционными характеристиками. Пулы могут быть вложенными. Аппаратный доступ к содержимому пула осуществляется через порт. Объект в типизированной памяти идентифицируется именем из пространства имен типизированной памяти.

    Отметим, что объект в памяти не обязательно должен быть резидентным в физической памяти.

    Согласно стандарту POSIX-2001, приоритет – это неотрицательная целочисленная величина, ассоциированная с процессом или потоком управления. Допустимый диапазон приоритетов определяется применяемой политикой планирования. Большие величины представляют более высокие приоритеты.

    Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.

    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.

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


    restrict pid, const char


    #include
    int posix_spawn (pid_t * restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]); int posix_spawnp (pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]);
    Листинг 3.1. Описание функций одношагового порождения процессов.
    Закрыть окно




    #include
    int posix_spawn_file_actions_init (posix_spawn_file_actions_t *file_actions);
    int posix_spawn_file_actions_destroy (posix_spawn_file_actions_t *file_actions);
    int posix_spawn_file_actions_addclose (posix_spawn_file_actions_t *file_actions, int fildes);
    int posix_spawn_file_actions_addopen ( posix_spawn_file_actions_t * restrict file_actions, int fildes, const char *restrict path, int oflag, mode_t mode);
    int posix_spawn_file_actions_adddup2 (posix_spawn_file_actions_t *file_actions, int fildes, int newfildes);
    Листинг 3.2. Описание функций формирования и ликвидации объектов типа posix_spawn_file_actions_t.
    Закрыть окно




    #include
    int posix_spawnattr_init ( posix_spawnattr_t *attr);
    int posix_spawnattr_destroy ( posix_spawnattr_t *attr);
    int posix_spawnattr_getflags ( const posix_spawnattr_t *restrict attr, short *restrict flags);
    int posix_spawnattr_setflags ( posix_spawnattr_t *attr, short flags);
    int posix_spawnattr_getpgroup ( const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);
    int posix_spawnattr_setpgroup ( posix_spawnattr_t *attr, pid_t pgroup);
    Листинг 3.3. Описание функций формирования и опроса атрибутных объектов порождаемых процессов.
    Закрыть окно




    #include #include
    int posix_spawnattr_getschedparam ( const posix_spawnattr_t *restrict attr, struct sched_param *restrict schedparam);
    int posix_spawnattr_setschedparam ( posix_spawnattr_t * restrict attr, const struct sched_param *restrict schedparam);
    int posix_spawnattr_getschedpolicy ( const posix_spawnattr_t *restrict attr, int *restrict schedpolicy);
    int posix_spawnattr_setschedpolicy ( posix_spawnattr_t *attr, int schedpolicy);
    Листинг 3.4. Описание функций опроса и установки параметров и политики планирования в атрибутных объектах порождаемых процессов.
    Закрыть окно




    #include #include int posix_spawnattr_getsigdefault ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);
    int posix_spawnattr_setsigdefault ( posix_spawnattr_t * restrict attr, const sigset_t *restrict sigdefault);
    int posix_spawnattr_getsigmask ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);
    int posix_spawnattr_setsigmask ( posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);
    Листинг 3.5. Описание функций опроса и установки подразумеваемой обработки и маски сигналов в атрибутных объектах порождаемых процессов.
    Закрыть окно




    typedef struct { short posix_attr_flags; pid_t posix_attr_pgroup; sigset_t posix_attr_sigmask; sigset_t posix_attr_sigdefault; int posix_attr_schedpolicy; struct sched_param posix_attr_schedparam; } posix_spawnattr_t;
    typedef char *posix_spawn_file_actions_t;
    int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []);
    Листинг 3.6. Фрагмент возможного содержимого файла spawn.h.
    Закрыть окно




    int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []) {
    /* Создадим новый процесс */ if ((*pid = fork()) == (pid_t) 0) { /* Порожденный процесс */ /* Позаботимся о группе процессов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETPGROUP) { /* Изменим унаследованную группу */ if (setpgid (0, attrp->posix_attr_pgroup) != 0) { /* Неудача */ exit (127); } }
    /* Позаботимся о действующих идентификаторах */ /* пользователя и группы */ if (attrp->posix_attr_flags & POSIX_SPAWN_RESETIDS) { /* В данном случае неудачи быть не может */ setuid (getuid ()); setgid (getgid ()); }
    /* Позаботимся о подразумеваемом способе */ /* обработки сигналов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) { struct sigaction deflt; sigset_t all_signals; int s;
    deflt.sa_handler = SIG_DFL; deflt.sa_flags = 0;
    sigfillset (&all_signals);
    /* Цикл по всем сигналам */ for (s = 0; sigismember (&all_signals, s); s++) { if (sigismember (&attrp->posix_attr_sigdefault, s)) { if (sigaction (s, &deflt, NULL) == -1) { exit (127); } } } }
    /* Проконтролируем остальные атрибуты */ /* . . . */
    /* Подменим образ процесса */ execve (path, argv, envp); exit (127); } else { /* Родительский (вызывающий) процесс */ if (*pid == (pid_t) (-1)) return errno; return 0; } }
    Листинг 3.7. Фрагмент возможной библиотечной реализации функции posix_spawn().
    Закрыть окно




    /* Запуск процесса с произвольным идентификатором */ /* пользователя */
    uid_t old_uid; uid_t new_uid = ...;
    old_uid = getuid (); setuid (new_uid);
    posix_spawn (...);
    setuid (old_uid);
    Листинг 3.8. Пример установки характеристики порожденного процесса, не принадлежащей к числу контролируемых стандартными средствами.
    Закрыть окно




    posix_spawn_file_actions_t file_actions;
    posix_spawn_file_actions_init ( &file_actions); posix_spawn_file_actions_addopen ( & file_actions, 1, "outfile", ...); posix_spawn_file_actions_adddup2 ( &file_actions, socket_pair [1], 0); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [0]); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [1]);
    posix_spawn (..., &file_actions, ...); posix_spawn_file_actions_destroy ( &file_actions);
    Листинг 3.9. Пример перенаправления стандартных ввода и вывода порождаемого процесса.
    Закрыть окно




    #include #include #include #include
    #define N 10000
    int main (void) { char *s_argv [] = {"dummy", NULL}; char *s_env [] = {NULL};
    int i;
    for (i = 0; i < N; i++) { if ((errno = posix_spawn ( NULL, "./dummy", NULL, NULL, s_argv, s_env)) != 0) { perror ("POSIX_SPAWN"); return (errno); } (void) wait (NULL); }
    return 0; }
    Листинг 3.10. Пример программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().
    Закрыть окно




    real 34.37 user 12.01 sys 22.07
    Листинг 3.11. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().
    Закрыть окно




    #include int sigqueue ( pid_t pid, int signo, const union sigval value);
    Листинг 3.12. Описание функции sigqueue().
    Закрыть окно




    #include
    int sigwaitinfo ( const sigset_t *restrict set, siginfo_t *restrict info);
    int sigtimedwait ( const sigset_t *restrict set, siginfo_t * restrict info, const struct timespec *restrict timeout);
    Листинг 3.13. Описание функций sigwaitinfo() и sigtimedwait().
    Закрыть окно




    #include int sigaltstack (const stack_t * restrict ss, stack_t *restrict oss);
    Листинг 3.14. Описание функции sigaltstack().
    Закрыть окно




    #include #include #include . . . stack_t sighstk; . . .
    if ((sighstk.ss_sp = malloc( SIGSTKSZ)) == NULL) { perror ("malloc (SIGSTKSZ)"); /* Аварийное завершение */ } sighstk.ss_size = SIGSTKSZ; sighstk.ss_flags = 0; if (sigaltstack (&sighstk, (stack_t *) NULL) != 0) { perror ("SIGALTSTACK"); . . . } . . .
    Листинг 3.15. Типичная схема определения альтернативного стека.
    Закрыть окно




    #include
    int sigsetjmp ( sigjmp_buf env, int savemask);
    void siglongjmp (sigjmp_buf env, int val);
    Листинг 3.16. Описание функций sigsetjmp() и siglongjmp().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов */ /* с использованием сигналов реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include
    /* Число обедающих философов */ #define QPH 5
    /* Время (в секундах) на обед */ #define FO 15
    /* Длительность еды */ #define ernd (rand () % 3 + 1)
    /* Длительность разговора */ #define trnd (rand () % 5 + 1)
    /* Номер сигнала, используемого для захвата и освобождения вилок */ #define SIG_FORK SIGRTMIN
    /* Номер сигнала, используемого для информирования философа */ #define SIG_PHIL SIGINT
    static pthread_t pt_id [QPH]; /* Массив идентификаторов */ /* потоков - философов */ static int fork_busy [QPH] = {0, }; /* Состояние вилок */ static int phil_req [QPH] = {0, }; /* Невыполненные */ /* заявки на вилки */ static sigjmp_buf phil_env [QPH]; /* Массив буферов для */ /* нелокальных переходов */
    static pid_t pid_wt; /* Идентификатор процесса,*/ /* контролирующего вилки */
    static pthread_key_t phil_key; /* Ключ индивидуальных */ /* данных потоков-философов */
    /* * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIG_PHIL */ /* * * * * * * * * * * * * * * * * * * */ static void phil_eat (int signo) { int no; /* Номер философа, которому достался сигнал */
    no = (int) pthread_getspecific (phil_key); if ((no > 0) && (no <= QPH)) { siglongjmp (phil_env [no – 1], signo); } }
    /* * * * * * * * * * * * * * * * * * * * * * */ /* Попытка выполнить заявку на захват вилок */ /* от философа номер no, если она есть */ /* * * * * * * * * * * * * * * * * * * * * * */ static void fork_lock (int no) { if (phil_req [no – 1] != 0) { /* Заявка есть. */ /* Вилки свободны? */ if ((fork_busy [no – 1] == 0) && (fork_busy [no % QPH] == 0)) { /* Выполним заявку */ fork_busy [no – 1] = fork_busy [no % QPH] = 1; phil_req [no – 1] = 0; (void) pthread_kill (pt_id [no – 1], SIG_PHIL); } } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, обслуживающего заявки на */ /* захват и освобождение вилок. */ /* Заявка передается в виде значения, ассоциированного */ /* с сигналом signo. */ /* Значение no > 0 запрашивает захват вилок для философа */ /* с номером no, no < 0 – освобождение вилок философа -no */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_waiter (void *signo) { siginfo_t sinfo; /* Структура для получения данных */ /* о сигнале */ int no; /* Номер философа, приславшего заявку */ sigset_t s_sgno; /* Маска ожидаемых сигналов */
    pid_wt = getpid ();
    /* Сформируем маску ожидаемых сигналов */ if ((sigemptyset (&s_sgno) != 0) || (sigaddset (&s_sgno, (int) signo) != 0)) { perror ("SIGEMPTYSET/SIGADDSET"); return (NULL); }
    while (1) { if (sigwaitinfo (&s_sgno, &sinfo) != (int) signo) { return (NULL); } else { /* Поступила заявка. */ /* Посмотрим, что от нас хотят */ if ((no = sinfo.si_value.sival_int) > 0) { /* Заявка на захват вилок. */ /* Запомним ее ... */ phil_req [no – 1] = 1; /* ... и попробуем выполнить */ fork_lock (no); } else { /* Освобождение вилок */ no = -no; fork_busy [no – 1] = fork_busy [no % QPH] = 0; /* Попробуем выполнить заявки от соседей */ fork_lock (no % QPH + 1); fork_lock (no == 1 ? QPH : (no – 1)); } } /* Другие сигналы нас не интересуют */ } /* while (1) */ }
    /* * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока-философа. */ /* Аргумент – номер философа */ /* * * * * * * * * * * * * * * * * * */ void *start_phil (void *no) { int fo; /* Время до конца обеда */ int t; /* Время очередного отрезка еды или беседы */ time_t tbe; /* Время, когда философу понадобились вилки */ union sigval sval; /* Значение посылаемого сигнала: */ /* (int) no – заказ вилок */ /* -(int) no – освобождение вилок */
    /* Запомним значение аргумента в качестве */ /* индивидуальных данных потока */ (void) pthread_setspecific (phil_key, no);
    /* Подготовка к обеду */ fo = FO;
    if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) { /* Сюда придем после нелокального перехода */ /* из обработчика сигнала SIG_PHIL. */ /* Философ просил вилки и получил их */ printf ("Философ %d ест\n", (int) no); t = ernd; sleep (t); /* Нужно вычесть времена еды и ожидания вилок */ fo -= (int) (time ((time_t *) NULL) – tbe) + t; /* Отдает вилки */ sval.sival_int = -((int) no); (void) sigqueue (pid_wt, SIG_FORK, sval); }
    while (fo > 0) { printf ("Философ %d беседует\n", (int) no); t = trnd; sleep (t); fo -= t;
    /* Пытается взять вилки */ tbe = time ((time_t *) NULL); sval.sival_int = (int) no; (void) sigqueue (pid_wt, SIG_FORK, sval);
    /* Пока вилки заняты, приходится беседовать... */ printf ("Философ %d беседует в ожидании вилок\n", (int) no); sleep (fo); fo = 0; } /* while */ printf ("Философ %d закончил обед\n", (int) no);
    return (NULL); }
    /* * * * * * * * * * */ /* Организация обеда */ /* * * * * * * * * * */ int main (void) { int no; /* Номер философа */ struct sigaction sact; /* Структура для обработки */ /* обычных сигналов */ pthread_t pt_wt; /* Идентификатор потока, */ /* управляющего вилками */
    /* Блокируем сигнал SIG_FORK */ if ((sigemptyset (&sact.sa_mask) == 0) && (sigaddset (&sact.sa_mask, SIG_FORK) == 0)) { (void) pthread_sigmask (SIG_BLOCK, &sact.sa_mask, (sigset_t *) NULL); }
    /* Установим для сигнала SIG_FORK флаг SA_SIGINFO */ sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = (void (*) (int, siginfo_t *, void *)) SIG_DFL; (void) sigaction (SIG_FORK, &sact, (struct sigaction *) NULL);
    /* Установим реакцию на сигнал SIG_PHIL */ sact.sa_handler = phil_eat; sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIG_PHIL, &sact, (struct sigaction *) NULL);
    /* Создадим поток, захватывающий и освобождающий вилки */ if ((errno = pthread_create (&pt_wt, NULL, start_waiter, (void *) SIG_FORK)) != 0) { perror ("PTHREAD_CREATE-1"); return (errno); }
    /* Создадим ключ индивидуальных данных */ if ((errno = pthread_key_create (&phil_key, (void (*) (void *)) NULL)) != 0) { perror ("PTHREAD_KEY_CREATE"); return (errno); }
    /* Все – к столу */ for (no = 1; no <= QPH; no++) { if ((errno = pthread_create (&pt_id [no – 1], NULL, start_phil, (void *) no)) != 0) { perror ("PTHREAD_CREATE"); return (no); } }
    /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) pthread_join (pt_id [no – 1], NULL); }
    (void) pthread_key_delete (phil_key);
    /* Завершим поток, контролирующий вилки */ (void) pthread_cancel (pt_wt); (void) pthread_join (pt_wt, NULL);
    return 0; }
    Листинг 3.17. Пример реализации обеда философов с использованием сигналов реального времени.
    Закрыть окно




    Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 4 закончил обед Философ 1 беседует в ожидании вилок Философ 5 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 3 закончил обед Философ 1 закончил обед Философ 5 закончил обед Философ 2 закончил обед
    Листинг 3.18. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени.
    Закрыть окно




    #include int clock_nanosleep ( clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp);
    Листинг 3.19. Описание функции clock_nanosleep().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * */ /* Организация периодических процессов */ /* с помощью функции clock_nanosleep() */ /* * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include
    /* * * * * * * * * * * * * * * * * * * * */ /* Сложение двух структур типа timespec */ /* * * * * * * * * * * * * * * * * * * * */ static void tmspc_add (struct timespec *a1, struct timespec *a2, struct timespec *res) { res->tv_sec = a1->tv_sec + a2->tv_sec + (a1->tv_nsec + a2->tv_nsec) / 1000000000; res->tv_nsec = (a1->tv_nsec + a2->tv_nsec) % 1000000000; }
    /* * * * * * * * * * * * * * * * * * * */ /* Организация периодического процесса */ /* * * * * * * * * * * * * * * * * * * */ int main (void) { struct timespec t_bp; /* Время начала очередного */ / *периода выполнения */ struct timespec prd = {1, 250000000}; /* Период */ /* выполнения: 1.25 сек */ clockid_t clk_id = CLOCK_REALTIME; /* Идентификатор */ /* используемых часов */ struct timespec t_tmp; int i; /* Запомним время начала выполнения */ (void) clock_gettime (clk_id, &t_bp); printf ("Начало выполнения: %ld сек %ld нсек\n", t_bp.tv_sec, t_bp.tv_nsec);
    for (i = 0; i < 8; i++) { /* Содержательные действия. */ /* Предполагается, что они укладываются в период */ sleep (1);
    /* Доспим до конца периода */ tmspc_add (&t_bp, &prd, &t_bp); (void) clock_nanosleep (clk_id, TIMER_ABSTIME, &t_bp, NULL);
    (void) clock_gettime (clk_id, &t_tmp); printf ("Конец периода: %ld сек %ld нсек\n", t_tmp.tv_sec, t_tmp.tv_nsec); }
    return 0; }
    Листинг 3.20. Пример применения функции clock_nanosleep() для организации периодического процесса.
    Закрыть окно




    Начало выполнения: 1079080828 сек 194254000 нсек Конец периода: 1079080829 сек 460021000 нсек Конец периода: 1079080830 сек 710023000 нсек Конец периода: 1079080831 сек 960020000 нсек Конец периода: 1079080833 сек 210021000 нсек Конец периода: 1079080834 сек 460023000 нсек Конец периода: 1079080835 сек 710021000 нсек Конец периода: 1079080836 сек 960094000 нсек Конец периода: 1079080838 сек 210022000 нсек
    Листинг 3.21. Возможные результаты выполнения программы, использующей функцию clock_nanosleep() для организации периодического процесса.
    Закрыть окно




    #include #include int timer_create ( clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid);
    Листинг 3.22. Описание функции timer_create().
    Закрыть окно




    #include int timer_delete (timer_t timerid);
    Листинг 3.23. Описание функции timer_delete().
    Закрыть окно




    #include
    int timer_gettime (timer_t timerid, struct itimerspec *value);
    int timer_settime ( timer_t timerid, int flags, const struct itimerspec *restrict value, struct itimerspec *restrict ovalue);
    int timer_getoverrun (timer_t timerid);
    Листинг 3.24. Описание функций timer_gettime(), timer_settime() и timer_getoverrun().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов */ /* с использованием сигналов реального времени */ /* и таймера для контроля длительности обеда */ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include
    /* Число обедающих философов */ #define QPH 5
    /* Время (в секундах) на обед */ #define FO 15
    /* Длительность еды */ #define ernd (rand () % 3 + 1)
    /* Длительность разговора */ #define trnd (rand () % 5 + 1)
    /* Номер сигнала, используемого для захвата */ /* и освобождения вилок */ #define SIG_FORK SIGRTMIN
    /* Номер сигнала, используемого таймером */ /* для окончания обеда */ #define SIG_DEND (SIGRTMIN + 1) /* Номер сигнала, используемого */ /* для информирования философа */ #define SIG_PHIL SIGINT
    static pthread_t pt_id [QPH]; /* Массив идентификаторов */ /* потоков-философов */ static int fork_busy [QPH] = {0, }; /* Состояние вилок */ static int phil_req [QPH] = {0, }; /* Невыполненные */ /* заявки на вилки */ static sigjmp_buf phil_env [QPH]; /* Массив буферов для */ /* нелокальных переходов */
    static pid_t pid_wt; /* Идентификатор процесса, */ /* контролирующего вилки */
    static pthread_key_t phil_key; /* Ключ индивидуальных */ /* данных потоков-философов */
    /* * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIG_PHIL */ /* * * * * * * * * * * * * * * * * * */ static void phil_eat (int signo) { int no; /* Номер философа, которому достался сигнал */
    no = (int) pthread_getspecific (phil_key); if ((no > 0) && (no <= QPH)) { siglongjmp (phil_env [no – 1], signo); } }
    /* * * * * * * * * * * * * * * * * * */ /* Деструктор индивидуальных данных */ /* потоков-философов */ /* * * * * * * * * * * * * * * * * * */ static void phil_destructor (void *no) { printf ("Философ %d закончил обед\n", (int) no); }
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая при срабатывании таймера, */ /* контролирующего длительность обеда */ /* * * * * * * * * * * * * * * * * * * * * * ** */ static void end_ph_dinner (union sigval dummy) { int i;
    for (i = 0; i < QPH; i++) { (void) pthread_cancel (pt_id [i]); } }
    /* * * * * * * * * * * * * * * * * * * * * */ /* Попытка выполнить заявку на захват вилок */ /* от философа номер no, если она есть */ /* * * * * * * * * * * * * * * * * * * * * */ static void fork_lock (int no) { if (phil_req [no – 1] != 0) { /* Заявка есть. */ /* Вилки свободны? */ if ((fork_busy [no – 1] == 0) && (fork_busy [no % QPH] == 0)) { /* Выполним заявку */ fork_busy [no – 1] = fork_busy [no % QPH] = 1; phil_req [no – 1] = 0; (void) pthread_kill (pt_id [no – 1], SIG_PHIL); } } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, обслуживающего заявки на */ /* захват и освобождение вилок. */ /* Заявка передается в виде значения, ассоциированного */ /* с сигналом signo. */ /* Значение no > 0 запрашивает захват вилок для философа */ /* с номером no, no < 0 – освобождение вилок философа -no */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_waiter (void *signo) { siginfo_t sinfo; /* Структура для получения данных */ /* о сигнале */ int no; /* Номер философа, приславшего */ /* заявку */ sigset_t s_sgno; /* Маска ожидаемых сигналов */
    pid_wt = getpid (); /* Сформируем маску ожидаемых сигналов */ if ((sigemptyset (&s_sgno) != 0) || (sigaddset (&s_sgno, (int) signo) != 0)) { perror ("SIGEMPTYSET/SIGADDSET"); return (NULL); }
    while (1) { if (sigwaitinfo (&s_sgno, &sinfo) != (int) signo) { return (NULL); } else { /* Поступила заявка. */ /* Посмотрим, что от нас хотят */ if ((no = sinfo.si_value.sival_int) > 0) { /* Заявка на захват вилок. */ /* Запомним ее ... */ phil_req [no – 1] = 1; /* ... и попробуем выполнить */ fork_lock (no); } else { /* Освобождение вилок */ no = -no; fork_busy [no – 1] = fork_busy [no % QPH] = 0; /* Попробуем выполнить заявки от соседей */ fork_lock (no % QPH + 1); fork_lock (no == 1 ? QPH : (no – 1)); } } /* Другие сигналы нас не интересуют */ } /* while (1) */ }
    /* * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока-философа. */ /* Аргумент – номер философа */ /* * * * * * * * * * * * * * * * * * */ void *start_phil (void *no) { union sigval sval; /* Значение посылаемого сигнала: */ /* (int) no – заказ вилок */ /* -(int) no – освобождение вилок */
    /* Запомним значение аргумента в качестве */ /* индивидуальных данных потока */ (void) pthread_setspecific (phil_key, no); if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) { /* Сюда придем после нелокального перехода */ /* из обработчика сигнала SIG_PHIL. */ /* Философ просил вилки и получил их */ printf ("Философ %d ест\n", (int) no); if (sleep (ernd) != 0) { return (NULL); } /* Отдает вилки */ sval.sival_int = -((int) no); (void) sigqueue (pid_wt, SIG_FORK, sval); }
    { /* Обед */ printf ("Философ %d беседует\n", (int) no); if (sleep (trnd) != 0) { return (NULL); }
    /* Пытается взять вилки */ sval.sival_int = (int) no; (void) sigqueue (pid_wt, SIG_FORK, sval);
    /* Пока вилки заняты, приходится беседовать... */ printf ("Философ %d беседует в ожидании вилок\n", (int) no); sleep (FO); } /* Конец обеда */
    return (NULL); }
    /* * * * * * * * * * */ /* Организация обеда */ /* * * * * * * * * * */ int main (void) { int no; /* Номер философа */ struct sigaction sact; /* Структура для обработки */ /* обычных сигналов */ struct sigevent dend; /* Структура для генерации */ /* сигнала таймером */ timer_t dend_tmrid; /* Идентификатор таймера, */ /* контролирующего длительность */ /* обеда */ struct itimerspec dend_tmrsp = {{0, 0}, {FO, 0}}; /* Структура для взведения таймера, */ /* контролирующего длительность обеда */ pthread_t pt_wt; /* Идентификатор потока, управляющего */ /* вилками */
    /* Блокируем сигнал SIG_FORK */ if ((sigemptyset (&sact.sa_mask) == 0) && (sigaddset (&sact.sa_mask, SIG_FORK) == 0)) { (void) pthread_sigmask (SIG_BLOCK, &sact.sa_mask, (sigset_t *) NULL); }
    /* Установим для сигнала SIG_FORK флаг SA_SIGINFO */ sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = (void (*) (int, siginfo_t *, void *)) SIG_DFL; (void) sigaction (SIG_FORK, &sact, (struct sigaction *) NULL);
    /* Установим реакцию на сигнал SIG_PHIL */ sact.sa_handler = phil_eat; sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIG_PHIL, &sact, (struct sigaction *) NULL);
    /* Создадим поток, захватывающий и освобождающий вилки */ if ((errno = pthread_create (&pt_wt, NULL, start_waiter, (void *) SIG_FORK)) != 0) { perror ("PTHREAD_CREATE-1"); return (errno); }
    /* Создадим ключ индивидуальных данных */ if ((errno = pthread_key_create (&phil_key, phil_destructor)) != 0) { perror ("PTHREAD_KEY_CREATE"); return (errno); } /* Создадим и взведем таймер, */ /* контролирующий длительность обеда */ dend.sigev_notify = SIGEV_THREAD; dend.sigev_signo = SIG_DEND; dend.sigev_notify_function = end_ph_dinner; dend.sigev_notify_attributes = NULL; if (timer_create (CLOCK_REALTIME, &dend, &dend_tmrid) != 0) { perror ("TIMER_CREATE"); return (-1); } if (timer_settime (dend_tmrid, 0, &dend_tmrsp, NULL) != 0) { perror ("TIMER_SETTIME"); return (-1); }
    /* Все – к столу */ for (no = 1; no <= QPH; no++) { if ((errno = pthread_create (&pt_id [no – 1], NULL, start_phil, (void *) no)) != 0) { perror ("PTHREAD_CREATE"); return (no); } }
    /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) pthread_join (pt_id [no – 1], NULL); }
    (void) pthread_key_delete (phil_key); (void) timer_delete (dend_tmrid);
    /* Поток, контролирующий вилки, */ /* можно не терминировать и не ждать */
    return 0; }
    Листинг 3.25. Пример реализации обеда философов с использованием сигналов реального времени и таймера.
    Закрыть окно




    Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 1 беседует в ожидании вилок Философ 4 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 3 беседует в ожидании вилок Философ 3 ест Философ 1 беседует Философ 5 ест Философ 4 беседует в ожидании вилок Философ 5 беседует Философ 1 закончил обед Философ 2 закончил обед Философ 3 беседует Философ 4 закончил обед Философ 5 закончил обед Философ 3 беседует в ожидании вилок Философ 3 закончил обед
    Листинг 3.26. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени и таймера.
    Закрыть окно



    Сигналы реального времени


    Приложениям реального времени требуются средства надежного, детерминированного, асинхронного извещения (уведомления) о событиях. Теоретически для этого можно было бы разработать совершенно новый механизм, допускающий исключительно эффективную реализацию, однако авторы стандарта POSIX-2001 не пошли по такому пути по той простой причине, что уже имеется механизм сигналов, который обладает почти всеми необходимыми свойствами. "Почти", потому что он не решает следующих проблем.
  • Не обеспечивается надежная доставка уведомлений о событиях. Если имеется ждущий сигнал, то при последующем появлении таких же сигналов их доставка не гарантируется.
  • Не поддерживается ранжированная по приоритетам доставка уведомлений о событиях . Если имеется несколько ждущих неблокированных сигналов, порядок их доставки не определен.
  • Не поддерживается дифференциация между сигналами одного типа.

  • Чтобы устранить перечисленные недостатки, механизм сигналов был расширен, а в стандарт была введена необязательная часть, получившая название "сигналы реального времени" (Realtime Signals Extension, RTS).
    Номера сигналов реального времени лежат в диапазоне от SIGRTMIN до SIGRTMAX. Всего таких сигналов должно быть не меньше, чем RTSIG_MAX. Обеспечивается ли поведение в реальном времени для других сигналов, зависит от реализации.
    "Жизненный цикл" сигналов реального времени состоит из четырех фаз:
  • генерация;
  • ожидание;
  • доставка;
  • обработка.

  • Сигналы реального времени могут генерироваться при срабатывании высокоточных таймеров, завершении операции асинхронного ввода/вывода, поступлении межпроцессного сообщения, выполнении функции sigqueue() и т.д.
    На фазе генерации сигналов центральную роль играет определенная в заголовочном файле структура типа sigevent, которая, согласно стандарту, должна содержать по крайней мере следующие поля.
    int sigev_notify; /* Способ уведомления */ int sigev_signo; /* Номер сигнала */
    union sigval sigev_value; /* Значение сигнала */
    void (*) (union sigval) sigev_notify_function; /* Функция уведомления */

    (pthread_attr_t *)sigev_notify_attributes; /* Атрибуты уведомления */

    Значение поля sigev_notify определяет способ уведомления об асинхронном событии.

    Константа SIGEV_NONE означает отсутствие уведомления (событие проходит незамеченным).

    Константа SIGEV_SIGNAL предписывает сгенерировать сигнал с номером sigev_signo. Если для этого сигнала установлен флаг SA_SIGINFO, он (сигнал) ставится в очередь к процессу. Тем самым обеспечивается надежная доставка уведомлений о событиях.

    Константа SIGEV_THREAD задает в качестве механизма уведомления вызов функции.

    Чтобы обеспечить дифференциацию между сигналами одного типа, с ними ассоциируется значение, которое задается при генерации сигнала либо в явном виде как отдельный аргумент соответствующей функции, либо как значение поля sigev_value структуры-аргумента типа sigevent.

    Значение сигнала реального времени может быть целым числом или указателем, поскольку объединение типа sigval должно определяться со следующими полями:

    int sival_int; /* Значение сигнала – целое число */ void *sival_ptr; /* Значение сигнала – указатель */

    Если для многопотоковой программы в качестве способа уведомления о наступлении события избран вызов функции (константа SIGEV_THREAD в поле sigev_notify), то указатель на эту функцию извлекается из поля sigev_notify_function, а ее аргументом служит значение сигнала. Эта функция выполняется как стартовая для вновь созданного обособленного потока управления с атрибутным объектом *sigev_notify_attributes.

    При подобном способе уведомления сигналы как таковые не генерируются, а уведомляющую функцию не следует путать с функцией обработки сигнала, которая вызывается в ином контексте и с другими аргументами.

    После того, как сигнал сгенерирован функцией sigqueue() или иным способом, позволяющим задать определяемое приложением значение, наступает фаза ожидания. Сигналы одного типа ставятся в очередь к процессу в порядке генерации.

    Сигналы, сгенерированные с помощью вызова функции kill() или в результате наступления такого события, как аппаратное прерывание, срабатывание будильника или ввод управляющего символа с терминала, для которых реализация не обеспечивает постановку в очередь, никак на эту очередь не влияют.



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

    На фазе обработки уведомления об асинхронном событии используется структура типа siginfo_t. Для сигналов реального времени она включает, помимо описанных в курсе [1], дополнительный элемент

    union sigval si_value; /* Значение сигнала */

    в который переносится значение из поля sigev_value структуры типа sigevent.

    Подразумеваемые действия при обработке сигнала реального времени (SIG_DFL) состоят в аварийном завершении процесса.

    Простейший способ сгенерировать сигнал реального времени – обратиться к функции sigqueue() (см. листинг 3.12).

    #include int sigqueue (pid_t pid, int signo, const union sigval value);

    Листинг 3.12. Описание функции sigqueue(). (html, txt)

    Функция sigqueue() посылает сигнал с номером signo и значением value процессу, идентификатор которого задан аргументом pid. Права на посылку сигнала определяются так же, как и для функции kill(); аналогично kill(), при нулевом значении signo сигнал не посылается, а проверяется существование процесса с идентификатором pid.

    Функция sigqueue() завершается немедленно, без какого-либо ожидания. Если для сигнала signo установлен флаг SA_SIGINFO и в наличии достаточно ресурсов, сигнал ставится в очередь к процессу pid. Если флаг SA_SIGINFO не установлен, сигнал посылается процессу-получателю по крайней мере один раз, но, возможно, без ассоциированного с ним значения.

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

    Дождаться доставки сигнала реального времени можно с помощью функций sigwaitinfo() и sigtimedwait() (см. листинг 3.13), являющихся аналогами рассмотренной в курсе [1] функции sigwait().

    #include

    int sigwaitinfo ( const sigset_t *restrict set, siginfo_t *restrict info);



    int sigtimedwait ( const sigset_t *restrict set, siginfo_t * restrict info, const struct timespec *restrict timeout);

    Листинг 3.13. Описание функций sigwaitinfo() и sigtimedwait(). (html, txt)

    Данные функции возвращают в качестве нормального результата номер полученного сигнала, входящего в набор set. Кроме того, если значение аргумента info отлично от NULL, заполняются поля si_signo (номер сигнала), si_code (источник сигнала) и, возможно, si_value (значение, если оно ассоциировано с сигналом) указуемого объекта структурного типа siginfo_t. Разумеется, полученный сигнал изымается из очереди ждущих, а соответствующие ресурсы освобождаются.

    Функция sigtimedwait() отличается тем, что ограничивает время ожидания сигнала заданным интервалом. Если аргумент timeout является пустым указателем, поведение функции не специфицировано. Если реализация поддерживает монотонные часы (CLOCK_MONOTONIC), они и будут использоваться для контроля времени ожидания.

    Если несколько потоков управления ждут один сигнал, он достанется кому-то одному из них.



    Поле ss_flags определяет состояние нового стека. Флаг SS_DISABLE в этом поле по сути означает отказ от альтернативного стека; в таком случае значения ss_sp и ss_size игнорируются.

    Альтернативный стек располагается в диапазоне адресов от ss_sp до (но не включая) ss_sp + ss_size. Стандарт не специфицирует, с какого конца и в каком направлении растет стек.

    Если значением аргумента oss служит непустой указатель, то после возврата из функции sigaltstack() в указуемую структуру типа stack_t будут помещены прежние значения характеристик альтернативного стека. В частности, в поле ss_flags будет отражено состояние стека, которое могут характеризовать по крайней мере следующие флаги.

    SS_ONSTACK

    Этот флаг означает, что в данный момент процесс выполняется на альтернативном стеке обработки сигналов. Попытки модифицировать стек в то время, как на нем происходит выполнение, трактуются как ошибочные.

    SS_DISABLE

    Этот флаг означает, что в данный момент альтернативный стек обработки сигналов отключен.

    Константа SIGSTKSZ задает подразумеваемый, а MINSIGSTKSZ – минимально допустимый размер альтернативного стека для функции обработки сигналов. Обычно, чтобы учесть системные накладные расходы, нужно сложить потребности приложения и MINSIGSTKSZ.

    Естественно, все заботы по контролю за переполнением и исчерпанием альтернативного стека возлагаются на приложение.

    На листинге 3.15 показана типичная схема определения альтернативного стека.

    #include #include #include . . . stack_t sighstk; . . .

    if ((sighstk.ss_sp = malloc( SIGSTKSZ)) == NULL) { perror ("malloc (SIGSTKSZ)"); /* Аварийное завершение */ } sighstk.ss_size = SIGSTKSZ; sighstk.ss_flags = 0; if (sigaltstack (&sighstk, (stack_t *) NULL) != 0) { perror ("SIGALTSTACK"); . . . } . . .

    Листинг 3.15. Типичная схема определения альтернативного стека.

    Обработка сигналов (не обязательно реального времени) нередко сочетается с нелокальными переходами. В таких случаях могут оказаться полезными функции sigsetjmp() и siglongjmp() (см.


    листинг 3.16). Они аналогичны функциям setjmp() и longjmp() с единственным содержательным отличием: если значение аргумента savemask функции sigsetjmp() отлично от нуля, маска сигналов вызывающего потока управления сохраняется как часть его окружения и восстанавливается после выполнения siglongjmp().

    #include

    int sigsetjmp (sigjmp_buf env, int savemask);

    void siglongjmp (sigjmp_buf env, int val);

    Листинг 3.16. Описание функций sigsetjmp() и siglongjmp().

    В качестве примера использования сигналов реального времени приведем еще один вариант реализации обеда философов (см. листинг 3.17).

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов */ /* с использованием сигналов реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include #include #include

    /* Число обедающих философов */ #define QPH 5

    /* Время (в секундах) на обед */ #define FO 15

    /* Длительность еды */ #define ernd (rand () % 3 + 1)

    /* Длительность разговора */ #define trnd (rand () % 5 + 1)

    /* Номер сигнала, используемого для захвата и освобождения вилок */ #define SIG_FORK SIGRTMIN

    /* Номер сигнала, используемого для информирования философа */ #define SIG_PHIL SIGINT

    static pthread_t pt_id [QPH]; /* Массив идентификаторов */ /* потоков - философов */ static int fork_busy [QPH] = {0, }; /* Состояние вилок */ static int phil_req [QPH] = {0, }; /* Невыполненные */ /* заявки на вилки */ static sigjmp_buf phil_env [QPH]; /* Массив буферов для */ /* нелокальных переходов */

    static pid_t pid_wt; /* Идентификатор процесса,*/ /* контролирующего вилки */

    static pthread_key_t phil_key; /* Ключ индивидуальных */ /* данных потоков-философов */

    /* * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIG_PHIL */ /* * * * * * * * * * * * * * * * * * * */ static void phil_eat (int signo) { int no; /* Номер философа, которому достался сигнал */



    no = (int) pthread_getspecific (phil_key); if ((no > 0) && (no <= QPH)) { siglongjmp (phil_env [no – 1], signo); } }

    /* * * * * * * * * * * * * * * * * * * * * * */ /* Попытка выполнить заявку на захват вилок */ /* от философа номер no, если она есть */ /* * * * * * * * * * * * * * * * * * * * * * */ static void fork_lock (int no) { if (phil_req [no – 1] != 0) { /* Заявка есть. */ /* Вилки свободны? */ if ((fork_busy [no – 1] == 0) && (fork_busy [no % QPH] == 0)) { /* Выполним заявку */ fork_busy [no – 1] = fork_busy [no % QPH] = 1; phil_req [no – 1] = 0; (void) pthread_kill (pt_id [no – 1], SIG_PHIL); } } }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, обслуживающего заявки на */ /* захват и освобождение вилок. */ /* Заявка передается в виде значения, ассоциированного */ /* с сигналом signo. */ /* Значение no > 0 запрашивает захват вилок для философа */ /* с номером no, no < 0 – освобождение вилок философа -no */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_waiter (void *signo) { siginfo_t sinfo; /* Структура для получения данных */ /* о сигнале */ int no; /* Номер философа, приславшего заявку */ sigset_t s_sgno; /* Маска ожидаемых сигналов */

    pid_wt = getpid ();

    /* Сформируем маску ожидаемых сигналов */ if ((sigemptyset (&s_sgno) != 0) || (sigaddset (&s_sgno, (int) signo) != 0)) { perror ("SIGEMPTYSET/SIGADDSET"); return (NULL); }

    while (1) { if (sigwaitinfo (&s_sgno, &sinfo) != (int) signo) { return (NULL); } else { /* Поступила заявка. */ /* Посмотрим, что от нас хотят */ if ((no = sinfo.si_value.sival_int) > 0) { /* Заявка на захват вилок. */ /* Запомним ее ... */ phil_req [no – 1] = 1; /* ... и попробуем выполнить */ fork_lock (no); } else { /* Освобождение вилок */ no = -no; fork_busy [no – 1] = fork_busy [no % QPH] = 0; /* Попробуем выполнить заявки от соседей */ fork_lock (no % QPH + 1); fork_lock (no == 1 ? QPH : (no – 1)); } } /* Другие сигналы нас не интересуют */ } /* while (1) */ }



    /* * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока-философа. */ /* Аргумент – номер философа */ /* * * * * * * * * * * * * * * * * * */ void *start_phil (void *no) { int fo; /* Время до конца обеда */ int t; /* Время очередного отрезка еды или беседы */ time_t tbe; /* Время, когда философу понадобились вилки */ union sigval sval; /* Значение посылаемого сигнала: */ /* (int) no – заказ вилок */ /* -(int) no – освобождение вилок */

    /* Запомним значение аргумента в качестве */ /* индивидуальных данных потока */ (void) pthread_setspecific (phil_key, no);

    /* Подготовка к обеду */ fo = FO;

    if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) { /* Сюда придем после нелокального перехода */ /* из обработчика сигнала SIG_PHIL. */ /* Философ просил вилки и получил их */ printf ("Философ %d ест\n", (int) no); t = ernd; sleep (t); /* Нужно вычесть времена еды и ожидания вилок */ fo -= (int) (time ((time_t *) NULL) – tbe) + t; /* Отдает вилки */ sval.sival_int = -((int) no); (void) sigqueue (pid_wt, SIG_FORK, sval); }

    while (fo > 0) { printf ("Философ %d беседует\n", (int) no); t = trnd; sleep (t); fo -= t;

    /* Пытается взять вилки */ tbe = time ((time_t *) NULL); sval.sival_int = (int) no; (void) sigqueue (pid_wt, SIG_FORK, sval);

    /* Пока вилки заняты, приходится беседовать... */ printf ("Философ %d беседует в ожидании вилок\n", (int) no); sleep (fo); fo = 0; } /* while */ printf ("Философ %d закончил обед\n", (int) no);

    return (NULL); }

    /* * * * * * * * * * */ /* Организация обеда */ /* * * * * * * * * * */ int main (void) { int no; /* Номер философа */ struct sigaction sact; /* Структура для обработки */ /* обычных сигналов */ pthread_t pt_wt; /* Идентификатор потока, */ /* управляющего вилками */

    /* Блокируем сигнал SIG_FORK */ if ((sigemptyset (&sact.sa_mask) == 0) && (sigaddset (&sact.sa_mask, SIG_FORK) == 0)) { (void) pthread_sigmask (SIG_BLOCK, &sact.sa_mask, (sigset_t *) NULL); }

    /* Установим для сигнала SIG_FORK флаг SA_SIGINFO */ sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = (void (*) (int, siginfo_t *, void *)) SIG_DFL; (void) sigaction (SIG_FORK, &sact, (struct sigaction *) NULL);



    /* Установим реакцию на сигнал SIG_PHIL */ sact.sa_handler = phil_eat; sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIG_PHIL, &sact, (struct sigaction *) NULL);

    /* Создадим поток, захватывающий и освобождающий вилки */ if ((errno = pthread_create (&pt_wt, NULL, start_waiter, (void *) SIG_FORK)) != 0) { perror ("PTHREAD_CREATE-1"); return (errno); }

    /* Создадим ключ индивидуальных данных */ if ((errno = pthread_key_create (&phil_key, (void (*) (void *)) NULL)) != 0) { perror ("PTHREAD_KEY_CREATE"); return (errno); }

    /* Все – к столу */ for (no = 1; no <= QPH; no++) { if ((errno = pthread_create (&pt_id [no – 1], NULL, start_phil, (void *) no)) != 0) { perror ("PTHREAD_CREATE"); return (no); } }

    /* Ожидание завершения обеда */ for (no = 1; no <= QPH; no++) { (void) pthread_join (pt_id [no – 1], NULL); }

    (void) pthread_key_delete (phil_key);

    /* Завершим поток, контролирующий вилки */ (void) pthread_cancel (pt_wt); (void) pthread_join (pt_wt, NULL);

    return 0; }

    Листинг 3.17. Пример реализации обеда философов с использованием сигналов реального времени.

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

    Для информирования философов о том, что нужные вилки захвачены и можно приступать к еде, применяются обычные сигналы, обрабатываемые асинхронно.

    В качестве небольшой тонкости обратим внимание на значение второго аргумента (оно отлично от нуля) в вызове функции sigsetjmp(). Таким образом обеспечиваетсявосстановление маски сигналов после нелокального перехода из функции обработки сигнала SIG_PHIL. Если этого не сделать, сигнал SIG_PHIL останется блокированным и второй раз философ вилок уже не получит (точнее, он не узнает о том, что вилки для него захвачены).



    Возможные результаты выполнения приведенной программы показаны на листинге 3.18.

    Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 4 закончил обед Философ 1 беседует в ожидании вилок Философ 5 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 3 закончил обед Философ 1 закончил обед Философ 5 закончил обед Философ 2 закончил обед

    Листинг 3.18. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени.


    Передача и прием сообщений в реальном времени


    Средства локальной передачи и приема сообщений присутствуют, вероятно, во всех или почти во всех операционных системах. Во многих версиях ОС Unix представлено по нескольку разновидностей подобных средств. Следуя за исторически сложившимися реализациями, стандарт POSIX-2001 предусматривает два вида очередей сообщений и обслуживающих их функций. Один из них был рассмотрен нами в курсе [1]. Здесь мы опишем вторую разновидность, предназначенную для использования в системах реального времени.
    Описываемые очереди сообщений являются общесистемными. Они доступны по именам, которые могут быть маршрутными именами файлов.
    Над очередями сообщений определены следующие группы функций:
  • открытие очереди;
  • отправка сообщения в очередь;
  • прием (синхронный или асинхронный) сообщения из очереди;
  • изменение атрибутов очереди;
  • регистрация на получение уведомления о появлении сообщения в очереди;
  • закрытие очереди;
  • удаление очереди.

  • Одну очередь могут открыть несколько посылающих и/или принимающих сообщения процессов. При открытии может производиться контроль прав доступа.
    Для каждой очереди задается фиксированная верхняя граница размера сообщений, которые могут быть в эту очередь отправлены.
    На порядок приема влияет имеющийся механизм приоритетов сообщений.
    Процесс может получать асинхронные уведомления о том, что в очереди появилось сообщение.
    Для открытия очереди служит функция mq_open() (см. листинг 4.1), которая, по аналогии с файлами, создает описание открытой очереди и ссылающийся на него дескриптор типа mqd_t, возвращаемый в качестве нормального результата.
    #include mqd_t mq_open ( const char *name, int oflag, ...);
    Листинг 4.1. Описание функции mq_open(). (html, txt)
    Трактовка имени очереди (аргумент name) зависит от реализации. Оговаривается только, что оно должна подчиняться ограничениям, налагаемым на маршрутные имена, и что если его первым символом является '/', то процессы, вызывающие mq_open() с одинаковыми значениями name, ссылаются на одну и ту же очередь (если, конечно, ее не удаляли).

    При реализации дескрипторов очередей сообщений могут применяться файловые дескрипторы. В таком случае приложение может открыть не менее OPEN_MAX файлов и очередей.

    Аргумент oflag специфицирует запрашиваемые виды доступа к очереди: на прием (чтение) и отправку (запись). Контроль прав доступа для очередей сообщений производится так же, как и для файлов.

    В целом набор флагов и их трактовка для очередей сообщений те же, что и для файлов: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NONBLOCK.

    Если установлен флаг O_CREAT, то при вызове функции mq_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и указатель на атрибутный объект (тип struct mq_attr *) создаваемой очереди сообщений.

    Согласно стандарту POSIX-2001, структура типа mq_attr, описанная в заголовочном файле , содержит по крайней мере следующие поля.

    long mq_flags; /* Флаги очереди сообщений */ long mq_maxmsg; /* Максимальное число сообщений в очереди */ long mq_msgsize; /* Максимальный размер сообщения в очереди */ long mq_curmsgs; /* Текущее число сообщений в очереди */

    Для опроса и/или установки атрибутов очереди служат функции mq_getattr() и mq_setattr() (см. листинг 4.2). Впрочем, про установку атрибутов сказано, пожалуй слишком сильно: посредством вызова 2 можно изменить лишь состояние флага 2 (и, возможно, некоторых других флагов, зависящих от реализации).

    #include

    int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat);

    int mq_setattr (mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat);

    Листинг 4.2. Описание функций mq_getattr() и mq_setattr(). (html, txt)

    После того, как процесс завершил работу с очередью сообщений, соответствующий дескриптор следует закрыть, воспользовавшись функцией mq_close() (см. листинг 4.3).

    #include int mq_close (mqd_t mqdes);

    Листинг 4.3. Описание функции mq_close(). (html, txt)

    Если очередь сообщений стала совсем ненужной, ее можно удалить с помощью функции mq_unlink() (см.листинг 4.4).

    #include int mq_unlink (const char *name);

    Листинг 4.4. Описание функции mq_unlink(). (html, txt)



    При реализации дескрипторов очередей сообщений могут применяться файловые дескрипторы. В таком случае приложение может открыть не менее OPEN_MAX файлов и очередей.

    Аргумент oflag специфицирует запрашиваемые виды доступа к очереди: на прием (чтение) и отправку (запись). Контроль прав доступа для очередей сообщений производится так же, как и для файлов.

    В целом набор флагов и их трактовка для очередей сообщений те же, что и для файлов: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NONBLOCK.

    Если установлен флаг O_CREAT, то при вызове функции mq_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и указатель на атрибутный объект (тип struct mq_attr *) создаваемой очереди сообщений.

    Согласно стандарту POSIX-2001, структура типа mq_attr, описанная в заголовочном файле , содержит по крайней мере следующие поля.

    long mq_flags; /* Флаги очереди сообщений */ long mq_maxmsg; /* Максимальное число сообщений в очереди */ long mq_msgsize; /* Максимальный размер сообщения в очереди */ long mq_curmsgs; /* Текущее число сообщений в очереди */

    Для опроса и/или установки атрибутов очереди служат функции mq_getattr() и mq_setattr() (см. листинг 4.2). Впрочем, про установку атрибутов сказано, пожалуй слишком сильно: посредством вызова 2 можно изменить лишь состояние флага 2 (и, возможно, некоторых других флагов, зависящих от реализации).

    #include

    int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat);

    int mq_setattr (mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat);

    Листинг 4.2. Описание функций mq_getattr() и mq_setattr().

    После того, как процесс завершил работу с очередью сообщений, соответствующий дескриптор следует закрыть, воспользовавшись функцией mq_close() (см. листинг 4.3).

    #include int mq_close (mqd_t mqdes);

    Листинг 4.3. Описание функции mq_close().

    Если очередь сообщений стала совсем ненужной, ее можно удалить с помощью функции mq_unlink() (см. листинг 4.4).



    #include int mq_unlink (const char *name);

    Листинг 4.4. Описание функции mq_unlink().

    Переходя к описанию содержательных действий с очередями сообщений, укажем, что отправка осуществляется функциями mq_send() и mq_timedsend() (см. листинги 4.5 и 4.6).

    #include int mq_send (mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);

    Листинг 4.5. Описание функции mq_send(). #include #include int mq_timedsend ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abstime);

    Листинг 4.6. Описание функции mq_timedsend().

    Более точно: функции mq_send() и mq_timedsend() помещают сообщение из msg_len байт, на которое указывает аргумент msg_ptr, в очередь, заданную дескриптором mqdes (если она не полна), в соответствии с приоритетом msg_prio (большим значениям msg_prio соответствует более высокий приоритет сообщения; допустимый диапазон - от 0 до MQ_PRIO_MAX - 1).

    Если очередь полна, а флаг O_NONBLOCK не установлен, вызов mq_send() блокируется до появления свободного места. Функция mq_timedsend() в таких случаях контролирует длительность ожидания (до заданного аргументом abstime абсолютного момента времени по часам CLOCK_REALTIME).

    Для извлечения (разумеется, с удалением) сообщений из очереди служат функции mq_receive() и mq_timedreceive() (см. листинги 4.7 и 4.8). Извлекается самое старое из сообщений с самым высоким приоритетом и помещается в буфер, на который указывает аргумент msg_ptr. Если размер буфера (значение аргумента msg_len) меньше атрибута очереди mq_msgsize, вызов завершается неудачей. Если значение msg_prio_ptr отлично от NULL, в указуемый объект помещается приоритет принятого сообщения.

    #include ssize_t mq_receive ( mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio_ptr);

    Листинг 4.7. Описание функции mq_receive().

    #include #include ssize_t mq_timedreceive ( mqd_t mqdes, char *restrict msg_ptr, size_t msg_len, unsigned *restrict msg_prio_ptr, const struct timespec *restrict abstime);



    Листинг 4.8. Описание функции mq_timedreceive().

    Если очередь пуста, а флаг O_NONBLOCK не установлен, вызов mq_receive() блокируется до появления сообщения. Функция mq_timedreceive() в таких случаях контролирует длительность ожидания.

    Нормальным результатом обеих функций является размер в байтах извлеченного из очереди сообщения. Как всегда, признаком ошибки служит -1.

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

    #include int mq_notify (mqd_t mqdes, const struct sigevent *notification);

    Листинг 4.9. Описание функции mq_notify().

    Для уведомлений используется механизм сигналов реального времени. В каждый момент времени только один процесс может быть зарегистрированным, а сама регистрация является одноразовой: после поступления уведомления она отменяется. Процесс может добровольно отменить регистрацию, если передаст NULL в качестве значения аргумента notification.

    Если, наряду с зарегистрированным процессом, имеется поток управления, ожидающий сообщения в вызове mq_receive() или mq_timedreceive(), поступившее сообщение достанется потоку, а процесс не получит никакого уведомления, как если бы очередь осталась пустой. Это очень по-человечески: живое стояние в очереди всегда ценилось выше всяких списков и уведомлений.

    На наш взгляд, возможность получать уведомления о том, что очередь сообщений стала непустой, является скорее заплатой, призванной скрыть неполноту функциональности poll() и select(), чем полноценным, практически полезным средством. В стандартизованном интерфейсе отсутствует концептуальная целостность - из соображений симметрии необходимы уведомления о том, что очередь стала неполной и в нее можно отправлять сообщения. Далее, состояние очереди может меняться параллельно с регистрацией и/или получением уведомления, и нет никаких средств, чтобы сделать соответствующую транзакцию атомарной. В результате остается неясным, когда процесс получит уведомление (и получит ли он его вообще), какое сообщение он примет после получения уведомления (и будет ли что принимать) и т.п., хотя, с другой стороны, устраивать конкуренцию за сообщения тоже не обязательно.



    Поучительно сопоставить два вида очередей сообщений - только что описанные и те, что были представлены в курсе [1]. Сразу видно, что первые устроены проще и, следовательно, могут быть реализованы эффективнее. Во-первых, структура типа mq_attr гораздо компактнее, чем msqid_ds. Нет и речи о хранении времени последних операций и идентификаторов процессов, их выполнивших. Во-вторых, упрощены производимые проверки. Контролируется максимальный размер одного сообщения, а не суммарный размер сообщений в очереди. Права доступа проверяются при открытии, а не при каждой операции. Только наличие приоритетов вносит элемент сложности, сопоставимый с обработкой типов сообщений, но приоритеты так или иначе должны быть реализованы - либо на уровне ОС, либо в приложении, поэтому лучше этот аспект стандартизовать и поручить операционной системе.

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

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа принимает сообщения */ /* и суммирует содержащиеся в них целые числа. */ /* Массивы случайных целых чисел */ /* генерируют несколько потоков управления. */ /* Каждый поток использует свою очередь сообщений.*/ /* Используется режим без блокировки, */ /* с уведомлениями о появлении сообщений в очереди*/ /* * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include #include #include

    /* Число потоков управления, порождающих случайные числа */ #define PT_N MQ_OPEN_MAX

    /* Число сообщений, генерируемых каждым потоком управления */ #define MSG_N 128

    /* Длина имени очереди сообщений */ #define MQ_NAME_LENGTH PATH_MAX

    /* Количество целых чисел в одном сообщении */ #define MSG_INT_SIZE 32

    /* Максимальное число сообщений в очереди */ #define MQ_MSGS_MAX 16



    /* Приоритет порождаемого сообщения */ #define prio_rnd (rand () % MQ_PRIO_MAX)

    /* Номер сигнала, используемого для уведомлений */ #define SIG_MQ_NOTIFY SIGRTMIN

    /* Массив идентификаторов очередей сообщений */ static mqd_t mq_des [PT_N];

    /* Массив структур для задания уведомлений */ /* о поступлении сообщений в очереди */ static struct sigevent evnt_mq_notify [PT_N];

    /* Мьютекс, используемый для синхронизации */ /* доступа к переменной sum */ static pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER;

    /* Общий результат суммирования */ static int sum = 0;

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая при получении уведомления*/ /* о том, что очередь сообщений стала непустой. */ /* Номер очереди передается как аргумент */ /* * * * * * * * * * * * * * * * * * * * * * * */ static void msg_arrvd (union sigval pt_nm) { int msg_buf [MSG_INT_SIZE]; mqd_t mqdes; unsigned int msg_prio; int msg_sum = 0; ssize_t msg_size; int i;

    mqdes = mq_des [pt_nm.sival_int];

    /* Примем и обработаем имеющиеся сообщения, */ /* а затем снова зарегистрируемся на получение */ /* такого же уведомления */ while ((msg_size = mq_receive (mqdes, (char *) msg_buf, MSG_INT_SIZE * sizeof (int), &msg_prio)) > 0) { for (i = 0; i < (msg_size / (signed int) sizeof (int)); i++) { msg_sum += msg_buf [i]; } msg_sum *= msg_prio; }

    if ((errno = pthread_mutex_lock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_LOCK"); } sum += msg_sum; if ((errno = pthread_mutex_unlock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); }

    if (mq_notify (mqdes, &evnt_mq_notify [pt_nm.sival_int]) != 0) { perror ("MQ_NOTIFY"); } }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа и */ /* посылающего сообщения. */ /* Аргумент - номер очереди сообщений */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_sender (void *pt_nm) {

    int msg_buf [MSG_INT_SIZE]; int i, j;

    /* Сформируем и пошлем заданное число сообщений */ /* (проверяя, не переполнилась ли очередь)*/ for (j = 0; j < MSG_N; j++) { for (i = 0; i < MSG_INT_SIZE; i++) { msg_buf [i] = rand (); } if (mq_send (mq_des [(int) pt_nm], (char *) msg_buf, MSG_INT_SIZE * sizeof (int), prio_rnd) != 0) { perror ("MQ_SEND"); return (NULL); } }



    return (NULL); }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание очереди сообщений, */ /* регистрация на получение уведомлений, */ /* создание и ожидание завершения потоков управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов порождаемых */ /* потоков. Эти потоки будут */ pthread_t pt_mqs [PT_N]; /* генерировать сообщения*/ /* Массив для генерации и хранения */ /* имен очередей сообщений */ char mq_name [PT_N] [MQ_NAME_LENGTH]; struct mq_attr mqattrs; /* Атрибуты создаваемых очередей */ int i;

    for (i = 0; i < PT_N; i++) { /* Создадим очереди сообщений */ /* и зарегистрируемся на получение уведомлений */ sprintf (mq_name [i], "g%d", i);

    mqattrs.mq_flags = O_NONBLOCK; mqattrs.mq_maxmsg = MQ_MSGS_MAX; mqattrs.mq_msgsize = MSG_INT_SIZE * sizeof (int); mqattrs.mq_curmsgs = 0; if ((mq_des [i] = mq_open (mq_name [i], O_RDWR | O_CREAT | O_NONBLOCK, 0777, &mqattrs)) == (mqd_t) (-1)) { perror ("MQ_OPEN"); return (-1); }

    /* Сформируем структуру evnt_mq_notify */ evnt_mq_notify [i].sigev_notify = SIGEV_THREAD; evnt_mq_notify [i].sigev_signo = SIG_MQ_NOTIFY; evnt_mq_notify [i].sigev_value.sival_int = i; evnt_mq_notify [i].sigev_notify_function = msg_arrvd; evnt_mq_notify [i].sigev_notify_attributes = NULL; if (mq_notify (mq_des [i], &evnt_mq_notify [i]) != 0) { perror ("MQ_NOTIFY_MAIN"); return (-1); }

    /* Создадим потоки управления */ if ((errno = pthread_create (&pt_mqs [i], NULL, start_sender, (void *) i)) != 0) { perror ("PTHREAD_CREATE"); return (i); } } /* for */

    /* Ожидание завершения */ for (i = 0; i < PT_N; i++) { (void) pthread_join (pt_mqs [i], NULL); }

    /* Закроем дескрипторы и удалим очереди */ for (i = 0; i < PT_N; i++) { (void) mq_close (mq_des [i]); (void) mq_unlink (mq_name [i]); }

    printf ("Общая сумма: %d\n", sum);

    if ((errno = pthread_mutex_destroy (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_DESTROY"); return (errno); }

    return 0; }

    Листинг 4.10. Пример программы, использующей очереди сообщений.

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


    mqd_t mq_open


    #include mqd_t mq_open ( const char *name, int oflag, ...);
    Листинг 4.1. Описание функции mq_open().
    Закрыть окно




    #include
    int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat);
    int mq_setattr ( mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat);
    Листинг 4.2. Описание функций mq_getattr() и mq_setattr().
    Закрыть окно




    #include int mq_close (mqd_t mqdes);
    Листинг 4.3. Описание функции mq_close().
    Закрыть окно




    #include int mq_unlink (const char *name);
    Листинг 4.4. Описание функции mq_unlink().
    Закрыть окно




    #include int mq_send ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
    Листинг 4.5. Описание функции mq_send().
    Закрыть окно




    #include #include int mq_timedsend ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abstime);
    Листинг 4.6. Описание функции mq_timedsend().
    Закрыть окно




    #include ssize_t mq_receive ( mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio_ptr);
    Листинг 4.7. Описание функции mq_receive().
    Закрыть окно




    #include #include ssize_t mq_timedreceive ( mqd_t mqdes, char * restrict msg_ptr, size_t msg_len, unsigned *restrict msg_prio_ptr, const struct timespec *restrict abstime);
    Листинг 4.8. Описание функции mq_timedreceive().
    Закрыть окно




    #include int mq_notify ( mqd_t mqdes, const struct sigevent *notification);
    Листинг 4.9. Описание функции mq_notify().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа принимает сообщения */ /* и суммирует содержащиеся в них целые числа. */ /* Массивы случайных целых чисел */ /* генерируют несколько потоков управления. */ /* Каждый поток использует свою очередь сообщений.*/ /* Используется режим без блокировки, */ /* с уведомлениями о появлении сообщений в очереди*/ /* * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include
    /* Число потоков управления, порождающих случайные числа */ #define PT_N MQ_OPEN_MAX
    /* Число сообщений, генерируемых каждым потоком управления */ #define MSG_N 128
    /* Длина имени очереди сообщений */ #define MQ_NAME_LENGTH PATH_MAX
    /* Количество целых чисел в одном сообщении */ #define MSG_INT_SIZE 32
    /* Максимальное число сообщений в очереди */ #define MQ_MSGS_MAX 16
    /* Приоритет порождаемого сообщения */ #define prio_rnd (rand () % MQ_PRIO_MAX)
    /* Номер сигнала, используемого для уведомлений */ #define SIG_MQ_NOTIFY SIGRTMIN
    /* Массив идентификаторов очередей сообщений */ static mqd_t mq_des [PT_N];
    /* Массив структур для задания уведомлений */ /* о поступлении сообщений в очереди */ static struct sigevent evnt_mq_notify [PT_N];
    /* Мьютекс, используемый для синхронизации */ /* доступа к переменной sum */ static pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER;
    /* Общий результат суммирования */ static int sum = 0;
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая при получении уведомления*/ /* о том, что очередь сообщений стала непустой. */ /* Номер очереди передается как аргумент */ /* * * * * * * * * * * * * * * * * * * * * * * */ static void msg_arrvd (union sigval pt_nm) { int msg_buf [MSG_INT_SIZE]; mqd_t mqdes; unsigned int msg_prio; int msg_sum = 0; ssize_t msg_size; int i;
    mqdes = mq_des [pt_nm.sival_int];
    /* Примем и обработаем имеющиеся сообщения, */ /* а затем снова зарегистрируемся на получение */ /* такого же уведомления */ while ((msg_size = mq_receive (mqdes, (char *) msg_buf, MSG_INT_SIZE * sizeof (int), &msg_prio)) > 0) { for (i = 0; i < (msg_size / (signed int) sizeof (int)); i++) { msg_sum += msg_buf [i]; } msg_sum *= msg_prio; }
    if ((errno = pthread_mutex_lock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_LOCK"); } sum += msg_sum; if ((errno = pthread_mutex_unlock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); }
    if (mq_notify (mqdes, &evnt_mq_notify [pt_nm.sival_int]) != 0) { perror ("MQ_NOTIFY"); } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа и */ /* посылающего сообщения. */ /* Аргумент - номер очереди сообщений */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_sender (void *pt_nm) {
    int msg_buf [MSG_INT_SIZE]; int i, j;
    /* Сформируем и пошлем заданное число сообщений */ /* (проверяя, не переполнилась ли очередь)*/ for (j = 0; j < MSG_N; j++) { for (i = 0; i < MSG_INT_SIZE; i++) { msg_buf [i] = rand (); } if (mq_send (mq_des [(int) pt_nm], (char *) msg_buf, MSG_INT_SIZE * sizeof (int), prio_rnd) != 0) { perror ("MQ_SEND"); return (NULL); } }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание очереди сообщений, */ /* регистрация на получение уведомлений, */ /* создание и ожидание завершения потоков управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов порождаемых */ /* потоков. Эти потоки будут */ pthread_t pt_mqs [PT_N]; /* генерировать сообщения*/ /* Массив для генерации и хранения */ /* имен очередей сообщений */ char mq_name [PT_N] [MQ_NAME_LENGTH]; struct mq_attr mqattrs; /* Атрибуты создаваемых очередей */ int i;
    for (i = 0; i < PT_N; i++) { /* Создадим очереди сообщений */ /* и зарегистрируемся на получение уведомлений */ sprintf (mq_name [i], "g%d", i);
    mqattrs.mq_flags = O_NONBLOCK; mqattrs.mq_maxmsg = MQ_MSGS_MAX; mqattrs.mq_msgsize = MSG_INT_SIZE * sizeof (int); mqattrs.mq_curmsgs = 0; if ((mq_des [i] = mq_open (mq_name [i], O_RDWR | O_CREAT | O_NONBLOCK, 0777, &mqattrs)) == (mqd_t) (-1)) { perror ("MQ_OPEN"); return (-1); }
    /* Сформируем структуру evnt_mq_notify */ evnt_mq_notify [i].sigev_notify = SIGEV_THREAD; evnt_mq_notify [i].sigev_signo = SIG_MQ_NOTIFY; evnt_mq_notify [i].sigev_value.sival_int = i; evnt_mq_notify [i].sigev_notify_function = msg_arrvd; evnt_mq_notify [i].sigev_notify_attributes = NULL; if (mq_notify (mq_des [i], &evnt_mq_notify [i]) != 0) { perror ("MQ_NOTIFY_MAIN"); return (-1); }
    /* Создадим потоки управления */ if ((errno = pthread_create (&pt_mqs [i], NULL, start_sender, (void *) i)) != 0) { perror ("PTHREAD_CREATE"); return (i); } } /* for */
    /* Ожидание завершения */ for (i = 0; i < PT_N; i++) { (void) pthread_join (pt_mqs [i], NULL); }
    /* Закроем дескрипторы и удалим очереди */ for (i = 0; i < PT_N; i++) { (void) mq_close (mq_des [i]); (void) mq_unlink (mq_name [i]); }
    printf ("Общая сумма: %d\n", sum);
    if ((errno = pthread_mutex_destroy (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_DESTROY"); return (errno); }
    return 0; }
    Листинг 4.10. Пример программы, использующей очереди сообщений.
    Закрыть окно




    #include
    sem_t *sem_open (const char *name, int oflag, ...);
    int sem_init (sem_t *sem, int pshared, unsigned value);
    Листинг 4.11. Описание функций sem_open() и sem_init().
    Закрыть окно




    #include
    int sem_close (sem_t *sem);
    int sem_unlink (const char *name);
    int sem_destroy (sem_t *sem);
    Листинг 4.12. Описание функций закрытия и ликвидации семафоров.
    Закрыть окно




    #include
    int sem_wait (sem_t *sem);
    int sem_trywait (sem_t *sem);
    Листинг 4.13. Описание функций захвата семафоров.
    Закрыть окно




    #include #include int sem_timedwait (sem_t * restrict sem, const struct timespec *restrict abstime);
    Листинг 4.14. Описание функции захвата семафора с контролем времени ожидания.
    Закрыть окно




    #include int sem_post (sem_t *sem);
    Листинг 4.15. Описание функции sem_post().
    Закрыть окно




    #include int sem_getvalue (sem_t * restrict sem, int *restrict sval);
    Листинг 4.16. Описание функции sem_getvalue().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа и помещает их буфер */ /* на один элемент, потребитель извлекает их оттуда и суммирует. */ /* Для синхронизации используются неименованные семафоры*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include /* Буфер для хранения генерируемых данных */ static int my_buf;
    /* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem;
    /* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem;
    /* Общий результат суммирования */ static int sum = 0;
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа */ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf = rand (); if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, читающего и суммирующего числа */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } }
    return (NULL); } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */
    /* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, 1) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); }
    /* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); }
    /* Дадим потокам повыполняться */ sleep (10);
    /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);
    /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL); /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); } if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }
    printf ("Сумма сгенерированных чисел: %d\n", sum);
    return 0; }
    Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа */ /* и помещает их в кольцевой буфер, */ /* потребитель извлекает их оттуда и суммирует.*/ /* Для синхронизации используются */ /* неименованные семафоры */ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include
    /* Буфер для хранения генерируемых данных */ static int my_buf [BUFSIZ];
    /* Индекс, по которому можно записать очередной элемент */ static int w_ind = 0;
    /* Индекс, по которому можно прочитать очередной элемент */ static int r_ind = 0;
    /* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem;
    /* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem;
    /* Общий результат суммирования */ static int sum = 0;
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа*/ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf [w_ind] = rand (); w_ind = (w_ind + 1) % BUFSIZ; if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } }
    return (NULL); }
    /* * * * * * * * * * * * * * * */ /* Стартовая функция потока, */ /* читающего и суммирующего числа*/ /* * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf [r_ind]; r_ind = (r_ind + 1) % BUFSIZ; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */
    /* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* разрешая заполнить весь буфер,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, BUFSIZ) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); }
    /* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); }
    /* Дадим потокам повыполняться */ sleep (10);
    /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);
    /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL);
    /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); }
    if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }
    printf ("Сумма сгенерированных чисел: %d\n", sum);
    return 0; }
    Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров.
    Закрыть окно




    /* Обедающие философы. Многопотоковая реализация с помощью семафоров. Запуск: mudrecSem [-a | -p | -I] [-t число_секунд] имя_философа ...
    Опции: -t число_секунд - сколько секунд моделируется
    Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -p - сначала захватывается нечетная вилка; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется.
    Пример запуска: mudrecSem -p -t 300 A B C D E F G H I J K L M N\ O P Q R S T U V W X Y Z */
    static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecSem.c,v 1.2 2004/03/18 10:28:38 sambor Exp $";
    #include #include #include #include #include #include #include #include #include #include
    #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))
    struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra;
    /* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */
    /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])
    /* Массив семафоров для синхронизации доступа к вилкам */ sem_t *semFork;
    /* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_odd (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this);
    /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;
    /* Возвращение вилок */ static void put_forks (struct mudrec *this);
    /* * Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn;
    while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL);
    sprintf (buffer, "%s: хочет есть\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));
    (*get_forks) (this);
    wait_time = time (NULL) - tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time);
    sprintf (buffer,"%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }
    /* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; }
    /* Ест */ { int eat_time = rand () % 20 + 1;
    sleep (eat_time);
    this->eat_time += eat_time; this->count++; sprintf (buffer,"%s: ел %d сек\n", this->name, eat_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }
    /* Отдает вилки */ put_forks (this);
    if (Stop) break;
    /* Размышляет */ { int think_time = rand () % 10 + 1;
    sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */
    sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));
    close (private_pFdIn);
    return (NULL); } /* Поток-философ */
    /* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { sem_post (&semFork [this->left_fork - 1]); sem_post (&semFork [this->right_fork - 1]); }
    /* Берет вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork);
    sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }
    /* Берем сначала нечетную вилку */ /* (если обе нечетные - то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
    int first; int last;
    if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); }
    sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }
    /* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
    for (;;) { sem_wait (&semFork [left - 1]); if (0 == sem_trywait (&semFork [right - 1])) return; sem_post (&semFork [left - 1]); sem_wait (&semFork [right - 1]); if (0 == sem_trywait (&semFork [left - 1])) return; sem_post (&semFork [right - 1]); } }
    /* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; }
    static void usage (char name []) { fprintf (stderr, "Использование: %s [-a | -p | -I] [-t число_секунд] " "имя_философа ...\n", name); exit (1); }
    /* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact;
    while ((c = getopt (argc, argv, "apIt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } }
    nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух */ /* философов неинтересно ... */
    /* Создание канала для протокола обработки событий */ pipe (protokol);
    kafedra = calloc (sizeof (struct mudrec), nMudr);
    /* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0); /* Укажем новичку, какими вилками пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } kafedra [nMudr - 1].right_fork = 1; /* Последний*/ /* пользуется вилкой первого */
    /* Зададим реакцию на сигналы и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, ( struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time);
    /* Создадим семафоры для охраны вилок */ semFork = calloc (sizeof (sem_t), nMudr); for (i = 0; i < nMudr; i++) { sem_init (&semFork [i], 0, 1 /* На каждое место */ /* по одной вилке */); }
    /* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create (&kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]);
    /* Выдача сообщений на стандартный вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);
    /* Уничтожение семафоров */ for (i = 0; i < nMudr; i++) { sem_destroy (&semFork [i]); }
    /* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i - 1];
    full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time;
    if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count;
    printf ("%s: ел %d раз в среднем: думал=%.1f " "ел=%.1f ждал=%.1f (максимум %d)\n", this->name, this->count, think_time, eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */
    { float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr;
    printf("Среднее число одновременно едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */
    free (semFork); free (kafedra);
    /* Сообщим об окончании работы. */ printf ("Конец обеда\n");
    return 0; }
    Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени.
    Закрыть окно




    #include
    int shm_open (const char *name, int oflag, mode_t mode);
    int shm_unlink (const char *name);
    Листинг 4.20. Описание функций shm_open() и shm_unlink().
    Закрыть окно




    #ifndef g_SHM #define g_SHM
    /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"
    /* Используемый номер сигнала реального времени */ #define SIG_SHM SIGRTMIN
    /* Используемые значения сигнала реального времени */ #define SIGVAL_LINE 0 #define SIGVAL_EOF EOF
    #endif
    Листинг 4.21. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, копирует */ /* строки со стандартного ввода на стандартный вывод,*/ /* "прокачивая" их через разделяемый сегмент памяти. */ /* Для синхронизации доступа к разделяемому сегменту */ /* используются сигналы реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include
    #include "g_shm.h"
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* чтение со стандартного ввода и запись строк в сегмент*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти*/ FILE *fp; /* Поток для записи в объект */ char line [LINE_MAX]; /* Буфер для копируемых строк */ struct sigaction sact; /* Структура для обработки сигналов */ union sigval sg_val;/* Значение сигнала */ int sg_no; /* Номер принятого сигнала */ pid_t cpid;/* Идентификатор порожденного процесса */ /* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); }
    /* Сформируем поток данных по файловому дескриптору */ /* объекта в разделяемой памяти */ assert ((fp = fdopen (fd_shm, "w")) != NULL);
    /* Отменим буферизацию вывода */ setbuf (fp, NULL);
    /* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&sact.sa_mask); (void) sigaddset (&sact.sa_mask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &sact.sa_mask, (sigset_t *) NULL);
    /* Установим для сигнала SIG_SHM флаг SA_SIGINFO */ sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = (void (*) (int, siginfo_t *, void *)) SIG_DFL; (void) sigaction (SIG_SHM, &sact, (struct sigaction *) NULL);
    /* Подготовительная работа закончена */
    switch (cpid = fork ()) { case -1: perror ("FORK"); return (2); case 0: /* Чтение из объекта и выдачу на стандартный */ /* вывод реализуем в порожденном процессе */ if (execl ("./g_r_shm", "g_r_shm", (char *) NULL) < 0) { perror ("EXECL"); return (3); } }
    /* Чтение со стандартного ввода и запись в объект */ /* возложим на родительский процесс.*/ /* В начальный момент объект в разделяемой памяти*/ /* доступен для записи */ assert (fseek (fp, 0, SEEK_SET) == 0); fputs ("Вводите строки\n", fp); /* Сообщим порожденному процессу,*/ /* что объект в разделяемой памяти заполнен*/ sg_val.sival_int = SIGVAL_LINE; assert (sigqueue (cpid, SIG_SHM, sg_val) == 0);
    while (fgets (line, sizeof (line), stdin) != NULL) { assert (fseek (fp, 0, SEEK_SET) == 0); /* Дождемся, когда в объект можно будет писать */ if ((sigwait (&sact.sa_mask, &sg_no) != 0) || (sg_no != SIG_SHM)) { return (4); } assert (fputs ("Вы ввели: ", fp) != EOF); assert (fputs (line, fp) != EOF); assert (sigqueue (cpid, SIG_SHM, sg_val) == 0); } /* Сообщим о конце файла */ sg_val.sival_int = SIGVAL_EOF; assert (sigqueue (cpid, SIG_SHM, sg_val) == 0);
    fclose (fp);
    (void) wait (NULL);
    if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (7); }
    return (0); }
    Листинг 4.22. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс читает строки из объекта в разделяемой памяти */ /* и копирует их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include
    #include "g_shm.h"
    /* * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти,*/ /* чтение из сегмента и выдача строк */ /* на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта */ /* в разделяемой памяти */ FILE *fp; /* Поток для чтения из объекта */ char line [LINE_MAX];/* Буфер для копируемых строк */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения */ /* данных о сигнале */ pid_t ppid; /* Идентификатор родительского */ /* процесса */
    /* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); }
    /* Сформируем поток по файловому дескриптору объекта */ /* в разделяемой памяти */ assert ((fp = fdopen (fd_shm, "r")) != NULL);
    /* Отменим буферизацию ввода */ setbuf (fp, NULL);
    /* Запомним идентификатор родительского процесса */ ppid = getppid ();
    /* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM);
    /* Подготовительная работа закончена */
    while ((fseek (fp, 0, SEEK_SET) == 0) && /* Дождемся, когда из объекта можно будет читать */ (sigwaitinfo (&smask, &sinfo) == SIG_SHM) && /* И прочитаем строку, а не конец файла */ (sinfo.si_value.sival_int == SIGVAL_LINE)) { (void) fgets (line, sizeof (line), fp); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); /* Выдадим, наконец, строку на стандартный вывод */ assert (fputs (line, stdout) != EOF); }
    fclose (fp);
    return 0; }
    Листинг 4.23. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно



    Разделяемые сегменты памяти


    Разделяемые сегменты памяти, называемые далее объектами в разделяемой памяти, - самый эффективный способ передачи данных между процессами. Одной операцией записи можно передать данные сразу многим процессам, разделяющим тот же объект.
    Объекты в разделяемой памяти полезны как для систем, поддерживающих виртуальную память и раздельные адресные пространства для процессов, так и для встроенных систем с минимальной аппаратной поддержкой.
    Минимальный мобильный программный интерфейс к объектам в разделяемой памяти включает функции открытия (возможно, с созданием) подобного объекта и получения его дескриптора, а также удаления ранее созданного объекта.
    Стандартный программный интерфейс состоит из двух функций: shm_open() и shm_unlink() (см. листинг 4.19).
    #include
    int shm_open (const char *name, int oflag, mode_t mode);
    int shm_unlink (const char *name);
    Листинг 4.20. Описание функций shm_open() и shm_unlink(). (html, txt)
    При открытии с помощью функции shm_open() возвращается файловый дескриптор. Имя (аргумент name) трактуется стандартным для рассматриваемых средств межпроцессного взаимодействия образом. Посредством аргумента oflag могут указываться флаги O_RDONLY, O_RDWR, O_CREAT, O_EXCL и/или O_TRUNC. Если объект создается, то режим доступа к нему формируется в соответствии со значением аргумента mode и маской создания файлов процесса.
    После создания объект в разделяемой памяти существует, пока не будет удален функцией shm_unlink(). Он сохраняет свое состояние после закрытия всех ссылающихся на него дескрипторов, однако эффект от перезагрузки системы стандарт POSIX-2001 не специфицирует.
    Представляется естественным, что способ доступа к объектам определяется типом дескриптора, возвращаемого при их открытии. Если это адрес, то доступ сводится к операциям чтения/записи из/в память. Если это файловый дескриптор, то для доступа должны использоваться функции файлового ввода/вывода - read(), write() и т.п. Подобное естественное применение объектов в разделяемой памяти иллюстрируется двухпроцессной программой, копирующей строки со стандартного ввода на стандартный вывод (см.
    листинги 4.21, 4.22, 4.23). Предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".

    #ifndef g_SHM #define g_SHM

    /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"

    /* Используемый номер сигнала реального времени */ #define SIG_SHM SIGRTMIN

    /* Используемые значения сигнала реального времени */ #define SIGVAL_LINE 0 #define SIGVAL_EOF EOF

    #endif

    Листинг 4.21. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)

    Листинг 4.22. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)

    Листинг 4.23. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)

    Объект в разделяемой памяти используется в приведенной программе как буфер на один элемент (одну строку). Чтение и запись производятся с начала объекта, для чего применяется файловое позиционирование. Разумеется, в такой ситуации порожденный процесс не может обычным образом обнаружить конец передаваемого ему файла, поэтому приходится применять дополнительные средства. В данном случае это сигналы реального времени, которые несут двойную нагрузку - обеспечивают синхронизацию доступа к объекту в разделяемой памяти и передают от родительского процесса порожденному информацию о конце файла.

    К сожалению, стандарт POSIX-2001 не следует приведенным выше естественным предположениям, касающимся связи между типом дескриптора и способом доступа к объекту, так что приведенная программа, строго говоря, не соответствует стандарту. Дело в том, что файловый дескриптор, возвращаемый функцией shm_open(), является дескриптором "второго сорта" (хотя и первой свежести): результат применения к нему функций fdopen(), read(), write() и т.п. не специфицирован.

    Далее мы увидим, для чего этот дескриптор можно употребить и как организовать межпроцессное взаимодействие через разделяемые сегменты памяти реального времени в полном соответствии с положениями стандарта POSIX-2001.Здесь же отметим, что в большинстве реализаций для представления разделяемых сегментов применяются файлы, отображенные в память, так что их дескрипторы вполне пригодны для выполнения файловых операций, а мобильность приведенной программы с практической точки зрения можно считать удовлетворительной.


    Семафоры реального времени


    Семафор реального времени - это эффективный механизм синхронизации процессов, представляющий собой общесистемный разделяемый ресурс, имеющий неотрицательное целочисленное значение.
    Основными операциями над семафором являются захват и освобождение. Если делается попытка захвата семафора, когда его значение равно нулю, выполнение вызывающего потока управления приостанавливается и он добавляется к множеству потоков, ждущих на семафоре; в противном случае значение уменьшается.
    Если при освобождении семафора множество ждущих потоков было непусто, один из них удаляется из этого множества и его выполнение возобновляется; в противном случае значение семафора просто увеличивается.
    Семафоры бывают именованными и безымянными (неименованными). Первые именуются цепочками символов и создаются функцией sem_open() с флагом O_CREAT, вторые создаются функцией sem_init(). При прочих операциях семафор идентифицируется открытым дескриптором (который может быть унаследован у родительского процесса, вызвавшего fork()). Дескриптор реализуется как указатель на объект типа sem_t.
    Перед выполнением операций семафор необходимо инициализировать, задав неотрицательное значение. Отрицательные значения (точнее, их абсолютная величина) могут использоваться реализацией для указания числа ждущих потоков управления.
    Семафор сохраняет свое состояние после закрытия последней ссылки на него, то есть если позднее он будет вновь открыт, его значение окажется тем же, что и перед закрытием.
    Детальное описание функций, обслуживающих семафоры, мы начнем, разумеется, с sem_open() и sem_init() (см. листинг 4.11), которые обеспечивают открытие, создание и инициализацию.
    #include
    sem_t *sem_open (const char *name, int oflag, ...);
    int sem_init (sem_t *sem, int pshared, unsigned value);
    Листинг 4.11. Описание функций sem_open() и sem_init(). (html, txt)
    Имена семафоров (аргумент name функции sem_open()) устроены и трактуются так же, как и описанные выше имена очередей сообщений. Флагов (в аргументе oflag) может быть установлено два: O_CREAT и/или O_EXCL.
    Если установлен флаг O_CREAT, то при вызове функции sem_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и значение семафора (тип unsigned int).

    В случае ошибки функция sem_open() возвращает значение SEM_FAILED, отличное от любого допустимого указателя на объект типа sem_t, а sem_init() "по старинке" возвращает -1.

    Отметим, что нормальный результат для функции sem_init() в POSIX-2001 не стандартизован; вероятно, в будущих версиях им станет нуль. Инициализированный объект типа sem_t помещается по указателю sem. Аргумент value задает начальное значение создаваемого неименованного семафора. Если аргумент pshared отличен от нуля, семафор разделяется между процессами; в противном случае разделение возможно только между потоками управления вызывающего процесса.

    Именованный семафор можно закрыть, обратившись к функции sem_close(), и удалить с помощью функции sem_unlink(); для ликвидации неименованных семафоров служит функция sem_destroy() (см. листинг 4.12). Нормальный результат этих функций равен нулю, в случае ошибки возвращается -1.

    #include

    int sem_close (sem_t *sem);

    int sem_unlink (const char *name);

    int sem_destroy (sem_t *sem);

    Листинг 4.12. Описание функций закрытия и ликвидации семафоров. (html, txt)

    Отметим, что эффект от вызова sem_close() для неименованного семафора, равно как и последствия вызова sem_destroy() для семафора именованного, не определены. Также не определен результат применения функции sem_destroy() к семафору, на котором имеются ждущие потоки управления.

    Для захвата семафоров служат функции sem_wait(), sem_trywait() и sem_timedwait() (см. листинги 4.13 и 4.14).

    #include

    int sem_wait (sem_t *sem);

    int sem_trywait (sem_t *sem);

    Листинг 4.13. Описание функций захвата семафоров. (html, txt) #include #include int sem_timedwait (sem_t *restrict sem, const struct timespec *restrict abstime);

    Листинг 4.14. Описание функции захвата семафора с контролем времени ожидания. (html, txt)



    Если значение семафора было положительным, все три перечисленные функции без каких-либо задержек завершаются успехом, уменьшая это значение до нуля и возвращая нулевой результат. В противном случае вызов sem_trywait() завершается неудачей, вызов sem_wait() блокируется до освобождения семафора, вызов sem_timedwait() также блокируется, но с контролем времени ожидания.

    Освобождение семафора осуществляется функцией sem_post() (см. листинг 4.15). Для возобновления выполнения (если таковое имеет место) выбирается наиболее приоритетный поток управления, а среди потоков с равными приоритетами - тот, что ждал дольше других.

    #include int sem_post (sem_t *sem);

    Листинг 4.15. Описание функции sem_post(). (html, txt)

    Функция sem_getvalue() (см. листинг 4.16) позволяет опросить значение семафора, не меняя его состояния.

    #include int sem_getvalue (sem_t *restrict sem, int *restrict sval);

    Листинг 4.16. Описание функции sem_getvalue(). (html, txt)

    Значение семафора записывается по указателю sval. Если семафор был захвачен, это значение окажется нулевым или отрицательным.

    Если сопоставить семафоры реального времени с теми, что были описаны в курсе [1], то можно сделать те же выводы, что и для очередей сообщений. Семафоры реального времени, согласно стандарту POSIX-2001, по сути являются бинарными. Они устроены проще и, следовательно, могут быть реализованы эффективнее. Главное - отсутствуют тяжеловесные, со сложной семантикой, трудные для реализации групповые операции.

    Использование семафоров реального времени проиллюстрируем программой, реализующей взаимодействие поставщик/потребитель (см. листинг 4.17).

    Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель. (html, txt)

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

    Отметим, что семафоры ликвидируются после терминирования использующих их потоков управления, что, согласно стандарту POSIX-2001, является безопасным.



    Поясним и обсудим сделанное выше замечание о том, что, согласно стандарту POSIX-2001, семафоры реального времени по сути являются бинарными. Такой вывод можно сделать по двум фразам из описания функции sem_trywait(). Во-первых, функция sem_trywait() захватывает семафор только в том случае, если он еще не захвачен, то есть если значение семафора положительно. Во-вторых, в случае успешного завершения семафор захватывается и должен оставаться в этом состоянии, пока не будет успешно выполнена функция sem_post().

    Здесь ничего не говорится об уменьшении положительного значения без захвата семафора, зато намеки на это имеются в части, где приводятся детальное разъяснение положений стандарта и обоснование принятых решений. Смущает и то, что в описании функции sem_post() не запрещается освобождать свободный семафор, что, естественно, выливается в увеличение его значения. Далее, в разных местах семафоры называются то бинарными, то целочисленными. Наконец, автору не известно ни одной реализации, где семафоры реального времени не были бы целочисленными. В общем, сложное это дело - толкование внутренне противоречивых стандартов, вступающих в конфликт с реальной жизнью...

    Если считать, что рассматриваемые семафоры являются целочисленными, приведенную выше программу можно усовершенствовать, сделав буфер кольцевым, с размером, большим единицы (см. листинг 4.18).

    Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров. (html, txt)

    Значение семафора w_sem равно числу элементов буфера, доступных для записи, r_sem - для чтения. Многопотоковым инвариантом программы является сумма этих величин, равная размеру буфера. В начальный момент буфер целиком доступен для записи. После этого вызовы sem_wait() и sem_post() уменьшают значение "своего" и увеличивают значение "чужого" семафора. Поток управления приостанавливается в sem_wait(), когда "свое" значение уменьшается до нуля, у нужно ждать, пока другой поток вызовом sem_post() не увеличит его, сделав положительным.

    Разумеется, обсуждение темы семафоров было бы неполным без обеда философов. Мы приведем программу, написанную С.В. Самборским (см. листинг 4.19). В ней семафоры используются как бинарные, поэтому ее стандартность и мобильность не вызывают сомнений.

    Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени. (html, txt)



    /* Дадим потокам повыполняться */ sleep (10);

    /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);

    /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL); /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); } if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }

    printf ("Сумма сгенерированных чисел: %d\n", sum);

    return 0; }

    Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель.

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

    Отметим, что семафоры ликвидируются после терминирования использующих их потоков управления, что, согласно стандарту POSIX-2001, является безопасным.

    Поясним и обсудим сделанное выше замечание о том, что, согласно стандарту POSIX-2001, семафоры реального времени по сути являются бинарными. Такой вывод можно сделать по двум фразам из описания функции sem_trywait(). Во-первых, функция sem_trywait() захватывает семафор только в том случае, если он еще не захвачен, то есть если значение семафора положительно. Во-вторых, в случае успешного завершения семафор захватывается и должен оставаться в этом состоянии, пока не будет успешно выполнена функция sem_post().

    Здесь ничего не говорится об уменьшении положительного значения без захвата семафора, зато намеки на это имеются в части, где приводятся детальное разъяснение положений стандарта и обоснование принятых решений. Смущает и то, что в описании функции sem_post() не запрещается освобождать свободный семафор, что, естественно, выливается в увеличение его значения. Далее, в разных местах семафоры называются то бинарными, то целочисленными. Наконец, автору не известно ни одной реализации, где семафоры реального времени не были бы целочисленными.


    В общем, сложное это дело - толкование внутренне противоречивых стандартов, вступающих в конфликт с реальной жизнью...

    Если считать, что рассматриваемые семафоры являются целочисленными, приведенную выше программу можно усовершенствовать, сделав буфер кольцевым, с размером, большим единицы (см. листинг 4.18).

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа */ /* и помещает их в кольцевой буфер, */ /* потребитель извлекает их оттуда и суммирует.*/ /* Для синхронизации используются */ /* неименованные семафоры */ /* * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include

    /* Буфер для хранения генерируемых данных */ static int my_buf [BUFSIZ];

    /* Индекс, по которому можно записать очередной элемент */ static int w_ind = 0;

    /* Индекс, по которому можно прочитать очередной элемент */ static int r_ind = 0;

    /* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem;

    /* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem;

    /* Общий результат суммирования */ static int sum = 0;

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа*/ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf [w_ind] = rand (); w_ind = (w_ind + 1) % BUFSIZ; if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } }

    return (NULL); }

    /* * * * * * * * * * * * * * * */ /* Стартовая функция потока, */ /* читающего и суммирующего числа*/ /* * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf [r_ind]; r_ind = (r_ind + 1) % BUFSIZ; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } }



    return (NULL); }

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */

    /* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* разрешая заполнить весь буфер,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, BUFSIZ) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); }

    /* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); }

    /* Дадим потокам повыполняться */ sleep (10);

    /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);

    /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL);

    /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); }

    if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }

    printf ("Сумма сгенерированных чисел: %d\n", sum);

    return 0; }

    Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров.

    Значение семафора w_sem равно числу элементов буфера, доступных для записи, r_sem - для чтения. Многопотоковым инвариантом программы является сумма этих величин, равная размеру буфера. В начальный момент буфер целиком доступен для записи. После этого вызовы sem_wait() и sem_post() уменьшают значение "своего" и увеличивают значение "чужого" семафора. Поток управления приостанавливается в sem_wait(), когда "свое" значение уменьшается до нуля, у нужно ждать, пока другой поток вызовом sem_post() не увеличит его, сделав положительным.



    Разумеется, обсуждение темы семафоров было бы неполным без обеда философов. Мы приведем программу, написанную С.В. Самборским (см. листинг 4.19). В ней семафоры используются как бинарные, поэтому ее стандартность и мобильность не вызывают сомнений.

    /* Обедающие философы. Многопотоковая реализация с помощью семафоров. Запуск: mudrecSem [-a | -p | -I] [-t число_секунд] имя_философа ...

    Опции: -t число_секунд - сколько секунд моделируется

    Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -p - сначала захватывается нечетная вилка; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется.

    Пример запуска: mudrecSem -p -t 300 A B C D E F G H I J K L M N\ O P Q R S T U V W X Y Z */

    static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecSem.c,v 1.2 2004/03/18 10:28:38 sambor Exp $";

    #include #include #include #include #include #include #include #include #include #include

    #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))

    struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra;

    /* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */

    /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])

    /* Массив семафоров для синхронизации доступа к вилкам */ sem_t *semFork;

    /* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_odd (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this);

    /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;

    /* Возвращение вилок */ static void put_forks (struct mudrec *this);



    /* * Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn;

    while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL);

    sprintf (buffer, "%s: хочет есть\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));

    (*get_forks) (this);

    wait_time = time (NULL) - tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time);

    sprintf (buffer,"%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }

    /* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; }

    /* Ест */ { int eat_time = rand () % 20 + 1;

    sleep (eat_time);

    this->eat_time += eat_time; this->count++; sprintf (buffer,"%s: ел %d сек\n", this->name, eat_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }

    /* Отдает вилки */ put_forks (this);

    if (Stop) break;

    /* Размышляет */ { int think_time = rand () % 10 + 1;

    sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */

    sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));

    close (private_pFdIn);

    return (NULL); } /* Поток-философ */

    /* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { sem_post (&semFork [this->left_fork - 1]); sem_post (&semFork [this->right_fork - 1]); }

    /* Берет вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork);

    sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }

    /* Берем сначала нечетную вилку */ /* (если обе нечетные - то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;



    int first; int last;

    if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); }

    sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }

    /* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;

    for (;;) { sem_wait (&semFork [left - 1]); if (0 == sem_trywait (&semFork [right - 1])) return; sem_post (&semFork [left - 1]); sem_wait (&semFork [right - 1]); if (0 == sem_trywait (&semFork [left - 1])) return; sem_post (&semFork [right - 1]); } }

    /* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; }

    static void usage (char name []) { fprintf (stderr, "Использование: %s [-a | -p | -I] [-t число_секунд] " "имя_философа ...\n", name); exit (1); }

    /* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact;

    while ((c = getopt (argc, argv, "apIt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } }

    nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух */ /* философов неинтересно ... */

    /* Создание канала для протокола обработки событий */ pipe (protokol);

    kafedra = calloc (sizeof (struct mudrec), nMudr);

    /* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0); /* Укажем новичку, какими вилками пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } kafedra [nMudr - 1].right_fork = 1; /* Последний*/ /* пользуется вилкой первого */



    /* Зададим реакцию на сигналы и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, ( struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time);

    /* Создадим семафоры для охраны вилок */ semFork = calloc (sizeof (sem_t), nMudr); for (i = 0; i < nMudr; i++) { sem_init (&semFork [i], 0, 1 /* На каждое место */ /* по одной вилке */); }

    /* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create (&kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]);

    /* Выдача сообщений на стандартный вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);

    /* Уничтожение семафоров */ for (i = 0; i < nMudr; i++) { sem_destroy (&semFork [i]); }

    /* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i - 1];

    full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time;

    if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count;

    printf ("%s: ел %d раз в среднем: думал=%.1f " "ел=%.1f ждал=%.1f (максимум %d)\n", this->name, this->count, think_time, eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */

    { float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr;

    printf("Среднее число одновременно едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */

    free (semFork); free (kafedra);

    /* Сообщим об окончании работы. */ printf ("Конец обеда\n");

    return 0; }

    Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени.


    Объекты в типизированной памяти


    Объекты в типизированной памяти - это конфигурируемые реализацией именованные пулы памяти, доступные одному или нескольким процессорам системы через один или несколько портов. Пример подобной конфигурации показан на рис. 5.1.
    Объекты в типизированной памяти
    Рис. 5.1.  Пример многопроцессорной системы с несколькими пулами памяти
    Здесь пул памяти M4 доступен только процессору P2. Пул M2 включает в себя пулы M2.1 и M2.2, причем доступ к M2.1 возможен только через порт B1. Остальные пулы доступны обоим процессорам через указанные на рисунке порты.
    Разные пулы памяти могут обладать различными операционными характеристиками. Это может быть статическая или динамическая память, ПЗУ, энергонезависимая память и т.п.
    Каждая допустимая комбинация пула памяти и порта идентифицируется именем, определяемым при конфигурировании системы способом, зависящим от реализации. Используя это имя, объект в типизированной памяти можно открыть и отобразить в адресное пространство процесса. Реализация должна поддерживать как динамическое выделение (резервирование) памяти из пула (аналог malloc(), когда приложение задает только запрашиваемый объем), так и отображение части пула с заданным в приложении смещением от начала. Зарезервированный фрагмент в последующем подлежит освобождению. Наконец, при доступе через определенный порт нужно иметь возможность выяснить смещение и размер непрерывного участка типизированной памяти.
    Согласно стандарту POSIX-2001, для открытия объектов в типизированной памяти служит функция posix_typed_mem_open() (см. листинг 5.11).
    #include int posix_typed_mem_open (const char *name, int oflag, int tflag);
    Листинг 5.11. Описание функции posix_typed_mem_open(). (html, txt)
    Нормальным результатом функции posix_typed_mem_open() является файловый дескриптор объекта в типизированной памяти с именем name.
    Аргумент oflag задает один из трех разрешенных видов доступа к объекту - O_RDONLY, O_WRONLY или O_RDWR.
    Значение аргумента tflag определяет поведение объекта в типизированной памяти при последующих отображениях посредством функции mmap().
    Может быть установлен один из трех флагов:

    POSIX_TYPED_MEM_ALLOCATE

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

    POSIX_TYPED_MEM_ALLOCATE_CONTIG

    При вызове mmap() резервировать и отображать один непрерывный сегмент памяти.

    POSIX_TYPED_MEM_MAP_ALLOCATABLE

    При вызове mmap() отображать объект в адресное пространство процесса, не воздействуя на возможности резервирования памяти.

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

    Отметим, что, в отличие от объектов в разделяемой памяти, объекты в типизированной памяти нельзя создать - их можно только открыть. Не применима к ним (точнее, к их открытым дескрипторам) и функция установки размера ftruncate().

    После того, как объект в типизированной памяти открыт, посредством функции posix_typed_mem_get_info() (см. листинг 5.12) можно выяснить максимальный объем памяти, доступной для резервирования. Это важно, поскольку типизированная память зачастую является дефицитным ресурсом.

    #include int posix_typed_mem_get_info (int fildes, struct posix_typed_mem_info *info);

    Листинг 5.12. Описание функции posix_typed_mem_get_info(). (html, txt)

    Соответствующая информация возвращается в структуре типа posix_typed_mem_info, на которую указывает аргумент info. Согласно стандарту POSIX-2001, эта структура должна содержать поле

    size_t posix_tmi_length; /* Максимальный объем памяти, */ /* доступной для резервирования*/

    Функция posix_typed_mem_get_info() принимает во внимание флаг POSIX_TYPED_MEM_ALLOCATE или POSIX_TYPED_MEM_ALLOCATE_CONTIG, если он был установлен при открытии объекта в типизированной памяти. При отсутствии этих флагов результат вызова posix_typed_mem_get_info() не специфицирован.



    Разумеется, возвращаемый максимальный объем памяти, доступной для резервирования, есть величина, динамически изменяющаяся. Если объект в типизированной памяти является разделяемым, то нет гарантии, что в интервале между опросом и попыткой резервирования этот объем останется прежним.

    Еще одним проявлением общего принципа "если объект может измениться, должна быть возможность опросить его текущее состояние" является наличие в стандарте POSIX-2001 функции posix_mem_offset() (см. листинг 5.13).

    #include int posix_mem_offset ( const void *restrict addr, size_t len, off_t *restrict off, size_t *restrict contig_len, int *restrict fildes);

    Листинг 5.13. Описание функции posix_mem_offset(). (html, txt)

    Функция posix_mem_offset() позволяет выяснить адрес (смещение от начала), длину и дескриптор объекта (блока) в типизированной памяти, отображенного в адресное пространство процесса, начиная с адреса addr и имеющего длину len. Искомое смещение записывается по указателю off. По указателю contig_len помещается минимум из значения len и длины максимального непрерывного блока типизированной памяти, отображенного, начиная с addr. По указателю fildes выдается дескриптор, использованный в вызове mmap() при задании опрашиваемого отображения (если с тех пор дескриптор закрыли, выдается -1).

    Если дескриптор объекта в типизированной памяти был открыт без флагов POSIX_TYPED_MEM_ALLOCATE и POSIX_TYPED_MEM_ALLOCATE_CONTIG, то возвращенные функцией posix_mem_offset() значения, будучи подставлены в вызов mmap(), зададут отображение в точности того же блока типизированной памяти, что и при первоначальном обращении к mmap().

    Поведение функции posix_mem_offset() при попытке опросить характеристики отображения объекта, не являющегося объектом в типизированной памяти, зависит от реализации.

    Рассмотрим возможную последовательность применения описанных функций. Пусть процессу A1, выполняющемуся на процессоре P1 (см. выше рис. 5.1), требуется зарезервировать блок памяти из пула M2, причем этот блок предполагается использовать совместно с процессом A2, выполняющемся на процессоре P2.


    Поскольку P2 имеет доступ только к M2.2, процессы должны использовать именно эту часть M2.

    В качестве первого шага процесс A1 вызывает функцию posix_typed_mem_open() с именем объекта "/m2.2-b1" (или каким-то еще в том же духе, включающим имена пула и порта) и установленным в аргументе tflag флагом POSIX_TYPED_MEM_ALLOCATE, получая открытый дескриптор, пригодный для резервирования памяти. Затем A1 обращается с этим дескриптором к функции mmap(), указывая в качестве длины довольно большое значение, например, 1048576. Пусть в ответ на запрос система выделяет два несмежных блока типизированной памяти длиной 524288 байт каждый, отображает их в один непрерывный фрагмент адресного пространства процесса A1 и возвращает указатель на него. Теперь процесс A1 может стандартным образом работать с полученным мегабайтным массивом.

    Если процесс A1 пожелает выяснить, какие части пула M2.2 были зарезервированы, он должен обратиться к функции posix_mem_offset() с адресом первого элемента массива и полной длиной (1048576). В ответ он получит смещение и длину первого из выделенных блоков (524288). Поскольку возвращенная длина меньше запрошенной, требуется повторный вызов posix_mem_offset() со смещением в полмегабайта от начала массива и соответственно уменьшенной длиной. (В общем случае, разумеется, следует организовать цикл, вызывая posix_mem_offset() до тех пор, пока не будет исчерпан весь отображенный массив. Чтобы избежать подобных относительно сложных действий, можно при открытии объекта в типизированной памяти установить в tflag флаг POSIX_TYPED_MEM_ALLOCATE_CONTIG, но тогда, возможно, попытка резервирования непрерывного мегабайтного блока закончится неудачей.)

    Чтобы обеспечить совместное использование типизированной памяти с процессом A2, A1 должен каким-то образом (очевидно, с помощью средств межпроцессного взаимодействия) передать тому смещения и длины зарезервированных блоков. Получив эти данные, A2 обращается к posix_typed_mem_open() с именем "/m2.2-b2" (доступ через порт B2) и нулевым значением tflag, а затем дважды вызывает mmap() для отображения обоих непрерывных фрагментов типизированной памяти в свое адресное пространство.Вероятно, при втором вызове целесообразно указать соответствующий адрес начала и флаг MAP_FIXED, чтобы "склеить" эти отображения и, как и в случае с A1, получить непрерывный мегабайтный массив.


    Отображение объектов в адресное пространство процессов


    Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в адресное пространство процесса, то доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия, так как данные, записанные в объект одним процессом, появляются в адресных пространствах всех участников отображения.
    Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
    Реализация должна обеспечивать, чтобы отображению подвергалось целое число страниц памяти из адресного пространства процесса. Размер страницы является значением конфигурационной переменной PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
    Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к генерации сигнала SIGBUS.
    К отображенным страницам могут предоставляться различные виды доступа: на чтение, запись и/или выполнение. При попытке нарушить наложенные ограничения процессу доставляется сигнал SIGSEGV.
    Отображение объектов в адресное пространство процессов осуществляется функцией mmap() (см. листинг 5.1).
    #include void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off);
    Листинг 5.1. Описание функции mmap(). (html, txt)
    Отображаемая часть объекта задается файловым дескриптором fildes, смещением off и длиной len. (Вот для чего, заметим в скобках, при открытии объекта в разделяемой памяти функцией shm_open() возвращается файловый дескриптор - чтобы передать его mmap() и отобразить объект в адресное пространство процесса, а вовсе не для того, чтобы применять к нему операции файлового ввода/вывода. В исторически сложившихся реализациях функция mmap() появилась как средство отображения в память обычных файлов, так что с расширением функциональности другие виды отображаемых объектов пришлось стричь под ту же гребенку.)

    Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в адресное пространство процесса, то доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия, так как данные, записанные в объект одним процессом, появляются в адресных пространствах всех участников отображения.
    Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
    Реализация должна обеспечивать, чтобы отображению подвергалось целое число страниц памяти из адресного пространства процесса. Размер страницы является значением конфигурационной переменной PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
    Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к генерации сигнала SIGBUS.
    К отображенным страницам могут предоставляться различные виды доступа: на чтение, запись и/или выполнение. При попытке нарушить наложенные ограничения процессу доставляется сигнал SIGSEGV.
    Отображение объектов в адресное пространство процессов осуществляется функцией mmap() (см. листинг 5.1).
    #include void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off);
    Листинг 5.1. Описание функции mmap().
    Отображаемая часть объекта задается файловым дескриптором fildes, смещением off и длиной len. (Вот для чего, заметим в скобках, при открытии объекта в разделяемой памяти функцией shm_open() возвращается файловый дескриптор - чтобы передать его mmap() и отобразить объект в адресное пространство процесса, а вовсе не для того, чтобы применять к нему операции файлового ввода/вывода. В исторически сложившихся реализациях функция mmap() появилась как средство отображения в память обычных файлов, так что с расширением функциональности другие виды отображаемых объектов пришлось стричь под ту же гребенку.)



    Значения аргументов addr, prot и flags задают характеристики отображения - рекомендуемый адрес в адресном пространстве процесса, запрашиваемые виды доступа к отображенным страницам и управляющие флаги.

    Результатом функции mmap() служит выбранный реализацией адрес в адресном пространстве процесса, начиная с которого отображен заданный фрагмент объекта, или, в случае неудачи, значение MAP_FAILED. Если в аргументе flags установлен флаг MAP_FIXED, результирующий адрес должен совпадать со значением addr; предполагается, что приложение располагает знаниями о целевой архитектуре, достаточными для точной спецификации отображения, что, впрочем, отрицательным образом сказывается на мобильности. Если флаг MAP_FIXED не установлен, а значение addr равно NULL, реализация имеет полную свободу в выборе результирующего адреса; подобную комбинацию следует рекомендовать как максимально мобильную.

    В значении аргумента flags, помимо MAP_FIXED, могут быть установлены флаги MAP_SHARED (запись в отображенную память изменяет отображаемый объект со всеми вытекающими отсюда последствиями) или MAP_PRIVATE (изменения в отображенной памяти видны только записывающему процессу и не распространяются на отображаемый объект). Стандарт POSIX-2001 не специфицирует, имеет ли место обратная зависимость, то есть видны ли процессу, установившему флаг MAP_PRIVATE, изменения в отображаемом объекте.

    Аргумент prot, определяющий разрешенные виды доступа к отображенным страницам, строится как комбинация флагов PROT_READ, PROT_WRITE и PROT_EXEC (разрешен доступ, соответственно, на чтение, запись и/или выполнение). Альтернативный вариант - установить флаг PROT_NONE, запрещающий доступ к странице (из общих соображений выключатель всегда должен быть под рукой). Значение аргумента prot должно быть согласовано с флагами, указанными при открытии объекта и получении дескриптора fildes.

    Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect() (см.


    листинг 5.2).

    #include int mprotect (void *addr, size_t len, int prot);

    Листинг 5.2. Описание функции mprotect(). (html, txt)

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

    Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.

    Для отмены отображений в адресное пространство процессов служит функция munmap() (см. листинг 5.3).

    #include int munmap (void *addr, size_t len);

    Листинг 5.3. Описание функции munmap(). (html, txt)

    Отображения отменяются для страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.

    Отображение объектов в адресное пространство процессов - универсальное, исключительно мощное средств. В качестве примера применим его для реализации упрощенного аналога служебной программы cat (см. листинг 5.4).

    Листинг 5.4. Пример программы, использующей файлы, отображенные в память. (html, txt)

    Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.



    Значения аргументов addr, prot и flags задают характеристики отображения - рекомендуемый адрес в адресном пространстве процесса, запрашиваемые виды доступа к отображенным страницам и управляющие флаги.

    Результатом функции mmap() служит выбранный реализацией адрес в адресном пространстве процесса, начиная с которого отображен заданный фрагмент объекта, или, в случае неудачи, значение MAP_FAILED. Если в аргументе flags установлен флаг MAP_FIXED, результирующий адрес должен совпадать со значением addr; предполагается, что приложение располагает знаниями о целевой архитектуре, достаточными для точной спецификации отображения, что, впрочем, отрицательным образом сказывается на мобильности. Если флаг MAP_FIXED не установлен, а значение addr равно NULL, реализация имеет полную свободу в выборе результирующего адреса; подобную комбинацию следует рекомендовать как максимально мобильную.

    В значении аргумента flags, помимо MAP_FIXED, могут быть установлены флаги MAP_SHARED (запись в отображенную память изменяет отображаемый объект со всеми вытекающими отсюда последствиями) или MAP_PRIVATE (изменения в отображенной памяти видны только записывающему процессу и не распространяются на отображаемый объект). Стандарт POSIX-2001 не специфицирует, имеет ли место обратная зависимость, то есть видны ли процессу, установившему флаг MAP_PRIVATE, изменения в отображаемом объекте.

    Аргумент prot, определяющий разрешенные виды доступа к отображенным страницам, строится как комбинация флагов PROT_READ, PROT_WRITE и PROT_EXEC (разрешен доступ, соответственно, на чтение, запись и/или выполнение). Альтернативный вариант - установить флаг PROT_NONE, запрещающий доступ к странице (из общих соображений выключатель всегда должен быть под рукой). Значение аргумента prot должно быть согласовано с флагами, указанными при открытии объекта и получении дескриптора fildes.

    Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect() (см.


    листинг 5.2).

    #include int mprotect (void *addr, size_t len, int prot);

    Листинг 5.2. Описание функции mprotect().

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

    Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.

    Для отмены отображений в адресное пространство процессов служит функция munmap() (см. листинг 5.3).

    #include int munmap (void *addr, size_t len);

    Листинг 5.3. Описание функции munmap().

    Отображения отменяются для страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.

    Отображение объектов в адресное пространство процессов - универсальное, исключительно мощное средств. В качестве примера применим его для реализации упрощенного аналога служебной программы cat (см. листинг 5.4).

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выдает на стандартный вывод файлы,*/ /* имена которых заданы в командной строке */ /* * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include

    int main (int argc, char *argv []) { struct stat sbuf; /* Структура для получения информации */ /* о файле */ int fd; /* Дескриптор обрабатываемого файла */ void *maddr; /* Адрес отображенного в память файла */ int i;

    for (i = 1; i < argc; i++) { if ((fd = open (argv [i], O_RDONLY)) < 0) { perror ("OPEN"); continue; } if (fstat (fd, &sbuf) != 0) { perror ("FSTAT"); close (fd); continue; } if ((maddr = mmap (NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ perror ("MMAP"); close (fd); continue; } if (write (STDOUT_FILENO, maddr, sbuf.st_size) != sbuf.st_size) { perror ("WRITE"); } if (munmap (maddr, sbuf.st_size) != 0) { perror ("MUNMAP"); } close (fd); } /* for */



    return 0; }

    Листинг 5.4. Пример программы, использующей файлы, отображенные в память.

    Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.

    При работе с объектами в памяти полезны функции truncate() и, особенно, ftruncate() (см. листинг 5.5), позволяющие установить размер объекта равным заданной величине length. Напомним, что попытка записи за пределы отображенного объекта не только не расширит его, но, весьма вероятно, приведет к доставке процессу сигнала SIGBUS.

    #include

    int truncate (const char *path, off_t length);

    int ftruncate (int fildes, off_t length);

    Листинг 5.5. Описание функций truncate() и ftruncate.

    Функции truncate() и ftruncate() применимы к обычным файлам, которые в первом случае задаются маршрутным именем, а во втором - открытым дескриптором. Кроме того, функция ftruncate() применима к объектам в разделяемой памяти (и это единственная узаконенная стандартом POSIX-2001 файловая операция над дескриптором подобного объекта). Других способов изменить размер объекта в разделяемой памяти стандарт не предусматривает.

    Применение объектов в разделяемой памяти в сочетании с функциями mmap() и ftruncate() иллюстрируется модифицированным вариантом двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод (см. листинги 5.6, 5.7, 5.8). Как и для исходного варианта, предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".

    #ifndef g_SHM #define g_SHM

    /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"

    /* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM

    #endif

    Листинг 5.6. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. /* * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, */ /* копирует строки со стандартного ввода на*/ /* стандартный вывод, используя в качестве */ /* буфера разделяемый сегмент, отображенный*/ /* в адресные пространства процессов. */ /* Для синхронизации доступа к разделяемому*/ /* сегменту используется сигнал SIGALRM */ /* * * * * * * * * * * * * * * * * * * * * */



    #include #include #include #include #include #include #include #include

    #include "g_shm.h"

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение строк со стандартного ввода в сегмент */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти */ void *addr_shm; /* Адрес отображенного в память объекта */ sigset_t smask; /* Маска блокируемых сигналов */ siginfo_t sinfo;/* Структура для получения данных о сигнале */ /* Длительность ожидания прихода сигнала */ struct timespec stmspc = {10, 0}; pid_t cpid; /* Идентификатор порожденного процесса */

    /* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); }

    /* Сделаем размер созданного объекта равным LINE_MAX */ if (ftruncate (fd_shm, LINE_MAX) != 0) { perror ("FTRUNCATE"); return (2); }

    /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-P"); return (3); }

    /* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &smask, (sigset_t *) NULL);

    /* Подготовительная работа закончена */

    switch (cpid = fork ()) { case -1: perror ("FORK"); return (4); case 0: /* Чтение из объекта и выдачу на стандартный*/ /* вывод реализуем в порожденном процессе*/ if (execl ("./g_r_shm", "g_r_shm", ( char *) NULL) < 0) { perror ("EXECL"); return (5); } }

    /* Чтение строк со стандартного ввода в объект */ /* возложим на родительский процесс. */ /* В начальный момент объект в разделяемой памяти */ /* доступен для записи*/ while (fgets (addr_shm, LINE_MAX, stdin) != NULL) { /* Сообщим порожденному процессу, */ /* что в объект в разделяемой памяти */ /* помещена очередная строка */ assert (kill (cpid, SIG_SHM) == 0); /* Дождемся, когда в объект можно будет прочитать*/ /* следующую строку */ if (sigtimedwait (&smask, &sinfo, &stmspc) != SIG_SHM) { break; } }



    /* Порожденный процесс должен завершиться*/ /* по контролю времени ожидания*/ (void) wait (NULL);

    if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (6); }

    return (0); }

    Листинг 5.7. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс берет строки из объекта в разделяемой памяти*/ /* и выдает их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include #include

    #include "g_shm.h"

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение из сегмента и выдача строк на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой */ /* памяти */ void *addr_shm; /* Адрес отображенного в память */ /* объекта */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения данных*/ /* о сигнале */ /* Длительность ожидания строки */ struct timespec stmspc = {10, 0}; pid_t ppid; /* Идентификатор родительского процесса */

    /* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); }

    /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-C"); return (2); }

    /* Запомним идентификатор родительского процесса */ ppid = getppid ();

    /* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM);

    /* Подготовительная работа закончена */ fputs ("Вводите строки\n", stdout);



    while (sigtimedwait (&smask, &sinfo, &stmspc) == SIG_SHM) { /* Дождались, когда в объекте появилась строка.*/ /* Выдадим ее на стандартный вывод*/ assert (fputs ("Вы ввели: ", stdout) != EOF); assert (fputs (addr_shm, stdout) != EOF); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); }

    return 0; }

    Листинг 5.8. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.

    В модифицированном варианте сигналы (не имеет значения - обычные или реального времени) применяются только как средство синхронизации. Для выявления конца файла задействован механизм контроля длительности ожидания. Читателю предлагается поварьировать эти длительности, немного задержаться с вводом строк с терминала и проанализировать поведение программы.

    Отметим, что при использовании объектов в разделяемой памяти, отображенных в адресные пространства процессов, передачи данных как таковой не требуется. В приведенной программе начальный процесс читает строки в разделяемый объект, а порожденный берет их оттуда же и выводит без каких-либо дополнительных копирований. Флаг MAP_SHARED в вызове mmap() обеспечивает доступность данных, записанных в объект одним процессом, всем другим процессам-читателям.

    Еще одна возможность, полезная в связи с отображением объектов в адресное пространство процессов, - синхронизация (согласование состояния) оперативной и долговременной памяти. Это возможность реализует функция msync() (см. листинг 5.9).

    #include int msync (void *addr, size_t len, int flags);

    Листинг 5.9. Описание функции msync().

    Функция msync() записывает в долговременную память все измененные страницы объекта в памяти, пересекающиеся с фрагментом адресного пространства процесса, начинающимся с адреса addr (это должна быть граница страницы) и имеющим длину len байт.

    Если объектом является файл, отображенный в память (естественно, без флага MAP_PRIVATE), то вызов msync() гарантирует завершение всех операций записи с обеспечением целостности данных синхронизированного ввода/вывода (см.


    курс [1]). Эффект от применения msync() к объектам в разделяемой и типизированной памяти не специфицирован.

    Аргумент flags определяет характер действий при записи измененных страниц. Флаг MS_ASYNC означает асинхронную, а MS_SYNC - синхронную запись. Кроме того, флаг MS_INVALIDATE предписывает выполнить инициализацию кэша данных (приведение его в соответствие с содержимым долговременной памяти).

    Отметим, что функция msync() обязана присутствовать только на системах, поддерживающих две необязательные возможности стандарта POSIX-2001 - файлы, отображенные в память, и синхронизированный ввод/вывод. В этом смысле ее можно считать дважды необязательной.

    Вообще говоря, вызов msync() - не единственная причина, способная инициировать запись в долговременную память; подобная запись может стать следствием нормальной системной активности.

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

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые функции систем управления базами*/ /* данных в памяти, следуя принципу неуничтожения информации */ /* (изменения записываются не в сам объет, а в его новую версию) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #include #include #include #include s #include

    /* Размер страницы */ static int pgsz;

    /* Структура для получения */ /* информации об исходном файле*/ static struct stat sbuf_src;

    /* Адрес отображенного в память исходного файла*/ static char *saddr;

    /* Признак того, что выполнялась функция */ /* обработки сигнала SIGSEGV */ static char sigsegv_catching;

    /* * * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGSEGV. */ /* Предполагается, что этот сигнал */ /* генерируется при попытке записи в */ /* страницы, доступные только на чтение. */ /* Функция добавляет разрешение на запись*/ /* * * * * * * * * * * * * * * * * * * * */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { void *page_addr; /* Адрес страницы, в которую*/ /*пытались писать*/



    page_addr = (char *) sig_info->si_addr - (off_t) ((char *) sig_info->si_addr - saddr) % pgsz; assert (mprotect (page_addr, pgsz, PROT_READ | PROT_WRITE) == 0); sigsegv_catching = 1; }

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Установка способа обработки сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * */ static int sigsegv_set_proc (void (*sigsegv_actn) (int, siginfo_t *, void *), struct sigaction *old_sigsegv_sact) { struct sigaction sact;

    (void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_actn; if (sigaction (SIGSEGV, &sact, old_sigsegv_sact) != 0) { perror ("SIGACTION"); return (-1); }

    return 0; }

    /* * * * * * * * * * * * * * * * * * * * * */ /* Функция начала работы с исходным файлом.*/ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * * */ static int init_src (char *name_src) { int fd_src; /* Дескриптор исходного файла */

    /* Откроем исходный файл и отобразим его в память */ if ((fd_src = open (name_src, O_RDONLY)) < 0) { perror ("OPEN-SRC"); return (-1); } if (fstat (fd_src, &sbuf_src) != 0) { perror ("FSTAT"); return (-1); } if ((saddr = (char *) mmap (NULL, sbuf_src.st_size, PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) { perror ("MMAP"); return (-1); }

    return 0; } /* * * * * * * * * * * * * * * * * * */ /* Опрос конфигурационных параметров */ /* * * * * * * * * * * * * * * * * * */ static int init_conf_params (void) { if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) { perror ("SYSCONF"); return (-1); }

    return 0; }

    /* * * * * * * * * * * */ /* Общая инициализация */ /* * * * * * * * * * * */ static int init_all (char *name_src) { if ((init_src (name_src) != 0) || (sigsegv_set_proc (sigsegv_sigaction, (struct sigaction *) NULL) != 0) || (init_conf_params () != 0)) { return (-1); }

    return 0; }

    /* * * * * * * * * * * * * * * * * */ /* Запись в файл измененных страниц*/ /* * * * * * * * * * * * * * * * * */ static int write_dlt (char *name_dlt) { int fd_dlt; /* Дескриптор файла изменений*/ volatile char tmp; /* Значение пробного байта*/ off_t pos;/* Позиция в отображенной памяти*/



    if ((fd_dlt = open (name_dlt, O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) { perror ("OPEN-DLT"); return (-1); }

    /* Измененные страницы выявим путем пробных записей. */ for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) { tmp = saddr [pos]; sigsegv_catching = 0; saddr [pos] = tmp; if (sigsegv_catching == 0) { /* В страницу удалось записать */ /* без генерации сигнала SIGSEGV.*/ /* Значит, она была доступна на запись.*/ /* Следовательно, ее модифицировали.*/ /* Запишем ее в файл изменений */ (void) lseek (fd_dlt, pos, SEEK_SET); assert (write (fd_dlt, saddr + pos, pgsz) == pgsz); } /* Уберем добавленное разрешение на запись */ assert (mprotect (saddr + pos, pgsz, PROT_READ) == 0); } /* for */

    return (close (fd_dlt)); }

    /* * * * * * * * * * * * * */ /* Функция main() вызывает */ /* описанные выше функции */ /* и обращается на запись */ /* к отображенной памяти */ /* * * * * * * * * * * * * */ int main (int argc, char *argv []) { if (argc != 3) { fprintf (stderr, "Использование: %s исходный_файл " "файл_изменений\n", argv [0]); return (-1); }

    if (init_all (argv [1]) != 0) { fprintf (stderr, "Ошибка инициализации\n"); return (-1); }

    printf ("Размер страницы: %d\n" "Адрес отображенного в память исходного файла: %p\n" "Длина отображенного в память исходного файла: %ld\n", pgsz, saddr, sbuf_src.st_size);

    saddr [0] = '*';

    return (write_dlt (argv [2])); }

    Листинг 5.10. Пример программы, использующей файлы, отображенные в память.

    Приведенная программа следует фундаментальному принципу неуничтожения информации. При модификациях порождаются новые версии объектов; первоначальные объекты остаются неизменными. Подобный подход особенно полезен, когда СУБД является технологическим элементом инструментальной среды разработки программ, поскольку историю создания и модификации программных систем обязательно нужно сохранять.

    Обратим внимание на изменение разрешенных видов доступа к отображенным страницам, реализуемое обращениями к функции mprotect().


    Первоначально отображенная память доступна только на чтение. При попытке записи в отображенную память генерируется сигнал SIGSEGV, и функция его обработки добавляет доступ на запись к соответствующей странице. В результате появляется возможность отслеживания изменений и сохранения в последующем только измененных страниц, так что дисковое пространство на версии объектов будет расходоваться экономно.

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

    К сожалению, приведенная программа не вполне соответствует стандарту POSIX-2001 в части обработки сигналов. По стандарту поведение процесса после нормального выхода из функций обработки сигналов SIGBUS, SIGFPE, SIGILL и SIGSEGV не определено, если только последние не были сгенерированы вызовами kill(), sigqueue() или raise(). (Заметим попутно, что и игнорирование перечисленных сигналов приводит к неопределенному поведению.) Строго говоря, и вызов mprotect() из функций обработки сигналов не считается безопасным. В общем, функция sigsegv_sigaction() - сама нестандартность и немобильность, и это, конечно, плохо, но то, что вся нестандартность и немобильность программы сосредоточена в одной небольшой функции, безусловно, хорошо.


    size_t len, int prot, int


    #include void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off);
    Листинг 5.1. Описание функции mmap().
    Закрыть окно




    #include int mprotect (void *addr, size_t len, int prot);
    Листинг 5.2. Описание функции mprotect().
    Закрыть окно




    #include int munmap (void *addr, size_t len);
    Листинг 5.3. Описание функции munmap().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выдает на стандартный вывод файлы,*/ /* имена которых заданы в командной строке */ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    int main (int argc, char *argv []) { struct stat sbuf; /* Структура для получения информации */ /* о файле */ int fd; /* Дескриптор обрабатываемого файла */ void *maddr; /* Адрес отображенного в память файла */ int i;
    for (i = 1; i < argc; i++) { if ((fd = open (argv [i], O_RDONLY)) < 0) { perror ("OPEN"); continue; } if (fstat (fd, &sbuf) != 0) { perror ("FSTAT"); close (fd); continue; } if ((maddr = mmap (NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ perror ("MMAP"); close (fd); continue; } if (write (STDOUT_FILENO, maddr, sbuf.st_size) != sbuf.st_size) { perror ("WRITE"); } if (munmap (maddr, sbuf.st_size) != 0) { perror ("MUNMAP"); } close (fd); } /* for */
    return 0; }
    Листинг 5.4. Пример программы, использующей файлы, отображенные в память.
    Закрыть окно




    #include
    int truncate (const char *path, off_t length);
    int ftruncate ( int fildes, off_t length);
    Листинг 5.5. Описание функций truncate() и ftruncate.
    Закрыть окно




    #ifndef g_SHM #define g_SHM
    /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"
    /* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM
    #endif
    Листинг 5.6. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, */ /* копирует строки со стандартного ввода на*/ /* стандартный вывод, используя в качестве */ /* буфера разделяемый сегмент, отображенный*/ /* в адресные пространства процессов. */ /* Для синхронизации доступа к разделяемому*/ /* сегменту используется сигнал SIGALRM */ /* * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include
    #include "g_shm.h"
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение строк со стандартного ввода в сегмент */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти */ void *addr_shm; /* Адрес отображенного в память объекта */ sigset_t smask; /* Маска блокируемых сигналов */ siginfo_t sinfo;/* Структура для получения данных о сигнале */ /* Длительность ожидания прихода сигнала */ struct timespec stmspc = {10, 0}; pid_t cpid; /* Идентификатор порожденного процесса */
    /* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); }
    /* Сделаем размер созданного объекта равным LINE_MAX */ if (ftruncate (fd_shm, LINE_MAX) != 0) { perror ("FTRUNCATE"); return (2); }
    /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-P"); return (3); }
    /* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &smask, (sigset_t *) NULL);
    /* Подготовительная работа закончена */
    switch (cpid = fork ()) { case -1: perror ("FORK"); return (4); case 0: /* Чтение из объекта и выдачу на стандартный*/ /* вывод реализуем в порожденном процессе*/ if (execl ("./g_r_shm", "g_r_shm", ( char *) NULL) < 0) { perror ("EXECL"); return (5); } }
    /* Чтение строк со стандартного ввода в объект */ /* возложим на родительский процесс. */ /* В начальный момент объект в разделяемой памяти */ /* доступен для записи*/ while (fgets (addr_shm, LINE_MAX, stdin) != NULL) { /* Сообщим порожденному процессу, */ /* что в объект в разделяемой памяти */ /* помещена очередная строка */ assert (kill (cpid, SIG_SHM) == 0); /* Дождемся, когда в объект можно будет прочитать*/ /* следующую строку */ if (sigtimedwait (&smask, &sinfo, &stmspc) != SIG_SHM) { break; } }
    /* Порожденный процесс должен завершиться*/ /* по контролю времени ожидания*/ (void) wait (NULL);
    if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (6); }
    return (0); }
    Листинг 5.7. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс берет строки из объекта в разделяемой памяти*/ /* и выдает их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include
    #include "g_shm.h"
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение из сегмента и выдача строк на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой */ /* памяти */ void *addr_shm; /* Адрес отображенного в память */ /* объекта */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения данных*/ /* о сигнале */ /* Длительность ожидания строки */ struct timespec stmspc = {10, 0}; pid_t ppid; /* Идентификатор родительского процесса */
    /* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); }
    /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-C"); return (2); }
    /* Запомним идентификатор родительского процесса */ ppid = getppid ();
    /* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM);
    /* Подготовительная работа закончена */ fputs ("Вводите строки\n", stdout);
    while (sigtimedwait (&smask, &sinfo, &stmspc) == SIG_SHM) { /* Дождались, когда в объекте появилась строка.*/ /* Выдадим ее на стандартный вывод*/ assert (fputs ("Вы ввели: ", stdout) != EOF); assert (fputs (addr_shm, stdout) != EOF); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); }
    return 0; }
    Листинг 5.8. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
    Закрыть окно




    #include int msync (void *addr, size_t len, int flags);
    Листинг 5.9. Описание функции msync().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые функции систем управления базами*/ /* данных в памяти, следуя принципу неуничтожения информации */ /* (изменения записываются не в сам объет, а в его новую версию) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include #include #include #include #include #include s #include
    /* Размер страницы */ static int pgsz;
    /* Структура для получения */ /* информации об исходном файле*/ static struct stat sbuf_src;
    /* Адрес отображенного в память исходного файла*/ static char *saddr;
    /* Признак того, что выполнялась функция */ /* обработки сигнала SIGSEGV */ static char sigsegv_catching;
    /* * * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGSEGV. */ /* Предполагается, что этот сигнал */ /* генерируется при попытке записи в */ /* страницы, доступные только на чтение. */ /* Функция добавляет разрешение на запись*/ /* * * * * * * * * * * * * * * * * * * * */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { void *page_addr; /* Адрес страницы, в которую*/ /*пытались писать*/
    page_addr = (char *) sig_info->si_addr - (off_t) ((char *) sig_info->si_addr - saddr) % pgsz; assert (mprotect (page_addr, pgsz, PROT_READ | PROT_WRITE) == 0); sigsegv_catching = 1; }
    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Установка способа обработки сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * */ static int sigsegv_set_proc (void (*sigsegv_actn) (int, siginfo_t *, void *), struct sigaction *old_sigsegv_sact) { struct sigaction sact;
    (void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_actn; if (sigaction (SIGSEGV, &sact, old_sigsegv_sact) != 0) { perror ("SIGACTION"); return (-1); }
    return 0; }
    /* * * * * * * * * * * * * * * * * * * * * */ /* Функция начала работы с исходным файлом.*/ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * * */ static int init_src (char *name_src) { int fd_src; /* Дескриптор исходного файла */
    /* Откроем исходный файл и отобразим его в память */ if ((fd_src = open (name_src, O_RDONLY)) < 0) { perror ("OPEN-SRC"); return (-1); } if (fstat (fd_src, &sbuf_src) != 0) { perror ("FSTAT"); return (-1); } if ((saddr = (char *) mmap (NULL, sbuf_src.st_size, PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) { perror ("MMAP"); return (-1); }
    return 0; } /* * * * * * * * * * * * * * * * * * */ /* Опрос конфигурационных параметров */ /* * * * * * * * * * * * * * * * * * */ static int init_conf_params (void) { if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) { perror ("SYSCONF"); return (-1); }
    return 0; }
    /* * * * * * * * * * * */ /* Общая инициализация */ /* * * * * * * * * * * */ static int init_all (char *name_src) { if ((init_src (name_src) != 0) || (sigsegv_set_proc (sigsegv_sigaction, (struct sigaction *) NULL) != 0) || (init_conf_params () != 0)) { return (-1); }
    return 0; }
    /* * * * * * * * * * * * * * * * * */ /* Запись в файл измененных страниц*/ /* * * * * * * * * * * * * * * * * */ static int write_dlt (char *name_dlt) { int fd_dlt; /* Дескриптор файла изменений*/ volatile char tmp; /* Значение пробного байта*/ off_t pos;/* Позиция в отображенной памяти*/
    if ((fd_dlt = open (name_dlt, O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) { perror ("OPEN-DLT"); return (-1); }
    /* Измененные страницы выявим путем пробных записей. */ for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) { tmp = saddr [pos]; sigsegv_catching = 0; saddr [pos] = tmp; if (sigsegv_catching == 0) { /* В страницу удалось записать */ /* без генерации сигнала SIGSEGV.*/ /* Значит, она была доступна на запись.*/ /* Следовательно, ее модифицировали.*/ /* Запишем ее в файл изменений */ (void) lseek (fd_dlt, pos, SEEK_SET); assert (write (fd_dlt, saddr + pos, pgsz) == pgsz); } /* Уберем добавленное разрешение на запись */ assert (mprotect (saddr + pos, pgsz, PROT_READ) == 0); } /* for */
    return (close (fd_dlt)); }
    /* * * * * * * * * * * * * */ /* Функция main() вызывает */ /* описанные выше функции */ /* и обращается на запись */ /* к отображенной памяти */ /* * * * * * * * * * * * * */ int main (int argc, char *argv []) { if (argc != 3) { fprintf (stderr, "Использование: %s исходный_файл " "файл_изменений\n", argv [0]); return (-1); }
    if (init_all (argv [1]) != 0) { fprintf (stderr, "Ошибка инициализации\n"); return (-1); }
    printf ("Размер страницы: %d\n" "Адрес отображенного в память исходного файла: %p\n" "Длина отображенного в память исходного файла: %ld\n", pgsz, saddr, sbuf_src.st_size);
    saddr [0] = '*';
    return (write_dlt (argv [2])); }
    Листинг 5.10. Пример программы, использующей файлы, отображенные в память.
    Закрыть окно




    #include int posix_typed_mem_open (const char *name, int oflag, int tflag);
    Листинг 5.11. Описание функции posix_typed_mem_open().
    Закрыть окно




    #include int posix_typed_mem_get_info ( int fildes, struct posix_typed_mem_info *info);
    Листинг 5.12. Описание функции posix_typed_mem_get_info().
    Закрыть окно




    #include int posix_mem_offset ( const void * restrict addr, size_t len, off_t *restrict off, size_t *restrict contig_len, int *restrict fildes);
    Листинг 5.13. Описание функции posix_mem_offset().
    Закрыть окно




    #include int mlock (const void *addr, size_t len);
    Листинг 5.14. Описание функции mlock().
    Закрыть окно




    #include int munlock (const void *addr, size_t len);
    Листинг 5.15. Описание функции munlock().
    Закрыть окно




    #include
    int mlockall (int flags);
    int munlockall (void);
    Листинг 5.16. Описание функций mlockall() и munlockall().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует удержание в памяти*/ /* всего адресного пространства процесса, */ /* в том числе стека с учетом возможного роста*/ /* * * * * * * * * * * * * * * * * * * * * * */
    #include #include
    #define LOCKED_STACK_SIZE 048576
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Следующая функция нужна, чтобы вызвать рост стека до*/ /* требуемого размера. На всякий случай примем меры для*/ /* защиты от слишком умного оптимизирующего компилятора*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ static int dummy_func (void) { char dummy_arr [LOCKED_STACK_SIZE]; int i; int res = 0;
    dummy_arr [0] = 0; for (i = 1; i < LOCKED_STACK_SIZE; i++) { dummy_arr [i] = dummy_arr [i - 1] + i; }
    for (i = 0; i < LOCKED_STACK_SIZE; i++) { res += dummy_arr [i]; }
    return (res); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() вызывает вспомогательную функцию */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0) { perror ("MLOCKALL"); return (1); }
    fprintf (stderr, "Результат вспомогательной функции:" "%d\n", dummy_func ());
    return 0; }
    Листинг 5.17. Пример программы, удерживающей в памяти растущий стек.
    Закрыть окно



    Средства удержания процессов в памяти


    Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.
  • поддержка страничной виртуальной памяти с подкачкой по запросу;
  • применение нескольких уровней кэширования;
  • наличие нескольких видов физической памяти с разными характеристиками (быстрой статической, более медленной, но и более дешевой динамической и т.п.).

  • Подкачка страниц, непопадание в кэш, обращение к более медленной памяти вносят элементы недетерминированности в работу приложений. Чтобы по возможности избавиться от них, в стандарт POSIX введены средства удержания в физической памяти страниц из адресного пространства процессов. Они не ликвидируют недетерминированность полностью (например, ничего не говорится о длительности трансляции логического адреса в физический), но позволяют обеспечить разумную верхнюю границу времени доступа к командам и данным процессов.
    Если реализация не поддерживает виртуальной памяти, то описываемые далее функции могут просто ничего не делать (кроме, быть может, проверки корректности аргументов).
    Для удержания в физической памяти группы страниц из адресного пространства процесса служит функция mlock() (см. листинг 5.14), воспользоваться которой может только процесс, обладающий соответствующими привилегиями (см. курс [1], где раскрывается понятие "соответствующие привилегии").
    #include int mlock (const void *addr, size_t len);
    Листинг 5.14. Описание функции mlock(). (html, txt)
    После успешного завершения вызова mlock() резидентными в памяти станут все страницы, пересекающиеся с частью адресного пространства процесса, начинающейся со значения addr и имеющей длину len байт. Реализация может требовать, чтобы значение addr указывало на границу страницы.
    Если некоторая страница несколько раз отображена в адресные пространства процессов, то для ее удержания в памяти достаточно позаботиться о каком-либо одном отображении.
    Удержание отменяется после вызовов fork() и exec(), а также ликвидации по какой-либо причине соответствующей части адресного пространства процесса (отмена отображения, завершение процесса и т.п.).
    Явная отмена удержания группы страниц реализуется функцией munlock() (см. листинг 5.15).

    #include int munlock (const void *addr, size_t len);

    Листинг 5.15. Описание функции munlock(). (html, txt)

    Более точно, munlock() разрешает больше не удерживать в памяти заданные страницы из адресного пространства вызывающего процесса. Если страница была отображена несколько раз и удерживалась в памяти несколькими вызовами mlock(), то одного обращения к munlock() для отмены удержания недостаточно.

    Если нужно удерживать в памяти все адресное пространство процесса (что имеет место для большинства приложений реального времени), целесообразно воспользоваться функциями mlockall() и munlockall() (см. листинг 5.16).

    #include

    int mlockall (int flags);

    int munlockall (void);

    Листинг 5.16. Описание функций mlockall() и munlockall(). (html, txt)

    Поскольку адресное пространство процесса при выполнении, вообще говоря, меняется, необходимо уточнить, что именно должно удерживаться в памяти. Для этого служит аргумент flags функции mlockall(), в значении которого могут фигурировать следующие флаги.

    MCL_CURRENT

    Удерживать в памяти страницы, присутствующие в адресном пространстве процесса на момент вызова.

    MCL_FUTURE

    Удерживать в памяти страницы, которые будут отображены в адресное пространство процесса после вызова.

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

    Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры физической памяти. В таком случае поведение операционной системы зависит от реализации.

    Функция munlockall() отменяет удержание для всех страниц, присутствующих в адресном пространстве процесса на момент вызова или отображенных позднее, если только при вызове mlockall() не был установлен флаг MCL_FUTURE.

    Определенную проблему составляет удержание в памяти страниц стека, рост которого не всегда можно точно оценить. (А стек, конечно, нуждается в удержании, иначе, например, функция обработки сигнала может выполниться с неожиданной задержкой.) Одно из возможных решений этой проблемы состоит в установке флага MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см.листинг 5.17).

    Листинг 5.17. Пример программы, удерживающей в памяти растущий стек. (html, txt)

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


    Функции управления планированием


    Мы приступаем к рассмотрению функций для опроса и/или установки политики и/или параметров планирования применительно к процессам. Функции аналогичной направленности для потоков управления были рассмотрены нами ранее.
    Стандарт POSIX-2001 предусматривает следующие функции управления планированием: sched_getscheduler() (опрос политики планирования процесса), sched_getparam() (опрос параметров планирования процесса), sched_setscheduler() (установка политики и параметров планирования процесса) и sched_setparam() (установка параметров планирования процесса) (см. листинг 6.1).
    #include
    int sched_getscheduler (pid_t pid);
    int sched_getparam (pid_t pid, struct sched_param *param);
    int sched_setscheduler (pid_t pid, int policy, const struct sched_param *param);
    int sched_setparam (pid_t pid, const struct sched_param *param);
    Листинг 6.1. Описание функций управления планированием. (html, txt)
    Функция sched_getscheduler() возвращает в качестве (нормального) результата политику планирования процесса с заданным идентификатором (в случае неудачи результат равен -1). Если значение аргумента pid равно нулю, имеется в виду вызывающий процесс. Вообще говоря, опрос атрибутов других процессов подвержен контролю прав доступа, зависящему от реализации.
    Функция sched_getparam() записывает параметры планирования заданного процесса по указателю param, в структуру типа sched_param; ее нормальный результат равен нулю. (Напомним, что, согласно стандарту POSIX-2001, у структуры типа sched_param только одно обязательное поле – приоритет sched_priority.)
    Весьма мощной является функция sched_setscheduler(). Она позволяет установить новые политику и параметры планирования заданного процесса и возвращает в качестве нормального результата прежнюю политику. Разумеется, установка атрибутов планирования подвержена контролю прав доступа в еще большей степени, чем опрос: даже в изменении собственных атрибутов процессу может быть отказано.
    Вызов sched_setscheduler() не влияет впрямую на планирование потоков управления.
    Косвенно могут быть затронуты лишь потоки заданного процесса, если их область планирования конкуренции есть PTHREAD_SCOPE_PROCESS (из-за того, что меняются атрибуты планирования содержащего их процесса).

    Любопытно отметить, что стандарт POSIX-2001 не требует атомарности вызова sched_setscheduler() с точки зрения выполняющихся потоков управления. Хотя в итоге изменится все или ничего, эти изменения могут наблюдаться как поэтапные; соответственно они будут сказываться на поведении потоков.

    Функция sched_setparam(), в отличие от sched_setscheduler(), изменяет только параметры планирования, но ее воздействие на заданный процесс стандартизовано детальнее.

    В общем случае заданный процесс помещается в хвост списка, соответствующего его новому приоритету.

    Если новый приоритет заданного процесса выше, чем у выполняющегося, и заданный процесс готов к выполнению, он вытесняет с процессора наименее приоритетный процесс. (Заметим в скобках, что это одно из немногих мест в стандарте, точно регламентирующих поведение приложения в рамках многопроцессорной конфигурации.)

    Если вызывающий процесс уменьшает собственный приоритет, он, в свою очередь, может оказаться вытесненным с процессора.

    Стандарт POSIX-2001 предоставляет средства для опроса характеристик политик планирования – минимального (sched_get_priority_min()) и максимального (sched_get_priority_max()) среди допустимых приоритетов, а также величины кванта выделяемого процессорного времени для политики циклического планирования (sched_rr_get_interval()) (см. листинг 6.2).

    #include

    int sched_get_priority_min (int policy);

    int sched_get_priority_max (int policy);

    int sched_rr_get_interval (pid_t pid, struct timespec *q_ptr);

    Листинг 6.2. Описание функций опроса характеристик политик планирования. (html, txt)

    Функция sched_rr_get_interval() записывает по указателю q_ptr в структуру типа timespec величину кванта процессорного времени, выделяемого заданному процессу, и возвращает нуль в качестве нормального результата.


    Нормальными результатами функций sched_get_priority_min() и sched_get_priority_max(), разумеется, служат, соответственно, минимальный и максимальный приоритеты.

    Несколько особняком стоит альтруистическая функция sched_yield() (см. листинг 6.3), позволяющая вызывающему потоку управления добровольно уступить процессор. Результат (нулевой) поток получит только после того, как вновь окажется в голове своего списка и будет выбран планировщиком на выполнение.

    #include int sched_yield (void);

    Листинг 6.3. Описание функции sched_yield(). (html, txt)

    Следующая программа (см. листинг 6.4) иллюстрирует применение описанных функций. Возможные результаты ее выполнения для ОС Linux и операционной системы реального времени oc2000 показаны на листингах 6.5 и 6.6.

    Листинг 6.4. Пример программы, использующей функции управления планированием. (html, txt)

    Листинг 6.5. Возможные результаты выполнения программы, использующей функции управления планированием, в ОС Linux. (html, txt)

    Листинг 6.6. Возможные результаты выполнения программы, использующей функции управления планированием, в операционной системе реального времени oc2000. (html, txt)

    Проиллюстрируем теперь ситуацию с инверсией приоритетов (см. листинг 6.7).

    Листинг 6.7. Пример программы, моделирующей ситуацию инверсии приоритетов. (html, txt)

    Идея приведенной программы состоит в том, что у потоков управления с низким и высоким приоритетами имеется общий семафор, охраняющий вход в критический интервал. Так получается, что этот семафор первым захватывает процесс с низким приоритетом. Его критический интервал мал, но в это время оказывается готовым к выполнению поток управления со средним приоритетом, который на заметное время занимает процессор, не давая низкоприоритетному выйти из критического интервала и освободить семафор. Из-за этого поток с высоким приоритетом оказывается блокированным.

    Из технических деталей обратим внимание на использование значения PTHREAD_EXPLICIT_SCHED аргумента inheritsched функции pthread_attr_setinheritsched().Оно предписывает извлекать характеристики планирования создаваемых потоков управления из атрибутного объекта, а не наследовать их у создающего потока. В данном случае это важно, так как потоки управления необходимо создавать с разными приоритетами.

    Возможные результаты выполнения приведенной программы под управлением операционной системы реального времени oc2000 показаны на листинге 6.8.

    Листинг 6.8. Возможные результаты выполнения программы, моделирующей ситуацию инверсии приоритетов, под управлением операционной системы реального времени oc2000. (html, txt)


    Основные идеи и понятия


    Мы приступаем к рассмотрению политик и параметров планирования применительно к использованию процессоров потоками управления (процессами), готовыми к выполнению. Эта тема уже затрагивалась в курсе [1] и предыдущих разделах данного курса, посвященных потокам управления.
    Приложения реального времени, чтобы удовлетворить налагаемым на них внешним ограничениям, должны располагать средствами контроля за порядком выполнения входящих в их состав параллельных процессов. При этом планирование должно быть быстрым и детерминированным, с гарантией вытеснения низкоприоритетных процессов высокоприоритетными сразу после того, как последние оказываются готовыми к выполнению.
    Процессы реального времени, по сравнению с прочими, должны обладать более высокими приоритетами. В таком случае они смогут передавать друг другу вычислительные ресурсы как мобильным, так и системно-зависимым образом.
    Общая идея приоритетного планирования состоит в следующем. У каждого потока управления (процесса) в каждый момент времени есть определенный приоритет. Равноприоритетные потоки управления, готовые к выполнению, объединяются в списки, так что поток попадает в список в соответствии со своим текущим приоритетом.
    Списки равноприоритетных потоков упорядочены, от первого элемента (головы) к последнему (хвосту). Цель политики планирования состоит в определении допустимых операций над множеством списков (например, перемещение потоков между списками и внутри них).
    С каждой политикой ассоциирован допустимый диапазон приоритетов. Диапазоны для разных политик, разумеется, могут и, более, того, должны, перекрываться, иначе, например, не удастся организовать параллельную детерминированную обработку периодических и непериодических событий.
    Реализация, удовлетворяющая стандарту POSIX, должна выбирать для выполнения процесс (поток управления), находящийся в голове наиболее приоритетного непустого списка, независимо от ассоциированной политики планирования. После этого процесс (поток управления) удаляется из списка.

    Другие действия (предыдущие и последующие) специфичны для установленной политики планирования. Рассмотрим их.

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

  • Если выполняемый поток управления вытесняется с процессора, он помещается в голову списка, соответствующего его приоритету.
  • Когда приостановленный (блокированный) поток управления становится готовым к выполнению, он помещается в хвост списка, соответствующего его приоритету.
  • Когда выполняемый поток управления вызывает функцию sched_setscheduler(), политика и приоритет планирования процесса, указанного в вызове, изменяются в соответствии со значением аргумента param (см. далее).
  • Когда выполняемый поток управления вызывает функцию sched_setparam(), приоритет планирования процесса, указанного в вызове, изменяется в соответствии со значением аргумента param (см. далее).
  • Когда выполняемый поток управления вызывает функцию pthread_setschedparam(), политика и приоритет планирования потока, указанного в вызове, изменяются в соответствии со значениями аргументов policy и param.
  • Когда выполняемый поток управления вызывает функцию pthread_setschedprio(), приоритет планирования потока, указанного в вызове, изменяется в соответствии со значением аргумента prio.
  • Если поток управления, чьи политика и/или приоритет планирования подверглись изменению вызовами, отличными от pthread_setschedprio(), является выполняемым или готовым к выполнению, он помещается в хвост списка, соответствующего его новому приоритету.
  • Если поток управления, чей приоритет планирования изменен вызовом pthread_setschedprio(), является выполняемым или готовым к выполнению, воздействие на его позицию в списке определяется направлением изменения, а именно:
  • если приоритет увеличился, поток помещается в хвост соответствующего списка;
  • если приоритет уменьшился, поток помещается в голову соответствующего списка;
  • если приоритет не изменился, не меняется и позиция потока в списке.
  • Когда выполняемый поток управления вызывает функцию sched_yield(), он помещается в хвост своего списка.
  • Ни при каких других событиях позиция потока управления в списках при данной политике планирования не изменяется.




  • Допустимый диапазон приоритетов должен включать по крайней мере 32 различных значения и ограничиваться величинами, которые возвращают функции sched_get_priority_min() и sched_get_priority_max() с аргументом SCHED_FIFO.

    Политика планирования по очереди удовлетворяет сформулированным выше требованиям к обслуживанию приложений реального времени. Критически важный (наиболее приоритетный) процесс будет выполняться всегда, когда он к этому готов, занимая процессор столько времени, сколько пожелает. Иногда политику SCHED_FIFO называют "выполнением до завершения" или "выполнением до блокирования". В многопользовательских системах с разделением времени подобная политика, разумеется, выглядела бы недружественной, но для систем реального времени она хороша прежде всего своей детерминированностью.

    Чтобы сделать планирование более справедливым по отношению к равноприоритетным процессам, можно воспользоваться политикой SCHED_RR (циклическое планирование ). Она эквивалентна SCHED_FIFO с одним исключением: когда время, в течение которого поток управления занимал процессор, становится больше или равным результату функции sched_rr_get_interval(), он перемещается в хвост соответствующего списка, а для выполнения выбирается головной поток. Таким образом, ни один из равноприоритетных потоков не сможет монополизировать процессор.

    Если поток управления вытесняется, а потом возобновляет выполнение, он "дорабатывает" выделенный ему квант процессорного времени.

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

  • текущий поток управления контролирует процессор в течение времени Q, где Q – величина кванта выделяемого процессорного времени;
  • если имеется N наиболее приоритетных потоков управления, то каждый из них будет ожидать доступ к процессору не более (N – 1) * Q единиц времени (или, что почти то же, будет получать доступ к процессору с периодом N * Q).




  • Если накладными и непредвиденными расходами процессорного времени пренебречь нельзя, встает вопрос, на кого их списывать. От ответа на этот вопрос зависит, какое из сформулированных выше утверждений станет ложным. Если списывать расходы "на систему" и честно выделять каждому потоку управления Q единиц процессорного времени, поток может возобновить свое выполнение существенно позднее, чем через (N – 1) * Q единиц времени. Обычно для приложений реального времени это неприемлемо (не гарантируется время отклика на события, обслуживаемые потоками), поэтому расходы списывают на потоки, выделяя каждому из них не более Q единиц процессорного времени, что делает ложным первое утверждение, но гарантирует истинность второго. Разумеется, взрывная активность периферийных устройств, "отвлекающих" процессор на обработку прерываний, может практически целиком "съесть" выделяемый потоку квант времени; если подобная активность окажется периодической с тем же периодом, что и циклическое планирование, некоторые потоки рискуют оказаться на голодном пайке, поэтому стандарт добавляет еще одно требование к реализации политики SCHED_RR: ни один поток управления не должен ожидать доступ к процессору неопределенно долго.

    Отметим, что стандарт POSIX-2001 не предоставляет средств для установки размера кванта времени, выделяемого потокам при циклическом планировании. Кроме того, не уточняется, является ли этот размер общесистемной величиной (как оно обычно бывает) или может быть разным для разных процессов.

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



    Период пополнения бюджета характеризуется значением элемента sched_ss_repl_period описанной ранее структуры типа sched_param. Доступный бюджет вычислительных ресурсов инициализируется значением элемента sched_ss_init_budget той же структуры.

    Политика спорадического планирования предусматривает ряд дополнительных условий, при выполнении которых приоритет потока управления "переключается" между значениями элементов sched_priority и sched_ss_low_priority структуры типа sched_param.

    Приоритет потока определяется следующим образом. Если доступный бюджет вычислительных ресурсов положителен и число ждущих операций пополнения бюджета меньше значения элемента sched_ss_max_repl структуры типа sched_param, потоку управления присваивается приоритет sched_priority, в противном случае - sched_ss_low_priority. Если значение sched_priorityне больше, чем sched_ss_low_priority, приоритет считается неопределенным. Изменение доступного бюджета вычислительных ресурсов и, следовательно, присваиваемого приоритета, производится по следующим правилам.

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

  • Каждый раз, когда поток управления помещается в хвост списка sched_priority (из-за того, что он перестал быть блокированным и стал готовым к выполнению, или из-за осуществления операции пополнения бюджета), этот момент запоминается как время активации.
  • Когда поток, выполнявшийся с приоритетом sched_priority, вытесняется с процессора, он помещается в голову списка для своего приоритета, а израсходованное им процессорное время вычитается из доступного бюджета вычислительных ресурсов (с заменой отрицательного результата на нулевой).
  • Когда поток, выполнявшийся с приоритетом sched_priority, блокируется, израсходованное им процессорное время вычитается из доступного бюджета вычислительных ресурсов (с той же оговоркой) и планируется операция пополнения бюджета .
  • Когда поток, выполнявшийся с приоритетом sched_priority, исчерпывает отведенное ему процессорное время, он помещается в хвост списка sched_ss_low_priority, израсходованное им процессорное время вычитается из доступного бюджета вычислительных ресурсов (который становится нулевым) и планируется операция пополнения бюджета.
  • Каждый раз, когда планируется операция пополнения бюджета, размер пополнения доступного бюджета вычислительных ресурсов устанавливается равным процессорному времени, израсходованному потоком управления с момента активации (см.


    выше). Операция пополнения бюджета планируется на момент времени, равный сумме времени активации и значения элемента sched_ss_repl_period структуры типа sched_param. Число операций пополнения бюджета, планируемых за один раз, не должно превышать значения sched_ss_max_repl.


  • Операция пополнения бюджета состоит в прибавлении в запланированное время размера пополнения к доступному бюджету вычислительных ресурсов. Если при этом бюджет станет больше начального, он уменьшается до значения sched_ss_initial_budget. Кроме того, если поток управления был готов к выполнению или выполнялся с приоритетом sched_ss_low_priority, он помещается в хвост списка sched_priority.


  • Приведенное описание выглядит довольно замысловатым, хотя основная идея относительно проста. Определенные вычислительные ресурсы резервируются для обработки непериодических событий с высоким приоритетом (sched_priority). Если отведенного времени не хватило, остальные события обрабатываются в фоновом режиме с (низким) приоритетом sched_ss_low_priority. Истраченное "приоритетное" время возвращается в бюджет в результате выполнения планируемых операций пополнения, по истечении периода пополнения. При этом поток может вернуться с низкого приоритета на высокий.

    По стандарту реализация должна допускать не менее _POSIX_SS_REPL_MAX ждущих операций пополнения бюджета. Значение этой конфигурационной константы определено равным четырем, так что один серверный поток управления может мобильным образом обслуживать за каждый период пополнения до четырех спорадических событий.

    В целом стандартизованная политика планирования SCHED_SPORADIC представляет собой разумный компромисс между эффективностью обработки непериодических событий, детерминированностью выполнения периодических процессов и сложностью реализации. Спорадическое планирование может применяться в системах авионики, управления роботами, промышленной автоматизации и т.п.

    Четвертая политика планирования, которую должны поддерживать реализации, соответствующие стандарту POSIX, называется "прочей" (SCHED_OTHER).


    Она необходима, чтобы мобильные приложения могли заявить, что они больше не нуждаются в политике планирования реального времени.

    Используемые в приложении политики и параметры планирования выбираются с целью выполнения предъявляемых требований реального времени, которые, в свою очередь, являются отражением условий функционирования приложения. Если это условия постоянны, нет нужды динамически менять политику и/или приоритеты. Чаше всего так и бывает. Но в некоторых случаях (например, при спуске космического аппарата) условия могут измениться кардинально, что требует если и не смены приложения, то его существенной перестройки. В этой связи важно отметить, что стандарт POSIX-2001 предоставляет достаточно средств для динамического изменения значений атрибутов планирования.

    Вообще говоря, установки и изменения атрибутов планирования могут производиться мелкими порциями, путем обращения к множеству функций вроде pthread_setschedprio(), или массово, в результате вызова одной функции с несколькими аргументами или одним структурным аргументом (такой, например, как pthread_setschedparam()). При начальной установке второй подход предпочтительнее. Из соображений минимальности интерфейса его следует распространить и на другие ситуации, что в общем и целом и сделано в стандарте POSIX-2001. Дополнительным доводом в пользу данного подхода является простота поддержания целостности конфигурации планирования, если значения отдельных характеристик могут оказаться некорректными для определенных реализаций: вызов "массовой" функции изменит все или ничего.

    Интуитивно очевидно, что планировщик должен запускаться тогда, когда происходят события, способные вызвать передачу процессора другому потоку управления (процессу). Важно, что стандарт POSIX-2001 идет существенно дальше интуитивной очевидности, явно перечисляя подобные события:

  • приостановка выполнения потока управления;
  • вытеснение потока управления;
  • возобновление выполнения потока управления;
  • вызов функции, способной изменить значения атрибутов планирования потока управления (процесса);
  • другие события, специфичные для политики планирования и указанные в ее описании.




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

    Подчеркнем концептуальную экономность и целостность стандарта POSIX-2001 применительно к планированию. Вообще говоря, можно представить себе подход, при котором доступ к процессору и к примитивам синхронизации базируется на разных принципах (скажем, при освобождении мьютекса он мог бы доставаться первому по очереди, то есть ждущему дольше других). Разработчики стандарта POSIX-2001 сочли такой подход неприемлемым, поскольку он по меньшей мере затрудняет анализ выполнения требований реального времени. Согласно стандарту, когда примитив синхронизации освобождается, он достается потоку управления, первому с точки зрения применимой политики планирования. (Иными словами, из числа блокированных (приостановленных) в разряд готовых к выполнению переводится самый приоритетный поток.) В POSIX-2001 есть только одно планирование и принципы его едины.

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

    Есть проблемы, трудные для текущего периода стандартизации. К таковым относится планирование в рамках многопроцессорных конфигураций. POSIX-2001 констатирует лишь, что для подобных конфигураций правила политик планирования интерпретируются способом, зависящим от реализации. Подлинная стандартизация здесь еще предстоит.

    Наконец, у приоритетного планирования есть концептуальные проблемы, такие, как инверсия приоритетов, когда высокоприоритетный поток управления, не приостановленный по собственной воле в ожидании какого-либо события, не может продолжить выполнение, в то время как поток с более низким приоритетом успешно выполняется.Зачастую инверсия приоритетов является следствием конкуренции за разделяемые ресурсы; далее мы рассмотрим соответствующие примеры.

    Несмотря на то, что в стандарте POSIX-2001 приняты определенные меры для ограничения инверсии приоритетов – при захвате мьютекса приоритет потока управления повышается в соответствии с протоколом, являющимся одним из атрибутов мьютекса (см. выше раздел, посвященный мьютексам), это конечно, нельзя считать полным решением проблемы инверсии (например, для семафоров ничего подобного не предусмотрено). Тем не менее, POSIX-2001 служит хорошей основой для разработки мобильных приложений реального времени, в том числе применительно к весьма сложным и тонким аспектам.


    pid_t pid, int policy, const


    #include
    int sched_getscheduler (pid_t pid);
    int sched_getparam (pid_t pid, struct sched_param *param);
    int sched_setscheduler ( pid_t pid, int policy, const struct sched_param *param);
    int sched_setparam (pid_t pid, const struct sched_param *param);
    Листинг 6.1. Описание функций управления планированием.
    Закрыть окно




    #include
    int sched_get_priority_min (int policy);
    int sched_get_priority_max (int policy);
    int sched_rr_get_interval (pid_t pid, struct timespec *q_ptr);
    Листинг 6.2. Описание функций опроса характеристик политик планирования.
    Закрыть окно




    #include int sched_yield (void);
    Листинг 6.3. Описание функции sched_yield().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа опрашивает характеристики политик планирования, */ /* а также атрибуты планирования текущего процесса */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include
    int main (void) { struct sched_param shdprm; /* Значения параметров */ /* планирования */ struct timespec qp; /* Величина кванта */ /* процессорного времени */
    printf ("Допустимые диапазоны приоритетов для разных " "политик планирования\n"); printf ("SCHED_FIFO : от %d до %d\n", sched_get_priority_min (SCHED_FIFO), sched_get_priority_max (SCHED_FIFO)); printf ("SCHED_RR : от %d до %d\n", sched_get_priority_min (SCHED_RR), sched_get_priority_max (SCHED_RR)); printf ("SCHED_OTHER: от %d до %d\n", sched_get_priority_min (SCHED_OTHER), sched_get_priority_max (SCHED_OTHER));
    printf ("Текущая политика планирования для текущего " "процесса: "); switch (sched_getscheduler (0)) { case SCHED_FIFO: printf ("SCHED_FIFO\n"); break; case SCHED_RR: printf ("SCHED_RR\n"); break; case SCHED_OTHER: printf ("SCHED_OTHER\n"); break; case -1: perror ("SCHED_GETSCHEDULER"); break; default: printf ("Неизвестная политика планирования\n"); }
    if (sched_getparam (0, &shdprm) == 0) { printf ("Текущий приоритет текущего процесса: %d\n", shdprm.sched_priority); } else { perror ("SCHED_GETPARAM"); }
    shdprm.sched_priority = 50; if (sched_setscheduler (0, SCHED_RR, &shdprm) == -1) { perror ("SCHED_SETSCHEDULER"); }
    if (sched_rr_get_interval (0, &qp) == 0) { printf ("Квант процессорного времени при " "циклическом планировании: %g сек\n", qp.tv_sec + qp.tv_nsec / 1000000000.0);
    } else { perror ("SCHED_RR_GET_INTERVAL"); }
    return 0; }
    Листинг 6.4. Пример программы, использующей функции управления планированием.
    Закрыть окно




    Допустимые диапазоны приоритетов для разных политик планирования
    SCHED_FIFO : от 1 до 99 SCHED_RR : от 1 до 99 SCHED_OTHER : от 0 до 0
    Текущая политика планирования для текущего процесса: SCHED_OTHER Текущий приоритет текущего процесса: 0 Квант процессорного времени при циклическом планировании: 0.15 сек
    Листинг 6.5. Возможные результаты выполнения программы, использующей функции управления планированием, в ОС Linux.
    Закрыть окно




    Допустимые диапазоны приоритетов для разных политик планирования SCHED_FIFO : от 1 до 255 SCHED_RR : от 1 до 255 SCHED_OTHER : от 0 до 255 Текущая политика планирования для текущего процесса: SCHED_FIFO Текущий приоритет текущего процесса: 100 Квант процессорного времени при циклическом планировании: 0.08 сек
    Листинг 6.6. Возможные результаты выполнения программы, использующей функции управления планированием, в операционной системе реального времени oc2000.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа моделирует ситуацию инверсии приоритетов. */ /* Идея состоит в том, что имеется три потока */ /* управления с низким, средним */ /* и высоким приоритетами. */ /* Поток с низким приоритетом захватывает семафор, */ /* когда на него никто больше не претендует, */ /* но не может освободить его, */ /* потому что его вытесняет с процессора */ /* поток со средним приоритетом. */ /* В это время поток с высоким приоритетом хочет */ /* захватить тот же семафор, */ /* но он занят и неизвестно когда освободится... */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    static sem_t sem_mi; /* Семафор для потока со средним */ /* приоритетом */ static sem_t sem_hi; /* Семафор для потока с высоким */ /* приоритетом */
    static double s = 0;
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока с высоким приоритетом */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_hi (void *dummy) { printf ("Поток с высоким приоритетом перед захватом " "семафора\n"); assert (sem_wait (&sem_hi) == 0); printf ("Поток с высоким приоритетом после захвата " "семафора\n"); assert (sem_post (&sem_hi) == 0);
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока со средним приоритетом */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_mi (void *dummy) { int i; double d = 1;
    printf ("Поток со средним приоритетом перед захватом " "семафора\n"); assert (sem_wait (&sem_mi) == 0); /* Займем процессор вычислениями */ for (i = 1; i < 100000000; i++) { s += d/i; d = -d; } printf ("Поток со средним приоритетом перед " "освобождением семафора\n"); assert (sem_post (&sem_mi) == 0);
    return (&s); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока с низким приоритетом */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_attr_t attrob; /* Атрибутный объект */ /* создаваемых потоков */ struct sched_param shdprm; /* Значения параметров */ /* планирования */ int shdplc; /* Политика планирования */ pthread_t pt_mi; /* Идентификатор потока */ /* со средним приоритетом */ pthread_t pt_hi; /* Идентификатор потока */ /* с высоким приоритетом */
    /* Создадим два семафора в захваченном состоянии */ assert (sem_init (&sem_mi, 0, 0) == 0); assert (sem_init (&sem_hi, 0, 0) == 0);
    /* Установим политику планирования и сделаем */ /* текущий поток управления низкоприоритетным */ shdprm.sched_priority = sched_get_priority_max (SCHED_FIFO) – 31; assert (pthread_setschedparam (pthread_self (), SCHED_FIFO, &shdprm) == 0);
    /* Инициализируем атрибутный объект */ /* для создаваемых потоков управления */ assert (pthread_attr_init (&attrob) == 0);
    /* Установим атрибуты планирования */ assert (pthread_attr_setinheritsched (&attrob, PTHREAD_EXPLICIT_SCHED) == 0); assert (pthread_attr_setschedpolicy (&attrob, SCHED_FIFO) == 0); shdprm.sched_priority += 15; assert (pthread_attr_setschedparam (&attrob, &shdprm) == 0);
    /* Создадим поток управления со средним приоритетом */ assert (pthread_create (&pt_mi, &attrob, start_mi, NULL) == 0);
    /* Подправим атрибутный объект и создадим */ /* поток управления с высоким приоритетом */ shdprm.sched_priority += 15; assert (pthread_attr_setschedparam (&attrob, &shdprm) == 0); assert (pthread_create (&pt_hi, &attrob, start_hi, NULL) == 0);
    /* Опросим параметры планирования потоков управления */ assert (pthread_getschedparam (pthread_self (), &shdplc, &shdprm) == 0); assert (shdplc == SCHED_FIFO); printf ("Низкий приоритет: %d\n", shdprm.sched_priority); assert (pthread_getschedparam (pt_mi, &shdplc, &shdprm) == 0); assert (shdplc == SCHED_FIFO); printf ("Средний приоритет: %d\n", shdprm.sched_priority); assert (pthread_getschedparam (pt_hi, &shdplc, &shdprm) == 0); assert (shdplc == SCHED_FIFO); printf ("Высокий приоритет: %d\n", shdprm.sched_priority);
    /* Создадим ситуацию инверсии приоритетов */ printf ("Поток с низким приоритетом\n" "перед освобождением семафора для потока " "со средним приоритетом\n"); assert (sem_post (&sem_mi) == 0);
    printf ("Поток с низким приоритетом\n" "перед освобождением семафора для потока " "с высоким приоритетом\n"); assert (sem_post (&sem_hi) == 0);
    (void) pthread_join (pt_mi, NULL); (void) pthread_join (pt_hi, NULL);
    assert (sem_destroy (&sem_mi) == 0); assert (sem_destroy (&sem_hi) == 0);
    return 0; }
    Листинг 6.7. Пример программы, моделирующей ситуацию инверсии приоритетов.
    Закрыть окно




    Поток со средним приоритетом перед захватом семафора Поток с высоким приоритетом перед захватом семафора Низкий приоритет: 224 Средний приоритет: 239 Высокий приоритет: 254 Поток с низким приоритетом перед освобождением семафора для потока со средним приоритетом Поток со средним приоритетом перед освобождением семафора Поток с низким приоритетом перед освобождением семафора для потока с высоким приоритетом Поток с высоким приоритетом после захвата семафора
    Листинг 6.8. Возможные результаты выполнения программы, моделирующей ситуацию инверсии приоритетов, под управлением операционной системы реального времени oc2000.
    Закрыть окно



    Функции асинхронного ввода/вывода


    Основными операциями асинхронного ввода/вывода являются чтение и запись данных (см. листинг 7.1).
    #include int aio_read (struct aiocb *aiocbp); int aio_write (struct aiocb *aiocbp);
    Листинг 7.1. Описание функций асинхронного чтения и записи. (html, txt)
    Функция aio_read() инициирует запрос на чтение aiocbp->aio_nbytes байт из файла, ассоциированного с дескриптором aiocbp->aio_fildes, начиная с абсолютной позиции aiocbp->aio_offset, в буфер с адресом aiocbp->aio_buf. Возврат из функции произойдет тогда, когда запрос будет принят к исполнению или поставлен в очередь; при этом нормальный результат равен нулю, а значение -1 (в совокупности с errno) свидетельствует об ошибке (например, исчерпаны ресурсы и запрос не удалось поставить в очередь).
    Функция aio_write() осуществляет соответствующие действия для записи данных. Если для файлового дескриптора aiocbp->aio_fildes установлен флаг O_APPEND, запись будет производиться в конец файла.
    Функция lio_listio() (см. листинг 7.2) позволяет за один вызов инициировать (поставить в очередь) список запросов на чтение и/или запись данных.
    #include int lio_listio ( int mode, struct aiocb *restrict const listio [restrict], int nent, struct sigevent *restrict sigev);
    Листинг 7.2. Описание функции lio_listio(). (html, txt)
    Элементы массива listio [] специфицируют отдельные запросы. В полях aio_lio_opcode указуемых структур типа aiocb могут располагаться значения LIO_READ (запрос на чтение), LIO_WRITE (запрос на запись) или LIO_NOP (пустой запрос). Остальные элементы указуемых структур интерпретируются в соответствии со смыслом запроса (можно считать, что адреса структур передаются в качестве аргументов функциям aio_read() или aio_write()). Число элементов в массиве listio [] задается аргументом nent.
    Значение аргумента mode определяет способ обработки списка запросов – синхронный (LIO_WAIT) или асинхронный (LIO_NOWAIT). В первом случае возврат из вызова lio_listio() произойдет только после завершения всех заказанных операций ввода/вывода; при этом аргумент sigev игнорируется.
    Во втором случае возврат из lio_listio() произойдет немедленно, а по завершении всех операций ввода/вывода в соответствии со значением аргумента sigev будет сгенерировано асинхронное уведомление.

    Нормальный результат выполнения функции lio_listio() равен нулю, но для двух описанных случаев он имеет разный смысл – успешное завершение всех операций ввода/вывода или только успешная постановка запросов в очередь соответственно. Во втором случае часть операций ввода/вывода может завершиться неудачей, и с каждой из них придется разбираться индивидуально. Подобное разбирательство позволяют осуществить функции aio_return() и aio_error() (см. листинг 7.3).

    #include

    ssize_t aio_return ( struct aiocb *aiocbp);

    int aio_error ( const struct aiocb *aiocbp);

    Листинг 7.3. Описание функций aio_return() и aio_error(). (html, txt)

    Аргументом этих функций служит адрес управляющего блока, который играет в данном случае роль идентификатора запроса. Функция aio_return() выдает значение, которое вернула бы соответствующая функция обычного ввода/вывода (если операция ввода/вывода в момент опроса не была завершена, возвращаемое значение, разумеется, не определено). К идентификатору данного запроса функцию aio_return() можно применить ровно один раз; последующие вызовы aio_return() и aio_error() могут завершиться неудачей. (Конечно, если в той же указуемой структуре типа aiocb сформировать новый запрос и выполнить его, возможность корректного вызова функций aio_return() и aio_error() появляется снова.)

    Результатом вызова aio_error() служит значение, которое установила бы в переменную errno соответствующая функция обычного ввода/вывода (или, если операция еще не завершена, результат равен EINPROGRESS). Если операция асинхронного ввода/вывода завершилась успешно, aio_error() вернет нулевой результат. Функцию aio_error() к идентификатору данного запроса можно применять произвольное число раз, и до завершения его выполнения, и после.

    Ожидающие обработки запросы на асинхронный ввод/вывод можно аннулировать, воспользовавшись функцией aio_cancel() (см.


    листинг 7.4).

    #include int aio_cancel (int fildes, struct aiocb *aiocbp);

    Листинг 7.4. Описание функции aio_cancel(). (html, txt)

    Функция aio_cancel() пытается аннулировать один ( если значение аргумента aiocbp отлично от NULL) или все ожидающие обработки запросы, в которых фигурирует файловый дескриптор fildes. После аннулирования генерируется соответствующее асинхронное уведомление, статус ошибки устанавливается равным ECANCELED, а в качестве возвращаемого значения выдается -1.

    Если запрос не удается аннулировать (возможные причины этого, вообще говоря, зависят от реализации), он выполняется стандартным образом.

    Результат вызова aio_cancel() равен AIO_CANCELED, если все указанные запросы аннулированы. Если хотя бы один из запросов уже выполняется, он не может быть аннулирован, и тогда в качестве результата возвращается значение AIO_NOTCANCELED (а со статусом запросов, как и для функции lio_listio(), нужно разбираться индивидуально, пользуясь функцией aio_error()). Результат AIO_ALLDONE свидетельствует о том, что выполнение всех указанных запросов не только начато, но и уже завершено. Во всех остальных случаях результат вызова aio_cancel() равен -1.



    Какой именно, как обычно, нужно выяснять индивидуально, пользуясь функциями aio_error() и aio_return().

    Описываемая далее группа функций (см. листинг 7.6) предназначена для согласования состояния буферизованных и хранимых в долговременной памяти данных, обеспечения целостности данных и файлов. Подобные функции необходимы в системах обработки транзакций, чтобы гарантировать определенное состояние долговременной памяти, даже если система аварийно завершит работу. Один элемент из этой группы – функцию msync() – мы уже рассматривали.

    #include void sync (void);

    #include int fsync (int fildes);

    #include int fdatasync (int fildes);

    #include int aio_fsync (int op, struct aiocb *aiocbp);

    Листинг 7.6. Описание функций синхронизации оперативной и долговременной памяти.

    Функция sync() имеет глобальный характер – она планирует запись в файловые системы всех измененных данных.

    Функция fsync() обеспечивает запись измененных данных в файл, заданный дескриптором fildes. Более того, после успешного возврата из функции (с нулевым результатом) окажутся выполненными все стоявшие в очереди к этому файлу запросы на ввод/вывод с гарантией целостности файла.

    Функция fdatasync() аналогична fsync(), но гарантируется лишь целостность данных (см. курс [1]).

    Функция aio_fsync() осуществляет "асинхронную синхронизацию" файла. Иными словами, порождается запрос, выполнение которого при значении аргумента op, равном O_DSYNC, эквивалентно вызову fdatasync (aiocbp->aio_fildes), а при op, равном O_SYNC, – fsync (aiocbp->aio_fildes). Как и для других операций асинхронного ввода/вывода, адрес управляющего блока может использоваться в качестве аргумента функций aio_error() и aio_return(), а после завершения генерируется асинхронное уведомление в соответствии со значением элемента aiocbp->aio_sigevent. Все остальные поля указуемой структуры типа aiocb игнорируются.

    Проиллюстрируем использование функций асинхронного ввода/вывода программой, подсчитывающей суммарное число строк в совокупности файлов (см.


    листинг 7.7).

    /* * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает суммарное */ /* число строк – в файлах аргументах */ /* командной строки. */ /* Если аргументы отсутствуют или в */ /* качестве имени задан минус, */ /* читается стандартный ввод. */ /* * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include #include #include #include

    int main (int argc, char *argv []) { /* Указатель на массив */ /* указателей на управляющие */ /* блоки запросов операций */ /* асинхронного чтения */ struct aiocb **lio_ptr; // Общее число строк в файлах long nlines = 0; // Признак повторного указания // стандартного ввода int dup_stdin = 0; int i;

    /* Сведем случай с отсутствием */ /* аргументов к общему, */ /* воспользовавшись одним из */ /* немногих стандартизованных */ /* файлов */ if (argc == 1) { argv [0] = "-"; } else { argv [0] = "/dev/null"; }

    /* Зарезервируем память, */ /* откроем заданные файлы */ /* и сформируем начальный список */ /* запросов на чтение */ assert ((lio_ptr = (struct aiocb **) malloc (sizeof (struct aiocb *) * argc)) != NULL);

    for (i = 0; i < argc; i++) { assert ((lio_ptr [i] = (struct aiocb *) malloc (sizeof (struct aiocb))) != NULL);

    if (strcmp (argv [i], "-") == 0) { if (dup_stdin == 0) { lio_ptr [i]->aio_fildes = STDIN_FILENO; dup_stdin = 1; } else { lio_ptr [i]->aio_fildes = fcntl (STDIN_FILENO, F_DUPFD, 0); } } else if ((lio_ptr [i]->aio_fildes = open (argv [i], O_RDONLY)) == -1) { perror ("OPEN"); free (lio_ptr [i]); lio_ptr [i] = NULL; continue; }

    lio_ptr [i]->aio_offset = 0; assert ((lio_ptr [i]->aio_buf = malloc(BUFSIZ)) != NULL); lio_ptr [i]->aio_nbytes = BUFSIZ; lio_ptr [i]->aio_reqprio = 0; lio_ptr [i]->aio_sigevent.sigev_notify = SIGEV_NONE; lio_ptr [i]->aio_lio_opcode = LIO_READ; } /* for (i < argc) */



    /* Поехали ... */ assert (lio_listio (LIO_NOWAIT, lio_ptr, argc, &lio_ptr [0]->aio_sigevent) == 0);

    /* Будем ждать завершения */ /* операций ввода/вывода, */ /* обрабатывать прочитанные */ /* данные и */ /* инициировать новые запросы */ /* на чтение */ while (aio_suspend (( const struct aiocb **) lio_ptr, argc, NULL) == 0) { /* Выясним, какой запрос */ /* и как выполнен */

    /* Число недочитанных файлов */ int nreqs = 0;

    for (i = 0; i < argc; i++) { if (lio_ptr [i] == NULL) { continue; }

    /* Есть обслуживаемые */ /* запросы */ nreqs++;

    if (aio_error (lio_ptr [i]) == EINPROGRESS) { continue; } { // Запрос выполнен

    // Число прочитанных байт ssize_t nb;

    if ((nb = aio_return (lio_ptr [i])) <= 0) { /* Дошли до конца файла*/ /* или чтение */ /* завершилось ошибкой */ (void) close(lio_ptr [i]->aio_fildes); free ((void *) lio_ptr [i]->aio_buf); free (lio_ptr [i]); lio_ptr [i] = NULL; nreqs--; } else { /* Обработаем прочитанные */ /* данные */

    /* Текущее начало поиска */ /* перевода строки */ char *p; /* Позиция, где нашли */ /* перевод строки */ char *p1; p = (char *) lio_ptr [i]->aio_buf; while ((p1 = (char *) memchr (p, '\n', nb - (p – (char *) lio_ptr [i]->aio_buf))) != NULL) { nlines++; p = p1 + 1; }

    /* Инициируем новый */ /* запрос на чтение */ lio_ptr [i]->aio_offset += nb; (void) aio_read (lio_ptr [i]); } } } /* for (i < argc) */

    /* Остались недочитанные */ /* файлы? */ if (nreqs == 0) { break; } } /* while */

    printf ("%ld\n", nlines);

    return 0; }

    Листинг 7.7. Пример программы, использующей функции асинхронного ввода/вывода.

    Отметим списочную форму начального представления и ожидания выполнения запросов на асинхронное чтение. Из технических деталей обратим внимание на пустой указатель в качестве элемента массива указателей на структуры типа aiocb (функция lio_listio() игнорирует такие элементы) и в качестве аргумента timeout функции aio_suspend() (означает бесконечное ожидание).

    Второй вариант решения той же задачи (см.


    листинг 7.8) демонстрирует другой способ уведомления о завершении операции асинхронного ввода/вывода – вызов функции.

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает суммарное число строк */ /* в файлах – аргументах командной строки. */ /* Если аргументы отсутствуют или в качестве имени */ /* задан минус, читается стандартный ввод. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include #include #include #include

    /* Указатель на массив указателей на управляющие */ /* блоки запросов операций асинхронного чтения */ static struct aiocb **lio_ptr;

    /* Общее число строк в файлах */ static long nlines_total = 0;

    /* Переменная условия, на которой ждут */ /* окончания обработки всех файлов */ static pthread_cond_t open_files_cond = PTHREAD_COND_INITIALIZER;

    /* Мьютекс, охраняющий доступ */ /* к переменной условия и nlines_total */ static pthread_mutex_t nlines_mutex = PTHREAD_MUTEX_INITIALIZER;

    /* * * * * * * * * * * * * * */ /* Функция, вызываемая */ /* при завершении операции */ /* асинхронного ввода/вывода */ /* * * * * * * * * * * * * * */ static void end_of_aio_op (union sigval sg_vl) { int no; /* Номер выполненного запроса */ /* в общем списке */ ssize_t nb; /* Число прочитанных байт */ long nlines = 0; /* Число строк в одной */ /* прочитанной порции */ no = sg_vl.sival_int;

    if ((nb = aio_return (lio_ptr [no])) <= 0) { /* Дошли до конца файла */ /* или чтение завершилось ошибкой */ (void) close (lio_ptr [no]->aio_fildes); free ((void *) lio_ptr [no]->aio_buf); free (lio_ptr [no]); lio_ptr [no] = NULL; (void) pthread_cond_signal (&open_files_cond); } else { /* Обработаем прочитанные данные */ char *p; /* Текущее начало поиска перевода строки */ char *p1; /* Позиция, где нашли перевод строки */

    p = (char *) lio_ptr [no]->aio_buf; while ((p1 = (char *) memchr (p, '\n', nb - (p – (char *) lio_ptr [no]->aio_buf))) != NULL) { nlines++; p = p1 + 1; }



    /* Прибавим локальную сумму к общей */ (void) pthread_mutex_lock (&nlines_mutex); nlines_total += nlines; (void) pthread_mutex_unlock (&nlines_mutex);

    /* Инициируем новый запрос на чтение */ lio_ptr [no]->aio_offset += nb; (void) aio_read (lio_ptr [no]); } }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция проверяет, сколько осталось открытых файлов */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static int n_open_files (int nfiles) { int nof = 0; /* Число открытых файлов */ int i;

    for (i = 0; i < nfiles; i++) { if (lio_ptr [i] != NULL) { nof++; } }

    return (nof); }

    /* * * * * * * * * * * * * * * * * * * * */ /* Обработка аргументов командной строки, */ /* инициация начальных запросов на чтение, */ /* ожидание завершения обработки файлов, */ /* вывод результатов */ /* * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { int dup_stdin = 0; /* Признак повторного указания */ /* стандартного ввода */ int i;

    /* Сведем случай с отсутствием аргументов к общему, */ /* воспользовавшись одним из немногих */ /* стандартизованных файлов */ if (argc == 1) { argv [0] = "-"; } else { argv [0] = "/dev/null"; }

    /* Зарезервируем память, откроем заданные файлы */ /* и инициируем начальные запросы на чтение */ assert ((lio_ptr = (struct aiocb **) malloc (sizeof (struct aiocb *) * argc)) != NULL);

    for (i = 0; i < argc; i++) { assert ((lio_ptr [i] = (struct aiocb *) malloc (sizeof (struct aiocb))) != NULL);

    if (strcmp (argv [i], "-") == 0) { if (dup_stdin == 0) { lio_ptr [i]->aio_fildes = STDIN_FILENO; dup_stdin = 1; } else { lio_ptr [i]->aio_fildes = fcntl (STDIN_FILENO, F_DUPFD, 0); } } else if ((lio_ptr [i]->aio_fildes = open (argv [i], O_RDONLY)) == -1) { perror ("OPEN"); free (lio_ptr [i]); lio_ptr [i] = NULL; continue; }

    lio_ptr [i]->aio_offset = 0; assert ((lio_ptr [i]->aio_buf = malloc (BUFSIZ)) != NULL); lio_ptr [i]->aio_nbytes = BUFSIZ; lio_ptr [i]->aio_reqprio = 0;



    lio_ptr [i]->aio_sigevent.sigev_notify = SIGEV_THREAD; lio_ptr [i]->aio_sigevent.sigev_signo = SIGRTMIN; lio_ptr [i]->aio_sigevent.sigev_value.sival_int = i; lio_ptr [i]->aio_sigevent.sigev_notify_function = end_of_aio_op; lio_ptr [i]->aio_sigevent.sigev_notify_attributes = NULL;

    /* Запрос готов, можно отправлять его на выполнение */ (void) aio_read (lio_ptr [i]); } /* for (i < argc) */

    /* Дождемся завершения обработки всех указанных */ /* в командной строке файлов */ while (n_open_files (argc) > 0) { (void) pthread_cond_wait (&open_files_cond, &nlines_mutex); }

    printf ("%ld\n", nlines_total);

    return 0; }

    Листинг 7.8. Модифицированный вариант программы, использующей функции асинхронного ввода/вывода.

    В модифицированном варианте можно отметить применение переменных условия как средства ожидания завершения "вдвойне асинхронной" обработки файлов (по вводу/выводу и по выполнению функций процессирования прочитанных данных в рамках специально создаваемых потоков управления). Когда очередной файл закрывается, то для генерации уведомления о возможном завершении всей обработки вызывается функция pthread_cond_signal(). Обратим внимание на то, что в этот момент вызывающий поток не является владельцем мьютекса, ассоциированного с переменной условия, но в данном случае это и не требуется.


    Основные идеи, понятия и объекты асинхронного ввода/вывода


    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных, продолжать работу параллельно с операциями передачи данных и получать асинхронные уведомления о завершении выполнения этих команд. Подобные возможности полезны для многих приложений реального времени, поскольку исключают задержки непредсказуемой длительности (или, по крайней мере, перекладывают эти задержки на другие компоненты). Типичный пример – сохранение данных для постпроцессирования (которое будет проводиться не в реальном времени).
    Вообще говоря, операции асинхронного ввода/вывода могут перекрываться во времени не только с работой инициировавшего их процесса, но и между собой, то есть в принципе возможно параллельное выполнение множества команд обмена данными с множеством файлов.
    Операция асинхронного чтения или записи данных считается завершенной, когда выполнен соответствующий синхронный ввод или вывод и модифицированы все ассоциированные поля состояния (например, время последнего доступа к файлу или последнего изменения файла).
    За исключением параллельной работы с инициировавшим их приложением и интерфейсных отличий, операции асинхронного ввода/вывода ведут себя так же, как и обычные функции read(), write(), lseek() и fsync(). Выдать запрос на асинхронный ввод/вывод – все равно, что создать отдельный поток управления, который неделимым образом осуществит позиционирование в файле и обмен данными.
    Параллельные асинхронные и синхронные операции обмена с одним и тем же файлом изменяют его так, как если бы они выполнялись последовательно.
    После завершения асинхронной операции ввода/вывода приложению может быть доставлен сигнал, который можно интерпретировать как разрешение на доступ к читаемым данным и повторное использование задействованных в операции буферов и управляющих блоков.
    "Жизненный цикл" операции асинхронного ввода/вывода включает два этапа:
  • постановка запроса в очередь;
  • выполнение запроса, осуществление ввода/вывода.

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

    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных, продолжать работу параллельно с операциями передачи данных и получать асинхронные уведомления о завершении выполнения этих команд. Подобные возможности полезны для многих приложений реального времени, поскольку исключают задержки непредсказуемой длительности (или, по крайней мере, перекладывают эти задержки на другие компоненты). Типичный пример – сохранение данных для постпроцессирования (которое будет проводиться не в реальном времени).
    Вообще говоря, операции асинхронного ввода/вывода могут перекрываться во времени не только с работой инициировавшего их процесса, но и между собой, то есть в принципе возможно параллельное выполнение множества команд обмена данными с множеством файлов.
    Операция асинхронного чтения или записи данных считается завершенной, когда выполнен соответствующий синхронный ввод или вывод и модифицированы все ассоциированные поля состояния (например, время последнего доступа к файлу или последнего изменения файла).
    За исключением параллельной работы с инициировавшим их приложением и интерфейсных отличий, операции асинхронного ввода/вывода ведут себя так же, как и обычные функции read(), write(), lseek() и fsync(). Выдать запрос на асинхронный ввод/вывод – все равно, что создать отдельный поток управления, который неделимым образом осуществит позиционирование в файле и обмен данными.
    Параллельные асинхронные и синхронные операции обмена с одним и тем же файлом изменяют его так, как если бы они выполнялись последовательно.
    После завершения асинхронной операции ввода/вывода приложению может быть доставлен сигнал, который можно интерпретировать как разрешение на доступ к читаемым данным и повторное использование задействованных в операции буферов и управляющих блоков.
    "Жизненный цикл" операции асинхронного ввода/вывода включает два этапа:
  • постановка запроса в очередь;
  • выполнение запроса, осуществление ввода/вывода.

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



    Пока операция не завершена, ее статус ошибки имеет значение EINPROGRESS. Теоретически, приложение может дожидаться завершения асинхронной операции, периодически опрашивая статус ошибки, пока не получит значение, отличное от EINPROGRESS.

    Из общих соображений следует, что если есть возможность поставить запрос в очередь, должны предоставляться средства для последующего удаления его из очереди (изменились обстоятельства, запрос стал не нужен). Стандарт POSIX-2001 удовлетворяет этому требованию.

    Полезно иметь возможность за один вызов поставить в очередь целый список предварительно сформированных запросов, специфицирующих операции ввода/вывода, вообще говоря, с разными файлами. Стандарт POSIX-2001 позволяет и это.

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

    Наиболее важным объектом, обслуживающим асинхронный ввод/вывод, является управляющий блок, оформленный в стандарте POSIX-2001 как структура типа aiocb со следующими полями.

    int aio_fildes; // Файловый дескриптор off_t aio_offset; // Позиция в файле volatile void *aio_buf; // Адрес буфера size_t aio_nbytes; // Число передаваемых // байт int aio_reqprio; // Величина понижения // приоритета struct sigevent aio_sigevent; // Номер и // значение // сигнала int aio_lio_opcode; // Запрошенная // операция

    Значением элемента aio_fildes является дескриптор файла, над которым выполняется асинхронная операция.

    Элемент aio_offset задает (абсолютную) позицию в файле, начиная с которой будет производиться ввод/вывод. Если для файлового дескриптора установлен флаг O_APPEND или заданное дескриптором устройство не поддерживает позиционирования, операции записи добавляют данные в конец.



    Поля aio_buf и aio_nbytes имеют тот же смысл, что и соответствующие аргументы функций read() и write().

    Если реализация поддерживает приоритетное планирование, очередь запросов на асинхронный ввод/вывод упорядочивается в соответствии с текущими приоритетами вызывающих процессов. Элемент aio_reqprio позволяет понизить (но не повысить) приоритет запроса. Его значение должно находиться в диапазоне от нуля до AIO_PRIO_DELTA_MAX включительно; оно вычитается из приоритета процесса.

    Элемент aio_sigevent, имеющий структурный тип sigevent, определяет способ уведомления вызывающего процесса о завершении операции ввода/вывода. Если значение aio_sigevent.sigev_notify равно SIGEV_NONE, никаких сигналов по завершении генерироваться не будет, но возвращаемое значение и статус ошибки будут сформированы должным образом.

    Элемент aio_lio_opcode используется только в списках запросов и специфицирует операцию чтения или записи.

    Адрес управляющего блока играет роль идентификатора, позволяющего получить доступ к возвращаемому значению и статусу ошибки.

    Управляющий блок и буфера данных, ассоциированные с операцией асинхронного ввода/вывода, используются системой тогда и только тогда, когда статус ошибки имеет значение EINPROGRESS. Разумеется, приложение не должно модифицировать в это время структуру aiocb.



    Пока операция не завершена, ее статус ошибки имеет значение EINPROGRESS. Теоретически, приложение может дожидаться завершения асинхронной операции, периодически опрашивая статус ошибки, пока не получит значение, отличное от EINPROGRESS.

    Из общих соображений следует, что если есть возможность поставить запрос в очередь, должны предоставляться средства для последующего удаления его из очереди (изменились обстоятельства, запрос стал не нужен). Стандарт POSIX-2001 удовлетворяет этому требованию.

    Полезно иметь возможность за один вызов поставить в очередь целый список предварительно сформированных запросов, специфицирующих операции ввода/вывода, вообще говоря, с разными файлами. Стандарт POSIX-2001 позволяет и это.

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

    Наиболее важным объектом, обслуживающим асинхронный ввод/вывод, является управляющий блок, оформленный в стандарте POSIX-2001 как структура типа aiocb со следующими полями.

    int aio_fildes; // Файловый дескриптор off_t aio_offset; // Позиция в файле volatile void *aio_buf; // Адрес буфера size_t aio_nbytes; // Число передаваемых // байт int aio_reqprio; // Величина понижения // приоритета struct sigevent aio_sigevent; // Номер и // значение // сигнала int aio_lio_opcode; // Запрошенная // операция

    Значением элемента aio_fildes является дескриптор файла, над которым выполняется асинхронная операция.

    Элемент aio_offset задает (абсолютную) позицию в файле, начиная с которой будет производиться ввод/вывод. Если для файлового дескриптора установлен флаг O_APPEND или заданное дескриптором устройство не поддерживает позиционирования, операции записи добавляют данные в конец.



    Поля aio_buf и aio_nbytes имеют тот же смысл, что и соответствующие аргументы функций read() и write().

    Если реализация поддерживает приоритетное планирование, очередь запросов на асинхронный ввод/вывод упорядочивается в соответствии с текущими приоритетами вызывающих процессов. Элемент aio_reqprio позволяет понизить (но не повысить) приоритет запроса. Его значение должно находиться в диапазоне от нуля до AIO_PRIO_DELTA_MAX включительно; оно вычитается из приоритета процесса.

    Элемент aio_sigevent, имеющий структурный тип sigevent, определяет способ уведомления вызывающего процесса о завершении операции ввода/вывода. Если значение aio_sigevent.sigev_notify равно SIGEV_NONE, никаких сигналов по завершении генерироваться не будет, но возвращаемое значение и статус ошибки будут сформированы должным образом.

    Элемент aio_lio_opcode используется только в списках запросов и специфицирует операцию чтения или записи.

    Адрес управляющего блока играет роль идентификатора, позволяющего получить доступ к возвращаемому значению и статусу ошибки.

    Управляющий блок и буфера данных, ассоциированные с операцией асинхронного ввода/вывода, используются системой тогда и только тогда, когда статус ошибки имеет значение EINPROGRESS. Разумеется, приложение не должно модифицировать в это время структуру aiocb.


    Описание функций асинхронного чтения


    #include int aio_read (struct aiocb *aiocbp); int aio_write (struct aiocb *aiocbp);
    Листинг 7.1. Описание функций асинхронного чтения и записи.
    Закрыть окно




    #include int lio_listio ( int mode, struct aiocb *restrict const listio [restrict], int nent, struct sigevent *restrict sigev);
    Листинг 7.2. Описание функции lio_listio().
    Закрыть окно




    #include
    ssize_t aio_return ( struct aiocb *aiocbp);
    int aio_error ( const struct aiocb *aiocbp);
    Листинг 7.3. Описание функций aio_return() и aio_error().
    Закрыть окно




    #include int aio_cancel ( int fildes, struct aiocb *aiocbp);
    Листинг 7.4. Описание функции aio_cancel().
    Закрыть окно




    #include int aio_suspend ( const struct aiocb *const listio [], int nent, const struct timespec *timeout);
    Листинг 7.5. Описание функции aio_suspend().
    Закрыть окно




    #include void sync (void);
    #include int fsync (int fildes);
    #include int fdatasync (int fildes);
    #include int aio_fsync ( int op, struct aiocb *aiocbp);
    Листинг 7.6. Описание функций синхронизации оперативной и долговременной памяти.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает суммарное */ /* число строк – в файлах аргументах */ /* командной строки. */ /* Если аргументы отсутствуют или в */ /* качестве имени задан минус, */ /* читается стандартный ввод. */ /* * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include #include
    int main (int argc, char *argv []) { /* Указатель на массив */ /* указателей на управляющие */ /* блоки запросов операций */ /* асинхронного чтения */ struct aiocb **lio_ptr; // Общее число строк в файлах long nlines = 0; // Признак повторного указания // стандартного ввода int dup_stdin = 0; int i;
    /* Сведем случай с отсутствием */ /* аргументов к общему, */ /* воспользовавшись одним из */ /* немногих стандартизованных */ /* файлов */ if (argc == 1) { argv [0] = "-"; } else { argv [0] = "/dev/null"; }
    /* Зарезервируем память, */ /* откроем заданные файлы */ /* и сформируем начальный список */ /* запросов на чтение */ assert ((lio_ptr = (struct aiocb **) malloc (sizeof (struct aiocb *) * argc)) != NULL);
    for (i = 0; i < argc; i++) { assert ((lio_ptr [i] = (struct aiocb *) malloc (sizeof (struct aiocb))) != NULL);
    if (strcmp (argv [i], "-") == 0) { if (dup_stdin == 0) { lio_ptr [i]->aio_fildes = STDIN_FILENO; dup_stdin = 1; } else { lio_ptr [i]->aio_fildes = fcntl (STDIN_FILENO, F_DUPFD, 0); } } else if ((lio_ptr [i]->aio_fildes = open (argv [i], O_RDONLY)) == -1) { perror ("OPEN"); free (lio_ptr [i]); lio_ptr [i] = NULL; continue; }
    lio_ptr [i]->aio_offset = 0; assert ((lio_ptr [i]->aio_buf = malloc(BUFSIZ)) != NULL); lio_ptr [i]->aio_nbytes = BUFSIZ; lio_ptr [i]->aio_reqprio = 0; lio_ptr [i]->aio_sigevent.sigev_notify = SIGEV_NONE; lio_ptr [i]->aio_lio_opcode = LIO_READ; } /* for (i < argc) */
    /* Поехали ... */ assert (lio_listio (LIO_NOWAIT, lio_ptr, argc, &lio_ptr [0]->aio_sigevent) == 0);
    /* Будем ждать завершения */ /* операций ввода/вывода, */ /* обрабатывать прочитанные */ /* данные и */ /* инициировать новые запросы */ /* на чтение */ while (aio_suspend (( const struct aiocb **) lio_ptr, argc, NULL) == 0) { /* Выясним, какой запрос */ /* и как выполнен */
    /* Число недочитанных файлов */ int nreqs = 0;
    for (i = 0; i < argc; i++) { if (lio_ptr [i] == NULL) { continue; }
    /* Есть обслуживаемые */ /* запросы */ nreqs++;
    if (aio_error (lio_ptr [i]) == EINPROGRESS) { continue; } { // Запрос выполнен
    // Число прочитанных байт ssize_t nb;
    if ((nb = aio_return (lio_ptr [i])) <= 0) { /* Дошли до конца файла*/ /* или чтение */ /* завершилось ошибкой */ (void) close(lio_ptr [i]->aio_fildes); free ((void *) lio_ptr [i]->aio_buf); free (lio_ptr [i]); lio_ptr [i] = NULL; nreqs--; } else { /* Обработаем прочитанные */ /* данные */
    /* Текущее начало поиска */ /* перевода строки */ char *p; /* Позиция, где нашли */ /* перевод строки */ char *p1; p = (char *) lio_ptr [i]->aio_buf; while ((p1 = (char *) memchr (p, '\n', nb - (p – (char *) lio_ptr [i]->aio_buf))) != NULL) { nlines++; p = p1 + 1; }
    /* Инициируем новый */ /* запрос на чтение */ lio_ptr [i]->aio_offset += nb; (void) aio_read (lio_ptr [i]); } } } /* for (i < argc) */
    /* Остались недочитанные */ /* файлы? */ if (nreqs == 0) { break; } } /* while */
    printf ("%ld\n", nlines);
    return 0; }
    Листинг 7.7. Пример программы, использующей функции асинхронного ввода/вывода.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает суммарное число строк */ /* в файлах – аргументах командной строки. */ /* Если аргументы отсутствуют или в качестве имени */ /* задан минус, читается стандартный ввод. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include #include #include #include
    /* Указатель на массив указателей на управляющие */ /* блоки запросов операций асинхронного чтения */ static struct aiocb **lio_ptr;
    /* Общее число строк в файлах */ static long nlines_total = 0;
    /* Переменная условия, на которой ждут */ /* окончания обработки всех файлов */ static pthread_cond_t open_files_cond = PTHREAD_COND_INITIALIZER;
    /* Мьютекс, охраняющий доступ */ /* к переменной условия и nlines_total */ static pthread_mutex_t nlines_mutex = PTHREAD_MUTEX_INITIALIZER;
    /* * * * * * * * * * * * * * */ /* Функция, вызываемая */ /* при завершении операции */ /* асинхронного ввода/вывода */ /* * * * * * * * * * * * * * */ static void end_of_aio_op (union sigval sg_vl) { int no; /* Номер выполненного запроса */ /* в общем списке */ ssize_t nb; /* Число прочитанных байт */ long nlines = 0; /* Число строк в одной */ /* прочитанной порции */ no = sg_vl.sival_int;
    if ((nb = aio_return (lio_ptr [no])) <= 0) { /* Дошли до конца файла */ /* или чтение завершилось ошибкой */ (void) close (lio_ptr [no]->aio_fildes); free ((void *) lio_ptr [no]->aio_buf); free (lio_ptr [no]); lio_ptr [no] = NULL; (void) pthread_cond_signal (&open_files_cond); } else { /* Обработаем прочитанные данные */ char *p; /* Текущее начало поиска перевода строки */ char *p1; /* Позиция, где нашли перевод строки */
    p = (char *) lio_ptr [no]->aio_buf; while ((p1 = (char *) memchr (p, '\n', nb - (p – (char *) lio_ptr [no]->aio_buf))) != NULL) { nlines++; p = p1 + 1; }
    /* Прибавим локальную сумму к общей */ (void) pthread_mutex_lock (&nlines_mutex); nlines_total += nlines; (void) pthread_mutex_unlock (&nlines_mutex);
    /* Инициируем новый запрос на чтение */ lio_ptr [no]->aio_offset += nb; (void) aio_read (lio_ptr [no]); } }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция проверяет, сколько осталось открытых файлов */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static int n_open_files (int nfiles) { int nof = 0; /* Число открытых файлов */ int i;
    for (i = 0; i < nfiles; i++) { if (lio_ptr [i] != NULL) { nof++; } }
    return (nof); }
    /* * * * * * * * * * * * * * * * * * * * */ /* Обработка аргументов командной строки, */ /* инициация начальных запросов на чтение, */ /* ожидание завершения обработки файлов, */ /* вывод результатов */ /* * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { int dup_stdin = 0; /* Признак повторного указания */ /* стандартного ввода */ int i;
    /* Сведем случай с отсутствием аргументов к общему, */ /* воспользовавшись одним из немногих */ /* стандартизованных файлов */ if (argc == 1) { argv [0] = "-"; } else { argv [0] = "/dev/null"; }
    /* Зарезервируем память, откроем заданные файлы */ /* и инициируем начальные запросы на чтение */ assert ((lio_ptr = (struct aiocb **) malloc (sizeof (struct aiocb *) * argc)) != NULL);
    for (i = 0; i < argc; i++) { assert ((lio_ptr [i] = (struct aiocb *) malloc (sizeof (struct aiocb))) != NULL);
    if (strcmp (argv [i], "-") == 0) { if (dup_stdin == 0) { lio_ptr [i]->aio_fildes = STDIN_FILENO; dup_stdin = 1; } else { lio_ptr [i]->aio_fildes = fcntl (STDIN_FILENO, F_DUPFD, 0); } } else if ((lio_ptr [i]->aio_fildes = open (argv [i], O_RDONLY)) == -1) { perror ("OPEN"); free (lio_ptr [i]); lio_ptr [i] = NULL; continue; }
    lio_ptr [i]->aio_offset = 0; assert ((lio_ptr [i]->aio_buf = malloc (BUFSIZ)) != NULL); lio_ptr [i]->aio_nbytes = BUFSIZ; lio_ptr [i]->aio_reqprio = 0;
    lio_ptr [i]->aio_sigevent.sigev_notify = SIGEV_THREAD; lio_ptr [i]->aio_sigevent.sigev_signo = SIGRTMIN; lio_ptr [i]->aio_sigevent.sigev_value.sival_int = i; lio_ptr [i]->aio_sigevent.sigev_notify_function = end_of_aio_op; lio_ptr [i]->aio_sigevent.sigev_notify_attributes = NULL;
    /* Запрос готов, можно отправлять его на выполнение */ (void) aio_read (lio_ptr [i]); } /* for (i < argc) */
    /* Дождемся завершения обработки всех указанных */ /* в командной строке файлов */ while (n_open_files (argc) > 0) { (void) pthread_cond_wait (&open_files_cond, &nlines_mutex); }
    printf ("%ld\n", nlines_total);
    return 0; }
    Листинг 7.8. Модифицированный вариант программы, использующей функции асинхронного ввода/вывода.
    Закрыть окно




    #include int posix_fadvise ( int fd, off_t offset, size_t len, int advice);
    #include int posix_fallocate (int fd, off_t offset, size_t len);
    #include int posix_madvise (void *addr, size_t len, int advice);
    #include int posix_memalign (void **memptr, size_t alignment, size_t size);
    Листинг 7.9. Описание функций рекомендательных интерфейсов.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает сумму байт в файле – */ /* аргументе командной строки, */ /* пытаясь оптимизировать чтение данных с помощью */ /* функций */ /* рекомендательных интерфейсов */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include #include #include
    int main (int argc, char *argv []) { int fd; /* Дескриптор файла-аргумента */ long sum = 0; /* Сумма байт в файле */ char *pbuf; /* Указатель на буфер обмена */ ssize_t nb; /* Число прочитанных байт */ int i;
    if (argc != 2) { fprintf (stderr, "Использование: %s имя_файла\n", argv [0]); return (1); }
    /* Откроем заданный файл на чтение */ if ((fd = open (argv [1], O_RDONLY)) == -1) { perror ("OPEN"); return (2); }
    /* Опишем дисциплину работы с файлом */
    if (((errno = posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL)) != 0) || ((errno = posix_fadvise (fd, 0, 0, POSIX_FADV_NOREUSE)) != 0)) { perror ("POSIX_FADVISE"); }
    /* Зарезервируем память под буфер обмена, */ /* следуя рекомендациям */ /* на выравнивание и размер. */ /* Предполагается, что значение BUFSIZ */ /* кратно всем нужным величинам. */ if ((errno = posix_memalign ((void **) &pbuf, BUFSIZ, BUFSIZ)) != 0) { perror ("POSIX_MEMALIGN"); return (errno); }
    /* Прочитаем файл и обработаем содержащиеся в нем данные */ while ((nb = read (fd, pbuf, BUFSIZ)) > 0) { for (i = 0; i < nb; i++) { sum += pbuf [i]; } }
    printf ("%ld\n", sum);
    free (pbuf); return (close (fd)); }
    Листинг 7.10. Пример программы, использующей функции рекомендательных интерфейсов.
    Закрыть окно



    Рекомендательные интерфейсы


    Напомним, что рекомендательные интерфейсы – это средство проинформировать операционную систему о поведении мобильного приложения, чтобы ОС могла принять меры для оптимизации его (приложения) обслуживания.
    В стандарте POSIX-2001 предусмотрена только оптимизация работы с файлами, которая может затрагивать следующие аспекты осуществляемого приложением ввода/вывода:
  • последовательный доступ;
  • кэширование;
  • передача данных;
  • предварительное резервирование долговременной памяти.

  • Если операционная система знает, что приложение осуществляет последовательный доступ к файлу, ей целесообразно производить предвыборку данных и отводить долговременную память под файл также последовательно. Очевидна в таком случае и дисциплина работы с кэшем данных. Напротив, при случайном доступе предвыборка данных только повредит, следует читать лишь то, что необходимо.
    Приложение может проинформировать ОС и о том, что данные не подлежат повторному использованию. Следовательно, их не нужно кэшировать, предпочтителен прямой обмен с пользовательскими буферами (которые должны быть определенным образом выравнены в оперативной памяти и иметь подходящий размер).
    Описанные возможности реализуют функции posix_fadvise(), posix_fallocate(), posix_madvise() и posix_memalign() (см. листинг 7.9).
    #include int posix_fadvise (int fd, off_t offset, size_t len, int advice);
    #include int posix_fallocate (int fd, off_t offset, size_t len);
    #include int posix_madvise (void *addr, size_t len, int advice);
    #include int posix_memalign (void **memptr, size_t alignment, size_t size);
    Листинг 7.9. Описание функций рекомендательных интерфейсов. (html, txt)
    Функция posix_fadvise() информирует реализацию об ожидаемом поведении приложения по отношению к части файла, ассоциированного с открытым дескриптором fd, которая начинается с позиции offset и имеет длину len байт (если значение аргумента len равно нулю, рекомендация распространяется до конца файла).
    Ожидаемое поведение специфицирует аргумент advice, который может принимать следующие значения.
    POSIX_FADV_NORMAL
    Подразумеваемое поведение – отсутствие рекомендаций.
    POSIX_FADV_SEQUENTIAL
    Предполагается, что приложение будет осуществлять доступ к указанной части файла последовательно, от меньших смещений к большим.
    POSIX_FADV_RANDOM
    Специфицирует случайный доступ.
    POSIX_FADV_WILLNEED
    Предполагается, что данные из указанной части файла скоро понадобятся.
    POSIX_FADV_DONTNEED
    Предполагается, что данные из указанной части файла в ближайшее время не понадобятся.
    POSIX_FADV_NOREUSE
    Специфицирует однократный доступ.
    Нормальный результат функции posix_fadvise() равен нулю; при обнаружении ошибки возвращается ее номер.

    Рекомендательные интерфейсы
    Рекомендательные интерфейсы
    © 2003-2007 INTUIT.ru. Все права защищены.

    Функции для работы с атрибутными объектами потоков трассировки


    Согласно стандарту POSIX-2001, система трассировки строится в объектно-ориентированном стиле. Структура большинства используемых объектов скрыта от приложения, поэтому требуется довольно много относительно мелких функций – конструкторов, селекторов, итераторов.
    Для систематического рассмотрения целесообразно представить множество функций трассировки в виде трехуровневой иерархии. Разветвление на верхнем уровне производится в соответствии с ролью процесса (трассирующий, трассируемый, анализирующий), а на втором уровне – в соответствии с классом обслуживаемых объектов (потоки и их атрибутные объекты, журналы, события и т.д.). На третьем уровне располагаются функциональные группы, предназначенные для выполнения определенных операций (создание/уничтожение, опрос/изменение и т.п.) над объектами определенных классов.
    Чтобы получить общее представление о функциях трассировки, рассмотрим сначала два верхних уровня описанной иерархии.
    Трассирующий процесс управляет ресурсами, ассоциированными с потоками трассировки, а именно:
  • атрибутными объектами потоков трассировки;
  • собственно потоками трассировки;
  • журналами трассировки;
  • идентификаторами типов событий;
  • фильтрами.

  • Трассируемый процесс имеет дело со следующими объектами:
  • события;
  • идентификаторы типов событий;
  • точки трассировки.

  • Анализирующий процесс читает:
  • потоки трассировки;
  • журналы;
  • события;
  • идентификаторы типов событий.

  • Перейдем к детальному рассмотрению функций трассировки.
    Для создания и уничтожения атрибутных объектов потоков трассировки служат функции posix_trace_attr_init() и posix_trace_attr_destroy() (см. листинг 8.1).
    #include int posix_trace_attr_init ( trace_attr_t *attr); int posix_trace_attr_destroy ( trace_attr_t *attr);
    Листинг 8.1. Описание функций создания и уничтожения атрибутных объектов потоков трассировки. (html, txt)
    Функция posix_trace_attr_init() инициализирует атрибутный объект подразумеваемыми значениями для всех атрибутов, используемых реализацией. Отметим, что два атрибута – имя версии системы трассировки и разрешающая способность часов, с помощью которых проставляются временные штампы, – доступны только на чтение.

    Согласно стандарту POSIX-2001, система трассировки строится в объектно-ориентированном стиле. Структура большинства используемых объектов скрыта от приложения, поэтому требуется довольно много относительно мелких функций – конструкторов, селекторов, итераторов.
    Для систематического рассмотрения целесообразно представить множество функций трассировки в виде трехуровневой иерархии. Разветвление на верхнем уровне производится в соответствии с ролью процесса (трассирующий, трассируемый, анализирующий), а на втором уровне – в соответствии с классом обслуживаемых объектов (потоки и их атрибутные объекты, журналы, события и т.д.). На третьем уровне располагаются функциональные группы, предназначенные для выполнения определенных операций (создание/уничтожение, опрос/изменение и т.п.) над объектами определенных классов.
    Чтобы получить общее представление о функциях трассировки, рассмотрим сначала два верхних уровня описанной иерархии.
    Трассирующий процесс управляет ресурсами, ассоциированными с потоками трассировки, а именно:
  • атрибутными объектами потоков трассировки;
  • собственно потоками трассировки;
  • журналами трассировки;
  • идентификаторами типов событий;
  • фильтрами.

  • Трассируемый процесс имеет дело со следующими объектами:
  • события;
  • идентификаторы типов событий;
  • точки трассировки.

  • Анализирующий процесс читает:
  • потоки трассировки;
  • журналы;
  • события;
  • идентификаторы типов событий.

  • Перейдем к детальному рассмотрению функций трассировки.
    Для создания и уничтожения атрибутных объектов потоков трассировки служат функции posix_trace_attr_init() и posix_trace_attr_destroy() (см. листинг 8.1).
    #include int posix_trace_attr_init ( trace_attr_t *attr); int posix_trace_attr_destroy ( trace_attr_t *attr);
    Листинг 8.1. Описание функций создания и уничтожения атрибутных объектов потоков трассировки.
    Функция posix_trace_attr_init() инициализирует атрибутный объект подразумеваемыми значениями для всех атрибутов, используемых реализацией. Отметим, что два атрибута – имя версии системы трассировки и разрешающая способность часов, с помощью которых проставляются временные штампы, – доступны только на чтение.



    Нормальным является нулевой результат; при наличии ошибки возвращается ее номер.

    Для манипулирования атрибутами, идентифицирующими поток трассировки, служат функции posix_trace_attr_getgenversion(), posix_trace_attr_getname(), posix_trace_attr_setname(), posix_trace_attr_getcreatetime() (см. листинг 8.2).

    #include int posix_trace_attr_getgenversion ( const trace_attr_t *attr, char *genversion); int posix_trace_attr_getname ( const trace_attr_t *attr, char *tracename); int posix_trace_attr_setname ( trace_attr_t *attr, const char *tracename); #include #include int posix_trace_attr_getcreatetime ( const trace_attr_t *attr, struct timespec *createtime);

    Листинг 8.2. Описание функций манипулирования атрибутами, идентифицирующими поток трассировки. (html, txt)

    Функции опроса копируют значения соответствующих атрибутов (имя версии системы трассировки, имя и время создания потока трассировки) из атрибутного объекта, заданного аргументом attr, в символьный массив (длина которого должна быть не меньше TRACE_NAME_MAX) или в структуру. Функция posix_trace_attr_setname() копирует имя (длина которого не должна превышать TRACE_NAME_MAX) в противоположном направлении.

    Функция posix_trace_attr_getclockres() (см. листинг 8.3) позволяет опросить разрешающую способность часов, с помощью которых проставляются временные штампы.

    #include #include int posix_trace_attr_getclockres ( const trace_attr_t *attr, struct timespec *resolution);

    Листинг 8.3. Описание функции опроса разрешающей способности часов, с помощью которых проставляются временные штампы. (html, txt)



    Нормальным является нулевой результат; при наличии ошибки возвращается ее номер.

    Для манипулирования атрибутами, идентифицирующими поток трассировки, служат функции posix_trace_attr_getgenversion(), posix_trace_attr_getname(), posix_trace_attr_setname(), posix_trace_attr_getcreatetime() (см. листинг 8.2).

    #include int posix_trace_attr_getgenversion ( const trace_attr_t *attr, char *genversion); int posix_trace_attr_getname ( const trace_attr_t *attr, char *tracename); int posix_trace_attr_setname ( trace_attr_t *attr, const char *tracename); #include #include int posix_trace_attr_getcreatetime ( const trace_attr_t *attr, struct timespec *createtime);

    Листинг 8.2. Описание функций манипулирования атрибутами, идентифицирующими поток трассировки.

    Функции опроса копируют значения соответствующих атрибутов (имя версии системы трассировки, имя и время создания потока трассировки) из атрибутного объекта, заданного аргументом attr, в символьный массив (длина которого должна быть не меньше TRACE_NAME_MAX) или в структуру. Функция posix_trace_attr_setname() копирует имя (длина которого не должна превышать TRACE_NAME_MAX) в противоположном направлении.

    Функция posix_trace_attr_getclockres() (см. листинг 8.3) позволяет опросить разрешающую способность часов, с помощью которых проставляются временные штампы.

    #include #include int posix_trace_attr_getclockres ( const trace_attr_t *attr, struct timespec *resolution);

    Листинг 8.3. Описание функции опроса разрешающей способности часов, с помощью которых проставляются временные штампы.

    Поведенческие атрибуты потоков и журналов обслуживаются функциями: posix_trace_attr_getinherited(), posix_trace_attr_setinherited(), posix_trace_attr_getstreamfullpolicy(), posix_trace_attr_setstreamfullpolicy(), posix_trace_attr_getlogfullpolicy(), posix_trace_attr_setlogfullpolicy() (см. листинг 8.4).

    #include int posix_trace_attr_getinherited ( const trace_attr_t *restrict attr, int *restrict inheritancepolicy); int posix_trace_attr_setinherited ( trace_attr_t *attr, int inheritancepolicy); int posix_trace_attr_getstreamfullpolicy ( const trace_attr_t *restrict attr, int *restrict streampolicy); int posix_trace_attr_setstreamfullpolicy ( trace_attr_t *attr, int streampolicy); int posix_trace_attr_getlogfullpolicy ( const trace_attr_t *restrict attr, int *restrict logpolicy); int posix_trace_attr_setlogfullpolicy ( trace_attr_t *attr, int logpolicy);



    Листинг 8.4. Описание функций манипулирования значениями поведенческих атрибутов потоков и журналов в атрибутных объектах.

    Функции posix_trace_attr_getinherited() и posix_trace_attr_setinherited() позволяют, соответственно, опросить и установить значение атрибута наследования трассировки порожденными процессами. Если значение этого атрибута равно POSIX_TRACE_INHERITED, то после вызовов fork() и/или spawn() родительский и порожденный процессы будут трассироваться параллельно с использованием общего потока трассировки. Подразумеваемое значение атрибута суть POSIX_TRACE_CLOSE_FOR_CHILD (не трассировать порожденные процессы).

    Атрибут, специфицирующий правила обработки ситуации заполнения, и для потоков (функции posix_trace_attr_getstreamfullpolicy() и posix_trace_attr_setstreamfullpolicy()), и для журналов трассировки (функции posix_trace_attr_getlogfullpolicy() и posix_trace_attr_setlogfullpolicy()) может принимать следующие значения.

    POSIX_TRACE_LOOP

    Для потоков – продолжение трассировки с записью новых событий поверх самых старых; для журналов – сброс всех событий из потока также с записью поверх самых старых.

    POSIX_TRACE_UNTIL_FULL

    Для потоков – приостановка трассировки с генерацией события POSIX_TRACE_STOP. Реализация обязана возобновить трассировку (с генерацией события POSIX_TRACE_START) после очистки потока, но может сделать это и раньше, за счет повторного использования пространства, которое занимали прочитанные события. Для журналов – прекращение сброса (последним в журнал помещается событие POSIX_TRACE_STOP). События, которые не поместились в журнал, пропадают.

    Для потоков трассировки еще одним возможным значением служит

    POSIX_TRACE_FLUSH

    Регулярно сбрасывать поток в журнал трассировки; в остальном действовать по правилам POSIX_TRACE_UNTIL_FULL.

    Если с потоком ассоциирован журнал, то подразумеваемым значением данного атрибута является POSIX_TRACE_FLUSH; в противном случае – POSIX_TRACE_LOOP.

    Для журналов трассировки описываемый атрибут может принимать значение



    POSIX_TRACE_APPEND

    Потенциально неограниченное расширение журнала.

    Подразумеваемым для журналов является значение POSIX_TRACE_LOOP.

    Опросить и/или установить хранящиеся в атрибутном объекте размеры событий, потоков и журналов можно с помощью функций: posix_trace_attr_getmaxdatasize(), posix_trace_attr_setmaxdatasize(), posix_trace_attr_getmaxsystemeventsize(), posix_trace_attr_getmaxusereventsize(), posix_trace_attr_getstreamsize(), posix_trace_attr_setstreamsize(), posix_trace_attr_getlogsize(), posix_trace_attr_setlogsize() (см. листинг 8.5).

    #include #include int posix_trace_attr_getmaxdatasize ( const trace_attr_t *restrict attr, size_t *restrict maxdatasize); int posix_trace_attr_setmaxdatasize ( trace_attr_t *attr, size_t maxdatasize); int posix_trace_attr_getmaxsystemeventsize ( const trace_attr_t *restrict attr, size_t *restrict eventsize); int posix_trace_attr_getmaxusereventsize ( const trace_attr_t *restrict attr, size_t data_len, size_t *restrict eventsize); int posix_trace_attr_getstreamsize ( const trace_attr_t *restrict attr, size_t *restrict streamsize); int posix_trace_attr_setstreamsize ( trace_attr_t *attr, size_t streamsize); int posix_trace_attr_getlogsize ( const trace_attr_t *restrict attr, size_t *restrict logsize); int posix_trace_attr_setlogsize ( trace_attr_t *attr, size_t logsize);

    Листинг 8.5. Описание функций манипулирования размерами событий, потоков и журналов в атрибутных объектах.

    Функции posix_trace_attr_getmaxdatasize() и posix_trace_attr_setmaxdatasize() позволяют опросить и установить максимальный размер (в байтах) данных, ассоциируемых с пользовательскими событиями. Подразумеваемое значение этого атрибута зависит от реализации.

    Функции posix_trace_attr_getmaxsystemeventsize() и posix_trace_attr_getmaxusereventsize() служат для опроса максимального размера системных и пользовательских событий, соответственно (во втором случае учитывается размер ассоциированных данных, определяемый значением аргумента data_len).

    Функции posix_trace_attr_getstreamsize() и posix_trace_attr_setstreamsize() ведают минимальным объемом памяти, который система трассировки обязана зарезервировать в потоке для хранения событий. Накладные расходы на размещение атрибутов потока, таких как идентифицирующие данные или правила поведения, в эту величину не входят. Пока сумма максимальных размеров событий не превосходит значения данного атрибута, события должны записываться без потери информации.

    Функции posix_trace_attr_getlogsize() и posix_trace_attr_setlogsize() обслуживают атрибут журнала трассировки – максимальный размер, резервируемый для хранения событий. Если правило обработки ситуации заполнения журнала определено как POSIX_TRACE_APPEND, данный атрибут игнорируется.


    Функции для работы с событиями


    От рассмотрения средств, доступных трассирующему процессу, мы переходим к функциям, используемым для оборудования трассируемых пользовательских приложений. Таких функций две: posix_trace_event() и posix_trace_eventid_open() (см. листинг 8.19).
    #include #include
    void posix_trace_event ( trace_event_id_t event_id, const void *restrict data_ptr, size_t data_len);
    int posix_trace_eventid_open ( const char *restrict event_name, trace_event_id_t *restrict event_id);
    Листинг 8.19. Описание функций для оборудования трассируемых пользовательских приложений. (html, txt)
    Вызов функции posix_trace_event() создает точку трассировки, при попадании в которую генерируется пользовательское событие с идентификатором типа event_id и ассоциированными данными, специфицированными аргументами data_ptr (адрес буфера) и data_len (размер буфера). Если трассировка идет и события заданного типа не задерживаются фильтром, соответствующая информация будет записана в потоки, созданные ранее для трассировки вызывающего процесса. В противном случае вызов posix_trace_event() игнорируется.
    Функция posix_trace_eventid_open(), ассоциирующая имя события с идентификатором типа, по сути аналогична posix_trace_trid_eventid_open(), только поток задается неявно (как поток, куда идет трассировка вызывающего процесса). Очевидно, подобная функция необходима для формирования аргумента event_id перед вызовами posix_trace_event().
    Что касается анализирующего процесса, то обычно он в первую очередь открывает ранее записанный журнал трассировки, обращаясь к функции posix_trace_open(), а, обработав, закрывает его с помощью функции posix_trace_close(), быть может, в промежутке позиционируясь на начало журнала посредством функции posix_trace_rewind() (см. листинг 8.20).
    #include int posix_trace_open ( int fildes, trace_id_t *trid); int posix_trace_close (trace_id_t trid); int posix_trace_rewind (trace_id_t trid);
    Листинг 8.20. Описание функций для работы с журналами трассировки. (html, txt)

    Функция posix_trace_open(), отправляясь от файлового дескриптора журнала трассировки fildes, открытого на чтение, создает поток трассировки и записывает его идентификатор по указателю trid. С помощью этого идентификатора анализирующий процесс может опрашивать атрибуты и статус потока и, главное, читать из него события, вызывая функцию posix_trace_getnext_event() (см. листинг 8.21).

    #include #include *lt;trace.h>

    int posix_trace_getnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void *restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable);

    int posix_trace_timedgetnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void *restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable, const struct timespec *restrict abstime);

    int posix_trace_trygetnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void *restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable);

    Листинг 8.21. Описание функций чтения событий трассировки. (html, txt)

    Функция posix_trace_getnext_event() выдает очередное событие из потока трассировки, который может быть как предварительно записанным в журнале, так и активным (в последнем случае анализирующий процесс должен совпадать с трассирующим). События выдаются в порядке их генерации, то есть от самого старого к самому новому.

    Информация о событии помещается в структуру типа posix_trace_event_info, на которую указывает аргумент event. Данные, ассоциированные с событием, записываются в буфер с адресом data_ptr и длиной num_bytes; по указателю data_len размещается число записанных в буфер байт данных. Наконец, в случае успешного чтения по указателю unavailable помещается нулевое значение.

    Если аргумент trid идентифицирует активный поток трассировки, вызов posix_trace_getnext_event() блокируется при отсутствии непрочитанных событий.

    Функции posix_trace_timedgetnext_event() и posix_trace_trygetnext_event() применимы только к активным потокам трассировки.


    При отсутствии непрочитанных событий первая блокируется, но с контролем длительности ожидания (до наступления заданного абсолютного момента времени), а вторая немедленно завершится, записав по указателю unavailable ненулевое значение.

    В качестве иллюстрации применения описанных выше средств рассмотрим трассировку обеда философов (см. листинг 8.22). Мы не будем приводить весь исходный текст, а перечислим лишь изменения в трассируемой части программы и полностью приведем новую функцию main(), в которой сосредоточены управление трассировкой и анализ сгенерированных событий.

    Листинг 8.22. Фрагмент исходного текста программы обеда философов с трассировкой. (html, txt)

    Отметим, что в качестве данных, ассоциированных с пользовательскими событиями, выступают целые числа – номера философов (по одному на событие, см. вызовы posix_trace_event()).

    Отметим также стиль формирования фильтра системных событий. Сначала в фильтрующее множество включаются все системные события (вызов posix_trace_eventset_fill(), а затем из него удаляются типы событий, представляющих интерес (вызовы posix_trace_eventset_del()).

    Если интересоваться системными событиями, то исходный текст управляющей и анализирующей частей неизбежно получается зависящим от целевой платформы. Приведенный вариант программы ориентирован на операционную систему реального времени oc2000. В этой связи обратим внимание на идентификаторы типов системных событий (traceSigGeneration, traceSigDelivery, traceSigCatchFunc) и на структуру ассоциированных с событиями данных (evdat [0], evdat [2], evdat [5] и т.п.), вообще говоря, свою для каждого типа.

    Фрагмент возможных результатов трассировки обеда двух философов показан на листинге 8.23.

    Листинг 8.23. Фрагмент возможных результатов трассировки обеда двух философов на операционной платформе oc2000. (html, txt)

    Трассировка – по-настоящему полезный инструмент в ситуациях, когда многопотоковая программа по непонятным причинам ведет себя не так, как хотелось бы. Рассмотрим пример, предложенный автору Н.В.


    Шмыревым (см. листинг 8.24). Пусть имеется критический интервал, вход в который охраняется мьютексом. Один поток управления пытается захватывать этот мьютекс с ожиданием, другой – без ожидания, с паузами между попытками (должен же мьютекс когда-нибудь освободиться?). Выясняется, что второму потоку не удается войти в критический интервал. Почему?

    Чтобы получить ответ на этот вопрос, уместно воспользоваться трассировкой системных событий, связанных с мьютексами.

    Листинг 8.24. Программа, демонстрирующая трассировку системных событий, связанных с мьютексами, на операционной платформе oc2000. (html, txt)

    Результаты трассировки сводятся к многократному повторению фрагмента, показанного на листинге 8.25. Второй поток управления не может попасть в критический интервал, потому что мьютекс оказывается захваченным первым потоком. Разумеется, величины задержек в приведенном примере подобраны так, чтобы длительность пауз между попытками входа в критический интервал второго потока управления была кратна периоду выполнения первого потока (что вполне может иметь место и в реальном приложении реального времени). Абстрактная надежда на то, что "все проходит" и мьютекс должен когда-нибудь освободиться, надо только набраться терпения и подождать, может и не сбыться. На подобные проблемы указывал еще Э. Дейкстра в своих первых работах по синхронизации параллельных процессов (см. [2] в дополнительной литературе).

    Листинг 8.25. Фрагмент возможных результатов трассировки системных событий, связанных с мьютексами, на операционной платформе oc2000. (html, txt)

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


    Функции, обслуживающие жизненный цикл потоков трассировки.


    На рис. 8.1 показаны основные элементы жизненного цикла потока трассировки и функции, вызов которых трассирующим процессом обеспечивает переход между состояниями.
    Функции, обслуживающие жизненный цикл потоков трассировки.
    Рис. 8.1.  Основные элементы жизненного цикла потока трассировки.
    Создание потока трассировки осуществляется функциями posix_trace_create() или posix_trace_create_withlog() (см. листинг 8.6).
    #include #include int posix_trace_create (pid_t pid, const trace_attr_t *restrict attr, trace_id_t *restrict trid); int posix_trace_create_withlog (pid_t pid, const trace_attr_t *restrict attr, int fildes, trace_id_t *restrict trid);
    Листинг 8.6. Описание функций создания потоков трассировки. (html, txt)
    Функция posix_trace_create() создает активный поток, в который будут записываться события трассировки целевого процесса с идентификатором pid (при нулевом pid трассируется вызывающий процесс). Ресурсы в потоке отводятся в соответствии со значениями элементов атрибутного объекта, заданного аргументом attr (если attr равен NULL, используются подразумеваемые значения).
    Идентификатор созданного потока записывается по указателю trid. Его может использовать только вызывающий процесс.
    Фильтр, ассоциированный с новым потоком, пуст.
    Одновременно могут быть активными несколько потоков трассировки, однако их общее число не должно превышать значения конфигурационной константы TRACE_SYS_MAX.
    Разумеется, попытки трассировать другие процессы подвержены контролю прав доступа.
    Функция posix_trace_create_withlog() делает то же, что и posix_trace_create(), но дополнительно ассоциирует с потоком журнал трассировки, заданный файловым дескриптором fildes.
    Сразу после создания потока трассировка считается приостановленной. Чтобы активизировать ее, следует воспользоваться функцией posix_trace_start(); для последующей приостановки нужно вызвать функцию posix_trace_stop() (см. листинг 8.7).
    #include int posix_trace_start (trace_id_t trid); int posix_trace_stop (trace_id_t trid);

    На рис. 8.1 показаны основные элементы жизненного цикла потока трассировки и функции, вызов которых трассирующим процессом обеспечивает переход между состояниями.
    Функции, обслуживающие жизненный цикл потоков трассировки.
    Рис. 8.1.  Основные элементы жизненного цикла потока трассировки.
    Создание потока трассировки осуществляется функциями posix_trace_create() или posix_trace_create_withlog() (см. листинг 8.6).
    #include #include int posix_trace_create (pid_t pid, const trace_attr_t *restrict attr, trace_id_t *restrict trid); int posix_trace_create_withlog (pid_t pid, const trace_attr_t *restrict attr, int fildes, trace_id_t *restrict trid);
    Листинг 8.6. Описание функций создания потоков трассировки.
    Функция posix_trace_create() создает активный поток, в который будут записываться события трассировки целевого процесса с идентификатором pid (при нулевом pid трассируется вызывающий процесс). Ресурсы в потоке отводятся в соответствии со значениями элементов атрибутного объекта, заданного аргументом attr (если attr равен NULL, используются подразумеваемые значения).
    Идентификатор созданного потока записывается по указателю trid. Его может использовать только вызывающий процесс.
    Фильтр, ассоциированный с новым потоком, пуст.
    Одновременно могут быть активными несколько потоков трассировки, однако их общее число не должно превышать значения конфигурационной константы TRACE_SYS_MAX.
    Разумеется, попытки трассировать другие процессы подвержены контролю прав доступа.
    Функция posix_trace_create_withlog() делает то же, что и posix_trace_create(), но дополнительно ассоциирует с потоком журнал трассировки, заданный файловым дескриптором fildes.
    Сразу после создания потока трассировка считается приостановленной. Чтобы активизировать ее, следует воспользоваться функцией posix_trace_start(); для последующей приостановки нужно вызвать функцию posix_trace_stop() (см. листинг 8.7).
    #include int posix_trace_start (trace_id_t trid); int posix_trace_stop (trace_id_t trid);
    Листинг 8.7.


    Листинг 8.7. Описание функций активизации и приостановки трассировки. (html, txt)

    После вызова posix_trace_start() в поток помещается системное событие POSIX_TRACE_START, а статус потока получает значение POSIX_TRACE_RUNNING (если трассировка уже шла, вызов posix_trace_start() игнорируется).

    После вызова posix_trace_stop() в поток помещается системное событие POSIX_TRACE_STOP, а статус потока получает значение POSIX_TRACE_SUSPENDED (если трассировка уже была приостановлена, вызов posix_trace_stop() игнорируется).

    Если поток трассировки заполнен, вызовы posix_trace_start() и posix_trace_stop() игнорируются.

    Функция posix_trace_shutdown() (см. листинг 8.8) завершает трассировку и освобождает ресурсы, ассоциированные с потоком, независимо от того, прочитал ли анализирующий процесс все записанные события.

    #include #include int posix_trace_shutdown (trace_id_t trid);

    Листинг 8.8. Описание функции завершения трассировки. (html, txt)

    Если с потоком ассоциирован журнал, в него сбрасываются все несохраненные события, записывается необходимая служебная информация (атрибуты трассировки, типы и имена событий и соответствие между ними и т.п.), после чего журнал закрывается.

    После возврата из posix_trace_shutdown() аргумент trid перестает быть корректным идентификатором потока трассировки.

    Трассировка может завершаться и неявным образом, когда трассирующий процесс терминируется или вызывает функцию семейства exec().

    По ходу трассировки управляющий процесс может инициировать сброс потока в журнал, обратившись к функции posix_trace_flush() (см листинг 8.9).

    #include #include int posix_trace_flush (trace_id_t trid);

    Листинг 8.9. Описание функции сброса потока трассировки в журнал. (html, txt)

    Сброс осуществляется с учетом правил обработки ситуации заполнения журнала.

    После завершения сброса пространство в потоке трассировки, которое занимали записанные в журнал события, может быть использовано повторно.

    Узнать о завершении операции сброса можно, опрашивая статус потока трассировки с помощью функции posix_trace_get_status() (см.листинг 8.10).

    #include int posix_trace_get_status (trace_id_t trid, struct posix_trace_status_info *statusinfo);

    Листинг 8.10. Описание функции опроса статуса потока трассировки. (html, txt)

    Функция обеспечивает согласованность данных, которые записываются в структуру типа posix_trace_status_info по указателю statusinfo. Сразу после завершения вызова признаки переполнения и для потока, и для журнала получают значение POSIX_TRACE_NO_OVERRUN, а в поле posix_stream_flush_error помещается нуль.



    Описание функций активизации и приостановки трассировки.

    После вызова posix_trace_start() в поток помещается системное событие POSIX_TRACE_START, а статус потока получает значение POSIX_TRACE_RUNNING (если трассировка уже шла, вызов posix_trace_start() игнорируется).

    После вызова posix_trace_stop() в поток помещается системное событие POSIX_TRACE_STOP, а статус потока получает значение POSIX_TRACE_SUSPENDED (если трассировка уже была приостановлена, вызов posix_trace_stop() игнорируется).

    Если поток трассировки заполнен, вызовы posix_trace_start() и posix_trace_stop() игнорируются.

    Функция posix_trace_shutdown() (см. листинг 8.8) завершает трассировку и освобождает ресурсы, ассоциированные с потоком, независимо от того, прочитал ли анализирующий процесс все записанные события.

    #include #include int posix_trace_shutdown (trace_id_t trid);

    Листинг 8.8. Описание функции завершения трассировки.

    Если с потоком ассоциирован журнал, в него сбрасываются все несохраненные события, записывается необходимая служебная информация (атрибуты трассировки, типы и имена событий и соответствие между ними и т.п.), после чего журнал закрывается.

    После возврата из posix_trace_shutdown() аргумент trid перестает быть корректным идентификатором потока трассировки.

    Трассировка может завершаться и неявным образом, когда трассирующий процесс терминируется или вызывает функцию семейства exec().

    По ходу трассировки управляющий процесс может инициировать сброс потока в журнал, обратившись к функции posix_trace_flush() (см листинг 8.9).

    #include #include int posix_trace_flush (trace_id_t trid);

    Листинг 8.9. Описание функции сброса потока трассировки в журнал.

    Сброс осуществляется с учетом правил обработки ситуации заполнения журнала.

    После завершения сброса пространство в потоке трассировки, которое занимали записанные в журнал события, может быть использовано повторно.

    Узнать о завершении операции сброса можно, опрашивая статус потока трассировки с помощью функции posix_trace_get_status() (см.


    листинг 8.10).

    #include int posix_trace_get_status (trace_id_t trid, struct posix_trace_status_info *statusinfo);

    Листинг 8.10. Описание функции опроса статуса потока трассировки.

    Функция обеспечивает согласованность данных, которые записываются в структуру типа posix_trace_status_info по указателю statusinfo. Сразу после завершения вызова признаки переполнения и для потока, и для журнала получают значение POSIX_TRACE_NO_OVERRUN, а в поле posix_stream_flush_error помещается нуль.

    Стандартом POSIX-2001 предусмотрен опрос атрибутов потока трассировки. Для этого служит функция posix_trace_get_attr() (см. листинг 8.11).

    #include int posix_trace_get_attr ( trace_id_t trid, trace_attr_t *attr);

    Листинг 8.11. Описание функции опроса атрибутов потока трассировки.

    Для возврата значений атрибутов найдено красивое решение – они помещаются в атрибутный объект по указателю attr, откуда могут быть извлечены рассмотренными выше функциями для работы с атрибутными объектами.

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

    #include #include int posix_trace_clear (trace_id_t trid);

    Листинг 8.12. Описание функции очистки потока трассировки.

    Разумеется, после очистки признак заполненности получает значение POSIX_TRACE_NOT_FULL.

    Если для журнала предусмотрено неограниченное расширение (POSIX_TRACE_APPEND), эффект от вызова posix_trace_clear() не специфицирован.

    При опросе и установке фильтров событий трассировки (функции posix_trace_get_filter() и posix_trace_set_filter(), см. листинг 8.13) применяется апробированная в других частях стандарта POSIX-2001 схема работы с множествами, основанная на функциях: posix_trace_eventset_empty(), posix_trace_eventset_fill(), posix_trace_eventset_add(), posix_trace_eventset_del(), posix_trace_eventset_ismember() (см. листинг 8.14).

    #include int posix_trace_get_filter ( trace_id_t trid, trace_event_set_t *set); int posix_trace_set_filter (trace_id_t trid, const trace_event_set_t *set, int how);



    Листинг 8.13. Описание функций опроса и установки фильтров событий трассировки.

    #include int posix_trace_eventset_empty ( trace_event_set_t *set); int posix_trace_eventset_fill ( trace_event_set_t *set, int what); int posix_trace_eventset_add ( trace_event_id_t event_id, trace_event_set_t *set); int posix_trace_eventset_del ( trace_event_id_t event_id, trace_event_set_t *set); int posix_trace_eventset_ismember ( trace_event_id_t event_id, const trace_event_set_t *restrict set, int *restrict ismember);

    Листинг 8.14. Описание функций для работы с множествами событий трассировки.

    Когда вызывается функция posix_trace_set_filter(), в поток трассировки помещается событие POSIX_TRACE_FILTER с указанием старого и нового фильтров. Аргумент how, определяющий, как меняется фильтр, может принимать следующие значения.

    POSIX_TRACE_SET_EVENTSET

    Новое значение фильтра задается аргументом set.

    POSIX_TRACE_ADD_EVENTSET

    Новое значение фильтра вычисляется как объединение текущего значения и множества, заданного аргументом set.

    POSIX_TRACE_SUB_EVENTSET

    Новое значение фильтра вычисляется как разность текущего значения и множества, заданного аргументом set.

    Вероятно, функции для работы с множествами событий трассировки не нуждаются в пространном описании. Аргумент event_id задает тип события. Функция posix_trace_eventset_empty() создает пустое множество событий. Функция posix_trace_eventset_fill() помещает в создаваемое множество события, определяемые аргументом what, который может принимать следующие значения.

    POSIX_TRACE_WOPID_EVENTS

    Все зависящие от реализации системные события, не ассоциированные с каким-либо процессом.

    POSIX_TRACE_SYSTEM_EVENTS

    Все зависящие от реализации системные события.

    POSIX_TRACE_ALL_EVENTS

    Все события (системные и пользовательские).

    Для функций posix_trace_eventset_add() и posix_trace_eventset_del() добавление уже присутствующего или, соответственно, удаление отсутствующего типа не считается ошибкой.

    Функция posix_trace_eventset_ismember() помещает по указателю ismember ненулевое значение, если тип event_id принадлежит множеству, заданному аргументом set.



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

    Играющие техническую роль функции posix_trace_trid_eventid_open(), posix_trace_eventid_get_name() и posix_trace_eventid_equal() (см. листинг 8.15), обслуживают идентификаторы типов событий, рассматриваемые как абстрактные объекты.

    #include int posix_trace_trid_eventid_open ( trace_id_t trid, const char *restrict event_name, trace_event_id_t *restrict event);

    int posix_trace_eventid_get_name ( trace_id_t trid, trace_event_id_t event, char *event_name);

    int posix_trace_eventid_equal ( trace_id_t trid, trace_event_id_t event1, trace_event_id_t event2);

    Листинг 8.15. Описание функций для работы с идентификаторами типов событий трассировки.

    Функция posix_trace_trid_eventid_open() ассоциирует с именем event_name, длина которого не должна превышать значения конфигурационной константы TRACE_EVENT_NAME_MAX, уникальный для потока трассировки, заданного аргументом trid, идентификатор типа событий и записывает его по указателю event. При повторном вызове posix_trace_trid_eventid_open() с тем же именем возвращается ранее созданный идентификатор. При попытке превысить максимально допустимое для процесса количество типов пользовательских событий TRACE_USER_EVENT_MAX возвращается предопределенный идентификатор POSIX_TRACE_UNNAMED_USEREVENT.

    Функция posix_trace_eventid_get_name() записывает в массив event_name имя события, ассоциированное с типом, заданным аргументом event.

    Сравнить два идентификатора типов из одного потока трассировки можно посредством функции posix_trace_eventid_equal(), возвращающей в случае равенства результат, отличный от нуля.

    Функции posix_trace_eventtypelist_getnext_id() и posix_trace_eventtypelist_rewind() (см. листинг 8.16) позволяют обрабатывать совокупность идентификаторов типов событий, присутствующих в заданном потоке трассировки.

    #include int posix_trace_eventtypelist_getnext_id ( trace_id_t trid, trace_event_id_t *restrict event, int *restrict unavailable); int posix_trace_eventtypelist_rewind (trace_id_t trid);



    Листинг 8.16. Описание функций для работы с совокупностью идентификаторов типов событий.

    Функция posix_trace_eventtypelist_getnext_id() является итератором. При первом обращении она записывает по указателю event первый идентификатор типа. При каждом следующем вызове туда же помещается очередной идентификатор. Пока перебор идентификаторов не закончен, по указателю unavailable записывается нулевое значение.

    Функция posix_trace_eventtypelist_rewind() позволяет вернуться к началу перебора.

    Приведем пример программы, выясняющей подразумеваемые значения атрибутов потока трассировки (см. листинг 8.17).

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выясняет подразумеваемые значения */ /* атрибутов потока трассировки */ /* * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include #include

    int main (void) { trace_attr_t attr; /* Атрибутный объект потока */ trace_id_t trid; /* Идентификатор потока */ char trbuf [TRACE_NAME_MAX]; /* Буфер для имен */ struct timespec tms; /* Структура для времен */ char dtbuf [LINE_MAX]; /* Буфер для данных о времени */ int res; /* Целочисленные результаты */ size_t mdatsz; /* Максимальный размер данных */ if ((errno = posix_trace_create (0, NULL, &trid)) != 0) { perror ("POSIX_TRACE_CREATE"); return (errno); }

    if ((errno = posix_trace_get_attr (trid, &attr)) != 0) { perror ("POSIX_TRACE_GET_ATTR"); return (errno); }

    if ((errno = posix_trace_attr_getgenversion (&attr, trbuf)) != 0) { perror ("POSIX_TRACE_ATTR_GETGENVERSION"); return (errno); } printf ("Версия системы трассировки: %s\n", trbuf);

    if ((errno = posix_trace_attr_getname (&attr, trbuf)) != 0) { perror ("POSIX_TRACE_ATTR_GETNAME"); return (errno); } printf ("Имя потока трассировки: %s\n", trbuf);

    if ((errno = posix_trace_attr_getcreatetime (&attr, &tms)) != 0) { perror ("POSIX_TRACE_ATTR_GETCREATETIME"); return (errno); } (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&tms.tv_sec)); printf ("Время создания потока трассировки: %s\n", dtbuf);



    if ((errno = posix_trace_attr_getclockres (&attr, &tms)) != 0) { perror ("POSIX_TRACE_ATTR_GETCLOCKRES"); return (errno); } printf ("Разрешающая способность часов потока " "трассировки: %ld нсек\n", tms.tv_nsec);

    if ((errno = posix_trace_attr_getstreamfullpolicy (&attr, &res)) != 0) { perror ("POSIX_TRACE_ATTR_GETSTREAMFULLPOLICY"); return (errno); } printf (" Правило обработки ситуации заполнения потока: "); switch (res) { case POSIX_TRACE_LOOP: printf ("POSIX_TRACE_LOOP\n"); break; case POSIX_TRACE_UNTIL_FULL: printf ("POSIX_TRACE_UNTIL_FULL\n"); break; case POSIX_TRACE_FLUSH: printf ("POSIX_TRACE_FLUSH\n"); break; default: printf ("неизвестно\n"); }

    if ((errno = posix_trace_attr_getlogfullpolicy (&attr, &res)) != 0) { perror ("POSIX_TRACE_ATTR_GETLOGFULLPOLICY"); return (errno); } printf ("Правило обработки ситуации заполнения " "журнала трассировки: "); switch (res) { case POSIX_TRACE_LOOP: printf ("POSIX_TRACE_LOOP\n"); break; case POSIX_TRACE_UNTIL_FULL: printf ("POSIX_TRACE_UNTIL_FULL\n"); break; case POSIX_TRACE_APPEND: printf ("POSIX_TRACE_APPEND\n"); break; default: printf ("неизвестно\n"); }

    if ((errno = posix_trace_attr_getmaxdatasize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXDATASIZE"); return (errno); } printf ("Максимальный размер данных в пользовательском " "событии: %d\n", mdatsz);

    if ((errno = posix_trace_attr_getmaxusereventsize (&attr, mdatsz, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXUSEREVENTSIZE"); return (errno); } printf ("Максимальный размер пользовательского события: " "%d\n", mdatsz);

    if ((errno = posix_trace_attr_getmaxsystemeventsize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXSYSTEMEVENTSIZE"); return (errno); } printf ("Максимальный размер системного события: " "%d\n", mdatsz);



    if ((errno = posix_trace_attr_getstreamsize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETSTREAMSIZE"); return (errno); } printf ("Размер потока трассировки: %d\n", mdatsz);

    return 0; }

    Листинг 8.17. Пример программы, выясняющей подразумеваемые значения атрибутов потока трассировки.

    Обратим внимание на использование конфигурационной константы TRACE_NAME_MAX для задания размера буфера имен.

    При запуске этой программы под управлением операционной системы реального времени oc2000 могут быть получены следующие результаты (см. листинг 8.18).

    Версия системы трассировки: 2.17 Имя потока трассировки: trace Время создания потока трассировки: Пн 19 АПР 2004 11:54:07 Разрешающая способность часов потока трассировки: 41904 нсек Правило обработки ситуации заполнения потока: POSIX_TRACE_UNTIL_FULL Правило обработки ситуации заполнения журнала трассировки: POSIX_TRACE_LOOP Максимальный размер данных в пользовательском событии: 100 Максимальный размер пользовательского события: 128 Максимальный размер системного события: 84 Размер потока трассировки: 409600

    Листинг 8.18. Возможные результаты работы программы, выясняющей подразумеваемые значения атрибутов потока трассировки.

    Отметим нестандартность конфигурации системы применительно к обработке ситуации заполнения потока: POSIX-2001 предусматривает, что подразумеваемыми (в зависимости от наличия журнала) могут быть лишь значения POSIX_TRACE_LOOP или POSIX_TRACE_FLUSH, но не POSIX_TRACE_UNTIL_FULL.

    Читателю предлагается самостоятельно вычислить, сколько событий максимального размера можно записать в поток.


    Основные идеи, понятия и объекты


    Под трассировкой в стандарте POSIX-2001 понимается порождение, накопление и анализ данных о событиях, имевших место при выполнении пользовательского приложения.
    Применительно к приложениям реального времени трассировка помогает достичь по крайней мере трех целей:
  • оптимизировать структуру приложения на основе анализа трассировочных данных (например, попытаться сгладить пики активности, распределить нагрузку равномерно по времени);
  • отладить приложение (традиционные интерактивные отладчики могут быть неприменимы к приложениям реального времени);
  • выявить причину аварийного завершения работы приложения (обнаружить место и первые признаки ненормального поведения).

  • С логической точки зрения в трассировке (в том виде, как она рассматривается в стандарте POSIX-2001) участвуют три процесса (физически они могут совпадать между собой):
  • трассируемый (целевой);
  • трассирующий (управляющий трассировкой);
  • анализирующий данные трассировки.

  • Сведения о действиях, производимых при выполнении приложения, фиксируются в виде объектов данных, называемых событиями трассировки (или, для краткости, просто событиями). События записываются в потоки трассировки, которые содержат также служебные данные, необходимые для интерпретации событий.
    Трассируемый процесс должен быть специальным образом оборудован: его программа должна содержать точки трассировки – действия, способные генерировать события трассировки. Для каждого трассируемого процесса должен быть открыт по крайней мере один поток трассировки.
    Процесс, создавший поток трассировки, называется трассирующим (управляющим трассировкой).
    Анализирующим называется процесс, извлекающий события трассировки из потока с целью получения информации о поведении трассируемого приложения.
    Одним из главных требований к системе трассировки является минимизация накладных расходов. Чтобы выполнить это требование, потоки трассировки, как правило, хранят только в оперативной памяти. Если нужно получить стабильную копию потока для последующего анализа, его следует сбросить в журнал трассировки, располагающийся в долговременной памяти.

    В данный момент происходит сброс потока трассировки в журнал.

    POSIX_TRACE_NOT_FLUSHING

    В данный момент не происходит сброс потока трассировки в журнал.

    В поле posix_stream_flush_error находится нуль, если последний сброс прошел нормально; в противном случае там хранится код первой случившейся при сбросе ошибки (информация о последующих ошибках теряется).

    Поле posix_log_overrun_status содержит признак потери информации в журнале с двумя возможными значениями: POSIX_TRACE_OVERRUN и POSIX_TRACE_NO_OVERRUN.

    Поле posix_log_full_status служит признаком заполненности журнала. Возможных значений, естественно, два: POSIX_TRACE_FULL и POSIX_TRACE_NOT_FULL.

    При создании потока трассировки его атрибуты стандартным для POSIX-2001 образом извлекаются из атрибутного объекта. Стандартом предусмотрены следующие атрибуты:

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


  • И для потоков, и для журналов трассировки правила обработки ситуации заполнения могут сводиться к записи новых событий поверх самых старых (политика POSIX_TRACE_LOOP) или к приостановке трассировки (POSIX_TRACE_UNTIL_FULL). Кроме того, для потоков может быть предусмотрен сброс в журнал с последующей очисткой (политика POSIX_TRACE_FLUSH), а для для журналов – потенциально неограниченное расширение (POSIX_TRACE_APPEND).

    Трассировка с записью в создаваемый поток может как наследоваться, так и не наследоваться порожденными процессами. Соответствующие значения признака наследования именуются POSIX_TRACE_INHERITED и POSIX_TRACE_CLOSE_FOR_CHILD.



    В заключение раздела еще раз подчеркнем важные достоинства, которыми обладает механизм трассировки.

  • В большинстве случаев трассировка приложения допустимым образом влияет на временные характеристики его выполнения, поэтому трассировку можно применять в штатном режиме функционирования.
  • Трассировка позволяет отразить поведение программы, состоящей из произвольного числа сущностей.
  • Трассировка позволяет унифицировать механизм сообщений о поведении приложения. Она является готовым решением по организации такого механизма.


  • Можно рекомендовать максимально широкое использование средств трассировки в процессе разработки и эксплуатации прикладных программ. Целесообразно с самого начала разработки программ встраивать в них генерацию (по возможности эффективную, без лишних накладных расходов) пользовательских событий во всех ошибочных и необычных ситуациях.

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


    и уничтожения атрибутных объектов потоков


    #include int posix_trace_attr_init ( trace_attr_t *attr); int posix_trace_attr_destroy ( trace_attr_t *attr);
    Листинг 8.1. Описание функций создания и уничтожения атрибутных объектов потоков трассировки.
    Закрыть окно




    #include int posix_trace_attr_getgenversion ( const trace_attr_t *attr, char *genversion); int posix_trace_attr_getname ( const trace_attr_t *attr, char *tracename); int posix_trace_attr_setname ( trace_attr_t *attr, const char *tracename); #include #include int posix_trace_attr_getcreatetime ( const trace_attr_t *attr, struct timespec *createtime);
    Листинг 8.2. Описание функций манипулирования атрибутами, идентифицирующими поток трассировки.
    Закрыть окно




    #include #include int posix_trace_attr_getclockres ( const trace_attr_t *attr, struct timespec *resolution);
    Листинг 8.3. Описание функции опроса разрешающей способности часов, с помощью которых проставляются временные штампы.
    Закрыть окно




    #include int posix_trace_attr_getinherited ( const trace_attr_t *restrict attr, int *restrict inheritancepolicy); int posix_trace_attr_setinherited ( trace_attr_t *attr, int inheritancepolicy); int posix_trace_attr_getstreamfullpolicy ( const trace_attr_t *restrict attr, int *restrict streampolicy); int posix_trace_attr_setstreamfullpolicy ( trace_attr_t *attr, int streampolicy); int posix_trace_attr_getlogfullpolicy ( const trace_attr_t *restrict attr, int *restrict logpolicy); int posix_trace_attr_setlogfullpolicy ( trace_attr_t *attr, int logpolicy);
    Листинг 8.4. Описание функций манипулирования значениями поведенческих атрибутов потоков и журналов в атрибутных объектах.
    Закрыть окно




    #include #include int posix_trace_attr_getmaxdatasize ( const trace_attr_t *restrict attr, size_t *restrict maxdatasize); int posix_trace_attr_setmaxdatasize ( trace_attr_t *attr, size_t maxdatasize); int posix_trace_attr_getmaxsystemeventsize ( const trace_attr_t *restrict attr, size_t *restrict eventsize); int posix_trace_attr_getmaxusereventsize ( const trace_attr_t * restrict attr, size_t data_len, size_t *restrict eventsize); int posix_trace_attr_getstreamsize ( const trace_attr_t *restrict attr, size_t *restrict streamsize); int posix_trace_attr_setstreamsize ( trace_attr_t *attr, size_t streamsize); int posix_trace_attr_getlogsize ( const trace_attr_t *restrict attr, size_t *restrict logsize); int posix_trace_attr_setlogsize ( trace_attr_t *attr, size_t logsize);
    Листинг 8.5. Описание функций манипулирования размерами событий, потоков и журналов в атрибутных объектах.
    Закрыть окно




    #include #include int posix_trace_create (pid_t pid, const trace_attr_t *restrict attr, trace_id_t *restrict trid); int posix_trace_create_withlog (pid_t pid, const trace_attr_t * restrict attr, int fildes, trace_id_t *restrict trid);
    Листинг 8.6. Описание функций создания потоков трассировки.
    Закрыть окно




    #include int posix_trace_start (trace_id_t trid); int posix_trace_stop (trace_id_t trid);
    Листинг 8.7. Описание функций активизации и приостановки трассировки.
    Закрыть окно




    #include #include int posix_trace_shutdown (trace_id_t trid);
    Листинг 8.8. Описание функции завершения трассировки.
    Закрыть окно




    #include #include int posix_trace_flush (trace_id_t trid);
    Листинг 8.9. Описание функции сброса потока трассировки в журнал.
    Закрыть окно




    #include int posix_trace_get_status (trace_id_t trid, struct posix_trace_status_info *statusinfo);
    Листинг 8.10. Описание функции опроса статуса потока трассировки.
    Закрыть окно




    #include int posix_trace_get_attr ( trace_id_t trid, trace_attr_t *attr);
    Листинг 8.11. Описание функции опроса атрибутов потока трассировки.
    Закрыть окно




    #include #include int posix_trace_clear (trace_id_t trid);
    Листинг 8.12. Описание функции очистки потока трассировки.
    Закрыть окно




    #include int posix_trace_get_filter ( trace_id_t trid, trace_event_set_t *set); int posix_trace_set_filter ( trace_id_t trid, const trace_event_set_t *set, int how);
    Листинг 8.13. Описание функций опроса и установки фильтров событий трассировки.
    Закрыть окно




    #include int posix_trace_eventset_empty ( trace_event_set_t *set); int posix_trace_eventset_fill ( trace_event_set_t *set, int what); int posix_trace_eventset_add ( trace_event_id_t event_id, trace_event_set_t *set); int posix_trace_eventset_del ( trace_event_id_t event_id, trace_event_set_t *set); int posix_trace_eventset_ismember ( trace_event_id_t event_id, const trace_event_set_t *restrict set, int *restrict ismember);
    Листинг 8.14. Описание функций для работы с множествами событий трассировки.
    Закрыть окно




    #include int posix_trace_trid_eventid_open ( trace_id_t trid, const char *restrict event_name, trace_event_id_t *restrict event);
    int posix_trace_eventid_get_name ( trace_id_t trid, trace_event_id_t event, char *event_name);
    int posix_trace_eventid_equal ( trace_id_t trid, trace_event_id_t event1, trace_event_id_t event2);
    Листинг 8.15. Описание функций для работы с идентификаторами типов событий трассировки.
    Закрыть окно




    #include int posix_trace_eventtypelist_getnext_id ( trace_id_t trid, trace_event_id_t *restrict event, int *restrict unavailable); int posix_trace_eventtypelist_rewind (trace_id_t trid);
    Листинг 8.16. Описание функций для работы с совокупностью идентификаторов типов событий.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выясняет подразумеваемые значения */ /* атрибутов потока трассировки */ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include #include
    int main (void) { trace_attr_t attr; /* Атрибутный объект потока */ trace_id_t trid; /* Идентификатор потока */ char trbuf [TRACE_NAME_MAX]; /* Буфер для имен */ struct timespec tms; /* Структура для времен */ char dtbuf [LINE_MAX]; /* Буфер для данных о времени */ int res; /* Целочисленные результаты */ size_t mdatsz; /* Максимальный размер данных */ if ((errno = posix_trace_create (0, NULL, &trid)) != 0) { perror ("POSIX_TRACE_CREATE"); return (errno); }
    if ((errno = posix_trace_get_attr (trid, &attr)) != 0) { perror ("POSIX_TRACE_GET_ATTR"); return (errno); }
    if ((errno = posix_trace_attr_getgenversion (&attr, trbuf)) != 0) { perror ("POSIX_TRACE_ATTR_GETGENVERSION"); return (errno); } printf ("Версия системы трассировки: %s\n", trbuf);
    if ((errno = posix_trace_attr_getname (&attr, trbuf)) != 0) { perror ("POSIX_TRACE_ATTR_GETNAME"); return (errno); } printf ("Имя потока трассировки: %s\n", trbuf);
    if ((errno = posix_trace_attr_getcreatetime (&attr, &tms)) != 0) { perror ("POSIX_TRACE_ATTR_GETCREATETIME"); return (errno); } (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&tms.tv_sec)); printf ("Время создания потока трассировки: %s\n", dtbuf);
    if ((errno = posix_trace_attr_getclockres (&attr, &tms)) != 0) { perror ("POSIX_TRACE_ATTR_GETCLOCKRES"); return (errno); } printf ("Разрешающая способность часов потока " "трассировки: %ld нсек\n", tms.tv_nsec);
    if ((errno = posix_trace_attr_getstreamfullpolicy (&attr, &res)) != 0) { perror ("POSIX_TRACE_ATTR_GETSTREAMFULLPOLICY"); return (errno); } printf (" Правило обработки ситуации заполнения потока: "); switch (res) { case POSIX_TRACE_LOOP: printf ("POSIX_TRACE_LOOP\n"); break; case POSIX_TRACE_UNTIL_FULL: printf ("POSIX_TRACE_UNTIL_FULL\n"); break; case POSIX_TRACE_FLUSH: printf ("POSIX_TRACE_FLUSH\n"); break; default: printf ("неизвестно\n"); }
    if ((errno = posix_trace_attr_getlogfullpolicy (&attr, &res)) != 0) { perror ("POSIX_TRACE_ATTR_GETLOGFULLPOLICY"); return (errno); } printf ("Правило обработки ситуации заполнения " "журнала трассировки: "); switch (res) { case POSIX_TRACE_LOOP: printf ("POSIX_TRACE_LOOP\n"); break; case POSIX_TRACE_UNTIL_FULL: printf ("POSIX_TRACE_UNTIL_FULL\n"); break; case POSIX_TRACE_APPEND: printf ("POSIX_TRACE_APPEND\n"); break; default: printf ("неизвестно\n"); }
    if ((errno = posix_trace_attr_getmaxdatasize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXDATASIZE"); return (errno); } printf ("Максимальный размер данных в пользовательском " "событии: %d\n", mdatsz);
    if ((errno = posix_trace_attr_getmaxusereventsize (&attr, mdatsz, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXUSEREVENTSIZE"); return (errno); } printf ("Максимальный размер пользовательского события: " "%d\n", mdatsz);
    if ((errno = posix_trace_attr_getmaxsystemeventsize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETMAXSYSTEMEVENTSIZE"); return (errno); } printf ("Максимальный размер системного события: " "%d\n", mdatsz);
    if ((errno = posix_trace_attr_getstreamsize (&attr, &mdatsz)) != 0) { perror ("POSIX_TRACE_ATTR_GETSTREAMSIZE"); return (errno); } printf ("Размер потока трассировки: %d\n", mdatsz);
    return 0; }
    Листинг 8.17. Пример программы, выясняющей подразумеваемые значения атрибутов потока трассировки.
    Закрыть окно




    Версия системы трассировки: 2. 17 Имя потока трассировки: trace Время создания потока трассировки: Пн 19 АПР 2004 11:54:07 Разрешающая способность часов потока трассировки: 41904 нсек Правило обработки ситуации заполнения потока: POSIX_TRACE_UNTIL_FULL Правило обработки ситуации заполнения журнала трассировки: POSIX_TRACE_LOOP Максимальный размер данных в пользовательском событии: 100 Максимальный размер пользовательского события: 128 Максимальный размер системного события: 84 Размер потока трассировки: 409600
    Листинг 8.18. Возможные результаты работы программы, выясняющей подразумеваемые значения атрибутов потока трассировки.
    Закрыть окно




    #include #include
    void posix_trace_event ( trace_event_id_t event_id, const void *restrict data_ptr, size_t data_len);
    int posix_trace_eventid_open ( const char *restrict event_name, trace_event_id_t *restrict event_id);
    Листинг 8.19. Описание функций для оборудования трассируемых пользовательских приложений.
    Закрыть окно




    #include int posix_trace_open ( int fildes, trace_id_t *trid); int posix_trace_close (trace_id_t trid); int posix_trace_rewind (trace_id_t trid);
    Листинг 8.20. Описание функций для работы с журналами трассировки.
    Закрыть окно




    #include #include *lt;trace.h>
    int posix_trace_getnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void * restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable);
    int posix_trace_timedgetnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void *restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable, const struct timespec *restrict abstime);
    int posix_trace_trygetnext_event ( trace_id_t trid, struct posix_trace_event_info *restrict event, void *restrict data_ptr, size_t num_bytes, size_t *restrict data_len, int *restrict unavailable);
    Листинг 8.21. Описание функций чтения событий трассировки.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов с использованием */ /* сигналов реального времени и таймера для контроля */ /* длительности обеда как пример применения средств трассировки */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    . . . #include
    . . . /* Идентификаторы генерируемых пользовательских событий */ static trace_event_id_t pev1; /* Заявка на захват вилок */ static trace_event_id_t pev2; /* Захват вилок */ static trace_event_id_t pev3; /* Освобождение вилок */
    . . . /* * * * * * * * * * * * * * * * * * * * * * */ /* Попытка выполнить заявку на захват вилок */ /* от философа номер no, если она есть */ /* * * * * * * * * * * * * * * * * * * * * * */ static void fork_lock (int no) {
    . . . phil_req [no – 1] = 0; /* Сгенерируем событие – захват вилок */ posix_trace_event (pev2, &no, sizeof (no));
    . . . }
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, обслуживающего заявки на захват */ /* и освобождение вилок. Заявка передается в виде значения, */ /* ассоциированного с сигналом signo. Значение no > 0 */ /* запрашивает захват вилок для философа с номером no, */ /* no < 0 – освобождение философа -no */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_waiter (void *signo) { int no; /* Номер философа, приславшего заявку */
    . . . /* Заявка на захват вилок. */ /* Запомним ее ... */ phil_req [no – 1] = 1; /* Сгенерируем событие – заявка на захват вилок ... */ posix_trace_event (pev1, &no, sizeof (no)); /* ... и попробуем выполнить заявку */ fork_lock (no); } else { /* Освобождение вилок */ no = -no; fork_busy [no – 1] = fork_busy [no % QPH] = 0; /* Сгенерируем событие – освобождение вилок */ posix_trace_event (pev3, &no, sizeof (no));
    . . . }
    /* * * * * * * * * * * * * */ /* Организация обеда */ /* (бывшая функция main()) */ /* * * * * * * * * * * * * */ void *dinorg (void *dummy) {
    . . . return (NULL); }
    /* * * * * * * * * * * * * * * * */ /* Управление трассировкой, */ /* выборка и распечатка событий */ /* * * * * * * * * * * * * * * * */ int main (void) { trace_id_t trid; /* Идентификатор потока трассировки */ trace_event_set_t fltr; /* Множество фильтруемых событий */ pthread_t pt_dinorg; /* Идентификатор потока управления, */ /* ведающего организацией обеда */ /* Информация о событии */ struct posix_trace_event_info evinf; size_t datsz; /* Размер данных в событии */ /* Буфер для имени события */ char evnam [TRACE_EVENT_NAME_MAX]; int evdat [BUFSIZ]; /* Данные "вилочных" событий */ int res; /* Целочисленные результаты */ if ((errno = posix_trace_create (0, NULL, &trid)) != 0) { perror ("POSIX_TRACE_CREATE"); return (errno); }
    /* Установим фильтр на все системные события ... */ (void) posix_trace_eventset_fill (&fltr, POSIX_TRACE_SYSTEM_EVENTS); /* ... и удалим из него события с сигналами */ (void) posix_trace_eventset_del (traceSigGeneration, &fltr); (void) posix_trace_eventset_del (traceSigDelivery, &fltr); (void) posix_trace_eventset_del (traceSigCatchFunc, &fltr); if ((errno = posix_trace_set_filter (trid, &fltr, POSIX_TRACE_SET_EVENTSET)) != 0) { perror ("POSIX_TRACE_SET_FILTER"); return (errno); }
    /* Зарегистрируем три типа пользовательских событий */ if (((errno = posix_trace_eventid_open ( "Заявка на вилки философа", &pev1)) != 0) || ((errno = posix_trace_eventid_open ( "Захват вилок философа", &pev2)) != 0) || ((errno = posix_trace_eventid_open ( "Освобождение вилок философа", &pev3)) != 0)) { perror ("POSIX_TRACE_TRID_EVENTID_OPEN"); return (errno); }
    /* Активизируем трассировку */ if ((errno = posix_trace_start (trid)) != 0) { perror ("POSIX_TRACE_START"); return (errno); }
    /* Организуем обед */ if ((errno = pthread_create (&pt_dinorg, NULL, dinorg, NULL)) != 0) { perror ("PTHREAD_CREATE-dinorg"); return (errno); }
    /* Дождемся завершения обеда */ (void) pthread_join (pt_dinorg, NULL);
    /* Остановим трассировку */ if ((errno = posix_trace_stop (trid)) != 0) { perror ("POSIX_TRACE_STOP"); return (errno); }
    /* Прочитаем и распечатаем сгенерированные события */ printf ("Помещенные в поток трассировки события " "с вилками:\n"); while (posix_trace_getnext_event (trid, &evinf, &evdat, sizeof (evdat), &datsz, &res), res == 0) { if (evinf.posix_event_id == traceSigGeneration) { printf ("Потоком %p порожден сигнал с номером %d " "и значением %d " "для потока %p\n", (void *) evdat [0], evdat [2], evdat [3], (void *) evdat [5]); else if (evinf.posix_event_id == traceSigDelivery) { printf ("Потоку %p доставлен сигнал с номером %d " "и значением %d\n", (void *) evdat [0], evdat [1], evdat [2]); } else if (evinf.posix_event_id == traceSigCatchFunc) { printf ("В контексте потока %p вызван обработчик " "сигнала с номером %d " "и значением %d\n", (void *) evdat [0], evdat [1], evdat [2]); } else if (posix_trace_eventid_get_name (trid, evinf.posix_event_id, evnam) == 0) { printf (" %s %d\n", evnam, evdat [0]); } }
    return 0; }
    Листинг 8.22. Фрагмент исходного текста программы обеда философов с трассировкой.
    Закрыть окно




    Помещенные в поток трассировки события с вилками: Потоком 0xf10b34 порожден сигнал с номером 24 и значением 1 для потока 0xf14d54 Потоку 0xf14d54 доставлен сигнал с номером 24 и значением 1 Заявка на вилки философа 1 Захват вилок философа 1 Потоком 0xf14d54 порожден сигнал с номером 2 и значением 0 для потока 0xf10b34 Потоку 0xf10b34 доставлен сигнал с номером 2 и значением 0 В контексте потока 0xf10b34 вызван обработчик сигнала с номером 2 и значением 0 Потоком 0xf0c914 порожден сигнал с номером 24 и значением 2 для потока 0xf14d54 Потоку 0xf14d54 доставлен сигнал с номером 24 и значением 2 Потоком 0xf10b34 порожден сигнал с номером 24 и значением -1 для потока 0xf14d54 Потоку 0xf14d54 доставлен сигнал с номером 24 и значением -1 Заявка на вилки философа 2 Освобождение вилок философа 1 . . .
    Листинг 8.23. Фрагмент возможных результатов трассировки обеда двух философов на операционной платформе oc2000.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Трассировка системных событий, связанных с мьютексами. */ /* Демонстрируется, что один поток управления */ /* не дает второму захватить мьютекс */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    enum { TimeFirstThreadCritical, TimeFirstThreadNonCritical, TimeSecondThreadInit, TimeSecondThreadWait, TimeSecondThreadCritical, TimeSecondThreadNonCritical, TimeTest, TIME_SIZE };
    /* Массив задержек всех видов */ static struct timespec my_times [TIME_SIZE];
    /* Мьютекс, охраняющий вход в критический интервал */ static pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
    /* * * * * * * * * * * * * * * */ /* Заполнение массива задержек */ /* * * * * * * * * * * * * * * */ void init_times (void) { struct timespec resolution; int i;
    for (i = 0; i < TIME_SIZE; i++) { my_times [i].tv_sec = 0; my_times [i].tv_nsec = 0; }
    if (clock_getres (CLOCK_REALTIME, &resolution) != 0) { perror ("CLOCK_GETRES"); resolution.tv_nsec = 20000000; }
    my_times [TimeFirstThreadCritical].tv_nsec = resolution.tv_nsec * 3; my_times [TimeFirstThreadNonCritical].tv_nsec = resolution.tv_nsec * 7; my_times [TimeSecondThreadInit].tv_nsec = resolution.tv_nsec * 2; my_times [TimeSecondThreadWait].tv_nsec = resolution.tv_nsec * 10; my_times [TimeSecondThreadCritical].tv_nsec = resolution.tv_nsec * 4; my_times [TimeSecondThreadNonCritical].tv_nsec = resolution.tv_nsec * 8; my_times [TimeTest].tv_sec = 3; }
    /* * * * * * * * * * * * * * * * * */ /* Поток, монополизирующий мьютекс */ /* * * * * * * * * * * * * * * * * */ void *tf1 (void *dummy) { while (1) { if ((errno = pthread_mutex_lock (&my_mutex)) != 0) { perror ("PTHREAD_MUTEX_LOCK"); return ((void *) errno); } /* Критический интервал */ if (nanosleep (&my_times [TimeFirstThreadCritical], NULL) != 0) { perror ("NANOSLEEP-1-1"); return ((void *) errno); } if ((errno = pthread_mutex_unlock (&my_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); return ((void *) errno); }
    /* Действия вне критического интервала */ if (nanosleep (&my_times [TimeFirstThreadNonCritical], NULL) != 0) { perror ("NANOSLEEP-1-2"); return ((void *) errno); } }
    return (NULL); }
    /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Поток, безуспешно пытающийся захватить мьютекс */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ void *tf2 (void *dummy) {
    /* Подготовительные действия */ if (nanosleep (&my_times [TimeSecondThreadInit], NULL) != 0) { perror ("NANOSLEEP-2-1"); return ((void *) errno); }
    while (1) { if (pthread_mutex_trylock (&my_mutex) != 0) { /* Ожидание освобождения мьютекса */ if (nanosleep (&my_times [TimeSecondThreadWait], NULL) != 0) { perror ("NANOSLEEP-2-2"); return ((void *) errno); } } else { /* Критический интервал */ if (nanosleep (&my_times [TimeSecondThreadCritical], NULL) != 0) { perror ("NANOSLEEP-2-3"); return ((void *) errno); } if ((errno = pthread_mutex_unlock (&my_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); return ((void *) errno); }
    /* Действия вне критического интервала */ if (nanosleep (&my_times [TimeSecondThreadNonCritical], NULL) != 0) { perror ("NANOSLEEP-2-4"); return ((void *) errno); } } }
    return (NULL); }
    /* * * * * * * * * * * * * * * * */ /* Создание потоков управления, */ /* управление трассировкой, */ /* выборка и распечатка событий */ /* * * * * * * * * * * * * * * * */ int main (void) { pthread_t pt1, pt2; /* Идентификаторы потоков управления */ trace_id_t trid; /* Идентификатор потока трассировки */ trace_event_set_t fltr; /* Множество фильтруемых событий */ /* Информация о событии */ struct posix_trace_event_info evinf; size_t datsz; /* Размер данных в событии */ void *evdat [BUFSIZ]; /* Данные событий */ int res; /* Целочисленные результаты */
    if ((errno = posix_trace_create (0, NULL, &trid)) != 0) { perror ("POSIX_TRACE_CREATE"); return (errno); } /* Установим фильтр на системные события, */ /* не связанные с захватом мьютексов */ (void) posix_trace_eventset_fill (&fltr, POSIX_TRACE_SYSTEM_EVENTS); (void) posix_trace_eventset_del (traceMutexLock, &fltr); (void) posix_trace_eventset_del (traceMutexTryLock, &fltr); (void) posix_trace_eventset_del (traceMutexUnlock, &fltr); if ((errno = posix_trace_set_filter (trid, &fltr, POSIX_TRACE_SET_EVENTSET)) != 0) { perror ("POSIX_TRACE_SET_FILTER"); return (errno); }
    /* Заполним массив задержек */ init_times ();
    /* Активизируем трассировку */ if ((errno = posix_trace_start (trid)) != 0) { perror ("POSIX_TRACE_START"); return (errno); }
    /* Создадим потоки управления, конкурирующие за мьютекс */ (void) pthread_create (&pt1, NULL, tf1, NULL); (void) pthread_create (&pt2, NULL, tf2, NULL);
    /* Дадим созданным потокам повыполняться */ (void) nanosleep (&my_times [TimeTest], NULL);
    /* Терминируем потоки управления */ (void) pthread_cancel (pt2); (void) pthread_cancel (pt1); /* Дождемся завершения */ (void) pthread_join (pt1, NULL); (void) pthread_join (pt2, NULL);
    /* Остановим трассировку */ if ((errno = posix_trace_stop (trid)) != 0) { perror ("POSIX_TRACE_STOP"); return (errno); } printf ("Помещенные в поток трассировки события " "с мьютексом %p:\n", &mamp;y_mutex); while (posix_trace_getnext_event (trid, &evinf, &evdat, sizeof (evdat), &datsz, &res), res == 0) { /* Нас интересуют только операции с мьютексом */ /* my_mutex */ if (evdat [3] != &my_mutex) { continue; } if (evinf.posix_event_id == traceMutexLock) { printf ("%ld сек. %ld нсек. Попытка захвата " "мьютекса:\n" "код ошибки %d, поток %p, " "владелец %p\n", evinf.posix_timestamp.tv_sec, evinf.posix_timestamp.tv_nsec, (int) evdat [0], evinf.posix_thread_id, evdat [4]); } else if (evinf.posix_event_id == traceMutexTryLock) { printf ("%ld сек. %ld нсек. Попытка захвата без " "ожидания мьютекса:\n" "код ошибки %d, " "поток %p, владелец %p\n", evinf.posix_timestamp.tv_sec, evinf.posix_timestamp.tv_nsec, (int) evdat [0], evinf.posix_thread_id, evdat [4]); } else if (evinf.posix_event_id == traceMutexUnlock) { printf ("%ld сек. %ld нсек. Освобождение " "мьютекса:\n" "код ошибки %d, поток %p\n", evinf.posix_timestamp.tv_sec, evinf.posix_timestamp.tv_nsec, (int) evdat [0], evinf.posix_thread_id); } }
    return 0; }
    Листинг 8.24. Программа, демонстрирующая трассировку системных событий, связанных с мьютексами, на операционной платформе oc2000.
    Закрыть окно




    Помещенные в поток трассировки события с мьютексом 0xf34dcc . . . 18 сек. 200100569 нсек. Попытка захвата мьютекса: код ошибки 0, поток 0xf18f74, владелец 0xf18f74 18 сек. 240099731 нсек. Попытка захвата без ожидания мьютекса: код ошибки 6, поток 0xf14d54, владелец 0xf18f74 18 сек. 260103084 нсек. Освобождение мьютекса: код ошибки 0, поток 0xf18f74 18 сек. 400098893 нсек. Попытка захвата мьютекса: код ошибки 0, поток 0xf18f74, владелец 0xf18f74 18 сек. 440100569 нсек. Попытка захвата без ожидания мьютекса: код ошибки 6, поток 0xf14d54, владелец 0xf18f74 18 сек. 460102246 нсек. Освобождение мьютекса: код ошибки 0, поток 0xf18f74 . . .
    Листинг 8.25. Фрагмент возможных результатов трассировки системных событий, связанных с мьютексами, на операционной платформе oc2000.
    Закрыть окно



    Формирование и выполнение командных строк


    Служебная программа xargs позволяет формировать и выполнять командные строки, объединяя зафиксированный набор начальных аргументов с аргументами, прочитанными со стандартного ввода:
    xargs [-E логич_конец_файла] [-I заменяемая_цепочка] [-L число] [-n число] [-p] [-s размер] [-t] [-x] [утилита [начальный_аргумент ...]]
    Программа xargs объединяет зафиксированный набор заданных начальных_аргументов с аргументами, прочитанными со стандартного ввода, и выполняет указанную утилиту (по умолчанию – echo) в рамках сформированной командной строки, ожидая завершения выполнения. Данный процесс повторяется до достижения физического или логического конца файла стандартного ввода или до возвращения выполненной командной строкой  кода завершения 255.
    Аргументы, прочитанные со стандартного ввода, – это непрерывные цепочки символов, разделенные одним или несколькими пробелами или переводами строки; пустые строки игнорируются.
    Опциям служебной программы xargs приписан следующий смысл.
    -E логич_конец_файла
    Цепочка символов  логич_конец_файла считается признаком логического конца файла  стандартного ввода.
    -I заменяемая_цепочка
    Режим вставки: утилита выполняется для каждой строки стандартного ввода, причем вся строка рассматривается как один аргумент и подставляется в начальные аргументы вместо каждого вхождения заменяемой цепочки. Допускается не более пяти начальных аргументов, содержащих одно или несколько подобных вхождений. Пробелы в начале вводимых строк отбрасываются. Сформированные аргументы не могут быть длиннее 255 символов. Опция  -I включает опцию  -x.
    -L число
    Выполнять утилиту для каждой группы из заданного числа непустых строк аргументов, прочитанных со стандартного ввода. Последний вызов утилиты может быть с меньшим числом строк аргументов. Считается, что строка заканчивается символом перевода строки, если только перед ним не стоит пробел; пробел в конце сигнализируют о том, что следующая непустая строка является продолжением данной.
    -n число
    Выполнить утилиту, используя максимально возможное количество аргументов, прочитанных со стандартного ввода, но не более заданного числа.
    Будет использовано меньше аргументов, если их общая длина превышает размер, специфицированный опцией  -s, или если для последнего вызова их осталось меньше, чем заданное число.

    -p

    Режим с приглашением: xargs перед каждым вызовом утилиты запрашивает подтверждение. Включается режим трассировки (-t), за счет чего в стандартный протокол выводится командная строка, которая должна быть выполнена, а за ней – приглашение "?...". Положительный ответ, прочитанный с устройства /dev/tty, приводит к выполнению утилиты; в противном случае данный вызов утилиты игнорируется.

    -s размер

    Максимальный общий размер (в символах) каждого списка аргументов установить равным заданной величине; в рамках этого ограничения со стандартного ввода берется максимально возможное число аргументов. Будет использовано меньше аргументов, если более сильными окажутся ограничения, наложенные опциями  -n или -L, или если встретится конец файла.

    -t

    Режим трассировки: каждая сформированная командная строка перед выполнением выдается в стандартный протокол.

    -x

    Завершить работу служебной программы xargs, если очередная сформированная командная строка, потребившая заданное опцией  -n число аргументов или заданное опцией  -L число строк, оказалась длиннее, чем специфицированный опцией  -s размер.

    Приведем несколько примеров применения служебной программы xargs. На всякий случай подчеркнем, что никто не утверждал, что Linux или какая-либо иная операционная система соответствует стандарту POSIX-2001. Весьма вероятно, что перед исполнением примеров их придется немного подправить.

    Следующая однострочная shell-процедура пересылает все файлы из каталога  $1 в каталог  $2 и сообщает о каждой пересылке, перед тем как ее выполнить:

    ls $1 | xargs -I {} -t mv $1/{} $2/{}

    Еще одна однострочная shell-процедура применяет служебную программу diff к последовательным парам своих аргументов.

    echo $* | xargs -n 2 diff

    Пользователя спрашивают, какие объектные файлы из текущего каталога должны быть занесены в библиотеку mylib.a.При выполнении первого конвейера (см. ниже) файлы заносятся по одному; при выполнении второго заносится сразу много файлов.

    ls *.o | xargs -p -L 1 ar -r mylib.a ls *.o | xargs -p -L 1 | xargs ar -r mylib.a

    Отметим, что во втором звене второго конвейера в качестве подразумеваемой утилиты будет использована служебная программа echo.


    Функции для работы с базой данных учетной информации о пользователях


    Мы продолжаем рассматривать функции, находящиеся на стыке пользовательских и административных средств.
    Стандартом POSIX-2001 предусмотрен набор функций для работы с базой данных учетной информации о пользователях. Эти функции реализуют последовательный просмотр учетных записей (getutxent()), поиск в базе (getutxid(), getutxline()), модификацию или добавление записей (pututxline()), возврат к началу (setutxent()) и завершение работы с базой (endutxent()) (см. листинг 9.7).
    #include
    struct utmpx *getutxent (void);
    struct utmpx *getutxid ( const struct utmpx *id);
    struct utmpx *getutxline ( const struct utmpx *line);
    struct utmpx *pututxline ( const struct utmpx *utmpx);
    void setutxent (void);
    void endutxent (void);
    Листинг 9.7. Описание функций для работы с базой данных учетной информации о пользователях. (html, txt)
    Центральную роль для описываемого набора функций играет структура типа utmpx, которая, согласно стандарту, должна содержать по крайней мере следующие поля.
    char ut_user []; /* Входное имя пользователя */ char ut_id []; /* Неспецифицированный */ /* инициализационный идентификатор */ /* процесса (например, первое поле */ /* в строке файла inittab) */ char ut_line []; /* Имя устройства */ pid_t ut_pid; /* Идентификатор процесса */ short ut_type; /* Тип записи */ struct timeval ut_tv; /* Время создания записи */
    В зависимости от типа записи (элемент ut_type) определяется подмножество полей, содержащих осмысленные значения. Для пустых записей (тип  EMPTY) таких полей нет вообще. Для типов  BOOT_TIME (идентифицирует время загрузки системы), OLD_TIME (время изменения показаний системных часов), NEW_TIME (показания системных часов после изменения) имеет смысл только время создания записи (элемент ut_tv). Записи  типа  USER_PROCESS идентифицируют пользовательские процессы и содержат полезную информацию в элементах ut_user (входное имя пользователя), ut_id, ut_line, ut_pid и ut_tv. Почти такое же подмножество полей имеет смысл для типа  LOGIN_PROCESS (по стандарту он идентифицирует лидера сеанса вошедшего в систему пользователя): ut_user (зависящее от реализации имя входного процесса), ut_id, ut_pid, ut_tv.
    Мы продолжаем рассматривать функции, находящиеся на стыке пользовательских и административных средств.
    Стандартом POSIX-2001 предусмотрен набор функций для работы с базой данных учетной информации о пользователях. Эти функции реализуют последовательный просмотр учетных записей (getutxent()), поиск в базе (getutxid(), getutxline()), модификацию или добавление записей (pututxline()), возврат к началу (setutxent()) и завершение работы с базой (endutxent()) (см. листинг 9.7).
    #include
    struct utmpx *getutxent (void);
    struct utmpx *getutxid ( const struct utmpx *id);
    struct utmpx *getutxline ( const struct utmpx *line);
    struct utmpx *pututxline ( const struct utmpx *utmpx);
    void setutxent (void);
    void endutxent (void);
    Листинг 9.7. Описание функций для работы с базой данных учетной информации о пользователях.
    Центральную роль для описываемого набора функций играет структура типа utmpx, которая, согласно стандарту, должна содержать по крайней мере следующие поля.
    char ut_user []; /* Входное имя пользователя */ char ut_id []; /* Неспецифицированный */ /* инициализационный идентификатор */ /* процесса (например, первое поле */ /* в строке файла inittab) */ char ut_line []; /* Имя устройства */ pid_t ut_pid; /* Идентификатор процесса */ short ut_type; /* Тип записи */ struct timeval ut_tv; /* Время создания записи */
    В зависимости от типа записи (элемент ut_type) определяется подмножество полей, содержащих осмысленные значения. Для пустых записей (тип  EMPTY) таких полей нет вообще. Для типов  BOOT_TIME (идентифицирует время загрузки системы), OLD_TIME (время изменения показаний системных часов), NEW_TIME (показания системных часов после изменения) имеет смысл только время создания записи (элемент ut_tv). Записи  типа  USER_PROCESS идентифицируют пользовательские процессы и содержат полезную информацию в элементах ut_user (входное имя пользователя), ut_id, ut_line, ut_pid и ut_tv. Почти такое же подмножество полей имеет смысл для типа  LOGIN_PROCESS (по стандарту он идентифицирует лидера сеанса вошедшего в систему пользователя): ut_user (зависящее от реализации имя входного процесса), ut_id, ut_pid, ut_tv.


    Наконец, для типов  INIT_PROCESS ( идентифицирует процесс, порожденный системным процессом init) и DEAD_PROCESS (по стандарту он идентифицирует лидера сеанса, завершившего выполнение) имеют смысл лишь элементы ut_id, ut_pid и ut_tv.

    Разумеется, реальные размеры массивов, являющихся элементами структуры, можно узнать, применяя к ним на целевой платформе функцию sizeof().

    Функция getutxent() читает очередную запись из базы данных учетной информации о пользователях. Если база данных еще не открыта, она открывается. При достижении конца базы данных выполнение функции завершается неудачей и возвращается пустой указатель. Нормальным результатом является указатель на копию прочитанной записи, размещенную в структуре типа utmpx.

    Функция getutxid(), начиная с текущей позиции, разыскивает запись  базы данных, в которой поле ut_type соответствует значению id->ut_type. Если элемент id->ut_type равен BOOT_TIME, OLD_TIME, или NEW_TIME, то требуется точное равенство типов. Если же id->ut_type равняется INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS или DEAD_PROCESS, то функция getutxid() вернет указатель на копию первой записи, тип которой равен одному из четырех перечисленных, и поле ut_id соответствует значению id->ut_id.

    Функция getutxline() аналогичным образом разыскивает запись, тип которой равен LOGIN_PROCESS или USER_PROCESS, а поле ut_line соответствует значению line->ut_line.

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

    Функция pututxline() записывает указанную utmpx-структуру в базу данных (если вызывающий процесс обладает соответствующими привилегиями). При этом для поиска нужного места используется функция getutxid(), если обнаруживается, что текущая позиция не является подходящей. В случае неудачи поиска  запись добавляется в конец базы данных.

    Отметим, что запись  базы данных, к которой было последнее обращение, может сохраняться реализацией в статической структуре (и указатель на нее возвращаться в качестве результата функций), поэтому доступ к нескольким записям требует копирования структур.


    Далее, при обращении к getutxid() или getutxline() реализация имеет право сначала проанализировать упомянутую статическую структуру и, если та окажется подходящей, не производить поиск, а вернуть ее же. Следовательно, если приложению требуется найти несколько подходящих записей, то после очередного успешного поиска и копирования структуры ее необходимо очистить.

    В свою очередь, согласно стандарту, приложение имеет право передать функции pututxline() указатель на статическую структуру, заполненную в результате обращения к getutxent(), getutxid() или getutxline(), предварительно изменив ее требуемым образом. Неявное чтение, осуществляемое функцией pututxline() для определения замещаемой записи, эту структуру не испортит.

    Функция setutxent() устанавливает указатель текущей позиции на начало базы данных. Ее следует вызывать перед поиском каждой новой записи, если предполагается, что поиск должен проводиться во всей базе.

    Функция endutxent() закрывает базу данных учетной информации о пользователях.

    Приведем пример использования описанных функций (см. листинг 9.8). Прочитаем и выведем все записи  базы данных учетной информации о пользователях.

    Листинг 9.8. Пример применения функций для работы с базой данных учетной информации о пользователях. (html, txt)

    Фрагмент возможных результатов выполнения приведенной программы на платформе ОС Linux показан на листинге 9.9. Его изучение позволяет лучше уяснить смысл элементов структуры utmpx для разных типов записей и, попутно, увидеть некоторые нестандартности в поведении ОС Linux.

    Листинг 9.9. Фрагмент возможных результатов выполнения программы, применяющей функции для работы с базой данных учетной информации о пользователях. (html, txt)

    Отметим, что нестандартный тип записи  1 соответствует смене уровня выполнения.

    Обратим также внимание на то, что в шаблоне поиска, производимого с помощью функции getutxid(), значение поля ut_type задано как INIT_PROCESS, а в результате поиска оно оказалось равным USER_PROCESS (в полном соответствии со стандартом).



    Наконец, для типов  INIT_PROCESS ( идентифицирует процесс, порожденный системным процессом init) и DEAD_PROCESS (по стандарту он идентифицирует лидера сеанса, завершившего выполнение) имеют смысл лишь элементы ut_id, ut_pid и ut_tv.

    Разумеется, реальные размеры массивов, являющихся элементами структуры, можно узнать, применяя к ним на целевой платформе функцию sizeof().

    Функция getutxent() читает очередную запись из базы данных учетной информации о пользователях. Если база данных еще не открыта, она открывается. При достижении конца базы данных выполнение функции завершается неудачей и возвращается пустой указатель. Нормальным результатом является указатель на копию прочитанной записи, размещенную в структуре типа utmpx.

    Функция getutxid(), начиная с текущей позиции, разыскивает запись  базы данных, в которой поле ut_type соответствует значению id->ut_type. Если элемент id->ut_type равен BOOT_TIME, OLD_TIME, или NEW_TIME, то требуется точное равенство типов. Если же id->ut_type равняется INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS или DEAD_PROCESS, то функция getutxid() вернет указатель на копию первой записи, тип которой равен одному из четырех перечисленных, и поле ut_id соответствует значению id->ut_id.

    Функция getutxline() аналогичным образом разыскивает запись, тип которой равен LOGIN_PROCESS или USER_PROCESS, а поле ut_line соответствует значению line->ut_line.

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

    Функция pututxline() записывает указанную utmpx-структуру в базу данных (если вызывающий процесс обладает соответствующими привилегиями). При этом для поиска нужного места используется функция getutxid(), если обнаруживается, что текущая позиция не является подходящей. В случае неудачи поиска  запись добавляется в конец базы данных.

    Отметим, что запись  базы данных, к которой было последнее обращение, может сохраняться реализацией в статической структуре (и указатель на нее возвращаться в качестве результата функций), поэтому доступ к нескольким записям требует копирования структур.


    Далее, при обращении к getutxid() или getutxline() реализация имеет право сначала проанализировать упомянутую статическую структуру и, если та окажется подходящей, не производить поиск, а вернуть ее же. Следовательно, если приложению требуется найти несколько подходящих записей, то после очередного успешного поиска и копирования структуры ее необходимо очистить.

    В свою очередь, согласно стандарту, приложение имеет право передать функции pututxline() указатель на статическую структуру, заполненную в результате обращения к getutxent(), getutxid() или getutxline(), предварительно изменив ее требуемым образом. Неявное чтение, осуществляемое функцией pututxline() для определения замещаемой записи, эту структуру не испортит.

    Функция setutxent() устанавливает указатель текущей позиции на начало базы данных. Ее следует вызывать перед поиском каждой новой записи, если предполагается, что поиск должен проводиться во всей базе.

    Функция endutxent() закрывает базу данных учетной информации о пользователях.

    Приведем пример использования описанных функций (см. листинг 9.8). Прочитаем и выведем все записи  базы данных учетной информации о пользователях.

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Пример использования функций для работы */ /* с базой данных учетной информации о пользователях */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include

    int main (void) { struct utmpx *utmpx_ptr; /* Указатель на текущую запись */ char dtbuf [LINE_MAX]; /* Буфер для данных о времени */ struct utmpx spat; /* Шаблон для поиска в базе */

    /* Прочитаем и распечатаем все записи в базе */ printf ("Содержимое базы данных учетной информации " "о пользователях\n"); while ((utmpx_ptr = getutxent ()) != NULL) { (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&(utmpx_ptr->ut_tv.tv_sec))); switch (utmpx_ptr->ut_type) { case EMPTY: printf ("Пустая запись\n"); break; case BOOT_TIME: printf ("Время загрузки системы: %s\n", dtbuf); break; case OLD_TIME: printf ("Время изменения показаний системных " "часов: %s\n", dtbuf); break; case NEW_TIME: printf ("Показания системных часов после " "изменения: %s\n", dtbuf); break; case USER_PROCESS: printf ("Процесс пользователя: %s, идентификатор: " "%d,\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Имя устройства: %s,\n", utmpx_ptr->ut_line); printf ("Время создания записи: %s\n", dtbuf); break; case LOGIN_PROCESS: printf ("Входной процесс: %s, идентификатор: %d,\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; case INIT_PROCESS: printf ("Процесс, порожденный системным процессом " "init:\n"); printf ("Идентификатор: %d,\n", utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; case DEAD_PROCESS: printf ("Лидер сеанса, завершивший выполнение:\n"); printf ("Идентификатор: %d,\n", utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; default: printf ("Нестандартный тип записи: %x\n", utmpx_ptr->ut_type); break; } }



    /* Найдем и распечатаем записи, */ /* инициализационный идентификатор которых */ /* равняется S4 */ spat.ut_type = INIT_PROCESS; (void) strncpy (spat.ut_id, "S4", sizeof (spat.ut_id));

    /* Позиционируемся на начало базы */ setutxent ();

    printf ("Записи, инициализационный идентификатор " "которых равняется S4:\n"); while ((utmpx_ptr = getutxid (&spat)) != NULL) { switch (utmpx_ptr->ut_type) { case USER_PROCESS: printf ("Процесс пользователя: %s, " "идентификатор: %d\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); break; case LOGIN_PROCESS: printf ("Входной процесс: %s, идентификатор: " "%d\n",utmpx_ptr->ut_user, utmpx_ptr->ut_pid); break; case INIT_PROCESS: printf ("Процесс, порожденный системным процессом " "init:\n"); printf ("Идентификатор: %d\n", utmpx_ptr->ut_pid); break; case DEAD_PROCESS: printf ("Лидер сеанса, завершивший " "выполнение:\n"); printf ("Идентификатор: %d\n", utmpx_ptr->ut_pid); break; default: printf ("Нестандартный тип результата поиска: " "%x\n", utmpx_ptr->ut_type); break; }

    /* Обеспечим сдвиг поиска с текущей записи */ utmpx_ptr->ut_id [0] = 0; }

    endutxent ();

    return 0; }

    Листинг 9.8. Пример применения функций для работы с базой данных учетной информации о пользователях.

    Фрагмент возможных результатов выполнения приведенной программы на платформе ОС Linux показан на листинге 9.9. Его изучение позволяет лучше уяснить смысл элементов структуры utmpx для разных типов записей и, попутно, увидеть некоторые нестандартности в поведении ОС Linux.

    Содержимое базы данных учетной информации о пользователях Лидер сеанса, завершивший выполнение: Идентификатор: 17, Инициализационный идентификатор процесса: si, Время создания записи: Tue Apr 27 10:08:42 2004 Время загрузки системы: Tue Apr 27 10:08:42 2004 Нестандартный тип записи: 1 Лидер сеанса, завершивший выполнение: Идентификатор: 284, Инициализационный идентификатор процесса: l5, Время создания записи: Tue Apr 27 10:09:15 2004 Лидер сеанса, завершивший выполнение: Идентификатор: 1115, Инициализационный идентификатор процесса: ud, Время создания записи: Tue Apr 27 10:09:15 2004 . . .


    Входной процесс: LOGIN, идентификатор: 1123, Инициализационный идентификатор процесса: S3, Время создания записи: Tue Apr 27 10:09:15 2004 Процесс пользователя: galat, идентификатор: 1124, Инициализационный идентификатор процесса: S4, Имя устройства: ttyS4, Время создания записи: Tue Apr 27 12:52:51 2004 Процесс пользователя: sambor, идентификатор: 1125, Инициализационный идентификатор процесса: S5, Имя устройства: ttyS5, Время создания записи: Tue Apr 27 13:57:31 2004 Процесс пользователя: kost, идентификатор: 1126, Инициализационный идентификатор процесса: S6, Имя устройства: ttyS6, Время создания записи: Tue Apr 27 10:09:30 2004 . . . Процесс, порожденный системным процессом init: Идентификатор: 1128, Инициализационный идентификатор процесса: x, Время создания записи: Tue Apr 27 10:09:15 2004 . . . Лидер сеанса, завершивший выполнение: Идентификатор: 11708, Инициализационный идентификатор процесса: /1, Время создания записи: Tue Apr 27 11:19:33 2004 . . . Записи, инициализационный идентификатор которых равняется S4: Процесс пользователя: galat, идентификатор: 1124

    Листинг 9.9. Фрагмент возможных результатов выполнения программы, применяющей функции для работы с базой данных учетной информации о пользователях.

    Отметим, что нестандартный тип записи  1 соответствует смене уровня выполнения.

    Обратим также внимание на то, что в шаблоне поиска, производимого с помощью функции getutxid(), значение поля ut_type задано как INIT_PROCESS, а в результате поиска оно оказалось равным USER_PROCESS (в полном соответствии со стандартом).


    Функции для работы с простыми базами данных


    Описываемые ниже функции полезны для реализации "игрушечных" баз данных при изготовлении прототипов приложений. Нет и речи о поддержке многопользовательского режима. Ключ в записи может быть только один. Суммарная длина ключа и данных (точнее, всех ключей, имеющих один хэш-код, и ассоциированных с ними данных) не должна превышать 1023 байта и т.д. и т.п.
    Набор стандартизованных функций "традиционно минимален" (см. листинг 9.10). Базу данных можно открыть (dbm_open()) и закрыть (dbm_close()), выбрать (dbm_fetch()), сохранить (dbm_store()) и удалить (dbm_delete()) запись по ключу, перебрать имеющиеся в базе ключи (dbm_firstkey(), dbm_nextkey()), опросить статус ошибки (dbm_error()) и очистить его (dbm_clearerr()).
    #include
    DBM *dbm_open (const char *file, int open_flags, mode_t file_mode);
    void dbm_close (DBM *db);
    datum dbm_fetch (DBM *db, datum key);
    int dbm_store (DBM *db, datum key, datum content, int store_mode);
    int dbm_delete (DBM *db, datum key);
    datum dbm_firstkey (DBM *db);
    datum dbm_nextkey (DBM *db);
    int dbm_error (DBM *db);
    int dbm_clearerr (DBM *db);
    Листинг 9.10. Описание функций для работы с простыми базами данных. (html, txt)
    Функцию dbm_open() можно считать аналогом open(), только в роли возвращаемого в качестве результата дескриптора базы данных выступает указатель на объект типа  DBM (структура последнего скрыта от приложения), база хранится в двух файлах – file.dir и file.pag, ее нельзя открыть только на запись, а применение флага O_APPEND ведет к неспецифицированным последствиям. В случае ошибки результат вызова dbm_open() равняется (DBM *) NULL.
    При работе с ключами и данными центральную роль играет структура типа datum, которая по стандарту должна содержать по крайней мере два поля.
    void *dptr; /* Указатель на прикладные данные */ size_t dsize; /* Размер прикладных данных */
    Функция dbm_fetch() служит для выборки  записи по ключу  key. Если таковая отсутствует или обнаруживается ошибка, то в возвращаемом объекте типа  datum элемент dptr равняется пустому указателю.

    Функция dbm_store() позволяет поместить данные, заданные аргументом  content, в базу. Аргумент  store_mode определяет способ сохранения новых данных. Если в базе уже есть запись с заданным ключом  key, то в режиме DBM_REPLACE новая запись замещает старую, а в режиме DBM_INSERT она (новая запись) игнорируется (и результат вызова равняется единице). Если записи с ключом  key в базе нет, новая запись добавляется к базе.

    Функция dbm_delete() предназначена для удаления данных и ключа  key.

    Нормальным результатом функций dbm_store() и dbm_delete() является нуль; в случае ошибки возвращается отрицательное значение.

    Функция dbm_nextkey() является итератором по ключам, хранящимся в базе, а dbm_firstkey() инициализирует этот итератор, возвращая первый ключ. При завершении перебора и при наличии ошибок возвращается объект типа  datum с пустым указателем в качестве значения элемента dptr. Если по ходу итераций содержимое базы менялось (вызывались функции dbm_store() и/или dbm_delete()), перебор ключей нужно начинать заново.

    Функция dbm_error() возвращает ненулевое значение при установленном статусе ошибки, функция dbm_clearerr() делает статус "безошибочным". Таким образом, вся диагностика по сути сводится к одному биту, интерпретировать который должно приложение.

    Несмотря на "игрушечность" описанного интерфейса, он находит определенное применение на Unix-системах. В качестве примера рассмотрим программу, которая распечатывает содержимое базы данных  aliases, расположенной в каталоге  /etc/mail на SPARC-станции (см. листинг 9.11).

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа перебирает все ключи в базе данных */ /* и выдает ассоциированную с ними информацию. */ /* Предполагается, что ключи и данные – текстовые*/ /* * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include

    int main (int argc, char *argv []) { DBM *dbdes; /* Дескриптор открытой базы */ datum ckey; /* Текущий ключ */ datum cdat; /* Текущие данные */ int nkeys = 0; /* Число ключей */



    if (argc != 2) { fprintf (stderr, "Использование: %s имя_базы\n", argv [0]); return (1); }

    if ((dbdes = dbm_open (argv [1], O_RDONLY, 0777)) == (DBM *) NULL) { fprintf (stderr, " Не удалось открыть базу данных %s\n", argv [1]); return (2); }

    for (ckey = dbm_firstkey (dbdes); ckey.dptr != NULL; ckey = dbm_nextkey (dbdes)) { nkeys++; printf ("Длина ключа номер %d: %d\n", nkeys, ckey.dsize); printf ("Ключ номер %d: %s\n", nkeys, ckey.dptr);

    if (cdat = dbm_fetch (dbdes, ckey), cdat.dptr != NULL) { printf ("Длина данных для ключа номер %d: %d\n", nkeys, cdat.dsize); printf ("Данные для ключа номер %d: %s\n", nkeys, cdat.dptr); } else { fprintf (stderr, "Отсутствуют данные для " "ключа номер %d\n", nkeys); } }

    printf ("Число ключей в базе: %d\n", nkeys);

    dbm_close (dbdes);

    return 0; }

    Листинг 9.11. Пример применения функций для работы с простыми базами данных.

    Результаты работы этой программы могут выглядеть так, как показано на листинге 9.12.

    Длина ключа номер 1: 16 Ключ номер 1: YP_LAST_MODIFIED Длина данных для ключа номер 1: 10 Данные для ключа номер 1: 0898782331 Длина ключа номер 2: 14 Ключ номер 2: mailer-daemon Длина данных для ключа номер 2: 11 Данные для ключа номер 2: postmaster Длина ключа номер 3: 14 Ключ номер 3: YP_MASTER_NAME Длина данных для ключа номер 3: 3 Данные для ключа номер 3: t41 Длина ключа номер 4: 11 Ключ номер 4: postmaster Длина данных для ключа номер 4: 5 Данные для ключа номер 4: root Длина ключа номер 5: 7 Ключ номер 5: nobody Длина данных для ключа номер 5: 10 Данные для ключа номер 5: /dev/null Длина ключа номер 6: 2 Ключ номер 6: @ Длина данных для ключа номер 6: 2 Данные для ключа номер 6: @ Число ключей в базе: 6

    Листинг 9.12. Возможные результаты выполнения программы, применяющей функции для работы с простыми базами данных.


    Функции для работы с псевдотерминалами


    В курсе [1] мы уже останавливались на понятии псевдотерминала. Опишем теперь соответствующие функции.
    Первым действием при работе с псевдотерминалами является открытие главного устройства, осуществляемое функцией posix_openpt() (см. листинг 9.49).
    #include #include int posix_openpt (int oflag);
    Листинг 9.49. Описание функции открытия главного устройства псевдотерминала. (html, txt)
    В значении аргумента  oflag есть смысл устанавливать флаги O_RDWR и O_NOCTTY (последний означает, что терминал не будет управляющим для процесса).
    Разумеется, нормальным результатом функции posix_openpt()> служит файловый дескриптор; в случае ошибки возвращается -1.
    Считается, что после открытия главного устройства  подчиненное устройство псевдотерминала  блокируется, и эту блокировку необходимо явным образом снять, вызвав функцию unlockpt() (см. листинг 9.50).
    #include int unlockpt (int masterfd);
    Листинг 9.50. Описание функции разблокирования подчиненного устройства псевдотерминала. (html, txt)
    Здесь masterfd – файловый дескриптор  главного устройства псевдотерминала, полученный в результате вызова функции posix_openpt().
    Нормальным для unlockpt() является нулевой результат.
    Функция grantpt() (см. листинг 9.51) позволяет сформировать нужные права доступа к подчиненному устройству псевдотерминала. Владелец устройства устанавливается в соответствии с реальным идентификатором пользователя вызывающего процесса; ему (владельцу) предоставляются права на чтение и запись.
    #include int grantpt (int masterfd);
    Листинг 9.51. Описание функции формирования прав доступа к подчиненному устройству псевдотерминала. (html, txt)
    Применительно к функции grantpt() в стандарте POSIX-2001 оговаривается одна тонкость: вызывающий процесс не должен обрабатывать сигнал SIGCHLD. Это можно понять (и оправдать), поскольку grantpt() выполняет суперпользовательское действие, реализация которого в некоторых ОС может быть сопряжена с порождением процессов.

    Чтобы узнать имя подчиненного устройства псевдотерминала, следует обратиться к функции ptsname() (см. листинг 9.52).

    #include char *ptsname (int masterfd);

    Листинг 9.52. Описание функции получения имени подчиненного устройства псевдотерминала. (html, txt)

    Разумеется, результирующий указатель может ссылаться на статическую область, перезаписываемую при каждом вызове ptsname(), поэтому рекомендуется полученное имя скопировать или поскорее использовать. Использование очевидно – посредством обычной функции open()  открыть подчиненное устройство псевдотерминала и получить его файловый дескриптор.

    Таким образом, в стандарте POSIX-2001 выстроена пятиэтапная модель получения доступа к псевдотерминалу:

  • открытие главного устройства псевдотерминала, получение его файлового дескриптора (осуществляется функцией posix_openpt());
  • разблокирование подчиненного устройства псевдотерминала (функция unlockpt());
  • формирование прав доступа к подчиненному устройству псевдотерминала (grantpt());
  • получение имени подчиненного устройства псевдотерминала (ptsname());
  • открытие подчиненного устройства псевдотерминала, получение его файлового дескриптора (open()).


  • Применение описанных функций иллюстрируется переработанным вариантом программы из курса [1], запускающей командный интерпретатор на псевдотерминале (см. листинг 9.53).

    Листинг 9.53. Пример программы, использующей псевдотерминалы. (html, txt)

    Здесь все пять упоминавшихся подготовительных этапов вошли в состав одного условного оператора. Обратим внимание на применение флага O_NOCTTY при вызове posix_openpt(), а также на то, что обращение к grantpt() выполнено до установки функции обработки сигнала  SIGCHLD.


    Функции и утилиты для работы с системным журналом


    Под системным журналом в стандарте POSIX-2001 понимается некое средство хранения и/или отображения данных, предназначенных для изучения системным администратором. Средство это, разумеется, зависит от реализации. Это может быть обычный локальный файл (например, /var/log/messages), список рассылки, удаленный сетевой ресурс и т.д.
    Для работы с системным журналом стандарт предлагает функции записи  сообщений (syslog()), установки фильтра (маски журналируемых сообщений, setlogmask()) и других параметров журналирования (openlog()) и, наконец, завершения работы с системным журналом (closelog()) (см. листинг 9.1). Средства чтения системного журнала, как и все, что связано с администрированием, в стандарт POSIX-2001 не входят.
    #include
    void syslog (int priority, const char *message, ... /* аргументы */);
    int setlogmask (int maskpri); void openlog (const char *ident, int logopt, int facility);
    void closelog (void);
    Листинг 9.1. Описание функций для работы с системным журналом. (html, txt)
    Сообщение, которое помещает в системный журнал функция syslog(), включает заголовок и тело.
    В заголовок входит по крайней мере временной штамп и идентифицирующая цепочка символов.
    Тело  сообщения формируется из аргумента  message, играющего роль формата, и следующих за ним необязательных аргументов аналогично тому, как если бы использовалась функция printf(), только допускается один дополнительный спецификатор преобразования – %m, не требующий аргумента и осуществляющий вывод текущего значения переменной errno.
    Значение аргумента  priority формируется как побитное ИЛИ флагов двух видов, задающих, соответственно, уровень серьезности и источник сообщения.
    Уровень серьезности может принимать следующие значения.
    LOG_EMERG
    Катастрофическая ситуация.
    LOG_ALERT
    Ситуация, требующая немедленного вмешательства (например, повреждение системной базы данных).
    LOG_CRIT
    Опасная ситуация (например, ошибки в работе аппаратуры).
    LOG_ERR
    Сообщение об ошибке.
    LOG_WARNING
    Предупреждающее сообщение.

    Под системным журналом в стандарте POSIX-2001 понимается некое средство хранения и/или отображения данных, предназначенных для изучения системным администратором. Средство это, разумеется, зависит от реализации. Это может быть обычный локальный файл (например, /var/log/messages), список рассылки, удаленный сетевой ресурс и т.д.
    Для работы с системным журналом стандарт предлагает функции записи  сообщений (syslog()), установки фильтра (маски журналируемых сообщений, setlogmask()) и других параметров журналирования (openlog()) и, наконец, завершения работы с системным журналом (closelog()) (см. листинг 9.1). Средства чтения системного журнала, как и все, что связано с администрированием, в стандарт POSIX-2001 не входят.
    #include
    void syslog (int priority, const char *message, ... /* аргументы */);
    int setlogmask (int maskpri); void openlog (const char *ident, int logopt, int facility);
    void closelog (void);
    Листинг 9.1. Описание функций для работы с системным журналом.
    Сообщение, которое помещает в системный журнал функция syslog(), включает заголовок и тело.
    В заголовок входит по крайней мере временной штамп и идентифицирующая цепочка символов.
    Тело  сообщения формируется из аргумента  message, играющего роль формата, и следующих за ним необязательных аргументов аналогично тому, как если бы использовалась функция printf(), только допускается один дополнительный спецификатор преобразования – %m, не требующий аргумента и осуществляющий вывод текущего значения переменной errno.
    Значение аргумента  priority формируется как побитное ИЛИ флагов двух видов, задающих, соответственно, уровень серьезности и источник сообщения.
    Уровень серьезности может принимать следующие значения.
    LOG_EMERG
    Катастрофическая ситуация.
    LOG_ALERT
    Ситуация, требующая немедленного вмешательства (например, повреждение системной базы данных).
    LOG_CRIT
    Опасная ситуация (например, ошибки в работе аппаратуры).
    LOG_ERR
    Сообщение об ошибке.
    LOG_WARNING
    Предупреждающее сообщение.



    LOG_NOTICE

    Ситуация, не являющаяся ошибочной, но, возможно, требующая специальных действий.

    LOG_INFO

    Информационное сообщение.

    LOG_DEBUG

    Отладочное сообщение.

    Из источников сообщений стандартизован только один (естественно, являющийся подразумеваемым) – пользовательский процесс (флаг LOG_USER). Зарезервированы флаги для системных источников, имена которых говорят сами за себя (LOG_KERN, LOG_MAIL, LOG_NEWS, LOG_UUCP, LOG_DAEMON, LOG_AUTH, LOG_CRON, LOG_LPR) и для абстрактных локальных сущностей (LOG_LOCAL0 – LOG_LOCAL7).

    Функция setlogmask() в качестве результата возвращает предыдущую и устанавливает новую маску журналируемых сообщений вызывающего процесса. В системный журнал будут помещаться только те сообщения, уровень серьезности которых присутствует в маске, заданной аргументом  maskpri. Для формирования этого аргумента по одному уровню серьезности стандартом предусмотрен макрос LOG_MASK (pri). Для задания маски, включающей несколько уровней, нужно взять побитное ИЛИ подобных выражений.

    Если значение maskpri равно нулю, текущая маска остается неизменной. Подразумевая маска является "полной", она специфицирует журналирование всех событий.

    Функция openlog() устанавливает значения атрибутов журналирования вызывающего процесса, влияющие на последующие обращения к syslog(). Аргумент  ident задает идентифицирующую цепочку, фигурирующую в заголовках всех сообщений. Аргумент  logopt специфицирует опции журналирования. Он формируется как побитное ИЛИ следующих флагов.

    LOG_PID

    Записывать вместе с сообщением  идентификатор процесса.

    LOG_CONS

    Выдавать сообщение на системную консоль, если его не удается поместить в журнал.

    LOG_NDELAY

    Немедленно открыть системный журнал (обычно открытие откладывается до записи первого сообщения).

    LOG_ODELAY

    Отложить открытие  системного журнала до первого вызова syslog() (выбор между немедленным или отложенным открытием способен повлиять на распределение файловых дескрипторов).

    LOG_NOWAIT

    Не ждать завершения процессов, которые могли быть порождены в ходе журналирования  сообщения.


    Эту опцию следует использовать в процессах, которые уведомляются о завершении потомков сигналом SIGCHLD.

    Аргумент  facility устанавливает подразумеваемое значение для источника тех сообщений, где он (источник) не указан явным образом.

    Функции openlog() и syslog() могут открывать (расходовать) файловые дескрипторы. Функция closelog() закроет их.

    Вообще говоря, вызывать openlog() до syslog() и setlogmask() не обязательно.

    К рассматриваемой прикладной области можно отнести служебную программу logger:

    logger цепочка_символов ...

    которая неким неспецифицированным образом сохраняет сообщение, содержащее заданные аргументы – цепочки символов. Предполагается, что со временем это сообщение прочитает системный администратор.

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

    logger $LOGNAME $(date) : не удалось прочитать файл report.txt

    Рассмотрим пример применения функций для работы с системным журналом (см. листинг 9.2).

    Листинг 9.2. Пример применения функций для работы с системным журналом. (html, txt)

    Результатом работы этой программы может быть строка, показанная на листинге 9.3.

    Подразумеваемая маска журналирования: ff

    Листинг 9.3. Возможные результаты применения функций для работы с системным журналом. (html, txt)



    LOG_NOTICE

    Ситуация, не являющаяся ошибочной, но, возможно, требующая специальных действий.

    LOG_INFO

    Информационное сообщение.

    LOG_DEBUG

    Отладочное сообщение.

    Из источников сообщений стандартизован только один (естественно, являющийся подразумеваемым) – пользовательский процесс (флаг LOG_USER). Зарезервированы флаги для системных источников, имена которых говорят сами за себя (LOG_KERN, LOG_MAIL, LOG_NEWS, LOG_UUCP, LOG_DAEMON, LOG_AUTH, LOG_CRON, LOG_LPR) и для абстрактных локальных сущностей (LOG_LOCAL0 – LOG_LOCAL7).

    Функция setlogmask() в качестве результата возвращает предыдущую и устанавливает новую маску журналируемых сообщений вызывающего процесса. В системный журнал будут помещаться только те сообщения, уровень серьезности которых присутствует в маске, заданной аргументом  maskpri. Для формирования этого аргумента по одному уровню серьезности стандартом предусмотрен макрос LOG_MASK (pri). Для задания маски, включающей несколько уровней, нужно взять побитное ИЛИ подобных выражений.

    Если значение maskpri равно нулю, текущая маска остается неизменной. Подразумевая маска является "полной", она специфицирует журналирование всех событий.

    Функция openlog() устанавливает значения атрибутов журналирования вызывающего процесса, влияющие на последующие обращения к syslog(). Аргумент  ident задает идентифицирующую цепочку, фигурирующую в заголовках всех сообщений. Аргумент  logopt специфицирует опции журналирования. Он формируется как побитное ИЛИ следующих флагов.

    LOG_PID

    Записывать вместе с сообщением  идентификатор процесса.

    LOG_CONS

    Выдавать сообщение на системную консоль, если его не удается поместить в журнал.

    LOG_NDELAY

    Немедленно открыть системный журнал (обычно открытие откладывается до записи первого сообщения).

    LOG_ODELAY

    Отложить открытие  системного журнала до первого вызова syslog() (выбор между немедленным или отложенным открытием способен повлиять на распределение файловых дескрипторов).

    LOG_NOWAIT

    Не ждать завершения процессов, которые могли быть порождены в ходе журналирования  сообщения.


    Эту опцию следует использовать в процессах, которые уведомляются о завершении потомков сигналом SIGCHLD.

    Аргумент  facility устанавливает подразумеваемое значение для источника тех сообщений, где он (источник) не указан явным образом.

    Функции openlog() и syslog() могут открывать (расходовать) файловые дескрипторы. Функция closelog() закроет их.

    Вообще говоря, вызывать openlog() до syslog() и setlogmask() не обязательно.

    К рассматриваемой прикладной области можно отнести служебную программу logger:

    logger цепочка_символов ...

    которая неким неспецифицированным образом сохраняет сообщение, содержащее заданные аргументы – цепочки символов. Предполагается, что со временем это сообщение прочитает системный администратор.

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

    logger $LOGNAME $(date) : не удалось прочитать файл report.txt

    Рассмотрим пример применения функций для работы с системным журналом (см. листинг 9.2).

    /* * * * * * * * * * * * * * * * * */ /* Пример использования функций */ /* для работы с системным журналом */ /* * * * * * * * * * * * * * * * * */

    #include #include

    int main (void) { int logmask; /* Прежняя маска журналирования */ /* Будем включать в журналируемые сообщения */ /* идентификатор процесса и выдавать их при */ /* возникновении проблем на системную консоль */ openlog ("Intuit syslog test", LOG_PID | LOG_CONS, LOG_USER);

    /* Пренебрежем предупреждениями и менее серьезными сообщениями */ logmask = setlogmask (LOG_MASK (LOG_EMERG) | LOG_MASK (LOG_ALERT) | LOG_MASK (LOG_CRIT) | LOG_MASK (LOG_ERR));

    printf ("Подразумеваемая маска журналирования: %x\n", logmask);

    /* Поместим сообщение в журнал */ syslog (LOG_ALERT | LOG_USER, "Как читать системный журнал?");

    /* Восстановим прежнюю маску журналирования */ (void) setlogmask (logmask);



    closelog ();

    return 0; }

    Листинг 9.2. Пример применения функций для работы с системным журналом.

    Результатом работы этой программы может быть строка, показанная на листинге 9.3.

    Подразумеваемая маска журналирования: ff

    Листинг 9.3. Возможные результаты применения функций для работы с системным журналом.

    Привлечь внимание системного администратора к возникшим проблемам можно не только помещая сообщения в системный журнал, но и выдавая их в стандартный протокол и/или на системную консоль. Для этого служит функция fmtmsg() (см. листинг 9.4).

    #include int fmtmsg (long classification, const char *label, int severity, const char *text, const char *action, const char *tag);

    Листинг 9.4. Описание функции fmtmsg().

    Функция fmtmsg() конструирует отформатированное сообщение, включая в него все аргументы, кроме первого.

    Аргумент  classification определяет источник и способ отображения сообщения. Он формируется как сумма констант, по одной из каждого класса. Классификация верхнего уровня определяет источник проблемы. В этот класс входят константы MM_HARD (аппаратура), MM_SOFT (программное обеспечение), MM_FIRM (программно-аппаратные средства). Сообщения, порожденные программным обеспечением, могут дополнительно классифицироваться константами MM_APPL (приложение), MM_UTIL (служебная программа), MM_OPSYS (операционная система). Проблемы классифицируются также по признаку нейтрализуемости – соответственно, MM_RECOVER и MM_NRECOV.

    Если сообщение предполагается направить в стандартный протокол, к значению аргумента  classification следует прибавить константу MM_PRINT; вывод на системную консоль задает константа MM_CONSOLE. Возможно одновременное указание обеих констант.

    Константа MM_NULLMC означает отсутствие классификационного компонента (естественно, ее значение равно нулю).

    Аргумент  label специфицирует первый из пяти компонентов выдаваемого сообщения. Он, как и classification, определяет источник сообщения, но делает это по-своему. Указуемая цепочка символов должна состоять из двух полей, разделенных двоеточием.


    Первое поле состоит не более чем из десяти байт, второе – из четырнадцати.

    Аргумент  severity характеризует серьезность проблемы. Стандартом POSIX-2001 предусмотрены следующие уровни серьезности.

    MM_HALT

    В приложении встретилась серьезная ошибка, его работа остановлена. В выводимую цепочку вставляется текст "HALT".

    MM_ERROR

    В работе приложения обнаружена ошибка. В выводимую цепочку вставляется текст "ERROR".

    MM_WARNING

    При работе приложения возникла необычная ситуация, возможно, являющаяся ошибочной и требующая внимания. В выводимую цепочку вставляется текст "WARNING".

    MM_INFO

    Информация о ситуации, не являющейся ошибочной. В выводимую цепочку вставляется текст "INFO".

    MM_NOSEV

    Данная константа обозначает отсутствие у сообщения  уровня серьезности.

    Аргумент  text в свободной форме описывает ситуацию, приведшую к генерации сообщения.

    Аргумент  action также в свободной форме описывает первый шаг по нейтрализации ошибки. Перед цепочкой, на которую указывает action, в сообщение вставляется префикс "TO FIX:".

    Аргумент  tag служит ссылкой на документацию по выявленной проблеме.

    На работу функции fmtmsg() влияет переменная окружения  MSGVERB, которая определяет, какие из пяти возможных компонентов сообщения будут выданы в стандартный протокол (на консоль всегда выдается полное сообщение). Значение этой переменной в общем случае состоит из пяти ключевых слов – label, severity, text, action, tag – разделенных двоеточиями. Если какие-то ключевые слова отсутствуют, соответствующие компоненты сообщения в стандартный протокол выданы не будут. Если переменная MSGVERB отсутствует в окружении, имеет пустое или некорректное значение, сообщение выдается целиком.

    Возможные результаты функции fmtmsg() устроены необычным образом. Константа MM_OK обозначает полный успех, MM_NOTOK – полную неудачу, MM_NOMSG – невозможность выдать сообщение в стандартный протокол, MM_NOCON – невозможность вывода на консоль.

    Приведем не очень серьезный пример применения функции fmtmsg() (см.


    листинг 9.5).

    #include #include

    int main (void) { if (fmtmsg (MM_SOFT + MM_OPSYS + MM_RECOVER + MM_PRINT + MM_CONSOLE, "POSIX:fmtmsg", MM_INFO, "Отсутствует функция fmtmsg()", "Установите функцию fmtmsg() или не пользуйтесь ею\n", "См. functions/fmtmsg.html") != MM_OK) { perror ("FMTMSG"); return (1); }

    return 0; }

    Листинг 9.5. Пример использования функции fmtmsg().

    В результате выполнения приведенной программы в стандартный протокол и на системную консоль будет выдано следующее сообщение (см. листинг 9.6).

    POSIX:fmtmsg: INFO: Отсутствует функция fmtmsg() TO FIX: Установите функцию fmtmsg() или не пользуйтесь ею См. functions/fmtmsg.html

    Листинг 9.6. Возможные результаты выполнения программы, использующей функцию fmtmsg().

    Читателю предлагается самостоятельно поэкспериментировать с этой программой, варьируя значение переменной окружения  MSGVERB.


    Манипулирование пользовательскими контекстами


    Согласно стандарту POSIX-2001, пользовательский контекст  потока управления включает содержимое машинных регистров, маску сигналов и текущий стек выполнения. Эти данные сосредоточены в структуре типа ucontext_t, содержащей по крайней мере следующие поля.
    ucontext_t *uc_link; /* Указатель на контекст, */ /* в котором будет возобновлено */ /* выполнение при выходе из */ /* данного контекста */ sigset_t uc_sigmask; /* Набор сигналов, блокированных */ /* в данном контексте */ stack_t uc_stack; /* Стек, используемый в данном */ /* контексте */ mcontext_t uc_mcontext; /* Машинно-зависимое представление */ /* сохраненного контекста */
    Стандарт POSIX-2001 предоставляет функции для опроса (getcontext()), модификации (makecontext()) и смены (setcontext() и swapcontext()) пользовательских контекстов (см. листинг 9.33).
    #include
    int getcontext (ucontext_t *ucp);
    void makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...);
    int setcontext (const ucontext_t *ucp);
    int swapcontext (ucontext_t *restrict oucp, const ucontext_t *restrict ucp);
    Листинг 9.33. Описание функций, манипулирующих пользовательскими контекстами потоков управления. (html, txt)
    Функция getcontext() – штатное средство получения исходного материала для манипулирования контекстами. Она запоминает текущий контекст вызывающего потока управления в структуре, на которую указывает аргумент  ucp. Ее нормальный результат равен нулю.
    Функция makecontext() модифицирует контекст, заданный аргументом  ucp. Когда (после вызовов setcontext() или swapcontext()) выполнение будет возобновлено в этом контексте, оно продолжится обращением к функции func() с передачей ей аргументов типа int в количестве argc, помещенных после argc при вызове makecontext(). Приложение должно позаботиться о том, чтобы модифицируемый контекст включал стек достаточного размера.
    Элемент uc_link структуры типа ucontext_t определяет контекст, в котором будет возобновлено выполнение после выхода из модифицированного функцией makecontext() контекста.
    Приложение должно позаботиться об инициализации этого элемента до обращения к makecontext().

    Функция setcontext() устанавливает пользовательский контекст вызывающего потока управления в соответствии с содержимым структуры, на которую указывает аргумент  ucp. После успешного вызова setcontext() возврата не происходит – выполнение возобновляется с точки, специфицированной новым контекстом, а именно: если этот контекст был сформирован в результате обращения к getcontext(), выполнение возобновляется возвратом из getcontext(); если контекст получен после makecontext(), вызывается функция func(), после возврата из которой поток управления продолжает работу во входном для makecontext() контексте.

    Если значением элемента uc_link структуры, на которую указывает аргумент  ucp, служит пустой указатель, то данный контекст соответствует функции main(), после выхода из которой выполнение потока управления завершается.

    Функция swapcontext() производит перестановку  пользовательских контекстов. Текущий контекст сохраняется в структуре, на которую указывает аргумент  oucp, а новый контекст формируется по значению аргумента  ucp.

    Обратим внимание на следующую тонкость. Когда вызывается функция обработки сигнала, текущий пользовательский контекст запоминается, а для выполнения обработчика формируется новый контекст. Стандарт POSIX-2001 не гарантирует, что после нелокального перехода из функции обработки сигнала посредством longjmp() будет аккуратно восстановлен контекст соответствующего вызова setjmp(). В подобных ситуациях рекомендуется применять функции siglongjmp() или setcontext().

    В качестве примера применения функций, манипулирующих пользовательскими контекстами, рассмотрим модифицированную программу из текста стандарта POSIX-2001 (см. листинг 9.34).

    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение функций, */ /* манипулирующих пользовательскими контекстами*/ /* * * * * * * * * * * * * * * * * * * * * * * */

    #include #include



    /* Размер стеков в формируемых пользовательских контекстах */ #define STACK_SIZE 4096

    /* Пространство для стеков */ static char st1 [STACK_SIZE]; static char st2 [STACK_SIZE];

    static ucontext_t ctx [3];

    static void f1 (int arg) { printf ("Вызвана функция %s с аргументом %d\n", "f1", arg); if (swapcontext (&ctx [1], &ctx [2]) != 0) { perror ("SWAPCONTEXT-1"); } printf ("Выход из функции %s\n", "f1"); }

    static void f2 (int arg1, int arg2) { printf ("Вызвана функция %s с аргументами %d, %d\n", "f2", arg1, arg2); if (swapcontext (&ctx [2], &ctx [1]) != 0) { perror ("SWAPCONTEXT-2"); } printf ("Выход из функции %s\n", "f2"); }

    int main (void) { (void) getcontext (&ctx [1]);

    printf ("Параметры первоначального контекста:\n" "адрес стека %p, размер стека %d\n", ctx[1].uc_stack.ss_sp, ctx[1].uc_stack.ss_size);

    /* В соответствии с общими рекомендациями */ /* позаботимся о стеке для модифицируемых контекстов */ ctx[1].uc_stack.ss_sp = st1; ctx[1].uc_stack.ss_size = sizeof (st1); ctx[1].uc_link = &ctx [0]; makecontext (&ctx [1], (void (*) (void)) f1, 1, 2);

    (void) getcontext (&ctx [2]); ctx[2].uc_stack.ss_sp = st2; ctx[2].uc_stack.ss_size = sizeof (st2); ctx[2].uc_link = &ctx [1]; makecontext (&ctx [2], (void (*) (void)) f2, 2, 3, 4); if (swapcontext (&ctx [0], &ctx [2]) != 0) { perror ("SWAPCONTEXT-3"); return (1); }

    return 0; }

    Листинг 9.34. Пример применения функций, манипулирующих пользовательскими контекстами.

    Обратим внимание на резервирование пространства под стек перед обращением к функции makecontext(), а также на связывание нескольких контекстов в список посредством поля uc_link.

    Результаты выполнения приведенной программы показаны на листинге 9.35.

    Параметры первоначального контекста: адрес стека (nil), размер стека 0 Вызвана функция f2 с аргументами 3, 4 Вызвана функция f1 с аргументом 2 Выход из функции f2 Выход из функции f1

    Листинг 9.35. Возможные результаты выполнения программы, применяющей функции манипулирования пользовательскими контекстами.


    Обход файловой иерархии


    Обход файловой иерархии – типовая задача, для решения которой стандартом POSIX-2001 предлагаются две сходные функции – ftw() и nftw() (см. листинг 9.46).
    #include
    int ftw (const char *path, int (*fn) (const char *, const struct stat *, int), int depth);
    int nftw (const char *path, int (*fn) (const char *, const struct stat *, int, struct FTW *), int depth, int flags);
    Листинг 9.46. Описание функций обхода файловой иерархии. (html, txt)
    Функция ftw() рекурсивно обходит иерархию каталогов, имеющую своим корнем каталог с маршрутным именем, на которое указывает аргумент  path. Для каждого объекта иерархии ftw() вызывает функцию fn(), передавая ей три аргумента: указатель на цепочку символов, ограниченную нулевым байтом и содержащую имя объекта; указатель на структуру типа stat, содержащую информацию об объекте; тип объекта, представленный целым числом.
    Возможны следующие значения типа объекта.
    FTW_D
    Каталог.
    FTW_DNR
    Каталог, недоступный на чтение.
    FTW_F
    Обычный файл.
    FTW_SL
    Символьная ссылка.
    FTW_NS
    Объект, отличный от символьной ссылки, для которого stat() не может выполниться успешно.
    Если тип объекта есть FTW_DNR, элементы этого каталога не просматриваются. Если тип есть FTW_NS, то структура типа stat будет содержать неопределенные значения. Примером объекта, который вызовет передачу функции fn() типа FTW_NS, является файл в каталоге, доступном для чтения, но не для поиска.
    Функция ftw() обрабатывает каталог перед обработкой его элементов.
    Функция ftw() использует не более одного файлового дескриптора на обход каждого уровня иерархии. Аргумент  depth ограничивает количество используемых таким образом дескрипторов; его значение должно принадлежать диапазону [1, OPEN_MAX].
    Обход завершится тогда, когда будет обойдена вся иерархия, или функция fn() вернет ненулевое значение, или возникнет ошибка, отличная от EACCES, при работе самой функции ftw() (например, ошибка ввода/вывода). Если дерево обойдено полностью, ftw() в качестве результата возвращает нуль.
    Если fn() вернет ненулевое значение, то ftw() прекратит обход и выдаст это значение. Если будет обнаружена ошибка при работе самой функции ftw(), то она вернет -1 и соответствующим образом установит значение переменной errno.

    Обратим внимание на следующее (впрочем, довольно очевидное) обстоятельство. Функция ftw() во время своей работы временно резервирует некоторые ресурсы (оперативную память, файловые дескрипторы). Если прервать ее нештатным образом (например, посредством нелокального перехода из функции fn() или обработчика сигнала), эти ресурсы останутся неосвобожденными. Рекомендуемый способ обработки прерываний заключается в том, чтобы зафиксировать факт получения прерывания и при очередном вызове fn() заставить ее вернуть ненулевое значение.

    Функция nftw() аналогична ftw(), однако и у нее самой, и у вызываемой ею функции fn() имеется по одному дополнительному аргументу. Дополнительный аргумент  flags управляет работой функции nftw(). Его значение формируется как побитное ИЛИ следующих флагов.

    FTW_CHDIR

    Делать текущим просматриваемый каталог. Если этот флаг не установлен, функция nftw() не изменит текущий каталог.

    FTW_DEPTH

    Обход вглубь: обрабатывать каталог после обработки его элементов.

    FTW_MOUNT

    Обрабатывать только файлы из той же файловой системы, что и path.

    FTW_PHYS

    Осуществлять физический обход без разрешения символьных ссылок.

    Третий аргумент функции fn(), по сравнению с ftw() может принимать следующие дополнительные значения.

    FTW_DP

    Каталог, элементы которого уже обработаны (данное условие может быть истинным только при установленном флаге FTW_DEPTH).

    FTW_SLN

    Символьная ссылка, именующая отсутствующий файл.

    Дополнительный, четвертый аргумент функции fn() является указателем на структуру типа FTW. Согласно стандарту, она должна содержать по крайней мере следующие поля.

    int base; /* Смещение простого имени файла */ /* от начала маршрутного имени, */ /* переданного fn() в качестве */ /* первого аргумента */ int level; /* Уровень текущего объекта */ /* относительно корня иерархии */ /* (у самого корня нулевой уровень) */



    В качестве примера обхода файловой иерархии рассмотрим программу, подсчитывающую суммарный размер и высоту дерева файлов (см. листинг 9.47);

    Листинг 9.47. Пример программы, осуществляющей обход файловой иерархии. (html, txt)

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

    Пусть в каталоге  /tmp существует непустой подкаталог gaga со следующим режимом доступа:

    drw-r--r-- 2 galat sys 4096 May 7 17:26 gaga

    Тогда результаты запуска приведенной программы с аргументом  /tmp могут выглядеть так, как показано на листинге 9.48.

    Листинг 9.48. Возможные результаты выполнения программы, осуществляющей обход файловой иерархии. (html, txt)


    Поиск и сортировка


    Поиск и сортировка данных, располагающихся в оперативной памяти, – типичная и очень важная часть многих приложений, качество реализации которой во многом определяет технические характеристики программной системы в целом.
    Стандарт POSIX-2001 предлагает несколько способов поиска в таблицах разных форматов.
    Бинарный поиск (см. листинг 9.13) является самым быстрым среди методов, основанных на сравнении ключей. Он применим к предварительно отсортированным одномерным массивам.
    #include void *bsearch (const void *key, const void *base, size_t nel, size_t width, int (*compar) (const void *, const void *));
    Листинг 9.13. Описание функции бинарного поиска bsearch(). (html, txt)
    Функция bsearch() предназначена для выполнения бинарного поиска в соответствии с алгоритмом, описанным Д. Кнутом (см. [4] в дополнительной литературе, пункт 6.2.1, алгоритм B).
    Функция bsearch() возвращает указатель внутрь массива на искомые данные или NULL в случае неудачи поиска. Предварительно массив должен быть отсортирован в возрастающем порядке согласно предоставленной функции сравнения  compar().
    Аргумент  key указывает на объект данных, разыскиваемый в массиве (ключ  поиска); base указывает на начало (первый элемент) массива; nel задает количество элементов в массиве; width специфицирует размер элемента в массиве.
    Аргумент compar() – это функция сравнения, аргументами которой служат два указателя на сравниваемые объекты – ключ и элемент массива. В соответствии с тем, какое целое число она возвращает: меньшее нуля, равное нулю или большее нуля, ключ считается меньшим, равным или большим по отношению к элементу массива.
    Для сортировки массивов целесообразно пользоваться функцией qsort() (см. листинг 9.14), реализующей метод быстрой сортировки (называемый также методом обменной сортировки с разделением, см. [4] в дополнительной литературе, пункт 5.2.2, алгоритм Q). Ее аргументы имеют тот же смысл, что и одноименные аргументы функции bsearch().
    #include void qsort (void *base, size_t nel, size_t width, int (*compar) (const void *, const void *));

    Листинг 9.14. Описание функции быстрой сортировки qsort(). (html, txt)

    Рассмотрим пример последовательного применения функций qsort() и bsearch() (см. листинг 9.15). Здесь в роли элементов массива выступают указатели на цепочки символов, которые размещены в области StringSpace; тот же тип имеет и ключ  поиска. Критерием сравнения служит алфавитная упорядоченность указуемых цепочек.

    Листинг 9.15. Пример применения функций быстрой сортировки и бинарного поиска. (html, txt)

    Отметим, что при сортировке будут перемещаться элементы массива PtsTable [] – указатели на цепочки символов, но, конечно, не сами цепочки.

    Если на компьютере, которым в данный момент пользуется автор, измерить время выполнения приведенной программы посредством утилиты  time с опцией  -p, результаты будут выглядеть следующим образом (см. листинг 9.16).

    Листинг 9.16. Возможные результаты выполнения программы, применяющей функции быстрой сортировки и бинарного поиска. (html, txt)

    Читателю предлагается сравнить эти результаты с экспериментально полученными собственными (и с гордостью убедиться, что его компьютер гораздо мощнее), а также оценить зависимость длительности быстрой сортировки и бинарного поиска от размера массива (и подтвердить теоретические оценки из [4]).

    Стандартом POSIX-2001, помимо бинарного, предусматривается еще несколько способов поиска – последовательный, с помощью хэш-таблиц и бинарных деревьев. Соответствующие описания сосредоточены в заголовочном файле .

    В идейном плане самым простым является последовательный поиск. Он может производиться с вставкой (функция lsearch()) или без таковой (lfind()) (см. листинг 9.17).

    #include

    void *lsearch (const void *key, void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));

    void *lfind (const void *key, const void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));

    Листинг 9.17. Описание функций последовательного поиска. (html, txt)

    Функции, реализующие последовательный поиск, по способу вызова напоминают bsearch(), только аргумент  nelp является указателем на число элементов в массиве, которое функция lsearch() может увеличить на единицу (если искомого элемента в массиве не было, его добавляют в конец).


    Разумеется, для последовательного поиска не требуется, чтобы массив был предварительно отсортирован. Упрощены и требования к функции сравнения  compar(): в случае неравенства ее результат должен быть отличен от нуля.

    В качестве иллюстрации применения функций последовательного поиска рассмотрим программу, генерирующую случайные цепочки символов до первого повторения (см. листинг 9.18).

    Листинг 9.18. Пример применения последовательного поиска с вставкой. (html, txt)

    Указатели на порождаемые случайные цепочки помещаются в массив PtsTable [] функцией lsearch(). В этой связи обратим внимание на нескольку вычурную организацию цикла for в функции main(). По сути здесь две переменные цикла – pss и nelst. Первая продвигается стандартным образом, в заголовке цикла, но проверяется на выход за допустимые границы в его теле; вторая, напротив, стандартно проверяется, но нестандартно продвигается (в результате вызова lsearch()).

    Возможные результаты выполнения этой программы показаны на листинге 9.19.

    Листинг 9.19. Возможные результаты выполнения программы, применяющей функцию последовательного поиска с вставкой. (html, txt)

    При экспериментах с приведенной программой следует соблюдать определенную осторожность, поскольку время ее работы квадратично зависит от величины TAB_SIZE.



    Листинг 9.16. Возможные результаты выполнения программы, применяющей функции быстрой сортировки и бинарного поиска.

    Читателю предлагается сравнить эти результаты с экспериментально полученными собственными (и с гордостью убедиться, что его компьютер гораздо мощнее), а также оценить зависимость длительности быстрой сортировки и бинарного поиска от размера массива (и подтвердить теоретические оценки из [4]).

    Стандартом POSIX-2001, помимо бинарного, предусматривается еще несколько способов поиска – последовательный, с помощью хэш-таблиц и бинарных деревьев. Соответствующие описания сосредоточены в заголовочном файле .

    В идейном плане самым простым является последовательный поиск. Он может производиться с вставкой (функция lsearch()) или без таковой (lfind()) (см. листинг 9.17).

    #include

    void *lsearch (const void *key, void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));

    void *lfind (const void *key, const void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));

    Листинг 9.17. Описание функций последовательного поиска.

    Функции, реализующие последовательный поиск, по способу вызова напоминают bsearch(), только аргумент  nelp является указателем на число элементов в массиве, которое функция lsearch() может увеличить на единицу (если искомого элемента в массиве не было, его добавляют в конец). Разумеется, для последовательного поиска не требуется, чтобы массив был предварительно отсортирован. Упрощены и требования к функции сравнения  compar(): в случае неравенства ее результат должен быть отличен от нуля.

    В качестве иллюстрации применения функций последовательного поиска рассмотрим программу, генерирующую случайные цепочки символов до первого повторения (см. листинг 9.18).

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа генерирует случайные цепочки символов до первого */ /* повторения (или до исчерпания отведенного пространства). */ /* Для выявления повторения применяется */ /* последовательный поиск с вставкой */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */



    #include #include #include #include

    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 200000

    /* Число элементов в таблице указателей на цепочки символов */ #define TAB_SIZE 20000

    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 7

    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];

    /* Массив указателей на цепочки символов */ static char *PtsTable [TAB_SIZE];

    /* * * * * * * * * * */ /* Функция сравнения */ /* * * * * * * * * * */ static int str_compar (const void *pkey, const void *pelem) { return strcoll (*((char **) pkey), *((char **) pelem)); }

    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Поиск первого повтора в последовательности */ /* случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { char *pss; /* Указатель на свободное место */ /* в области StringSpace */ char **res; /* Результат поиска с вставкой */ size_t nelst; /* Число занятых элементов */ /* в массиве указателей */ size_t onelst; /* Число элементов в массиве */ /* до поиска с вставкой */ for (pss = StringSpace, nelst = 0; nelst < TAB_SIZE; pss += STRING_SIZE) { if (((pss + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (1); }

    str_rnd (pss, STRING_SIZE);

    onelst = nelst; res = (char **) lsearch (&pss, PtsTable, &nelst, sizeof (PtsTable [0]), str_compar); if (onelst == nelst) { /* Искомая цепочка уже была порождена ранее */ printf ("Для случайных цепочек длины %d\n" "первое совпадение получено на цепочке " "%s\n", STRING_SIZE, pss); printf ("Первый раз цепочка была порождена " "под номером %d,\n" "второй – под номером " "%d\n", (res – PtsTable) / sizeof (PtsTable [0]) + 1, nelst + 1); return 0; } } /* for */



    printf ("Из %d случайных цепочек длины %d все " "оказались уникальными\n", TAB_SIZE, STRING_SIZE);

    return 0; }

    Листинг 9.18. Пример применения последовательного поиска с вставкой.

    Указатели на порождаемые случайные цепочки помещаются в массив PtsTable [] функцией lsearch(). В этой связи обратим внимание на нескольку вычурную организацию цикла for в функции main(). По сути здесь две переменные цикла – pss и nelst. Первая продвигается стандартным образом, в заголовке цикла, но проверяется на выход за допустимые границы в его теле; вторая, напротив, стандартно проверяется, но нестандартно продвигается (в результате вызова lsearch()).

    Возможные результаты выполнения этой программы показаны на листинге 9.19.

    Для случайных цепочек длины 7 первое совпадение получено на цепочке GLPCSX Первый раз цепочка была порождена под номером 2548, второй - под номером 12530 real 34.80 user 13.70 sys 0.03

    Листинг 9.19. Возможные результаты выполнения программы, применяющей функцию последовательного поиска с вставкой.

    При экспериментах с приведенной программой следует соблюдать определенную осторожность, поскольку время ее работы квадратично зависит от величины TAB_SIZE.

    Управление хэш-таблицами  поиска производится в соответствии с алгоритмом, описанным Д. Кнутом (см. [4] в дополнительной литературе, раздел 6.4, алгоритм D). Предоставляются функции для создания (hcreate()) и ликвидации (hdestroy()) хэш-таблиц, а также для выполнения в них поиска (hsearch()), быть может, с вставкой (см. листинг 9.20). Сразу отметим, что в каждый момент времени может быть активна только одна хэш-таблица.

    #include

    int hcreate (size_t nel);

    void hdestroy (void);

    ENTRY *hsearch (ENTRY item, ACTION action);

    Пример 9.20. Описание функций управления хэш-таблицами поиска.

    Предполагается, что элементы таблицы поиска имеют тип ENTRY, определенный так, как показано на листинге 9.21.

    typedef struct entry { char *key; /* Ключ поиска */ void *data; /* Дополнительные данные, */ /* ассоциированные с ключом */ } ENTRY;



    Листинг 9.21. Описание типа ENTRY.

    Функция hcreate() резервирует достаточное количество памяти для таблицы и должна вызываться перед обращением к hsearch(). Значением аргумента  nel является ожидаемое максимальное количество элементов в таблице. Это число можно взять с запасом, чтобы уменьшить среднее время поиска.

    Нормальный для hcreate() результат отличен от нуля.

    Функция hdestroy() ликвидирует таблицу поиска. За вызовом этой функции может следовать новое обращение к функции создания таблицы hcreate().

    Функция hsearch() возвращает указатель внутрь таблицы на искомые данные. Аргумент  item – это структура типа ENTRY, содержащая два указателя: item.key указывает на сравниваемый ключ (функцией сравнения при поиске в хэш-таблице служит strcmp()), а item.data – на любые дополнительные данные, ассоциированные с этим ключом.

    Аргумент  action имеет тип ACTION, определенный так, как показано на листинге 9.22. Он задает способ действий в случае неудачного поиска: значение ENTER предписывает производить поиск с вставкой, то есть в случае неудачи искомый элемент следует поместить в таблицу; значение FIND предписывает в случае неудачи вернуть пустой указатель NULL. Пустой указатель возвращается и тогда, когда значение аргумента  action равно ENTER, и таблица заполнена.

    enum { FIND, ENTER } ACTION;

    Листинг 9.22. Определение типа ACTION.

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

    /* * * * * * * * * * * * * * * * * * * * */ /* Программа помещает в хэш-таблицу */ /* заданное число элементов с указателями*/ /* на случайные цепочки символов, */ /* а затем выполняет в этой таблице */ /* поиск новых случайных цепочек, */ /* пока он не окажется успешным */ /* * * * * * * * * * * * * * * * * * * * */

    #include #include #include



    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 10000000

    /* Число элементов, помещаемых в хэш-таблицу */ #define TAB_NEL 1000000

    /* Размер хэш-таблицы */ #define TAB_SIZE (2 * TAB_NEL)

    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 10

    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];

    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Заполнение хэш-таблицы, поиск повтора в */ /* последовательности случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { ENTRY item; /* Искомый элемент */ char sbuf [STRING_SIZE]; /* Буфер для формирования */ /* случайных цепочек */ double ntr; /* Номер найденной */ /* случайной цепочки */ size_t i;

    if (hcreate (TAB_SIZE) == 0) { fprintf (stderr, "%s: Не удалось создать хэш-таблицу" " размера %d\n", argv [0], TAB_SIZE); return (1); }

    item.data = NULL; /* Нет ассоциированных данных */ /* Заполним таблицу */ for (item.key = StringSpace, i = 0; i < TAB_NEL; item.key += STRING_SIZE, i++) { if (((item.key + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (2); }

    str_rnd (item.key, STRING_SIZE);

    if (hsearch (item, ENTER) == NULL) { fprintf (stderr, "%s: Переполнена хэш-таблица\n", argv [0]); return (3); } } /* for */

    /* Будем формировать и искать новые случайные цепочки */ item.key = sbuf; ntr = 0; do { str_rnd (item.key, STRING_SIZE); ntr++; } while (hsearch (item, FIND) == NULL); printf ("Удалось найти %g-ю по счету случайную цепочку %s\n", ntr, item.key);



    hdestroy ();

    return 0; }

    Листинг 9.23. Пример применения функций, управляющих хэш-таблицами поиска.

    Обратим внимание на то, что размер хэш-таблицы выбран вдвое большим по сравнению с реально используемым числом элементов; это уменьшает число коллизий (случаев совпадения хэш-кодов разных ключей) и ускоряет их разрешение. При небольшом числе коллизий время поиска одного элемента в хэш-таблице ограничено константой (не зависит от количества элементов в таблице). Это значит, что время работы приведенной программы должно быть пропорционально размеру таблицы, то есть по порядку величины оно меньше, чем для рассмотренной выше комбинации быстрой сортировки и бинарного поиска (убирается множитель, равный логарифму числа элементов). Сделанный вывод подтверждается результатами измерения времени работы программы (см. листинг 9.24).

    Удалось найти 168221-ю по счету случайную цепочку VBBDZTNMZ real 9.61 user 9.36 sys 0.25

    Листинг 9.24. Возможные результаты выполнения программы, применяющей функции управления хэш-таблицами поиска.

    Читателю предлагается измерить время работы этой программы на своем компьютере, сравнить его с аналогичным временем для быстрой сортировки и бинарного поиска, а также оценить зависимость среднего времени поиска от размера таблицы (и подтвердить теоретические оценки из [4]).

    Бинарные деревья  поиска – замечательное средство, позволяющее эффективно (за время, логарифмически зависящее от числа элементов) осуществлять операций поиска с вставкой (функция tsearch()) и без таковой (tfind()), удаления (tdelete()) и, кроме того, выполнять обход всех элементов (функция twalk()) (см. листинг 9.25). Функции реализуют алгоритмы T и D, описанные в пункте 6.2.2 книги Д. Кнута [4].

    #include

    void *tsearch (const void *key, void **rootp, int (*compar) (const void *, const void *));

    void *tfind (const void *key, void *const *rootp, int (*compar) (const void *, const void *));

    void *tdelete (const void *restrict key, void **restrict rootp, int (*compar) (const void *, const void *));



    void twalk (const void *root, void (*action) (const void *, VISIT, int));

    Листинг 9.25. Описание функций управления бинарными деревьями поиска.

    Функция tsearch() используется для построения дерева и доступа к нему. Аргумент  key является указателем на искомые данные (ключ). Если в дереве есть узел, первым полем которого является ссылка на данные, равные искомым, то результатом функции служит указатель на этот узел. В противном случае в дерево вставляется вновь созданный узел со ссылкой на искомые данные и возвращается указатель на него. Отметим, что копируются только указатели, поэтому прикладная программа сама должна позаботиться о хранении данных.

    Аргумент  rootp указывает на переменную, которая является указателем на корень дерева. Ее значение, равное NULL, специфицирует пустое дерево; в этом случае в результате выполнения функции tsearch() переменная устанавливается равной указателю на единственный узел – корень вновь созданного дерева.

    Подобно функции tsearch(), функция tfind() осуществляет поиск по ключу, возвращая в случае успеха указатель на соответствующий узел. Однако в случае неудачного поиска функция tfind() возвращает пустой указатель NULL.

    Функция tdelete(), как и tfind(), сначала производит поиск, но не останавливается на этом, а удаляет найденный узел из бинарного дерева. Результатом tdelete() служит указатель на вышележащий по сравнению с удаляемым узел или NULL, если поиск оказался неудачным.

    Функция twalk() осуществляет обход  бинарного дерева в глубину, слева направо (дерево строится функцией tsearch() так, что, в соответствии с функцией сравнения  compar(), все узлы левого поддерева предшествуют его корню, который, в свою очередь, предшествует узлам правого поддерева). Аргумент  root указывает на корень обрабатываемого (под)дерева (любой узел может быть использован в качестве корня для обхода соответствующего поддерева).

    Очевидно, в процессе обхода все неконцевые узлы посещаются трижды (при спуске в левое поддерево, при переходе из левого поддерева в правое и при возвращении из правого поддерева), а концевые (листья) – один раз.


    Эти посещения обозначаются величинами типа VISIT с исключительно неудачными именами (см. листинг 9.26).

    enum { preorder, postorder, endorder, leaf } VISIT;

    Листинг 9.26. Определение типа VISIT.

    Имена неудачны, потому что они совпадают с названиями разных способов обхода деревьев (см., например, [3] в дополнительной литературе, пункт 2.3.1). В частности, порядок обхода, реализуемый функцией twalk(), называется в [3] прямым (по-английски – preorder). Остается надеяться, что читатель не даст себя запутать и уверенно скажет, что в данном контексте postorder – это второе посещение неконцевого узла  бинарного дерева  поиска, а не какой-то там обратный порядок обхода.

    Аргумент  action – это функция, которую twalk() вызывает при попадании в узел во время обхода. Она, в свою очередь, имеет три аргумента. Первым из них служит адрес текущего узла. Структура, на которую указывает этот аргумент, стандартом не специфицируется; оговаривается только, что указатель на узел можно привести к типу "указатель на указатель на хранимые данные" (то есть на данные, ассоциированные с узлом, и содержащие, в частности, ключ  поиска). Второй аргумент вызываемой функции – это значение определенного выше типа VISIT. Напомним еще раз, что оно показывает, который раз (первый, второй или третий) осуществляется доступ к неконцевому узлу во время обхода дерева в глубину и слева направо или свидетельствует, что узел является концевым (листом). Третий аргумент – это уровень узла в дереве (в предположении, что корень имеет уровень 0).

    Читатель наверняка уже догадался, что далее последует пример программы, строящей бинарное дерево  поиска для случайных цепочек символов (см. листинг 9.27). Впрочем, приведенная программа делает и еще кое-что: подсчитывает число узлов и высоту дерева, распечатывает несколько первых по алфавиту цепочек из числа помещенных в дерево и, конечно, генерирует новые случайные цепочки, пока их поиск в дереве не окажется успешным. Для разнообразия узел с найденной цепочкой удаляется.



    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа осуществляет поиск с вставкой в бинарном */ /* дереве, помещая в него заданное число элементов с */ /* указателями на случайные цепочки символов. */ /* Затем подсчитывается число узлов и высота дерева. */ /* Следующим действием является распечатка */

    /* нескольких первых цепочек. */ /* После этого выполняется поиск новых случайных цепочек,*/ /* пока он не окажется успешным. */ /* Найденный элемент удаляется из дерева */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include #include #include #include

    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 10000000

    /* Число элементов, помещаемых в дерево */ #define TREE_NEL 1000000

    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 10

    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];

    /* Число узлов в бинарном дереве поиска */ static size_t node_count;

    /* Максимальный уровень узла дерева */ static int max_level; /* Буфер для функций setjmp и longjmp */ static jmp_buf buf_env;

    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, которая вызывается при обходе дерева */ /* с целью подсчета числа узлов и высоты */ /* * * * * * * * * * * * * * * * * * * * * * * * */ static void tw_nnh (const void *pnode, VISIT nv, int level) { if (nv == preorder) { node_count++; } else if (nv == leaf) { node_count++; if (level > max_level) { max_level = level; } } }

    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, которая вызывается при обходе дерева */ /* с целью распечатки нескольких первых */ /* по алфавиту цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ static void tw_pfs (const void *pnode, VISIT nv, int level) { if (node_count <= 0) { /* Нужное число цепочек выведено,*/ /* прерываем обход дерева */ longjmp (buf_env, 1); } if ((nv == postorder) || (nv == leaf)) { printf ("%s\n", *((char **) pnode)); node_count--; } }



    /* * * * * * * * * * * * * * * * * * */ /* Создание бинарного дерева поиска, */ /* определение его характеристик, */ /* поиск повтора в последовательности*/ /* случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { void *root; /* Указатель на корень дерева */ char *key; /* Указатель на искомую */ /* цепочку символов */ char sbuf [STRING_SIZE]; /* Буфер для формирования */ /* случайных цепочек */ double ntr; /* Номер найденной случайной */ /* цепочки */ size_t i;

    /* Создадим бинарное дерево поиска */ root = NULL; for (key = StringSpace, i = 0; i < TREE_NEL; key += STRING_SIZE, i++) { if (((key + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (1); }

    str_rnd (key, STRING_SIZE);

    if (tsearch (key, &root, (int (*) (const void *, const void *)) strcoll) == NULL) { fprintf (stderr, "%s: Поиск с вставкой в бинарное" " дерево " "завершился неудачей\n", argv [0]); return (2); } } /* for */

    /* Подсчитаем число узлов и высоту созданного дерева */ node_count = 0; max_level = 0; twalk (root, tw_nnh); printf ("В дереве оказалось %d узлов\n", node_count); printf ("Его высота равна %d\n", max_level);

    /* Распечатаем несколько первых (по алфавиту) цепочек, */ /* помещенных в созданное дерево */ node_count = 10; printf ("Первые %d по алфавиту цепочек в дереве:\n", node_count); if (setjmp (buf_env) == 0) { twalk (root, tw_pfs); }

    /* Будем формировать и искать новые случайные цепочки */ ntr = 0; do { str_rnd (sbuf, STRING_SIZE); ntr++; } while (tdelete (sbuf, &root, (int (*) (const void *, const void *)) strcoll) == NULL); printf ("Удалось найти и удалить из дерева %g-ю по счету " "случайную цепочку %s\n", ntr, sbuf);

    return 0; }

    Листинг 9.27. Пример применения функций управления бинарными деревьями поиска.

    Отметим гибкость бинарных деревьев  поиска как структуры данных.


    Не требуется заранее резервировать определенное пространство – деревья растут не более, чем это необходимо. При добавлении и удалении узлов деревья динамически перестраиваются без специального переупорядочения или массовых передвижек.

    Обратим внимание на частичный обход дерева при распечатке нескольких первых по алфавиту цепочек, реализуемый с использованием механизма нелокальных переходов. Сама по себе функция twalk() ориентирована, разумеется, на полный обход (также представленный в программе).

    Возможные результаты выполнения приведенной программы показаны на листинге 9.28.

    В дереве оказалось 1000000 узлов Его высота равна 25 Первые 10 по алфавиту цепочек в дереве: AAAATNRAS AAACHCCLB AAACSJQBP AAADLHFAZ AAAFWLRXM AAAFXGQEC AAAGBMHHA AAAGFAXFI AAAHKLCWW AAAHLOSVQ Удалось найти и удалить из дерева 168221-ю по счету случайную цепочку VBBDZTNMZ real 20.24 user 20.25 sys 0.15

    Листинг 9.28. Возможные результаты выполнения программы, применяющей функции управления бинарными деревьями поиска.

    Отметим, что среди первых 1000000 случайных цепочек символов длины 10 повторов не оказалось. Дерево  поиска получилось весьма сбалансированным, с высотой, лишь немногим большей двоичного логарифма от числа узлов. Приведенные данные о времени выполнения подтверждают высокую эффективность бинарных деревьев как инструмента поиска.

    Полноты ради упомянем еще о двух функциях, описанных в заголовочном файле : insque() и remque() (см. листинг 9.29). Они предназначены для выполнения операций над очередями, реализованными как двусвязанные списки.

    #include

    void insque (void *element, void *pred);

    void remque (void *element);

    Листинг 9.29. Описание функций, выполняющих операции над очередями.

    Функция insque() осуществляет вставку элемента, на который указывает аргумент  element, после элемента pred. В качестве элемента должна выступать структура, первые два поля которой являются указателями на структуры того же типа – соответственно, следующий и предыдущий элементы очереди.


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

    Функция remque() удаляет заданный элемент из очереди.

    Очередь может быть линейной или циклической. В первом случае она ограничена пустыми указателями, во втором крайние указатели должны быть зациклены. Вставка первого элемента в линейную очередь осуществляется вызовом insque (&element, NULL); при инициализации циклической очереди о ссылках должно позаботиться приложение (см. листинг 9.30).

    #include

    . . . struct qelem { struct qelem *q_forw; struct qelem *q_back; char *data; . . . };

    struct qelem element1; struct qelem element2;

    . . . element1.q_forw = &element1; element1.q_back = &element1;

    insque (&element2, &element1);

    . . .

    Листинг 9.30. Пример инициализации циклической очереди и вставки в нее второго элемента.

    Трудно сказать, есть ли смысл в стандартизации функций, исходный текст которых занимает пару строк...

    Из тех же соображений полноты вернемся к теме сортировки и упомянем служебную программу tsort:

    tsort [файл]

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

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

    Например, если применить утилиту  tsort к файлу, содержащему строки, показанные на листинге 9.31, то можно получить результат, приведенный на листинге 9.32.

    a b c d d e f g e f h h

    Листинг 9.31. Пример исходных данных для служебной программы tsort.

    a c h b d e f g

    Листинг 9.32. Возможный результат применения служебной программы tsort.


    int priority, const char


    #include
    void syslog ( int priority, const char *message, ... /* аргументы */);
    int setlogmask (int maskpri); void openlog (const char *ident, int logopt, int facility);
    void closelog (void);
    Листинг 9.1. Описание функций для работы с системным журналом.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * */ /* Пример использования функций */ /* для работы с системным журналом */ /* * * * * * * * * * * * * * * * * */
    #include #include
    int main (void) { int logmask; /* Прежняя маска журналирования */ /* Будем включать в журналируемые сообщения */ /* идентификатор процесса и выдавать их при */ /* возникновении проблем на системную консоль */ openlog ("Intuit syslog test", LOG_PID | LOG_CONS, LOG_USER);
    /* Пренебрежем предупреждениями и менее серьезными сообщениями */ logmask = setlogmask (LOG_MASK (LOG_EMERG) | LOG_MASK (LOG_ALERT) | LOG_MASK (LOG_CRIT) | LOG_MASK (LOG_ERR));
    printf ("Подразумеваемая маска журналирования: %x\n", logmask);
    /* Поместим сообщение в журнал */ syslog (LOG_ALERT | LOG_USER, "Как читать системный журнал?");
    /* Восстановим прежнюю маску журналирования */ (void) setlogmask (logmask);
    closelog ();
    return 0; }
    Листинг 9.2. Пример применения функций для работы с системным журналом.
    Закрыть окно




    Подразумеваемая маска журналирования: ff
    Листинг 9.3. Возможные результаты применения функций для работы с системным журналом.
    Закрыть окно




    #include int fmtmsg ( long classification, const char *label, int severity, const char *text, const char *action, const char *tag);
    Листинг 9.4. Описание функции fmtmsg().
    Закрыть окно




    #include #include
    int main (void) { if (fmtmsg (MM_SOFT + MM_OPSYS + MM_RECOVER + MM_PRINT + MM_CONSOLE, "POSIX:fmtmsg", MM_INFO, "Отсутствует функция fmtmsg()", "Установите функцию fmtmsg() или не пользуйтесь ею\n", "См. functions/fmtmsg.html") != MM_OK) { perror ("FMTMSG"); return (1); }
    return 0; }
    Листинг 9.5. Пример использования функции fmtmsg().
    Закрыть окно




    POSIX:fmtmsg: INFO: Отсутствует функция fmtmsg() TO FIX: Установите функцию fmtmsg() или не пользуйтесь ею См. functions/fmtmsg.html
    Листинг 9.6. Возможные результаты выполнения программы, использующей функцию fmtmsg().
    Закрыть окно




    #include
    struct utmpx *getutxent (void);
    struct utmpx *getutxid ( const struct utmpx *id);
    struct utmpx *getutxline ( const struct utmpx *line);
    struct utmpx *pututxline ( const struct utmpx *utmpx);
    void setutxent (void);
    void endutxent (void);
    Листинг 9.7. Описание функций для работы с базой данных учетной информации о пользователях.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Пример использования функций для работы */ /* с базой данных учетной информации о пользователях */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    int main (void) { struct utmpx *utmpx_ptr; /* Указатель на текущую запись */ char dtbuf [LINE_MAX]; /* Буфер для данных о времени */ struct utmpx spat; /* Шаблон для поиска в базе */
    /* Прочитаем и распечатаем все записи в базе */ printf ("Содержимое базы данных учетной информации " "о пользователях\n"); while ((utmpx_ptr = getutxent ()) != NULL) { (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (&(utmpx_ptr->ut_tv.tv_sec))); switch (utmpx_ptr->ut_type) { case EMPTY: printf ("Пустая запись\n"); break; case BOOT_TIME: printf ("Время загрузки системы: %s\n", dtbuf); break; case OLD_TIME: printf ("Время изменения показаний системных " "часов: %s\n", dtbuf); break; case NEW_TIME: printf ("Показания системных часов после " "изменения: %s\n", dtbuf); break; case USER_PROCESS: printf ("Процесс пользователя: %s, идентификатор: " "%d,\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Имя устройства: %s,\n", utmpx_ptr->ut_line); printf ("Время создания записи: %s\n", dtbuf); break; case LOGIN_PROCESS: printf ("Входной процесс: %s, идентификатор: %d,\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; case INIT_PROCESS: printf ("Процесс, порожденный системным процессом " "init:\n"); printf ("Идентификатор: %d,\n", utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; case DEAD_PROCESS: printf ("Лидер сеанса, завершивший выполнение:\n"); printf ("Идентификатор: %d,\n", utmpx_ptr->ut_pid); printf ("Инициализационный идентификатор процесса: " "%s,\n", utmpx_ptr->ut_id); printf ("Время создания записи: %s\n", dtbuf); break; default: printf ("Нестандартный тип записи: %x\n", utmpx_ptr->ut_type); break; } }
    /* Найдем и распечатаем записи, */ /* инициализационный идентификатор которых */ /* равняется S4 */ spat.ut_type = INIT_PROCESS; (void) strncpy (spat.ut_id, "S4", sizeof (spat.ut_id));
    /* Позиционируемся на начало базы */ setutxent ();
    printf ("Записи, инициализационный идентификатор " "которых равняется S4:\n"); while ((utmpx_ptr = getutxid (&spat)) != NULL) { switch (utmpx_ptr->ut_type) { case USER_PROCESS: printf ("Процесс пользователя: %s, " "идентификатор: %d\n", utmpx_ptr->ut_user, utmpx_ptr->ut_pid); break; case LOGIN_PROCESS: printf ("Входной процесс: %s, идентификатор: " "%d\n",utmpx_ptr->ut_user, utmpx_ptr->ut_pid); break; case INIT_PROCESS: printf ("Процесс, порожденный системным процессом " "init:\n"); printf ("Идентификатор: %d\n", utmpx_ptr->ut_pid); break; case DEAD_PROCESS: printf ("Лидер сеанса, завершивший " "выполнение:\n"); printf ("Идентификатор: %d\n", utmpx_ptr->ut_pid); break; default: printf ("Нестандартный тип результата поиска: " "%x\n", utmpx_ptr->ut_type); break; }
    /* Обеспечим сдвиг поиска с текущей записи */ utmpx_ptr->ut_id [0] = 0; }
    endutxent ();
    return 0; }
    Листинг 9.8. Пример применения функций для работы с базой данных учетной информации о пользователях.
    Закрыть окно




    Содержимое базы данных учетной информации о пользователях Лидер сеанса, завершивший выполнение: Идентификатор: 17, Инициализационный идентификатор процесса: si, Время создания записи: Tue Apr 27 10:08:42 2004 Время загрузки системы: Tue Apr 27 10:08:42 2004 Нестандартный тип записи: 1 Лидер сеанса, завершивший выполнение: Идентификатор: 284, Инициализационный идентификатор процесса: l5, Время создания записи: Tue Apr 27 10:09:15 2004 Лидер сеанса, завершивший выполнение: Идентификатор: 1115, Инициализационный идентификатор процесса: ud, Время создания записи: Tue Apr 27 10:09:15 2004 . . . Входной процесс: LOGIN, идентификатор: 1123, Инициализационный идентификатор процесса: S3, Время создания записи: Tue Apr 27 10:09:15 2004 Процесс пользователя: galat, идентификатор: 1124, Инициализационный идентификатор процесса: S4, Имя устройства: ttyS4, Время создания записи: Tue Apr 27 12:52:51 2004 Процесс пользователя: sambor, идентификатор: 1125, Инициализационный идентификатор процесса: S5, Имя устройства: ttyS5, Время создания записи: Tue Apr 27 13:57:31 2004 Процесс пользователя: kost, идентификатор: 1126, Инициализационный идентификатор процесса: S6, Имя устройства: ttyS6, Время создания записи: Tue Apr 27 10:09:30 2004 . . . Процесс, порожденный системным процессом init: Идентификатор: 1128, Инициализационный идентификатор процесса: x, Время создания записи: Tue Apr 27 10:09:15 2004 . . . Лидер сеанса, завершивший выполнение: Идентификатор: 11708, Инициализационный идентификатор процесса: /1, Время создания записи: Tue Apr 27 11:19:33 2004 . . . Записи, инициализационный идентификатор которых равняется S4: Процесс пользователя: galat, идентификатор: 1124
    Листинг 9.9. Фрагмент возможных результатов выполнения программы, применяющей функции для работы с базой данных учетной информации о пользователях.
    Закрыть окно




    #include
    DBM *dbm_open (const char *file, int open_flags, mode_t file_mode);
    void dbm_close (DBM *db);
    datum dbm_fetch (DBM *db, datum key);
    int dbm_store (DBM *db, datum key, datum content, int store_mode);
    int dbm_delete (DBM *db, datum key);
    datum dbm_firstkey (DBM *db);
    datum dbm_nextkey (DBM *db);
    int dbm_error (DBM *db);
    int dbm_clearerr (DBM *db);
    Листинг 9.10. Описание функций для работы с простыми базами данных.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа перебирает все ключи в базе данных */ /* и выдает ассоциированную с ними информацию. */ /* Предполагается, что ключи и данные – текстовые*/ /* * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include
    int main (int argc, char *argv []) { DBM *dbdes; /* Дескриптор открытой базы */ datum ckey; /* Текущий ключ */ datum cdat; /* Текущие данные */ int nkeys = 0; /* Число ключей */
    if (argc != 2) { fprintf (stderr, "Использование: %s имя_базы\n", argv [0]); return (1); }
    if ((dbdes = dbm_open (argv [1], O_RDONLY, 0777)) == (DBM *) NULL) { fprintf (stderr, " Не удалось открыть базу данных %s\n", argv [1]); return (2); }
    for (ckey = dbm_firstkey (dbdes); ckey.dptr != NULL; ckey = dbm_nextkey (dbdes)) { nkeys++; printf ("Длина ключа номер %d: %d\n", nkeys, ckey.dsize); printf ("Ключ номер %d: %s\n", nkeys, ckey.dptr);
    if (cdat = dbm_fetch (dbdes, ckey), cdat.dptr != NULL) { printf ("Длина данных для ключа номер %d: %d\n", nkeys, cdat.dsize); printf ("Данные для ключа номер %d: %s\n", nkeys, cdat.dptr); } else { fprintf (stderr, "Отсутствуют данные для " "ключа номер %d\n", nkeys); } }
    printf ("Число ключей в базе: %d\n", nkeys);
    dbm_close (dbdes);
    return 0; }
    Листинг 9.11. Пример применения функций для работы с простыми базами данных.
    Закрыть окно




    Длина ключа номер 1: 16 Ключ номер 1: YP_LAST_MODIFIED Длина данных для ключа номер 1: 10 Данные для ключа номер 1: 0898782331 Длина ключа номер 2: 14 Ключ номер 2: mailer-daemon Длина данных для ключа номер 2: 11 Данные для ключа номер 2: postmaster Длина ключа номер 3: 14 Ключ номер 3: YP_MASTER_NAME Длина данных для ключа номер 3: 3 Данные для ключа номер 3: t41 Длина ключа номер 4: 11 Ключ номер 4: postmaster Длина данных для ключа номер 4: 5 Данные для ключа номер 4: root Длина ключа номер 5: 7 Ключ номер 5: nobody Длина данных для ключа номер 5: 10 Данные для ключа номер 5: /dev/null Длина ключа номер 6: 2 Ключ номер 6: @ Длина данных для ключа номер 6: 2 Данные для ключа номер 6: @ Число ключей в базе: 6
    Листинг 9.12. Возможные результаты выполнения программы, применяющей функции для работы с простыми базами данных.
    Закрыть окно




    #include void *bsearch (const void *key, const void *base, size_t nel, size_t width, int (*compar) (const void *, const void *));
    Листинг 9.13. Описание функции бинарного поиска bsearch().
    Закрыть окно




    #include void qsort (void *base, size_t nel, size_t width, int (*compar) (const void *, const void *));
    Листинг 9.14. Описание функции быстрой сортировки qsort().
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * */ /* Программа сортирует массив указателей */ /* на случайные цепочки символов, а затем */ /* выполняет в этом массиве бинарный поиск */ /* * * * * * * * * * * * * * * * * * * * * */
    #include #include #include
    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 10000000
    /* Число элементов в таблице указателей на цепочки символов */ #define TAB_SIZE 1000000
    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 10
    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];
    /* Массив указателей на цепочки символов */ static char *PtsTable [TAB_SIZE]; /* Число занятых элементов в массиве указателей */ static size_t nelst;
    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }
    /* * * * * * * * * * * * * * * * * */ /* Заполнение массива указателями */ /* на случайные цепочки символов */ /* * * * * * * * * * * * * * * * * */ static void tabl_fill (void) { char *pss; /* Указатель на свободное место */ /* в области StringSpace */ int i;
    for (pss = StringSpace, i = 0; i < TAB_SIZE; pss += STRING_SIZE, i++) { if (((pss + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "tabl_fill: исчерпано " "пространство цепочек\n"); nelst = i; return; } str_rnd (pss, STRING_SIZE); PtsTable [i] = pss; } nelst = TAB_SIZE; }
    /* * * * * * * * * * */ /* Функция сравнения */ /* * * * * * * * * * */ static int str_compar (const void *pkey, const void *pelem) { return strcoll (*((char **) pkey), *((char **) pelem)); }
    /* * * * * * * * * * * */ /* Сортировка и поиск */ /* * * * * * * * * * * */ int main (void) { char *skey; /* Указатель на искомую цепочку символов */ char **res; /* Результат бинарного поиска */ /* Буфер для формирования случайных цепочек */ char sbuf [STRING_SIZE]; double ntr; /* Номер найденной случайной цепочки */
    /* Заполнение массивов */ tabl_fill ();
    /* Сортировка массива указателей */ qsort (PtsTable, nelst, sizeof (PtsTable [0]), str_compar);
    /* Формирование ключа поиска */ /* (будем искать первую из случайных цепочек) */ skey = StringSpace; if ((res = (char **) bsearch (&skey, PtsTable, nelst, sizeof (PtsTable [0]), str_compar)) != NULL) { printf ("Указатель на первую цепочку %s\n" "после сортировки стал %d-м элементом массива\n", skey, (res – PtsTable) / sizeof (PtsTable [0])); } else { printf ("Не удалось найти цепочку %s\n", skey); }
    /* Будем формировать и искать новые случайные цепочки */ skey = sbuf; ntr = 0; do { str_rnd (skey, STRING_SIZE); ntr++;
    } while (bsearch (&skey, PtsTable, nelst, sizeof (PtsTable [0]), str_compar) == NULL); printf ("Удалось найти %g-ю по счету случайную цепочку" " %s\n", ntr, skey);
    return 0; }
    Листинг 9.15. Пример применения функций быстрой сортировки и бинарного поиска.
    Закрыть окно




    Указатель на первую цепочку NWLRBBMQB после сортировки стал 133253-м элементом массива Удалось найти 168221-ю по счету случайную цепочку VBBDZTNMZ real 15.67 user 15.57 sys 0.10
    Листинг 9.16. Возможные результаты выполнения программы, применяющей функции быстрой сортировки и бинарного поиска.
    Закрыть окно




    #include
    void *lsearch (const void *key, void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));
    void *lfind (const void *key, const void *base, size_t *nelp, size_t width, int (*compar) (const void *, const void *));
    Листинг 9.17. Описание функций последовательного поиска.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа генерирует случайные цепочки символов до первого */ /* повторения (или до исчерпания отведенного пространства). */ /* Для выявления повторения применяется */ /* последовательный поиск с вставкой */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include
    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 200000
    /* Число элементов в таблице указателей на цепочки символов */ #define TAB_SIZE 20000
    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 7
    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];
    /* Массив указателей на цепочки символов */ static char *PtsTable [TAB_SIZE];
    /* * * * * * * * * * */ /* Функция сравнения */ /* * * * * * * * * * */ static int str_compar (const void *pkey, const void *pelem) { return strcoll (*((char **) pkey), *((char **) pelem)); }
    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }
    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Поиск первого повтора в последовательности */ /* случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { char *pss; /* Указатель на свободное место */ /* в области StringSpace */ char **res; /* Результат поиска с вставкой */ size_t nelst; /* Число занятых элементов */ /* в массиве указателей */ size_t onelst; /* Число элементов в массиве */ /* до поиска с вставкой */ for (pss = StringSpace, nelst = 0; nelst < TAB_SIZE; pss += STRING_SIZE) { if (((pss + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (1); }
    str_rnd (pss, STRING_SIZE);
    onelst = nelst; res = (char **) lsearch (&pss, PtsTable, &nelst, sizeof (PtsTable [0]), str_compar); if (onelst == nelst) { /* Искомая цепочка уже была порождена ранее */ printf ("Для случайных цепочек длины %d\n" "первое совпадение получено на цепочке " "%s\n", STRING_SIZE, pss); printf ("Первый раз цепочка была порождена " "под номером %d,\n" "второй – под номером " "%d\n", (res – PtsTable) / sizeof (PtsTable [0]) + 1, nelst + 1); return 0; } } /* for */
    printf ("Из %d случайных цепочек длины %d все " "оказались уникальными\n", TAB_SIZE, STRING_SIZE);
    return 0; }
    Листинг 9.18. Пример применения последовательного поиска с вставкой.
    Закрыть окно




    Для случайных цепочек длины 7 первое совпадение получено на цепочке GLPCSX Первый раз цепочка была порождена под номером 2548, второй - под номером 12530 real 34.80 user 13.70 sys 0.03
    Листинг 9.19. Возможные результаты выполнения программы, применяющей функцию последовательного поиска с вставкой.
    Закрыть окно




    #include
    int hcreate (size_t nel);
    void hdestroy (void);
    ENTRY *hsearch ( ENTRY item, ACTION action);
    Пример 9.20. Описание функций управления хэш-таблицами поиска.
    Закрыть окно




    typedef struct entry { char *key; /* Ключ поиска */ void *data; /* Дополнительные данные, */ /* ассоциированные с ключом */ } ENTRY;
    Листинг 9.21. Описание типа ENTRY.
    Закрыть окно




    enum { FIND, ENTER } ACTION;
    Листинг 9.22. Определение типа ACTION.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * */ /* Программа помещает в хэш-таблицу */ /* заданное число элементов с указателями*/ /* на случайные цепочки символов, */ /* а затем выполняет в этой таблице */ /* поиск новых случайных цепочек, */ /* пока он не окажется успешным */ /* * * * * * * * * * * * * * * * * * * * */
    #include #include #include
    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 10000000
    /* Число элементов, помещаемых в хэш-таблицу */ #define TAB_NEL 1000000
    /* Размер хэш-таблицы */ #define TAB_SIZE (2 * TAB_NEL)
    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 10
    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];
    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }
    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Заполнение хэш-таблицы, поиск повтора в */ /* последовательности случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { ENTRY item; /* Искомый элемент */ char sbuf [STRING_SIZE]; /* Буфер для формирования */ /* случайных цепочек */ double ntr; /* Номер найденной */ /* случайной цепочки */ size_t i;
    if (hcreate (TAB_SIZE) == 0) { fprintf (stderr, "%s: Не удалось создать хэш-таблицу" " размера %d\n", argv [0], TAB_SIZE); return (1); }
    item.data = NULL; /* Нет ассоциированных данных */ /* Заполним таблицу */ for (item.key = StringSpace, i = 0; i < TAB_NEL; item.key += STRING_SIZE, i++) { if (((item.key + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (2); }
    str_rnd (item.key, STRING_SIZE);
    if (hsearch (item, ENTER) == NULL) { fprintf (stderr, "%s: Переполнена хэш-таблица\n", argv [0]); return (3); } } /* for */
    /* Будем формировать и искать новые случайные цепочки */ item.key = sbuf; ntr = 0; do { str_rnd (item.key, STRING_SIZE); ntr++; } while (hsearch (item, FIND) == NULL); printf ("Удалось найти %g-ю по счету случайную цепочку %s\n", ntr, item.key);
    hdestroy ();
    return 0; }
    Листинг 9.23. Пример применения функций, управляющих хэш-таблицами поиска.
    Закрыть окно




    Удалось найти 168221- ю по счету случайную цепочку VBBDZTNMZ real 9.61 user 9.36 sys 0.25
    Листинг 9.24. Возможные результаты выполнения программы, применяющей функции управления хэш-таблицами поиска.
    Закрыть окно




    #include
    void *tsearch (const void *key, void **rootp, int (*compar) (const void *, const void *));
    void *tfind (const void *key, void *const *rootp, int (*compar) (const void *, const void *));
    void *tdelete (const void *restrict key, void **restrict rootp, int (*compar) (const void *, const void *));
    void twalk (const void *root, void (*action) (const void *, VISIT, int));
    Листинг 9.25. Описание функций управления бинарными деревьями поиска.
    Закрыть окно




    enum { preorder, postorder, endorder, leaf } VISIT;
    Листинг 9.26. Определение типа VISIT.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа осуществляет поиск с вставкой в бинарном */ /* дереве, помещая в него заданное число элементов с */ /* указателями на случайные цепочки символов. */ /* Затем подсчитывается число узлов и высота дерева. */ /* Следующим действием является распечатка */
    /* нескольких первых цепочек. */ /* После этого выполняется поиск новых случайных цепочек,*/ /* пока он не окажется успешным. */ /* Найденный элемент удаляется из дерева */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include #include #include #include
    /* Размер области для хранения цепочек символов */ #define SPACE_SIZE 10000000
    /* Число элементов, помещаемых в дерево */ #define TREE_NEL 1000000
    /* Длина одной цепочки символов */ /* (включая завершающий нулевой байт) */ #define STRING_SIZE 10
    /* Область для хранения цепочек символов */ static char StringSpace [SPACE_SIZE];
    /* Число узлов в бинарном дереве поиска */ static size_t node_count;
    /* Максимальный уровень узла дерева */ static int max_level; /* Буфер для функций setjmp и longjmp */ static jmp_buf buf_env;
    /* * * * * * * * * * * * * * * * * * * * * */ /* Формирование случайной цепочки символов */ /* * * * * * * * * * * * * * * * * * * * * */ static void str_rnd (char *buf, size_t str_siz) { for ( ; str_siz > 1; str_siz--) { *buf++ = 'A' + rand () % 26; } if (str_siz > 0) { *buf = 0; } }
    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, которая вызывается при обходе дерева */ /* с целью подсчета числа узлов и высоты */ /* * * * * * * * * * * * * * * * * * * * * * * * */ static void tw_nnh (const void *pnode, VISIT nv, int level) { if (nv == preorder) { node_count++; } else if (nv == leaf) { node_count++; if (level > max_level) { max_level = level; } } }
    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, которая вызывается при обходе дерева */ /* с целью распечатки нескольких первых */ /* по алфавиту цепочек символов */ /* * * * * * * * * * * * * * * * * * * * * * * * */ static void tw_pfs (const void *pnode, VISIT nv, int level) { if (node_count <= 0) { /* Нужное число цепочек выведено,*/ /* прерываем обход дерева */ longjmp (buf_env, 1); } if ((nv == postorder) || (nv == leaf)) { printf ("%s\n", *((char **) pnode)); node_count--; } }
    /* * * * * * * * * * * * * * * * * * */ /* Создание бинарного дерева поиска, */ /* определение его характеристик, */ /* поиск повтора в последовательности*/ /* случайных цепочек символов */ /* * * * * * * * * * * * * * * * * * */ int main (int argc, char *argv []) { void *root; /* Указатель на корень дерева */ char *key; /* Указатель на искомую */ /* цепочку символов */ char sbuf [STRING_SIZE]; /* Буфер для формирования */ /* случайных цепочек */ double ntr; /* Номер найденной случайной */ /* цепочки */ size_t i;
    /* Создадим бинарное дерево поиска */ root = NULL; for (key = StringSpace, i = 0; i < TREE_NEL; key += STRING_SIZE, i++) { if (((key + STRING_SIZE) – (StringSpace + SPACE_SIZE)) > 0) { fprintf (stderr, "%s: Исчерпано пространство " "цепочек\n", argv [0]); return (1); }
    str_rnd (key, STRING_SIZE);
    if (tsearch (key, &root, (int (*) (const void *, const void *)) strcoll) == NULL) { fprintf (stderr, "%s: Поиск с вставкой в бинарное" " дерево " "завершился неудачей\n", argv [0]); return (2); } } /* for */
    /* Подсчитаем число узлов и высоту созданного дерева */ node_count = 0; max_level = 0; twalk (root, tw_nnh); printf ("В дереве оказалось %d узлов\n", node_count); printf ("Его высота равна %d\n", max_level);
    /* Распечатаем несколько первых (по алфавиту) цепочек, */ /* помещенных в созданное дерево */ node_count = 10; printf ("Первые %d по алфавиту цепочек в дереве:\n", node_count); if (setjmp (buf_env) == 0) { twalk (root, tw_pfs); }
    /* Будем формировать и искать новые случайные цепочки */ ntr = 0; do { str_rnd (sbuf, STRING_SIZE); ntr++; } while (tdelete (sbuf, &root, (int (*) (const void *, const void *)) strcoll) == NULL); printf ("Удалось найти и удалить из дерева %g-ю по счету " "случайную цепочку %s\n", ntr, sbuf);
    return 0; }
    Листинг 9.27. Пример применения функций управления бинарными деревьями поиска.
    Закрыть окно




    В дереве оказалось 1000000 узлов Его высота равна 25 Первые 10 по алфавиту цепочек в дереве: AAAATNRAS AAACHCCLB AAACSJQBP AAADLHFAZ AAAFWLRXM AAAFXGQEC AAAGBMHHA AAAGFAXFI AAAHKLCWW AAAHLOSVQ Удалось найти и удалить из дерева 168221-ю по счету случайную цепочку VBBDZTNMZ real 20.24 user 20.25 sys 0.15
    Листинг 9.28. Возможные результаты выполнения программы, применяющей функции управления бинарными деревьями поиска.
    Закрыть окно




    #include
    void insque (void *element, void *pred);
    void remque (void *element);
    Листинг 9.29. Описание функций, выполняющих операции над очередями.
    Закрыть окно




    #include
    . . . struct qelem { struct qelem *q_forw; struct qelem *q_back; char *data; . . . };
    struct qelem element1; struct qelem element2;
    . . . element1.q_forw = &element1; element1.q_back = &element1;
    insque (&element2, &element1);
    . . .
    Листинг 9.30. Пример инициализации циклической очереди и вставки в нее второго элемента.
    Закрыть окно




    a b c d d e f g e f h h
    Листинг 9.31. Пример исходных данных для служебной программы tsort.
    Закрыть окно




    a c h b d e f g
    Листинг 9.32. Возможный результат применения служебной программы tsort.
    Закрыть окно




    #include
    int getcontext (ucontext_t *ucp);
    void makecontext (ucontext_t *ucp, void (*func) (void), int argc, ...);
    int setcontext (const ucontext_t *ucp);
    int swapcontext (ucontext_t *restrict oucp, const ucontext_t *restrict ucp);
    Листинг 9.33. Описание функций, манипулирующих пользовательскими контекстами потоков управления.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение функций, */ /* манипулирующих пользовательскими контекстами*/ /* * * * * * * * * * * * * * * * * * * * * * * */
    #include #include
    /* Размер стеков в формируемых пользовательских контекстах */ #define STACK_SIZE 4096
    /* Пространство для стеков */ static char st1 [STACK_SIZE]; static char st2 [STACK_SIZE];
    static ucontext_t ctx [3];
    static void f1 (int arg) { printf ("Вызвана функция %s с аргументом %d\n", "f1", arg); if (swapcontext (&ctx [1], &ctx [2]) != 0) { perror ("SWAPCONTEXT-1"); } printf ("Выход из функции %s\n", "f1"); }
    static void f2 (int arg1, int arg2) { printf ("Вызвана функция %s с аргументами %d, %d\n", "f2", arg1, arg2); if (swapcontext (&ctx [2], &ctx [1]) != 0) { perror ("SWAPCONTEXT-2"); } printf ("Выход из функции %s\n", "f2"); }
    int main (void) { (void) getcontext (&ctx [1]);
    printf ("Параметры первоначального контекста:\n" "адрес стека %p, размер стека %d\n", ctx[1].uc_stack.ss_sp, ctx[1].uc_stack.ss_size);
    /* В соответствии с общими рекомендациями */ /* позаботимся о стеке для модифицируемых контекстов */ ctx[1].uc_stack.ss_sp = st1; ctx[1].uc_stack.ss_size = sizeof (st1); ctx[1].uc_link = &ctx [0]; makecontext (&ctx [1], (void (*) (void)) f1, 1, 2);
    (void) getcontext (&ctx [2]); ctx[2].uc_stack.ss_sp = st2; ctx[2].uc_stack.ss_size = sizeof (st2); ctx[2].uc_link = &ctx [1]; makecontext (&ctx [2], (void (*) (void)) f2, 2, 3, 4); if (swapcontext (&ctx [0], &ctx [2]) != 0) { perror ("SWAPCONTEXT-3"); return (1); }
    return 0; }
    Листинг 9.34. Пример применения функций, манипулирующих пользовательскими контекстами.
    Закрыть окно




    Параметры первоначального контекста: адрес стека (nil), размер стека 0 Вызвана функция f2 с аргументами 3, 4 Вызвана функция f1 с аргументом 2 Выход из функции f2 Выход из функции f1
    Листинг 9.35. Возможные результаты выполнения программы, применяющей функции манипулирования пользовательскими контекстами.
    Закрыть окно




    #include
    int fegetenv (fenv_t *fenvp);
    int fesetenv (const fenv_t *fenvp);
    Листинг 9.36. Описание функций опроса и установки текущей среды вещественной арифметики.
    Закрыть окно




    #include int feholdexcept (fenv_t *fenvp);
    Листинг 9.37. Описание функции feholdexcept().
    Закрыть окно




    #include int feupdateenv (const fenv_t *fenvp);
    Листинг 9.38. Описание функции feupdateenv().
    Закрыть окно




    #include
    int fegetexceptflag (fexcept_t *flagp, int excepts);
    int fesetexceptflag (const fexcept_t *flagp, int excepts);
    Листинг 9.39. Описание функций опроса и установки флагов состояния среды вещественной арифметики.
    Закрыть окно




    #include
    int fetestexcept (int excepts);
    int feclearexcept (int excepts);
    int feraiseexcept (int excepts);
    Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций.
    Закрыть окно




    #include
    int fegetround (void);
    int fesetround (int round);
    Листинг 9.41. Описание функций опроса и установки режима округления.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение некоторых функций */ /* управления средой вещественной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include
    #pragma STDC FENV_ACCESS ON
    int main (void) { double d1, d2, d3, s; int res;
    printf ("Представление флагов состояния вещественной " "арифметики\n"); printf (" FE_DIVBYZERO: %x\n", FE_DIVBYZERO); printf (" FE_INEXACT: %x\n", FE_INEXACT); printf (" FE_INVALID: %x\n", FE_INVALID); printf (" FE_OVERFLOW: %x\n", FE_OVERFLOW); printf (" FE_UNDERFLOW: %x\n", FE_UNDERFLOW);
    printf ("Представление режимов округления\n"); printf (" FE_DOWNWARD: %x\n", FE_DOWNWARD); printf (" FE_TONEAREST: %x\n", FE_TONEAREST); printf (" FE_TOWARDZERO: %x\n", FE_TOWARDZERO); printf (" FE_UPWARD: %x\n", FE_UPWARD);
    printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущий режим округления: %x\n", fegetround ());
    feclearexcept (FE_ALL_EXCEPT);
    /* Вызовем ситуацию исчезновения порядка */ d1 = 1; do { d1 /= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^-inf: %g\n", d1);
    feclearexcept (res); /* Вызовем ситуацию переполнения */ d2 = 1; do { d2 *= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^+inf: %g\n", d2);
    feclearexcept (res);
    /* Вызовем ситуацию деления на нуль */ d3 = 1 / d1; res = fetestexcept (FE_ALL_EXCEPT); printf ("Исключительные ситуации: %x\n", res); printf ("1/0: %g\n", d3);
    feclearexcept (res);
    /* Пример того, как может возникать потеря точности */ s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s – 2) > 0); printf ("Исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("sqrt (2): %g\n", s);
    return 0; }
    Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики.
    Закрыть окно




    Представление флагов состояния вещественной арифметики FE_DIVBYZERO: 4 FE_INEXACT: 20 FE_INVALID: 1 FE_OVERFLOW: 8 FE_UNDERFLOW: 10 Представление режимов округления FE_DOWNWARD: 400 FE_TONEAREST: 0 FE_TOWARDZERO: c00 FE_UPWARD: 800 Текущие исключительные ситуации: 0 Текущий режим округления: 0 Исключительные ситуации: 30 2^-inf: 0 Исключительные ситуации: 28 2^+inf: inf Исключительные ситуации: 4 1/0: inf Исключительные ситуации: 20 sqrt (2): 1.41421
    Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые операции */ /* интервальной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
    #include #include
    #pragma STDC FENV_ACCESS ON
    /* Интервальное представление числа */ typedef struct ditvl { double lb; double ub; } ditvl_t;
    /* * * * * * * * * * * * * * * * * * * * */ /* Сложение интервалов. */ /* Сумма помещается в выходной аргумент. */ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * */ int ditvl_add (const ditvl_t *a1, const ditvl_t *a2, ditvl_t *res) { fenv_t cfenv;
    /* Сохраним текущую среду вещественной арифметики */ if (fegetenv (&cfenv) != 0) { perror ("FEGETENV"); return (-1); }
    /* Нижние границы нужно складывать с округлением вниз */ if (fesetround (FE_DOWNWARD) != 0) { perror ("FESETROUND"); return (-1); } res->lb = a1->lb + a2->lb;
    /* Верхние границы складываются с округлением вверх */ if (fesetround (FE_UPWARD) != 0) { perror ("FESETROUND"); return (-1); } res->ub = a1->ub + a2->ub;
    /* Восстановим среду вещественной арифметики */ if (fesetenv (&cfenv) != 0) { perror ("FESETENV"); return (-1); }
    return 0; }
    /* * * * * * * * */ /* Унарный минус */ /* * * * * * * * */ int ditvl_uminus (const ditvl_t *a, ditvl_t *res) { res->lb = -(a->ub); res->ub = -(a->lb);
    return 0; }
    /* * * * * * * * */ /* Вызов функций */ /* * * * * * * * */ int main (void) { ditvl_t pi = {3.141592, 3.141593}; ditvl_t e = {2.718281, 2.718282}; ditvl_t res; ditvl_t tmp;
    printf ("Представление числа pi: (%f, %f)\n", pi.lb, pi.ub); printf ("Представление числа e: (%f, %f)\n", e.lb, e.ub);
    /* Вычислим сумму pi и e */ (void) ditvl_add (&pi, &e, &res); printf ("Сумма pi и e: (%f, %f)\n", res.lb, res.ub);
    /* Вычислим разность pi и e */ (void) ditvl_uminus (&e, &tmp); (void) ditvl_add (&pi, &tmp, &res); printf ("Разность pi и e: (%f, %f)\n", res.lb, res.ub);
    printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущие режимы округления: %x\n", fegetround ());
    return 0; }
    Листинг 9.44. Пример использования различных режимов округления.
    Закрыть окно




    Представление числа pi: (3.141592, 3.141593) Представление числа e: (2.718281, 2.718282) Сумма pi и e: (5.859873, 5.859875) Разность pi и e: (0.423310, 0.423312) Текущие исключительные ситуации: 0 Текущие режимы округления: 0
    Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики.
    Закрыть окно




    #include
    int ftw (const char *path, int (*fn) (const char *, const struct stat *, int), int depth);
    int nftw (const char *path, int (*fn) (const char *, const struct stat *, int, struct FTW *), int depth, int flags);
    Листинг 9.46. Описание функций обхода файловой иерархии.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * */ /* Программа определяет суммарный размер */ /* и высоту файловой иерархии */ /* * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include
    /* Суммарный размер файлов в иерархии */ static off_t fsize = 0;
    /* Максимальный уровень файлов в иерархии */ static int flevel = 0;
    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая для каждого файла в иерархии */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ static int nftwfunc (const char *filename, const struct stat *statptr, int filetype, struct FTW *pfwt) { /* Если установлен флаг FTW_NS, завершим обход */ if (filetype == FTW_NS) { perror ("STAT"); fprintf (stderr, "Отсутствуют данные о файле %s\n", filename); return 1; } fsize += statptr->st_size; if (pfwt->level > flevel) { flevel = pfwt->level; }
    return 0; }
    /* * * * * * * * * * * */ /* Организация обхода */ /* * * * * * * * * * * */ int main (int argc, char *argv []) { if (argc != 2) { fprintf (stderr, "Использование: %s корень_иерархии\n", argv [0]); return (1); }
    if (nftw (argv [1], nftwfunc, 16, FTW_MOUNT | FTW_PHYS) == -1) { perror ("NFTW"); }
    printf ("Суммарный размер обработанных файлов: %ld\n", fsize); printf ("Высота иерархии файлов: %d\n", flevel);
    return 0; }
    Листинг 9.47. Пример программы, осуществляющей обход файловой иерархии.
    Закрыть окно




    STAT: Permission denied Отсутствуют данные о файле /tmp/gaga/ gugu Суммарный размер обработанных файлов: 2645778 Высота иерархии файлов: 2
    Листинг 9.48. Возможные результаты выполнения программы, осуществляющей обход файловой иерархии.
    Закрыть окно




    #include #include int posix_openpt (int oflag);
    Листинг 9.49. Описание функции открытия главного устройства псевдотерминала.
    Закрыть окно




    #include int unlockpt (int masterfd);
    Листинг 9.50. Описание функции разблокирования подчиненного устройства псевдотерминала.
    Закрыть окно




    #include int grantpt (int masterfd);
    Листинг 9.51. Описание функции формирования прав доступа к подчиненному устройству псевдотерминала.
    Закрыть окно




    #include char *ptsname (int masterfd);
    Листинг 9.52. Описание функции получения имени подчиненного устройства псевдотерминала.
    Закрыть окно




    /* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запускает shell на псевдотерминале */ /* * * * * * * * * * * * * * * * * * * * * * * * */
    #define _XOPEN_SOURCE 600
    #include #include #include #include #include #include #include #include #include
    /* * * * * * * * * * * * * * * * * * */ /* Действия при завершении процесса */ /* * * * * * * * * * * * * * * * * * */ static void termination (int errcode) { endwin (); exit (errcode); }
    /* * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGCHLD */ /* * * * * * * * * * * * * * * * * * */ static void chldied (int dummy) { /* Просто кончимся */ termination (34); }
    /* * * * * * * * * * * * * * * * * * * * */ /* Организация работы с псевдотерминалом */ /* * * * * * * * * * * * * * * * * * * * */ int main (void) { WINDOW *win1, *win2; /* win1 – окно только для рамки */ /* win2 – окно для shell'а */
    int pty, tty; /* Дескрипторы обеих сторон */ /* псевдотерминала */ int fr; /* Результат fork'а */ unsigned char ch; /* Прочитанный символ */ struct termios pt; /* Структура характеристик */ /* псевдотерминала */ struct pollfd fds [2]; /* Массив параметров для */ /* вызова poll */ int w2lines, w2cols; /* Размер создаваемого окна */ int x, y; /* Координаты в окне */ struct sigaction sact; int i;
    initscr (); cbreak (); noecho ();
    win1 = newwin (LINES, COLS, 0, 0); box (win1, 0, 0); wrefresh (win1);
    w2lines = LINES – 2; w2cols = COLS – 4; win2 = newwin (w2lines, w2cols, 1, 2); scrollok (win2, TRUE);
    /* Откроем псевдотерминал */ if (((pty = posix_openpt (O_RDWR | O_NOCTTY)) < 0) || (unlockpt (pty) == -1) || (grantpt (pty) == -1) || ((tty = open (ptsname (pty), O_RDWR)) < 0)) { fprintf (stderr, "Не удалось открыть псевдотерминал\n"); perror ("POSIX_OPENPT"); return (1); }
    /* Установим подходящие характеристики псевдотерминала */ if (tcgetattr (pty, &pt) < 0) { perror ("PTY TERMIOS GET ERROR"); return (2); } pt.c_iflag = 0; pt.c_oflag = ONLCR; pt.c_cflag = CS8 | HUPCL; pt.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK; pt.c_cc [VINTR] = 3; /* CTRL+C */ pt.c_cc [VEOF] = 4; /* CTRL+D */ if (tcsetattr (pty, TCSADRAIN, &pt) < 0) { perror ("PTY TERMIOS SET ERROR"); return (3); }
    /* То же – для стандартного ввода */ (void) tcgetattr (0, &pt); pt.c_lflag &= ~ISIG; (void) tcsetattr (0, TCSADRAIN, &pt);
    /* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL);
    /* Раздвоимся на процесс чтения с клавиатуры */ /* и вывода на экран и на процесс, */ /* в рамках которого запустим shell */
    if ((fr = fork ()) < 0) { perror ("FORK1 ERROR"); termination (-1); } else if (fr) { /* Это процесс, читающий с клавиатуры */ /* и выводящий на экран */ close (tty);
    /* Будем ждать ввода с клавиатуры или псевдотерминала */ fds [0].fd = 0; fds [0].events = POLLIN; fds [1].fd = pty; fds [1].events = POLLIN;
    while (1) { if (poll (fds, 2, -1) < 0) { perror ("POLL ERROR"); termination (0); } if (fds [0].revents & POLLIN) { /* Пришел символ со стандартного ввода */ read (0, &ch, 1); write (pty, &ch, 1); } if (fds [1].revents & POLLIN) { /* Пришел символ с псевдотерминала */ read (pty, &ch, 1); switch (ch) { case '\n': { /* Проинтерпретируем перевод строки */ getyx (win2, y, x); if (y == (w2lines – 1)) { wmove (win2, y, w2cols – 1); waddch (win2, (chtype) ch); } else { wmove (win2, y + 1, 0); } break; } default: { /* Символ не интерпретируется */ waddch (win2, (chtype) ch); break; } } wrefresh (win2); } } /* Просто кончимся */ termination (0);
    } else { /* Порожденный процесс – запустим в нем shell */ /* Закроем все файлы, кроме псевдотерминала */ for (i = 0; i < RLIMIT_NOFILE; i++) { if (i != tty) { (void) close (i); } }
    /* Сделаем процесс лидером сеанса */ (void) setsid ();
    /* Свяжем стандартные ввод, вывод и протокол */ /* с псевдотерминалом */ (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); close (tty);
    /* Поместим в окружение параметры псевдотерминала */ { char lnbuf [20]; char clbuf [20];
    sprintf (lnbuf, "LINES=%2d", w2lines); sprintf (clbuf, "COLUMNS=%2d", w2cols);
    putenv (lnbuf); putenv (clbuf); }
    if (execl ("/bin/sh", "sh", (char *) NULL) < 0) { perror ("EXECL ERROR"); exit (-1); } }
    return 0; }
    Листинг 9.53. Пример программы, использующей псевдотерминалы.
    Закрыть окно



    Управление средой вещественной арифметики


    Средства управления средой вещественной арифметики, включенные в стандарт POSIX-2001, позволяют выполнить требования двух других стандартов – вещественной арифметики (IEC 60559:1989) и языка C (ISO/IEC 9899:1999).
    Среда вещественной арифметики включает сущности двух видов: флаги состояния и управляющие режимы.
    Флаг состояния  вещественной арифметики – это системная переменная, значение которой устанавливается (но никогда не очищается) при возбуждении исключительной ситуации и содержит дополнительную информацию о случившемся исключении.
    Под управляющим режимом  вещественной арифметики также понимается системная переменная, но ее значение может быть установлено приложением для воздействия на выполнение последующих операций с вещественными числами.
    Тип данных fenv_t, определенный в заголовочном файле , представляет всю среду, тип fexcept_t – совокупность флагов состояния, включая ассоциированную с флагами информацию.
    Применительно к вещественной арифметике стандарт POSIX-2001 предусматривает следующие исключительные ситуации: FE_DIVBYZERO (деление на нуль), FE_INEXACT (потеря точности), FE_INVALID (некорректная операция), FE_OVERFLOW (переполнение), FE_UNDERFLOW (исчезновение порядка). Побитное ИЛИ перечисленных констант обозначается как FE_ALL_EXCEPT.
    Стандартом специфицированы четыре режима (направления) округления: FE_DOWNWARD (вниз, то есть к минус бесконечности), FE_TONEAREST (к ближайшему представимому), FE_TOWARDZERO (к нулю), FE_UPWARD (вверх, то есть к плюс бесконечности).
    Подразумеваемая среда вещественной арифметики (существующая на момент начала выполнения прикладной программы) обозначается константой FE_DFL_ENV, имеющей тип указателя на константный объект типа  fenv_t.
    Если приложение проверяет флаги состояния, устанавливает собственные управляющие режимы или выполняется в режимах, отличных от подразумеваемого, то при компиляции необходимо воспользоваться управляющим комментарием (#pragma) FENV_ACCESS:
    #pragma STDC FENV_ACCESS ON
    Опросить и установить текущую среду вещественной арифметики можно с помощью функций fegetenv() и fesetenv() (см.
    Функция fesetexceptflag() выполняет обратную операцию. Как и в случае функции fesetenv(), исключительные ситуации при этом не возбуждаются.

    Функции fetestexcept(), feclearexcept() и feraiseexcept() (см. листинг 9.40) служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.

    #include

    int fetestexcept (int excepts);

    int feclearexcept (int excepts);

    int feraiseexcept (int excepts);

    Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций. (html, txt)

    Функция fetestexcept() проверяет, какие из флагов, заданные аргументом  excepts, в данный момент установлены; результатом служит их побитное ИЛИ.

    Функция feclearexcept() пытается сбросить, а feraiseexcept() – возбудить заданные исключительные ситуации. Побочным эффектом возбуждения ситуаций переполнения (FE_OVERFLOW) и исчезновения порядка (FE_UNDERFLOW) может стать потеря точности (FE_INEXACT).

    Опросить и установить режим округления можно с помощью функций fegetround() и fesetround() (см. листинг 9.41).

    #include

    int fegetround (void);

    int fesetround (int round);

    Листинг 9.41. Описание функций опроса и установки режима округления. (html, txt)

    Свидетельством неудачного завершения функции fegetround() служит отрицательный результат.

    Продемонстрируем применение некоторых функций управления средой вещественной арифметики (см. листинг 9.42).

    Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики. (html, txt)

    Возможные результаты выполнения приведенной программы показаны на листинге 9.43.

    Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики. (html, txt)

    Отметим, что и для исключительных ситуаций, и для режима округления подразумеваемые значения равны нулю, но нули это разные: первый свидетельствует об отсутствии исключений, в то время как второй обозначает режим округления до ближайшего представимого числа.

    Обратим внимание также на то, что вместе с исключительными ситуациями  переполнения и исчезновения порядка устанавливается также флаг потери точности.

    В качестве второго примера рассмотрим программу, реализующую некоторые операции интервальной арифметики (см. листинг 9.44).

    Листинг 9.44. Пример использования различных режимов округления. (html, txt)

    Программа переустанавливает режимы округления, поскольку при сложении нижних границ интервалов округлять нужно вниз, а при сложении верхних – вверх. Разумеется, в функции ditvl_add() для сохранения и восстановления режима округления можно было воспользоваться функциями fegetround()/fesetround(), а не fegetenv()/fesetenv().

    На листинге 9.45 показаны возможные результаты выполнения приведенной программы.

    Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики. (html, txt)

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



    листинг 9.36).

    #include

    int fegetenv (fenv_t *fenvp);

    int fesetenv (const fenv_t *fenvp);

    Листинг 9.36. Описание функций опроса и установки текущей среды вещественной арифметики.

    Отметим, что функция fesetenv() не возбуждает исключительных ситуаций, она только задает значения флагов состояния. Нормальным для обеих функций является нулевой результат.

    Сохранение текущей среды может сочетаться с ее изменением. Так, функция feholdexcept() (см. листинг 9.37) не только запоминает текущую среду по указателю fenvp, но также очищает флаги состояния и устанавливает "безостановочный" режим (продолжать выполнение при возникновении исключительных ситуаций  вещественной арифметики). Очевидно, пользоваться функцией feholdexcept() имеет смысл, если, помимо безостановочного, реализация предоставляет другие режимы обработки исключений.

    #include int feholdexcept (fenv_t *fenvp);

    Листинг 9.37. Описание функции feholdexcept().

    Функция feupdateenv() (см. листинг 9.38) выполняет еще более сложные действия. Она сохраняет в своей локальной памяти информацию о текущей исключительной ситуации, устанавливает новую среду по аргументу  fenvp и затем пытается возбудить в ней сохраненное исключение. Подобные манипуляции полезны, когда массовые вычисления производятся в безостановочном режиме, а затем режим меняется и обрабатывается все то нехорошее, что накопилось за это время.

    #include int feupdateenv (const fenv_t *fenvp);

    Листинг 9.38. Описание функции feupdateenv().

    Для опроса и установки флагов состояния стандартом POSIX-2001 предусмотрены функции fegetexceptflag() и fesetexceptflag() (см. листинг 9.39).

    #include

    int fegetexceptflag (fexcept_t *flagp, int excepts);

    int fesetexceptflag (const fexcept_t *flagp, int excepts);

    Листинг 9.39. Описание функций опроса и установки флагов состояния среды вещественной арифметики.

    Функция fegetexceptflag() помещает по указателю flagp зависящее от реализации представление флагов, заданных аргументом  excepts, и ассоциированной с ними информации.


    Функция fesetexceptflag() выполняет обратную операцию. Как и в случае функции fesetenv(), исключительные ситуации при этом не возбуждаются.

    Функции fetestexcept(), feclearexcept() и feraiseexcept() (см. листинг 9.40) служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.

    #include

    int fetestexcept (int excepts);

    int feclearexcept (int excepts);

    int feraiseexcept (int excepts);

    Листинг 9.40. Описание функций проверки, сброса и возбуждения исключительных ситуаций.

    Функция fetestexcept() проверяет, какие из флагов, заданные аргументом  excepts, в данный момент установлены; результатом служит их побитное ИЛИ.

    Функция feclearexcept() пытается сбросить, а feraiseexcept() – возбудить заданные исключительные ситуации. Побочным эффектом возбуждения ситуаций переполнения (FE_OVERFLOW) и исчезновения порядка (FE_UNDERFLOW) может стать потеря точности (FE_INEXACT).

    Опросить и установить режим округления можно с помощью функций fegetround() и fesetround() (см. листинг 9.41).

    #include

    int fegetround (void);

    int fesetround (int round);

    Листинг 9.41. Описание функций опроса и установки режима округления.

    Свидетельством неудачного завершения функции fegetround() служит отрицательный результат.

    Продемонстрируем применение некоторых функций управления средой вещественной арифметики (см. листинг 9.42).

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует применение некоторых функций */ /* управления средой вещественной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include

    #pragma STDC FENV_ACCESS ON

    int main (void) { double d1, d2, d3, s; int res;

    printf ("Представление флагов состояния вещественной " "арифметики\n"); printf (" FE_DIVBYZERO: %x\n", FE_DIVBYZERO); printf (" FE_INEXACT: %x\n", FE_INEXACT); printf (" FE_INVALID: %x\n", FE_INVALID); printf (" FE_OVERFLOW: %x\n", FE_OVERFLOW); printf (" FE_UNDERFLOW: %x\n", FE_UNDERFLOW);



    printf ("Представление режимов округления\n"); printf (" FE_DOWNWARD: %x\n", FE_DOWNWARD); printf (" FE_TONEAREST: %x\n", FE_TONEAREST); printf (" FE_TOWARDZERO: %x\n", FE_TOWARDZERO); printf (" FE_UPWARD: %x\n", FE_UPWARD);

    printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущий режим округления: %x\n", fegetround ());

    feclearexcept (FE_ALL_EXCEPT);

    /* Вызовем ситуацию исчезновения порядка */ d1 = 1; do { d1 /= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^-inf: %g\n", d1);

    feclearexcept (res); /* Вызовем ситуацию переполнения */ d2 = 1; do { d2 *= 2; } while ((res = fetestexcept (FE_ALL_EXCEPT)) == 0); printf ("Исключительные ситуации: %x\n", res); printf ("2^+inf: %g\n", d2);

    feclearexcept (res);

    /* Вызовем ситуацию деления на нуль */ d3 = 1 / d1; res = fetestexcept (FE_ALL_EXCEPT); printf ("Исключительные ситуации: %x\n", res); printf ("1/0: %g\n", d3);

    feclearexcept (res);

    /* Пример того, как может возникать потеря точности */ s = 1; do { s = (s + 2 / s) * 0.5; } while ((s * s – 2) > 0); printf ("Исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("sqrt (2): %g\n", s);

    return 0; }

    Листинг 9.42. Пример применения некоторых функций управления средой вещественной арифметики.

    Возможные результаты выполнения приведенной программы показаны на листинге 9.43.

    Представление флагов состояния вещественной арифметики FE_DIVBYZERO: 4 FE_INEXACT: 20 FE_INVALID: 1 FE_OVERFLOW: 8 FE_UNDERFLOW: 10 Представление режимов округления FE_DOWNWARD: 400 FE_TONEAREST: 0 FE_TOWARDZERO: c00 FE_UPWARD: 800 Текущие исключительные ситуации: 0 Текущий режим округления: 0 Исключительные ситуации: 30 2^-inf: 0 Исключительные ситуации: 28 2^+inf: inf Исключительные ситуации: 4 1/0: inf Исключительные ситуации: 20 sqrt (2): 1.41421



    Листинг 9.43. Возможные результаты выполнения программы, применяющей некоторые функции управления средой вещественной арифметики.

    Отметим, что и для исключительных ситуаций, и для режима округления подразумеваемые значения равны нулю, но нули это разные: первый свидетельствует об отсутствии исключений, в то время как второй обозначает режим округления до ближайшего представимого числа.

    Обратим внимание также на то, что вместе с исключительными ситуациями  переполнения и исчезновения порядка устанавливается также флаг потери точности.

    В качестве второго примера рассмотрим программу, реализующую некоторые операции интервальной арифметики (см. листинг 9.44).

    /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые операции */ /* интервальной арифметики */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */

    #include #include

    #pragma STDC FENV_ACCESS ON

    /* Интервальное представление числа */ typedef struct ditvl { double lb; double ub; } ditvl_t;

    /* * * * * * * * * * * * * * * * * * * * */ /* Сложение интервалов. */ /* Сумма помещается в выходной аргумент. */ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * */ int ditvl_add (const ditvl_t *a1, const ditvl_t *a2, ditvl_t *res) { fenv_t cfenv;

    /* Сохраним текущую среду вещественной арифметики */ if (fegetenv (&cfenv) != 0) { perror ("FEGETENV"); return (-1); }

    /* Нижние границы нужно складывать с округлением вниз */ if (fesetround (FE_DOWNWARD) != 0) { perror ("FESETROUND"); return (-1); } res->lb = a1->lb + a2->lb;

    /* Верхние границы складываются с округлением вверх */ if (fesetround (FE_UPWARD) != 0) { perror ("FESETROUND"); return (-1); } res->ub = a1->ub + a2->ub;

    /* Восстановим среду вещественной арифметики */ if (fesetenv (&cfenv) != 0) { perror ("FESETENV"); return (-1); }

    return 0; }

    /* * * * * * * * */ /* Унарный минус */ /* * * * * * * * */ int ditvl_uminus (const ditvl_t *a, ditvl_t *res) { res->lb = -(a->ub); res->ub = -(a->lb);



    return 0; }

    /* * * * * * * * */ /* Вызов функций */ /* * * * * * * * */ int main (void) { ditvl_t pi = {3.141592, 3.141593}; ditvl_t e = {2.718281, 2.718282}; ditvl_t res; ditvl_t tmp;

    printf ("Представление числа pi: (%f, %f)\n", pi.lb, pi.ub); printf ("Представление числа e: (%f, %f)\n", e.lb, e.ub);

    /* Вычислим сумму pi и e */ (void) ditvl_add (&pi, &e, &res); printf ("Сумма pi и e: (%f, %f)\n", res.lb, res.ub);

    /* Вычислим разность pi и e */ (void) ditvl_uminus (&e, &tmp); (void) ditvl_add (&pi, &tmp, &res); printf ("Разность pi и e: (%f, %f)\n", res.lb, res.ub);

    printf ("Текущие исключительные ситуации: %x\n", fetestexcept (FE_ALL_EXCEPT)); printf ("Текущие режимы округления: %x\n", fegetround ());

    return 0; }

    Листинг 9.44. Пример использования различных режимов округления.

    Программа переустанавливает режимы округления, поскольку при сложении нижних границ интервалов округлять нужно вниз, а при сложении верхних – вверх. Разумеется, в функции ditvl_add() для сохранения и восстановления режима округления можно было воспользоваться функциями fegetround()/fesetround(), а не fegetenv()/fesetenv().

    На листинге 9.45 показаны возможные результаты выполнения приведенной программы.

    Представление числа pi: (3.141592, 3.141593) Представление числа e: (2.718281, 2.718282) Сумма pi и e: (5.859873, 5.859875) Разность pi и e: (0.423310, 0.423312) Текущие исключительные ситуации: 0 Текущие режимы округления: 0

    Листинг 9.45. Возможные результаты выполнения программы, реализующей некоторые операции интервальной арифметики.

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


    Асинхронный ввод/вывод


    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных, продолжать работу параллельно с операциями передачи данных и получать асинхронные уведомления о завершении выполнения этих команд.
    После завершения асинхронной операции ввода/вывода приложению может быть доставлен сигнал.
    Жизненный цикл операции асинхронного ввода/вывода включает два этапа:
  • постановка запроса в очередь;
  • выполнение запроса, осуществление ввода/вывода.

  • Основными операциями асинхронного ввода/вывода являются чтение (aio_read()) и запись (aio_write()) данных.
    Функция lio_listio() позволяет за один вызов поставить в очередь список запросов на чтение и/или запись данных.
    Для выяснения статуса операций асинхронного ввода/вывода служат функции aio_return() и aio_error().
    Ожидающие обработки запросы на асинхронный ввод/вывод можно аннулировать, воспользовавшись функцией aio_cancel().
    Согласно стандарту POSIX-2001, списком можно не только инициировать запросы на асинхронный ввод/вывод, но и ожидать их завершения. Для этого служит функция aio_suspend().
    Функции sync(), fsync(), fdatasync(), aio_fsync() предназначены для согласования состояния буферизованных и хранимых в долговременной памяти данных, обеспечения целостности данных и файлов.


    Часы и таймеры


    Стандартом POSIX-2001 предусмотрены средства, позволяющие создавать для процессов таймеры, которые генерируют уведомления о наступлении заданного момента в виде сигналов реального времени. таймеры полезны, например, для организации периодических процессов.
    Чтобы создать для процессов таймер, следует обратиться к функции timer_create().
    Для удаления таймера служит функция timer_delete().
    Для выполнения содержательных действий с таймерами служат функции timer_gettime(), timer_settime() и timer_getoverrun().
    Отметим, что наличие функции timer_getoverrun() позволяет избавиться от неопределенно большого расхода ресурсов на постановку в очередь сигналов реального времени, которые процесс не успевает обрабатывать.
    Для приложений реального времени важна возможность использования не только процессорных, но и монотонных часов. В основном из этих соображений в стандарте POSIX-2001 присутствует расширенный аналог рассмотренной в курсе [1] функции nanosleep() – clock_nanosleep().


    Объекты в памяти


    Механизм объектов в памяти служит цели минимизации времени и унификации доступа. Стандартом POSIX-2001 предусмотрено три вида таких объектов:
  • файлы, отображенные в память;
  • объекты в разделяемой памяти;
  • объекты в типизированной памяти.

  • Рассматриваемый класс средств базируется на идее отображения объекта в адресное пространство процесса, после чего доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия.
    Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
    Отображение объектов в адресное пространство процессов осуществляется функцией mmap().
    Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect().
    Для отмены отображений в адресное пространство процессов служит функция munmap().
    При работе с объектами в памяти полезны функции truncate() и ftruncate(), позволяющие установить размер объекта равным заданной величине.
    Еще одна возможность, полезная в связи с отображением объектов в адресное пространство процессов, – синхронизация (согласование состояния) оперативной и долговременной памяти. Эту возможность реализует функция msync().
    Стандартизованный программный интерфейс к объектам в разделяемой памяти включает функции shm_open() для открытия (возможно, с созданием) подобного объекта и получения его дескриптора, а также shm_unlink() для удаления ранее созданного объекта.
    Объекты в типизированной памяти – это конфигурируемые реализацией именованные пулы памяти, доступные одному или нескольким процессорам системы через один или несколько портов.
    Каждая допустимая комбинация пула памяти и порта идентифицируется именем, определяемым при конфигурировании системы способом, зависящим от реализации. Используя это имя, объект в типизированной памяти можно открыть при помощи функции posix_typed_mem_open(), а затем отобразить в адресное пространство процесса.
    После того, как объект в типизированной памяти открыт, посредством функции posix_typed_mem_get_info() можно выяснить максимальный объем памяти, доступной для резервирования.
    Функция posix_mem_offset() позволяет выяснить адрес (смещение от начала), длину и дескриптор объекта (блока) в типизированной памяти, отображенного в адресное пространство процесса.


    Одношаговое порождение процессов


    При стандартизации средств одношагового порождения процессов преследовались две основные цели:
  • возможность использования на аппаратных конфигурациях без устройства управления памятью и без какого-либо специфического оборудования;
  • совместимость с существующими POSIX-стандартами.

  • Чтобы достичь перечисленных целей, функции posix_spawn() и posix_spawnp() контролируют шесть видов наследуемых сущностей:
  • файловые дескрипторы;
  • идентификатор группы процессов;
  • идентификаторы пользователя и группы процесса;
  • параметры планирования;
  • маску сигналов;
  • способ обработки сигналов, игнорируемых родительским процессом.

  • Как правило, все открытые дескрипторы родительского процесса остаются таковыми и в порожденном, за исключением тех, у которых установлен флаг FD_CLOEXEC. Кроме того, принимается во внимание объект типа posix_spawn_file_actions_t, который содержит действия по закрытию, открытию и/или дублированию файловых дескрипторов. Для формирования объектов типа posix_spawn_file_actions_t служат функции posix_spawn_file_actions_addclose(), posix_spawn_file_actions_addopen() и posix_spawn_file_actions_adddup2().
    За контроль других сущностей, наследуемых при одношаговом порождении процессов, отвечают атрибутные объекты, для формирования и опроса которых служат функции posix_spawnattr_init(), posix_spawnattr_destroy(), posix_spawnattr_getflags(), posix_spawnattr_setflags(), posix_spawnattr_getpgroup(), posix_spawnattr_setpgroup(), posix_spawnattr_getschedparam(), posix_spawnattr_setschedparam(), posix_spawnattr_getschedpolicy(), posix_spawnattr_setschedpolicy(), posix_spawnattr_getsigdefault(), posix_spawnattr_setsigdefault(), posix_spawnattr_getsigmask(), posix_spawnattr_setsigmask().
    Реализация одношагового порождения процессов может быть непосредственной, выполненной на уровне ядра операционной системы, и библиотечной, основанной на функциях fork()/exec(). Естественно, надеяться на повышение эффективности по сравнению с двухшаговым порождением имеет смысл только в первом случае.


    Основные понятия и объекты, рассматриваемые в курсе


    Настоящий курс является продолжением курса [1]. В принципе, разделение единой темы "Программирование в стандарте POSIX" на две части носит скорее технический, чем принципиальный характер, однако у второй части есть свой стержень – мобильное программирование приложений реального времени.
    Основной структурной единицей приложения реального времени является поток управления. В этом контексте процессы трактуются не столько как самостоятельные сущности, сколько как организующие оболочки для потоков. Более точно, под процессом понимается адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами, которые этим потокам требуются.
    Для задания характеристик потоков управления (и других рассматриваемых в настоящем курсе сущностей) в стандарте POSIX-2001 активно используются атрибутные объекты. Атрибутный объект – это шаблон, по которому можно создать произвольное число потоков с одинаковыми характеристиками, избегая их многократной спецификации.
    По отношению к потокам управления вводится понятие безопасных функций, которые можно вызывать параллельно в нескольких потоках без нарушения корректности их функционирования. К числу безопасных принадлежат "чистые" функции, а также функции, обеспечивающие взаимное исключение перед доступом к разделяемым объектам. Если в стандарте явно не оговорено противное, функция считается потоково-безопасной.
    Для приложений реального времени исключительно важно и активно используется понятие планирования.
    Планированием, согласно стандарту POSIX-2001, называется применение политики выбора процесса или потока управления, готового к выполнению, для его перевода в число активных, а также политики изменения списков равноприоритетных потоков управления.
    Под политикой планирования понимается набор правил, используемых для определения порядка выполнения процессов или потоков управления для достижения некоторой цели.
    Операции с потоками управления можно подразделить на две группы:
  • создание, терминирование, выполнение других управляющих операций;
  • синхронизация.


  • Настоящий курс является продолжением курса [1]. В принципе, разделение единой темы "Программирование в стандарте POSIX" на две части носит скорее технический, чем принципиальный характер, однако у второй части есть свой стержень – мобильное программирование приложений реального времени.
    Основной структурной единицей приложения реального времени является поток управления. В этом контексте процессы трактуются не столько как самостоятельные сущности, сколько как организующие оболочки для потоков. Более точно, под процессом понимается адресное пространство вместе с выполняемыми в нем потоками управления, а также системными ресурсами, которые этим потокам требуются.
    Для задания характеристик потоков управления (и других рассматриваемых в настоящем курсе сущностей) в стандарте POSIX-2001 активно используются атрибутные объекты. Атрибутный объект – это шаблон, по которому можно создать произвольное число потоков с одинаковыми характеристиками, избегая их многократной спецификации.
    По отношению к потокам управления вводится понятие безопасных функций, которые можно вызывать параллельно в нескольких потоках без нарушения корректности их функционирования. К числу безопасных принадлежат "чистые" функции, а также функции, обеспечивающие взаимное исключение перед доступом к разделяемым объектам. Если в стандарте явно не оговорено противное, функция считается потоково-безопасной.
    Для приложений реального времени исключительно важно и активно используется понятие планирования.
    Планированием, согласно стандарту POSIX-2001, называется применение политики выбора процесса или потока управления, готового к выполнению, для его перевода в число активных, а также политики изменения списков равноприоритетных потоков управления.
    Под политикой планирования понимается набор правил, используемых для определения порядка выполнения процессов или потоков управления для достижения некоторой цели.
    Операции с потоками управления можно подразделить на две группы:
  • создание, терминирование, выполнение других управляющих операций;
  • синхронизация.




  • В таком порядке они и рассматриваются в настоящем курсе.

    При анализе средств синхронизации потоков управления следует учитывать, что, по сравнению с процессами, потоки управления характеризуются двумя особенностями:

  • на порядок меньшими накладными расходами на обслуживание;
  • существенно более тесным взаимодействием в общем адресном пространстве.


  • Чтобы быть практически полезными, средства синхронизации, специально ориентированные на потоки управления, должны быть оптимизированы с учетом обеих отмеченных особенностей.

    К числу таких средств, присутствующих в стандарте POSIX-2001, принадлежат мьютексы, переменные условия, блокировки чтение-запись, спин-блокировки и барьеры.

    Мьютекс – это синхронизирующий объект, использование которого позволяет множеству потоков управления упорядочить доступ к разделяемым данным.

    Переменная условия в качестве синхронизирующего объекта дает потокам управления возможность многократно приостанавливать выполнение, пока некий ассоциированный предикат не станет истинным. Говорят, что поток, выполнение которого приостановлено на переменной условия, блокирован на этой переменной.

    Блокировки чтение-запись (много читателей или один писатель) в каждый момент времени позволяют нескольким потокам управления одновременно иметь к данным доступ на чтение или только одному потоку – доступ на запись.

    Спин-блокировки представляют собой низкоуровневое средство синхронизации. Как и мьютексы, они предназначены для упорядочения доступа множества потоков управления к разделяемым данным.

    Барьеры предназначены для синхронизации множества потоков управления в определенной точке их выполнения.

    Средства синхронизации могут использоваться для достижения двух существенно разных целей:

  • захват (как правило, на короткое время) разделяемого объекта для защиты критического интервала;
  • ожидание (долгое или даже потенциально неограниченное) наступления некоторого события, выполнения некоторого условия.


  • Мьютексы и блокировки можно отнести к первой из выделенных категорий, переменные условия и барьеры – ко второй.



    Тема " мобильное программирование приложений реального времени" разбита в курсе на следующие разделы:

  • одношаговое порождение процессов;
  • сигналы реального времени;
  • часы и таймеры;
  • средства межпроцессного взаимодействия;
  • объекты в памяти;
  • удержание процессов в памяти;
  • приоритетное планирование;
  • асинхронный ввод/вывод;
  • рекомендательные интерфейсы.


  • Одношаговое порождение процессов основано на применении функций posix_spawn() и posix_spawnp().

    Под сигналами реального времени понимается не нарушающее совместимости расширение общего механизма сигналов, повышающее детерминированность за счет постановки сигналов, асинхронно доставленных приложению, в очередь.

    Основные понятия, ассоциированные с часами и таймерами, были рассмотрены в курсе [1]. В настоящем курсе анализируются необязательные элементы стандарта POSIX-2001, специфичные для реального времени.

    В качестве средств межпроцессного взаимодействия в реальном времени в стандарт POSIX-2001 включены очереди сообщений, семафоры и разделяемые сегменты памяти (см. также курс [1], где детально анализируется другой класс средств межпроцессного взаимодействия с аналогичными названиями).

    Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти. Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.

    Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти.

    Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.

    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.

    Рекомендательные интерфейсы – это средство проинформировать операционную систему о поведении мобильного приложения, чтобы ОС могла принять меры для оптимизации его обслуживания.


    Например, если прогнозируется последовательный доступ к файлу, ОС может осуществлять предвыборку (опережающее чтение), чтобы совместить во времени ввод и обработку данных.

    Приложения реального времени сложно не только разрабатывать, но и отлаживать, поскольку в понятие семантической корректности входит дополнительный компонент – соблюдение временных ограничений. Для решения этой проблемы в стандарте POSIX-2001 предусмотрен механизм трассировки.

    Под трассировкой понимается порождение, накопление и анализ данных о событиях, имевших место при выполнении пользовательского приложения.

    Применительно к приложениям реального времени трассировка помогает достичь по крайней мере трех целей:

  • оптимизировать структуру приложения на основе анализа трассировочных данных;
  • отладить приложение;
  • выявить причину аварийного завершения работы приложения.


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

    Таковы основные понятия и объекты, ставшие предметом рассмотрения в настоящем курсе.



    В таком порядке они и рассматриваются в настоящем курсе.

    При анализе средств синхронизации потоков управления следует учитывать, что, по сравнению с процессами, потоки управления характеризуются двумя особенностями:

  • на порядок меньшими накладными расходами на обслуживание;
  • существенно более тесным взаимодействием в общем адресном пространстве.


  • Чтобы быть практически полезными, средства синхронизации, специально ориентированные на потоки управления, должны быть оптимизированы с учетом обеих отмеченных особенностей.

    К числу таких средств, присутствующих в стандарте POSIX-2001, принадлежат мьютексы, переменные условия, блокировки чтение-запись, спин-блокировки и барьеры.

    Мьютекс – это синхронизирующий объект, использование которого позволяет множеству потоков управления упорядочить доступ к разделяемым данным.

    Переменная условия в качестве синхронизирующего объекта дает потокам управления возможность многократно приостанавливать выполнение, пока некий ассоциированный предикат не станет истинным. Говорят, что поток, выполнение которого приостановлено на переменной условия, блокирован на этой переменной.

    Блокировки чтение-запись (много читателей или один писатель) в каждый момент времени позволяют нескольким потокам управления одновременно иметь к данным доступ на чтение или только одному потоку – доступ на запись.

    Спин-блокировки представляют собой низкоуровневое средство синхронизации. Как и мьютексы, они предназначены для упорядочения доступа множества потоков управления к разделяемым данным.

    Барьеры предназначены для синхронизации множества потоков управления в определенной точке их выполнения.

    Средства синхронизации могут использоваться для достижения двух существенно разных целей:

  • захват (как правило, на короткое время) разделяемого объекта для защиты критического интервала;
  • ожидание (долгое или даже потенциально неограниченное) наступления некоторого события, выполнения некоторого условия.


  • Мьютексы и блокировки можно отнести к первой из выделенных категорий, переменные условия и барьеры – ко второй.



    Тема " мобильное программирование приложений реального времени" разбита в курсе на следующие разделы:

  • одношаговое порождение процессов;
  • сигналы реального времени;
  • часы и таймеры;
  • средства межпроцессного взаимодействия;
  • объекты в памяти;
  • удержание процессов в памяти;
  • приоритетное планирование;
  • асинхронный ввод/вывод;
  • рекомендательные интерфейсы.


  • Одношаговое порождение процессов основано на применении функций posix_spawn() и posix_spawnp().

    Под сигналами реального времени понимается не нарушающее совместимости расширение общего механизма сигналов, повышающее детерминированность за счет постановки сигналов, асинхронно доставленных приложению, в очередь.

    Основные понятия, ассоциированные с часами и таймерами, были рассмотрены в курсе [1]. В настоящем курсе анализируются необязательные элементы стандарта POSIX-2001, специфичные для реального времени.

    В качестве средств межпроцессного взаимодействия в реальном времени в стандарт POSIX-2001 включены очереди сообщений, семафоры и разделяемые сегменты памяти (см. также курс [1], где детально анализируется другой класс средств межпроцессного взаимодействия с аналогичными названиями).

    Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти. Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.

    Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти.

    Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.

    Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.

    Рекомендательные интерфейсы – это средство проинформировать операционную систему о поведении мобильного приложения, чтобы ОС могла принять меры для оптимизации его обслуживания.


    Например, если прогнозируется последовательный доступ к файлу, ОС может осуществлять предвыборку (опережающее чтение), чтобы совместить во времени ввод и обработку данных.

    Приложения реального времени сложно не только разрабатывать, но и отлаживать, поскольку в понятие семантической корректности входит дополнительный компонент – соблюдение временных ограничений. Для решения этой проблемы в стандарте POSIX-2001 предусмотрен механизм трассировки.

    Под трассировкой понимается порождение, накопление и анализ данных о событиях, имевших место при выполнении пользовательского приложения.

    Применительно к приложениям реального времени трассировка помогает достичь по крайней мере трех целей:

  • оптимизировать структуру приложения на основе анализа трассировочных данных;
  • отладить приложение;
  • выявить причину аварийного завершения работы приложения.


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

    Таковы основные понятия и объекты, ставшие предметом рассмотрения в настоящем курсе.


    Передача и прием сообщений в реальном времени


    Над описанными в настоящем курсе очередями сообщений определены следующие группы функций:
  • открытие очереди;
  • отправка сообщения в очередь;
  • прием (синхронный или асинхронный) сообщения из очереди;
  • изменение атрибутов очереди;
  • регистрация на получение уведомления о появлении сообщения в очереди;
  • закрытие очереди;
  • удаление очереди.

  • Для открытия очереди служит функция mq_open(). Одну очередь могут открыть несколько посылающих и/или принимающих сообщения процессов. При открытии может производиться контроль прав доступа.
    Отправка осуществляется функциями mq_send() и mq_timedsend().
    Для извлечения сообщений из очереди служат функции mq_receive() и mq_timedreceive(). На порядок приема влияет имеющийся механизм приоритетов сообщений.
    Для опроса и/или установки атрибутов очереди служат функции mq_getattr() и mq_setattr(). Для каждой очереди задается фиксированная верхняя граница размера сообщений, которые могут быть в эту очередь отправлены.
    Посредством функции mq_notify() процесс может зарегистрироваться на получение уведомления о том, что в очередь, бывшую до этого пустой, поступило сообщение.
    После того, как процесс завершил работу с очередью сообщений, соответствующий дескриптор следует закрыть, воспользовавшись функцией mq_close().
    Очередь сообщений можно удалить с помощью функции mq_unlink().


    Потоки управления


    В рамках любого процесса существует по крайней мере один поток управления. Потоки в пределах процесса именуются идентификаторами. Для выяснения собственного идентификатора поток может воспользоваться функцией pthread_self().
    Порождению потоков управления обычно предшествует создание атрибутного объекта, осуществляемое функцией pthread_attr_init(). Когда объект перестает быть нужным, его ликвидируют, вызывая pthread_attr_destroy().
    Атрибутные объекты трактуются стандартом POSIX-2001 как абстрактные. Их структура скрыта от приложений, а все манипуляции выполняются посредством методов для выборки и изменения атрибутов.
    В число поддерживаемых входят атрибуты стека (функции pthread_attr_getstack(), pthread_attr_setstack(), pthread_attr_getguardsize(), pthread_attr_setguardsize()), планирования (pthread_attr_getschedparam(), pthread_attr_setschedparam(), pthread_attr_getschedpolicy(), pthread_attr_setschedpolicy(), pthread_attr_getscope(), pthread_attr_setscope(), pthread_attr_getinheritsched(), pthread_attr_setinheritsched()) и обособленности (pthread_attr_getdetachstate(), pthread_attr_setdetachstate()).
    Обратим внимание на средство контроля переполнения стека – защитную область, располагающуюся за верхней границей стека. При переполнении и попадании указателя стека в защитную область операционная система должна фиксировать ошибку.
    Значения атрибутов планирования могут задаваться не только при создании потока управления. Стандарт POSIX-2001 предоставляет средства для их динамического изменения и опроса – функции pthread_getschedparam(), pthread_setschedparam(), pthread_setschedprio(), pthread_getconcurrency(), pthread_setconcurrency().
    К числу атрибутов потока управления можно отнести обслуживающие его часы процессорного времени. Для выяснения их идентификатора достаточно обратиться к функции pthread_getcpuclockid().
    Еще один атрибут потока управления – маска блокированных сигналов. Поток может опросить и/или изменить ее посредством вызова функции pthread_sigmask().
    Все потоки управления одного процесса разделяют общее адресное пространство и, следовательно, имеют общие данные.
    Чтобы сделать некоторые данные индивидуальными для потока, нужно с помощью функции pthread_key_create() создать ключ и ассоциировать с ним индивидуальные данные, воспользовавшись функцией pthread_setspecific(). В дальнейшем эти данные можно извлекать посредством функции pthread_getspecific(). Подчеркнем, что при обращении по одному (разделяемому) ключу разные потоки будут получать доступ к разным данным.

    Создать один ключ, очевидно, нужно один раз. Для решения проблемы однократного выполнения инициализирующих действий в многопотоковой среде стандарт POSIX-2001 предлагает функцию pthread_once().

    За удаление ключа индивидуальных данных потоков управления отвечает функция pthread_key_delete().

    Модель порождения потоков управления отличается от соответствующей модели для процессов. При создании нового потока задается функция, с вызова которой начнется его выполнение, то есть вместо пары вида fork()/exec() создающий поток должен обратиться лишь к одной функции – pthread_create().

    От "родительского" вновь созданный поток управления наследует маску сигналов и среду вещественной арифметики.

    К числу средств создания потоков можно отнести и функцию fork(). С ней можно ассоциировать обработчики, зарегистрировав их с помощью функции pthread_atfork(). В каждом обращении к pthread_atfork() фигурируют три обработчика. Первый выполняется в контексте потока, вызвавшего fork(), до разветвления процесса; второй – в том же контексте, но после разветвления; третий – в контексте единственного потока порожденного процесса.

    поток управления можно терминировать изнутри и извне (из других потоков того же процесса). Обычное средство внутреннего терминирования – выход из стартовой функции. Тот же эффект достигается вызовом функции pthread_exit().

    Заказать терминирование извне потока управления с заданным идентификатором можно, воспользовавшись функцией pthread_cancel().

    На выполнение "заказа" влияют состояние восприимчивости к терминированию (разрешено/запрещено) и тип терминирования (отложенное или немедленное, асинхронное), установленные для потока, а также достижение точки терминирования.


    Эти атрибуты опрашиваются и изменяются с помощью функций pthread_setcancelstate(), pthread_setcanceltype() и pthread_testcancel().

    Стандартом POSIX- 2001 предусмотрено существование стека обработчиков завершения, ассоциированного с потоком управления. Операции над этим стеком возложены на функции pthread_cleanup_push() и pthread_cleanup_pop().

    После того как выполнятся все обработчики завершения, в неспецифицированном порядке вызываются деструкторы индивидуальных данных.

    Возможность дождаться завершения заданного потока управления реализуется функцией pthread_join().

    Помимо заказа на терминирование, потоку управления можно направить сигнал, воспользовавшись функцией pthread_kill().

    Полезная операция, связанная с обработкой завершения потока управления, – его динамическое обособление, выполняемое функцией pthread_detach().

    Обратим внимание на то, что, согласно стандарту POSIX-2001, функции, обслуживающие потоки управления, никогда не завершаются с частичным результатом и не выдают код ошибки EINTR. Восстановление нормального состояния после того, как ожидание было прервано доставкой и обработкой сигнала, возлагается на операционную систему, а не на приложение.


    Приоритетное планирование


    У каждого потока управления (процесса) в каждый момент времени есть определенный приоритет. Равноприоритетные потоки управления, готовые к выполнению, объединяются в списки. Эти списки упорядочены, от первого элемента (головы) к последнему (хвосту).
    Цель политики планирования состоит в определении допустимых операций над множеством списков (например, перемещение потоков между списками и внутри них).
    Реализация, удовлетворяющая стандарту POSIX, должна выбирать для выполнения процесс (поток управления), находящийся в голове наиболее приоритетного списка, независимо от ассоциированной политики планирования.
    Политика SCHED_FIFO (планирование по очереди) предписывает упорядочивать потоки управления в пределах каждого списка по длительности ожидания.
    Чтобы сделать планирование более справедливым по отношению к равноприоритетным процессам, можно воспользоваться политикой SCHED_RR (циклическое планирование). Она эквивалентна SCHED_FIFO с одним исключением: когда время, в течение которого поток управления занимал процессор, становится больше или равным результату функции sched_rr_get_interval(), он перемещается в хвост соответствующего списка, а для выполнения выбирается головной поток. Таким образом, ни один из равноприоритетных потоков не сможет монополизировать процессор.
    По сравнению с SCHED_FIFO и SCHED_RR, политика SCHED_SPORADIC (спорадическое планирование) представляется гораздо более сложной. Она позволяет резервировать процессорное время для выполнения непериодических действий.
    Четвертая политика планирования, которую должны поддерживать реализации, соответствующие стандарту POSIX, называется "прочей" (SCHED_OTHER). Она необходима, чтобы мобильные приложения могли заявить, что они больше не нуждаются в политике планирования реального времени.
    Стандарт POSIX-2001 предусматривает следующие функции управления планированием процессов: sched_getscheduler() (опрос политики планирования процесса), sched_getparam() (опрос параметров планирования процесса), sched_setscheduler() (установка политики и параметров планирования процесса) и sched_setparam() (установка параметров планирования процесса).
    Стандарт POSIX-2001 предоставляет также средства для опроса характеристик политик планирования – минимального (sched_get_priority_min()) и максимального (sched_get_priority_max()) среди допустимых приоритетов, а также величины кванта выделяемого процессорного времени для политики циклического планирования (sched_rr_get_interval()).
    Функция sched_yield() позволяет вызывающему потоку управления добровольно уступить процессор.


    Рекомендательные интерфейсы


    В стандарте POSIX-2001 предусмотрена оптимизация работы с файлами, которая может затрагивать следующие аспекты осуществляемого приложением ввода/вывода:
  • последовательный доступ;
  • кэширование;
  • передача данных;
  • предварительное резервирование долговременной и оперативной памяти.

  • Описанные возможности реализуют функции posix_fadvise(), posix_fallocate(), posix_madvise() и posix_memalign().
    Ожидаемое поведение может специфицироваться как:
  • отсутствие рекомендаций;
  • последовательный доступ;
  • случайный доступ;
  • доступ в ближайшее время;
  • отсутствие доступа в ближайшее время;
  • однократный доступ.

  • При оптимизации обмена данными с файлами, наряду с применением рекомендательных интерфейсов, целесообразно учитывать значения ряда конфигурационных констант:
    POSIX_REC_MIN_XFER_SIZE
    Минимальное рекомендуемое число передаваемых байт при обмене данными с файлами. Рекомендуется также, чтобы и смещение передаваемой порции данных от начала файла было кратным POSIX_REC_MIN_XFER_SIZE.
    POSIX_REC_MAX_XFER_SIZE
    Максимальное рекомендуемое число передаваемых байт при обмене данными с файлами.
    POSIX_REC_XFER_ALIGN
    Рекомендуемое значение для выравнивания границы буфера обмена данными с файлами.


    Семафоры реального времени


    Семафор реального времени – это эффективный механизм синхронизации процессов, который представляет собой общесистемный разделяемый ресурс, имеющий неотрицательное целочисленное значение.
    Основными операциями над семафором являются захват и освобождение. Если делается попытка захвата семафора, когда его значение равно нулю, выполнение вызывающего потока управления приостанавливается и он добавляется к множеству потоков, ждущих на семафоре.
    Если при освобождении семафора множество ждущих потоков было непусто, один из них удаляется из этого множества и его выполнение возобновляется; в противном случае значение семафора просто увеличивается.
    Семафоры бывают именованными и безымянными. Первые именуются цепочками символов и создаются функцией sem_open() с флагом O_CREAT, вторые создаются функцией sem_init().
    Именованный семафор можно закрыть, обратившись к функции sem_close(), и удалить с помощью функции sem_unlink(); для ликвидации неименованных семафоров служит функция sem_destroy().
    Для захвата семафоров служат функции sem_wait(), sem_trywait() и sem_timedwait().
    Освобождение семафора осуществляется функцией sem_post().
    Функция sem_getvalue() позволяет опросить значение семафора, не меняя его состояния.
    Необходимо отметить, что описание семафоров реального времени в стандарте POSIX-2001 внутренне противоречиво. В большинстве мест явно написано, что семафоры эти целочисленные; в то же время, из описания функции sem_wait() следует, что имеются в виду бинарные семафоры. Вероятно, ошибочно это последнее описание.


    Сигналы реального времени


    Приложениям реального времени требуются средства надежного, детерминированного, асинхронного извещения (уведомления) о событиях. Для удовлетворения этой потребности механизм сигналов был расширен, а в стандарт была введена необязательная часть, получившая название "сигналы реального времени" (Realtime Signals Extension, RTS).
    Номера сигналов реального времени лежат в диапазоне от SIGRTMIN до SIGRTMAX. Всего таких сигналов должно быть не меньше, чем RTSIG_MAX.
    "Жизненный цикл" сигналов реального времени состоит из четырех фаз:
  • генерация;
  • ожидание;
  • доставка;
  • обработка.

  • Сигналы реального времени могут генерироваться при срабатывании высокоточных таймеров, завершении операции асинхронного ввода/вывода, поступлении межпроцессного сообщения, выполнении функции sigqueue() и т.д.
    На фазе генерации сигналов центральную роль играет структура типа sigevent, которая, помимо прочего, определяет способ уведомления и значение сигнала.
    Стандартом POSIX-2001 предусмотрено три способа уведомления об асинхронных событиях: SIGEV_NONE (отсутствие уведомления), SIGEV_SIGNAL (генерация сигнала с возможной постановкой в очередь к процессу) и SIGEV_THREAD (вызов функции).
    Значение сигнала реального времени может быть целым числом или указателем.
    Сгенерировать сигнал реального времени можно, обратившись к функции sigqueue().
    После того, как сигнал сгенерирован, наступает фаза ожидания. Сигналы одного типа ставятся в очередь к процессу в порядке генерации.
    Дождаться доставки сигнала реального времени можно с помощью функций sigwaitinfo() и sigtimedwait().
    При наличии нескольких неблокированных ждущих сигналов реального времени, их доставка процессу производится в порядке возрастания номеров. Тем самым поддерживается ранжированная по приоритетам доставка уведомлений.
    Для приложений реального времени, функционирующих на аппаратных конфигурациях с ограниченными ресурсами, может оказаться полезной возможность (распространяющаяся на все сигналы) выполнять функции обработки сигналов не на основном, а на альтернативном стеке. Функция sigaltstack() позволяет установить и/или опросить характеристики альтернативного стека.
    Обработка сигналов (не обязательно реального времени) нередко сочетается с нелокальными переходами. В таких случаях могут оказаться полезными функции sigsetjmp() и siglongjmp().


    Средства синхронизации потоков управления


    Функции, обслуживающие мьютексы, можно разбить на следующие группы:
  • инициализация и разрушение мьютексов: pthread_mutex_init(), pthread_mutex_destroy();
  • захват и освобождение мьютексов: pthread_mutex_lock(), pthread_mutex_trylock(), pthread_mutex_timedlock(), pthread_mutex_unlock();
  • опрос и установка атрибутов мьютекса:
    pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling();
  • инициализация и разрушение атрибутных объектов мьютексов:
    pthread_mutexattr_init(), pthread_mutexattr_destroy();
  • опрос и установка атрибутов мьютекса в атрибутных объектах:
    pthread_mutexattr_gettype(), pthread_mutexattr_settype(), pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared(), pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol(), pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling();

  • Для инициализации статически описанных мьютексов с подразумеваемыми значениями атрибутов целесообразно пользоваться макросом PTHREAD_MUTEX_INITIALIZER.
    У инициализированного мьютекса имеется четыре атрибута:
  • тип (обслуживается функциями pthread_mutexattr_gettype() и pthread_mutexattr_settype());
  • верхняя грань приоритетов выполнения (функции pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling(), pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling());
  • протокол (pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol());
  • признак использования несколькими процессами (pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared()).

  • В стандарте POSIX-2001 определены четыре типа мьютексов:
    PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_DEFAULT.
    Атрибут "протокол" влияет на планирование потока управления во время владения мьютексом. Согласно стандарту, возможных протоколов три: PTHREAD_PRIO_NONE, PTHREAD_PRIO_INHERIT, PTHREAD_PRIO_PROTECT.
    Пусть имеется некоторый предикат (условие), зависящий от значений переменных, разделяемых несколькими потоками управления. Совместное использование мьютексов, переменных условия и обслуживающих их функций позволяет организовать экономное ожидание состояния истинности этого предиката.
    С разделяемыми переменными, фигурирующими в предикате, ассоциируется мьютекс, который необходимо захватить перед началом проверок. Затем поток управления входит в цикл вида

    while (! предикат) { Ожидание на переменной условия с освобождением мьютекса. После успешного завершения ожидания поток вновь оказывается владельцем мьютекса. }

    После нормального выхода из цикла проверяемое условие истинно; можно выполнить требуемые действия и освободить мьютекс.

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

    Функции, предлагаемые стандартом POSIX-2001 для обслуживания переменных условия, можно разделить на следующие группы:

  • инициализация и разрушение переменных условия:

    pthread_cond_init(), pthread_cond_destroy();
  • блокирование (ожидание) на переменной условия:

    pthread_cond_wait(), pthread_cond_timedwait();
  • разблокирование (прекращение ожидания) потоков управления, блокированных на переменной условия: pthread_cond_broadcast(), pthread_cond_signal();
  • инициализация и разрушение атрибутных объектов переменных условия: pthread_condattr_init(), pthread_condattr_destroy();
  • опрос и установки атрибутов переменных условия в атрибутных объектах: признака использования несколькими процессами (обслуживается функциями pthread_condattr_getpshared(), pthread_condattr_setpshared()) и идентификатора часов реального времени, используемых для ограничения ожидания на переменной условия (функции pthread_condattr_getclock(), pthread_condattr_setclock().


  • Блокировки чтение-запись – интеллектуальное средство синхронизации, отличающее читателей от писателей. В большинстве случаев разделяемые данные чаще читают, чем изменяют, и это делает блокировки чтение-запись весьма употребительными.

    Применительно к блокировкам чтение-запись предоставляются следующие группы функций:

  • инициализация и разрушение блокировок:

    pthread_rwlock_init(), pthread_rwlock_destroy();
  • установка блокировки на чтение: pthread_rwlock_rdlock(), pthread_rwlock_tryrdlock(), pthread_rwlock_timedrdlock();
  • установка блокировки на запись: pthread_rwlock_wrlock(), pthread_rwlock_trywrlock(), pthread_rwlock_timedwrlock();
  • снятие блокировки чтение-запись: pthread_rwlock_unlock();
  • инициализация и разрушение атрибутных объектов блокировок:



    pthread_rwlockattr_init(), pthread_rwlockattr_destroy();
  • опрос и установки атрибутов блокировок в атрибутных объектах:

    pthread_rwlockattr_getpshared(), pthread_rwlockattr_setpshared().


  • Спин- блокировки представляют собой низкоуровневое средство синхронизации, предназначенное в первую очередь для применения в многопроцессорных конфигурациях с разделяемой памятью. По сравнению с мьютексами спин-блокировки могут иметь то преимущество, что (активное) ожидание и установка не связаны с переключением контекстов, активизацией планировщика и т.п. Если ожидание оказывается кратким, минимальными будут и накладные расходы.

    Согласно стандарту POSIX-2001, спин-блокировки обслуживаются следующими группами функций:

  • инициализация и разрушение спин-блокировок:

    pthread_spin_init(), pthread_spin_destroy();
  • установка спин-блокировки: pthread_spin_lock(), pthread_spin_trylock();
  • снятие спин-блокировки: pthread_spin_unlock().


  • Барьеры – своеобразное средство синхронизации, идея которого заключается в том, чтобы в определенной точке ожидания собралось заданное число потоков управления. Только после этого они смогут продолжить выполнение.

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

    Функции, ассоциированные с барьерами, подразделяются на следующие группы:

  • инициализация и разрушение барьеров: pthread_barrier_init(), pthread_barrier_destroy();
  • синхронизация на барьере: pthread_barrier_wait();
  • инициализация и разрушение атрибутных объектов барьеров:

    pthread_barrierattr_init(), pthread_barrierattr_destroy();
  • опрос и установки атрибутов барьеров в атрибутных объектах:

    pthread_barrierattr_getpshared(), pthread_barrierattr_setpshared(). Когда к функции pthread_barrier_wait() обратилось требуемое число потоков управления, одному из них (стандарт POSIX-2001 не специфицирует, какому именно) в качестве результата возвращается именованная константа PTHREAD_BARRIER_SERIAL_THREAD, а всем другим достаются нули.После этого барьер возвращается в начальное (инициализированное) состояние, а выделенный поток может выполнить соответствующие объединительные действия.



  • Средства удержания процессов в памяти


    Для повышения детерминированности поведения приложений реального времени в стандарт POSIX введены средства удержания в физической памяти страниц из адресного пространства процессов.
    Для удержания в физической памяти группы страниц из адресного пространства процесса служит функция mlock().
    Удержание отменяется после вызовов fork() и exec(), а также в результате ликвидации по какой-либо причине соответствующей части адресного пространства процесса. Явная отмена удержания группы страниц реализуется функцией munlock().
    Если нужно удерживать в памяти все адресное пространство процесса (что имеет место для большинства приложений реального времени), целесообразно воспользоваться функциями mlockall() и munlockall().


    Технологические интерфейсы


    Для работы с системным журналом стандарт POSIX-2001 предлагает функции записи сообщений (syslog()), установки фильтра (маски журналируемых сообщений, setlogmask()) и других параметров журналирования (openlog()) и, наконец, завершения работы с системным журналом (closelog()).
    У журналируемых сообщений имеются такие атрибуты, как уровень серьезности и источник.
    К этой же прикладной области можно отнести служебную программу logger и функцию fmtmsg().
    Стандартом POSIX-2001 предусмотрен набор функций для работы с базой данных учетной информации о пользователях. Эти функции реализуют последовательный просмотр учетных записей (getutxent()), поиск в базе (getutxid(), getutxline()), модификацию или добавление записей (pututxline()), возврат к началу (setutxent()) и завершение работы с базой (endutxent()).
    Универсальный характер носят функции для работы с простыми базами данных. Подобную базу можно открыть (dbm_open()) и закрыть (dbm_close()), выбрать (dbm_fetch()), сохранить (dbm_store()) и удалить (dbm_delete()) запись по ключу, перебрать имеющиеся в базе ключи (dbm_firstkey(), dbm_nextkey()), опросить статус ошибки (dbm_error()) и очистить его (dbm_clearerr()).
    Важный подкласс технологических интерфейсов образуют средства поиска и сортировки.
    Бинарный поиск представлен функцией bsearch(), последовательный – функциями lsearch() (поиск с вставкой) и lfind(). Управление хэш-таблицами осуществляется посредством функций hcreate(), hdestroy() и hsearch(). Бинарные деревья поиска обслуживаются функциями tsearch() (поиск с вставкой), tfind(), tdelete() и twalk() (обход деревьев).
    Для сортировки массивов целесообразно пользоваться функцией qsort(). Существует также служебная программа tsort.
    Функции insque() и remque() реализуют вставку и удаление элементов очереди.
    Согласно стандарту POSIX-2001, пользовательский контекст потока управления включает содержимое машинных регистров, маску сигналов и текущий стек выполнения. Стандарт предоставляет функции для опроса (getcontext()), модификации (makecontext()) и смены (setcontext() и swapcontext()) пользовательских контекстов.

    Рассматриваемая в стандарте POSIX- 2001 среда вещественной арифметики включает сущности двух видов: флаги состояния и управляющие режимы.

    Если приложение проверяет флаги состояния, устанавливает собственные управляющие режимы или выполняется в режимах, отличных от подразумеваемого, то при компиляции необходимо воспользоваться управляющим комментарием (#pragma) FENV_ACCESS:

    #pragma STDC FENV_ACCESS ON

    Опросить и установить текущую среду вещественной арифметики можно с помощью функций fegetenv() и fesetenv().

    Сохранение текущей среды может сочетаться с ее изменением. Функция feholdexcept() очищает флаги состояния и устанавливает "безостановочный" режим, а feupdateenv() сохраняет в своей локальной памяти информацию о текущей исключительной ситуации, устанавливает новую среду и затем пытается возбудить в ней сохраненное исключение.

    Для опроса и установки флагов состояния стандартом POSIX-2001 предусмотрены функции fegetexceptflag() и fesetexceptflag().

    Функции fetestexcept(), feclearexcept() и feraiseexcept() служат, соответственно, для проверки, сброса и возбуждения исключительных ситуаций.

    Опросить и установить режим округления можно с помощью функций fegetround() и fesetround().

    Обход файловой иерархии – типовая задача, для решения которой стандартом POSIX-2001 предлагаются две сходные функции – ftw() и nftw().

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

    В качестве последнего элемента технологических интерфейсов рассматриваются функции для работы с псевдотерминалами.

    В стандарте POSIX-2001 выстроена пятиэтапная модель получения доступа к псевдотерминалу:

  • открытие главного устройства псевдотерминала, получение его файлового дескриптора (осуществляется функцией posix_openpt());
  • разблокирование подчиненного устройства псевдотерминала (функция unlockpt());
  • формирование прав доступа к подчиненному устройству псевдотерминала (grantpt());
  • получение имени подчиненного устройства псевдотерминала (ptsname());
  • открытие подчиненного устройства псевдотерминала, получение его файлового дескриптора (open()).


  • На этом мы завершаем описание средств программирования приложений в стандарте POSIX.


    Трассировка


    С логической точки зрения в трассировке участвуют три процесса:
  • трассируемый (целевой);
  • трассирующий (управляющий трассировкой);
  • анализирующий данные трассировки.

  • Сведения о действиях, производимых при выполнении приложения, фиксируются в виде объектов данных, называемых событиями трассировки. События записываются в потоки трассировки, которые содержат также служебные данные, необходимые для интерпретации событий.
    Трассируемый процесс должен быть специальным образом оборудован: его программа должна содержать точки трассировки – действия, способные генерировать события трассировки.
    События трассировки подразделяются на две категории:
  • пользовательские;
  • системные.

  • Пользовательские события генерируются при вызове функции posix_trace_event().
    Системные события генерируются реализацией в ответ на действия ОС или приложения.
    У событий трассировки есть типы и имена, между которыми устанавливается взаимно-однозначное соответствие.
    Стандартом POSIX-2001 предусмотрен механизм фильтрации, позволяющий отключить генерацию событий определенных типов и, тем самым, уменьшать объем трассировочных данных.
    Потоки трассировки, как правило, хранят только в оперативной памяти. Если нужно получить стабильную копию потока для последующего анализа, его следует сбросить в журнал трассировки.
    И для потоков, и для журналов трассировки правила обработки ситуации заполнения могут сводиться к записи новых событий поверх самых старых или к приостановке трассировки. Кроме того, для потоков может быть предусмотрен сброс в журнал с последующей очисткой, а для для журналов – потенциально неограниченное расширение.
    При создании потока трассировки его атрибуты стандартным для POSIX-2001 образом извлекаются из атрибутного объекта.
    Для создания и уничтожения атрибутных объектов потоков трассировки применяются функции posix_trace_attr_init() и posix_trace_attr_destroy().
    Для манипулирования атрибутами, идентифицирующими поток трассировки, служат функции posix_trace_attr_getgenversion(), posix_trace_attr_getname(), posix_trace_attr_setname(), posix_trace_attr_getcreatetime().

    Функция posix_trace_attr_getclockres() позволяет опросить разрешающую способность часов, с помощью которых проставляются временные штампы.

    Поведенческие атрибуты потоков и журналов обслуживаются функциями posix_trace_attr_getinherited(), posix_trace_attr_setinherited(), posix_trace_attr_getstreamfullpolicy(), posix_trace_attr_setstreamfullpolicy(), posix_trace_attr_getlogfullpolicy(), posix_trace_attr_setlogfullpolicy().

    Опросить и установить хранящиеся в атрибутном объекте размеры событий, потоков и журналов можно с помощью функций posix_trace_attr_getmaxdatasize(), posix_trace_attr_setmaxdatasize(), posix_trace_attr_getmaxsystemeventsize(), posix_trace_attr_getmaxusereventsize(), posix_trace_attr_getstreamsize(), posix_trace_attr_setstreamsize(), posix_trace_attr_getlogsize(), posix_trace_attr_setlogsize().

    Создание потока трассировки осуществляется функциями posix_trace_create() или posix_trace_create_withlog().

    Чтобы активизировать трассировку, следует воспользоваться функцией posix_trace_start(); для последующей приостановки нужно вызвать функцию posix_trace_stop().

    Функция posix_trace_shutdown() завершает трассировку и освобождает ресурсы, ассоциированные с потоком.

    По ходу трассировки управляющий процесс может инициировать сброс потока в журнал, обратившись к функции posix_trace_flush().

    Опрос статуса потока трассировки осуществляется с помощью функции posix_trace_get_status().

    Стандартом POSIX-2001 предусмотрен опрос атрибутов потока трассировки. Для этого служит функция posix_trace_get_attr().

    Поток и журнал трассировки можно очистить от хранящихся там событий, воспользовавшись функцией posix_trace_clear().

    При опросе и установке фильтров событий трассировки (функции posix_trace_get_filter() и posix_trace_set_filter()) применяется апробированная схема работы с множествами, основанная на функциях posix_trace_eventset_empty(), posix_trace_eventset_fill(), posix_trace_eventset_add(), posix_trace_eventset_del(), posix_trace_eventset_ismember().

    Играющие техническую роль функции posix_trace_trid_eventid_open(), posix_trace_eventid_get_name() и posix_trace_eventid_equal() обслуживают идентификаторы типов событий, рассматриваемые как абстрактные объекты.

    Функции posix_trace_eventtypelist_getnext_id() и posix_trace_eventtypelist_rewind() позволяют обрабатывать совокупность идентификаторов типов событий, присутствующих в заданном потоке трассировки.

    Функция posix_trace_eventid_open() ассоциирует имя события с идентификатором типа.

    Анализирующий процесс обычно открывает журнал трассировки, обращаясь к функции posix_trace_open(), а обработав, закрывает его с помощью функции posix_trace_close(), быть может, в промежутке позиционируясь на начало журнала посредством функции posix_trace_rewind(). Для чтения событий служат функции posix_trace_getnext_event(), posix_trace_timedgetnext_event() и posix_trace_trygetnext_event().


    

        Бизнес: Предпринимательство - Малый бизнес - Управление