《操作系统实践-基于Linux应用与内核编程》第五章-时间测量与计时

前言:

内容参考《操作系统实践-基于Linux应用与内核编程》一书的示例代码和教材内容,所做的读书笔记。本文记录再这里按照书中示例做一遍代码编程实践加深对操作系统的理解。

引用:

《操作系统实践-基于Linux应用与内核编程》

作者:房胜、李旭健、黄玲、李哲

出版社:清华大学出版社

资源:

 教材资源

链接: https://caiyun.139.com/m/i?1A5Ch36dl1whD  提取码:jdQe

课件和电子资料源码

链接: https://caiyun.139.com/m/i?1A5CvEKY07Uzs  提取码:xyv0

正文:

1. 第五章-Linux系统中的时间

Linux系统中时间表示,在Linux中可以使用命令Date来查看时间日期。

使用命令‘date’显示的是本地时间其中的 CST 表示中国标注时间。,使用命令'data -u'可以显示世界协调时间,UTC表示世界协调时间。

root@FM104-TW:/tmp # date
Fri Feb 23 11:15:41 CST 2024
root@FM104-TW:/tmp # date -u
Fri Feb 23 03:17:14 UTC 2024
root@FM104-TW:/tmp #

2. 第五章-Linux系统中的时间函数

GNU/Linux提供了三个标准的API来获取系统时间,分别是time(),gettimeofday(),clock_gettime(),它们的区别仅在于获取的时间精度不同,可以根据需要选取合适的调用。

时间函数的具体细节如下表

函数定义含义返回值精度
time()获取自1970年1月1日0时到当前的秒数,存储在time_t结构中time_t
gettimeofday()返回自1970年1月1日0时到当前的时间,用struct timeval timeval数据结构表示struct timeval微秒
clock_gettime()返回自1970年1月1日0时到当前的时间,用struct timespec 数据结构表示。支持不广泛,属于实时扩展struct timespec纳秒
ftime()返回自1970年1月1日0时到当前的时间,用struct timeb表示。已经过时,被time()替换,不建议使用。struct timeb毫秒

最早出现UNIX操作系统考虑到计算机生产的年代和应用时限,取1970年1月1日作为UNIX Time的纪元时间。在Linux操作系统初期,time_t类型以32位整数表示,所以最大支持到2038年1月1日,称为UNIX 2038 Bug。在Linux内核3.13版本中,time_t类型扩展到64位整数表示。

使用API time()获取到的时间time_t使用自1970年1月1日0时到当前的秒数来表示的,是一个很大的整数并不方便人类阅读,人类更习惯的还是类似“2024年2月23日11点25分32秒”这种日期时间表示格式。因此在Linux系统得到秒,毫秒,微秒,纳秒等点前时间之后,还需要将这些数字转换为人类熟悉的时间表示。

如下图所示,为了方便使用,Linux提供了标准的API time()/gettimeofda()从内核获取当前时间之后,当前时间可以被两大类函数转换为固定格式的时间字符串和用户指定的时间字符串。

3. 第五章-利用程序显示时间

相关函数调用

a)time()提供秒级时间精度

函数原型: time_t time(time_t *timer)

头文件: time.h

b)gettimeofday()提供微秒级时间精度

函数原型:int gettimeofday(struct timeval *tv, struct timezone *tz);

头文件: time.h

功能:获取系统时间,以tv锁指的结构返回,当地时区信息则妨碍tz所指结构中(可用NULL),成功返回0,否则返回-1。在linux内核3.13.10中timeval结构和timezone结构定义在inlcude\uapi\linux\time.h头文件,具体如下

struct timeval {
  __kernel_time_t    tv_sec;         /* sceond */
  __kernel_suseconds_t tv_usec;      /* micor second */
}

struct timezone {
  int     tz_minuteswest;            /*minutes west of Greewich */
  int     tz_dsttime;                /* type of dst correction */
}

获取时间之后,就可以以某种方式显示时间了。若要火哥固定格式的时间字符串,可以利用ctime()函数转换时间格式。ctime()函数声明为:

char *ctime(const time_t *clock)

该函数将秒数转化为字符串,样式为"Thu Dec 15 16:09:48 2014",这个字符串的长度和显示格式是固定的。

