فصل ۱۰ - Time

زمان پراسس یا process time مقدار زمان cpu است که توسط پراسس استفاده شده است. محاسبه‌ی process time برای بهینه‌سازی و بهبود عملکرد برنامه مفید است.

زمان واقعی یا real time زمانی است که

  1. از یک نقطه‌ی استاندارد (calendar time)
  2. از یک نقطه‌ی ثابت در عمر یک پراسس اندازه‌گیری می‌شود (elapsed time)

بسیاری از معماری‌های کامپیوتر دارای سخت‌افزاری درونی هستند که کرنل را قادر می‌سازد تا real time و process time را اندازه‌گیری کند.

از آنجایی که نمایش human readable زمان بستگی به موقعیت جغرافیایی و قراردادهای زبانی و فرهنگی دارد در ادامه‌ی مبحث time به بررسی مبحث‌های timezone و locale خواهیم پرداخت.

  • UTC = Universal Coodrdinated Time (Previously known and Greenwich Mean Time or GMT)
  • UNIX Epoch = 1 January 1970
  • Micro = one-millionth
  • CEST = Central European Summer Time
  • CET = Central European Time
  • EST = US Eastern Standard Time
  • I18N = Internationalazation

calendar time که نسبت به UNIX Epoch محاسبه می‌شود در داخل متغیری از نوع time_t ذخیره می‌شود که یک متغیر integer است که در SUSv3 تعریف شده است.

time_t یک عدد integer علامت‌دار است. و لذا در سیستم‌های ۳۲ بیتی مشکل سال ۲۰۳۸ را داریم هر چند تا سال ۲۰۳۸ اکثر سیستم‌های یونیکسی ۶۴ بیتی و بالاتر خواهند بود ولی مشکل احتمالا در مورد سیستم‌های embedded که از طول عمر بالاتری نسبت به سیستم‌های دسکتاپ برخوردار هستند باقی خواهد بود. (صفحه ۱۸۶)

سیستم کال gettimeofday(2) زمان تقویمی یا calendar time را در بافری که tv به آن اشاره می‌کند برمی‌گرداند.

#include <sys/time.h>

struct timeval {
    time_t      tv_sec;     /* Seconds sice 00:00:00, 1 Jan 1970 UTC */
    suseconds_t tv_usec;    /* Addistional microseconds (long int) */
}

int gettimeofday(struct timeval *tv, struct timezone *tz);
                        Returns 0 on success, or -1 on error

دقت فیلد tv_usec توسط معماری سخت‌افزار تعیین می‌شود.

وجود فیلد tz در سیستم کال gettimeofday دلایل تاریخی دارد و امروزه منسوخ شده است و همیشه باید با NULL ست شود. (مطالعه‌ی بیشتر صفحه‌ی ۱۸۷)

راه ساده‌تری نیز برای گرفتن ثانیه‌های گذشته شده از Epoch وجود دارد و آن استفاده از سیستم کال time(2) است. (مقدار موجود در فیلد tv_sec از struct timeval را مستقیما به صورت integer می‌دهد.)

#include <time.h>

time_t time(time_t *timep);
            Returns number of seconds since the Epoch, or (time_t) -1 on error

وجود همزمان دو سیستم کال time و gettimeofday دلایل تاریخی دارد. سیستم کال اولیه time بوده است ولی در 4.3BSD سیستم کال gettimeofday برای دقت بیشتر اضافه شد. امروزه سیستم کال time را می‌توان به صورت library function که سیستم کال gettimeofday را فراخوانی می‌کند پیاده‌سازی کرد.

دقت کنید که سیستم کال time یک خروجی را از دو طریق برمی‌گرداند، یکی از طریق خروجی تابع و دیگری از طریق آرگومان تابع. معمولا آن را به این صورت استفاده می کنیم: t = time(NULL);

مثال از دو تابع time و gettimeofday

#include <time.h>

char *ctime(const time_t *timep);
            Returns pointer to statically allocated string terminated by
            newline and \0 on success, or NULL on error

تابع ctime که یک library function است یک رشته‌ی ۲۶ کاراکتری استاندارد برمی‌گرداند. تابع ctime در مقدار بازگشتی خود local timezone و DST را لحاظ می‌کند. مقدار برگشتی این تابع به صورت statically allocated است و در فراخوانی بعد رونویسی می‌شود.

مثالی از تابع ctime

