Программирование в стандарте POSIX

Время и работа с ним

Разбить на страницы
Показывать лекцию целиком

Основные понятия и объекты

Согласно стандарту POSIX, за начало отсчета (астрономического) времени принимается ноль часов, ноль минут, ноль секунд первого января 1970-го года универсального координированного времени (далее для краткости именуемого также всемирным ).

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

Московское время опережает всемирное на три часа. Имеется в виду так называемое декретное московское время, которое не следует путать с поясным.

Поясное время Москвы, лежащей во втором часовом поясе, опережает всемирное на два часа.

Московское летнее время опережает всемирное на 4 часа.

За стандартную единицу измерения астрономического времени в POSIX-2001 принята секунда. Пусть некоторый момент времени задан в терминах секунд (значение tm_sec ), минут ( tm_min ), часов ( tm_hour ), а также номером дня в году ( tm_yday ) и номером года минус 1900 ( tm_year ). Тогда число секунд всемирного времени, прошедшее на этот момент от начала отсчета, вычисляется по формуле

tm_sec + tm_min*60 + tm_hour*3600 + 
tm_yday*86400 + (tm_year-70)*31536000 + 
((tm_year-69)/4)*86400 - ((tm_year-1)/100)*
86400 + ((tm_year+299)/400)*86400

(три последних слагаемых предназначены для учета високосных лет).

Учет разного рода поправок к всемирному времени (таких, например, как вставка дополнительной секунды в начале 1996 года ) зависит от реализации.

Для представления данных о моментах времени в программах на языке C обычно используется структура tm, описанная во включаемом файле <time.h> и содержащая, согласно стандарту POSIX-2001, по крайней мере следующие поля.

int tm_sec;   /* Секунды [0,60]             */
int tm_min;   /* Минуты [0,59]              */
int tm_hour;  /* Часы [0,23]                */
int tm_mday;  /* Номер дня в месяце [1,31]  */
int tm_mon;   /* Номер месяца в году [0,11] */
int tm_year;  /* Номер года минус 1900      */
int tm_wday;  /* Номер дня недели [0,6]     */
        /* (воскресенью соответствует ноль) */
int tm_yday;  /* Номер дня в году [0,365]   */
int tm_isdst; 
           /* Признак учета летнего времени */

Диапазон [0, 60] для поля tm_sec позволяет справляться с производимой время от времени вставкой дополнительной секунды.

Значение поля tm_year задается со знаком, что позволяет представлять годы до 1900.

Значение признака tm_isdst должно быть положительным, если действует летнее время, нулевым, если летнее время не действует, или отрицательным, если про летнее время ничего не известно.

Часами называется программный или аппаратный объект, предназначенный для измерения видимого или истинного хода времени.

Показания часов можно опросить и установить (в допустимых для часов пределах).

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

Под скачком часов понимается разность между двумя последовательными, различными с точки зрения приложения (использующего стандартные средства) показаниями часов .

Часы называются монотонными, если их нельзя установить стандартными средствами и они не могут иметь отрицательных скачков.

Такт часов - это зависящие от реализации промежутки времени, на которые дробится каждая секунда .

Реальным (или астрономическим) называется время, измеренное по системным часам безотносительно к тому, какой процесс (поток управления) выполняется.

Под временем выполнения ( процессорным временем ) понимается время, затрачиваемое на выполнение процесса (потока управления), включая работающие от его имени системные сервисы.

Время выполнения конкретного процесса или потока управления измеряется часами процессорного времени.

Под мониторингом времени выполнения понимается оперативное отслеживание процессорного времени, затрачиваемого процессом (потоком управления).

Виртуальное время процесса - время, измеряемое системными часами, пока процесс выполняется.

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

Таймером процессорного времени называется таймер, ассоциированный с часами процессорного времени .

Взвести (зарядить) - значит запустить таймер, измеряющий ход времени и позволяющий уведомить процесс о наступлении заданного момента.

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

Таймеры можно подразделить на одноразовые и периодические.

Одноразовые таймеры снимаются со взвода после первого (и единственного) срабатывания.

Периодические таймеры после очередного срабатывания запускаются вновь в соответствии с однажды заданной спецификацией следующего срабатывания.

Под избыточным срабатыванием таймера понимается ситуация, когда таймер срабатывает до того, как процесс обработал сигнал о предыдущем срабатывании.

Все, что касается часов процессорного времени и таймеров, относится к необязательным частям стандарта POSIX-2001. Тем не менее, мы детально рассмотрим соответствующие средства ввиду их важности и употребительности.

Опрос и установка показаний часов реального времени

Простейшим средством опроса и/или изменения текущих даты и времени является служебная программа date:

date  [-u] [+формат]
date  [-u] ммддччмм[[вв]гг]

В первой форме утилита date выдает на стандартный вывод дату и время (по умолчанию - текущие), в соответствии с заданным форматом. Во второй форме date позволяет установить системные дату и время.

При наличии опции -u работа ведется без учета часового пояса, во всемирном времени (как если бы значением переменной окружения TZ была цепочка "UTC0" ). Без этой опции учитывается значение TZ или, если таковое не установлено или пусто, принимается во внимание системное подразумеваемое значение.

Если в командной строке date задан аргумент, начинающийся со знака +, форматом вывода даты и времени управляет пользователь. Формат включает спецификаторы преобразований и прочие символы (последние выводятся без изменений). Спецификаторы преобразований начинаются со знака % и замещаются при выводе соответствующим значением.

Спецификаторы преобразований
Спецификатор Описание
%a Сокращенное название дня недели.
%A Полное название дня недели.
%b Сокращенное название месяца.
%B Полное название месяца.
%c Принятое в данной языково-культурной среде представление даты и времени.
%C Две первые цифры четырехзначного номера года [00, 99].
%d Номер дня в месяце [01, 31].
%D Дата в формате мм/дд/гг.
%e Номер дня в месяце [1, 31] в двухсимвольном поле с дополнением, при необходимости, пробела.
%h То же, что %b.
%H Номер часа [00, 23].
%I Номер часа [01, 12].
%j Номер дня в году [001, 366].
%m Номер месяца [01, 12].
%M Минуты [00, 59].
%n Перевод строки
%S Секунды [00, 60].
%t Табуляция
%T Время в формате чч:мм:сс.
%u Номер дня недели [1, 7] (единица соответствует понедельнику).
%U Номер недели в году [00, 53]. Первым днем недели считается воскресенье. Все дни нового года, предшествующие первому воскресенью, относятся к нулевой неделе.
%V Номер недели в году [01, 53]. Первым днем недели считается понедельник. Если первое января приходится на пятницу, субботу или воскресенье, конец недели формально относят к прошлому году, а первой считают следующую неделю.
%w Номер дня недели [0, 6] (ноль соответствует воскресенью).
%W Номер недели в году [00, 53]. Первым днем недели считается понедельник. Все дни нового года, предшествующие первому понедельнику, относятся к нулевой неделе.
%x Принятое в данной языково-культурной среде представление даты.
%X Принятое в данной языково-культурной среде представление времени.
%y Две младшие цифры номера года [00, 99].
%Y Четырехзначный номер года.
%Z Имя часового пояса.
%% Знак процента.

Чтобы установить текущие дату и время, необходимо, чтобы система допускала данное действие, а у процесса были соответствующие привилегии.

Во второй форме вызова утилиты date первая пара букв м обозначает номер месяца, дд - номер дня в месяце, чч - часы, мм - минуты, ввгг - год. Если две первые цифры года опущены, то диапазон [69, 99] трактуется как [1969, 1999], а [00, 68] - как [2000, 2068]. Если год не указан, имеется в виду текущий.

Приведем несколько примеров употребления служебной программы date.

Вывод в подразумеваемом формате, используемом командой date без аргументов, может выглядеть так:

Fri Dec 26 17:48:02 MSK 2003

Опция -u повлияет на вывод следующим образом:

Fri Dec 26 14:48:05 UTC 2003

Команда

date '+Дата: %d.%m.%Y%nВремя: %H:%M:%S'

выдаст примерно следующее:

Дата: 26.12.2003
Время: 17:49:15

Для сдвига показаний системных часов на один час вперед можно воспользоваться shell-процедурой, показанной в листинге 12.1 (предполагается, что ее выполняют днем при наличии соответствующих привилегий).

moda=`date +%m%d`
ho=$((`date +%H` + 1))
mi=`date +%M`
date ${moda}${ho}${mi}

На уровне языка C для опроса текущего временисекундах от начала отсчета ) служит функция time() (см. листинг 12.2).

#include <time.h>
time_t time (time_t *tloc);

Если значение аргумента tloc отлично от пустого указателя, то текущее время не только возвращается в качестве результата, но еще и записывается по заданному адресу.

Отметим, что если тип time_t реализован как 32-разрядное целое со знаком, то в 2038-м году наступит переполнение (так называемая проблема 2038-го года ). Стандарт POSIX-2001 не справляется с проблемой в полной мере, но облегчает ее решение за счет использования более или менее абстрактного типа time_t вместо какой-либо конкретной разновидности int. Вероятно, в последующие версии стандарта POSIX будут включены требования к способности значений типа time_t представлять время в далеком будущем (например, вплоть до 10000-го года ) или явное требование не менее чем 64-разрядности.

С функцией time() ассоциирована функция difftime(), вычисляющая (в виде значения типа double ) разность в секундах между двумя моментами времени ( time1 - time0, см. листинг 12.3).

#include <time.h>
double difftime (time_t time1, time_t time0);

Узнать текущее время с большей точностью позволяет функция gettimeofday() (см. листинг 12.4).

#include <sys/time.h>
int gettimeofday (struct timeval *restrict tp, 
                  void *restrict tzp);

Согласно стандарту POSIX-2001, описанная в заголовочном файле <sys/time.h> структура timeval содержит по крайней мере следующие поля.

time_t      tv_sec;     /* Секунды */
suseconds_t tv_usec;    /* Микросекунды */

Функция gettimeofday() записывает текущее время, выраженное в секундах и микросекундах от начала отсчета, в структуру типа timeval, на которую указывает аргумент tp. Указатель tzp должен быть пустым.

Впрочем, микросекунды - это еще на предел точности, а опрос системных часов - не предел общности. В необязательную часть стандарта POSIX-2001, регламентирующую работу с таймерами, входят функции, позволяющие опросить и установить показания заданных часов, а также узнать их разрешающую способность (см. листинг 12.5).

#include <time.h>
int clock_getres (clockid_t clock_id, 
                  struct timespec *res);
int clock_gettime (clockid_t clock_id, 
                   struct timespec *tp);
int clock_settime (clockid_t clock_id, 
                   const struct timespec *tp);

Структура timespec отличается от timeval тем, что вместо микросекунд хранит наносекунды:

time_t tv_sec;  /* Секунды */
long   tv_nsec; /* Наносекунды */

Функция clock_getres() позволяет опросить разрешающую способность часов с заданным идентификатором и поместить результат в структуру, на которую указывает аргумент res.

Функция clock_gettime() записывает в аналогичную структуру текущие показания заданных часов.

Наконец, функция clock_settime() устанавливает заданные часы на основании значений полей структуры типа timespec, на которую указывает аргумент tp.

Часы могут быть общесистемными, т. е. видимыми для всех процессов, или принадлежащими процессу (измеряют время, имеющее смысл только для конкретного процесса). Все реализации должны поддерживать общесистемные часы реального времени, измеряющие время от начала отсчета и имеющие идентификатор CLOCK_REALTIME. Интерпретация показаний других часов (если таковые имеются) стандартом не специфицируется.

Установка новых показаний для часов CLOCK_REALTIME влияет на срабатывание таймеров, взведенных на абсолютное время, но не затрагивает интервальные таймеры.

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

Применение описанных выше функций иллюстрируется программой, показанной в листинге 12.6. Возможно, при редактировании ее внешних связей придется указать дополнительные библиотеки (например, -lrt ), что является следствием использования необязательных компонентов стандарта POSIX.

#include <stdio.h>
#include <time.h>
#include <sys/time.h>

int main (void) {
 struct timespec tmsp;
 struct timeval tmvl;
 time_t st;
 double s = 0;
 double d = 1;
 int i;

 if (clock_getres (CLOCK_REALTIME, tmsp) == -1) {
    perror ("CLOCK_GETRES");
    return (1);
 }
 printf ("Разрешающая способность общесистемных часов: %ld нсек.\n",
            tmsp.tv_nsec);

 if (clock_gettime (CLOCK_REALTIME, tmsp) == -1) {
    perror ("CLOCK_GETTIME");
    return (2);
 }
 printf ("Текущее время по общесистемным часам: %ld сек. %ld нсек.\n",
            tmsp.tv_sec, tmsp.tv_nsec);

(void) gettimeofday (tmvl, NULL);
 printf ("Текущее время, функция gettimeofday(): %ld сек. %ld мксек.\n",
            tmvl.tv_sec, tmvl.tv_usec);

 (void) time (st);
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
 printf ("Время выполнения цикла: %g сек.\n", difftime (time (NULL), st));

 return (0);
}

Результаты работы этой программы могут выглядеть так, как показано в листинге 12.7.

Разрешающая способность общесистемных часов: 
10000000 нсек.
Текущее время по общесистемным часам: 
1072534678 сек. 296598000 нсек.
Текущее время, функция gettimeofday(): 
1072534678 сек. 296637 мксек.
Время выполнения цикла: 15 сек.

Преобразование данных о времени

Стандартом POSIX-2001 предусмотрено несколько способов представления данных о времени. Выше были описаны структура tm и тип time_t. Кроме того, время может записываться в виде цепочки символов. (Есть еще структуры timeval и timespec, но они в данном контексте играют подчиненную роль, лишь уточняя значения типа time_t.)

Для выполнения преобразований между разными представлениями данных о времени служат описываемые далее функции (см. также рис. 12.1).

(рис 12.1) Функции для выполнения преобразований между разными представлениями данных о времени.

Функции gmtime() и localtime() (см. листинг 12.8) преобразуют значения типа time_t в структуру типа tm. Соотношение между временем в секундах от начала отсчета и значениями полей структуры типа tm дается в приведенной выше формуле. Кроме того, функция localtime() учитывает данные о часовом поясе и сезонных поправках.

#include <time.h>
struct tm *gmtime (const time_t *tloc);
struct tm *localtime (const time_t *tloc)

Для учета данных о часовом поясе и сезонных поправках используются внешние переменные tzname, timezone, daylight, значения которых функция tzset() устанавливает по переменной окружения TZ (см. листинг 12.9).

