少儿英语做游戏网站推荐网上营销网站
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 2018date 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依然是从启动开始的实际间隔。