Linux时间类型、函数和休眠函数

        转载请注明出处: http://blog.csdn.net/luotuo44/article/details/39374759

本文主要涉及Linux时间类型、时间函数以及Linux提供的睡眠函数。

时间类型和对应的函数:

time_t:

最不陌生的时间类型恐怕是time_t这个类型了吧。它出现在C语言的标准库。但ISO C中并没有规定time_t是什么类型、范围以及精度,不过在POSIX中一般是被实现为有符号的整型。

time_t的单位是秒。函数time()的返回值就是一个time_t类型,表示从1970-01-01 00:00:00 UTC (即Epoch时间)到现在有多少秒。要注意的是,该函数返回的是一个UTC时间,不是我们平常使用的北京时间。可以用函数localtime把这个UTC时间转换成当地时间。我们可以用这个函数把UTC时间转换成北京时间。localtime的返回值是一个struct  tm结构体指针。定义如下:

struct tm {
	int tm_sec;    /* 秒 [0-60] 有闰秒*/
	int tm_min;    /* 分钟 [0-59] */
	int tm_hour;   /* 小时 [0-23) */
	int tm_mday;   /* 日 [1-31) */
	int tm_mon;    /*月 [0-11) */
	int tm_year;   /* 年 其值等于所在年份- 1900 */
	int tm_wday;   /* 星期 ]0-6] 星期天为0 */
	int tm_yday;   /* 今天是今年的第几天 [0-365]  1月1日是第0天*/
	int tm_isdst;  /* 夏令时标志位*/
};

注意各个成员的范围,不要搞错了。此外,对于秒,是有闰秒的。所以范围是[0, 60]。

可以看到,相对于一个time_t,这个struct  tm结构体的成员还是很有用的。因为一个给定time_t,人们根本就不知道它表示的是哪个时间,但转换成struct  tm结构体后就能一眼看出是什么时间。

如果并不想把时间转换成本地时间,而仅仅转换成struct tm,那么可以使用函数gmtime,它同样是返回一个struct tm指针。因为localtime和gmtime都是返回一个指针,所以它们都不是线程安全的。对于地,有localtime_r和gmtime_r。这个两个函数是线程安全的。如果像把一个struct tm时间转换成time_t可以使用mktime函数。有时也想把用这个时间类型转换成一个字符串方便输出,标准C语言也提供了这些函数。下面给出这些函数的声明。

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);

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);

一般来说,这些函数都是在time.h头文件中。但对于线程安全的那几个函数,有些编译器还要另外#include<pthread.h>。

clock_t:

它的实际类型是有符号整型,它一般是用来表示一个进程占用的CPU时间。函数clock()返回就一个clock_t类型值,表示进程从启动到调用clock函数,使用了多少CPU时间。将返回值除以CLOCKS_PER_SEC,就能得到以秒为单位的时间。clock函数声明在time.h头文件中。

struct timeval:

相对来说,time_t的粒度还是比较大,单位是秒。

Linux还提供了下面另外一个精确度高一些类型strut timeval。

struct timeval {
	time_t      tv_sec;     /* seconds */
	suseconds_t tv_usec;    /* microseconds 微秒*/
};

成员tv_usec表示的是微秒,1秒 = 1 000毫秒 = 1 000 000微秒。

一般是在函数gettimeofday函数中使用到这个时间类型。

#include<syt/time.h>
	int gettimeofday(struct timeval *tv, struct timezone *tz);//第二个参数一般为NULL

像time函数语言,它也返回一个从1970-01-01 00:00:00 UTC(即Epoch时间)到现在流逝的时间,当然这个时间也就是系统时间,为UTC时间不是当地时间。也可以把struct timeval当作现在的时间,将成员tv_sec用上面的localtime转换即可。

Linux还定义了一系列用于struct  timeval的操作函数。比如相加减,清空,比较。

#include <sys/time.h>
void timeradd(struct timeval *a, struct timeval *b,
			 struct timeval *res);//res = a + b