#include <time.h>
extern char *tzname[2];
extern long timezone;
extern int daylight;
void tzset (void);

Элементам массива tzname присваиваются имена местного часового пояса в стандартном ( tzname [0] ) и "летнем" ( tzname [1] ) вариантах. Значение переменной timezone устанавливается равным разности в секундах между всемирным и местным поясным временем. Переменной daylight присваивается отличное от нуля значение, если для местного часового пояса предусмотрен переход на летнее время.

Отметим, что в общем случае значение переменой окружения TZ устроено довольно сложным образом:

Станд_поясСмещение[Лет_пояс[Смещение]
      [,Нач_лет[/Время],Кон_лет[/Время]]]

Здесь Станд_пояс и Лет_пояс - имена, присваиваемые элементам массива tzname [], Смещение - разность между всемирным и местным поясным временем (в виде чч[:мм[:сс]]), Нач_лет и Кон_лет, соответственно, даты начала и окончания действия летнего времени (обычно их задают в виде Mмм.н.д - месяц, неделя, день ), время - время перехода (по умолчанию - два часа ночи). Можно видеть, что данных для вычисления местного времени оказывается вполне достаточно.

Функцию mktime() (см. листинг 12.10) можно считать обратной по отношению к localtime(). Она преобразует местное время, заданное в виде структуры типа tm, в значение типа time_t, т. е. в секунды от начала отсчета по всемирному времени.

#include <time.h>
time_t mktime (struct tm *tmptr);

При входе в функцию mktime() значения полей tm_wday и tm_yday структуры tm, на которую указывает аргумент tmptr, игнорируются; при выходе они устанавливаются должным образом. Значения других полей также приводятся к стандартным для них диапазонам (при входе это условие может не выполняться).

Другим весьма мощным средством преобразования местного времени из структурного в текстовое представление является функция strftime() (см. листинг 12.11). Как и служебная программа date, она преобразует дату и время в соответствии с заданным форматом, только исходными данными служит не текущий момент времени, а структура типа tm, на которую указывает аргумент tmptr, и результат направляется не на стандартный вывод, а в буфер, заданный указателем s и длиной maxsize.

#include <time.h>
size_t strftime (char *restrict s, 
   size_t maxsize, const char *restrict format,
   const struct tm *restrict tmptr);

По сравнению с date, функция strftime() содержит несколько дополнительных спецификаторов форматных преобразований. Перечислим наиболее употребительные из них.

Спецификаторы преобразований
Спецификатор Описание
%F Представление даты в соответствии со стандартом ISO 8601:2000: эквивалент последовательности спецификаторов %Y-%m-%d.
%R Часы и минуты (%H:%M) в 24-часовом представлении.
%z Смещение относительно всемирного времени, представленное по стандарту ISO 8601:2000: +ччмм или -ччмм (положительные значения соответствуют часовым поясам к востоку от Гринвича).

Функция strftime() возвращает число байт, помещенное в буфер (без учета завершающего нулевого байта). Если буфер оказался мал, возвращается ноль.

На роль обратной по отношению к strftime() могут претендовать сразу две функции: strptime() и getdate() (см. листинг 12.12).

#include <time.h>
char *strptime (const char *restrict s, 
           const char *restrict format, 
           struct tm *restrict tmptr);
struct tm *getdate (const char *s);

Функция strptime() напоминает sscanf(): она сканирует цепочку символов, на которую указывает аргумент s, в соответствии с заданным форматом, включающим описанные выше спецификаторы преобразований, а также, быть может, пробельные и обычные символы, и помещает извлеченные значения в структуру типа tm по указателю tmptr. В качестве результата возвращается указатель на первый несканированный символ или NULL в случае неудачи.

Функция getdate(), по сути аналогичная strptime(), использует для разбора входной цепочки s форматы, содержащиеся в файле, чье полное маршрутное имя задано переменной окружения DATEMSK (для интерпретации выбирается первый подходящий формат). Если дата и время специфицированы не полностью (например, задан только день недели), как исходные берутся данные о первом подходящем моменте времени, начиная с текущего. Если в формате присутствует спецификатор %Z, выходная структура инициализируется текущим временем в сканируемом часовом поясе. В противном случае применяется местное время.

С помощью внешней переменной (или макроса) getdate_err функция getdate() возвращает коды ошибок.

Приведем пример использования описанных функций преобразования данных о времени (см. листинг 12.13).

#include <stdio.h>
#include <time.h>
#include <limits.h>

int main (void) {
 char dtbuf [LINE_MAX];        /* Буфер для данных о времени */
 time_t st;
 struct tm stm;

 (void) time (st);

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (st));
 printf ("Текущее всемирное время: %s\n", dtbuf);

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (st));
 printf ("Текущее местное время: %s\n", dtbuf);

 /* Узнаем, каким днем недели будет 01 января 2038 года */
 stm.tm_year = 2038 - 1900;
 stm.tm_mon = 1 - 1;
 stm.tm_mday = 1;
 stm.tm_hour = 0;
 stm.tm_min = 0;
 stm.tm_sec = 0;
 stm.tm_isdst = -1;
 if ((st = mktime (stm)) == (time_t) (-1)) {
    perror ("MKTIME");
 } else {
    (void) strftime (dtbuf, sizeof (dtbuf), "%A", stm);
    printf ("День недели 01 января 2038 года: %s\n", dtbuf);
    printf ("Число секунд от начала отсчета в начале 2038 года (шест.): %x\n",
              (unsigned int) st);
 }

 /* Узнаем, когда наступит переполнение значений типа time_t, */
 /* представленных как 32-разрядное целое со знаком           */
 st = (time_t) 0x7fffffff;
 (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (st));
 printf ("Всемирное время конца 32-разрядного отсчета: %s\n", dtbuf);

 /* Преобразуем эту дату в формат ISO 8601:2000 */
 if (strptime (dtbuf, "%c", stm) == NULL) {
    perror ("STRPTIME");
 } else {
    (void) strftime (dtbuf, sizeof (dtbuf), "%F", stm);
    printf ("Дата конца 32-разрядного отсчета в формате ISO 8601:2000: %s\n",
              dtbuf);
 }

 return (0);
}

Возможные результаты выполнения этой программы показаны в листинге 12.14.

Текущее всемирное время: Sat Jan  3 13:54:02 2004
Текущее местное время: Sat Jan  3 16:54:02 2004
День недели 01 января 2038 года: Friday
Число секунд от начала отсчета в начале 2038 года (шест.): 7fe7ed50
Всемирное время конца 32-разрядного отсчета: Tue Jan 19 03:14:07 2038
Дата конца 32-разрядного отсчета в формате ISO 8601:2000: 2038-01-19

Последовательность инструкций языка C

char dtbuf [];
time_t st;
(void) strftime (dtbuf, sizeof (dtbuf), "%c",
                        localtime (st));

является стандартной для получения текущего времени в текстовом виде. (Разумеется, вместо " %c " допустим другой спецификатор преобразования.) Нужно помнить только, что функции gmtime() и localtime() возвращают указатели на статические структуры, содержимое которых, возможно, перезаписывается при каждом вызове, поэтому, если оно понадобится в дальнейшем, его следует скопировать в собственные объекты.

Опрос показаний часов процессорного времени

Базовым средством для работы с часами процессорного времени является функция clock() (см. листинг 12.15). Она возвращает в качестве результата процессорное время, затраченное процессом с некоего момента, зависящего от реализации и связанного только с его (процесса) запуском. В случае ошибки результат равен ( clock_t ) (-1).

#include <time.h>
clock_t clock (void);

Чтобы перевести время, возвращаемое функцией clock(), в секунды, его следует поделить на константу CLOCKS_PER_SEC, которая определена в заголовочном файле <time.h> равной одному миллиону, хотя разрешающая способность часов процессорного времени в конкретной реализации может и отличаться от микросекунды.

Отметим, что если значения типа clock_t представлены как 32-разрядные целые со знаком, то менее чем через 36 минут ( процессорного времени ) наступит переполнение.

В листинге 12.16 показан пример использования функции clock(). Поскольку начало отсчета для часов процессорного времени не специфицировано, их показания опрашиваются в начале и конце измеряемого промежутка, а результатом измерения затраченного процессорного времени служит разность этих показаний.

#include <stdio.h>
#include <time.h>

int main (void) {
time_t st;
clock_t ct;
double s = 0;
double d = 1;
int i;

fprintf (stderr, "Начало выполнения цикла\n");
(void) time (st);
ct = clock;
for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
}
fprintf (stderr, "Процессорное время выполнения цикла: %g сек.\n",
            (double) (clock - ct) / CLOCKS_PER_SEC);
fprintf (stderr, "Астрономическое время выполнения цикла: %g сек.\n",
            difftime (time (NULL), st));

return (0);
}

Возможные результаты работы этой программы показаны в листинге 12.17. Процессорное время получилось больше астрономического из-за ошибок округления.

Начало выполнения цикла
Процессорное время выполнения цикла: 15.19 сек.
Астрономическое время выполнения цикла: 15 сек.

Для измерения времени выполнения простой команды можно воспользоваться служебной программой time (не входящей, правда, в обязательную часть стандарта POSIX-2001 ):

time [-p] команда [аргумент ...]

Она выдает в стандартный протокол астрономическое и процессорное время, прошедшее от запуска до завершения команды с указанными аргументами. Если задана опция -p, время выдается в секундах в виде вещественных чисел. Процессорное время подразделяется на пользовательское (затраченное самой командой) и системное (ушедшее на оказание системных услуг команде). Если предположить, что выполнимый файл приведенной выше программы называется my_tcex, то командная строка

time -p my_tcex

выдаст в стандартный протокол примерно следующее (см. листинг 12.18):

Начало выполнения цикла
Процессорное время выполнения цикла: 15.2 сек.
Астрономическое время выполнения цикла: 15 сек.
real 15.20
user 15.20
sys 0.00

Если нужно измерить время выполнения конвейера или списка команд, их можно поместить в файл, превратив тем самым в простую команду, или воспользоваться конструкцией вида

time sh -c 'составная команда'

Оружием более крупного калибра является специальная встроенная в shell команда

times

Она выдает на стандартный вывод процессорное время, затраченное командным интерпретатором и порожденными им процессами. Например, после выполнения команд

my_tcex  my_tcex
times
на стандартный вывод может быть выдано:
0m0.010s 0m0.000s
0m30.410s 0m0.000s

Можно видеть, что сам командный интерпретатор практически не занимал процессор, а два порожденных им процесса в сумме потребили чуть меньше тридцати с половиной секунд ; время системных услуг в обоих случаях оказалось пренебрежимо малым.

Реализация описанных выше утилит time и times опирается на функцию times() (см. листинг 12.19). Она опрашивает данные о времени выполнения вызывающего процесса и порожденных процессов, завершение которых ожидалось с помощью функций wait() или waitpid(). В отличие от clock(), функция times() измеряет время в тактах часов, и переполнение 32-разрядного представления значений типа clock_t происходит не через полчаса, а примерно в течение года (если секунда делится на 60 - 100 тактов). Соответственно, для перевода результатов работы times() в секунды их нужно делить на sysconf ( _SC_CLK_TCK ), а не на CLOCKS_PER_SEC.

#include <sys/times.h>
clock_t times (struct tms *buffer);

В качестве результата функция times() возвращает астрономическое время, прошедшее от некоторого момента в прошлом (например, от загрузки системы), но основные, содержательные данные, относящиеся к вызывающему процессу и его потомкам, помещаются в структуру типа tms, которая описана в заголовочном файле <sys/times.h> и, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля:

clock_t tms_utime;  
/* Процессорное время, затраченное */
/* вызывающим процессом            */

clock_t tms_stime;  
/* Процессорное время, затраченное системой */
/* на обслуживание вызывающего процесса     */

clock_t tms_cutime; 
/* Процессорное время, затраченное 
/*завершившимися порожденными процессами    */

clock_t tms_cstime; 
/* Процессорное время, затраченное системой */
/* на обслуживание завершившихся            */
/* порожденных процессов                    */

Значения времени завершившихся порожденных процессов ( tms_utime и tms_cutime, а также tms_stime и tms_cstime ) добавляются, соответственно, к элементам tms_cutime и tms_cstime структуры родительского процесса при возврате из функций wait() или waitpid(). Это происходит рекурсивно: если порожденный процесс ожидал завершения своих потомков, то в упомянутых элементах структуры накопятся данные о времени выполнения поддерева процессов.

В качестве примера использования функции times() измерим время работы рассматривавшейся ранее программы, копирующей строки со стандартного ввода на стандартный вывод через сокеты адресного семейства AF_UNIX с помощью порожденного процесса (см. листинг 12.20).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через пару сокетов.                                   */
/* Используются функции ввода/вывода нижнего уровня.                     */
/* Измеряется астрономическое и процессорное время                       */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/times.h>

#define MY_PROMPT       "Вводите строки\n"
#define MY_MSG          "Вы ввели: "

