Видно, что процесс диагностики выполняется бесконечно. Он запускает цикл работ по диагностике, затем «засыпает» на 15 секунд, потом «просыпается», и все повторяется заново.
Если оглянуться назад в мрачные и смутные однозадачные времена, когда один процессор обслуживал одного пользователя программы такого сорта реализовывались путем выполнения функцией
void sleep(int nseconds) {
long i;
while (nseconds--) {
for (i = 0; i < CALIBRATED_VALUE; i++);
}
}
В те дни, поскольку в машине не выполнялось никаких других задач, такие программы не составляли большой проблемы, поскольку никакой другой процесс не беспокоило, что вы используете своей функцией
Если вы должны были реализовать некоторое подобие «многозадачного режима», то это обычно делалось путем применения процедуры прерывания, которая либо срабатывала от аппаратного таймера, либо выполнялась в пределах периода «активного ожидания», оказывая при этом некоторое воздействие на калибровку отсчета времени. Это обычно не вызывало беспокойства.
К счастью, в решении этих проблем мы уже ушли далеко вперед. Вспомните параграф «Диспетчеризация и реальный мир» (глава «Процессы и потоки»), там описываются причины, по которым ядро выполняет перепланирование потоков. Причины могут быть следующие:
• аппаратное прерывание;
• системный вызов;
• сбой (исключение).
В данной главе мы подробно проанализируем две первые причины из вышеуказанного списка — аппаратные прерывания и системные вызовы.
Когда поток вызывает функцию
Все это время ядро принимает регулярно поступающие аппаратные прерывания таймера. Положим для определенности, что эти аппаратные прерывания происходят ровно каждые 10 миллисекунд.
Давайте немного переформулируем это утверждение: каждый раз, когда такое прерывание обслуживается соответствующей подпрограммой обработки прерывания (ISR) ядра, это значит, что истек очередной 10-миллисекундный интервал. Ядро отслеживает время суток путем увеличения специальной внутренней переменной на значение, соответствующее 10 миллисекундам, с каждым вызовом обработчика прерывания.
Так что, реализуя 15-секундный таймер, ядро в действительности выполняет следующее:
• Устанавливает переменную в текущее время плюс 15 секунд.
• В обработчике прерываний сравнивает эту переменную с текущим временем.
• Когда текущее время станет равным (или больше) данной переменной, поток снова ставится в очередь готовности.
При использовании множества параллельно работающих таймеров — например, когда необходимо активизировать несколько потоков в различные моменты времени — ядро просто ставит запросы в очередь, отсортировав таймеры по возрастанию их времени истечения (в голове очереди при этом окажется таймер с минимальным временем истечения). Обработчик прерывания будет анализировать только переменную, расположенную в голове очереди.
Источники прерываний таймера
На этом мы, пожалуй, закончим наш краткий экскурс по стране таймеров и перейдем к вещам, которые уже не так очевидны.
Откуда возникают прерывания таймера? На рисунке ниже приведены аппаратные компоненты (и некоторые характерные для PC значения параметров), отвечающие за генерацию этих прерываний.
Источники прерываний таймера в PC.
Из рисунка видно, что в PC используется высокочастотный аппаратный генератор синхроимпульсов (МГц-диапазона). Высокочастотный меандр делится при помощи аппаратного счетчика (на рисунке — микросхема Intel 82C54), который понижает частоту импульсов до сотен килогерц или сотен герц (диапазон, в котором их уже может обработать ISR). ISR таймера входит в состав ядра и взаимодействует непосредственно с его кодом и внутренними структурами данных. В процессорах архитектуры не-x86 (MIPS, PowerPC) тоже происходит подобная последовательность событий; в некоторых микросхемах аппаратный таймер может быть непосредственно встроен в процессор.