void timersub(struct timeval *a, struct timeval *b,
			 struct timeval *res);//res = a - b

void timerclear(struct timeval *tvp);

int timerisset(struct timeval *tvp);

int timercmp(struct timeval *a, struct timeval *b, CMP);

参数CMP就是我们平常用的比较符号<、>=、!=等等。

有一点要注意的是,因为gettimeofday获取的是系统的时间。因为用户是可以手动修改这个系统时间的。所以gettimeofday获取的时间可能是错误的时间。

如果程序想在两个不同的时刻分别获取系统,然后计算时间差。那么gettimeofday函数不是最佳选择。原因就是用户可以修改系统时间。此时应该使用monotonic时间。monotonic时间是boot启动后到现在的时间,没人能修改它。这个monotonic时间下面会讲解。

struct timespec:

struct timespec {
	time_t   tv_sec;        /* seconds */
	long     tv_nsec;       /* nanoseconds 纳秒*/
};

它能提供的时间精确度是纳秒。当然,实际上硬件是不一定支持这个精确度的。 有三个与之相关的函数。

//glibc2.17之前的版本在链接时需加上-lrt
#include <time.h>
int clock_getres(clockid_t clk_id, struct timespec *res);
int clock_gettime(clockid_t clk_id, struct timespec *tp);
int clock_settime(clockid_t clk_id, const struct timespec *tp);

可以用ls-al /lib/libc.so.6或者ldd –version命令查看glibc的版本,uname -r命令查看Linux内核版本。

参数clk_id表示时钟类型,Linux提供下面这些时钟类型。

  • CLOCK_REALTIME: 系统范围的实时时钟。系统范围是指系统的所有用户所有程序都使用。实时时钟是指系统现在的时间(就像Windows右下角的那个时间),这和gettimeofday函数获取的系统时间是相同的。系统时间是可以修改的,所以可以使用clock_settime(CLOCK_REALTIME)修改系统的时间
  • CLOCK_REALTIME_COARSE: 一个更快但时间粒度更大(即时间精确度没那么高)的CLOCK_REALTIME。这个函数是Linux系统特有的,在2.6.32内核版本中首次出现
  • CLOCK_MONOTONIC:monotonic时间。monotonic是单调的意思,也就是说它只会单调递增,不能被人手动修改。POSIX只是说明它是一个单调时间,并没有指定从什么时候开始算起的单调时间。所以有些系统取了Epoch时间,而有些系统(比如Linux)就取boot启动的时间。但也并不影响它的本身意义
  • CLOCK_MONOTONIC_COARSE:一个更快但时间粒度更大(即时间精确度没那么高)的CLOCK_MONOTONIC。这个函数是Linux系统特有的,在2.6.32内核版本中首次出现
  • CLOCK_BOOTTIME: 同CLOCK_MONOTONIC一样,只是当系统被挂起时,一样会计时(CLOCK_MONOTONIC不会)
  • CLOCK_PROCESS_CPUTIME_ID: 进程使用了多少CPU时间。该进程的所有线程所使用的CPU时间都会被统计进来
  • CLOCK_THREAD_CPUTIME_ID: 线程使用了多少CPU时间

clock_getres函数是用来获取对应时钟类型能够提供的时间精确度,res参数保存其精确度。在设置的时候,设置的时间值也应该是这个精确度的倍数。后面的休眠函数有些也会使用到上面这些时钟类型,休眠的时间也应该是精确度的倍数,否则由休眠函数截断取倍数。比如nanosleep函数。

函数clock_gettime是用来获取对应时钟的时间。函数clock_settime则是用来设置对应时钟的时间。有些时钟是不能被设置的。

休眠函数:

Linux提供了不同时间精确度的休眠函数。这些函数有些是在Linux高版本才出现的,有些是非线程安全的。下面就详细说明每一个休眠函数。

sleep:

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

