Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра Linux
Назад Внутренние механизмы ядра Вперед

Измерения временных интервалов

Пассивное измерение уже прошедших интервалов времени (например, для оценивания потребовавшихся трудозатрат, профилирования) — это простейший класс задач, требующих оперирования с функциями времени. Если мы зададимся целью измерять прошедший временной интервал в шкале системного таймера, то вся измерительная процедура реализуется простейшим образом:

	   u32 j1, j2;
	   ...
	   j1 = jiffies;                   // начало измеряемого интервала
	   ...
	   j2 = jiffies;                   // завершение измеряемого интервала    
	   int interval = ( j2 — j1) / HZ; // интервал в секундах

Что мы и используем в первом примере модуля (архив time.tgz) из области механизмов времени, этот модуль всего лишь замеряет интервал времени, которое он был загружен в ядро:

interv.c :

	#include <linux/module.h> 
	#include <linux/jiffies.h> 
	#include <linux/types.h> 
	
	static u32 j; 
	
	static int __init init( void ) { 
	   j = jiffies; 
	   printk( KERN_INFO "module: jiffies on start = %X\n", j ); 
	   return 0; 
	} 
	
	void cleanup( void ) { 
	   static u32 j1; 
	   j1 = jiffies; 
	   printk( KERN_INFO "module: jiffies on finish = %X\n", j1 ); 
	   j = j1 - j; 
	   printk( KERN_INFO "module: interval of life = %d\n", j / HZ ); 
	   return; 
	} 
	
	module_init( init ); 
	module_exit( cleanup ); 

Вот результат выполнения такого модуля — обратите внимание на хорошее соответствие временного интервала (15 секунд), замеренного в пространстве пользователя (командным интерпретатором) и интервального измерения в ядре:

$ date; sudo insmod ./interv.ko

	Сбт Июл 23 23:18:45 EEST 2011 

$ date; sudo rmmod interv

	Сбт Июл 23 23:19:01 EEST 2011 

$ dmesg | tail -n 50 | grep module:

	module: jiffies on start = 131D080
	module: jiffies on finish = 1320CCD 
	module: interval of life = 15 

Счётчик системных тиков jiffies и специальные функции для работы с ним описаны в <linux/jiffies.h>, хотя вы обычно будете просто подключать <linux/sched.h>, который автоматически подключает <linux/jiffies.h>. Излишне говорить, что jiffies и jiffies_64 должны рассматриваться как только читаемые. Счётчик jiffies считает системные тики от момента последней загрузки системы.

При последовательных считываниях jiffies может быть зафиксировано его переполнение (32 бит значение). Чтобы не заморачиваться с анализом, ядро предоставляет четыре однотипных макроса для сравнения двух значений счетчика импульсов таймера, которые корректно обрабатывают переполнение счетчиков. Они определены в файле <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 - значение, с которым его необходимо сравнить. Макросы возвращают значение true, если выполняются соотношения момент ов времени unknown и known, в противном случае возвращается значение false.

Иногда, однако, необходимо обмениваться представлением времени с программами пользовательского пространства, которые, как правило, предоставляют значения времени структурами timeval и timespec. Эти две структуры предоставляют точное значение времени как структуру из двух числел: секунды и микросекунды используются в старой и популярной структуре timeval, а в новой структуре timespec используются секунды и наносекунды. Ядро экспортирует четыре вспомогательные функции для преобразования значений времени выраженного в jiffies из/в эти структуры:

	#include <linux/time.h>
	unsigned long timespec_to_jiffies( struct timespec *value );
	void jiffies_to_timespec( unsigned long jiffies, struct timespec *value );
	unsigned long timeval_to_jiffies( struct timeval *value );
	void jiffies_to_timeval( unsigned long jiffies, struct timeval *value);

Доступ к 64-х разрядному счётчику тиков не так прост, как доступ к jiffies. В то время, как на 64-х разрядных архитектурах эти две переменные являются фактически одной, доступ к значению jiffies_64 для 32-х разрядных процессоров не атомарный. Это означает, что вы можете прочитать неправильное значение, если обе половинки переменной обновляются, пока вы читаете их. Ядро экспортирует специальную вспомогательную функцию, которая делает правильное блокирование:

	#include <linux/jiffies.h>
	#include <linux/types.h>
	u64 get_jiffies_64( void );

