题目:kernel_mktime() 详解 —— Linux-0.11 学习笔记(四)

init/main.c文件中,有一个函数static void time_init(void)

该函数读取 CMOS 实时时钟信息作为开机时间,并保存到全局变量startup_time (以秒为单位)中。

static void time_init(void)
{struct tm time;do {time.tm_sec = CMOS_READ(0); //当前时间的秒值,格式均是BCD码time.tm_min = CMOS_READ(2); //当前时间的分钟值time.tm_hour = CMOS_READ(4); //当前时间的小时值time.tm_mday = CMOS_READ(7); //当前的日time.tm_mon = CMOS_READ(8);  //当前的月time.tm_year = CMOS_READ(9); //当前的年,只有后2位数,例如97表示1997年} while (time.tm_sec != CMOS_READ(0));BCD_TO_BIN(time.tm_sec);  // 转换成二进制数值BCD_TO_BIN(time.tm_min);BCD_TO_BIN(time.tm_hour);BCD_TO_BIN(time.tm_mday);BCD_TO_BIN(time.tm_mon);BCD_TO_BIN(time.tm_year);time.tm_mon--;           // 减一后月份范围是0~11startup_time = kernel_mktime(&time);
}

6~11行:读出当前时间,注意,格式为BCD码值;
13~18行:把BCD码值转换成二进制;

第19行:time.tm_mon--;这里把月份值减一,为什么这样做,后文会说明。

第20行:调用函数kernel_mktime(),计算从 1970 年 1 月 1 日 0 时起到此次开机时刻经过的秒数,作为开机时间。

上面的代码就说到这里,CMOS_READ, BCD_TO_BIN等宏定义以后再说。本文想说说kernel_mktime这个函数。此函数在文件kernel/mktime.c 的第 41 行。kernel/mktime.c这个文件很短,仅有58行。

1. 宏定义

#define MINUTE 60        //1分钟经过的秒数
#define HOUR (60*MINUTE) //1小时经过的秒数
#define DAY (24*HOUR)    //一天经过的秒数
#define YEAR (365*DAY)   //一年经过的秒数(不考虑闰年)

2. 从1.1x.1经过的秒数

static int month[12] = {0,                                        //[0]  1.1-1.1DAY*(31),                                 //[1]  1.1-2.1DAY*(31+29),                              //[2]  1.1-3.1DAY*(31+29+31),                           //[3]  1.1-4.1DAY*(31+29+31+30),                        //[4]  1.1-5.1              DAY*(31+29+31+30+31),                     //[5]  1.1-6.1DAY*(31+29+31+30+31+30),                  //[6]  1.1-7.1DAY*(31+29+31+30+31+30+31),               //[7]  1.1-8.1DAY*(31+29+31+30+31+30+31+31),            //[8]  1.1-9.1DAY*(31+29+31+30+31+30+31+31+30),         //[9]  1.1-10.1DAY*(31+29+31+30+31+30+31+31+30+31),     //[10]  1.1-11.1DAY*(31+29+31+30+31+30+31+31+30+31+30)   //[11]  1.1-12.1
};

假如当前是4月,问:从本年1月1日起到4月1日,经过了多少秒?

可以先算出经过了多少天,再把天数乘以DAY(见宏定义)。如果用 D(m) 表示月份m的总天数,那么答案就是:

( D(1) + D(2) + D(3) ) * DAY

把上面的问题一般化为:假如当前是x月,问:从本年1月1日起到x月1日,经过了多少秒?

答案是:

( D(1) + D(2) + D(3) + ... + D(x-1) ) * DAY

思路就是这样, Linus 用的是查表法,于是就有了上面的数组。比如从CMOS中读出的是8月份,那么答案就是month[7];再比如读出的是12月份,那么答案就是month[11];再来个特殊情况,比如读出的是1月份,那么就是0,即month[0]. 看出来了吧,索引值比真实的月份值少1,这就是time.tm_mon--;的原因。

注意,代码中假设今年是闰年,即2月份有29天。

3. 结构体struct tm

struct tm {int tm_sec;int tm_min;int tm_hour;int tm_mday;int tm_mon;int tm_year;  //以上6行不用多说,用来保存读出来的年月日时分秒int tm_wday;int tm_yday;int tm_isdst; //夏令时标志
};

8~10行:这3个成员好像没有用到。

4. kernel_mktime()函数

long kernel_mktime(struct tm * tm)
{long res;int year;year = tm->tm_year - 70; //计算70年到现在(今年的1.1)经过的年数
/* magic offsets (y+1) needed to get leapyears right.*/res = YEAR*year + DAY*((year+1)/4); //把年换算成秒,把闰年多出来的天也换算成秒res += month[tm->tm_mon]; //把今年的1.1到现在的x.1换算成秒
/* and (y+2) here. If it wasn't a leap-year, we have to adjust */if (tm->tm_mon>1 && ((year+2)%4))res -= DAY;res += DAY*(tm->tm_mday-1); //不算今天res += HOUR*tm->tm_hour;   res += MINUTE*tm->tm_min;res += tm->tm_sec;return res;
}

总的来说,计算的方法是先整后零:从1970.1.1算到今年的1.1,再算到本月1日,再算到今天的0点,再到此刻的时分秒。

第6行:因为是从1970年算起,且tm->tm_year中是年份的末2位,所以要减去70。举例来说,如果是1998年,那么tm->tm_year = 98year = 28.

注意:因为年份是 2 位表示方式,所以会有2000年问题。我们可以简单地在最前面(比如第5行)添加一条语句来解决这个问题:

if(tm->tm_year < 70)tm->tm_year += 100;

推导过程:

20xx−1970=(2000+xx)−(1900+70)=2000+xx−1900−70=100+xx−7020xx−1970=(2000+xx)−(1900+70)=2000+xx−1900−70=100+xx−70

20xx-1970 =(2000+xx) - (1900+70) = 2000+xx-1900 - 70 = 100 + xx - 70

举例来说,假如是2007年,那么tm->tm_year = 7,执行上面的2行语句后,tm->tm_year = 107,再执行原来的第6行,year = 37

第8行:res = YEAR*year + DAY*((year+1)/4);

(year+1)/4表示从1970年1.1到今年的1.1,经过了几个闰年。注意:1972年是闰年。

为什么是这个式子,或者说为什么它是对的,列出来找找规律就明白了。

读出的年份 year的值 经过的闰年数 备注
1970,1971,1972 0,1,2 0 因为截至今年的1.1,所以即使读出1972年,也不能算是经过了闰年,后面的1976、1980等同理
1973,1974,1975,1976 3,4,5,6 1 如果读出1973~1976,因为经过了1972,所以算为1
1977,1978,1979,1980 7,8,9,10 2 如果读出1977~1980,因为经过了1972和1976,所以算为2

通过上表的中间2列,可以归纳出公式:

经过的闰年数=(year+1)/4经过的闰年数=(year+1)/4

经过的闰年数 = (year+1)/4

第9行:res += month[tm->tm_mon];在前文第2节已经解释了。

到目前为止(代码第10行之前),已经计算了1970年1月1日0时到今年本月1日0时经历的秒数。

11~12行:

if (tm->tm_mon>1 && ((year+2)%4))res -= DAY;

如果此表达式(year+2)%4)取值为0,则说明是闰年(观察上表中带下划线的数字就可以得出);取值不为0,说明不是闰年;

