版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dongkun152/article/details/86677621
linux 时间系统 一 时间相关的系统调用
时间相关的系统调用,这里主要说明的是用来记录时间(打时间戳)和delay时间的系统调用。它们是linux时间系统的一部分。 时间相关的操作在应用层和内核层都很重要。下面的代码基于linux-4.9内核, ARCH=mips
首先是两个比较重要的系统调用:
gettimeofday
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz)
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
gettimeofday系统调用是用内核vsyscall实现的。查看进程的maps, vsyscall在vdso(virtual dynamic shared object)区域。vdso区域是进程启动时内核向进程映射一段空间,这样做是为了减少某些频繁调用的系统调用的开销。
int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
{
const union mips_vdso_data *data = get_vdso_data();
struct timespec ts;
int ret;
ret = do_realtime(&ts, data);
if (ret)
return ret;
if (tv) {
tv->tv_sec = ts.tv_sec;
tv->tv_usec = ts.tv_nsec / 1000;
}
if (tz) {
tz->tz_minuteswest = data->tz_minuteswest;
tz->tz_dsttime = data->tz_dsttime;
}
return 0;
}
/* do realtime 直接读取导出的realtime时间*/
static __always_inline int do_realtime(struct timespec *ts,
const union mips_vdso_data *data)
{
u32 start_seq;
u64 ns;
do {
start_seq = vdso_data_read_begin(data);
if (data->clock_mode == VDSO_CLOCK_NONE)
return -ENOSYS;
ts->tv_sec = data->xtime_sec;
ns = get_ns(data);
} while (vdso_data_read_retry(data, start_seq));
ts->tv_nsec = 0;
timespec_add_ns(ts, ns);
return 0;
}
下面是vdso导出的数据区域
union mips_vdso_data {
struct {
/*timekeeper 维护的realtime时间*/
u64 xtime_sec;
u64 xtime_nsec;
/*从realtime时间向monotonic时间的偏移*/
u32 wall_to_mono_sec;
u32 wall_to_mono_nsec;
u32 seq_count;
u32 cs_shift;
u8 clock_mode;
u32 cs_mult;
u64 cs_cycle_last;
u64 cs_mask;
s32 tz_minuteswest;
s32 tz_dsttime;
};
u8 page[PAGE_SIZE];
};
clock_gettime
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespec *tp);
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
clockid 用来指定选择哪一个clock。
内核维护两个clock
- CLOCK_REALTIME(wall clock): 墙上时间,记录从1970-01-01 00:00:00时刻开始的时间,当进行时间同步操作的时候会被修改。当没有进行时间同步时,跟CLOCK_MONOTONIC相同
- CLOCK_MONOTONIC: 单调递增的时间,不受修改系统时间的影响。
int __vdso_clock_gettime(clockid_t clkid, struct timespec *ts)
{
const union mips_vdso_data *data = get_vdso_data();
int ret;
switch (clkid) {
case CLOCK_REALTIME_COARSE:
ret = do_realtime_coarse(ts, data);
break;
case CLOCK_MONOTONIC_COARSE:
ret = do_monotonic_coarse(ts, data);
break;
case CLOCK_REALTIME:
ret = do_realtime(ts, data);
break;
case CLOCK_MONOTONIC:
ret = do_monotonic(ts, data);
break;
default:
ret = -ENOSYS;
break;
}
/* If we return -ENOSYS libc should fall back to a syscall. */
return ret;
}
从代码中可以看出,当id是CLOCK_REALTIME时,进行的动作与gettimeofday相同。当id是CLOCK_MONOTONIC时,执行do_monotonic。
static __always_inline int do_monotonic(struct timespec *ts,
const union mips_vdso_data *data)
{
u32 start_seq;
u64 ns;
u32 to_mono_sec;
u32 to_mono_nsec;
do {
start_seq = vdso_data_read_begin(data);
if (data->clock_mode == VDSO_CLOCK_NONE)
return -ENOSYS;
ts->tv_sec = data->xtime_sec;
ns = get_ns(data);
to_mono_sec = data->wall_to_mono_sec;
to_mono_nsec = data->wall_to_mono_nsec;
} while (vdso_data_read_retry(data, start_seq));
ts->tv_sec += to_mono_sec;
ts->tv_nsec = 0;
timespec_add_ns(ts, ns + to_mono_nsec);
return 0;
}
do_monotonic同样是直接获取系统维护的时间xtime_sec, 但是后面要用wall_to_mono_*进行修正。
下面的例子来说明两个的区别:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
const char *command = "date -s \"2018-10-24 09:00:00\"";
int main(int argc, char *argv[])
{
struct timeval time_now;
struct timespec time_test;
struct timeval time_change;
char buffer[64] = {0};
gettimeofday(&time_now, NULL);
strftime(buffer, 64, "Current date/time(1): %m-%d-%Y/%T", localtime(&time_now.tv_sec));
printf("%s\n", buffer);
printf("realtime:\n");
clock_gettime(CLOCK_REALTIME, &time_test);
time_change.tv_sec = time_test.tv_sec;
time_change.tv_usec = time_test.tv_nsec / 1000;
strftime(buffer, 64, "Current date/time(2): %m-%d-%Y/%T", localtime(&time_change.tv_sec));
printf("%s\n", buffer);
printf("monotonic time:\n");
clock_gettime(CLOCK_MONOTONIC, &time_test);
time_change.tv_sec = time_test.tv_sec;
time_change.tv_usec = time_test.tv_nsec / 1000;
strftime(buffer, 64, "Current date/time(3): %m-%d-%Y/%T", localtime(&time_change.tv_sec));
printf("%s\n", buffer);
system(command);
printf("\ndate change time %s:\n\n", command);
printf("time now:\n");
gettimeofday(&time_now, NULL);
strftime(buffer, 64, "Current date/time(1): %m-%d-%Y/%T", localtime(&time_now.tv_sec));
printf("%s\n", buffer);
printf("realtime:\n");
clock_gettime(CLOCK_REALTIME, &time_test);
time_change.tv_sec = time_test.tv_sec;
time_change.tv_usec = time_test.tv_nsec / 1000;
strftime(buffer, 64, "Current date/time(2): %m-%d-%Y/%T", localtime(&time_change.tv_sec));
printf("%s\n", buffer);
printf("monotonic time:\n");
clock_gettime(CLOCK_MONOTONIC, &time_test);
time_change.tv_sec = time_test.tv_sec;
time_change.tv_usec = time_test.tv_nsec / 1000;
strftime(buffer, 64, "Current date/time(3): %m-%d-%Y/%T", localtime(&time_change.tv_sec));
printf("%s\n", buffer);
return 0;
}
下边是系统刚启动一小段时间的运行结果:
/mnt # ./date_test
Current date/time(1): 01-01-1970/00:46:51
realtime:
Current date/time(2): 01-01-1970/00:46:51
monotonic time:
Current date/time(3): 01-01-1970/00:46:51
Wed Oct 24 09:00:00 UTC 2018
date change time date -s "2018-10-24 09:00:00":
time now:
Current date/time(1): 10-24-2018/09:00:00
realtime:
Current date/time(2): 10-24-2018/09:00:00
monotonic time:
Current date/time(3): 01-01-1970/00:46:51
修改系统时间之后,用monotonic time 转化出来的localtime依然是从启动开始的实际间隔。