Linux Kernel (Ядро линукса) (часть 1)


Получение и освобождение семафоров

Вначале мы обсудим, как освобождать семафор, потому что это намного проще, чем получить его. Когда процесс хочет снять блокировку, установленную семафором ядра, он вызывает функцию up . Она эквивалентна следующему фрагменту ассемблерного кода:
movl $sem->count,есх lock; incl (есх) jg If
lea ecx,eax pushl edx pushl ecx call up popl ecx popl edx
1:Здесь up — это функция на языке С:
attribute ( (гедрастп(З) ) ) void up(struct semaphore sem){wake_up (&sem->wait) ;}
Функция up увеличивает поле count семафора sem, а затем проверяет, стало ли поле больше 0. Увеличение значения count и установка флага, проверяемого последующей инструкцией перехода, должны быть выполнены атомарно, иначе какой-нибудь другой управляющий тракт ядра сможет обратиться к этому полю, что приведет к катастрофе. Если count больше 0, значит, в очереди ожидания нет спящих процессов, и ничего делать не надо. В противном случае вызывается функция up , которая будит один спящий процесс. Обратите внимание, что функция up о получает параметр из регистра еах

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

Реализация функции down о довольно сложна, но, в сущности, она эквивалентна следующему коду:

down:movl $sem->count,есх lock; decl (ecx); jns If
lea есх, еах pushl edx pushl есх call down
popl ecx popl edx
1:Здесь down — это функция на языке С: attribute((гедрастп(З))) void down(struct semaphore sem){
DECLARE_WAITQUEUE(wait, current); unsigned long flags;
current->state = TASK_UNINTERRUPTIBLE; spin_lock_irqsave(&sem->wait.lock, flags); add_wait_queue_exclusive_locked(&sem->wait, &wait); sem->sleepers++; for (;;) {if (!atomic_add_negative(sem->sleepers-l, &sem->count)) { sem->sleepers = 0; break;}
sem->sleepers = 1;spin_unlock_irqrestore (&sem->wait. lock, flags); schedule();spin_lock_irqsave(&sem->wait.lock, flags); current->state =TASK_UNINTERRUPTIBLE;}remove_wait_queue_locked(&sem->wait,&wait); wake_up_locked(&sem->wait);spin_unlock_irqrestore(&sem->wait.lock, flags); current->state = TASK_RUNNING;}
Функция down уменьшает поле count семафора sem, а затем проверяет, стало ли поле отрицательным. И в этом случае уменьшение значения и последующая проверка должны быть выполнены атомарно. Если значение count больше или равно 0, текущий процесс получает ресурс, и выполнение протекает нормально. В противном случае, когда значение count отрицательно, процесс должен быть приостановлен. Содержимое некоторых регистров сохраняется в стеке, после чего вызывается функция down .

Функция down о изменяет состояние текущего процесса с task running на task__uninterruptible и помещает процесс в очередь ожидания семафора. Перед обращением к полям структуры semaphore функция получает спин-бло- кировку sem->wait. lock, которая защищает очередь ожидания семафора и отключает локальные прерывания. Обычно функции, работающие с очередями ожидания, захватывают и освобождают соответствующую спин- блокировку по мере необходимости, когда добавляют или удаляют элемент.