示例程序

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

int main(int argc, char *argv[])
{
    time_t time_raw;

    time(&time_raw);
    printf("Current the local time is [%ld]\n", (long)time_raw);

    //Convert to fix-format string 
    printf("Currnet the local time is %s\n", ctime(&time_raw));
    
    return 0;
}


##保存上面的源码到 ctime.c 文件
##编译
gcc -o ctime ctime.c

##运行
./ctime

##结果

4. 第五章-高级显示时间格式

ctime()显示时间字符串的长度和格式是固定的。如果想自定义显示时间格式可以使用函数gmtime()和localtime()。

a)localtime()函数,将time_t表示的时间转换为struct tm表示的本地时区时间格式

原型: struct tm *localtime(const time_t *timep)

头文件: time.h

b)gmtime()函数,将time_t表示的时间转换为struct tm表示的GMT时区时间格式,并以struct tm*返回

原型: struct tm *gmtime(const time_t *timep)

头文件:time.h

c)strftime()函数,将struct tm表示的时间转换为自定义格式字符串时间

原型:size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

头文件: time.h

struct tm 是表示有年,月,日,小时,分钟,秒的时间结构体,请定义在

struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minustes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_ond;    /* Month (0-11) */
    int tm_year;   /* Year -1900 */
    int tm_wday;   /* Day of the week (0-6, suday=0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
}

4. 第五章-计时器

除了测量时间之外,有时也需要做一些任务。一种简单的方法是while循环加sleep函数,比如间隔一分钟更新一次网络连接状态等,这可以满足很多程序的定时需要,但是这样在sleep是程序会一直处于空闲状态。为了使程序在空闲众泰是能够做些其它有用的工作,就必须使用定时器了。Linux上最常用的定时器是setitimerji计时器。

Linux为每一个进程(程序)提供了3个setitimer间隔定时器。(interval timer)

ITIMER_REAL: 减少实际时间,到期的是否发出SIGALARM信号。

ITIMER_VIRTUAL:减少有效时间(减去进程执行的时间),到期的时候发出SIGVTALARM信号。

ITIMER_PROF: 减少进程的有效时间和系统时间(为进程调度用时间)。这个经常和上面一个联合使用计算系统内核时间和用户时间,产生SIGPROF信号。

REAL时间是指人类感受到的时间,即通常所说的时间,比如现在是上午11点19分,那么一份中之后的REAL时间就是上午11点20分。

VIRTUAL时间是进程执行的时间,Linux作为一个多用户,多任务操作系统,在过去的一分钟内,指定进程实际在CPU上执行的时间往往并没有1分钟,因为其他进程也会被Liunx调度执行,在哪些时间内,虽然自然时间在流逝,但指定进程并没有真正运行。VIRTUAL时间就是指定进程真正的有效执行时间。比如11点19分开始的1分钟内,进程P1被Linux调度执行并占用CPU的执行时间为30秒,那么VIRTUAL时间对进程P1来讲就是30秒。此时自然时间已经到了11点20分,但从进程P1的眼中看来,时间只过了30秒。

PROF时间比较独特,对进程P1来说从5点10分开始的1分钟内,虽然自己的执行时间为30秒,但实际上还有10秒钟内核是在执行P1发起的系统调用,那么这10秒时间也要被加入到PROF时间。这种时间定义主要用于全面衡量进程的性能,因为在统计程序的性能时候,10秒的系统调用时间也应该算到P1的头上。

setitimer计时器的API主要用两个,即getitimer和setitimer。getitimer函数用于得到间隔计时器的时间值存放在value中,settimer函数用于设置间隔计时器的时间值为newval,并将旧值保存在oldval中,参数which用户表示选择拿一个计时器,其定义描述如下。

int getitimer(int which, struct itimerval *value);
int setitimer(int which, struct itimerval *newval, struct itimerval *oldval);

其中,itimerval结构体定义在 include/uapi/linux/time.h中,结构体主要包含两个成员,it_value是第一次调用后出发定时器的时间,当这个值递减为0时,系统会向进程发送相应信号。此后将以 it_interval为周期定时出发定时器。

struct itimerval {
    struct timeval     it_interval;     /* timer interval */
    struct timeval     it_value;        /* current value */
}

