ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

为什么计算重启后时间依然正确?

2022-01-20 12:33:03  阅读:179  来源: 互联网

标签:IO 正确 BCD 重启 端口 指令 计算 寄存器 CMOS


TIME

  • 首发公号:Rand_cs

相信很多朋友接触计算机的时候都有这么一个疑惑,为什么计算机在关机断电,隔一段时间后重启的时间依然正确?

这背后的原因其实不难猜测,关机后重启的时间正确说明关机的情况下时钟仍然在工作,关机的情况下时钟仍然在工作,说明这个时钟应是有备用电源支持它工作的。

这个时钟叫做 R T C RTC RTC, R e a l Real Real T i m e Time Time C l o c k Clock Clock,可以“永久”的存放系统时间,也就是说它在系统关闭,没有电源的情况下也能继续工作。这里说的是没有计算机的电源(那大个儿电池),电子设备要工作那肯定还是需要电源的,这个电源是主板上的一个微型纽扣电池,计算机断电后实时时钟 R T C RTC RTC 就靠它来供电工作,持续对系统计时,这也就是为什么关机重新启动后时间还是正确的原因。

R T C RTC RTC 时间存放在 C M O S CMOS CMOS 中, C M O S CMOS CMOS, C o m p l e m e n t a r y Complementary Complementary M e t a l Metal Metal O x i d e Oxide Oxide S e m i c o n d u c t o r Semiconductor Semiconductor,互补金属氧化物半导体,别管这绕口名字的本身含义,只需要知道 C M O S CMOS CMOS 就是一 R A M RAM RAM 芯片,是 B I O S BIOS BIOS 的 M e m o r y Memory Memory。 B I O S BIOS BIOS 有各种的设置信息,这些信息没有存放在 B I O S BIOS BIOS 本身的芯片里,而是存放在 C M O S CMOS CMOS 里面。

对 C M O S CMOS CMOS 中的数据的读写是通过两个 I / O I/O I/O 端口来实现的,其中,端口 0 x 70 0x70 0x70 是一个字节的只写端口,用它来选择 C M O S CMOS CMOS 的寄存器,然后再通过 0 x 71 0x71 0x71 端口来读写选择的寄存器,也就是前面所说的 i n d e x / d a t a index/data index/data 的方式访问 C M O S CMOS CMOS 数据。下面就来看看 CMOS 中与时间相关的一些数据(寄存器):

CMOS 寄存器(index)

时间

  • 00 h 00h 00h,系统时间“秒数”字段
  • 02 h 02h 02h,系统时间“分钟”字段
  • 04 h 04h 04h,系统时间“小时”字段
  • 07 h 07h 07h,系统“日期”字段(0~31)
  • 08 h 08h 08h,系统“月份"字段(0~12)
  • 09 h 09h 09h,系统公元纪年的后两位(00 表示 2000,01 表示 2001,依次类推)

状态

  • 0 A h 0Ah 0Ah,状态寄存器 A A A
    • bit 7,0 表示目前可读时间,1 表示日期正在更新,稍后读取。
  • 0 B h 0Bh 0Bh,状态寄存器B
    • bit 2,0 表示使用 b c d bcd bcd 格式,1 表示二进制格式

上述就是与时间有关的一些寄存器,其他部分有兴趣的可以看后面的链接。另外在前面启动我们曾提到过 0 x F 0xF 0xF 号寄存器存放的是 s h u t d o w n shutdown shutdown c o d e code code, B S P BSP BSP 在启动 A P AP AP 时将 s h u t d o w n shutdown shutdown c o d e code code 设置为 0 x A 0xA 0xA 使得 A P AP AP 跳去执行 w a r m warm warm r e s e t reset reset v e c t o r vector vector 记录的引导程序,这部分详见启动理论部分@@@@@@@@

相关函数

读取CMOS寄存器

static uint
cmos_read(uint reg)         //0x70端口选择寄存器,0x71端口读出来
{
  outb(CMOS_PORT,  reg);   //选择寄存器:向70h端口写寄存器索引
  microdelay(200);         //等一会儿

  return inb(CMOS_RETURN); //从71h端口将数据读出来
}

这个函数就是向 0 x 70 0x70 0x70 端口写要读写的寄存器索引,然后再从 0 x 71 0x71 0x71 端口操作该寄存器,这里就是从 0 x 71 0x71 0x71 端口将数据给读出来

读取时间

struct rtcdate {
  uint second;
  uint minute;
  uint hour;
  uint day;
  uint month;
  uint year;
};
static void fill_rtcdate(struct rtcdate *r)     //读取时间
{
  r->second = cmos_read(SECS);   //秒数
  r->minute = cmos_read(MINS);   //分钟
  r->hour   = cmos_read(HOURS);  //小时
  r->day    = cmos_read(DAY);    //日期
  r->month  = cmos_read(MONTH);  //月份
  r->year   = cmos_read(YEAR);   //年份
}c

这个函数就是调用 c m o s _ r e a d cmos\_read cmos_read 将存储在 C M O S CMOS CMOS 中的墙上时间给读取出来,这个函数之上还封装了一层 c m o s t i m e cmostime cmostime:

