Friday, May 18th

Last update12:13:00 PM GMT

Вы находитесь на: FreeBSD Службы ядра Добровольное переключение контекста

Добровольное переключение контекста

Добровольное переключение контекста имеет место всегда, когда поток должен ожидать доступности ресурса или наступления события. Добровольные переключения контекста при обычной работе системы случаются часто. Например, поток обычно блокируется каждый раз, когда запрашивает данные от устройства ввода, такого, как терминал или диск. В FreeBSD добровольные переключения контекста инициируются посредством процедуры sleep(). Когда потоку больше не нужен процессор, он вызывает sleep() с указанными приоритетом планирования и каналом ожидания. Приоритет, указанный в вызове sleep(), является приоритетом, который должен быть назначен потоку при его пробуждении. Этот приоритет не влияет на приоритет планирования уровня пользователя.

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

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

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

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

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

· Запрещает прерывания, которые могут вызвать изменения состояния потока, запросив мьютекс schedlock.

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

· Устанавливает в качестве приоритета потока приоритет, который будет у потока при его пробуждении, и устанавливает флаг SLEEPING.

· Помещает поток в конец очереди ожидания, выбранный на шаге 2.

· Вызывает miswitch() для запуска выполнения нового потока; мьютекс schedlock освобождается, как часть переключения на другой поток.

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

Структура очереди для спящих потоков


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

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

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

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

Удаляет поток из очереди ожидания.

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

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

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


Похожие:
Еще по теме:
Советуем прочитать:

Основные детали системы фасадной теплоизоляции Rockwool - Это теплоизоляция ФАСАД БАТТС.
Сейчас 66 гостей онлайн

Реклама на сайте: