当前位置: 首页 > 科技观察

原来操作系统获取时间的方式也这么 low

时间:2023-03-14 14:36:20 科技观察

原来操作系统获取时间的方式也是如此低级敲击键盘,或者调用printf等库函数,在屏幕上输出信息,同时支持换行等友好设计和滚动。这些都是通过tty_init初始化和对外封装的小函数函数来实现的。我们继续看下一个倒霉的初始化,time_init。voidmain(void){...mem_init(main_memory_start,memory_end);trap_init();blk_dev_init();chr_dev_init();tty_init();time_init();sched_init();buffer_init(buffer_memory_end);hd_init();floppy_init();sti();move_to_user_mode();if(!fork()){init();}for(;;)pause();}以前很好奇,操作系统是怎么获取当前时间的?当然,现在都联网了,可以从网络上实时同步。那么在没有网络的情况下,为什么操作系统启动后可以显示当前时间呢?电脑关机后操作系统是否还在某处运行,勤奋地数着秒表?当然不是,那我们今天就打开这个time_init函数一探究竟。打开这个函数后,我又开心了,因为它很短,没有再进行方法调用。#defineCMOS_READ(addr)({\outb_p(0x80|addr,0x70);\inb_p(0x71);\})#defineBCD_TO_BIN(val)((val)=((val)&15)+((val)>>4)*10)staticvoidtime_init(void){structtmtime;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#defineCMOS_READ(addr)({\outb_p(0x80|addr,0x70);\inb_p(0x71);\})就是先写出到一个端口,然后读入。这是一个基本的方式用于CPU与外设交互。CPU与外设的交互基本上是通过端口,向一些端口写值表示外设做什么,然后从其他端口读取值接收外设的反馈。.至于这个外设在内部是如何实现的,对于使用它的操作系统来说就是一个黑盒子,不用关心。对于我们程序员来说,大可不必关心。谈CMOS外设的交互可能感觉不太好,我们来看看与硬盘的交互。最常见的就是读取硬盘,我们看硬盘的端口表。端口读写0x1F0数据寄存器数据寄存器0x??1F1错误寄存器特性寄存器0x??1F2扇区计数寄存器扇区计数寄存器0x??1F3扇区号寄存器或LBA块地址0~7扇区号或LBA块地址0~70x1F4磁道号低8位或LBA块地址8~15轨道号低8位或LBA块地址8~150x1F5轨道号高8位或LBA块地址16~23轨道号高8位或LBA块地址16~230x1F6驱动器/磁头或LBA块地址24~27drive/headorLBAblockaddress24~270x1F7commandregisterorstatusregister命令寄存器读取硬盘是,向除第一个端口外的后面几个端口写入数据,告诉读取硬盘的哪个扇区,读取多少,然后从0x1F0端口逐字节读取数据。这样就完成了一次硬盘读取操作。如果觉得不够具体,那就来个具体的版本吧。在0x1F2处写入要读取的扇区数在0x1F3~0x1F6处写入计算出的LBA起始地址这四个端口在0x1F7处写入读取命令的指令号不断查看0x1F7(此时已经成为状态寄存器的意义)busy位,如果第四步不忙,则开始从0x1F0开始读取数据到内存中的指定位置,直到读取完成。CPU底层是如何与外设打交道的,你有没有感觉?是不是也不难?照着别人的操作手册,然后不假思索的按要求读写端口就好了。当然,读取硬盘的无脑循环可以直接让CPU读取写入内存,这样会占用CPU的计算资源。也可以交给DMA设备读取释放CPU,但是和硬盘的交互都是按照硬件手册上的端口说明进行的,其实是一层封装。好了,我们已经了解了处理外设的基本玩法。那么我们要在代码中处理哪些外设呢?它是CMOS。它是主板上一块可读写的RAM芯片,开机时长按一个键即可进入设置页面。然后我们的代码实际上是在处理它并获取它的一些数据。让我们回头看看代码。staticvoidtime_init(void){structtmtime;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(7);(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读写CMOS上的指定端口,在turn获取年月日时分秒等信息。具体的操作代码也写了,按照CMOS手册的要求读写指定端口就可以了,就不展开了。所以你看,其实操作系统程序也是靠和外部设备打交道来获取这些信息的,并不是它自己的神通。操作系统最大的魅力在于,它利用了CPU的力量、硬盘的力量、内存的力量,以及现在CMOS的力量,成就了一件伟大的事情。至于CMOS是怎么知道时间的,这个不在我们的讨论范围之内。接下来BCD_TO_BIN就是将BCD转BIN,因为从CMOS中得到的年月日都是BCD码值,需要转成二进制值存储在我们的变量中,所以需要一个小算法转换它,这是没有意义的。最后一步,kernel_mktime,也很简单。就是根据刚才的时分秒数据,计算从1970年1月1日0点到开机时间经过的秒数,存入变量startup_time中作为开机时间。如果你想研究,你可以仔细看看这段代码,但我认为你不需要阅读这些细节。startup_time=kernel_mktime(&time);//kernel/mktime.clongkernel_mktime(structtm*tm){longres;intyear;year=tm->tm_year-70;res=YEAR*year+DAY*((year+1)/4);res+=月[tm->tm_mon];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;returnres;}就是这样。所以今天其实只是在计算一个startup_time变量。至于以后谁用这个变量,怎么用,那是另外一回事了。相信你已经逐渐意识到,操作系统中很多地方都是以外围需求的形式进行查询的,比如硬盘信息、显示模式、今天的开机时间等。所以至少现在,你不应该觉得这个操作系统有多么“高端”。很多时候,看别人的硬件手册,得到自己想要的资料,自己用,还是自己用,很麻烦。它进行各种设置。但是你要耐得住寂寞,要真正体现操作系统强大的设计,还得往下看。想知道接下来发生了什么,就听下一章吧。本文转载自微信公众号《低并发编程》,可通过以下二维码关注。转载本文请联系低并发编程公众号。本网站已获得低并发编程许可。

最新推荐
猜你喜欢