Директивы компоновщика ld(1)
, которые используются для сборки главного образа ядра (для аппаратной платформы x86 описаны в файле arch/i386/kernel/vmlinux.lds.S
), указывают компоновщику, что переменную jiffies
необходимо совместить с началом переменной jiffies_64
.
jiffies = jiffies_64;
Следовательно, переменная jiffies
— это просто 32 младших разряда полной 64-разрядной переменной jiffies_64
. Так как в большинстве случаев переменная jiffies
используется для измерения промежутков времени, то для большей части кода существенными являются только младшие 32 бит.
В случае применения 64-разрядного значения, переполнение не может возникнуть за время существования чего-либо. В следующем разделе будут рассмотрены проблемы, связанные с переполнением (хотя переполнение счетчика импульсов системного таймера и не желательно, но это вполне нормальное и ожидаемое событие). Код, который используется для управления ходом времени, использует все 64 бит, и это предотвращает возможность переполнения 64-разрядного значения. На рис. 10.1 показана структура переменных jiffies
и jiffies_64
.
Рис. 10.1. Структура переменных jiffies
и jiffies_64
Код, который использует переменную jiffies
, просто получает доступ к тридцати двум младшим битам переменной jiffies_64
. Функция get_jiffies_64()
может быть использована для получения полного 64-разрядного значения[57]. Такая необходимость возникает редко, следовательно большая часть кода просто продолжает считывать младшие 32 разряда непосредственно из переменной jiffies
.
На 64-разрядных аппаратных платформах переменные jiffies_64
и jiffies
просто совпадают. Код может либо непосредственно считывать значение переменной jiffies
, либо использовать функцию get_jiffies_64()
, так как оба этих способа позволяют получить аналогичный эффект.
Переполнение переменной jiffies
Переменная jiffies
, так же как и любое целое число языка программирования С, после достижения максимально возможного значения переполняется. Для 32-разрядного беззнакового целого числа максимальное значение равно 2³²- 1. Поэтому перед тем как счетчик импульсов системного таймера переполнится, должно прийти 4294967295 импульсов таймера. Если значение счетчика равно этому значению и счетчик увеличивается на 1, то значение счетчика становится равным нулю.
Рассмотрим пример переполнения.
unsigned long timeout = jiffies + HZ/2; /* значение лимита времени
равно 0.5 с */
/* выполним некоторые действия и проверим, не слишком ли это много
заняло времени ... */
if (timeout < jiffies) {
/* мы превысили лимит времени — это ошибка ... */
} else {
/* мы не превысили лимит времени — это хорошо ... */
}
Назначение этого участка кода — установить лимит времени до наступления некоторого события в будущем, а точнее полсекунды от текущего момента. Код может продолжить выполнение некоторой работы — возможно, записать некоторые данные в аппаратное устройство и ожидать ответа. После выполнения, если весь процесс превысил лимит установленного времени, код соответственным образом обрабатывает ошибку.
В данном примере может возникнуть несколько потенциальных проблем, связанных с переполнением. Рассмотрим одну из них. Что произойдет, если переменная jiffies
переполнится и снова начнет увеличиваться с нуля после того, как ей было присвоено значение переменной timeout
? При этом условие гарантированно не выполнится, так как значение переменной jiffies
будет меньше, чем значение переменной timeout
, хотя логически оно должно быть больше. По идее значение переменной jiffies
должно быть огромным числом, всегда большим значения переменной timeout
. Так как эта переменная переполнилась, то теперь ее значение стало очень маленьким числом, которое, возможно, отличается от нуля на несколько импульсов таймера. Из-за переполнения результат выполнения оператора if
меняется на противоположный!
К счастью, ядро предоставляет четыре макроса для сравнения двух значений счетчика импульсов таймера, которые корректно обрабатывают переполнение счетчиков. Они определены в файле
следующим образом.
#define time_after(unknown, known) ((long)(known) - (long)(unknown) < 0)
#define time_before(unknown, known) \
((long) (unknown) - (long) (known) < 0)
#define time_after_eq(unknown, known) \
((long)(unknown) - (long) (known) >= 0)