从sleep的参数名称可以看到,该函数的休眠时间单位是秒。由于这个休眠函数可能会被外界的信号所打断,所以其有一个返回值,用来指明余下的休眠时间。如果没有被信号打断,而休眠了参数锁指定的时间,那么将返回0。

由于sleep函数可能是利用信号SIGALRM实现的,所以不应该在使用sleep函数的同时使用alarm函数。其实在《UNIX环境高级编程》里面,也看到书中有一个例子是怎么实现sleep函数,其中用到的方法就是alarm。

明显,如果sleep是用SIGALRM实现的话,那么就肯定不是线程安全的。

要注意的是,在Windows中提供了一个Sleep函数(S是大写的),它的时间单位是毫秒。

usleep:

#include <unistd.h>
int usleep(useconds_t usec);//微秒

usleep休眠函数的时间粒度比较小,是微秒,足够了。并且该函数是线程安全的。

当这个休眠函数不被打断地休眠了指定的时间,将返回0。如果被信号打断了,将返回-1,并且将errno设置为EINTR。如果指定的参数不小于1000000也将返回-1,它认为休眠时间太长了,此时errno被设置为EINVAL。

如果要使用这个函数,那么glibc的版本要不小于2.12。可以用命令ls -al /lib/libc.so.6查看glibc的版本。

nanosleep:

#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);//纳秒

nanosleep函数能够提供纳秒级的休眠时间。这是相当不错的。参数req指明要休眠的时间。如果成功休眠了指定的时间,将返回0。

如果休眠被一个信号给打断了,那么将返回-1,errno被赋值为EINTR。此时如果参数rem不为NULL的话,rem将被赋值成余下的休眠时间。有了rem,即使被信号打断,还是可以继续休眠,直到一开始指定的休眠时间。虽然这种方法理论上是可行,不过实际上可能出现时间漂移。比如说,你要休眠30ms,过了10ms后,被一个信号给打断了。此时rem将得到20ms的剩余值。如果CPU执行信号处理函数用了10ms,接着再休眠rem指明的时间。这样就出现时间漂移,一共休眠了40ms。可以用clock_nanosleep避免这个问题,因为它可以使用绝对时间。

req的tv_nsec 成员的值范围得是0到999999999。如果是其他值,该函数直接返回-1,errno被设置为EINVAL。

POSIX.1规定,nanosleep是使用CLOCK_REALTIME时钟来检测时间的(即检测是否超时)。然而Linux却是使用CLOCK_MONOTONIC时钟。看上去Linux的做法是明智的,因为前面已经说过用户是可以通过调用clock_settime函数修改CLOCK_REALTIME时钟的。不过,其实POSIX.1也规定了修改CLOCK_REALTIME时钟并不会影响到nanosleep函数的使用。明显要做到这一点,代码会复杂一些。使用CLOCK_MONOTONIC时钟就不用考虑这个问题。

clock_nanosleep:

//glibc2.17之前的版本在链接时需加上-lrt
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
				   const struct timespec *request,
				   struct timespec *remain);

参数clock_id指明该函数使用哪个时钟检测有没有睡够。有CLOCK_REALTIME、 CLOCK_MONOTONIC 、CLOCK_PROCESS_CPUTIME_ID三种可选时钟。上面已经对这些时间进行了讲解这里就不多说了。

参数flags指明用的是不是绝对时间。如果该值为0,则使用的是一个时间段(即休眠的时长)。如果该参数为设置为TIMER_ABSTIME,那么就是使用绝对时间。

参数request指明休眠时间。有参数flags指明这是一个时间段还是绝对时间。

参数remain的作用和前面nanosleep函数的rem参数一样。当flags指明request是一个时间段,并且本函数被信号打断时,remain将得到余下的休眠时间。这个参数可以为NULL。

如果flags指定使用绝对时间,并且request指明的时间小于或者等于当前时间,那么clock_nanosleep函数将马上返回,不会进入休眠。

