gettimeofday和settimeofday的用法及示例

gettimeofdaysettimeofday 是用于获取和设置系统时间的系统调用,适用于需要高精度时间操作的场景。以下是它们的详细用法及示例:


1. gettimeofday

函数原型:

1
2
3
#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

参数说明:

  • tv:指向 struct timeval 的指针,用于存储获取的时间(精确到微秒)。
  • tz:时区信息,通常已废弃,设置为 NULL

struct timeval 结构:

1
2
3
4
struct timeval {
time_t tv_sec; // 秒(自 1970-01-01 00:00:00 UTC)
suseconds_t tv_usec; // 微秒
};

示例:获取当前时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>

int main() {
struct timeval tv;
if (gettimeofday(&tv, NULL) == 0) {
printf("Seconds since Epoch: %ld\n", tv.tv_sec);
printf("Microseconds: %ld\n", tv.tv_usec);
} else {
perror("gettimeofday failed");
}
return 0;
}

2. settimeofday

函数原型:

1
2
3
#include <sys/time.h>

int settimeofday(const struct timeval *tv, const struct timezone *tz);

参数说明:

  • tv:指向 struct timeval 的指针,包含要设置的时间。
  • tz:时区信息,通常设置为 NULL

注意事项:

  • 需要 root 权限(如使用 sudo 运行程序)。
  • 直接修改系统时间可能导致不可预见的后果(如日志混乱),需谨慎使用。

示例:设置系统时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
struct timeval tv;
struct tm tm_time;

// 设置目标时间:2023-10-01 12:00:00(UTC)
tm_time.tm_year = 2023 - 1900; // 年份从 1900 开始计数
tm_time.tm_mon = 10 - 1; // 月份 0-11(10 月为 9)
tm_time.tm_mday = 1;
tm_time.tm_hour = 12;
tm_time.tm_min = 0;
tm_time.tm_sec = 0;
tm_time.tm_isdst = -1; // 自动判断夏令时

// 将 tm 结构转换为 time_t(UTC 时间)
time_t t = mktime(&tm_time);
if (t == (time_t)-1) {
perror("mktime failed");
exit(EXIT_FAILURE);
}

// 填充 timeval 结构
tv.tv_sec = t;
tv.tv_usec = 0;

// 设置系统时间(需 root 权限)
if (settimeofday(&tv, NULL) == 0) {
printf("System time updated successfully.\n");
} else {
perror("settimeofday failed");
exit(EXIT_FAILURE);
}

return 0;
}

注意事项

  1. 时区处理

    • mktime 默认将 struct tm 视为本地时间。若需使用 UTC 时间,可使用 timegm(非标准,需在 Linux 中定义 _BSD_SOURCE)或调整时区:
      1
      2
      setenv("TZ", "UTC", 1);  // 设置时区为 UTC
      tzset(); // 生效时区设置
      处理完毕后恢复原时区。
  2. 权限问题

    • 运行 settimeofday 示例时需使用 sudo
      1
      sudo ./settime_example
  3. 替代方案

    • 对于时间调整,推荐使用 ntp 服务或 adjtime 逐步校正。
    • 新代码建议使用 clock_gettimeclock_settime(精度更高)。

通过上述示例和说明,可安全地使用 gettimeofday 获取时间,并在必要时用 settimeofday 设置系统时间。

Linux内核ktime_get_real_ts64函数用法及示例

ktime_get_real_ts64 是 Linux 内核中用于获取当前真实时间(即“墙上时间”,wall time)的高精度函数,返回的时间格式为 timespec64(64 位秒和纳秒)。以下是详细用法及示例:


1. 函数原型与头文件

1
2
3
#include <linux/timekeeping.h>

void ktime_get_real_ts64(struct timespec64 *ts);

参数说明:

  • ts:指向 struct timespec64 的指针,用于存储获取的时间。

struct timespec64 结构:

1
2
3
4
struct timespec64 {
time64_t tv_sec; // 秒(自 1970-01-01 00:00:00 UTC)
long tv_nsec; // 纳秒 [0, 999,999,999]
};

2. 使用场景

  • 在内核模块中获取高精度实时时间(如日志记录、性能分析)。
  • 需要避免用户空间与内核空间上下文切换的开销时。
  • 支持 64 位时间(避免 2038 年溢出问题)。

3. 示例代码

