Linux 时间函数

2014-06-02 Monday     linux , program

简单介绍下 Linux 中与时间相关的函数。

获取时间函数

简单介绍下与获取时间相关的系统调用。

time() 秒级

#include <time.h>

//----- time_t一般为long int,不过不同平台可能不同,打印可通过printf("%ju", (uintmax_t)ret)打印
time_t time(time_t *tloc);

char *ctime(const time_t *timep);                // 错误返回NULL
char *ctime_r(const time_t *timep, char *buf);   // buf至少26bytes,返回与buf相同

如果 tloc 不为 NULL,那么数据同时会保存在 tloc 中,成功返回从 epoch 开始的时间,单位为秒;否则返回 -1 。

ftime() 毫秒级

#include <sys/timeb.h>
struct timeb {
    time_t   time;                // 为1970-01-01至今的秒数
    unsigned   short   millitm;   // 毫秒
    short   timezonel;            // 为目前时区和Greenwich相差的时间,单位为分钟,东区为负
    short   dstflag;              // 非0代表启用夏时制
};
int ftime(struct timeb *tp);

总是返回 0 。

gettimeofday() 微秒级

gettimeofday() 函数可以获得当前系统的绝对时间。

struct timeval {
    time_t      tv_sec;
    suseconds_t tv_usec;
};
int gettimeofday ( struct timeval * tv , struct timezone * tz )

可以通过如下函数测试。

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
int main(void)
{
    int i=10000000;
    struct timeval begin, end;
    gettimeofday(&begin, NULL);
    while (--i);
    gettimeofday(&end, NULL);
    double span = end.tv_sec-begin.tv_sec + (end.tv_usec-begin.tv_usec)/1000000.0;
    printf("time: %.12f\n", span);
    return 0;
}

clock_gettime() 纳秒级

编译连接时需要加上 -lrt,不过不加也可以编译,应该是非实时的。struct timespect *tp 用来存储当前的时间,其结构和函数声明如下,返回 0 表示成功,-1 表示失败。

struct timespec {
    time_t tv_sec;    /* seconds */
    long tv_nsec;     /* nanoseconds */
};
int clock_gettime(clockid_t clk_id, struct timespect *tp);

clk_id 用于指定计时时钟的类型,简单列举如下几种,详见 man 2 clock_gettime

  • CLOCK_REALTIME/CLOCK_REALTIME_COARSE
    系统实时时间 (wall-clock time),即从 UTC 1970.01.01 00:00:00 开始计时,随系统实时时间改变而改变,包括通过系统函数手动调整系统时间,或者通过 adjtime() 或者 NTP 调整时间。

  • CLOCK_MONOTONIC/CLOCK_MONOTONIC_COARSE
    从系统启动开始计时,不受系统时间被用户改变的影响,但会受像 adjtime() 或者 NTP 之类渐进调整的影响。

  • CLOCK_MONOTONIC_RAW
    与上述的 CLOCK_MONOTONIC 相同,只是不会受 adjtime() 以及 NTP 的影响。

  • CLOCK_PROCESS_CPUTIME_ID
    本进程到当前代码系统 CPU 花费的时间。

  • CLOCK_THREAD_CPUTIME_ID
    本线程到当前代码系统 CPU 花费的时间。

可以通过如下程序测试。

#include <time.h>
#include <stdio.h>

struct timespec diff(struct timespec start, struct timespec end)
{
    struct timespec temp;
    if ((end.tv_nsec-start.tv_nsec)<0) {
        temp.tv_sec = end.tv_sec-start.tv_sec-1;
        temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
    } else {
        temp.tv_sec = end.tv_sec-start.tv_sec;
        temp.tv_nsec = end.tv_nsec-start.tv_nsec;
    }
    return temp;
}

int main(void)
{
    struct timespec t, begin, end;
    int temp, i;
    clock_gettime(CLOCK_REALTIME, &t);
    printf("CLOCK_REALTIME: %d, %d\n", t.tv_sec, t.tv_nsec);
    clock_gettime(CLOCK_MONOTONIC, &t);
    printf("CLOCK_MONOTONIC: %d, %d\n", t.tv_sec, t.tv_nsec);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t);
    printf("CLOCK_THREAD_CPUTIME_ID: %d, %d\n", t.tv_sec, t.tv_nsec);

    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &begin);
    printf("CLOCK_PROCESS_CPUTIME_ID: %d, %d\n", begin.tv_sec, begin.tv_nsec);
    for (i = 0; i < 242000000; i++)
        temp+=temp;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
    t = diff(begin, end);
    printf("diff CLOCK_PROCESS_CPUTIME_ID: %d, %d\n", t.tv_sec, t.tv_nsec);
}

时间转换相关

将时间转换为自 1970.01.01 以来逝去时间的秒数,发生错误时返回 -1 。

struct tm {
	int tm_sec;         /* seconds [0, 59] */
	int tm_min;         /* minutes [0, 59] */
	int tm_hour;        /* hours [0, 23] */
	int tm_mday;        /* day of the month [1, 31] */
	int tm_mon;         /* month [0, 11] */
	int tm_year;        /* year now.year - 1900 */
	int tm_wday;        /* day of the week, [0, 6] 0->sunday */
	int tm_yday;        /* day in the year [0, 365] */
	int tm_isdst;       /* daylight saving time */
};

