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

         

Функции управления планированием


Мы приступаем к рассмотрению функций для опроса и/или установки политики и/или параметров планирования применительно к процессам. Функции аналогичной направленности для потоков управления были рассмотрены нами ранее.

Стандарт POSIX-2001 предусматривает следующие функции управления планированием: sched_getscheduler() (опрос политики планирования процесса), sched_getparam() (опрос параметров планирования процесса), sched_setscheduler() (установка политики и параметров планирования процесса) и sched_setparam() (установка параметров планирования процесса) (см. листинг 6.1).

#include <sched.h>

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 <sched.h>

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 <sched.h> 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 <sched.h>
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 <sched.h>
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 <sched.h> int sched_yield (void);
Листинг 6.3. Описание функции sched_yield().
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа опрашивает характеристики политик планирования, */ /* а также атрибуты планирования текущего процесса */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h> #include <sched.h>
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 <stdio.h> #include <pthread.h> #include <sched.h> #include <semaphore.h> #include <assert.h>
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 (&quot;Средний приоритет: %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 <aio.h> 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 <aio.h> 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 <aio.h>

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 <aio.h> 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 <unistd.h> void sync (void);

#include <unistd.h> int fsync (int fildes);

#include <unistd.h> int fdatasync (int fildes);

#include <aio.h> 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 <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <aio.h> #include <assert.h> #include <errno.h>

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 <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <aio.h> #include <pthread.h> #include <assert.h>

/* Указатель на массив указателей на управляющие */ /* блоки запросов операций асинхронного чтения */ 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 <aio.h> int aio_read (struct aiocb *aiocbp); int aio_write (struct aiocb *aiocbp);
Листинг 7.1. Описание функций асинхронного чтения и записи.
Закрыть окно




#include <aio.h> int lio_listio ( int mode, struct aiocb *restrict const listio [restrict], int nent, struct sigevent *restrict sigev);
Листинг 7.2. Описание функции lio_listio().
Закрыть окно




#include <aio.h>
ssize_t aio_return ( struct aiocb *aiocbp);
int aio_error ( const struct aiocb *aiocbp);
Листинг 7.3. Описание функций aio_return() и aio_error().
Закрыть окно




#include <aio.h> int aio_cancel ( int fildes, struct aiocb *aiocbp);
Листинг 7.4. Описание функции aio_cancel().
Закрыть окно




#include <aio.h> int aio_suspend ( const struct aiocb *const listio [], int nent, const struct timespec *timeout);
Листинг 7.5. Описание функции aio_suspend().
Закрыть окно




#include <unistd.h> void sync (void);
#include <unistd.h> int fsync (int fildes);
#include <unistd.h> int fdatasync (int fildes);
#include <aio.h> int aio_fsync ( int op, struct aiocb *aiocbp);
Листинг 7.6. Описание функций синхронизации оперативной и долговременной памяти.
Закрыть окно




/* * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает суммарное */ /* число строк – в файлах аргументах */ /* командной строки. */ /* Если аргументы отсутствуют или в */ /* качестве имени задан минус, */ /* читается стандартный ввод. */ /* * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <aio.h> #include <assert.h> #include <errno.h>
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 <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <aio.h> #include <pthread.h> #include <assert.h>
/* Указатель на массив указателей на управляющие */ /* блоки запросов операций асинхронного чтения */ 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 <fcntl.h> int posix_fadvise ( int fd, off_t offset, size_t len, int advice);
#include <fcntl.h> int posix_fallocate (int fd, off_t offset, size_t len);
#include <sys/mman.h> int posix_madvise (void *addr, size_t len, int advice);
#include <stdlib.h> int posix_memalign (void **memptr, size_t alignment, size_t size);
Листинг 7.9. Описание функций рекомендательных интерфейсов.
Закрыть окно




/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа подсчитывает сумму байт в файле – */ /* аргументе командной строки, */ /* пытаясь оптимизировать чтение данных с помощью */ /* функций */ /* рекомендательных интерфейсов */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */
#define _XOPEN_SOURCE 600
#include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <errno.h>
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 <fcntl.h> int posix_fadvise (int fd, off_t offset, size_t len, int advice);

#include <fcntl.h> int posix_fallocate (int fd, off_t offset, size_t len);

#include <sys/mman.h> int posix_madvise (void *addr, size_t len, int advice);

#include <stdlib.h> 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 <trace.h> 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 <trace.h> 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 <trace.h> 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 <time.h> #include <trace.h> 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 <time.h> #include <trace.h> 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 <trace.h> 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 <time.h> #include <trace.h> 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 <time.h> #include <trace.h> 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 <trace.h> 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 <sys/types.h> #include <trace.h> 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 <sys/types.h> #include <trace.h>

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 <trace.h> 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 <sys/types.h> #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 <sys/types.h> #include <trace.h> 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 <trace.h> 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 <sys/types.h> #include <trace.h> 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 <trace.h> 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 <sys/types.h> #include <trace.h> int posix_trace_shutdown (trace_id_t trid);

Листинг 8.8. Описание функции завершения трассировки. (html, txt)

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

После возврата из posix_trace_shutdown() аргумент trid перестает быть корректным идентификатором потока трассировки.

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

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

#include <sys/types.h> #include <trace.h> int posix_trace_flush (trace_id_t trid);

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

Сброс осуществляется с учетом правил обработки ситуации заполнения журнала.

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

Узнать о завершении операции сброса можно, опрашивая статус потока трассировки с помощью функции posix_trace_get_status() (см.листинг 8.10).

#include <trace.h> 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 <sys/types.h> #include <trace.h> int posix_trace_shutdown (trace_id_t trid);

Листинг 8.8. Описание функции завершения трассировки.

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

После возврата из posix_trace_shutdown() аргумент trid перестает быть корректным идентификатором потока трассировки.

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

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

#include <sys/types.h> #include <trace.h> int posix_trace_flush (trace_id_t trid);

Листинг 8.9. Описание функции сброса потока трассировки в журнал.

Сброс осуществляется с учетом правил обработки ситуации заполнения журнала.

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

Узнать о завершении операции сброса можно, опрашивая статус потока трассировки с помощью функции posix_trace_get_status() (см.


листинг 8.10).

#include <trace.h> 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 <trace.h> int posix_trace_get_attr ( trace_id_t trid, trace_attr_t *attr);

Листинг 8.11. Описание функции опроса атрибутов потока трассировки.

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

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

#include <sys/types.h> #include <trace.h> 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 <trace.h> 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 <trace.h> 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 <trace.h> 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 <trace.h> 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 <sys/types.h> #include <stdio.h> #include <trace.h> #include <time.h> #include <limits.h> #include <errno.h>

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 <trace.h> int posix_trace_attr_init ( trace_attr_t *attr); int posix_trace_attr_destroy ( trace_attr_t *attr);
Листинг 8.1. Описание функций создания и уничтожения атрибутных объектов потоков трассировки.
Закрыть окно




#include <trace.h> 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 <time.h> #include <trace.h> int posix_trace_attr_getcreatetime ( const trace_attr_t *attr, struct timespec *createtime);
Листинг 8.2. Описание функций манипулирования атрибутами, идентифицирующими поток трассировки.
Закрыть окно




#include <time.h> #include <trace.h> int posix_trace_attr_getclockres ( const trace_attr_t *attr, struct timespec *resolution);
Листинг 8.3. Описание функции опроса разрешающей способности часов, с помощью которых проставляются временные штампы.
Закрыть окно




#include <trace.h> 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 <sys/types.h> #include <trace.h> 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 <sys/types.h> #include <trace.h> 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 <trace.h> int posix_trace_start (trace_id_t trid); int posix_trace_stop (trace_id_t trid);
Листинг 8.7. Описание функций активизации и приостановки трассировки.
Закрыть окно




#include <sys/types.h> #include <trace.h> int posix_trace_shutdown (trace_id_t trid);
Листинг 8.8. Описание функции завершения трассировки.
Закрыть окно




#include <sys/types.h> #include <trace.h> int posix_trace_flush (trace_id_t trid);
Листинг 8.9. Описание функции сброса потока трассировки в журнал.
Закрыть окно




#include <trace.h> int posix_trace_get_status (trace_id_t trid, struct posix_trace_status_info *statusinfo);
Листинг 8.10. Описание функции опроса статуса потока трассировки.
Закрыть окно




#include <trace.h> int posix_trace_get_attr ( trace_id_t trid, trace_attr_t *attr);
Листинг 8.11. Описание функции опроса атрибутов потока трассировки.
Закрыть окно




#include <sys/types.h> #include <trace.h> int posix_trace_clear (trace_id_t trid);
Листинг 8.12. Описание функции очистки потока трассировки.
Закрыть окно




#include <trace.h> 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 <trace.h> 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 <trace.h> 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 <trace.h> 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 <sys/types.h> #include <stdio.h> #include <trace.h> #include <time.h> #include <limits.h> #include <errno.h>
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 <sys/types.h> #include <trace.h>
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 <trace.h> 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 <sys/types.h> #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 <trace.h>
. . . /* Идентификаторы генерируемых пользовательских событий */ 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 <stdio.h> #include <pthread.h> #include <time.h> #include <trace.h> #include <errno.h>
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.
Закрыть окно