如果tm->tm_mon>1成立,说明现在的月份是3~12(注意之前的减一);否则现在的月份是1或者2;

以上2个条件,组合起来有4种情况。

现在的月份 今年是闰年吗? 结论
1,2 因为算到本月1日,所以不牵扯2.29;
1,2 同上
3-12 多算了2.29,所以要减去1天
3-12 是闰年,算2.29没有错

根据上面的分析,只有表格第3行这种情况需要减去1天,于是就有了上面的代码。

剩下的代码就很好理解了,这里不再赘述。

【完】

参考资料

《Linux内核完全剖析》(赵炯,机械工业出版社,2006)

kernel_mktime() 详解 —— Linux-0.11 学习笔记(四)相关推荐

  1. vue.js2.0 java_详解vite2.0配置学习(typescript版本)

    介绍 尤于溪的原话. vite与 Vue CLI 类似,vite 也是一个提供基本项目脚手架和开发服务器的构建工具. vite基于浏览器原生ES imports的开发服务器.跳过打包这个概念,服务端按 ...

  2. 各种音视频编解码学习详解之 编解码学习笔记(四):Mpeg系列——Mpeg 4

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解成很多小的篇幅,方便阅读.大神博客传送门 ...

  3. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  4. 各种音视频编解码学习详解之 编解码学习笔记(三):Mpeg系列——Mpeg 1和Mpeg 2

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解很多小的篇幅,方便阅读.大神博客传送门: ...

  5. 各种音视频编解码学习详解之 编解码学习笔记(十三):容器

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解成很多小的篇幅,方便阅读.大神博客传送门 ...

  6. 各种音视频编解码学习详解之 编解码学习笔记(八):Real系列

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解成很多小的篇幅,方便阅读.大神博客传送门 ...

  7. 各种音视频编解码学习详解之 编解码学习笔记(十):Ogg系列

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解成很多小的篇幅,方便阅读.大神博客传送门 ...

  8. 孙鑫VC++深入详解第三章学习笔记

    第三章 3.1创建MFC AppWizard 如何利用vs2019创建MFC应用见参考文献[1] 需要注意的地方有 [1] 创建MFC单文档应用程序 [2]开启类视图窗口 3.2基于MFC的程序框架剖 ...

  9. 各种音视频编解码学习详解之 编解码学习笔记(六):H.26x系列

    最近在研究音视频编解码这一块儿,看到@bitbit大神写的[各种音视频编解码学习详解]这篇文章,非常感谢,佩服的五体投地.奈何大神这边文章太长,在这里我把它分解成很多小的篇幅,方便阅读.大神博客传送门 ...

  10. Spring5 详解 B站 狂神 学习笔记

    目录 1. Spring 1.简介 2.优点 3.组成 4.拓展 2.IOC理论推导 IOC本质 3.HelloSpring 思考问题? 4.IOC创建对象的方式 5.Spring配置 1.别名 2. ...