内核模块示例:获取并打印当前时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <linux/module.h>
#include <linux/ktime.h>
#include <linux/timekeeping.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of ktime_get_real_ts64");

static int __init example_init(void) {
struct timespec64 ts;

// 获取当前真实时间
ktime_get_real_ts64(&ts);

// 打印时间(秒 + 纳秒)
printk(KERN_INFO "Current time: %lld seconds, %ld nanoseconds\n",
(long long)ts.tv_sec, ts.tv_nsec);

return 0;
}

static void __exit example_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

module_init(example_init);
module_exit(example_exit);

4. 编译与加载

Makefile 示例:

1
2
3
4
5
6
7
obj-m += example.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
# 编译模块
make

# 加载模块
sudo insmod example.ko

# 查看输出(dmesg)
dmesg | tail

# 卸载模块
sudo rmmod example

5. 注意事项

  1. 内核上下文安全性

    • ktime_get_real_ts64 可以在大多数上下文中调用(包括中断上下文),但需确认具体内核版本的实现。
    • 避免在持有锁时调用,以减少延迟。
  2. 时间精度

    • 返回的时间精度为纳秒级,但实际精度取决于硬件和内核配置(如 CONFIG_HIGH_RES_TIMERS)。
  3. 时间来源

    • 返回的时间是系统实时时间,可能受 NTP 调整或 settimeofday 影响。
  4. 64 位时间

    • 使用 time64_t 避免 2038 年溢出问题(32 位系统兼容)。

6. 对比其他时间函数

函数 描述 精度 适用场景
ktime_get_real_ts64 获取实时时间(墙上时间) 纳秒 需要 64 位时间的场景
ktime_get 获取单调时间(不受 NTP 影响) 纳秒 性能测量、超时计算
do_gettimeofday 获取实时时间(旧接口) 微秒 遗留代码兼容
current_kernel_time 获取实时时间(旧接口) 微秒 已废弃,不推荐使用

7. 时间格式转换

若需将 timespec64 转换为可读字符串,可使用 time64_to_tmsnprintf

1
2
3
4
5
6
7
8
9
#include <linux/time.h>

struct tm tm_time;
time64_to_tm(ts.tv_sec, 0, &tm_time); // 转换为分解时间(UTC)

// 格式化输出(示例)
printk(KERN_INFO "UTC Time: %04d-%02d-%02d %02d:%02d:%02d\n",
tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);

通过上述示例和说明,可以在内核模块中安全地使用 ktime_get_real_ts64 获取高精度实时时间。

Linux内核time64_to_tm 函数的用法及示例

time64_to_tm 是 Linux 内核中用于将 64 位时间戳time64_t,自 1970-01-01 00:00:00 UTC 的秒数)转换为人类可读的分解时间(struct tm)的函数。以下是其详细用法及示例:


1. 函数原型与头文件

1
2
3
#include <linux/time.h>

void time64_to_tm(time64_t totalsecs, int offset, struct tm *result);

参数说明:

  • **totalsecs**:待转换的 64 位时间戳(秒数)。
  • **offset**:时区偏移(单位:秒)。若设为 0,表示使用 UTC 时间;若需本地时间,需传入时区偏移(如东八区为 8*3600)。
  • **result**:指向 struct tm 的指针,用于存储转换后的分解时间。

struct tm 结构:

1
2
3
4
5
6
7
8
9
10
11
struct tm {
int tm_sec; // 秒 [0-59]
int tm_min; // 分 [0-59]
int tm_hour; // 小时 [0-23]
int tm_mday; // 日 [1-31]
int tm_mon; // 月 [0-11](0 代表 1 月)
int tm_year; // 年份(自 1900 年起的差值)
int tm_wday; // 星期 [0-6](0 代表周日)
int tm_yday; // 一年中的第几天 [0-365]
int tm_isdst; // 夏令时标志(负数表示未知)
};

2. 使用场景

  • 在内核模块中将时间戳转换为可读的年、月、日、时、分、秒。
  • 生成日志时间戳、调试信息或处理时间相关逻辑。

3. 示例代码

内核模块示例:将时间戳转换为分解时间并打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <linux/module.h>
#include <linux/time.h>
#include <linux/timekeeping.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example of time64_to_tm");