time_t mktime(struct tm *timeptr);

如果时间在夏令时,tm_isdst 设置为 1 ,否则设置为 0 ,若未知,则设置为 -1 。

字符串转换

其中 strftime() 用来格式化日期、日期时间和时间的函数,支持 date、datetime、time 等类,将其转换为字符串;而 strptime() 就是从字符串表示的日期时间按格式化字符串要求转换为相应的日期时间。

示例如下:

#define _XOPEN_SOURCE

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	struct tm tm;
	char buffer[256], *rcs;
	int rc;

	memset(&tm, 0, sizeof(struct tm));
	rcs = strptime("2001-11-12 18:31:01", "%Y-%m-%d %H:%M:%S", &tm);
	if (rcs == NULL) { /* such as: 20010-10-12 */
		printf("Format error\n");
		return -1;
	}

	rc = strftime(buffer, sizeof(buffer), "%4-%m-%d %H:%M:%S (%Z %z)", &tm);
	if (rc == 0) { /* Not enough buffer, */
		buffer[sizeof(buffer) - 1] = 0;
		printf("Got zero\n");
		return -1;
	}

	puts(buffer);

	return 0;
}

其中 strftime() 会按照格式将上述内容格式输出,如果不满足则原样复制,而且格式化字符基本已经占满所有 26 个字符。

gettimeofday() 效率

很多时候需要获取当前时间,如计算 http 耗时,数据库事务 ID 等,那么 gettimeofday() 这个函数做了些什么?内核 1ms 一次的时钟中断可以支持微秒精度吗?如果在系统繁忙时,频繁的调用它是否有问题吗?

gettimeofday() 是 C 库提供的函数,它封装了内核里的 sys_gettimeofday() 系统调用。

在 x86_64 体系上,使用 vsyscall 实现了 gettimeofday() 这个系统调用,简单来说,就是创建了一个共享的内存页面,它的数据由内核来维护,但是,用户态也有权限访问这个内核页面,由此,不通过中断 gettimeofday() 也就拿到了系统时间。

函数作用

gettimeofday() 会把内核保存的墙上时间和 jiffies 综合处理后返回给用户。先看看 gettimeofday() 是如何做的,首先它调用了 sys_gettimeofday() 系统调用。

asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
{
    if (likely(tv != NULL)) {
        struct timeval ktv;
        do_gettimeofday(&ktv);
        if (copy_to_user(tv, &ktv, sizeof(ktv)))
            return -EFAULT;
    }
    if (unlikely(tz != NULL)) {
        if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
            return -EFAULT;
    }
    return 0;
}

调用 do_gettimeofday() 取得当前时间存储到变量 ktv 上,并调用 copy_to_user() 复制到用户空间,每个体系都有自己的实现,这里就简单看下 x86_64 体系下 do_gettimeofday() 的实现:

void do_gettimeofday(struct timeval *tv)
{
    unsigned long seq, t;
    unsigned int sec, usec;

    do {
        seq = read_seqbegin(&xtime_lock);

        sec = xtime.tv_sec;
        usec = xtime.tv_nsec / 1000;

        /* i386 does some correction here to keep the clock
           monotonous even when ntpd is fixing drift.
           But they didn't work for me, there is a non monotonic
           clock anyways with ntp.
           I dropped all corrections now until a real solution can
           be found. Note when you fix it here you need to do the same
           in arch/x86_64/kernel/vsyscall.c and export all needed
           variables in vmlinux.lds. -AK */

        t = (jiffies - wall_jiffies) * (1000000L / HZ) +
            do_gettimeoffset();
        usec += t;

    } while (read_seqretry(&xtime_lock, seq));

    tv->tv_sec = sec + usec / 1000000;
    tv->tv_usec = usec % 1000000;
}

可以看到,该函数只是把 xtimejiffies 修正后返回给用户,而 xtime 变量和 jiffies 的维护更新频率,就决定了时间精度,而 jiffies 一般每 10ms 或者 1ms 才处理一次时钟中断,那么这是不是意味着精度只到 1ms ?

微秒级精度

获取时间是通过 High Precision Event Timer 维护,这个模块会提供微秒级的中断,并更新 xtime 和 jiffies 变量;接着,看下 x86_64 体系结构下的维护代码:

static struct irqaction irq0 = {
    timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL
};

这个 timer_interrupt() 函数会处理 HPET 时间中断,来更新 xtime 变量。

总结

//----- 将时间格式转为字符串,不修改时区,使用标准格式
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

//----- 转换为本地时间,同样使用标准格式
char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

//----- 将时间戳转换为GMT时区的标准时间
struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

//----- 将时间戳转换为本地时区的时间格式
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

time_t mktime(struct tm *tm);
double difftime(time_t time1, time_t time0);

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


如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^


About This Blog

Recent Posts

Categories

Related Links

  • RTEMS
    RTEMS
  • GNU
  • Linux Kernel
  • Arduino

Search


This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2018 – Jin Yang