Но, как правило, на большинстве процессорных архитектур для измерения временных интервалов могут быть использованы другие дополнительные механизмы, учитывая именно простоту реализации такой задачи, что уже обсуждалось ранее. Такие дополнительные источники информации о времени позволяют получить много выше (на несколько порядков!) разрешение, чем опираясь на системный таймер (а иначе зачем нужно было бы привлекать новые механизмы?). Простейшим из таких прецизионных датчиков времени может быть регистр-счётчик периодов тактирующей частоты процессора с возможностью его программного считывания.

Наиболее известным примером такого регистра-счётчика является TSC (timestamp counter), введённый в x86 процессоры, начиная с Pentium и с тех пор присутствует во всех последующих моделях процессоров этого семейства, включая платформу x86_64. Это 64-х разрядный регистр, который считает тактовые циклы процессора, он может быть прочитан и из пространства ядра и из пользовательского пространства. После подключения <asm/msr.h> (заголовок для x86, означающий machine-specific registers ), можное использовать один из макросов:

- rdtsc( low32, high32 ) - атомарно читает 64-х разрядное значение в две 32-х разрядные переменные;

- rdtscl( low32 ) - (чтение младшей половины) читает младшую половину регистра в 32-х разрядную переменную, отбрасывая старшую половину;

- rdtscll( var64 ) - читает 64-х разрядное значение в переменную long long;

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

	unsigned long ini, end;
	rdtscl( ini ); /* здесь выполняется какое-то действие ... */ rdtscl( end );
	printk( "time was: %li\n", end — ini );

Более обстоятельный пример измерения временных интервалов, используя счётчик процессорных тактов, можно найти в файле memtim.c архива mtest.tgz примеров, посвящённому тестированию распределителя памяти.

Большинство других платформ также предлагают аналогичную (но в чём-то отличающуюся в деталях) функциональную возможность. Заголовки ядра, поэтому, включают архитектурно-независимую функцию, скрывающую существующие различия реализации, и которую можно использовать вместо rdtsc(). Она называется get_cycles() (определена в <asm/timex.h>). Её прототип:

	#include <linux/timex.h>
	cycles_t get_cycles( void );

Эта функция определена для любой платформы, и она всегда возвращает нулевое значение на платформах, которые не имеют реализации регистра счётчика циклов. Тип cycles_t является соответствующим целочисленным типом без знака для хранения считанного значения.

Примечание: Нулевое значение, возвращаемое get_cycles() на платформах, не предоставляющих соответствующей реализации, делает возможным обеспечить переносимость между аппаратными платформами тщательно прописанного кода (там, где это есть, используется get_cycles(), а там, где этой возможности нет, тот же код реализуется, опираясь на последовательность системных тиков). Подобный подход реализован в нескольких различных местах системы Linux.

Для наблюдения эффектов измерений в службе времени рассмотрим тестовое приложение (архив time.tgz), которое для такого анализа может быть, с равным успехом, реализовано как процесс в пространстве пользователя — наблюдаемые эффекты будут те же :

clock.c :

	#include "libdiag.h"
	
	int main( int argc, char *argv[] ) {
	   printf( "%016llX\n", rdtsc() );
	   printf( "%016llX\n", rdtsc() );
	   printf( "%016llX\n", rdtsc() );
	   printf( "%d\n", proc_hz() );
	   return EXIT_SUCCESS;
	};

Для измерения значений размерности времени, мы подготовим небольшую целевую статическую библиотеку (libdiag.a), которую станем применять не только в этом тесте, но и в других примерах пользовательского пространства. Вот основные библиотечные модули:

- «ручная» (для наглядности, на инлайновой ассемблерной вставке), реализация счётчика процессорных циклов rdtsc() для пользовательского пространства, которая выполняет те же функции, что и вызовы в ядре rdtsc(), rdtscl(), rdtscll(), или get_cycles():

rdtsc.c :

	#include "libdiag.h"
	
	unsigned long long rdtsc( void ) {
	   unsigned long long int x;
	   asm volatile ( "rdtsc" : "=A" (x) );
	   return x;
	}

- калибровка затрат (процессорных тактов) на само выполнение вызова rdtsc(), делается это как два непосредственно следующих друг за другом вызова rdtsc(), для снижения погрешностей это значение усредняется по циклу:

calibr.c :

	#include "libdiag.h"
	
	#define NUMB 10
	unsigned calibr( int rep ) {
	   uint32_t n, m, sum = 0;
	   n = m = ( rep >= 0 ? NUMB : rep );
	   while( n-- ) {
	      uint64_t cf, cs;
	      cf = rdtsc();
	      cs = rdtsc();
	      sum += (uint32_t)( cs - cf );
	   }
	   return sum / m;
	}

