linux 时间系统 一 时间相关的系统调用

版权声明:本文为博主原创文章,未经博主允许不得转载。 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依然是从启动开始的实际间隔。

猜你喜欢

转载自blog.csdn.net/dongkun152/article/details/86677621
今日推荐