نسخه‌ی reentrant از تابع ctime به نام ctime_r وجود دارد که آرگومان دومی از نوع آرایه‌ای از کاراکترها می‌گیرد و خروجی را در این آرایه نیز قرار می‌دهد. (علاوه بر خروجی statically allocated خودش)

#include <time.h>

char *ctime_r(const time_t *restrict timep, char buf[restrict 26]);

شکستن time_t به اجزای کوچکتر یا به عبارت بهتر به struct tm

تابع gmtime زمان تقویمی یا calendar time را بر مبنای timezone گرینویچ به struct tm می‌شکند. برعکس gmtime تابع localtime در شکستن calendar time به struct tm تنظیمات timezone و DST سیستم لوکال را لحاظ می‌کند.

#include <time.h>

struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
            Both return a pointer to a statically allocated broken-down
                            time structure on success, or NULL on error

نسخه‌ی reentrant این دو تابع به نام‌های gmtime_r و localtime_r نیز وجود دارند.

#define _BSD_SOURCE

struct tm {
    int tm_sec;         /* Seconds (0-60) */
    int tm_min;         /* Minutes (0-59) */
    int tm_hour;        /* Hours (0-23) */
    int tm_mday;        /* Day of month (1-31) */
    int tm_mon;         /* Month (0-11) */
    int tm_year;        /* year since 1900 */
    int tm_wday;        /* Day of week (Sunday = 0) */
    int tm_yday;        /* Day in the year (0-365; 1 Jan = 0) */
    int tm_isdst;       /* Daylight saving time flag
                            > 0: DST is in effect;
                            = 0: DST is not in effect;
                            < 0: DST informtaion is not available; */

    long int tm_gmtoff;     /* Number of seconds that represented time falls east of UTC */
    const char *tm_zone;    /*Abbreviated timezone name (e.g. CEST) */
};

برای استفاده از دو فیلد آخر باید فیچر تست ماکروی _BSD_SOURCE‍‍ تعریف شده باشد. SUSv3 هیچکدام از این دو فیلد را مشخص نکرده است و فقط روی پیاده‌سازی‌های کمی از UNIX وجود دارند.

The tm_sec field can be up to 60 (rather than 59) to account for the leap seconds that are occasionally applied to adjust human calendars to the astronomically exact (the so-called tropical) year. (?)