static int __init example_init(void) {
struct timespec64 ts;
struct tm tm_time;

// 1. 获取当前时间(64 位时间戳)
ktime_get_real_ts64(&ts);

// 2. 将时间戳转换为分解时间(UTC)
time64_to_tm(ts.tv_sec, 0, &tm_time); // offset=0 表示 UTC

// 3. 打印分解时间
printk(KERN_INFO "UTC Time: %04d-%02d-%02d %02d:%02d:%02d\n",
tm_time.tm_year + 1900, // 年份需 +1900
tm_time.tm_mon + 1, // 月份从 0 开始,需 +1
tm_time.tm_mday,
tm_time.tm_hour,
tm_time.tm_min,
tm_time.tm_sec);

return 0;
}

static void __exit example_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

module_init(example_init);
module_exit(example_exit);

4. 编译与加载

Makefile 示例:

1
2
3
4
5
6
7
obj-m += example.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 编译模块
make

# 加载模块
sudo insmod example.ko

# 查看输出(dmesg)
dmesg | tail -n 2

# 输出示例:
# [ 1234.567890] UTC Time: 2023-10-05 14:30:45
# [ 1234.567891] Module unloaded

# 卸载模块
sudo rmmod example

5. 注意事项

  1. 时区处理

    • 若需本地时间,需手动计算时区偏移(例如东八区为 8*3600 秒):
      1
      time64_to_tm(ts.tv_sec, 8 * 3600, &tm_time);  // 东八区
    • 内核中不直接支持时区数据库,需硬编码偏移量。
  2. 时间精度

    • time64_to_tm 仅处理秒级时间戳,纳秒部分(ts.tv_nsec)需额外处理。
  3. 年份和月份的修正

    • tm_year 返回的是自 1900 年起的差值,需 +1900 得到实际年份。
    • tm_mon 范围是 0-11(0 代表 1 月),需 +1 得到实际月份。
  4. 函数安全性

    • time64_to_tm 是内核安全函数,可在中断上下文中调用。

6. 对比用户空间函数

函数/操作 用户空间 内核空间
时间戳转分解时间 localtime() time64_to_tm()
时间戳来源 time() ktime_get_real_ts64()
时区支持 自动读取系统时区 需手动指定偏移量

7. 扩展应用

将分解时间格式化为字符串

若需生成自定义时间字符串(如 YYYY-MM-DD HH:MM:SS),可结合 snprintf

1
2
3
4
5
6
7
8
9
char time_buf[64];
snprintf(time_buf, sizeof(time_buf), "%04d-%02d-%02d %02d:%02d:%02d",
tm_time.tm_year + 1900,
tm_time.tm_mon + 1,
tm_time.tm_mday,
tm_time.tm_hour,
tm_time.tm_min,
tm_time.tm_sec);
printk(KERN_INFO "Formatted Time: %s\n", time_buf);

通过上述示例,可以轻松在内核模块中使用 time64_to_tm 将时间戳转换为可读格式,适用于日志记录、调试等场景。

使用time64_to_tm接口获取本地时间的方法及示例

在内核中使用 time64_to_tm 获取本地时间需要 手动指定时区偏移量(因为内核不直接支持系统时区配置)。以下是具体方法及示例:


1. 核心思路

  1. 获取 UTC 时间戳:通过 ktime_get_real_ts64 获取当前 UTC 时间(64 位秒数)。
  2. 计算时区偏移:本地时间 = UTC 时间 + 时区偏移(例如东八区偏移为 8*3600 秒)。
  3. 转换时间格式:调用 time64_to_tm 将时间戳转换为分解时间(struct tm)。

2. 完整示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <linux/module.h>
#include <linux/time.h>
#include <linux/timekeeping.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Get local time with time64_to_tm");

static int __init example_init(void) {
struct timespec64 ts_utc;
struct tm tm_local;
int timezone_offset = 8 * 3600; // 东八区(北京时间)偏移:+8小时

// 1. 获取当前 UTC 时间
ktime_get_real_ts64(&ts_utc);

// 2. 转换为本地时间(UTC时间 + 时区偏移)
time64_to_tm(ts_utc.tv_sec + timezone_offset, 0, &tm_local);

// 3. 打印本地时间(注意年份和月份的修正)
printk(KERN_INFO "Local Time: %04d-%02d-%02d %02d:%02d:%02d\n",
tm_local.tm_year + 1900, // 年份需要 +1900
tm_local.tm_mon + 1, // 月份从 0 开始,需要 +1
tm_local.tm_mday,
tm_local.tm_hour,
tm_local.tm_min,
tm_local.tm_sec);

return 0;
}

