🚀 前言
本文主要解释了计算机断电重启后能准确读取时间的原因,内容很短,对应于书中的第17回。希望各位给个三连,拜托啦,这对我真的很重要!!!
目录
- 🚀 前言
- 🏆 计算机时间从哪里来
- 🏆 时间初始化
- 🎯总结
- 📖参考资料
🏆 计算机时间从哪里来
熟悉嵌入式的都知道硬件上有一个模块叫实时时钟(Real Time Clock,RTC),也叫CMOS时钟。这个模块的电源是独立的,采用纽扣电池供电。当操作系统关机时,就用这个来记录时间。其工作原理类似电子手表,通过石英晶体振荡记录时间流逝。
🏆 时间初始化
知道了上电后时间从哪里来后,我们只需要关注内核中如何获取即可。内核中时间初始化的源码如下:
static void time_init(void)
{struct tm time;do {time.tm_sec = CMOS_READ(0);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);} 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--;startup_time = kernel_mktime(&time);
}
这个函数里面最多的就是CMOS_READ
以及BCD_TO_BIN
宏函数,按照字面意思,CMOS_READ
就是读取CMOS的数据,分别是年,月,日,小时,分钟,秒;BCD_TO_BIN
就是将BCD码(CMOS RAM中存放数据的编码格式)转换为我们常用的二进制。
首先是CMOS_READ
函数:
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
可以看到这个宏函数是先写再读,实际上这也是CPU与外设交互的做法,都是通过端口写入值让外设干嘛,再从另一个端口读值接收外设的反馈。按照CMOS手册要求的读写端口进行读写即可,就像上面代码的循环里面一样。
接下来看BCD_TO_BIN
函数:
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
由于是BCD码,即四位表示十进制的一位,因此首先先把后四位与15(0b1111)做与运算,后面将前面4位后移并乘10表示十位,最后将十位与个位进行相加。
最后还有一个函数:
long kernel_mktime(struct tm * tm)
{long res;int year;year = tm->tm_year - 70;
/* magic offsets (y+1) needed to get leapyears right.*/res = YEAR*year + DAY*((year+1)/4);res += month[tm->tm_mon];
/* 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日0时起至开机当前经过的秒数,作为开机时间存储在变量中。
🎯总结
本文是一个很简单的模块——时间初始化。其核心就是CPU访问外设是通过端口的,先向端口写入一个指令表示我要干什么,然后再去另一个端口读取反馈数据。
📖参考资料
[1] linux源码趣读
[2] 一个64位操作系统的设计与实现
[3] Linux内核的时间管理