下面以一个例子说明计时器的使用,要求没两秒打印一个固定的字符串,源码可以在《操作系统实践-基于Linux应用与内核编程》附带的电子资源中找到。

4.1 实验3 使用ITIMER_REAL计时器

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

void sighandle(int signo)
{
	struct timeval tm;
	gettimeofday(&tm, NULL);
	
	switch(signo){
		case SIGALRM:
			printf("SIGALRM ");
			break;
		case SIGPROF:
			printf("SIGPROF ");
			break;
		case SIGVTALRM:
			printf("SIGVTALRM ");
			break;
	}

	printf("get signal:%d , current time:%d.%d\n", signo, tm.tv_sec, tm.tv_usec/1000);
}

int main(int argc, char *argv[])
{
	struct itimerval new_value;
	
	signal(SIGALRM, sighandle);
	signal(SIGPROF, sighandle);
	signal(SIGVTALRM, sighandle);
	
	
	new_value.it_value.tv_sec = 1;
	new_value.it_value.tv_usec = 0;
	
	new_value.it_interval.tv_sec = 1;
	new_value.it_interval.tv_usec = 0;

	if(setitimer(ITIMER_REAL, &new_value, NULL) < 0)
	{
		printf("setitimer failed:%d\n", errno);
		exit(1);
	}
	
	while(1);
	
	return 0;
}

执行结果,从执行结果看setitimer()使用ITIMER_REAL统计实际的时间,打印出来的时间戳是准确的1000ms和我们设置的一样。

SIGALRM get signal:14 , current time:1709003871.859
SIGALRM get signal:14 , current time:1709003872.859
SIGALRM get signal:14 , current time:1709003873.859
SIGALRM get signal:14 , current time:1709003874.859
SIGALRM get signal:14 , current time:1709003875.859
SIGALRM get signal:14 , current time:1709003876.859
SIGALRM get signal:14 , current time:1709003877.859

4.2 实验3 使用ITIMER_PROF 计时器

按照提示使用ITIMER_PROF作为 setitimer()的计时器,测试结果和源码如下。

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

void sighandle(int signo)
{
	struct timeval tm;
	gettimeofday(&tm, NULL);
	
	switch(signo){
		case SIGALRM:
			printf("SIGALRM ");
			break;
		case SIGPROF:
			printf("SIGPROF ");
			break;
		case SIGVTALRM:
			printf("SIGVTALRM ");
			break;
	}

	printf("get signal:%d , current time:%d.%d\n", signo, tm.tv_sec, tm.tv_usec/1000);
}

int main(int argc, char *argv[])
{
	struct itimerval new_value;
	
	signal(SIGALRM, sighandle);
	signal(SIGPROF, sighandle);
	signal(SIGVTALRM, sighandle);
	
	
	new_value.it_value.tv_sec = 1;
	new_value.it_value.tv_usec = 0;
	
	new_value.it_interval.tv_sec = 1;
	new_value.it_interval.tv_usec = 0;

	if(setitimer(ITIMER_PROF, &new_value, NULL) < 0)
	{
		printf("setitimer failed:%d\n", errno);
		exit(1);
	}
	
	while(1);
	
	return 0;
}

运行结果。

SIGPROF get signal:27 , current time:1709004059.311
SIGPROF get signal:27 , current time:1709004061.771
SIGPROF get signal:27 , current time:1709004064.263
SIGPROF get signal:27 , current time:1709004066.947
SIGPROF get signal:27 , current time:1709004069.259
SIGPROF get signal:27 , current time:1709004071.762

4.2 实验3 两种计时器结果分析

对setitimer()计时器ITTIMER_REAL和ITTIMER_PROF,的执行结果进行对比分析。

ITIMER_REAL计时器:是我们熟悉的实际系统时间为准的,获取的时间就是gettimeofday()墙上时钟时间,也是人类熟悉的自然时间。

ITIMER_PROF计时器:是统计进程实际被调度到CPU上执行的虚拟时间,加上进程调度(系统调用)的时间。因为同一个时间中有多个进程在系统中调度执行,这个时间要比实际时间要长。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值