static void __exit example_exit(void) {
printk(KERN_INFO "Module unloaded\n");
}

module_init(example_init);
module_exit(example_exit);

3. 编译与验证

Makefile 示例:

1
2
3
4
5
6
7
obj-m += local_time.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译模块
make

# 加载模块(需root权限)
sudo insmod local_time.ko

# 查看输出(dmesg)
dmesg | tail -n 1

# 示例输出:
# [ 1234.567890] Local Time: 2023-10-06 10:30:45

# 卸载模块
sudo rmmod local_time

4. 关键细节说明

(1) 时区偏移计算

  • 公式本地时间 = UTC时间 + 时区偏移
  • 示例
    • 东八区(北京):8小时 = 8*3600秒
    • 西五区(纽约):-5小时 = -5*3600秒

(2) 夏令时(DST)问题

  • 内核不自动处理夏令时,若需支持,需手动调整偏移量(例如东八区夏令时偏移为 +9*3600)。
  • 实际应用中,硬编码偏移量可能不灵活,需根据业务需求处理。

(3) 时区偏移来源

  • 硬编码:简单但不可动态调整(如示例中的 timezone_offset = 8*3600)。
  • 内核参数传递:通过模块参数动态设置偏移量:
    1
    2
    3
    static int timezone_offset = 8*3600;
    module_param(timezone_offset, int, 0644);
    MODULE_PARM_DESC(timezone_offset, "Timezone offset in seconds (e.g., 28800 for UTC+8)");
    加载模块时指定:
    1
    sudo insmod local_time.ko timezone_offset=28800

5. 对比用户空间行为

操作 用户空间 内核空间
获取时间 time() / gettimeofday() ktime_get_real_ts64()
转换本地时间 localtime()(自动读时区) time64_to_tm() + 手动偏移
时区配置 /etc/localtimeTZ 变量 需硬编码或动态传递偏移量

6. 扩展应用:动态获取系统时区

若需在内核中动态获取系统时区偏移,需结合用户空间协作(需 root 权限):

1
2
3
4
5
6
7
8
9
10
11
// 示例:通过系统调用或 procfs 获取用户空间时区偏移
// (此处为伪代码,实际需设计内核与用户空间的通信机制)
static int get_system_timezone_offset(void) {
// 1. 用户空间程序读取系统时区偏移(如通过 `date +%z`)
// 2. 将偏移量传递给内核模块(通过 netlink、sysfs 等)
// 3. 返回偏移量(秒)
return 8*3600; // 示例值
}

// 在模块初始化中使用:
timezone_offset = get_system_timezone_offset();

7. 总结

  • 核心步骤:获取 UTC 时间 → 添加时区偏移 → 调用 time64_to_tm
  • 适用场景:内核日志时间戳、驱动程序时间记录等。
  • 局限性:时区偏移需手动维护,无法直接响应系统时区变化。

Linux下获取时区接口

在 Linux 系统中,获取时区信息可以通过多种接口实现,具体分为 用户空间内核空间 两类场景。以下是详细的接口说明及示例:


一、用户空间获取时区

1. 使用 localtimetzset 函数

通过标准库函数读取系统时区配置,依赖环境变量 TZ 或系统默认时区文件 /etc/localtime

示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main() {
time_t t = time(NULL);
struct tm *local = localtime(&t);

// 获取时区名称和偏移(需调用 tzset 初始化)
tzset(); // 初始化时区信息

printf("Timezone Name: %s\n", tzname[0]); // 时区缩写(如 CST)
printf("UTC Offset: %ld hours\n", timezone / 3600); // 秒转为小时(注意符号)
printf("Local Time: %s", asctime(local)); // 本地时间字符串

return 0;
}
输出示例:
1
2
3
Timezone Name: CST
UTC Offset: -8 hours
Local Time: Wed Oct 6 15:30:45 2023
关键点:
  • tzname[0]: 时区名称(非夏令时)。
  • timezone: 时区偏移(秒),UTC 时间 = 本地时间 + timezone(东区为负值,西区为正值)。
  • daylight: 是否启用夏令时(0 或 1)。

2. 直接读取 /etc/localtime 文件

系统时区通常通过符号链接 /etc/localtime 指向 /usr/share/zoneinfo/ 中的时区文件。

示例命令:
1
2
3
4
5
# 查看当前时区文件路径
ls -l /etc/localtime