当clock_nanosleep成功休眠了指定的时间,将返回0。否则返回错误值,不设置errno变量。这点和其他的休眠函数不同。有下面这些错误值。

FAULT: request或者remain指向一个非法地址(即野指针)

EINTR:本函数被信号函数打断了

EINVAL:request的tv_nsec成员的范围不是在0到999999999,即它是一个非法值。当clock_id不是去上面说到的那三个值时,也将返回EINVAL。要注意:clock_nanosleep并不支持CLOCK_THREAD_CPUTIME_ID。

该函数要注意的一些问题:因为该函数可以使用CLOCK_REALTIME时钟并且该时钟可以被手动修改。这可能会影响clock_nanosleep函数。首先,如果flags指明的是一个时间段,那么clock_nanosleep和nanosleep函数一样,没有影响。

如果用户修改了CLOCK_REALTIME时钟的值,必然会对正在利用该时钟进行睡眠的clock_nanosleep产生影响。

该函数的使用,需要Linux内核版本不小于2.6,glibc版本不小于2.1

除了上面正规的休眠函数外,还可以利用其他的函数来实现休眠。

select:

#include <sys/select.h>
#include <sys/types.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

select函数的最后一个参数就是一个时间值。当然它是一个时间段,不是绝对时间。当我们需要将select用作一个休眠函数时,直接把前面四个参数设置为0即可。

如果休眠了指定的时间,该函数将返回0。如果中途被信号打断,那么将返回-1,errno被设置为EINTR。不同于其他的休眠函数,此时并没有一个可移植的方法知道剩余的休眠时间。虽然部分的Linux会修改timeout参数,得到剩余的休眠时间。

poll:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

将前面的两个参数设为0,poll就成了一个休眠函数。参数timeout指定的休眠时间也是一个时间段,并且其时间单位是毫秒。

如果休眠了指定的时间,该函数返回0。如果中途被信号打断了,那么返回-1,errno被设置为EINTR。

休眠时间精确度:

由上面列出的一些休眠函数可以知道,Linux提供了各个时间精度的休眠函数:sleep(秒级)、poll(毫秒级)、usleep(微秒级)、nanosleep(纳秒级)。

休眠函数普遍存在的问题:

休眠函数是有一个问题,那就是不能真正准确休眠指定的时间。主要原因有二个。

  1. 系统提供的时间的精确度不够。比如,系统只能提供10ms的精确度,你却想休眠15ms。此时系统只能把真正休眠时间设置为10ms或者20ms。一般是向上取,即取20ms
  2. CPU调度问题。虽然线程的休眠时间到了,要唤醒了。但此时CPU还在忙着其他事,没有空来唤醒休眠线程。那么休眠线程真正休眠的时间也将是不准确的

参考:

http://stackoverflow.com/questions/471248/what-is-ultimately-a-time-t-typedef-to

http://man7.org/linux/man-pages/man2/clock_gettime.2.html

http://www.unix.com/man-page/POSIX/3posix/clock_settime/

http://stackoverflow.com/questions/14726401/starting-point-for-clock-monotonic?lq=1

http://man7.org/linux/man-pages/man3/usleep.3.html

http://man7.org/linux/man-pages/man3/sleep.3.html

http://man7.org/linux/man-pages/man2/nanosleep.2.html

http://man7.org/linux/man-pages/man2/clock_nanosleep.2.html

      《UNIX网络编程卷一》

时间: 2024-09-29 22:26:32

Linux时间类型、函数和休眠函数的相关文章

Linux时间函数

系统环境:ubuntu10.04 1.Linux下常用时间类型time_t.struct tm.struct timeval.struct timespec 1.1 time_t时间类型time_t类型在time.h中定义:typedef long time_t; 可见,time_t实际是一个长整型.其值表示为从UTC(coordinated universal time)时间1970年1月1日00时00分00秒(也称为Linux系统的Epoch时间)到当前时刻的秒数.由于time_t类型长度的

Linux时间结构体和获得时间函数