int main (void) {
 int sds [2];
 char buf [1];
 int new_line = 1;     /* Признак того, что надо выдать сообщение MY_MSG   */
                              /* перед отображением очередной строки              */
 clock_t st_ct;        /* Данные об астрономическом и процессорном времени */
 struct tms st_cpt;    /* в начале работы программы                        */
 clock_t en_ct;        /* Данные об астрономическом и процессорном времени */
 struct tms en_cpt;    /* в конце работы программы                         */
 long tck_p_sec;       /* Количество тактов часов в секунде                */

 /* Опросим данные о времени начала выполнения */
 if ((st_ct = times (st_cpt)) == (clock_t) (-1)) {
    perror ("TIMES-1");
    return (1);
 }

 /* Создадим пару соединенных безымянных сокетов */
 if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) {
    perror ("SOCKETPAIR");
    return (2);
 }

 switch (fork ()) {
    case -1:
      perror ("FORK");
      return (3);
    case 0:
      /* Чтение из сокета sds [0] и выдачу на стандартный вывод */
      /* реализуем в порожденном процессе                       */
      while (read (sds [0], buf, 1) == 1) {
    if (write (STDOUT_FILENO, buf, 1) != 1) {
            perror ("WRITE TO STDOUT");
      break;
    }
      }
      exit (0);
 }

 /* Чтение со стандартного ввода и запись в сокет sds [1] */
 /* возложим на родительский процесс                      */
 if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
                                          sizeof (MY_PROMPT) - 1) {
    perror ("WRITE TO SOCKET-1");
 }

 while (read (STDIN_FILENO, buf, 1) == 1) {
    /* Перед отображением очередной строки */
    /* нужно выдать сообщение MY_MSG       */
    if (new_line) {
      if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
         perror ("WRITE TO SOCKET-2");
         break;
      }
    }
    if (write (sds [1], buf, 1) != 1) {
      perror ("WRITE TO SOCKET-3");
      break;
    }
    new_line = (buf [0] == '\n');
 }
 shutdown (sds [1], SHUT_WR);

 (void) wait (NULL);

 /* Опросим данные о времени конца выполнения, */
 /* вычислим и выдадим результаты измерений    */
 if ((en_ct = times (en_cpt)) == (clock_t) (-1)) {
    perror ("TIMES-2");
    return (4);
 }

 tck_p_sec = sysconf (_SC_CLK_TCK);
 fprintf (stderr, "Тактов в секунде: %ld\n", tck_p_sec);
 fprintf (stderr, "Астрономическое время работы программы: %g сек.\n",
             (double) (en_ct - st_ct) / tck_p_sec);
 fprintf (stderr, "Процессорное время, затраченное процессом: %g сек.\n",
             (double) (en_cpt.tms_utime - st_cpt.tms_utime) / tck_p_sec);
 fprintf (stderr, "Процессорное время, затраченное системой: %g сек.\n",
             (double) (en_cpt.tms_stime - st_cpt.tms_stime) / tck_p_sec);
 fprintf (stderr, "Аналогичные данные для порожденных процессов:\n");
 fprintf (stderr, "%g сек.\n%g сек.\n",
             (double) (en_cpt.tms_cutime - st_cpt.tms_cutime) / tck_p_sec,
             (double) (en_cpt.tms_cstime - st_cpt.tms_cstime) / tck_p_sec);

 return (0);
}

Если перенаправить стандартный ввод в какой-либо текстовый файл заметных размеров, а стандартный вывод - в другой файл, можно получить результаты, подобные тем, что показаны в листинге 12.21.

Тактов в секунде: 100
Астрономическое время работы программы: 1.19 сек.
Процессорное время, затраченное процессом: 0.02 сек.
Процессорное время, затраченное системой: 0.08 сек. 
Аналогичные данные для порожденных процессов:
0.09 сек.
1 сек.

Прямое манипулирование часами процессорного времени возможно после обращения к функции clock_getcpuclockid() (см. листинг 12.22), позволяющей выяснить их идентификатор. Оговоримся, однако, что эта функция не входит в обязательную часть стандарта POSIX-2001 (она относится к продвинутым средствам реального времени).

#include <time.h>
int clock_getcpuclockid 
      (pid_t pid, clockid_t *clock_id);

Идентификатор часов процессорного времени, обслуживающих заданный аргументом pid процесс, возвращается по адресу, специфицированному аргументом clock_id. Если значение pid равно нулю, имеется в виду вызывающий процесс. Признаком успешного завершения является нулевой результат; в противном случае возвращается код ошибки.

В листинге 12.23 показан пример употребления функции clock_getcpuclockid() и манипулирования часами процессорного времени. Возможные результаты выполнения этой программы - в листинге 12.24.

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <errno.h>

int main (void) {
 clockid_t clk_id;     /* Идентификатор часов процессорного времени */
 struct timespec tmsp;
 double s = 0;
 double d = 1;
 int i;

 if ((errno = clock_getcpuclockid ((pid_t) 0, clk_id)) != 0) {
    perror ("CLOCK_GETCPUCLOCKID");
    return (1);
 }

 if (clock_getres (clk_id, tmsp) == -1) {
    perror ("CLOCK_GETRES");
    return (2);
 }
 printf ("Разрешающая способность часов процессорного времени: %ld нсек.\n",
      tmsp.tv_nsec);

 /* Измерим процессорное время выполнения цикла. */
 fprintf (stderr, "Начало выполнения цикла\n");
 tmsp.tv_sec = tmsp.tv_nsec = 0;
 if (clock_settime (clk_id, tmsp) == -1) {
    perror ("CLOCK_SETTIME");
    return (3);
 }
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
 if (clock_gettime (clk_id, tmsp) == -1) {
    perror ("CLOCK_GETTIME");
    return (4);
 }
 fprintf (stderr, "Процессорное время выполнения цикла: %ld сек. %ld нсек.\n",
      tmsp.tv_sec, tmsp.tv_nsec);

 return (0);
}
Разрешающая способность часов процессорного времени: 2 нсек.
Начало выполнения цикла
Процессорное время выполнения цикла: 15 сек. 350313054 нсек.

Обратим внимание на два момента. Перед началом измеряемого участка программы на часах процессорного времени были установлены нулевые показания, что, разумеется, не влияет на приоритет процесса и его планирование. Конечно, можно было обойтись без переустановки, запомнив показания часов в начале и конце измеряемого промежутка и произведя затем их вычитание, но технически это чуть сложнее; кроме того, нам хотелось применить функцию clock_settime(). Второй заслуживающий отдельного упоминания момент состоит в отсутствии необходимости выяснять идентификатор часов процессорного времени вызывающего процесса с помощью функции clock_getcpuclockid(): можно воспользоваться именованной константой CLOCK_PROCESS_CPUTIME_ID.

Опрос и изменение данных о времени, ассоциированных с файлами

Информацию о файле, в том числе данные о времени последнего доступа и изменения, можно получить с помощью описанных ранее функций семейства stat().

Для изменения ассоциированных с файлами данных о времени служит функция utime() (см. листинг 12.25), оперирующая в терминах секунд от начала отсчета.

#include <utime.h>
int utime (const char *path, 
           const struct utimbuf *times);

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

Если значение times отлично от NULL, оно трактуется как указатель на структуру типа utimbuf, которая, согласно стандарту POSIX-2001, содержит по крайней мере следующие поля:

time_t actime;  
/* Время последнего доступа   */

time_t modtime; 
/* Время последнего изменения */

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

В случае успешного завершения функция utime() возвращает нулевой результат и, кроме того, переустанавливает время последнего изменения статуса файла.

Приведем пример программы, изменяющей время последнего доступа к файлу (см. листинг 12.26).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа устанавливает время последнего доступа к файлу -  */
/* аргументу командной строки, равное текущему времени          */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <utime.h>

int main (int argc, char *argv []) {
 struct stat st_buf;    /* Буфер для опроса данных о файле                  */
 struct utimbuf ut_buf; /* Буфер для формирования изменяемых данных о файле */

 if (argc != 2) {
    fprintf (stderr, "Использование: %s файл\n", argv [0]);
    return (1);
 }

 if (stat (argv [1], st_buf) != 0) {
    perror ("STAT");
    return (2);
 }

 ut_buf.actime = time (NULL);
 ut_buf.modtime = st_buf.st_mtime;

 if (utime (argv [1], ut_buf) != 0) {
    perror ("UTIME");
    return (3);
 }

 return (0);
}

Чтобы изменить только одно из двух ассоциированных с файлом времен, необходимо с помощью функции stat() получить информацию о файле, перенести неизменяемое значение из структуры типа stat в utimbuf, установить требуемым образом второй элемент структуры типа utimbuf и только после этого обратиться к функции utime().

Читателю предлагается убедиться, что приведенная программа работает должным образом и что опция -u служебной программы ls предписывает оперировать со временем последнего доступа вместо времени последнего изменения.

Приостановка выполнения на заданное время

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

Функция sleep() (см. листинг 12.27) позволяет приостановить выполнение процесса (потока управления) на заданное число секунд. Если в это время будет доставлен сигнал, который не игнорируется, вызов sleep() завершится досрочно, а результат будет равен "недоспанному" числу секунд. (В случае нормального завершения результат равен нулю, что формально можно считать частным случаем предыдущего.)

#include <unistd.h>
unsigned sleep (unsigned seconds);

Обратим внимание на то, что аргумент и результат функции sleep() описаны как unsigned. Это значит, что приложения, строго соответствующие стандарту POSIX-2001, не должны передавать sleep() величины, превышающие минимально допустимое значение конфигурационной константы UINT_MAX, которое в стандарте ISO C [5] полагается равным 65535. Использование больших величин, вообще говоря, ограничивает мобильность приложений.

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

#include <time.h>
int nanosleep (const struct timespec *rqtp, 
               struct timespec *rmtp);

Аргумент rqtp функции nanosleep() задает запрашиваемую длительность приостановки. По указателю rmtp, если он не пуст, возвращается "недоспанное" время. В отличие от sleep(), результат nanosleep() равен -1 - как в результате ошибки, так и при "недосыпании", вызванном доставкой сигнала.

В общем случае ожидание наступления какого-либо события может быть активным или пассивным. Активное ожидание предполагает постоянный, повторяющийся в цикле опрос ассоциированных с событием значений и, следовательно, вызывает расход процессорного времени. При пассивном ожидании процесс приостанавливается (например, на семафоре) до наступления события, не занимая процессор. Функции sleep() и nanosleep() позволяют выбрать компромиссный способ ожидания, когда процессорного времени расходуется немного и оперативность реагирования на события оказывается удовлетворительной.

В листинге 12.29 показан пример использования функции sleep() для "полупассивного" ожидания изменения файла. Разумеется, обработка файла может быть более содержательной, однако представленная схема действий носит весьма общий характер и используется, например, служебными программами cron, tail и др.

/* * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа отслеживает изменение размера файла,  */
/* заданного как аргумент командной строки         */
/* * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <limits.h>

static time_t lt_mod = 0;       /* Время последнего изменения файла */
static struct stat st_buf;      /* Данные о файле                   */

/* Функция начальной обработки файла */
static void proc_init (const char *path) {
 fprintf (stderr, "Данные о размере файла %s\n", path);
 fprintf (stderr, "Время изменения            Размер\n");
}

/* Функция проверки, нужно ли обрабатывать файл */
/* Результат > 0 - нужно                        */
/*             0 - не нужно                     */
/* lt; 0 - ошибка                       */
static int check_mod (const char *path) {
 if (stat (path, st_buf) != 0) {
    perror ("STAT");
    return (-1);
 }

 if (st_buf.st_mtime != lt_mod) {
    /* Файл изменился и, следовательно, нуждается в обработке */
    lt_mod = st_buf.st_mtime;
    return (1);
 }

 return 0;
}

/* Функция обработки файла.                            */
/* Выведем время последнего изменения и текущий размер */
static void proc_mod (void) {
 char dtbuf [LINE_MAX];        /* Буфер для данных о времени */

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (lt_mod));
 fprintf (stderr, "%s   %ld\n", dtbuf, st_buf.st_size);
}

int main (int argc, char *argv []) {
 int res;

 if (argc != 2) {
    fprintf (stderr, "Использование: %s файл\n", argv [0]);
    return (1);
 }

 proc_init (argv [1]);
 while (1) {
    if ((res = check_mod (argv [1])) > 0) {
      proc_mod ();
    } else if (res lt; 0) {
      return (res);
    }
    sleep (1);
 }

 return 0;
}

Возможные результаты работы этой программы показаны в листинге 12.30.

Данные о размере файла /var/log/cron
Время изменения            Размер
Tue Jan  6 12:50:00 2004   11191
Tue Jan  6 13:01:00 2004   11263
Tue Jan  6 13:10:00 2004   11409
Tue Jan  6 13:20:00 2004   11481
  . . .
Tue Jan  6 13:40:00 2004   11624
  . . .

Функция nanosleep() позволяет до некоторой степени промоделировать работу функций poll() и select(), реализующих пассивное ожидание готовности данных, в тех ситуациях, когда данные поступают из разнородных источников, которые poll() и select() не обслуживают (не только из файлов, но и из очередей сообщений, как результат опроса значений семафоров и т.п.). В листинге 12.31 показан цикл "полуактивного" (с короткими приостановками) ожидания поступления данных из канала и обнуления значения семафора.

#define N               ...
#define MY_BUFSIZ       ...

struct timespec tmsp = {0, 20000000};
int fds [2];                    /* Файловые дескрипторы канала               */
int semid;                      /* Идентификатор набора семафоров            */
struct sembuf sembuf [1];       /* Массив для задания операций над семафором */
char buf [MY_BUFSIZ];           /* Буфер для чтения данных из канала         */
int brd;                        /* Число прочитанных байт                    */
int i;

  . . .

fcntl (fds [0], F_SETFL, O_NONBLOCK);
sembuf [0].sem_num = 0;
sembuf [0].sem_flg = IPC_NOWAIT;
sembuf [0].sem_op =  0;