# 示例输出:
lrwxrwxrwx 1 root root 33 Oct 1 12:00 /etc/localtime -> /usr/share/zoneinfo/Asia/Shanghai
解析时区名称:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

int main() {
char tzpath[PATH_MAX];
ssize_t len = readlink("/etc/localtime", tzpath, sizeof(tzpath)-1);
if (len != -1) {
tzpath[len] = '\0';
printf("Timezone File: %s\n", tzpath); // 如 /usr/share/zoneinfo/Asia/Shanghai
}
return 0;
}

3. 使用 gettimeofday(已废弃时区参数)

尽管 gettimeofdaystruct timezone 参数已过时,仍可通过它获取粗略的时区偏移:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/time.h>
#include <stdio.h>

int main() {
struct timeval tv;
struct timezone tz;

gettimeofday(&tv, &tz);
printf("Timezone Offset: UTC%d%02d\n",
-tz.tz_minuteswest / 60, // 小时偏移(西区正值)
tz.tz_minuteswest % 60); // 分钟偏移
return 0;
}
输出示例:
1
Timezone Offset: UTC+08:00

二、内核空间获取时区

Linux 内核默认 不直接管理时区,系统时间以 UTC 存储。若需在内核模块中获取时区信息,需通过以下方法:

1. 硬编码时区偏移

在模块中手动指定时区偏移(如东八区):

1
2
3
4
5
6
7
8
#include <linux/time.h>

static int timezone_offset = 8 * 3600; // 东八区:+8 小时

// 使用示例
struct timespec64 ts_utc;
ktime_get_real_ts64(&ts_utc);
time64_t local_sec = ts_utc.tv_sec + timezone_offset;

2. 通过用户空间传递时区

通过内核与用户空间通信(如 sysfs、netlink 或模块参数)动态获取时区偏移。

示例:模块参数传递时区
1
2
3
4
5
6
7
8
#include <linux/module.h>

static int timezone_offset = 0;
module_param(timezone_offset, int, 0644);
MODULE_PARM_DESC(timezone_offset, "Timezone offset in seconds (e.g., 28800 for UTC+8)");

// 加载模块时指定:
// sudo insmod mymodule.ko timezone_offset=28800

3. 读取 RTC 时间(可能包含时区)

部分系统将 RTC 时间设置为本地时间(而非 UTC),可通过 RTC 驱动接口获取:

1
2
3
4
5
6
7
8
9
10
#include <linux/rtc.h>

struct rtc_time tm;
struct rtc_device *rtc = rtc_class_open("rtc0");
rtc_read_time(rtc, &tm);
rtc_class_close(rtc);

printk("RTC Time: %04d-%02d-%02d %02d:%02d:%02d\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);

三、动态获取系统时区(高级)

1. 解析 /proc/timedate 信息

通过 /proc/timedate 文件读取时区名称(需内核支持):

1
2
3
cat /proc/timedate
# 输出示例:
Timezone: Asia/Shanghai (CST, +0800)

2. 使用 Glibc 扩展函数 gettimeofday

通过 struct timezone 获取时区偏移(注意:非跨平台兼容):

1
2
3
4
5
6
7
#include <sys/time.h>

void print_timezone() {
struct timezone tz;
gettimeofday(NULL, &tz);
printf("Timezone Offset: %d minutes west of UTC\n", tz.tz_minuteswest);
}

四、总结

场景 方法 特点
用户空间 localtime + tzset 依赖系统时区配置,支持动态调整
读取 /etc/localtime 符号链接 直接获取时区文件路径
gettimeofday(时区参数) 已废弃,但可获取粗略偏移
内核空间 硬编码时区偏移 简单但不灵活
通过模块参数传递 动态配置,依赖用户输入
读取 RTC 时间(若设置为本地时间) 依赖硬件配置

注意事项

  1. 时区与夏令时

    • 用户空间可通过 tzname[1] 获取夏令时时区名称(如 CDT),但内核无内置支持。
    • 夏令时偏移需手动处理。
  2. 内核限制

    • 内核不直接管理时区,需依赖外部输入或硬编码。
    • 修改系统时区需通过用户空间工具(如 timedatectl)。
  3. 跨平台兼容性

    • 避免依赖 /proc/sys 中的非标准文件。
    • 优先使用标准库函数(如 localtime_r)。

通过上述接口,开发者可以在不同场景下灵活获取和处理时区信息。