Однако функция down о использует спин-блокировку очереди ожиданияДЛЯ защиты и других полей структуры semaphore, чтобы никакой процесс, работающий на другом процессоре, не смог прочитать или модифицировать их. С этой целью функция down вызывает версии функций работы с очередями, имеющие суффикс locked. Эти версии предполагают, что спин- блокировка уже получена до их вызова.
Основная задача функции down состоит в том, чтобы приостановить текущий процесс до освобождения семафора. Однако способ, которым она это делает, достаточно сложен. Чтобы вам легче было разобраться в коде, помните, что поле sleepers семафора обычно содержит 0, если в очереди ожидания семафора нет спящих процессов, и 1 в противном случае. Мы постараемся разъяснить код, рассмотрев несколько типичных примеров.
- Семафор MUTEX открыт (count равно 1, sleepers равно 0)— макрос down лишь записывает 0 в поле count и переходит к следующей инструкции основной программы; таким образом, функция down вообще не выполняется.
- Семафор MUTEX закрыт (count равно 0, sleepers равно 0)— макрос down
уменьшает значение count и вызывает функцию down о (count равно-1,sleepers равно 0). На каждом шаге цикла функция проверяет, отрицательно ли значение count (заметим, что поле count не меняется функцией atomic add negativeо, потому что на момент вызова функции поле sleepers равно 0):
• если поле count отрицательно, функция вызывает функцию schedule о, чтобы приостановить текущий процесс. Поле count по-прежнему равно-1, а поле sleepers становится равным 1. Впоследствии процесс возобновляет работу внутри цикла и снова производит проверку;
• если поле count неотрицательно, функция сбрасывает поле sleepers в ноль и выходит из цикла. Она пытается разбудить другой процесс в очереди ожидания семафора (но в нашем сценарии очередь теперь пуста) и освобождает семафор. После выхода оба поля, count и sleepers, равны 0, что и требуется, когда семафор закрыт, но никакой процесс его не ждет.
- Семафор mutex закрыт, есть другие спящие процессы (count равно-1, sleepers равно 1)— макрос down уменьшает значение count и вызывает
функцию down о (count равно-2, sleepers равно 1). Функция временно записывает в поле sleepers и отменяет уменьшение, выполненное макросом down, прибавляя значение sleepers-1 к ПОЛЮ count. В то же время функция проверяет, является ли значение count по-прежнему отрицательным (семафор мог быть освобожден удерживавшим его процессом непосредственно перед входом функции down в критическую область):
• если поле count отрицательно, функция записывает 1 в поле sleepers и вызывает функцию schedule о, чтобы приостановить текущий процесс. Поле count по-прежнему равно-1, а поле sleepers становится равным 1;
• если поле count неотрицательно, функция сбрасывает поле sleepers в ноль, пытается разбудить еще один процесс в очереди ожидания семафора и освобождает семафор. После выхода оба поля, count и sleepers, равны 0. Эти значения кажутся ошибочными, поскольку имеются другие спящие процессы. Однако следует принять во внимание, что какой- то процесс в очереди был разбужен. Этот процесс выполняет следующую итерацию цикла, функция atomic add negative вычитает 1 из count, возвращая этому полю значение -1. Кроме того, перед возвращением в состояние ожидания разбуженный процесс записывает 1 в поле sleepers.

Итак, код корректно работает во всех случаях. Заметим, что функция
wake upo в функции down будит, самое большее, один процесс, потому
что спящие процессы в очереди ожидания являются эксклюзивными
Только обработчики исключений, особенно служебные процедуры системных вызовов, могут пользоваться функцией down . Обработчикам прерываний и функциям отложенного выполнения нельзя вызывать функцию down , потому что она приостанавливает процесс, когда семафор занят. По этой причине Linux предлагает программистам функцию down tryiock , которую можно без опасений вызывать из упомянутых асинхронных функций. Она идентична функции down, за исключением случая, когда ресурс занят. В этой ситуации функция немедленно возвращает управление, не переводя процесс в состояние ожидания.

Кроме того, существует функция down interruptibie , несколько отличающаяся от предыдущих. Она широко используется в драйверах устройств, поскольку позволяет процессам, принявшим сигнал, пока они заблокированы семафором, отказаться от операции "опустить семафор". Если спящий процесс разбужен сигналом для получения необходимого ресурса, функция увеличивает поле count семафора и возвращает значение -eintr. С другой стороны, если функция down interruptibie доходит до нормального завершения,она возвращает 0. Таким образом, драйвер устройства может отменить операцию ввода/вывода, если возвращено значение -eintr.В заключение отметим, что, поскольку процессы обычно находят семафоры в открытом положении, семафорные функции оптимизированы именно для этого случая. В частности, функция up о не выполняет инструкции перехода, если очередь ожидания семафора пуста; аналогичным образом, функция down не выполняет инструкции перехода, если семафор открыт. Сложность реализации семафора объясняется как раз усилиями разработчиков избежать дорогостоящих операций в главной ветви выполнения программы.

Предыдущая страница | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | Следующая страница




Возможно, Вас также заинтересует:

ОС Knoppix - это Linux без про...

ВведениеЕсли вы цените свое время, умеете считать деньги и знаете стоимость информации, то эта книга...

Linux Kernel (Ядро линукса) (ч...

Спин-блокировкаСпин-блокировка необходима в многопроцессорной системе, потому что могут возникнуть...

Linux Kernel (Ядро линукса) (ч...

Копирование при записи В системах Unix первых поколений создание процесса было реализовано довольно...

Linux Kernel (Ядро линукса) (ч...

Буферы блоков и головы буферовУ каждого буфера есть дескриптор голова буфера, имеющий тип buffer...