for (i = 0; i lt; N; i++) {
 nanosleep (tmsp, NULL);

 if ((brd = read (fds [0], buf, MY_BUFSIZ) > 0) {
    /* Обработка прочитанных данных.                       */
    /* Возможно, поступление данных на этом не закончилось */
        . . .
    continue;
 }

 if (semop (semid, sembuf, 1) == 0) {
    /* Значение семафора обнулено, ждать больше нечего */
        . . .
    if (brd <= 0) {
      close (fds [0]);
      break;
    }
 }
}

  . . .

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

Работа с интервальными таймерами

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

Описываемые далее средства для работы с интервальными таймерами входят в необязательную часть стандарта POSIX-2001, именуемую "X/Open-расширение системного интерфейса" (XSI).

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

ITIMER_REAL

Таймер реального времени. Он ассоциирован с часами CLOCK_REALTIME и, следовательно, его показания уменьшаются в реальном масштабе времени. Когда он срабатывает, процессу доставляется сигнал SIGALRM.

ITIMER_VIRTUAL

Таймер виртуального времени процесса. Его показания уменьшаются в соответствии с течением виртуального времени процесса, т. е. только тогда, когда процесс выполняется. При срабатывании таймера процессу доставляется сигнал SIGVTALRM.

ITIMER_PROF

Таймер профилирования. Показания этого таймера уменьшаются с течением виртуального времени процесса, а также тогда, когда работают запрошенные им системные сервисы. Он предназначен для построения статистическими методами профилей расходования процессом процессорного времени. При его срабатывании процессу доставляется сигнал SIGPROF.

Согласно стандарту POSIX-2001, интервальные таймеры обслуживаются функциями getitimer() и setitimer() (см. листинг 12.32).

#include <sys/time.h>
int getitimer (int timer_id, 
    struct itimerval *cvalue);
int setitimer (int timer_id, 
    const struct itimerval *restrict nvalue, 
    struct itimerval *restrict ovalue);

Поддерживаемые стандартом значения аргумента timer_id перечислены выше. Другие характеристики интервальных таймеров задаются в структуре типа itimerval, которая должна содержать по крайней мере следующие поля:

struct timeval it_interval;   
/* Интервал таймера */

struct timeval it_value;      
/* Текущие показания таймера */
/* (ведется обратный отсчет) */

(Напомним, что структура типа timeval была описана выше. Она содержит по крайней мере два поля, задающие время, соответственно, в секундах и микросекундах.)

Значение поля it_value (если оно отлично от нуля) показывает время, оставшееся до срабатывания таймера. После срабатывания таймер запускается вновь с начальным значением поля it_value, равным it_interval (если последнее отлично от нуля).

Установка нулевого значения в поле it_value, независимо от величины it_interval, снимает таймер со взвода. Если сделать нулевым значение it_interval, таймер будет разряжен после очередного срабатывания. Таким способом можно реализовать таймер с ограниченной периодичностью (в частности, одноразовый).

Функция getitimer() запоминает текущие характеристики таймера с заданным идентификатором в структуре, на которую указывает аргумент cvalue. Функция setitimer() - ее можно назвать многоцелевой - взводит или снимает таймер со взвода, устанавливает новые характеристики, пользуясь значением аргумента nvalue, и сохраняет старые по указателю ovalue (если он отличен от NULL ).

Признаком успешного завершения обеих функций является нулевой результат.

Здесь уместно акцентировать внимание на одной тонкости, которую до сих пор мы старательно замалчивали. Поскольку разрешающая способность любых часов конечна, для показаний часов и таймеров можно установить только те значения, которые кратны этой разрешающей способности. Если соответствующие аргументы функций clock_settime(), nanosleep() или setitimer() не удовлетворяют данному условию, производится округление. Для часов используется наилучшее приближение снизу, для длительности приостановки выполнения и таймеров - наилучшее приближение сверху ( стандарт POSIX-2001 требует, чтобы длительности приостановки выполнения и промежутка времени до срабатывания таймера были не меньше заказанных).

Последнее обстоятельство позволяет выяснить размер такта часов реального времени довольно неожиданным образом (впрочем, вполне стандартным и мобильным с точки зрения спецификаций POSIX-2001 ) (см. листинг 12.33).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа выясняет размер такта часов реального времени */ 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/time.h>

int main (void) {
 struct itimerval itvl;

 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 1;
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 0;

 if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
    perror ("SETITIMER");
    return (1);
 }

 if (getitimer (ITIMER_REAL, itvl) < 0) {
    perror ("GETITIMER");
    return (2);
 }

 if (itvl.it_interval.tv_usec < 2) {
    printf ("Не удалось выяснить размер такта часов реального времени\n");
 } else {
    printf ("Размер такта часов реального времени: %ld мксек\n",
              itvl.it_interval.tv_usec);
 }

 return 0;
}

Здесь интервальный таймер не взводится (поскольку значение it_value - нулевое) и, соответственно, не срабатывает. Он нужен лишь для того, чтобы операционная система подкорректировала должным образом (с учетом разрешающей способности часов реального времени ) значение поля it_interval.tv_usec. В результате корректировки первоначально присвоенная этому полю единица может превратиться, например, в 10000 (микросекунд), что дает тактовую частоту часов реального времени 100 Гц.

Следующая программа (см. листинг 12.34) сравнивает ход реального и виртуального времени процесса, определяя, сколько реального времени прошло за один квант виртуального (в данном случае квант - это 100000 мксек, т. е. 0.1 сек). Колебания показаний, естественно, вызываются нагрузкой на процессор, индуцируемой другими, параллельно работающими процессами. В принципе, целенаправленная организация подобных колебаний и их анализ могут быть использованы для создания так называемых скрытых каналов по времени (см. [7]).

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа сравнивает ход реального и виртуального */
/* времени процесса                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

#define TM_BUF_SIZE     128

                /* Массив для хранения показаний часов     */
                /* реального времени в момент срабатывания */
                /* таймера виртуального времени процесса   */
static struct timeval rt_vls [TM_BUF_SIZE];
                /* Указатель на текущий элемент */
                /* массива rt_vls               */
static struct timeval *prt_vls = rt_vls;

/* Функция обработки срабатывания таймеров */
/* (сигналы SIGALRM и SIGVTALRM)           */
static void proc_itexprtn (int signo) {
 struct itimerval itmvl = {{0, 0}, {0, 0}};

 /* Запомним текущее астрономическое время */
 (void) gettimeofday (prt_vls++, NULL);
 if (signo == SIGALRM) {
    /* Сработал таймер реального времени.   */
    /* Разрядим таймер виртуального времени */
    /* и завершимся (с выдачей результатов) */
    (void) setitimer (ITIMER_VIRTUAL, itmvl, NULL);
    exit (0);
 }
}

/* Функция выдачи данных о ходе реального времени             */
/* на фоне равномерного течения виртуального времени процесса */
static void print_rt_data (void) {
 struct timeval *tprt_vls = rt_vls;
 int i = 0;

 printf ("Прошедшее реальное время за один квант виртуального\n");
 while (++tprt_vls != prt_vls) {
    printf (" %3d %10ld мксек\n", ++i,
         (tprt_vls->tv_sec - (tprt_vls - 1)->tv_sec) * 1000000 +
         (tprt_vls->tv_usec - (tprt_vls - 1)->tv_usec));
 }
}

int main (void) {
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;

 /* Установим реакцию на сигналы SIGALRM и SIGVTALRM.           */
 /* Позаботимся, чтобы функция обработки не могла быть прервана */
 /* срабатыванием другого таймера                               */
 if ((sigemptyset (sset) < 0) || (sigaddset (sset, SIGALRM) < 0) ||
      (sigaddset (sset, SIGVTALRM) < 0)) {
    perror ("SIGEMPTYSET or SIGADDSET");
    return (1);
 }

 sact.sa_handler = proc_itexprtn;
 sact.sa_flags = 0;
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, sact, NULL) < 0) {
    perror ("SIGACTION-SIGALRM");
    return (2);
 }
 if (sigaction (SIGVTALRM, sact, NULL) < 0) {
    perror ("SIGACTION-SIGVTALRM");
    return (3);
 }

 /* Зарегистрируем функцию print_rt_data() в atexit() */
 if (atexit (print_rt_data) != 0) {
    perror ("ATEXIT");
    return (4);
 }

 /* Взведем таймер реального времени как одноразовый */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 0;
 itvl.it_value.tv_sec = 10;
 itvl.it_value.tv_usec = 0;
 if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
    perror ("SETITIMER-REAL");
    return (5);
 }

 /* Установим начало отсчета для данных о реальном времени */
 (void) gettimeofday (prt_vls++, NULL);

 /* Таймер виртуального времени сделаем периодическим */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 100000;    /* 0.1 сек */
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 100000;
 if (setitimer (ITIMER_VIRTUAL, itvl, NULL) < 0) {
    perror ("SETITIMER-VIRTUAL");
    return (6);
 }

 /* Убедимся, что пока процесс приостановлен, */
 /* его виртуальное время также стоит         */
 sleep (5);

 /* Нагрузим процессор вычислениями, которые не должны завершиться */
 /* до срабатывания таймера реального времени                      */
 {
    double s = 0;
    double d = 1;
    int i;

    for (i = 1; i <= 100000000; i++) {
      s += d / i;
      d = -d;
    }
 }

 return 0;
}

Результаты работы программы могут выглядеть так, как показано в листинге 12.35. Можно видеть, что, во-первых, доля процессорного времени, которая достается процессу, может существенно колебаться (см., например, отсчеты 4, 6 и 8) и, во-вторых, пока выполнение процесса приостановлено, его виртуальное время также стоит (см. инструкцию sleep (5) в листинге 12.34, отсчет 1 в результатах).

Прошедшее реальное время за один квант 
виртуального
1   5255378 мксек
2   249993  мксек
3   250003  мксек
4   100000  мксек
5   250177  мксек
6   859831  мксек
7   100054  мксек
8   729943  мксек
9   580003  мксек
10  99989   мксек
11  580129  мксек
12  429881  мксек
13  99990   мксек

Интервальные таймеры полезны как средство управления ходом выполнения программы. В частности, они позволяют ограничить время работы отдельных фрагментов, что особенно важно для приложений реального времени. В программе, показанной в листинге 12.36, фигурируют две функции обработки данных; каждой из них отводится не более заданного константой IT_PERIOD числа секунд.

/* * * * * * * * * * * * * * * * * * * * */
/* Программа вызывает функции обработки  */
/* и контролирует время их выполнения    */
/* с помощью интервального таймера       */
/* * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <setjmp.h>

/* Период интервального таймера (в секундах) */
#define IT_PERIOD       1

static jmp_buf buf_env; /* Буфер для функций setjmp и longjmp */

static double s;        /* Результат функций обработки данных */

/* Функция обработки срабатывания таймера реального времени */
/* (сигнал SIGALRM)                                         */
static void proc_sigalrm (int dummy) {
 printf ("Текущий результат текущей функции обработки данных: %g\n", s);
 longjmp (buf_env, 1);
}

/* Первая функция обработки данных */
/* (вычисляет ln (2))              */
static void proc_data_1 (void) {
 double d = 1;
 int i;

 s = 0;
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
}

/* Вторая функция обработки данных */
/* (вычисляет sqrt (2))            */
static void proc_data_2 (void) {
 s = 1;
 do {
    s = (s + 2 / s) * 0.5;
 } while ((s * s - 2) > 0.000000001);
}

int main (void) {
                              /* Массив указателей на функции обработки данных */
 void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL};
                              /* Указатель на указатель              */
                              /* на текущую функцию обработки данных */
 void (**tfptr) (void);
                              /* Вспомогательная временная переменная */
 void (*tmpfptr) (void);
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;
 int i;

 /* Установим реакцию на сигнал SIGALRM */
 if (sigemptyset (sset) < 0) {
    perror ("SIGEMPTYSET");
    return (1);
 }

 sact.sa_handler = proc_sigalrm;
 sact.sa_flags = SA_NODEFER;   /* Не блокировать SIGALRM            */
                                         /* в функции обработки этого сигнала */
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, sact, NULL) < 0) {
    perror ("SIGACTION");
    return (2);
 }

 /* На всякий случай сделаем таймер реального времени периодическим */
 itvl.it_interval.tv_sec = IT_PERIOD;
 itvl.it_interval.tv_usec = 0;

 /* Цикл вызова функций обработки данных. */
 /* Выполним его дважды                   */
 for (i = 0; i < 2; i++) {
    tfptr = fptrs;
    (void) setjmp (buf_env);
    /* Сюда вернется управление после срабатывания таймера */
    while ((tmpfptr = *tfptr++) != NULL) {
      /* Даже если предыдущая функция обработки данных */
      /* закончилась до того, как сработал таймер,     */
      /* обеспечим текущей функции полный интервал     */
      itvl.it_value.tv_sec = IT_PERIOD;
      itvl.it_value.tv_usec = 0;
      if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
         perror ("SETITIMER");
         return (3);
      }
      (*tmpfptr) ();
      printf ("Результат текущей функции обработки данных: %g\n", s);
    }
 }

 return 0;
}

Результаты работы приведенной программы могут выглядеть так, как показано в листинге 12.37. Видно, что первая функция обработки данных не уложилась в отведенное время, поэтому ее выполнение было прервано срабатыванием таймера ; второй функции этого времени оказалось достаточно.

Текущий результат текущей функции 
обработки данных: 0.693147
Результат текущей функции обработки 
данных: 1.41421
Текущий результат текущей функции 
обработки данных: 0.693147
Результат текущей функции обработки 
данных: 1.41421

Комбинация нелокальных переходов и асинхронной обработки сигналов представляет собой взрывоопасную смесь, обращаться с которой следует крайне осторожно. Несмотря на кажущуюся простоту, в пояснениях нуждается целый ряд фрагментов приведенной выше программы.

Во-первых, обратим внимание на использование флага SA_NODEFER, предписывающего не блокировать сигнал при входе в функцию его (сигнала) обработки. Этот флаг необходим, поскольку не происходит нормального выхода из функции proc_sigalrm(), а значит, не происходит и восстановления маски сигналов процесса. В результате, после первого входа в функцию обработки сигнала SIGALRM он рискует остаться заблокированным, вследствие чего процесс не будет извещен о последующих срабатываниях интервального таймера реального времени.

Во-вторых, следует исходить из возможности приостановки выполнения процесса в произвольном месте, в частности, внутри вычисления выражений. Если оформить вызов функции обработки данных в виде инструкции (**tfptr++) (), то нет гарантии, что компилятор не запланирует увеличение указателя tfptr после возврата из функции, выполнение которой может быть прервано срабатыванием таймера. В таком случае управление вернется в начало внутреннего цикла, а указатель не увеличится, что приведет к повторному вызову той же функции обработки данных. Чтобы справиться с этой проблемой, пришлось задействовать вспомогательную временную переменную tmpfptr.

В-третьих, процессу может выделяться сколь угодно малая доля процессорного времени, а потому соотношение между виртуальным и реальным временем оказывается недетерминированным. Если, например, расположить вызов setjmp() после взведения таймера (см. листинг 12.38), то таймер реального времени может сработать до инициализации буфера buf_env, после чего нелокальный переход из функции обработки сигнала приведет к полному хаосу.

if (setitimer (ITIMER_REAL, 
    itvl, NULL) < 0) {
     . . . 
 }

     . . .

 (void) setjmp (buf_env);

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

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

К теме интервальных таймеров логически примыкает функция alarm() (см. листинг 12.39), позволяющая вызывающему процессу заказать доставку сигнала SIGALRM через заданное число секунд реального времени. Если в момент обращения к alarm() предыдущий заказ существовал и еще не был выполнен, он отменяется, а если значение аргумента seconds отлично от нуля, его место занимает новый.

#include <unistd.h>
unsigned alarm (unsigned seconds);

При наличии предыдущего заказа функция alarm() возвращает число секунд, оставшееся до его выполнения; в противном случае результат равен нулю.

Примеры использования функции alarm() были приведены при рассмотрении средств межпроцессного взаимодействия.

Страницы:

Основные понятия и объекты

Согласно стандарту POSIX, за начало отсчета (астрономического) времени принимается ноль часов, ноль минут, ноль секунд первого января 1970-го года универсального координированного времени (далее для краткости именуемого также всемирным ).

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

Московское время опережает всемирное на три часа. Имеется в виду так называемое декретное московское время, которое не следует путать с поясным.