关于Linux下时间编程的问题: 1. Linux下与时间有关的结构体 struct timeval { int tv_sec; int tv_usec; }; 其中tv_sec是由凌晨开始算起的秒数,tv_usec则是微秒(10E-6 second). struct timezone { int tv_minuteswest; int tv_dsttime; }; tv_minuteswest是格林威治时间往西方的时差,tv_dsttime则是时间的修正方式. struct timespec

linux 时间函数

linux 时间函数: time/time_t  秒 ftime/ struct timeb 毫秒 gettimeofday/struct timeval us clock_gettime/ struct timespec ns gmtime/localtime/timegm/mktime/strftime/struct tm 其中 time 精度低, ftime 已废弃, clock_gettime 系统调用,精度高 gettimeofday  vsyscall ,系统态, 开销低 定时函数,

mysql时间类型总结及常用时间函数

日期时间和类型 常用日期和时间类型 字节 year                1       表示年份                   值范围:(1901----2155) date                4        表示年月日               例如 :2018-03-09   值范围:1000-01-01 至9999-12-31 time                3        表示时分秒                                

Linux时间时区详解与常用时间函数

时间与时区 整个地球分为二十四时区,每个时区都有自己的本地时间. Ø  UTC时间 与 GMT时间 我们可以认为格林威治时间就是时间协调时间(GMT = UTC),格林威治时间和UTC时间都用秒数来计算的. Ø  UTC时间与本地时间 UTC + 时区差 = 本地时间 时区差东为正,西为负.在此,把东八区时区差记为 +0800 UTC + (+0800) = 本地(北京)时间 Ø  UTC与Unix时间戳 在计算机中看到的UTC时间都是从(1970年01月01日 0:00:00)开始计算秒数的.

编写C语言跨平台函数(以清屏和休眠函数为例)

支持C语言的平台有许多,常见的编译器如VC.gcc.Clang等.不同的编译器共同点是都支持标准C(ANSI C),但是各自却又有自己独立的.平台相关的功能以及函数接口.这通常为程序的移植性带来很多问题.这里我简单谈一下解决方案. 常见思路 常见的解决跨平台移植的思路就是利用 宏.不同编译器有各自不同的宏,宏有很多,具体可以参考编译器的相关手册.通过判断一个宏是否存在来选择性的包含头文件或调用函数,其本质就是一种条件编译. 比如一些平台相关的函数,在不同平台要包含不同文件. #if define

Linux操作系统中的系统调用接口函数

在分析病毒样本时发现有些系统函数是必用,有些超常用,现在都列出来,希望和大家交流 转载请注明出处:http://blog.csdn.net/u010484477     O(∩_∩)O谢谢 进程控制 fork 创建一个新进程 clone 按指定条件创建子进程 execve 运行可执行文件 exit 中止进程 _exit 立即中止当前进程 getdtablesize 进程所能打开的最大文件数 getpgid 获取指定进程组标识号 setpgid 设置指定进程组标志号 getpgrp 获取当前进程组

linux编程下signal()函数

linux编程下signal()函数 当服务器close一个连接时,若client端接着发数据.根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了.根据信号的默认处理规则SIGPIPE信号的默认执行动作是 terminate(终止.退出), 所以client会退出. 若不想客户端退出可以把 SIGPIPE设为SIG_IGN 如: signal(SIGPIPE,SIG_IGN); 这时SI

【Linux程序设计】之环境系统函数综合实验

这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的.贴出来纯粹是聊胜于无. 实验题目:Linux环境下系统函数综合实验 实验目的:熟悉并掌握Linux环境下数学函数.字符函数.系统时间与日期函数.环境控制函数.内存分配函数以及数据结构中常用函数的使用方法. 一.Linux环境下数学函数的使用 设计程序,满足如下要求: 1.使用rand函数产生10个介于1到10之间的随机数值.要求在程序中对每行代码添加注释. 1 #include<stdio.h> 2 #i