1 はじめに
Linux でのプログラミングでは、ユーザー モードでもカーネル モードでも、時間の取得がよく使用されます。コードを記述するときに簡単に参照できるように、ユーザー モードとカーネル モードのそれぞれで一般的に使用されるいくつかの時刻取得インターフェイスを以下に示します。
Linux 時間サブシステムの開発履歴と詳細な紹介については、「Linux 時間サブシステムの詳細な理解」を参照してください。
2. ユーザーモード取得時間
- 2.1クロック_gettime()
#include <time.h>
int clock_gettime (clockid_t __clock_id, struct timespec *__tp);
作用
: システムクロックの種類に基づいて現在時刻を取得します。__clock_id
: システムクロックの種類。一般的に使用される値:- CLOCK_REALTIME: UTC 1970-1-1 0:0:0 から始まるシステム相対時間。システム時間を変更すると、取得される値も変わります。
- CLOCK_MONOTONIC: システム絶対時間/単調時間 (システムが再起動してからの時間) システム時間を変更しても影響はありません。
- CLOCK_PROCESS_CPUTIME_ID: このプロセスが現在のコード システム CPU に到達するまでにかかる時間。
- CLOCK_THREAD_CPUTIME_ID: このスレッドが現在のコード システム CPU に到達するまでにかかる時間。
__tp
:現在時刻を保存します。返回值
: 成功した場合は 0、失敗した場合は -1 を返します。
timespec
構造:
struct timespec
{
__time_t tv_sec; /* Seconds. 秒 */
__syscall_slong_t tv_nsec; /* Nanoseconds. 纳秒*/
};
例:
#include <stdio.h>
#include <string.h>
#include <time.h>
long long get_clock_sys_time_ns(void)
{
struct timespec tp;
long long time_ns = 0;
clock_gettime(CLOCK_MONOTONIC, &tp);
time_ns = (long long)tp.tv_sec * 1000000000 + tp.tv_nsec;
return time_ns;
}
int main(void)
{
struct timespec tp;
///< 获取从1970年1月1日到目前的时间
memset(&tp, 0, sizeof(struct timespec));
clock_gettime(CLOCK_REALTIME, &tp);
printf("clock_id = CLOCK_REALTIME, sec = %ld, nsec = %ld\n", tp.tv_sec, tp.tv_nsec);
///< 获取系统启动时间
memset(&tp, 0, sizeof(struct timespec));
clock_gettime(CLOCK_MONOTONIC, &tp);
printf("clock_id = CLOCK_MONOTONIC, sec = %ld, nsec = %ld, sys_time = %lld ns\n", tp.tv_sec, tp.tv_nsec, get_clock_sys_time_ns());
///< 获取本进程运行时间
memset(&tp, 0, sizeof(struct timespec));
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp);
printf("clock_id = CLOCK_PROCESS_CPUTIME_ID, sec = %ld, nsec = %ld\n", tp.tv_sec, tp.tv_nsec);
///< 获取本线程运行时间
memset(&tp, 0, sizeof(struct timespec));
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp);
printf("clock_id = CLOCK_THREAD_CPUTIME_ID, sec = %ld, nsec = %ld\n", tp.tv_sec, tp.tv_nsec);
return 0;
}
- 2.2. gettimeofday()
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
作用
:現在時刻を取得(1970年1月1日から現在時刻まで)tv
:現在の UTC 時間tz
:現在のタイムゾーン情報返回值
: 成功した場合は 0、失敗した場合は -1 を返します。
タイムバル構造:
struct timeval
{
__time_t tv_sec; /* Seconds. 秒*/
__suseconds_t tv_usec; /* Microseconds. 微秒*/
};
タイムゾーン構造:
struct timezone
{
int tz_minuteswest; /* Minutes west of GMT. 和Greenwich时间差了多少分钟 */
int tz_dsttime; /* Nonzero if DST is ever in effect. 日光节约时间的状态 */
};
例:
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
long long get_sys_time_ms(void)
{
long long time_ms = 0;
struct timeval tv;
gettimeofday(&tv, NULL);
time_ms = ((long long)tv.tv_sec*1000000 + tv.tv_usec) / 1000;
return time_ms;
}
int main(void)
{
///< 获取系统时间
printf("sys_time = %lld ms\n", get_sys_time_ms());
return 0;
}
- 2.3. 時間()
#include <time.h>
time_t time(time_t *tloc);
作用
: 1970-01-01 00:00:00 +0000 (UTC) からの秒数を取得しますtloc
: 返された秒ストレージ ポインタ返回值
: 成功した場合は秒数を返し、失敗した場合は -1 を返し、エラーの理由は errno に格納されます。
time_tのタイプ:
typedef long time_t;
例:
#include <stdio.h>
#include <time.h>
time_t get_utc_time(void)
{
return time(NULL);
}
int main(int argc, char **argv)
{
time_t utc_time = get_utc_time();
printf("utc_time = %ld s\n", utc_time);
return 0;
}
- 2.4. 現地時間()
#include <time.h>
struct tm *localtime(const time_t *timep);
作用
: time_t 型の時刻を struct tm 型の時刻に変換しますtimep
: 現在の UTC 秒返回值
:現地時間に戻る
TM構造:
struct tm
{
int tm_sec; /* Seconds. [0-60] (1 leap second) */
int tm_min; /* Minutes. [0-59] */
int tm_hour; /* Hours. [0-23] */
int tm_mday; /* Day. [1-31] */
int tm_mon; /* Month. [0-11] 注意:0代表1月,以此类推*/
int tm_year; /* Year - 1900. 该值为实际年份减去1900*/
int tm_wday; /* Day of week. [0-6] 注意:0代表星期一,以此类推*/
int tm_yday; /* Days in year.[0-365] 从每年的1月1日开始的天数,其中0代表1月1日,以此类推*/
int tm_isdst; /* DST. [-1/0/1] 夏玲时标识符*/
};
例:
#include <stdio.h>
#include <time.h>
time_t get_utc_time(void)
{
return time(NULL);
}
int main(int argc, char **argv)
{
time_t utc_time = get_utc_time();
printf("utc_time = %ld s\n", utc_time);
struct tm *local_tm = localtime(&utc_time);
printf("local time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", local_tm->tm_year + 1900,
local_tm->tm_mon + 1,
local_tm->tm_mday,
local_tm->tm_hour,
local_tm->tm_min,
local_tm->tm_sec);
return 0;
}
- 2.5. ローカルタイム_r()
#include <time.h>
struct tm *localtime_r(const time_t *timep, struct tm *result);
作用
: time_t 型の時刻を struct tm 型の時刻に変換しますtimep
: 現在の UTC 秒result
: 現地時間返回值
:現地時間に戻る
注:
localtime はスレッドセーフな関数ではありません。リアルタイム要件が高いシステムの場合、複数のスレッドが同時に localtime を呼び出すと、データが上書きされる可能性があります。代わりに localtime_r を使用してください。
例:
#include <stdio.h>
#include <time.h>
time_t get_utc_time(void)
{
return time(NULL);
}
int main(int argc, char **argv)
{
time_t utc_time = get_utc_time();
printf("utc_time = %ld s\n", utc_time);
struct tm result;
struct tm *local_tm = localtime_r(&utc_time, &result);
printf("local time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", local_tm->tm_year + 1900,
local_tm->tm_mon + 1,
local_tm->tm_mday,
local_tm->tm_hour,
local_tm->tm_min,
local_tm->tm_sec);
printf("result time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", result.tm_year + 1900,
result.tm_mon + 1,
result.tm_mday,
result.tm_hour,
result.tm_min,
result.tm_sec);
return 0;
}
- 2.6. gmtime()
#include <time.h>
struct tm *gmtime(const time_t *timep);
作用
: tm 構造体の GMT 時刻 (UTC 時刻) を返します。timep
: 現在の UTC 秒返回值
:現地時間に戻る
例:
#include <stdio.h>
#include <time.h>
time_t get_utc_time(void)
{
return time(NULL);
}
int main(int argc, char **argv)
{
time_t utc_time = get_utc_time();
printf("utc_time = %ld s\n", utc_time);
struct tm *gmt_tm = gmtime(&utc_time);
printf("gmt time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", gmt_tm->tm_year + 1900,
gmt_tm->tm_mon + 1,
gmt_tm->tm_mday,
gmt_tm->tm_hour,
gmt_tm->tm_min,
gmt_tm->tm_sec);
return 0;
}
localtime と gmtime の違い:
localtime と gmtime はどちらも C 言語の関数で、time_t 型の時刻を struct tm 型の時刻に変換するために使用されます。それらの違いは、gmtime は time_t を協定世界時である UTC 時間に変換するのに対し、localtime は time_t を現地時間に変換することです。
例: gmtime インターフェイスと localtime インターフェイスによって返された時間を使用して、ローカル タイム ゾーンを計算します。
#include <stdio.h>
#include <time.h>
time_t get_utc_time(void)
{
return time(NULL);
}
int main(int argc, char **argv)
{
time_t utc_time = get_utc_time();
printf("utc_time = %ld s\n", utc_time);
struct tm *gmt_tm = gmtime(&utc_time);
printf("gmt time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", gmt_tm->tm_year + 1900,
gmt_tm->tm_mon + 1,
gmt_tm->tm_mday,
gmt_tm->tm_hour,
gmt_tm->tm_min,
gmt_tm->tm_sec);
int gmt_hour = gmt_tm->tm_hour;
struct tm *local_tm = localtime(&utc_time);
printf("local time = %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n", local_tm->tm_year + 1900,
local_tm->tm_mon + 1,
local_tm->tm_mday,
local_tm->tm_hour,
local_tm->tm_min,
local_tm->tm_sec);
int local_hour = local_tm->tm_hour;
int local_time_zone = local_hour - gmt_hour;
if (local_time_zone < -12)
{
local_time_zone += 24;
}
else if (local_time_zone > 12)
{
local_time_zone -= 24;
}else{
}
printf("local_time_zone = %d\n", local_time_zone);
return 0;
}
3. カーネル状態取得時間
- 3.1. do_gettimeofday() (新しいカーネルには存在しない可能性がある古い関数)
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
作用
: C 標準ライブラリの gettimeofday() と同じ使用法tv
:現在の UTC 時間
タイムバル構造:
struct timeval
{
__time_t tv_sec; /* Seconds. 秒*/
__suseconds_t tv_usec; /* Microseconds. 微秒*/
};
例:
#include <linux/module.h>
#include<linux/time.h>
MODULE_LICENSE("GPL");
int __init do_gettimeofday_init(void)
{
printk("do_gettimeofday test begin.\n");
struct timeval now=
{
.tv_sec=0,
.tv_usec=0
}; //声明一个变量
do_gettimeofday(&now); //调用函数获取时间,此时间是距离1970-01-01 00:00:00的时间
/*显示当前时间差*/
printk("the seconds of the day is: %ld\n", now.tv_sec); //秒数
printk("the microseconds of the day is: %ld\n", now.tv_usec); //微秒数
printk("do_gettimeofday test over.\n");
return 0;
}
void __exit do_gettimeofday_exit(void)
{
printk("Goodbye do_gettimeofday test\n");
}
module_init(do_gettimeofday_init);
module_exit(do_gettimeofday_exit);
-
3.2. ktime_t形式に基づく時刻
参考:Linux カーネルの時計取得 -
- ktime_get()
#include "linux/ktime.h" ktime_t ktime_get(void);
作用
: 取得されるのは CLOCK_MONOTONIC 時間です。ktime_get によって取得された時間にはデバイスのスリープ時間はカウントされません。この時間統計の開始点はデバイスの起動後です。
返回值
: データ型を ktime_t 形式でナノ秒単位で返します。ktime_tの定義:
typedef s64 ktime_t;
例:
time_test_drv_init ktime_t curTime = 0; curTime = ktime_get(); TIME_TEST_INFO("ktime_get:%lld ns", curTime);
-
- ktime_get_ts64()
#include "linux/time64.h" void ktime_get_ts64(struct timespec64 *ts);
作用
: 関数は ktime_get と全く同じですが、時間を表すデータ型が ktime_t から timespec64 に変わっている点が異なります。timespec64は次のように定義されます。
struct timespec64 { time64_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
timespec64 には秒とナノ秒が含まれており、ktime_t ナノ秒と比較して、この時間表現は人間が見るのに適しています。
例:
static void show_time_ts64(const char* caller, const int line, const struct timespec64 *curTimeTs) { pr_info("%s,%d:%lld s %ld ns\n", caller, __LINE__, curTimeTs->tv_sec, curTimeTs->tv_nsec); } time_test_drv_init struct timespec64 curTimeTs; ktime_get_boottime_ts64(&curTimeTs); show_time_ts64(__func__, __LINE__, &curTimeTs);
-
3. ktime_get_boottime()
static inline ktime_t ktime_get_boottime(void);
作用
:ktime_get_boottime と ktime_get で取得される時間の最大の違いは、デバイスがスリープに入る時間が含まれていることと、この時間統計の開始点もデバイスの起動後であることです。
返回值
: 戻り値の型はktime_t、単位はナノ秒です。例:
time_test_drv_init ktime_t curTime = 0; curTime = ktime_get_boottime(); TIME_TEST_INFO("ktime_get_boottime:%lld ns", curTime);
-
4. ktime_get_boottime_ts()
void ktime_get_boottime_ts64(struct timespec64 *);
作用
: ktime_get_boottime_ts の機能は ktime_get_boottime と全く同じですが、時間を表すデータ型が ktime_t から timespec64 に変わっている点が異なります。 -
5. ktime_get_real()
ktime_t ktime_get_real(void);
作用
: ktime_get_real で取得される時刻の開始点は、デバイスの起動時刻ではなく、UTC を基準とした相対値、つまり 1970 年からの時刻です。
例:time_test_drv_init ktime_t curTime = 0; curTime = ktime_get_real(); TIME_TEST_INFO("ktime_get_real:%lld ns", curTime);
-
6. ktime_get_real_ts()
void ktime_get_real_ts(struct timespec64 *);
作用
: ktime_get_real_ts の機能は ktime_get_real とまったく同じですが、時間を表すデータ型が ktime_t から timespec64 に変更される点が異なります。
例:time_test_drv_init struct timespec64 curTimeTs; ktime_get_real_ts64(&curTimeTs);
-
7.まとめ
- 7.1. ktime_tの時間インターフェースを返します。
インターフェース 時間システム 説明する ktime_t ktime_get( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 ktime_t ktime_get_boottime( void ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 ktime_t ktime_get_real( void ); CLOCK_REALTIME 自然時系 ktime_t ktime_get_locktai( void ); CLOCK_TAI 自然時系 ktime_t ktime_get_raw( void ); CLOCK_MONOTONIC_RAW システム起動時刻を時刻の原点とする時刻制度 -
CLOCK_REALTIMEとCLOCK_TAI はどちらも自然時刻系ですが、後者はうるう秒の影響を受けないという違いがあります。ただし、ユーザー空間が時刻を設定し、NTP も自然時刻を調整するため、両方の時刻システムがリバウンドを引き起こす可能性があります。
-
CLOCK_BOOTTIME、CLOCK_MONOTONIC、CLOCK_MONOTONIC_RAW、これら 3 つはシステム起動時点を時間の原点とする時間系です。CLOCK_BOOTTIME と CLOCK_MONOTONIC の違いは、前者はシステムのスリープ時に実行時間が一時停止されないのに対し、後者は一時停止されることですが、どちらも NTP の影響を受ける点は同じです。CLOCK_MONOTONIC_RAW と CLOCK_MONOTONIC は基本的に同じですが、前者は NTP の影響を受けません。
-
7.2. u64 (ナノ秒) の時間インターフェイスを返します。
インターフェース 時間システム 説明する u64 ktime_get_ns( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 u64 ktime_get_boottime_ns( void ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 u64 ktime_get_real_ns( void ); CLOCK_REALTIME 自然時系 u64 ktime_get_locktai_ns( void ); CLOCK_TAI 自然時系 u64 ktime_get_raw_ns( void ); CLOCK_MONOTONIC_RAW システム起動時刻を時刻の原点とする時刻制度 - 7.3. timespec64の時間インターフェースを返します。
インターフェース 時間システム 説明する void ktime_get_ts64( struct timespec64 * ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 void ktime_get_boottime_ts64( struct timespec64 * ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 void ktime_get_real_ts64( struct timespec64 * ); CLOCK_REALTIME 自然時系 void ktime_get_ Clocktai_ts64( struct timespec64 * ); CLOCK_TAI 自然時系 void ktime_get_raw_ts64( struct timespec64 * ); CLOCK_MONOTONIC_RAW システム起動時刻を時刻の原点とする時刻制度 #include "linux/time64.h" struct timespec64 { time64_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
- 7.4. time64_t (秒)を返す時間インターフェース:
インターフェース 時間システム 説明する time64_t ktime_get_秒( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 time64_t ktime_get_boottime_秒( void ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 time64_t ktime_get_real_秒( void ); CLOCK_REALTIME 自然時系 time64_t ktime_get_locktai_秒( void ); CLOCK_TAI 自然時系 time64_t ktime_get_raw_秒( void ); CLOCK_MONOTONIC_RAW システム起動時刻を時刻の原点とする時刻制度 - 7.5. 大まかな ktime_t を返す時間インターフェース:
インターフェース 時間システム 説明する ktime_t ktime_get_coarse( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 ktime_t ktime_get_coarse_boottime( void ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 ktime_t ktime_get_coarse_real( void ); CLOCK_REALTIME 自然時系 ktime_t ktime_get_coarse_locktai( void ); CLOCK_TAI 自然時系 - 7.6. 大まかな u64 (ナノ秒) を返す時間インターフェイス:
インターフェース 時間システム 説明する u64 ktime_get_coarse_ns( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 u64 ktime_get_coarse_boottime_ns( void ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 u64 ktime_get_coarse_real_ns( void ); CLOCK_REALTIME 自然時系 u64 ktime_get_coarse_ Clocktai_ns( void ); CLOCK_TAI 自然時系 - 7.7. 大まかな timespec64 を返す時間インターフェース:
インターフェース 時間システム 説明する void ktime_get_coarse_ts64( struct timespec64 * ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 void ktime_get_coarse_boottime_ts64( struct timespec64 * ); クロック_ブートタイム システム起動時刻を時刻の原点とする時刻制度 void ktime_get_coarse_real_ts64( struct timespec64 * ); CLOCK_REALTIME 自然時系 void ktime_get_coarse_ Clocktai_ts64( struct timespec64 * ); CLOCK_TAI 自然時系 - 7.8.高速な u64 (ナノ秒)を返す時間インターフェイス:
インターフェース 時間システム 説明する u64 ktime_get_mono_fast_ns( void ); クロック_モノトニック システム起動時刻を時刻の原点とする時刻制度 u64 ktime_get_boot_fast_ns( void); CLOCK_BOOTTIME 以系统启动的时间点为时间原点的时间体系 u64 ktime_get_real_fast_ns( void ); CLOCK_REALTIME 自然时间体系 u64 ktime_get_raw_fast_ns( void ); CLOCK_MONOTONIC_RAW 以系统启动的时间点为时间原点的时间体系
4.自己实现的延时函数示例
void delay_us(uint32_t nus)
{
volatile uint32_t startts, endts, ts;
ts = nus;
startts = get_time_us();
endts = startts + ts;
if (endts > startts)
{
while (get_time_us() < endts);
}
else
{
while (get_time_us() > endts);
while (get_time_us() < endts);
}
}
5.有关__clock_id的详细说明
clockid
的取值常用的有以下:
-
CLOCK_REALTIME,就是我们所说的自然时间,由于计算机上的时间有可能不准,所以是可以设置的,所以有可能会产生跳跃。后面所有的时间体系都是不可设置的,下面不再一一说明了。CLOCK_REALTIME_ALARM、CLOCK_REALTIME_COARSE、CLOCK_TAI虽然本身是不可设置的,但是都会受到CLOCK_REALTIME设置的影响,有可能会产生跳跃。
-
CLOCK_REALTIME_ALARM,和CLOCK_REALTIME相同,在定时器设置时才有用,ALARM代表的是定时设置,如果目标定时时间到了的时候系统在休眠,会唤醒系统。
-
CLOCK_REALTIME_COARSE,和CLOCK_REALTIME相同,精度不高但是获取比较快。
-
CLOCK_TAI,和CLOCK_REALTIME相同,但是不考虑闰秒问题,TAI是International Atomic Time的反向简称。
-
CLOCK_MONOTONIC,由于前面几个时间体系都有可能会产生回跳,计算机中需要有一个单调递增的时间体系。此时间体系的时间原点并不重要,在Linux中是以系统启动的时间点作为时间原点,在计算机休眠时会暂停走时,受adjtime和NTP的影响可能会向前跳跃。
-
CLOCK_MONOTONIC_COARSE,同上,但是精度降低,访问更快。
-
CLOCK_MONOTONIC_RAW,同CLOCK_MONOTONIC,但是不受adjtime和NTP的影响。
-
CLOCK_BOOTTIME,以系统启动时间为时间原点的时间体系,不受其它因素的影响,计算机休眠时依然走时。
-
CLOCK_BOOTTIME_ALARM,同上,在定时器设置时才有用,ALARM代表的是定时设置,如果目标定时时间到了的时候系统在休眠,会唤醒系统。
-
CLOCK_PROCESS_CPUTIME_ID,以进程创建时间为时间原点,进程运行时走时,进程休眠时暂停走时。
-
CLOCK_THREAD_CPUTIME_ID,以线程创建时间为时间原点,线程运行时走时,线程休眠时暂停走时。