Поясное время Москвы, лежащей во втором часовом поясе, опережает всемирное на два часа.

Московское летнее время опережает всемирное на 4 часа.

За стандартную единицу измерения астрономического времени в POSIX-2001 принята секунда. Пусть некоторый момент времени задан в терминах секунд (значение tm_sec ), минут ( tm_min ), часов ( tm_hour ), а также номером дня в году ( tm_yday ) и номером года минус 1900 ( tm_year ). Тогда число секунд всемирного времени, прошедшее на этот момент от начала отсчета, вычисляется по формуле

tm_sec + tm_min*60 + tm_hour*3600 + 
tm_yday*86400 + (tm_year-70)*31536000 + 
((tm_year-69)/4)*86400 - ((tm_year-1)/100)*
86400 + ((tm_year+299)/400)*86400

(три последних слагаемых предназначены для учета високосных лет).

Учет разного рода поправок к всемирному времени (таких, например, как вставка дополнительной секунды в начале 1996 года ) зависит от реализации.

Для представления данных о моментах времени в программах на языке C обычно используется структура tm, описанная во включаемом файле <time.h> и содержащая, согласно стандарту POSIX-2001, по крайней мере следующие поля.

int tm_sec;   /* Секунды [0,60]             */
int tm_min;   /* Минуты [0,59]              */
int tm_hour;  /* Часы [0,23]                */
int tm_mday;  /* Номер дня в месяце [1,31]  */
int tm_mon;   /* Номер месяца в году [0,11] */
int tm_year;  /* Номер года минус 1900      */
int tm_wday;  /* Номер дня недели [0,6]     */
        /* (воскресенью соответствует ноль) */
int tm_yday;  /* Номер дня в году [0,365]   */
int tm_isdst; 
           /* Признак учета летнего времени */

Диапазон [0, 60] для поля tm_sec позволяет справляться с производимой время от времени вставкой дополнительной секунды.

Значение поля tm_year задается со знаком, что позволяет представлять годы до 1900.

Значение признака tm_isdst должно быть положительным, если действует летнее время, нулевым, если летнее время не действует, или отрицательным, если про летнее время ничего не известно.

Часами называется программный или аппаратный объект, предназначенный для измерения видимого или истинного хода времени.

Показания часов можно опросить и установить (в допустимых для часов пределах).

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

Под скачком часов понимается разность между двумя последовательными, различными с точки зрения приложения (использующего стандартные средства) показаниями часов .

Часы называются монотонными, если их нельзя установить стандартными средствами и они не могут иметь отрицательных скачков.

Такт часов - это зависящие от реализации промежутки времени, на которые дробится каждая секунда .

Реальным (или астрономическим) называется время, измеренное по системным часам безотносительно к тому, какой процесс (поток управления) выполняется.

Под временем выполнения ( процессорным временем ) понимается время, затрачиваемое на выполнение процесса (потока управления), включая работающие от его имени системные сервисы.

Время выполнения конкретного процесса или потока управления измеряется часами процессорного времени.

Под мониторингом времени выполнения понимается оперативное отслеживание процессорного времени, затрачиваемого процессом (потоком управления).

Виртуальное время процесса - время, измеряемое системными часами, пока процесс выполняется.

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

Таймером процессорного времени называется таймер, ассоциированный с часами процессорного времени .

Взвести (зарядить) - значит запустить таймер, измеряющий ход времени и позволяющий уведомить процесс о наступлении заданного момента.

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

Таймеры можно подразделить на одноразовые и периодические.

Одноразовые таймеры снимаются со взвода после первого (и единственного) срабатывания.

Периодические таймеры после очередного срабатывания запускаются вновь в соответствии с однажды заданной спецификацией следующего срабатывания.

Под избыточным срабатыванием таймера понимается ситуация, когда таймер срабатывает до того, как процесс обработал сигнал о предыдущем срабатывании.

Все, что касается часов процессорного времени и таймеров, относится к необязательным частям стандарта POSIX-2001. Тем не менее, мы детально рассмотрим соответствующие средства ввиду их важности и употребительности.

Опрос и установка показаний часов реального времени

Простейшим средством опроса и/или изменения текущих даты и времени является служебная программа date:

date  [-u] [+формат]
date  [-u] ммддччмм[[вв]гг]

В первой форме утилита date выдает на стандартный вывод дату и время (по умолчанию - текущие), в соответствии с заданным форматом. Во второй форме date позволяет установить системные дату и время.

При наличии опции -u работа ведется без учета часового пояса, во всемирном времени (как если бы значением переменной окружения TZ была цепочка "UTC0" ). Без этой опции учитывается значение TZ или, если таковое не установлено или пусто, принимается во внимание системное подразумеваемое значение.

Если в командной строке date задан аргумент, начинающийся со знака +, форматом вывода даты и времени управляет пользователь. Формат включает спецификаторы преобразований и прочие символы (последние выводятся без изменений). Спецификаторы преобразований начинаются со знака % и замещаются при выводе соответствующим значением.

Спецификаторы преобразований
Спецификатор Описание
%a Сокращенное название дня недели.
%A Полное название дня недели.
%b Сокращенное название месяца.
%B Полное название месяца.
%c Принятое в данной языково-культурной среде представление даты и времени.
%C Две первые цифры четырехзначного номера года [00, 99].
%d Номер дня в месяце [01, 31].
%D Дата в формате мм/дд/гг.
%e Номер дня в месяце [1, 31] в двухсимвольном поле с дополнением, при необходимости, пробела.
%h То же, что %b.
%H Номер часа [00, 23].
%I Номер часа [01, 12].
%j Номер дня в году [001, 366].
%m Номер месяца [01, 12].
%M Минуты [00, 59].
%n Перевод строки
%S Секунды [00, 60].
%t Табуляция
%T Время в формате чч:мм:сс.
%u Номер дня недели [1, 7] (единица соответствует понедельнику).
%U Номер недели в году [00, 53]. Первым днем недели считается воскресенье. Все дни нового года, предшествующие первому воскресенью, относятся к нулевой неделе.
%V Номер недели в году [01, 53]. Первым днем недели считается понедельник. Если первое января приходится на пятницу, субботу или воскресенье, конец недели формально относят к прошлому году, а первой считают следующую неделю.
%w Номер дня недели [0, 6] (ноль соответствует воскресенью).
%W Номер недели в году [00, 53]. Первым днем недели считается понедельник. Все дни нового года, предшествующие первому понедельнику, относятся к нулевой неделе.
%x Принятое в данной языково-культурной среде представление даты.
%X Принятое в данной языково-культурной среде представление времени.
%y Две младшие цифры номера года [00, 99].
%Y Четырехзначный номер года.
%Z Имя часового пояса.
%% Знак процента.

Чтобы установить текущие дату и время, необходимо, чтобы система допускала данное действие, а у процесса были соответствующие привилегии.

Во второй форме вызова утилиты date первая пара букв м обозначает номер месяца, дд - номер дня в месяце, чч - часы, мм - минуты, ввгг - год. Если две первые цифры года опущены, то диапазон [69, 99] трактуется как [1969, 1999], а [00, 68] - как [2000, 2068]. Если год не указан, имеется в виду текущий.

Приведем несколько примеров употребления служебной программы date.

Вывод в подразумеваемом формате, используемом командой date без аргументов, может выглядеть так:

Fri Dec 26 17:48:02 MSK 2003

Опция -u повлияет на вывод следующим образом:

Fri Dec 26 14:48:05 UTC 2003

Команда

date '+Дата: %d.%m.%Y%nВремя: %H:%M:%S'

выдаст примерно следующее:

Дата: 26.12.2003
Время: 17:49:15

Для сдвига показаний системных часов на один час вперед можно воспользоваться shell-процедурой, показанной в листинге 12.1 (предполагается, что ее выполняют днем при наличии соответствующих привилегий).

moda=`date +%m%d`
ho=$((`date +%H` + 1))
mi=`date +%M`
date ${moda}${ho}${mi}

На уровне языка C для опроса текущего временисекундах от начала отсчета ) служит функция time() (см. листинг 12.2).

#include <time.h>
time_t time (time_t *tloc);

Если значение аргумента tloc отлично от пустого указателя, то текущее время не только возвращается в качестве результата, но еще и записывается по заданному адресу.

Отметим, что если тип time_t реализован как 32-разрядное целое со знаком, то в 2038-м году наступит переполнение (так называемая проблема 2038-го года ). Стандарт POSIX-2001 не справляется с проблемой в полной мере, но облегчает ее решение за счет использования более или менее абстрактного типа time_t вместо какой-либо конкретной разновидности int. Вероятно, в последующие версии стандарта POSIX будут включены требования к способности значений типа time_t представлять время в далеком будущем (например, вплоть до 10000-го года ) или явное требование не менее чем 64-разрядности.

С функцией time() ассоциирована функция difftime(), вычисляющая (в виде значения типа double ) разность в секундах между двумя моментами времени ( time1 - time0, см. листинг 12.3).

#include <time.h>
double difftime (time_t time1, time_t time0);

Узнать текущее время с большей точностью позволяет функция gettimeofday() (см. листинг 12.4).

#include <sys/time.h>
int gettimeofday (struct timeval *restrict tp, 
                  void *restrict tzp);

Согласно стандарту POSIX-2001, описанная в заголовочном файле <sys/time.h> структура timeval содержит по крайней мере следующие поля.

time_t      tv_sec;     /* Секунды */
suseconds_t tv_usec;    /* Микросекунды */

Функция gettimeofday() записывает текущее время, выраженное в секундах и микросекундах от начала отсчета, в структуру типа timeval, на которую указывает аргумент tp. Указатель tzp должен быть пустым.

Впрочем, микросекунды - это еще на предел точности, а опрос системных часов - не предел общности. В необязательную часть стандарта POSIX-2001, регламентирующую работу с таймерами, входят функции, позволяющие опросить и установить показания заданных часов, а также узнать их разрешающую способность (см. листинг 12.5).

#include <time.h>
int clock_getres (clockid_t clock_id, 
                  struct timespec *res);
int clock_gettime (clockid_t clock_id, 
                   struct timespec *tp);
int clock_settime (clockid_t clock_id, 
                   const struct timespec *tp);

Структура timespec отличается от timeval тем, что вместо микросекунд хранит наносекунды:

time_t tv_sec;  /* Секунды */
long   tv_nsec; /* Наносекунды */

Функция clock_getres() позволяет опросить разрешающую способность часов с заданным идентификатором и поместить результат в структуру, на которую указывает аргумент res.

Функция clock_gettime() записывает в аналогичную структуру текущие показания заданных часов.

Наконец, функция clock_settime() устанавливает заданные часы на основании значений полей структуры типа timespec, на которую указывает аргумент tp.

Часы могут быть общесистемными, т. е. видимыми для всех процессов, или принадлежащими процессу (измеряют время, имеющее смысл только для конкретного процесса). Все реализации должны поддерживать общесистемные часы реального времени, измеряющие время от начала отсчета и имеющие идентификатор CLOCK_REALTIME. Интерпретация показаний других часов (если таковые имеются) стандартом не специфицируется.

Установка новых показаний для часов CLOCK_REALTIME влияет на срабатывание таймеров, взведенных на абсолютное время, но не затрагивает интервальные таймеры.

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

Применение описанных выше функций иллюстрируется программой, показанной в листинге 12.6. Возможно, при редактировании ее внешних связей придется указать дополнительные библиотеки (например, -lrt ), что является следствием использования необязательных компонентов стандарта POSIX.

#include <stdio.h>
#include <time.h>
#include <sys/time.h>

int main (void) {
 struct timespec tmsp;
 struct timeval tmvl;
 time_t st;
 double s = 0;
 double d = 1;
 int i;

 if (clock_getres (CLOCK_REALTIME, tmsp) == -1) {
    perror ("CLOCK_GETRES");
    return (1);
 }
 printf ("Разрешающая способность общесистемных часов: %ld нсек.\n",
            tmsp.tv_nsec);

 if (clock_gettime (CLOCK_REALTIME, tmsp) == -1) {
    perror ("CLOCK_GETTIME");
    return (2);
 }
 printf ("Текущее время по общесистемным часам: %ld сек. %ld нсек.\n",
            tmsp.tv_sec, tmsp.tv_nsec);

(void) gettimeofday (tmvl, NULL);
 printf ("Текущее время, функция gettimeofday(): %ld сек. %ld мксек.\n",
            tmvl.tv_sec, tmvl.tv_usec);

 (void) time (st);
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
 printf ("Время выполнения цикла: %g сек.\n", difftime (time (NULL), st));

 return (0);
}

Результаты работы этой программы могут выглядеть так, как показано в листинге 12.7.

Разрешающая способность общесистемных часов: 
10000000 нсек.
Текущее время по общесистемным часам: 
1072534678 сек. 296598000 нсек.
Текущее время, функция gettimeofday(): 
1072534678 сек. 296637 мксек.
Время выполнения цикла: 15 сек.

Преобразование данных о времени

Стандартом POSIX-2001 предусмотрено несколько способов представления данных о времени. Выше были описаны структура tm и тип time_t. Кроме того, время может записываться в виде цепочки символов. (Есть еще структуры timeval и timespec, но они в данном контексте играют подчиненную роль, лишь уточняя значения типа time_t.)

Для выполнения преобразований между разными представлениями данных о времени служат описываемые далее функции (см. также рис. 12.1).

(рис 12.1) Функции для выполнения преобразований между разными представлениями данных о времени.

Функции gmtime() и localtime() (см. листинг 12.8) преобразуют значения типа time_t в структуру типа tm. Соотношение между временем в секундах от начала отсчета и значениями полей структуры типа tm дается в приведенной выше формуле. Кроме того, функция localtime() учитывает данные о часовом поясе и сезонных поправках.

#include <time.h>
struct tm *gmtime (const time_t *tloc);
struct tm *localtime (const time_t *tloc)

Для учета данных о часовом поясе и сезонных поправках используются внешние переменные tzname, timezone, daylight, значения которых функция tzset() устанавливает по переменной окружения TZ (см. листинг 12.9).

#include <time.h>
extern char *tzname[2];
extern long timezone;
extern int daylight;
void tzset (void);

Элементам массива tzname присваиваются имена местного часового пояса в стандартном ( tzname [0] ) и "летнем" ( tzname [1] ) вариантах. Значение переменной timezone устанавливается равным разности в секундах между всемирным и местным поясным временем. Переменной daylight присваивается отличное от нуля значение, если для местного часового пояса предусмотрен переход на летнее время.