void cmostime(struct rtcdate *r)
{
  struct rtcdate t1, t2;
  int sb, bcd;

  sb = cmos_read(CMOS_STATB);  //读取状态寄存器B

  bcd = (sb & (1 << 2)) == 0;    //0是BCD格式,为默认值,1是二进制值

  // make sure CMOS doesn't modify time while we read it
  for(;;) {
    fill_rtcdate(&t1);   //读取时间
    if(cmos_read(CMOS_STATA) & CMOS_UIP)  //如果时间正更新,稍后读取
        continue;
    fill_rtcdate(&t2);
    if(memcmp(&t1, &t2, sizeof(t1)) == 0) //如果两者一样,break,如此操作应是为了确保时间准确
      break;
  }

  // convert
  if(bcd) {
#define    CONV(x)     (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))   //BCD码转10进制
    CONV(second);
    CONV(minute);
    CONV(hour  );
    CONV(day   );
    CONV(month );
    CONV(year  );
#undef     CONV
  }

  *r = t1;
  r->year += 2000;    //读取出来的year位公元纪年的后两位,所以加上2000
}

整个流程应该是很简单的,主要注意一下 B C D BCD BCD 码,所谓 B C D BCD BCD 码,就是使用 4 位二进制来表示十进制数 0 ∼ 9 0 \sim 9 0∼9, B C D BCD BCD 码也分为多种,最常见的就是 8421 8421 8421 码, 8421 8421 8421 表示各位的权重为 8 8 8 4 4 4 2 2 2 1 1 1,这很像二进制表示。举个例子来看看其中差别, B C D BCD BCD 码 0101 0101 0101 表示十进制 5,这似乎与 5 的二进制表示没什么不同,但如果表示十进制数 15, B C D BCD BCD 码为 0001 0001 0001 0101 0101 0101,而 15 的二进制表示为 1111 1111 1111,这就是它们之间的差别,十进制的每一位都用 4 位 二进制来表示。有了这认识之后来看如何 BCD 码如何转化为十进制:

unsigned char bcd2dec(unsigned char bcd)
{
      return ((bcd & 0xf) + ((bcd>>4)*10));
}

原理上很简单, B C D BCD BCD 码从低到高每四位表示着十进制数中的一位,其权重从低到高分别为个十百千,这里因为表示时间的数都很小,只用到了 8 位 B C D BCD BCD 码,所以前四位对应十位,需要乘 10,再加上个位(后四位)就是对应的十进制数字了。

上述就是获取读取 R T C RTC RTC 时间的操作,其实很简单啊,操作 C M O S CMOS CMOS 的 0 x 70 0x70 0x70 和 0 x 71 0x71 0x71 端口再作转化就完事了。这些操作有特权级限制的,因为涉及到了 I O IO IO 操作指令 inout。对于指令的分类除了常见的什么数据转移指令,算数类指令等等,还有一些特殊的指令分类:特权指令和敏感指令,这部分本应该在启动理论那一块儿就该讲述的,结果那一块的零散知识点太多,写到最后搞忘了,实在抱歉这里补上。

先说敏感指令,敏感指的是对 I O IO IO 特权级敏感,这类指令有 in ins out outs sti cli 应该都很熟悉我就不解释它们有什么作用了。敏感指令涉及到了 I O IO IO 特权级,这部分我在进程讲述 T S S TSS TSS, I O IO IO 位图的时候详细说过,这里再简单回顾一下。这类指令的执行会检查 C P L ≤ I O P L CPL \le IOPL CPL≤IOPL,级检查当前特权级是否高于 E L F A G S ELFAGS ELFAGS 中记录的 I O IO IO 特权级,如果当前特权级大,直接执行没什么问题,如果当前特权级小,那么再检查 I O IO IO 位图对应的端口是否被禁止访问,如果没有禁止,则执行指令,如果禁止了,则抛出一般保护性错。而再 x v 6 xv6 xv6 里面, I O P L IOPL IOPL 设置的是 0,没有使用 I O IO IO 位图即默认禁止所有端口,所以 x v 6 xv6 xv6 里的敏感指令只能在内核态下运行

而特权指令则是只有 C P L = 0 CPL=0 CPL=0 即在内核态下才能执行的指令,这类指令都关乎中断,需要最高权限才能执行,有很多,咱们随便看看几个比较熟悉的: l g d t lgdt lgdt 加载 G D T GDT GDT 用的, l t r ltr ltr 加载任务寄存器,与控制寄存器相关的 m o v mov mov 指令, h l t hlt hlt 停机指令等等,其他特权指令有兴趣的见 i n t e l intel intel 手册卷三 5.9 5.9 5.9,手册在我公众号后台回复 手册 即可获取链接。

好了本文就到这里吧,有什么问题还请批评指正,也欢迎大家来同我探讨交流一起学习一起进步。

标签:IO,正确,BCD,重启,端口,指令,计算,寄存器,CMOS
来源: https://blog.csdn.net/weixin_46645613/article/details/122598860

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有