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


Рассмотрим первый вызов функции cascade

. Она принимает в качестве аргументов адрес, хранящийся в переменной base, адрес поля base->tv2 и индекс списка в поле base->tv2, содержащего таймеры, время которых истечет в ближайшие 256 тиков. Этот индекс определяется путем анализа соответствующих битов поля base->timer_jiffies. Функция переносит все динамические таймеры из списка, хранящегося в поле base->tv2, в соответствующие списки в поле base->tvl, после чего возвращает положительное значение при условии, что не все списки в поле base->tv2 опустели. Если они пусты, функция cascade вызывается еще раз для пополнения поля base->tv2 таймерами из списка в поле base->tv3 и т. д.
• увеличивает на единицу значение в поле base->timer j if f ies;
• для каждого динамического таймера в списке base->tvi.vec[index] функция вызывает соответствующую таймерную функцию.

В частности, для каждого элемента t, имеющего тип timer_iist, функция выполняет следующие действия:
- удаляет элемент t из списка, принадлежащего полю base->tvi;
- в многопроцессорных системах записывает в поле base->running_ timer значение &t;
- записывает null в поле t. base;
- освобождает спин-блокировку base->iock и включает локальные прерывания;
- выполняет таймерную функцию t.function, передавая ей t.data в качестве аргумента;
- получает спин-блокировку base->iock и отключает локальные прерывания;
-переходит к следующему таймеру в списке, если таковой имеется;
• все таймеры в списке обработаны. Функция переходит к следующему шагу самого внешнего цикла while.
4. Самый внешний цикл while закончен, и это означает, что обработаны все таймеры, время которых истекло. В многопроцессорных системах функция устанавливает поле base->running_timer в значение null.
5. Освобождает спин-блокировку base->iock и включает локальные прерывания.

Поскольку значения jiffies и timer jiffies обычно совпадают, самый внешний цикл while, как правило, выполняется только один раз. Вообще говоря, самый внешний ЦИКЛ выполняется jiffies - base->timer_jiffies + 1 раз. Кроме того, если во время работы функции run timer softirq произойдет прерывание по таймеру, динамические таймеры, время которых истекает на этом тике, тоже принимаются во внимание, потому что переменная jiffies асинхронно увеличивается обработчиком глобальных таймерных прерываний.

Обратите внимание, что функция run timer softirqo получает спин- блокировку base->iock и отключает локальные прерывания непосредственно перед входом во внешний цикл. Прерывания включаются, и спин-блокировка освобождается сразу после вызова каждой функции динамического таймера на все время ее работы. Это гарантирует, что структуры динамического таймера не будут испорчены чередующимися управляющими трактами ядра. Резюмируем. Этот довольно сложный алгоритм обеспечивает высочайшую производительность. Чтобы понять, почему, предположим ради простоты, что softirq-функция timer softirq выполняется сразу после возникновения соответствующего прерывания по таймеру. Тогда в 255 из 256 (в 99.6% случаев) функция run timer softirqo просто выполнит функции таймеров, время которых истекло, если таковые имеются. Для периодического пополнения массива base->tvi. vec достаточно 63 раза из 64 разбить один список в поле base->tv2 на 256 СПИСКОВ В поле base->tvl. Массив base->tv2. vec, в свою очередь, должен пополняться в 0.006% случаев (то есть каждые 16,4 с). Аналогичным образом, массив base->tv3.vec пополняется каждые 17 мин 28 с, а массив base->tv4.vec— каждые 18 ч 38 мин. Пополнять массив base-> tv5. vec не нужно.

Применение динамических таймеров: системный вызов nanosleepO

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

Мы обсудим служебную процедуру системного вызова nanosleep о, т. е. функцию sys nanosieep о, которая принимает в качестве параметра указатель на структуру timespec и приостанавливает процесс, пока не истечет заданный интервал времени. Служебная процедура вначале вызывает функцию copy from user , чтобы скопировать значения, хранящиеся в структуре timespec режима пользователя, в локальную переменную t. Если структура timespec определяет ненулевую задержку, функция выполняет следующий код:
current->state = TASK_INTERRUPTIBLE;
remaining = schedule_timeout(timespec_to_jiffies(&t)+1);
Функция timespec to j iffies о преобразует в тики временной интервал, хранящийся в структуре time spec. На всякий случай, функция sys nanosieep прибавляет один тик к значению, вычисленному функцией
timespec_to_jiffies.

Ядро реализует тайм-ауты процессов с помощью динамических таймеров. Они появляются в функции scheduie timeout , которая, по сути, выполняет следующие операторы:
struct timer_list timer;
unsigned long expire = timeout + jiffies;
init_timer (&timer) ;
timer.expires = expire;
timer.data = (unsigned long) current;
timer.function = process_timeout;
add_timer(&timer);
schedule(); / process suspended until timer expires /
del_singleshot_timer_sync (&timer) ;
timeout = expire — jiffies;
return (timeout de1ay(1oops);}void ndelay(unsigned long nsecs){unsigned long loops;loops =(nsecsHZcurrent_cpu_data.loops_per_jiffy)/1000000000; cur_timer->delay(loops);}
Обе используют в своей работе метод delay объекта-таймера cur_timer, который в качестве параметра принимает отрезок времени, выраженный в "циклах".

Точное значение одного "цикла" зависит от объекта-таймера, на который ссылается указатель cur timer

- если cur_timer указывает на объекты timer_hpet, timer_pmtmr И timer_tsc, один "цикл" соответствует одному рабочему циклу процессора, т. е. промежутку между двумя соседними тактовыми сигналами процессора;
- если cur timer указывает на объекты timer_none ИЛИ timer_pit, один "цикл" соответствует одной итерации "плотного" цикла из программных инструкций.

На этапе инициализации, после того как указатель cur timer установлен с ПОМОЩЬЮ функции select timer , ядро выполняет функцию calibrate_ delay о, которая определяет, сколько "циклов" помещается в одном тике. Полученное значение сохраняется в переменной current cpu_data.ioops_ per jiffy, чтобы функции udeiayo и ndeiayO могли использовать его для преобразования микросекунд и, соответственно, наносекунд в "циклы”.
Конечно, для точного измерения времени метод cur_timer->deiay о пользуется аппаратными устройствами НРЕТ или TSC, если это возможно. В противном случае, если нет ни чипа НРЕТ, ни TSC, метод выполняет loops итераций "плотного” цикла программных инструкций.

Предыдущая страница | 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...