Отметим, что в общем случае значение переменой окружения TZ устроено довольно сложным образом:

Станд_поясСмещение[Лет_пояс[Смещение]
      [,Нач_лет[/Время],Кон_лет[/Время]]]

Здесь Станд_пояс и Лет_пояс - имена, присваиваемые элементам массива tzname [], Смещение - разность между всемирным и местным поясным временем (в виде чч[:мм[:сс]]), Нач_лет и Кон_лет, соответственно, даты начала и окончания действия летнего времени (обычно их задают в виде Mмм.н.д - месяц, неделя, день ), время - время перехода (по умолчанию - два часа ночи). Можно видеть, что данных для вычисления местного времени оказывается вполне достаточно.

Функцию mktime() (см. листинг 12.10) можно считать обратной по отношению к localtime(). Она преобразует местное время, заданное в виде структуры типа tm, в значение типа time_t, т. е. в секунды от начала отсчета по всемирному времени.

#include <time.h>
time_t mktime (struct tm *tmptr);

При входе в функцию mktime() значения полей tm_wday и tm_yday структуры tm, на которую указывает аргумент tmptr, игнорируются; при выходе они устанавливаются должным образом. Значения других полей также приводятся к стандартным для них диапазонам (при входе это условие может не выполняться).

Другим весьма мощным средством преобразования местного времени из структурного в текстовое представление является функция strftime() (см. листинг 12.11). Как и служебная программа date, она преобразует дату и время в соответствии с заданным форматом, только исходными данными служит не текущий момент времени, а структура типа tm, на которую указывает аргумент tmptr, и результат направляется не на стандартный вывод, а в буфер, заданный указателем s и длиной maxsize.

#include <time.h>
size_t strftime (char *restrict s, 
   size_t maxsize, const char *restrict format,
   const struct tm *restrict tmptr);

По сравнению с date, функция strftime() содержит несколько дополнительных спецификаторов форматных преобразований. Перечислим наиболее употребительные из них.

Спецификаторы преобразований
Спецификатор Описание
%F Представление даты в соответствии со стандартом ISO 8601:2000: эквивалент последовательности спецификаторов %Y-%m-%d.
%R Часы и минуты (%H:%M) в 24-часовом представлении.
%z Смещение относительно всемирного времени, представленное по стандарту ISO 8601:2000: +ччмм или -ччмм (положительные значения соответствуют часовым поясам к востоку от Гринвича).

Функция strftime() возвращает число байт, помещенное в буфер (без учета завершающего нулевого байта). Если буфер оказался мал, возвращается ноль.

На роль обратной по отношению к strftime() могут претендовать сразу две функции: strptime() и getdate() (см. листинг 12.12).

#include <time.h>
char *strptime (const char *restrict s, 
           const char *restrict format, 
           struct tm *restrict tmptr);
struct tm *getdate (const char *s);

Функция strptime() напоминает sscanf(): она сканирует цепочку символов, на которую указывает аргумент s, в соответствии с заданным форматом, включающим описанные выше спецификаторы преобразований, а также, быть может, пробельные и обычные символы, и помещает извлеченные значения в структуру типа tm по указателю tmptr. В качестве результата возвращается указатель на первый несканированный символ или NULL в случае неудачи.

Функция getdate(), по сути аналогичная strptime(), использует для разбора входной цепочки s форматы, содержащиеся в файле, чье полное маршрутное имя задано переменной окружения DATEMSK (для интерпретации выбирается первый подходящий формат). Если дата и время специфицированы не полностью (например, задан только день недели), как исходные берутся данные о первом подходящем моменте времени, начиная с текущего. Если в формате присутствует спецификатор %Z, выходная структура инициализируется текущим временем в сканируемом часовом поясе. В противном случае применяется местное время.

С помощью внешней переменной (или макроса) getdate_err функция getdate() возвращает коды ошибок.

Приведем пример использования описанных функций преобразования данных о времени (см. листинг 12.13).

#include <stdio.h>
#include <time.h>
#include <limits.h>

int main (void) {
 char dtbuf [LINE_MAX];        /* Буфер для данных о времени */
 time_t st;
 struct tm stm;

 (void) time (st);

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (st));
 printf ("Текущее всемирное время: %s\n", dtbuf);

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (st));
 printf ("Текущее местное время: %s\n", dtbuf);

 /* Узнаем, каким днем недели будет 01 января 2038 года */
 stm.tm_year = 2038 - 1900;
 stm.tm_mon = 1 - 1;
 stm.tm_mday = 1;
 stm.tm_hour = 0;
 stm.tm_min = 0;
 stm.tm_sec = 0;
 stm.tm_isdst = -1;
 if ((st = mktime (stm)) == (time_t) (-1)) {
    perror ("MKTIME");
 } else {
    (void) strftime (dtbuf, sizeof (dtbuf), "%A", stm);
    printf ("День недели 01 января 2038 года: %s\n", dtbuf);
    printf ("Число секунд от начала отсчета в начале 2038 года (шест.): %x\n",
              (unsigned int) st);
 }

 /* Узнаем, когда наступит переполнение значений типа time_t, */
 /* представленных как 32-разрядное целое со знаком           */
 st = (time_t) 0x7fffffff;
 (void) strftime (dtbuf, sizeof (dtbuf), "%c", gmtime (st));
 printf ("Всемирное время конца 32-разрядного отсчета: %s\n", dtbuf);

 /* Преобразуем эту дату в формат ISO 8601:2000 */
 if (strptime (dtbuf, "%c", stm) == NULL) {
    perror ("STRPTIME");
 } else {
    (void) strftime (dtbuf, sizeof (dtbuf), "%F", stm);
    printf ("Дата конца 32-разрядного отсчета в формате ISO 8601:2000: %s\n",
              dtbuf);
 }

 return (0);
}

Возможные результаты выполнения этой программы показаны в листинге 12.14.

Текущее всемирное время: Sat Jan  3 13:54:02 2004
Текущее местное время: Sat Jan  3 16:54:02 2004
День недели 01 января 2038 года: Friday
Число секунд от начала отсчета в начале 2038 года (шест.): 7fe7ed50
Всемирное время конца 32-разрядного отсчета: Tue Jan 19 03:14:07 2038
Дата конца 32-разрядного отсчета в формате ISO 8601:2000: 2038-01-19

Последовательность инструкций языка C

char dtbuf [];
time_t st;
(void) strftime (dtbuf, sizeof (dtbuf), "%c",
                        localtime (st));

является стандартной для получения текущего времени в текстовом виде. (Разумеется, вместо " %c " допустим другой спецификатор преобразования.) Нужно помнить только, что функции gmtime() и localtime() возвращают указатели на статические структуры, содержимое которых, возможно, перезаписывается при каждом вызове, поэтому, если оно понадобится в дальнейшем, его следует скопировать в собственные объекты.

Опрос показаний часов процессорного времени

Базовым средством для работы с часами процессорного времени является функция clock() (см. листинг 12.15). Она возвращает в качестве результата процессорное время, затраченное процессом с некоего момента, зависящего от реализации и связанного только с его (процесса) запуском. В случае ошибки результат равен ( clock_t ) (-1).

#include <time.h>
clock_t clock (void);

Чтобы перевести время, возвращаемое функцией clock(), в секунды, его следует поделить на константу CLOCKS_PER_SEC, которая определена в заголовочном файле <time.h> равной одному миллиону, хотя разрешающая способность часов процессорного времени в конкретной реализации может и отличаться от микросекунды.

Отметим, что если значения типа clock_t представлены как 32-разрядные целые со знаком, то менее чем через 36 минут ( процессорного времени ) наступит переполнение.

В листинге 12.16 показан пример использования функции clock(). Поскольку начало отсчета для часов процессорного времени не специфицировано, их показания опрашиваются в начале и конце измеряемого промежутка, а результатом измерения затраченного процессорного времени служит разность этих показаний.

#include <stdio.h>
#include <time.h>

int main (void) {
time_t st;
clock_t ct;
double s = 0;
double d = 1;
int i;

fprintf (stderr, "Начало выполнения цикла\n");
(void) time (st);
ct = clock;
for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
}
fprintf (stderr, "Процессорное время выполнения цикла: %g сек.\n",
            (double) (clock - ct) / CLOCKS_PER_SEC);
fprintf (stderr, "Астрономическое время выполнения цикла: %g сек.\n",
            difftime (time (NULL), st));

return (0);
}

Возможные результаты работы этой программы показаны в листинге 12.17. Процессорное время получилось больше астрономического из-за ошибок округления.

Начало выполнения цикла
Процессорное время выполнения цикла: 15.19 сек.
Астрономическое время выполнения цикла: 15 сек.

Для измерения времени выполнения простой команды можно воспользоваться служебной программой time (не входящей, правда, в обязательную часть стандарта POSIX-2001 ):

time [-p] команда [аргумент ...]

Она выдает в стандартный протокол астрономическое и процессорное время, прошедшее от запуска до завершения команды с указанными аргументами. Если задана опция -p, время выдается в секундах в виде вещественных чисел. Процессорное время подразделяется на пользовательское (затраченное самой командой) и системное (ушедшее на оказание системных услуг команде). Если предположить, что выполнимый файл приведенной выше программы называется my_tcex, то командная строка

time -p my_tcex

выдаст в стандартный протокол примерно следующее (см. листинг 12.18):

Начало выполнения цикла
Процессорное время выполнения цикла: 15.2 сек.
Астрономическое время выполнения цикла: 15 сек.
real 15.20
user 15.20
sys 0.00

Если нужно измерить время выполнения конвейера или списка команд, их можно поместить в файл, превратив тем самым в простую команду, или воспользоваться конструкцией вида

time sh -c 'составная команда'

Оружием более крупного калибра является специальная встроенная в shell команда

times

Она выдает на стандартный вывод процессорное время, затраченное командным интерпретатором и порожденными им процессами. Например, после выполнения команд

my_tcex  my_tcex
times
на стандартный вывод может быть выдано:
0m0.010s 0m0.000s
0m30.410s 0m0.000s

Можно видеть, что сам командный интерпретатор практически не занимал процессор, а два порожденных им процесса в сумме потребили чуть меньше тридцати с половиной секунд ; время системных услуг в обоих случаях оказалось пренебрежимо малым.

Реализация описанных выше утилит time и times опирается на функцию times() (см. листинг 12.19). Она опрашивает данные о времени выполнения вызывающего процесса и порожденных процессов, завершение которых ожидалось с помощью функций wait() или waitpid(). В отличие от clock(), функция times() измеряет время в тактах часов, и переполнение 32-разрядного представления значений типа clock_t происходит не через полчаса, а примерно в течение года (если секунда делится на 60 - 100 тактов). Соответственно, для перевода результатов работы times() в секунды их нужно делить на sysconf ( _SC_CLK_TCK ), а не на CLOCKS_PER_SEC.

#include <sys/times.h>
clock_t times (struct tms *buffer);

В качестве результата функция times() возвращает астрономическое время, прошедшее от некоторого момента в прошлом (например, от загрузки системы), но основные, содержательные данные, относящиеся к вызывающему процессу и его потомкам, помещаются в структуру типа tms, которая описана в заголовочном файле <sys/times.h> и, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля:

clock_t tms_utime;  
/* Процессорное время, затраченное */
/* вызывающим процессом            */

clock_t tms_stime;  
/* Процессорное время, затраченное системой */
/* на обслуживание вызывающего процесса     */

clock_t tms_cutime; 
/* Процессорное время, затраченное 
/*завершившимися порожденными процессами    */

clock_t tms_cstime; 
/* Процессорное время, затраченное системой */
/* на обслуживание завершившихся            */
/* порожденных процессов                    */

Значения времени завершившихся порожденных процессов ( tms_utime и tms_cutime, а также tms_stime и tms_cstime ) добавляются, соответственно, к элементам tms_cutime и tms_cstime структуры родительского процесса при возврате из функций wait() или waitpid(). Это происходит рекурсивно: если порожденный процесс ожидал завершения своих потомков, то в упомянутых элементах структуры накопятся данные о времени выполнения поддерева процессов.

В качестве примера использования функции times() измерим время работы рассматривавшейся ранее программы, копирующей строки со стандартного ввода на стандартный вывод через сокеты адресного семейства AF_UNIX с помощью порожденного процесса (см. листинг 12.20).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через пару сокетов.                                   */
/* Используются функции ввода/вывода нижнего уровня.                     */
/* Измеряется астрономическое и процессорное время                       */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/times.h>

#define MY_PROMPT       "Вводите строки\n"
#define MY_MSG          "Вы ввели: "

int main (void) {
 int sds [2];
 char buf [1];
 int new_line = 1;     /* Признак того, что надо выдать сообщение MY_MSG   */
                              /* перед отображением очередной строки              */
 clock_t st_ct;        /* Данные об астрономическом и процессорном времени */
 struct tms st_cpt;    /* в начале работы программы                        */
 clock_t en_ct;        /* Данные об астрономическом и процессорном времени */
 struct tms en_cpt;    /* в конце работы программы                         */
 long tck_p_sec;       /* Количество тактов часов в секунде                */

 /* Опросим данные о времени начала выполнения */
 if ((st_ct = times (st_cpt)) == (clock_t) (-1)) {
    perror ("TIMES-1");
    return (1);
 }

 /* Создадим пару соединенных безымянных сокетов */
 if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) {
    perror ("SOCKETPAIR");
    return (2);
 }

 switch (fork ()) {
    case -1:
      perror ("FORK");
      return (3);
    case 0:
      /* Чтение из сокета sds [0] и выдачу на стандартный вывод */
      /* реализуем в порожденном процессе                       */
      while (read (sds [0], buf, 1) == 1) {
    if (write (STDOUT_FILENO, buf, 1) != 1) {
            perror ("WRITE TO STDOUT");
      break;
    }
      }
      exit (0);
 }

 /* Чтение со стандартного ввода и запись в сокет sds [1] */
 /* возложим на родительский процесс                      */
 if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
                                          sizeof (MY_PROMPT) - 1) {
    perror ("WRITE TO SOCKET-1");
 }

 while (read (STDIN_FILENO, buf, 1) == 1) {
    /* Перед отображением очередной строки */
    /* нужно выдать сообщение MY_MSG       */
    if (new_line) {
      if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
         perror ("WRITE TO SOCKET-2");
         break;
      }
    }
    if (write (sds [1], buf, 1) != 1) {
      perror ("WRITE TO SOCKET-3");
      break;
    }
    new_line = (buf [0] == '\n');
 }
 shutdown (sds [1], SHUT_WR);

 (void) wait (NULL);

 /* Опросим данные о времени конца выполнения, */
 /* вычислим и выдадим результаты измерений    */
 if ((en_ct = times (en_cpt)) == (clock_t) (-1)) {
    perror ("TIMES-2");
    return (4);
 }

 tck_p_sec = sysconf (_SC_CLK_TCK);
 fprintf (stderr, "Тактов в секунде: %ld\n", tck_p_sec);
 fprintf (stderr, "Астрономическое время работы программы: %g сек.\n",
             (double) (en_ct - st_ct) / tck_p_sec);
 fprintf (stderr, "Процессорное время, затраченное процессом: %g сек.\n",
             (double) (en_cpt.tms_utime - st_cpt.tms_utime) / tck_p_sec);
 fprintf (stderr, "Процессорное время, затраченное системой: %g сек.\n",
             (double) (en_cpt.tms_stime - st_cpt.tms_stime) / tck_p_sec);
 fprintf (stderr, "Аналогичные данные для порожденных процессов:\n");
 fprintf (stderr, "%g сек.\n%g сек.\n",
             (double) (en_cpt.tms_cutime - st_cpt.tms_cutime) / tck_p_sec,
             (double) (en_cpt.tms_cstime - st_cpt.tms_cstime) / tck_p_sec);

 return (0);
}