مثال از توابع gmtime و localtime (برای استفاده از دو فیلد tm_zone و tm_gmtoff نیازی به استفاده از فیچر تست ماکروی #define _BSD_SOURCE نبود.)

برای تبدیل یک struct tm به زمان تقویمی یا calendar time (نوع time_t) از تابع mktime(3) استفاده می‌کنیم. struct tm ورودی به صورت local time تفسیر می‌شود. در مرحله‌ی تبدیل دو فیلد tm_wday و tm_yday نادیده گرفته می‌شوند.

time zone در عملیات تبدیل تابع (3)mktime به صورت خودکار مورد استفاده قرار می‌گیرد. ولی DST بر مبنای struct tm ورودی لحاظ خود شد. معمولا آن را منفی قرار می‌دهیم تا خود تابع کشف کند که آیا در آن زمان DST فعال است یا نه.

#include <time.h>

time_t mktime(struct tm *timeptr);
                Returns seconds since the Epoch corresponding to timeptr
                                     on success, or (time_t) -1 on error

تابع mktime ممکن است آرگومان ورودی از نوع struct tm را تغییر دهد. حداقل مطمئن خواهد شد که فیلدهای tm_wday و tm_yday دارای مقادیر درست بر حسب زمان وارد شده هستند.

تابع mktime نیاز ندارد تا مقادیر قرار گرفته در فیلدها در range صحیح باشند. اگر مقادیر فیلدها درست نباشد خودش تغییرات لازم را برای درست کردن آن‌ها انجام می‌دهد. مقادیر فیلدها می‌توانند منفی هم باشند. به این صورت می‌توان عملیات ریاضی جمع و تفریق را بر روی زمان شکسته شده بر حسب struct tm انجام داد.

مثالی از تابع mktime

تابع asctime(3) خروجی‌ای مانند تابع ctime تولید می‌کند با این تفاوت که به جای اشاره‌گری به calendar time اشاره‌گری به struct tm می‌گیرد. علاوه بر این به عکس تابع ctime تنظیمات timezone سیستم روی خروجی تابع asctime تاثیر ندارد چون struct tm داده شده از طریق توابع gmtime یا localtime لوکالایز شده است و نتیجه هم اکنون در داخل فیلد‌های struct tm قرار دارد.

#include <time.h>

char *asctime(const struct tm *timeptr);
                    Returns pointer to statically allocated string terminated
                               by newline and \0 on success, or NULL on error

مانند تابع ctime در تابع asctime نیز هیچ‌گونه کنترلی روی فرمت رشته‌ی خروجی نداریم.

یک استراکچر را می‌توان به این صورت مقدار دهی کرد. struct1 = *struct2;

مثال از کلیه‌ی توابع خوانده شده تا اینجا

مثال فوق به وضوح نشان می‌دهد که سیستم کال‌های time و gettimeofday در مقدار خروجی خود timezone را لحاظ می‌کنند. یعنی خروجی این توابع مقدار ثانیه‌های گذشته از Unix Epoch بر حسب UTC به اضافه‌ی timezone است.

تابع strftime(3) کنترل بیشتری نسبت به تابع asctime در نوع خروجی فراهم می‌کند.

#include <time.h>
    
size_t strftime(char *outstr, size_t maxsize, const char *format,
                const struct tm *timeptr);
                            Returns number of bytes placed in outstr (excluding
                            terminating null byte) on success, or 0 on error

اگر طول string حاصل از تابع strftime، با احتساب NULL آخر رشته، از maxsize بیشتر باشد، تابع strftime به نشانه‌ی خطا صفر برمی‌گرداند و محتوای outstr مشخص شده نیست.

مشاهده‌ی specifierهای تابع strftime

The %U and %W specifiers both produce a week number in the year. The %U week numbers are calculated such that the first week containing a Sunday is numbered 1, and the partial week preceding that is numbered 0. If Sunday happens to fall as the first day of the year, then there is no week 0, and the last day of the year falls in week 53. The %W week numbers work in the same way, but with Monday rather than Sunday.

تابع currTime در فایل lpi.c

مثال از تابع strftime: در این مثال می‌بینیم که برخلاف چیزی که در شکل ۱۰.۱ کتاب ذکر شده تابع strftime تنظیمات timezone را در خروجی خود لحاظ نمی‌کند و محتوای struct tm را دقیقا مصرف می‌کند.

تابع strptime(3) برعکس تابع (3)strftime است. strptime یک رشته‌ی تاریخ به اضافه‌ی زمان را می‌شکند و اجزای آن را در struct tm داده شده قرار می‌دهد.

#define _XOPEN_SOURCE
#include <time.h>

char *strptime(const char *str, const char *format, struct tm *timeptr);
                                Returns pointer to next unprocessed character in
                                                str on success, or NULL or error

تابع strptime در صورت موفقیت یک اشاره‌گر به کاراکتر بعدی پردازش نشده برمی‌گرداند. در صورت match شدن کامل رشته str با format اشاره‌گر به کاراکتر \0 پایان str اشاره می‌کند. در صورتی که format نتواند با بخشی از str مچ شود تابع به عنوان خطا NULL برمی‌گرداند. قواعد match شدن شبیه قواعد تابع scanf است.

The conversion specifications are similar to those given to strftime(). The major difference is that the specifiers are more general. For example, both %a and %A can accept a weekday name in either full or abbreviated form, and %d or %e can be used to read a day of month with or without a leading 0 in the case of single-digit days. In addition, case is ignored; for example, May and MAY are equivalent month names. The strptime(3) manual page provides more detailes.

پیاده‌سازی glibc از تابع strptime فیلدهایی از struct tm را که در رشته‌ی فرمت مشخص نشده‌اند تغییر نمی‌دهد. یعنی می‌توانیم فیلدهای یک struct tm را در فراخوانی‌های متعدد strptime به صورت تدریجی مقداردهی کنیم. (ر.ک ۱۹۶) ولی نمی‌توان روی این رفتار در همه‌ی پیاده‌سازی‌های یونیکس تکیه کنیم. در یک برنامه‌ی portable باید مطمئن شویم که format و str همه‌ی فیلدهای struct tm را به صورت صحیح پوشش می‌دهند و مقداردهی می‌کنند یا اینکه struct tm به صورت صحیحی از قبل مقداردهی اولیه شده است.

strptime هیچ وقت فیلد tm_isdst را در struct tm مقداردهی نمی‌کند.

در struct tm فیلد tm_mday=0 به معنای روز آخر ماه قبل است. مثال در این مورد

تابع getdate هم عملکردی مشابه strptime دارد ولی مستعد خطاهای امنیتی است. (ر.ک ۱۹۶ و getdate(3))

تبدیل time_t به رشته‌ی قابل خواندن تاریخ و زمان بر حسب تابع gmtime یا localtime و تابع asctime

مثال از توابع strptime و strftime

تابع localtime به سادگی با استفاده از تابع gmtime و اختلاف زمانی نسبت به UTC نوشته می‌شود. پیاده سازی localtime

توابع این فصل تا اینجا

# output function arg1 arg2 arg3 arg4
1 time_t time time_t *
2 int gettimeofday struct timeval * NULL
3 char * ctime time_t *
4 struct tm * gmtime time_t *
5 struct tm * localtime time_t *
6 char * asctime struct tm *
7 time_t mktime struct tm *
8 size_t strftime char *buf size_t buf_size char *format struct tm *tm
9 char * strptime char *str char *format struct tm *tm

کشورهای مختلف و حتی مناطق مختلف یک کشور در timezone ها و رژیم‌های مختلف DST قرار دارند.

به علت حجیم بودن و فرار بودن اطلاعات مربوط به timezone ها، سیستم این اطلاعات را در فرمت‌های استاندارد نگهداری می‌کند. این فایل‌ها در دایرکتوری /usr/share/zoneinfo قرار دارند. وقتی برای یک برنامه یک timezone انتخاب می‌کنیم در واقع آدرس نسبی یک فایل درون این دایرکتوری را مشخص می‌کنیم.

فرمت فایل timezone در tzfile(5) مستند سازی شده است.

local time سیستم در فایل timezone قرار گرفته در آدرس /etc/localtime که معمولا به یک فایل در درون دایرکتوری فوق لینک شده تعریف شده است.

Timezone files are build using zic(8), the zone information compiler. The zdump(8) command can be used to display the time as it would be currently according to the timezone in a specified timezone file.

برای مشخص کردن یک timezone وقتی یک برنامه را اجرا می‌کنیم باید متغیر محیطی TZ را ست کنیم. مقدار TZ به این صورت است که ابتدا یک : قرار می‌گیرد و سپس نام یک timezone که در داخل دایرکتوری /usr/share/zoneinfo قرار دارد می‌آید. ست کردن timezone به صورت خودکار توابع ctime و localtime و mktime و strftime را تحت تاثیر قرار می‌دهد.

برای به دست آوردن تنظیمات فعلی timezone از تابع tzset(3) استفاده می‌کنیم. این تابع سه متغیر سراسری را مقداردهی می‌کند.

char *tzname[2];        /* Name of timezone and alternate (DST) timezone */
int  daylight;          /* Nonzero if there is an alternate (DST) timezone */
long timezone;          /* Seconds difference between UTC and local
                           standard time */

مثال از تابع tzset

متغیر محیطی غیر استاندارد مربوط به GNU به نام TZDIR می‌تواند فولدر پیش‌فرض فایل‌های timezone را رونویسی کند.

تابع tzset ابتدا دنبال متغیر محیطی TZ می‌گردد و از آن استفاده می‌کند. اگر این متغیر ست نشده باشد از timezone تعریف شده در فایل /etc/localtime استفاده می‌کند. اگر متغیر محیطی TZ با مقدار empty یا اشتباه تعریف شده باشد از UTC استفاده می‌شود.

مثال برای timezone. متغیر محیطی TZ را در خط فرمان تغییر دهید و نتیجه را مشاهده کنید.

راه دیگری که در استاندارد SUSv3 تعریف شده است و به مراتب دقیقتر ولی سخت‌نویس‌تر از شیوه‌ی نخست تعیین کردن timezone در خط فرمان است در صفحه‌ی ۱۹۹ توضیح داده شده است ولی ما شیوه‌ی نخست را ترجیح می‌دهیم:

TZ=":Europe/Berlin" ./a.out

SUSv3 defianes a locale as the "subset of a user's environment that depends on language and cultural conventions."

به طور ایده‌ال هر برنامه‌ای که قرار باشد در بیش از یک سیستم اجرا شود باید با مفهوم locale پیاده‌سازی شود تا ورودی و خروجی را بر مبنای ترجیحات زبانی و فرمت مورد پسند کاربر انجام دهد.

locale ها هم مثل timezone ها اطلاعاتی پرحجم و فرار هستند. به همین خاطر به جای اینکه تمام برنامه‌ها حجم انبوهی از اطلاعات locale را ذخیره کنند این اطلاعات در فایل‌های با فرمت استاندارد ذخیره می‌شوند.

اطلاعات locale در داخل دایرکتوری /usr/share/locale ذخیره می‌شود. هر زیر دایرکتوری در این دایرکتوری با قرارداد زیر نامگذاری می‌شود:

language[_territory[.codeset]][@modifier]

The language is a two-letter ISO language code, and the territory is a two-letter ISO country code. The codeset designates a character-encoding set. The modifier provides a means of distinguishing multiple locales directories whose language, territory, and codeset are the same.

An example of a complete locale directory name is de_DE.utf-8@euro as the locale for: German language, Germany, UTF-8 character encoding, employing the euro as the monetary unit.

fr_CH مشخص کننده‌ی locale مناطق فرانسوی زبان کشور سوئیس است. CH از کلمه‌ی لاتین Condoederatio Helvetica که نام لاتین Switzerland است گرفته شده است. سوییس چهار زبان رسمی دارد.

وقتی ما یک locale را برای استفاده‌ی برنامه مشخص می‌کنیم در واقع داریم نام یکی از زیر دایرکتوری‌های داخل دایرکتوری /usr/share/locale را به برنامه می‌دهیم.

The Normalized codeset is a version of the codeset name in which all nonalphanumeric characters are removed, all letters are converted to lowercase, and the resulting string is prepended with the characters iso.

اگر locale مشخص شده برای برنامه وجود نداشته باشد کتابخانه‌ی C سعی می‌کند به ترتیب با حذف بخش‌های زیر یک locale مناسب را پیدا کند.

  1. codeset
  2. normalized codeset
  3. territory
  4. modifier

اگر locale پیدا نشود، یعنی زیر دایرکتوری مربوط به آن در دایرکتوری localeها وجود نداشته باشد، و با حذف بخش‌های فوق باز هم به یک locale قابل قبول نرسیم تابع setlocale خطا خواهد داد.

The file /usr/share/locale/locale.alias defines alternative ways of specifying locales to a program.

فایل‌های قرار گرفته در داخل هر زیر دایرکتوری locale به شرح زیر است:

نام فایل هدف
LC_CTYPE فایلی که طبقه‌بندی کاراکترها و قوانین مربوط به case conversion کاراکترها را مشخص می‌کند. (تابع isalpha(3) را ببینید.)
LC_COLLATE فایلی که قوانین collation برای یک character set را مشخص می‌کند.
LC_MONETARY فایلی که قوانین مربوط به فرمت مقادیر پولی را مشخص می‌کند. (تابع localeconv(3) را ببینید.)
LC_NUMERIC فایلی که قوانین مربوط به فرمت اعداد به مقادیر پولی را مشخص می‌کند.
LC_TIME فایلی که قوانین مربوط به فرمت مقادیر date و time را مشخص می‌کند.
LC_MESSAGES A directory containing files specifying formats and values used for affirmative and negative (yes/no) responses.

The LC_COLLATE defines a set of rules describing how the characters in a character set are ordered (i.e., the "alphabetical" order for the character set). These rules determine the operation of the strcoll(3) and strxfrm(3) functions. Even languages using Latin-based scripts don't follow the same ordering rules. (refer to page 202)

The LC_MESSAGES directory is one step toward internationalizing the messages displayed by a program. More extensive I18N of program messages can be accomplished through the use of either messages catalogs (see the catopen(3) and catgets(3) manual pages) or the GNU gettext API.

کتابخانه‌ی glibc نسخه‌ی 2.2.2 تعدادی طبقه‌بندی locale غیر استاندارد به اسامی زیر معرفی کرده است. برای توضیحات به صفحه ۲۰۲ مراجعه کنید.

  1. LC_ADDRESS
  2. LC_IDENTIFICATION
  3. LC_MEASUREMENT (imperial vs metric)
  4. LC_NAME
  5. LC_PAPER (defines standard paper size)
  6. LC_TELEPHONE

SUSv3 می‌گوید که یک locale به نام POSIX که به خاطر مسائل تاریخی C نیز نامیده می‌شود باید در سیستم وجود داشته باشد. این locale رفتار تاریخی سیستم‌های یونیکس را پیاده‌سازی می‌کند بنابراین از کرکتر ست اسکی و نام‌های انگلیسی برای روزها و ماه‌ها و جواب‌های بله و خیر استفاده می‌کند. اجزا monetary و numeric این locale تعریف نشده است.

دستور locale(1) اطلاعات locale جاری (در محیط shell) را نشان می‌دهد. دستور locale -a لیست تمام locale های تعریف شده در سیستم را نشان می‌دهد.

برای ست کردن و یا query گرفتن locale جاری برنامه از تابع setlocale استفاده می‌کنیم.

#include <locale.h>

char *setlocale(int category, const char *locale);
            Returns pointer to a (usually statically allocated) string
            identifying the new or current locale on success, or NULL on error.

category همان نام‌های LC_* قبل هستند. واضح است که می‌توان locale یک category را تغییر داد و دست به locale دیگر category ها نزد. در این صورت مثلا datetime به فرمت آلمانی نمایش داده می‌شود ولی مقادیر عددی به فرمت آمریکایی نشان داده خواهد شد.

اگر بخواهیم locale تمام category ها را عوض کنیم از ثابت LC_ALL استفاده می‌کنیم.

به نظر می‌رسد locale های قابل ثبت در داخل دایرکتوری /usr/lib/locale قرار دارند و شاید هم locale هایی باشند که از خروجی دستور locale -a به دست می‌آیند.

اگر از تابع setlocale(LC_ALL, ""); استفاده کنیم category های locale از روی متغیرهای محیطی سیستم که به نام همین category ها تعریف شده‌اند خوانده می‌شوند. اگر از این تابع استفاده نکنیم متغیرهای محیطی که حاوی مقادیر locale هستند هیچ تاثیری بر برنامه‌ی ما ندارند.

اولویت متغیرهای محیطی به شرح زیر است:

  1. LC_ALL
  2. LC_*
  3. LANG

متغیر محیطی LANG هم مثل متغیر محیطی LC_ALL تمام category های locale را با یک مقدار ست می‌کند. ولی اولویت کمتری دارد. از این متغیر محیطی می‌توان به عنوان مقدار پیش‌فرض استفاده کرد. به این صورت که locale پیش‌فرض را درون متغیر محیطی LANG قرار داد و سپس category هایی که قصد تغییر locale آن‌ها را داریم با متغیر محیطی LC_* مقداردهی کنیم.

تنظیمات locale بر طیف وسیعی از توابع از قبیل strftime و strptime تاثیر می‌گذارد.

System time is usually maintained by tools such as the Network Time Protocol daemon.

اپلیکیشن‌هایی که قصد تغییر ساعت سیستم با دو اینترفیس settimeofday(2) و adjtime(3) را دارند باید مطمئن شوند که اجرا کننده‌ی این اپلیکشن‌ها دسترسی CAP_SYS_TIME را دارد. سیستم کال (2)settimeofday در SUSv3 تعریف نشده است.

#define _BSD_SOURCE
#include <sys/time.h>

int settimeofday(const struct timeval *tv, const struct timezone *tz);
                                  Returns 0 on success, or -1 on error

همانند تابع gettimeofday در این تابع نیز دیگر از tz استفاده نمی‌کنیم و باید آن را NULL گذاشت. داشتن فیلد tv.tv_usec به معنی داشتن دقت در حد میکروثانیه نیست. (ر.ک ۲۰۴)

مثال از سیستم کال settimeofday

تغییر یکباره و ناگهانی ساعت سیستم باعث ایجاد مشکل در برنامه‌هایی مثل make(1) و لاگ فایل‌ها می‌شود. به همین خاطر برای ایجاد تغییرات کوچک در حد چند ثانیه معمولا بهتر است از تابع adjtime(3) استفاده کنیم که باعث می‌شود ساعت سیستم به صورت تدریجی به مقدار خواسته شده مقداردهی شود.

#define _BSD_SOURCE
#include <sys/time.h>

int adjtime(struct timeval *delta, struct timeval *olddelta);
                         Returns 0 on success, or -1 on error

The delta argument points to a timeval structure that specifies the number of seconds and microseconds by which to change the Time.

If delta value is positive, then a small amount of additional time is added to the system clock each second, until the desired amount of time has been added. If the delta value is negative, the clock is slowed down in a similar fashion.

نرخ تنظیم ساعت در لینوکس معماری x86-32، یک ثانیه در هر ۲۰۰۰ ثانیه است (یا ۴۳٫۲ ثانیه در روز)

اما در مورد آرگومان olddelta:

  1. اگر در هنگام فراخواندن تابع adjtime قبلا نیز آن را فراخوانده باشیم مقدار زمانی که از تنظیم قبلی باقی مانده است در آرگومان olddelta قرار می‌گیرد. اگر به دانستن این مقدار علاقه‌ای نداریم می‌توانیم این آرگومان را NULL قرار دهیم.
  2. برعکس مورد قبل اگر فقط بخواهیم بفهمیم که چقدر از زمان باقی مانده است تا ساعت سیستم تنظیم شود آرگومان delta را NULL قرار می‌دهیم و مقدار قرار گرفته در آرگومان olddelta را می‌خوانیم.

تابع (3)adjtime روی سیستم کال مخصوص لینوکس adjtimex(2) پیاده سازی شده است. این سیستم کال توسط Network Time Protocol (NTP) daemon به کار گرفته می‌شود.

The accuracy of various time-related system calls described in this book is limited to the resolution of the system software clock, which measures time in units called jiffies. The size of a jiffy is defined by the constants HZ whithin the kernel source code. This is the unit in which the kernel allocates the CPU to processes under the round-robin time-sharing scheduling algorithm.

در لینوکس معماری x86-32 تا کرنل ۲.۴ و خود ۲.۴ نرخ software clock، صد هرتز است. یعنی هر ثانیه ۱۰۰ جیفی است پس هر جیفی ۱۰ میلی‌ثانیه است. از آنجایی که سرعت CPU ها نسبت به زمانی که لینوکس برای اولین بار پیاده‌سازی شد، در کرنل ۲.۶.۰ نرخ software clock در لینوکس معماری x86-32 به ۱۰۰۰ هرتز افزایش یافت.

مزیت نرخ software clock بالاتر این است که تایمرها می‌توانند با دقت بالاتری عمل کنند و اندازه‌گیری زمان‌ها نیز دقیق‌تر می‌شود. اما باید در نظر داشت که به خاطر زمان context switch که مقداری از زمان مفید CPU را می‌گیرد نباید نرخ software clock را خیلی بالا برد.

Process time مقدار زمان CPU است که به یک پراسس از زمان پیدایشش تاکنون اختصاص داده شده است. کرنل CPU time را به دو بخش تقسیم می‌کند:

  1. User CPU time مقدار زمان CPU که پروسس در user mode اجرا می‌شود. User CPU time مقدار زمانی است که برنامه به CPU دسترسی دارد.
  2. System CPU time مقدار زمانی است که CPU در kernel mode کار می‌کند. این زمان می‌تواند مصروف اجرای سیستم کال‌ها یا اجرای بعضی دیگر از task ها از جانب برنامه‌ی کاربر شود مثل سرویس دادن به نقص‌های صفحه.

با تفاسیر فوق process time برابر است با مجموع User CPU time و System CPU time که توسط یک پروسس مصرف شده است. برای مطالعه بیشتر time(1) را ملاحظه کنید.

سیستم کال times(2) اطلاعات مربوط به process time را بازیابی می‌کند. مقادیر بازگشتی در یک استراکچر از نوع tms قرار می‌گیرند.

#include <sys/times.h>

clock_t times(struct tms *buf);
            Returns number of clock ticks (sysconf(_SC_CLK_TCK)) since
           arbitrary time in past on success, or (clock_t) -1 on error

خروجی سیستم کال (2)times در واقع همان real time است که بر حسب clock tick از یک زمان دلخواه در گذشته محاسبه می‌شود. SUSv3 از قصد این مبدا زمانی را مشخص نکرده است و صرفا می‌گوید که این مبدا باید در طول عمر calling process ثابت باشد. بنابراین تنها روش قابل حمل استفاده از این مقدار اختلاف عدد میان دو فراخوانی سیستم کال times متوالی است. حتی با این شیوه هم ممکن است clock_t سر ریز کند و دوباره از صفر شمرده شود. در این صورت مقدار دوم از مقدار اول کوچکتر خواهد بود! راه قابل اطمینان برای محاسبه‌ی گذشت زمان استفاده از gettimeofday است.

در لینوکس می‌توان آرگومان buf را NULL مقداردهی کرد ولی این روش portable نیست.

struct tms {
    clock_t tms_utime;      /* User CPU time used by caller */
    clock_t tms_stime;      /* System CPU time used by caller */
    clock_t tms_cutime;     /* User CPU time of all (waited for) children */
    clock_t tms_cstime;     /* System CPU time of all (waited for) children */
};

The last two fields return information about the CPU time used by all child processes that have terminated and for which the parent (i.e., the caller of times()) has done a wait() system call.

نوع داده‌ی clock_t از نوع integer است که زمان را برحسب واحدهایی به نام clock ticks اندازه گیری می‌کنید. در یک ثانیه sysconf(_SC_CLK_TCK); کلاک تیک وجود دارد. اگر مقدار clock tick های موجود در هر یک از ۴ فیلد فوق را بر مقدار اخیر تقسیم کنیم مقدار زمان داده شده به پروسس را بر حسب ثانیه به دست خواهیم آورد.

On most Linux hardware architectures, sysconf(_SC_CLK_TCK); returns the number 100. This corresponds to the kernel constant USER_HZ. However, USER_HZ can be defined with a value other than 100 on a few architectures, such as Alpha and IA-64.

استخراج clock ticks از طریق تابع sysconf

پیاده سازی دستور time (منطق برنامه جای بهبود دارد؟)

تابع clock(3) اینترفیس ساده‌تری برای بازیابی process time ارائه می‌کند. این تابع یک مقدار برمی‌گرداند که معادل user CPU time و system CPU time یا به طور کلی معادل total CPU time است که توسط برنامه مصرف شده است.

#include <time.h>

clock_t clock(void);
            Returns total CPU time used by the calling process measured in
            CLOCKS_PER_SEC, or (clock_t)-1 on error.

The value returned by clock() is measured in units of CLOCKS_PER_SEC, so we must divide by this value to arrive at the number of seconds of CPU time used by the process.

مقدار CLOCKS_PER_SECOND توسط POSIX.1 فارغ از دقت software clock عدد ثابت ۱،۰۰۰،۰۰۰ است.

نمایش مقدار CLOCKS_PER_SEC

اگرچه نوع مقدار برگشتی توسط تابع clock که clock_t است با نوع داده مورد استفاده توسط سیستم کال times یکی است ولی واحدهای اندازه‌گیری آن‌ها فرق می‌کند. این امر به علت تداخل تعاریف clock_t در POSIX.1 و استاندارد زبان برنامه‌نویسی C است.

SUSv3 می‌گوید که CLOCKS_PER_SEC می‌تواند به صورت variable هم تعریف شود علی‌رغم اینکه مقدارش عدد ثابت ۱،۰۰۰،۰۰۰ است لذا نمی‌توانیم آن را به عنوان یک compile-time constant لحاظ کنیم یعنی نمی‌توانیم آن را در #ifdef ها که مربوط دستورات preprocessor است استفاده کنیم.

مثالی از کلیه توابع این مبحث

وقتی Real time از یک نقطه‌ی استاندارد اندازه‌گیری می‌شود، مثلا از UNIX epoch آن را calendar time می‌نامیم ولی وقتی از یک نقطه‌ی غیر استاندارد در عمر process اندازه‌گیری می‌شود آن را elapsed time می‌نامیم.

process time به دو جز user CPU time و system CPU time تقسیم می‌شود.

مشکلات

مبحث locale تست به مقدار کافی انجام ندادم. چند locale جدید روی سیستم باید نصب شود و تست‌های صفحه‌ی ۲۰۴ انجام شود.

از تابع adjtime مثالی ننوشتم.

در مورد بحث زیر بیشتر تامل شود.

جستجوی بیشتر برای یادگیری

  1. Tropical year چیست؟ (تعداد روزهای سال ۳۶۵٫۲۴۲۱۹؟)
  2. در دستور time(1) مقدار process real time چیست؟

مطالعه‌ی بیشتر

نوشته شده در: 1405-03-10 (1 هفته 1 روز 1 ساعت پیش)

من محسن هستم؛ برنامه‌نویس تفننی!

برای ارتباط با من یا در همین سایت کامنت بگذارید و یا به dokaj.ir(at)gmail.com ایمیل بزنید.

در مورد این مطلب یادداشتی بنویسید.