- измерение частоты процессора (число процессорных тактов за секундный интервал):

proc_hz.c :

	#include "libdiag.h"
	
	unsigned long proc_hz( void ) {   
	   time_t t1, t2;
	   uint64_t cf, cs;
	   time( &t1 );
	   while( t1 == time( &t2 ) ) cf  = rdtsc();
	   while( t2 == time( &t1 ) ) cs  = rdtsc();
	   return (unsigned long)( cs - cf - calibr( 1000 ) );
	}

- перевод потока в реал-тайм режим диспетчирования (в частности, на FIFO дисциплину), что бывает очень важно сделать при любых измерениях временных интервалов:

set_rt.c :

	void set_rt( void ) { 
	   struct sched_param sched_p;           // Information related to scheduling priority 
	   sched_getparam( getpid(), &sched_p );  // Change the scheduling policy to SCHED_FIFO 
	   sched_p.sched_priority = 50;          // RT Priority 
	   sched_setscheduler( getpid(), SCHED_FIFO, &sched_p ); 
	} 

Выполним этот тест на компьютерах x86 самой разной архитектуры (1, 2, 4 ядра), времени изготовления, производительности и версий ядра (собственно, только для такого сравнения и есть целесообразность готовить такой тест):

$ cat /proc/cpuinfo

	processor       : 0 
	...
	model name      : Celeron (Coppermine) 
	...
	cpu MHz         : 534.569 
	...

$ ./clock

	0000005E00E366B5 
	0000005E00E887B8 
	0000005E00EC3F15 
	534551251 

$ cat /proc/cpuinfo

	processor	: 0 
	...
	model name	: Genuine Intel(R) CPU           T2300  @ 1.66GHz 
	...
	cpu MHz		: 1000.000 
	...
	processor	: 1 
	...
	model name	: Genuine Intel(R) CPU           T2300  @ 1.66GHz 
	...
	cpu MHz		: 1000.000 

$ ./clock

	00001D4AAD8FBD34 
	00001D4AAD920562 
	00001D4AAD923BD6 
	1662497985 

$ cat /proc/cpuinfo

	processor	: 0 
	...
	model name	: Intel(R) Core(TM)2 Quad  CPU   Q8200  @ 2.33GHz 
	...
	cpu MHz		: 1998.000 
	...
	processor	: 1 
	...
	model name	: Intel(R) Core(TM)2 Quad  CPU   Q8200  @ 2.33GHz 
	...
	cpu MHz		: 2331.000 
	processor	: 2 
	...
	model name	: Intel(R) Core(TM)2 Quad  CPU   Q8200  @ 2.33GHz 
	...
	cpu MHz		: 1998.000 
	...
	processor	: 3 
	...
	model name	: Intel(R) Core(TM)2 Quad  CPU   Q8200  @ 2.33GHz 
	...
	cpu MHz		: 1998.000 

$ ./clock

	000000000E98F3BB 
	000000000E9A75E8 
	000000000E9A925F 
	2320044881 

Наблюдать подобную картину сравнительно на различном оборудовании — чрезвычайно полезное и любопытное занятие, но мы не станем сейчас останавливаться на деталях наблюдаемого, отметим только высокую точность совпадения независимых измерений, и то, что rdtsc() (или обратная величина частоты) измеряет, собственно, не частоту работы процессора (или какого-то отдельно взятого процессора в SMP системе), а тактирующую частоту процессоров в системе.

Наконец, мы соберём элементарный модуль ядра, который выведет нам значения тех основных констант и переменных службы времени, о которых говорилось:

tick.c :

	#include <linux/module.h>
	#include <linux/jiffies.h>
	#include <linux/types.h>    
	
	static int __init hello_init( void ) {
	   unsigned long j;
	   u64 i;
	   j = jiffies;
	   printk( KERN_INFO "jiffies = %lX\n", j );
	   printk( KERN_INFO "HZ value = %d\n", HZ );
	   i = get_jiffies_64();
	   printk( "jiffies 64-bit = %016llX\n", i );
	   return -1;
	}
	module_init( hello_init );

Выполнение:

$ sudo /sbin/insmod ./tick.ko

	insmod: error inserting './tick.ko': -1 Operation not permitted

$ dmesg | tail -n3

	jiffies = 24AB3A0
	HZ value = 1000
	jiffies 64-bit = 00000001024AB3A0

Предыдущий раздел: Оглавление Следующий раздел:
Информация о времени в ядре   Абсолютное время