Если перенаправить стандартный ввод в какой-либо текстовый файл заметных размеров, а стандартный вывод - в другой файл, можно получить результаты, подобные тем, что показаны в листинге 12.21.

Тактов в секунде: 100
Астрономическое время работы программы: 1.19 сек.
Процессорное время, затраченное процессом: 0.02 сек.
Процессорное время, затраченное системой: 0.08 сек. 
Аналогичные данные для порожденных процессов:
0.09 сек.
1 сек.

Прямое манипулирование часами процессорного времени возможно после обращения к функции clock_getcpuclockid() (см. листинг 12.22), позволяющей выяснить их идентификатор. Оговоримся, однако, что эта функция не входит в обязательную часть стандарта POSIX-2001 (она относится к продвинутым средствам реального времени).

#include <time.h>
int clock_getcpuclockid 
      (pid_t pid, clockid_t *clock_id);

Идентификатор часов процессорного времени, обслуживающих заданный аргументом pid процесс, возвращается по адресу, специфицированному аргументом clock_id. Если значение pid равно нулю, имеется в виду вызывающий процесс. Признаком успешного завершения является нулевой результат; в противном случае возвращается код ошибки.

В листинге 12.23 показан пример употребления функции clock_getcpuclockid() и манипулирования часами процессорного времени. Возможные результаты выполнения этой программы - в листинге 12.24.

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <errno.h>

int main (void) {
 clockid_t clk_id;     /* Идентификатор часов процессорного времени */
 struct timespec tmsp;
 double s = 0;
 double d = 1;
 int i;

 if ((errno = clock_getcpuclockid ((pid_t) 0, clk_id)) != 0) {
    perror ("CLOCK_GETCPUCLOCKID");
    return (1);
 }

 if (clock_getres (clk_id, tmsp) == -1) {
    perror ("CLOCK_GETRES");
    return (2);
 }
 printf ("Разрешающая способность часов процессорного времени: %ld нсек.\n",
      tmsp.tv_nsec);

 /* Измерим процессорное время выполнения цикла. */
 fprintf (stderr, "Начало выполнения цикла\n");
 tmsp.tv_sec = tmsp.tv_nsec = 0;
 if (clock_settime (clk_id, tmsp) == -1) {
    perror ("CLOCK_SETTIME");
    return (3);
 }
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
 if (clock_gettime (clk_id, tmsp) == -1) {
    perror ("CLOCK_GETTIME");
    return (4);
 }
 fprintf (stderr, "Процессорное время выполнения цикла: %ld сек. %ld нсек.\n",
      tmsp.tv_sec, tmsp.tv_nsec);

 return (0);
}
Разрешающая способность часов процессорного времени: 2 нсек.
Начало выполнения цикла
Процессорное время выполнения цикла: 15 сек. 350313054 нсек.

Обратим внимание на два момента. Перед началом измеряемого участка программы на часах процессорного времени были установлены нулевые показания, что, разумеется, не влияет на приоритет процесса и его планирование. Конечно, можно было обойтись без переустановки, запомнив показания часов в начале и конце измеряемого промежутка и произведя затем их вычитание, но технически это чуть сложнее; кроме того, нам хотелось применить функцию clock_settime(). Второй заслуживающий отдельного упоминания момент состоит в отсутствии необходимости выяснять идентификатор часов процессорного времени вызывающего процесса с помощью функции clock_getcpuclockid(): можно воспользоваться именованной константой CLOCK_PROCESS_CPUTIME_ID.

Опрос и изменение данных о времени, ассоциированных с файлами

Информацию о файле, в том числе данные о времени последнего доступа и изменения, можно получить с помощью описанных ранее функций семейства stat().

Для изменения ассоциированных с файлами данных о времени служит функция utime() (см. листинг 12.25), оперирующая в терминах секунд от начала отсчета.

#include <utime.h>
int utime (const char *path, 
           const struct utimbuf *times);

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

Если значение times отлично от NULL, оно трактуется как указатель на структуру типа utimbuf, которая, согласно стандарту POSIX-2001, содержит по крайней мере следующие поля:

time_t actime;  
/* Время последнего доступа   */

time_t modtime; 
/* Время последнего изменения */

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

В случае успешного завершения функция utime() возвращает нулевой результат и, кроме того, переустанавливает время последнего изменения статуса файла.

Приведем пример программы, изменяющей время последнего доступа к файлу (см. листинг 12.26).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа устанавливает время последнего доступа к файлу -  */
/* аргументу командной строки, равное текущему времени          */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <utime.h>

int main (int argc, char *argv []) {
 struct stat st_buf;    /* Буфер для опроса данных о файле                  */
 struct utimbuf ut_buf; /* Буфер для формирования изменяемых данных о файле */

 if (argc != 2) {
    fprintf (stderr, "Использование: %s файл\n", argv [0]);
    return (1);
 }

 if (stat (argv [1], st_buf) != 0) {
    perror ("STAT");
    return (2);
 }

 ut_buf.actime = time (NULL);
 ut_buf.modtime = st_buf.st_mtime;

 if (utime (argv [1], ut_buf) != 0) {
    perror ("UTIME");
    return (3);
 }

 return (0);
}

Чтобы изменить только одно из двух ассоциированных с файлом времен, необходимо с помощью функции stat() получить информацию о файле, перенести неизменяемое значение из структуры типа stat в utimbuf, установить требуемым образом второй элемент структуры типа utimbuf и только после этого обратиться к функции utime().

Читателю предлагается убедиться, что приведенная программа работает должным образом и что опция -u служебной программы ls предписывает оперировать со временем последнего доступа вместо времени последнего изменения.

Приостановка выполнения на заданное время

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

Функция sleep() (см. листинг 12.27) позволяет приостановить выполнение процесса (потока управления) на заданное число секунд. Если в это время будет доставлен сигнал, который не игнорируется, вызов sleep() завершится досрочно, а результат будет равен "недоспанному" числу секунд. (В случае нормального завершения результат равен нулю, что формально можно считать частным случаем предыдущего.)

#include <unistd.h>
unsigned sleep (unsigned seconds);

Обратим внимание на то, что аргумент и результат функции sleep() описаны как unsigned. Это значит, что приложения, строго соответствующие стандарту POSIX-2001, не должны передавать sleep() величины, превышающие минимально допустимое значение конфигурационной константы UINT_MAX, которое в стандарте ISO C [5] полагается равным 65535. Использование больших величин, вообще говоря, ограничивает мобильность приложений.

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

#include <time.h>
int nanosleep (const struct timespec *rqtp, 
               struct timespec *rmtp);

Аргумент rqtp функции nanosleep() задает запрашиваемую длительность приостановки. По указателю rmtp, если он не пуст, возвращается "недоспанное" время. В отличие от sleep(), результат nanosleep() равен -1 - как в результате ошибки, так и при "недосыпании", вызванном доставкой сигнала.

В общем случае ожидание наступления какого-либо события может быть активным или пассивным. Активное ожидание предполагает постоянный, повторяющийся в цикле опрос ассоциированных с событием значений и, следовательно, вызывает расход процессорного времени. При пассивном ожидании процесс приостанавливается (например, на семафоре) до наступления события, не занимая процессор. Функции sleep() и nanosleep() позволяют выбрать компромиссный способ ожидания, когда процессорного времени расходуется немного и оперативность реагирования на события оказывается удовлетворительной.

В листинге 12.29 показан пример использования функции sleep() для "полупассивного" ожидания изменения файла. Разумеется, обработка файла может быть более содержательной, однако представленная схема действий носит весьма общий характер и используется, например, служебными программами cron, tail и др.

/* * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа отслеживает изменение размера файла,  */
/* заданного как аргумент командной строки         */
/* * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
#include <limits.h>

static time_t lt_mod = 0;       /* Время последнего изменения файла */
static struct stat st_buf;      /* Данные о файле                   */

/* Функция начальной обработки файла */
static void proc_init (const char *path) {
 fprintf (stderr, "Данные о размере файла %s\n", path);
 fprintf (stderr, "Время изменения            Размер\n");
}

/* Функция проверки, нужно ли обрабатывать файл */
/* Результат > 0 - нужно                        */
/*             0 - не нужно                     */
/* lt; 0 - ошибка                       */
static int check_mod (const char *path) {
 if (stat (path, st_buf) != 0) {
    perror ("STAT");
    return (-1);
 }

 if (st_buf.st_mtime != lt_mod) {
    /* Файл изменился и, следовательно, нуждается в обработке */
    lt_mod = st_buf.st_mtime;
    return (1);
 }

 return 0;
}

/* Функция обработки файла.                            */
/* Выведем время последнего изменения и текущий размер */
static void proc_mod (void) {
 char dtbuf [LINE_MAX];        /* Буфер для данных о времени */

 (void) strftime (dtbuf, sizeof (dtbuf), "%c", localtime (lt_mod));
 fprintf (stderr, "%s   %ld\n", dtbuf, st_buf.st_size);
}

int main (int argc, char *argv []) {
 int res;

 if (argc != 2) {
    fprintf (stderr, "Использование: %s файл\n", argv [0]);
    return (1);
 }

 proc_init (argv [1]);
 while (1) {
    if ((res = check_mod (argv [1])) > 0) {
      proc_mod ();
    } else if (res lt; 0) {
      return (res);
    }
    sleep (1);
 }

 return 0;
}

Возможные результаты работы этой программы показаны в листинге 12.30.

Данные о размере файла /var/log/cron
Время изменения            Размер
Tue Jan  6 12:50:00 2004   11191
Tue Jan  6 13:01:00 2004   11263
Tue Jan  6 13:10:00 2004   11409
Tue Jan  6 13:20:00 2004   11481
  . . .
Tue Jan  6 13:40:00 2004   11624
  . . .

Функция nanosleep() позволяет до некоторой степени промоделировать работу функций poll() и select(), реализующих пассивное ожидание готовности данных, в тех ситуациях, когда данные поступают из разнородных источников, которые poll() и select() не обслуживают (не только из файлов, но и из очередей сообщений, как результат опроса значений семафоров и т.п.). В листинге 12.31 показан цикл "полуактивного" (с короткими приостановками) ожидания поступления данных из канала и обнуления значения семафора.

#define N               ...
#define MY_BUFSIZ       ...

struct timespec tmsp = {0, 20000000};
int fds [2];                    /* Файловые дескрипторы канала               */
int semid;                      /* Идентификатор набора семафоров            */
struct sembuf sembuf [1];       /* Массив для задания операций над семафором */
char buf [MY_BUFSIZ];           /* Буфер для чтения данных из канала         */
int brd;                        /* Число прочитанных байт                    */
int i;

  . . .

fcntl (fds [0], F_SETFL, O_NONBLOCK);
sembuf [0].sem_num = 0;
sembuf [0].sem_flg = IPC_NOWAIT;
sembuf [0].sem_op =  0;

