Чтение онлайн

ЖАНРЫ

Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform

Кёртен Роб

Шрифт:

Вы можете верить этому или нет, но это POSIX-совместимая ситуация. POSIX утверждает, что функция fork может возвращать ENOSYS. На самом же деле происходит вот что: Си-библиотека QNX/Neutrino не рассчитана на ветвление процесса с потоками. Когда вы вызываете pthread_create, эта функция устанавливает флаг, сигнализирующий что-то типа «не позволяйте этому процессу применять fork, потому что механизм ветвления в данном случае не определен». Затем, при вызове fork, этот флаг проверяется и, если он установлен, это принуждает fork возвратить значение ENOSYS.

Такая реализация была сделана преднамеренно, и причина этого кроется в потоках и мутексах. Если бы

этого ограничения не было (и оно может быть снято в будущих версиях QNX/ Neutrino), то вновь созданный процесс, как и предполагается, имел бы то же самое число потоков, что и исходный. Однако, тут возникает сложность, потому что некоторые из исходных потоков могут являться владельцами мутексов. Поскольку вновь создаваемый процесс имеет ту же область данных, что и исходный, библиотека должна была бы отслеживать, какие мутексы принадлежат каким потокам в исходном процессе, и затем дублировать принадлежность мутексов в новом процессе. Это не является невозможным: есть функция, называемая pthread_atfork, которая обеспечивает процессу возможность обрабатывать такие ситуации. Однако, на момент написания этой книги функциональные возможности pthread_atfork используются не всеми мутексами в Си-библиотеке QNX/Neutrino.

Так что же использовать?

Очевидно, если вы переносите в QNX/Neutrino программу из другой ОС, вы пожелаете использовать те же механизмы, что и в исходной программе. Я бы посоветовал избегать в новом коде применения функции fork, и вот почему:

• функция fork не работает с несколькими потоками — см. выше;

• при работе с fork в условиях многопоточности вы должны будете зарегистрировать обработчик pthread_atfork и локировать каждый мутекс по отдельности перед собственно ветвлением, а это усложнит структуру программы;

• дочерние процессы, созданные fork, копируют все открытые дескрипторы файлов. Как мы увидим позже в главе «Администратор ресурсов», это требует много дополнительных усилий, которые может быть совершенно напрасными, если дочерний процесс затем сразу сделает exec и тем самым закроет все открытые дескрипторы.

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

Запуск потока

Теперь, когда мы знаем, как запустить другой процесс, давайте рассмотрим, как осуществить запуск другого потока.

Любой поток может создать другой поток в том же самом процессе; на это не налагается никаких ограничений (за исключением объема памяти, конечно!) Наиболее общий путь реализации этого — использование вызова функций POSIX pthread_create:

#include <pthread.h>

int pthread_create(pthread_t *thread,

 const pthread_attr_t *attr,

 void*(*start_routine)(void*), void *arg);

Функция pthread_create имеет четыре аргумента :

thread указатель на
pthread_t
, где хранится идентификатор потока
attr атрибутная запись
start_routine подпрограмма, с которой начинается поток
arg параметр,
который передается подпрограмме start_routine

Отметим, что указатель thread и атрибутная запись (attr) — необязательные элементы, вы может передавать вместо них NULL.

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

Если бы нам было до этого дело, мы бы сделали так:

pthread_t tid;

pthread_create(&tid, ...

printf("Новый поток имеет идентификатор %d\n", tid);

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

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

Новый поток начинает выполнение с функции start_routine, с параметром arg.

Атрибутная запись потока

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

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

pthread_attr_t
:

typedef struct {

 int flags;

 size_t stacksize;

 void *stackaddr;

 void (*exitfunc)(void *status);

 int policy;

 struct sched_param param;

 unsigned guardsize;

} pthread_attr_t;

В основном эти поля используются как:

flags Неисчисляемые (булевы) характеристики потока — например, создается поток как «обособленный» или «синхронизирующий».
stacksize, stackaddr и guardsize Параметры стека.
exitfunc Функция, выполняемая перед завершением потока.
policy и param Параметры диспетчеризации.

Доступны следующие функции:

Управление атрибутами

pthread_attr_destroy

pthread_attr_init

Флаги (булевы характеристики)

pthread_attr_getdetachstate

pthread_attr_setdetachstate

pthread_attr_getinheritsched

pthread_attr_setinheritsched

pthread_attr_getscope

pthread_attr_setscope

Поделиться с друзьями: