時間主要有三款:
- CLOCK_REALTIME (wall clock,calendar):目前時間
- 對 Epoch 時間 1970-01-01 00:00:00 +0000 (UTC) 開始算。
- Epoch 時間大約是 UNIX 系統誕生的時陣。Epoch 意思是「出現新進步和大變革的時代」,UNIX 系統誕生家己講是大時代。
- 紀錄 Epoch 開始的時間,其中「秒」存在資料類型 time_t 的變數,在 32-bit Linux 系統是 signed 32-bit,可計約 68 年,到 2038 年會有溢位問題。
- 有時需要調整,可能透過網路或 date 指令手動調整,所以有突然間跳較快跳較慢。
- 會使搭配獨立的硬體時鐘,但不是必要的。硬體時鐘 (Real-Time Clock (RTC)、CMOS clock、BIOS clock),可能只有精確到秒,關機時會使靠電池繼續走,開機時電腦會使馬上取得目前時間。此外,它嘛也可能提供鬧鐘、喚醒等其它功能。只有適當的時機才會去存取硬體時間,例如開機時、或有所調整時,可透過系統管理指令 hwclock 或 /etc/rtcN 存取。
- CLOCK_MONOTONIC 開機時間,對開機開始算,就是 uptime 顯示的時間,是一直增加的,不受調時間影響而有跳的問題。
- CPU time:程式實際有用到 CPU 的時間片段總和,還可以細分成 user 及 system 兩部份,分別是在 user mode 花的時間,以及因此 process 執行 system call 在 kernel 的執行時間。指令 time 用來量測程式使用的 CPU time。程式內部可用系統呼叫 times() 及 getrusage(),或函式 clock() 量測 CPU time。
軟體上的任何計時,都是由 kernel 內部固定週期的 jiffy 中斷來驅動。目前時間可能透過網路調整,但驅動還是靠 jiffy。jiffy 週期精準度,會影響軟體計時的精準度,沒有網路校時,一段時間後,目前時間可能就不準了。
每次 jiffy 中斷,Kernel 系統變數 jiffies 就會加一。jiffy 中斷週期換著頻率,就是 Kernel 編譯設定值 HZ,在不同版本的 kernel 或處理器平台會有不同的值,一般在 100 ~ 1000 之間,也就是 jiffy 週期是在 0.01秒 ~ 0.001秒之間,決定了許多系統呼叫時間的精確度。例如 select() 跟 sigtimedwait() 的 timeout、getrusage() 的 CPU time 量測等。系統呼叫 times() 是個特例,回報的時間單位是 kernel 常數 USER_HZ,應用程式可使用sysconf(_SC_CLK_TCK) 得知 USER_HZ。
High-resolution timers (HRTs)
Linux 2.6.21 以前,系統時間最小單位是 jiffy,之後的 CONFIG_HIGH_RES_TIMERS 開始支援高解析度
timer,讓時間可以更精確,一般可達 microsecond (原理?)。可用系統呼叫 clock_getres() 或看
/proc/timer_list 的 resolution 查看 timer 解析度。
取得時間的系統呼叫
time_t // 回傳 Epoch 開始的秒數。-1 當 timep 有誤時。 time( time_t *timep); // 非 NULL 時亦 回傳 Epoch 開始的秒數。
一般使用 time(NULL),time() 實作可能是呼叫 gettimeofday()。int // 0 成功,–1 錯誤 gettimeofday( struct timeval *tv, // 提供空間回傳 Epoch 秒數和 µsec。 struct timezone *tz); // 廢棄,需指定為 NULL。
雖然 tv_usec 提供 microsecond precision,但實際 accuracy 由 architecture-dependent 實作決定。- clock_gettime():取得秒數和 nsec (struct timespec)。
- CLOCK_REALTIME 從 Epoch 開始計數。
- CLOCK_MONOTONIC 從開機開始計數。實際上開機多久是紀錄和 realtime (wall clock) 的時間差 (變數 wall_to_monotonic),如有調校 realtime 或 suspend,wall_to_monotonic 也會跟著一起調整。(為什麼不用 jiffies 轉換就好?)
- 其它還有 CPU 時間等。
註:Epoch 的 time()、gettimeofday()、和 CLOCK_REALTIME 都是 __get_realtime_clock_ts(),而 CLOCK_MONOTONIC 是用 ktime_get_ts()。
設定或調校目前時間
在這段,設定和調校意思不同,設定跳躍式調整,調校是逐漸調整。
- 設定時間
- settimeofday()
- clock_settime():使用 settimeofday() 設定日期時間,限 realtime。
- stime():使用 settimeofday() 設定秒數。glibc 2.31 以後移除,該改用 clock_settime()。
- 調校時間
- adjtimex()
- adjtime():呼叫 adjtimex() 調校時間
日期 (calendar) 時間
struct timeval { time_t tv_sec; // Epoch 開始的秒數 suseconds_t tv_usec; // 額外 microseconds (long int) };
時間轉換函數
time_t 和其它格式間轉換,包括 struct tm、固定或使用者定義的列印格式,內含時區、日光節約時間、和 localization 轉換的處理。
函數庫函數將 Epoch 秒數 (time_t) 轉成年月日時分秒等分別紀錄的 struct tm 格式
- gmtime(): 轉成 struct tm
- gmtime_r():gmtime() 的 reentry 版本,存在使用者提供的 struct tm。
- localtime():轉成 struct tm,考量時區和日光節約時間,同時做 tzset() 的動作。
- localtime_r():同 localtime(),但存在使用者提供的資料結構,不設 tzname, timezone, daylight。
tm_sec 欄位在閏秒時會到 60。
struct tm 轉成字串
- asctime(), asctime_r():轉成標準的列印格式
- strftime():轉成自訂的列印格式
ctime(t):相當於 asctime(localtime(t)),自動考量時區和日光節約時間。ctime_r()
反過來轉換的有
strptime():字串轉成 struct tm
mktime()、timegm()、timelocal():struct tm 轉成 time_t
mktime() 忽略 tm_wday 和 tm_yday 原本的值並修正,其它值超出範圍或負數也會調整。會考量時區。tm_isdst 決定是否使用 DST 設定,0 忽略 DST、正值不管日期總是調整 DST、負值依據日期調整 DST,最後有調整 tm_isdst 設為正值、否則設為 0。
timezone
幾乎所有時間計數不需要考慮 timezone,只有顯示時需要轉換成本地時間。少數,例如 vfat 檔案系統,如果 kernel 的 timezone 不對,檔案的時戳會設錯。hwclock --hctosys 依據 TZ 或 /usr/share/zoneinfo 設定 kernel timezone。
TZ 有兩種格式
- <dst>[offset],start[/time],end[/time]]
- <std><offset>[<dst>]
- <std> 是時區名稱,至少有三個英文字母
- <offset> 加上本地時間可以得到 UTC 時間,在子午線東方會是負的。格式是 [-]hh[:mm[:ss]],可以包括時分秒。
- <dst> 有用到日光節約時間 (datlight saving time) 才有,說明一年中的哪些日子改用不同的 <offset>,格式是 dst[offset],start[/time],end[/time]
- dst 是日光節約時間的名稱
- offset 格式跟上述 <offset> 一樣,是日光節約日用的 <offset>,如省略則預設提早一個小時
- start 跟 end 說明使用日光節約時間開始跟結束的日子,有三種格式
- Jn:n 為 1 ~ 365 的 Julian day,不計 Leap days,所以二月二十九日不能表示,三月一日是 60。
- n:n 為 0 ~ 365 的 Julian day,閏年加上二月二十九日。
- Mm.w.d:月、周、日
- time 表示切換日光節約時間是在本地時間幾點切換,如省略則是 02:00:00。
- 範例:NZST-12:00:00NZDT-13:00:00,M10.1.0,M3.3.0
- :[filespec]
Timer slack
從 Linux 2.6.28 開始,可以控制 thread 的 "timer slack" 值,延遲喚醒 certain 系統呼叫 that block with a timeout,讓 kernel 合併 (coalesce) 一些喚醒事件,來減少喚醒次數並節省電源。細節請見系統呼叫 prctl() 的 PR_SET_TIMERSLACK 描述。
參考
- man 7 time
- man hwclock
- man ctime
- man strftime
- man strptime
- man timezone
- https://lirobo.blogspot.com/2015/08/busybox-ntpd.html
- TLPI chap. 10 和 chap 26
- man rtc
- Documentation/rtc.txt
- man adjtimex
- userspance 延遲用 sleep、kernel 延遲用 mdelay
- sched_yield、pthread_delay_np
- [轉載整理]Linux系統時間
- 使用 realtime 的呼叫
* 顯示日期時間
* 檔案時間用 timeval:utimes()
* select(), pselect()
* sem_timedwait() semtimedwait()
和 realtime 無關的呼叫
* timeval 運算:timercmp(), timersub()
使用 int 表示 msec
* int32_t 範圍是 -2147483648 ~ 2147483647 約 24 天,uint32_t 的話約 49 天。