Выбрать главу

extern unsigned long volatile jiffies;

Вторая переменная определяется в файле <linux/jiffies.h> в следующем виде.

extern u64 jiffies_64;

Директивы компоновщика 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 меняется на противоположный!

К счастью, ядро предоставляет четыре макроса для сравнения двух значений счетчика импульсов таймера, которые корректно обрабатывают переполнение счетчиков. Они определены в файле <linux/jiffies.h> следующим образом.

#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)

#define \

 time_before_eq(unknown, known) ((long)(known) - (long) (unknown) >= 0)

Параметр unknown — это обычно значение переменной jiffies, а параметр known — значение, с которым его необходимо сравнить.

Макрос time_after(unknown, known) возвращает значение true, если момент времени unknown происходит после момента времени known, в противном случае возвращается значение false. Макрос time_before(unknown, known) возвращает значение true, если момент времени unknown происходит раньше, чем момент времени known, в противном случае возвращается значение false. Последние два макроса работают аналогично первым двум, за исключением того, что возвращается значение "истинно", если оба параметра равны друг другу.

Версия кода из предыдущего примера, которая предотвращает ошибки, связанные с переполнением, будет выглядеть следующим образом.

unsigned long timeout = jiffies + HZ/2; /* значение лимита времени

                                           равно 0.5 с */

/* выполним некоторые действия и проверим, не слишком ли это много

   заняло времени ... */

if (time_after(jiffies, timeout}) {

 /* мы превысили лимит времени — это ошибка ... */

} else {

 /* мы не превысили лимит времени — это хорошо ... */

}

Если любопытно, каким образом эти макросы предотвращают ошибки, связанные с переполнением, то попробуйте подставить различные значения параметров. А затем представьте, что один из параметров переполнился, и посмотрите, что при этом произойдет.

Пространство пользователя и параметр HZ

Раньше изменение параметра НZ приводило к аномалиям в пользовательских программах. Это происходило потому, что значения параметров, связанных со временем, экспортировались в пространство пользователя в единицах, равных количеству импульсов системного таймера в секунду. Так как такой интерфейс использовался давно, то в пользовательских приложениях считалось, что параметр HZ имеет определенное конкретное значение. Следовательно, при изменении значения параметра HZ изменялись значения, которые экспортируются в пространство пользователя, в одинаковое число раз. Информация о том, во сколько раз изменились значения, в пространство пользователя не передавалась! Полученное от ядра значение времени работы системы могло интерпретироваться как 20 часов, хотя на самом деле оно равнялось только двум часам.

вернуться

57

Необходима специальная функция, так как на 32-разрядных аппаратных платформах нельзя атомарно обращаться к двум машинным словам 64-разрядного значения, Специальная функция, перед тем как считать значение, блокирует счетчик импульсов системного таймера с помощью блокировки xtime_lock.