最新文章

  1. python接收输入的一行字符只统计数字的个数,Python(统计字符),python实例,输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数...
  2. 实验室博士背着导师私发了两篇SCI,导师知道了会怎样?
  3. 《结网》十年,《结网2》开启产品经理的无限游戏
  4. java 集成grizzly_java – 与Jersey和Spring集成Grizzly2.2.X
  5. 漫说单例模式--宝宝成长记 你真的了解了吗?
  6. MySQL 中 MyISAM 中的查询为什么比 InnoDB 快?
  7. wxHtml 示例:帮助浏览器
  8. 终于有人把Java技术知识面试体系整理出来了,这些文档让你的面试稳如泰山
  9. 百度是如何收录没有提交过的新站
  10. Spcok简约图片分享网站Typecho主题
  11. MongoDB 或者 redis 可以替代 memcached 吗?
  12. web前端基础(14html里面的事件)
  13. html gif循环播放,Easy GIF Animator 7设置gif动图循环播放次数的方法
  14. postgres整库导入导出
  15. python题库填空_Python题库
  16. 电脑双屏开机后副屏黑屏_电脑两个显示器怎么设置,电脑显示器黑屏
  17. 使用 React 开发小程序
  18. 视频教程-Python开发全教程-Python
  19. 大数据早报:Firebase推出机器学习功能 英伟达大幅扩大深度学习学院规模(11.2)
  20. Java与mysql数据库连接

热门文章

  1. java版DVD影碟片出租赁系统C/S模式 java电影购票系统课程设计
  2. POJ 3628 Bookshelf 2 (01背包)
  3. 0-1背包问题优化算法详解
  4. TOPAS 命令详解
  5. 剪刀、石头、布机器人比赛
  6. VS.Net 2005 Beta2连接Team Foundation Server的问题
  7. 图片保存路径更改 python
  8. python 如何引用同一个目录下的另一个py文件
  9. 使用tensorflow出现 ImportError: DLL load failed: 找不到指定的程序
  10. vivado实现基本D触发器