for (i = 0; i lt; N; i++) {
 nanosleep (tmsp, NULL);

 if ((brd = read (fds [0], buf, MY_BUFSIZ) > 0) {
    /* Обработка прочитанных данных.                       */
    /* Возможно, поступление данных на этом не закончилось */
        . . .
    continue;
 }

 if (semop (semid, sembuf, 1) == 0) {
    /* Значение семафора обнулено, ждать больше нечего */
        . . .
    if (brd <= 0) {
      close (fds [0]);
      break;
    }
 }
}

  . . .

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

Работа с интервальными таймерами

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

Описываемые далее средства для работы с интервальными таймерами входят в необязательную часть стандарта POSIX-2001, именуемую "X/Open-расширение системного интерфейса" (XSI).

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

ITIMER_REAL

Таймер реального времени. Он ассоциирован с часами CLOCK_REALTIME и, следовательно, его показания уменьшаются в реальном масштабе времени. Когда он срабатывает, процессу доставляется сигнал SIGALRM.

ITIMER_VIRTUAL

Таймер виртуального времени процесса. Его показания уменьшаются в соответствии с течением виртуального времени процесса, т. е. только тогда, когда процесс выполняется. При срабатывании таймера процессу доставляется сигнал SIGVTALRM.

ITIMER_PROF

Таймер профилирования. Показания этого таймера уменьшаются с течением виртуального времени процесса, а также тогда, когда работают запрошенные им системные сервисы. Он предназначен для построения статистическими методами профилей расходования процессом процессорного времени. При его срабатывании процессу доставляется сигнал SIGPROF.

Согласно стандарту POSIX-2001, интервальные таймеры обслуживаются функциями getitimer() и setitimer() (см. листинг 12.32).

#include <sys/time.h>
int getitimer (int timer_id, 
    struct itimerval *cvalue);
int setitimer (int timer_id, 
    const struct itimerval *restrict nvalue, 
    struct itimerval *restrict ovalue);

Поддерживаемые стандартом значения аргумента timer_id перечислены выше. Другие характеристики интервальных таймеров задаются в структуре типа itimerval, которая должна содержать по крайней мере следующие поля:

struct timeval it_interval;   
/* Интервал таймера */

struct timeval it_value;      
/* Текущие показания таймера */
/* (ведется обратный отсчет) */

(Напомним, что структура типа timeval была описана выше. Она содержит по крайней мере два поля, задающие время, соответственно, в секундах и микросекундах.)

Значение поля it_value (если оно отлично от нуля) показывает время, оставшееся до срабатывания таймера. После срабатывания таймер запускается вновь с начальным значением поля it_value, равным it_interval (если последнее отлично от нуля).

Установка нулевого значения в поле it_value, независимо от величины it_interval, снимает таймер со взвода. Если сделать нулевым значение it_interval, таймер будет разряжен после очередного срабатывания. Таким способом можно реализовать таймер с ограниченной периодичностью (в частности, одноразовый).

Функция getitimer() запоминает текущие характеристики таймера с заданным идентификатором в структуре, на которую указывает аргумент cvalue. Функция setitimer() - ее можно назвать многоцелевой - взводит или снимает таймер со взвода, устанавливает новые характеристики, пользуясь значением аргумента nvalue, и сохраняет старые по указателю ovalue (если он отличен от NULL ).

Признаком успешного завершения обеих функций является нулевой результат.

Здесь уместно акцентировать внимание на одной тонкости, которую до сих пор мы старательно замалчивали. Поскольку разрешающая способность любых часов конечна, для показаний часов и таймеров можно установить только те значения, которые кратны этой разрешающей способности. Если соответствующие аргументы функций clock_settime(), nanosleep() или setitimer() не удовлетворяют данному условию, производится округление. Для часов используется наилучшее приближение снизу, для длительности приостановки выполнения и таймеров - наилучшее приближение сверху ( стандарт POSIX-2001 требует, чтобы длительности приостановки выполнения и промежутка времени до срабатывания таймера были не меньше заказанных).

Последнее обстоятельство позволяет выяснить размер такта часов реального времени довольно неожиданным образом (впрочем, вполне стандартным и мобильным с точки зрения спецификаций POSIX-2001 ) (см. листинг 12.33).

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа выясняет размер такта часов реального времени */ 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/time.h>

int main (void) {
 struct itimerval itvl;

 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 1;
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 0;

 if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
    perror ("SETITIMER");
    return (1);
 }

 if (getitimer (ITIMER_REAL, itvl) < 0) {
    perror ("GETITIMER");
    return (2);
 }

 if (itvl.it_interval.tv_usec < 2) {
    printf ("Не удалось выяснить размер такта часов реального времени\n");
 } else {
    printf ("Размер такта часов реального времени: %ld мксек\n",
              itvl.it_interval.tv_usec);
 }

 return 0;
}

Здесь интервальный таймер не взводится (поскольку значение it_value - нулевое) и, соответственно, не срабатывает. Он нужен лишь для того, чтобы операционная система подкорректировала должным образом (с учетом разрешающей способности часов реального времени ) значение поля it_interval.tv_usec. В результате корректировки первоначально присвоенная этому полю единица может превратиться, например, в 10000 (микросекунд), что дает тактовую частоту часов реального времени 100 Гц.

Следующая программа (см. листинг 12.34) сравнивает ход реального и виртуального времени процесса, определяя, сколько реального времени прошло за один квант виртуального (в данном случае квант - это 100000 мксек, т. е. 0.1 сек). Колебания показаний, естественно, вызываются нагрузкой на процессор, индуцируемой другими, параллельно работающими процессами. В принципе, целенаправленная организация подобных колебаний и их анализ могут быть использованы для создания так называемых скрытых каналов по времени (см. [7]).

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа сравнивает ход реального и виртуального */
/* времени процесса                                  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

#define TM_BUF_SIZE     128

                /* Массив для хранения показаний часов     */
                /* реального времени в момент срабатывания */
                /* таймера виртуального времени процесса   */
static struct timeval rt_vls [TM_BUF_SIZE];
                /* Указатель на текущий элемент */
                /* массива rt_vls               */
static struct timeval *prt_vls = rt_vls;

/* Функция обработки срабатывания таймеров */
/* (сигналы SIGALRM и SIGVTALRM)           */
static void proc_itexprtn (int signo) {
 struct itimerval itmvl = {{0, 0}, {0, 0}};

 /* Запомним текущее астрономическое время */
 (void) gettimeofday (prt_vls++, NULL);
 if (signo == SIGALRM) {
    /* Сработал таймер реального времени.   */
    /* Разрядим таймер виртуального времени */
    /* и завершимся (с выдачей результатов) */
    (void) setitimer (ITIMER_VIRTUAL, itmvl, NULL);
    exit (0);
 }
}

/* Функция выдачи данных о ходе реального времени             */
/* на фоне равномерного течения виртуального времени процесса */
static void print_rt_data (void) {
 struct timeval *tprt_vls = rt_vls;
 int i = 0;

 printf ("Прошедшее реальное время за один квант виртуального\n");
 while (++tprt_vls != prt_vls) {
    printf (" %3d %10ld мксек\n", ++i,
         (tprt_vls->tv_sec - (tprt_vls - 1)->tv_sec) * 1000000 +
         (tprt_vls->tv_usec - (tprt_vls - 1)->tv_usec));
 }
}

int main (void) {
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;

 /* Установим реакцию на сигналы SIGALRM и SIGVTALRM.           */
 /* Позаботимся, чтобы функция обработки не могла быть прервана */
 /* срабатыванием другого таймера                               */
 if ((sigemptyset (sset) < 0) || (sigaddset (sset, SIGALRM) < 0) ||
      (sigaddset (sset, SIGVTALRM) < 0)) {
    perror ("SIGEMPTYSET or SIGADDSET");
    return (1);
 }

 sact.sa_handler = proc_itexprtn;
 sact.sa_flags = 0;
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, sact, NULL) < 0) {
    perror ("SIGACTION-SIGALRM");
    return (2);
 }
 if (sigaction (SIGVTALRM, sact, NULL) < 0) {
    perror ("SIGACTION-SIGVTALRM");
    return (3);
 }

 /* Зарегистрируем функцию print_rt_data() в atexit() */
 if (atexit (print_rt_data) != 0) {
    perror ("ATEXIT");
    return (4);
 }

 /* Взведем таймер реального времени как одноразовый */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 0;
 itvl.it_value.tv_sec = 10;
 itvl.it_value.tv_usec = 0;
 if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
    perror ("SETITIMER-REAL");
    return (5);
 }

 /* Установим начало отсчета для данных о реальном времени */
 (void) gettimeofday (prt_vls++, NULL);

 /* Таймер виртуального времени сделаем периодическим */
 itvl.it_interval.tv_sec = 0;
 itvl.it_interval.tv_usec = 100000;    /* 0.1 сек */
 itvl.it_value.tv_sec = 0;
 itvl.it_value.tv_usec = 100000;
 if (setitimer (ITIMER_VIRTUAL, itvl, NULL) < 0) {
    perror ("SETITIMER-VIRTUAL");
    return (6);
 }

 /* Убедимся, что пока процесс приостановлен, */
 /* его виртуальное время также стоит         */
 sleep (5);

 /* Нагрузим процессор вычислениями, которые не должны завершиться */
 /* до срабатывания таймера реального времени                      */
 {
    double s = 0;
    double d = 1;
    int i;

    for (i = 1; i <= 100000000; i++) {
      s += d / i;
      d = -d;
    }
 }

 return 0;
}

Результаты работы программы могут выглядеть так, как показано в листинге 12.35. Можно видеть, что, во-первых, доля процессорного времени, которая достается процессу, может существенно колебаться (см., например, отсчеты 4, 6 и 8) и, во-вторых, пока выполнение процесса приостановлено, его виртуальное время также стоит (см. инструкцию sleep (5) в листинге 12.34, отсчет 1 в результатах).

Прошедшее реальное время за один квант 
виртуального
1   5255378 мксек
2   249993  мксек
3   250003  мксек
4   100000  мксек
5   250177  мксек
6   859831  мксек
7   100054  мксек
8   729943  мксек
9   580003  мксек
10  99989   мксек
11  580129  мксек
12  429881  мксек
13  99990   мксек

Интервальные таймеры полезны как средство управления ходом выполнения программы. В частности, они позволяют ограничить время работы отдельных фрагментов, что особенно важно для приложений реального времени. В программе, показанной в листинге 12.36, фигурируют две функции обработки данных; каждой из них отводится не более заданного константой IT_PERIOD числа секунд.

/* * * * * * * * * * * * * * * * * * * * */
/* Программа вызывает функции обработки  */
/* и контролирует время их выполнения    */
/* с помощью интервального таймера       */
/* * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <setjmp.h>

/* Период интервального таймера (в секундах) */
#define IT_PERIOD       1

static jmp_buf buf_env; /* Буфер для функций setjmp и longjmp */

static double s;        /* Результат функций обработки данных */

/* Функция обработки срабатывания таймера реального времени */
/* (сигнал SIGALRM)                                         */
static void proc_sigalrm (int dummy) {
 printf ("Текущий результат текущей функции обработки данных: %g\n", s);
 longjmp (buf_env, 1);
}

/* Первая функция обработки данных */
/* (вычисляет ln (2))              */
static void proc_data_1 (void) {
 double d = 1;
 int i;

 s = 0;
 for (i = 1; i <= 100000000; i++) {
    s += d / i;
    d = -d;
 }
}

/* Вторая функция обработки данных */
/* (вычисляет sqrt (2))            */
static void proc_data_2 (void) {
 s = 1;
 do {
    s = (s + 2 / s) * 0.5;
 } while ((s * s - 2) > 0.000000001);
}

int main (void) {
                              /* Массив указателей на функции обработки данных */
 void (*fptrs []) (void) = {proc_data_1, proc_data_2, NULL};
                              /* Указатель на указатель              */
                              /* на текущую функцию обработки данных */
 void (**tfptr) (void);
                              /* Вспомогательная временная переменная */
 void (*tmpfptr) (void);
 struct itimerval itvl;
 struct sigaction sact;
 sigset_t sset;
 int i;

 /* Установим реакцию на сигнал SIGALRM */
 if (sigemptyset (sset) < 0) {
    perror ("SIGEMPTYSET");
    return (1);
 }

 sact.sa_handler = proc_sigalrm;
 sact.sa_flags = SA_NODEFER;   /* Не блокировать SIGALRM            */
                                         /* в функции обработки этого сигнала */
 sact.sa_mask = sset;
 if (sigaction (SIGALRM, sact, NULL) < 0) {
    perror ("SIGACTION");
    return (2);
 }

 /* На всякий случай сделаем таймер реального времени периодическим */
 itvl.it_interval.tv_sec = IT_PERIOD;
 itvl.it_interval.tv_usec = 0;

 /* Цикл вызова функций обработки данных. */
 /* Выполним его дважды                   */
 for (i = 0; i < 2; i++) {
    tfptr = fptrs;
    (void) setjmp (buf_env);
    /* Сюда вернется управление после срабатывания таймера */
    while ((tmpfptr = *tfptr++) != NULL) {
      /* Даже если предыдущая функция обработки данных */
      /* закончилась до того, как сработал таймер,     */
      /* обеспечим текущей функции полный интервал     */
      itvl.it_value.tv_sec = IT_PERIOD;
      itvl.it_value.tv_usec = 0;
      if (setitimer (ITIMER_REAL, itvl, NULL) < 0) {
         perror ("SETITIMER");
         return (3);
      }
      (*tmpfptr) ();
      printf ("Результат текущей функции обработки данных: %g\n", s);
    }
 }

 return 0;
}

Результаты работы приведенной программы могут выглядеть так, как показано в листинге 12.37. Видно, что первая функция обработки данных не уложилась в отведенное время, поэтому ее выполнение было прервано срабатыванием таймера ; второй функции этого времени оказалось достаточно.

Текущий результат текущей функции 
обработки данных: 0.693147
Результат текущей функции обработки 
данных: 1.41421
Текущий результат текущей функции 
обработки данных: 0.693147
Результат текущей функции обработки 
данных: 1.41421

Комбинация нелокальных переходов и асинхронной обработки сигналов представляет собой взрывоопасную смесь, обращаться с которой следует крайне осторожно. Несмотря на кажущуюся простоту, в пояснениях нуждается целый ряд фрагментов приведенной выше программы.

Во-первых, обратим внимание на использование флага SA_NODEFER, предписывающего не блокировать сигнал при входе в функцию его (сигнала) обработки. Этот флаг необходим, поскольку не происходит нормального выхода из функции proc_sigalrm(), а значит, не происходит и восстановления маски сигналов процесса. В результате, после первого входа в функцию обработки сигнала SIGALRM он рискует остаться заблокированным, вследствие чего процесс не будет извещен о последующих срабатываниях интервального таймера реального времени.

Во-вторых, следует исходить из возможности приостановки выполнения процесса в произвольном месте, в частности, внутри вычисления выражений. Если оформить вызов функции обработки данных в виде инструкции (**tfptr++) (), то нет гарантии, что компилятор не запланирует увеличение указателя tfptr после возврата из функции, выполнение которой может быть прервано срабатыванием таймера. В таком случае управление вернется в начало внутреннего цикла, а указатель не увеличится, что приведет к повторному вызову той же функции обработки данных. Чтобы справиться с этой проблемой, пришлось задействовать вспомогательную временную переменную tmpfptr.

В-третьих, процессу может выделяться сколь угодно малая доля процессорного времени, а потому соотношение между виртуальным и реальным временем оказывается недетерминированным. Если, например, расположить вызов setjmp() после взведения таймера (см. листинг 12.38), то таймер реального времени может сработать до инициализации буфера buf_env, после чего нелокальный переход из функции обработки сигнала приведет к полному хаосу.

if (setitimer (ITIMER_REAL, 
    itvl, NULL) < 0) {
     . . . 
 }

     . . .

 (void) setjmp (buf_env);

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

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

К теме интервальных таймеров логически примыкает функция alarm() (см. листинг 12.39), позволяющая вызывающему процессу заказать доставку сигнала SIGALRM через заданное число секунд реального времени. Если в момент обращения к alarm() предыдущий заказ существовал и еще не был выполнен, он отменяется, а если значение аргумента seconds отлично от нуля, его место занимает новый.

#include <unistd.h>
unsigned alarm (unsigned seconds);

При наличии предыдущего заказа функция alarm() возвращает число секунд, оставшееся до его выполнения; в противном случае результат равен нулю.

Примеры использования функции alarm() были приведены при рассмотрении средств межпроцессного взаимодействия.

Вернуться к учебному плану