<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          第17回 | 原來操作系統(tǒng)獲取時(shí)間的方式也這么 low

          共 5346字,需瀏覽 11分鐘

           ·

          2022-01-17 00:15

          新讀者看這里,老讀者直接跳過。


          本系列會(huì)以一個(gè)讀小說的心態(tài),從開機(jī)啟動(dòng)后的代碼執(zhí)行順序,帶著大家閱讀和賞析 Linux 0.11 全部核心代碼,了解操作系統(tǒng)的技術(shù)細(xì)節(jié)和設(shè)計(jì)思想。



          你會(huì)跟著我一起,看著一個(gè)操作系統(tǒng)從啥都沒有開始,一步一步最終實(shí)現(xiàn)它復(fù)雜又精巧的設(shè)計(jì),讀完這個(gè)系列后希望你能發(fā)出感嘆,原來操作系統(tǒng)源碼就是這破玩意。


          以下是已發(fā)布文章的列表,詳細(xì)了解本系列可以先從開篇詞看起。


          開篇詞

          第一部分 進(jìn)入內(nèi)核前的苦力活

          第一回 | 最開始的兩行代碼
          第二回 | 自己給自己挪個(gè)地兒
          第三回 | 做好最最基礎(chǔ)的準(zhǔn)備工作?
          第四回 | 把自己在硬盤里的其他部分也放到內(nèi)存來
          第五回 | 進(jìn)入保護(hù)模式前的最后一次折騰內(nèi)存
          第六回 | 先解決段寄存器的歷史包袱問題
          第七回 | 六行代碼就進(jìn)入了保護(hù)模式
          第八回 | 煩死了又要重新設(shè)置一遍 idt 和 gdt
          第九回 | Intel 內(nèi)存管理兩板斧:分段與分頁(yè)
          第十回 | 進(jìn)入 main 函數(shù)前的最后一躍!
          第一部分總結(jié)

          第二部分 大戰(zhàn)前期的初始化工作

          第11回 | 整個(gè)操作系統(tǒng)就 20 幾行代碼

          第12回 | 管理內(nèi)存前先劃分出三個(gè)邊界值

          第13回 | 主內(nèi)存初始化 mem_init

          第14回 | 中斷初始化 trap_init

          第15回 | 塊設(shè)備請(qǐng)求項(xiàng)初始化 blk_dev_init

          第16回 | 控制臺(tái)初始化 tty_init


          本系列的 GitHub 地址如下(文末閱讀原文可直接跳轉(zhuǎn))

          https://github.com/sunym1993/flash-linux0.11-talk



          ------- 正文開始?-------



          書接上回,上回書咱們說到,通過初始化控制臺(tái)的 tty_init 操作,內(nèi)核代碼可以很方便地在控制臺(tái)輸出字符啦!

          作為用戶也可以通過敲擊鍵盤,或調(diào)用諸如 printf 這樣的庫(kù)函數(shù),在屏幕上輸出信息,同時(shí)支持換行和滾屏等友好設(shè)計(jì),這些都是 tty_init 初始化,以及其對(duì)外封裝的小功能函數(shù),來實(shí)現(xiàn)的。
          ?
          ?
          我們繼續(xù)往下看下一個(gè)初始化的倒霉鬼,time_init
          void?main(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();
          }
          曾經(jīng)我很好奇,操作系統(tǒng)是怎么獲取到當(dāng)前時(shí)間的呢

          當(dāng)然,現(xiàn)在都聯(lián)網(wǎng)了,可以從網(wǎng)絡(luò)上實(shí)時(shí)同步。那當(dāng)沒有網(wǎng)絡(luò)時(shí),為什么操作系統(tǒng)在啟動(dòng)之后,可以顯示出當(dāng)前時(shí)間呢?難道操作系統(tǒng)在電腦關(guān)機(jī)后,依然不停地在某處運(yùn)行著,勤勤懇懇數(shù)著秒表么?

          當(dāng)然不是,那我們今天就打開這個(gè) time_init 函數(shù)一探究竟。

          打開這個(gè)函數(shù)后我又是很開心,因?yàn)楹芏蹋覜]有更深入的方法調(diào)用。
          #define?CMOS_READ(addr)?({?\
          ????outb_p(0x80|addr,0x70);?\
          ????inb_p(0x71);?\
          })


          #define?BCD_TO_BIN(val)?((val)=((val)&15)?+?((val)>>4)*10)

          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);
          }
          夢(mèng)想的代碼呀!
          ?
          那主要就是對(duì) CMOS_READ BCD_TO_BIN 都是啥意思展開講一下就明白了了。
          ?
          首先是?CMOS_READ
          #define?CMOS_READ(addr)?({?\
          ????outb_p(0x80|addr,0x70);?\
          ????inb_p(0x71);?\
          })

          就是對(duì)一個(gè)端口先 out 寫一下,再 in 讀一下。
          ?
          這是 CPU 與外設(shè)交互的一個(gè)基本玩法,CPU 與外設(shè)打交道基本是通過端口,往某些端口寫值來表示要這個(gè)外設(shè)干嘛,然后從另一些端口讀值來接受外設(shè)的反饋。

          至于這個(gè)外設(shè)內(nèi)部是怎么實(shí)現(xiàn)的,對(duì)使用它的操作系統(tǒng)而言,是個(gè)黑盒,無需關(guān)心。那對(duì)于我們程序員來說,就更不用關(guān)心了。

          對(duì) CMOS 這個(gè)外設(shè)的交互講起來可能沒感覺,我們看看與硬盤的交互。
          ?
          最常見的就是讀硬盤了,我們看硬盤的端口表。

          端口


          0x1F0

          數(shù)據(jù)寄存器數(shù)據(jù)寄存器

          0x1F1

          錯(cuò)誤寄存器特征寄存器

          0x1F2

          扇區(qū)計(jì)數(shù)寄存器扇區(qū)計(jì)數(shù)寄存器

          0x1F3

          扇區(qū)號(hào)寄存器或 LBA 塊地址 0~7扇區(qū)號(hào)或 LBA 塊地址 0~7
          0x1F4
          磁道數(shù)低 8 位或 LBA 塊地址 8~15磁道數(shù)低 8 位或 LBA 塊地址 8~15
          0x1F5
          磁道數(shù)高 8 位或 LBA 塊地址 16~23磁道數(shù)高 8 位或 LBA 塊地址 16~23
          0x1F6
          驅(qū)動(dòng)器/磁頭或 LBA 塊地址 24~27驅(qū)動(dòng)器/磁頭或 LBA 塊地址 24~27
          0x1F7
          命令寄存器或狀態(tài)寄存器命令寄存器

          那讀硬盤就是,往除了第一個(gè)以外的后面幾個(gè)端口寫數(shù)據(jù),告訴要讀硬盤的哪個(gè)扇區(qū),讀多少。然后再?gòu)?0x1F0 端口一個(gè)字節(jié)一個(gè)字節(jié)的讀數(shù)據(jù)。這就完成了一次硬盤讀操作。
          ?
          如果覺得不夠具體,那來個(gè)具體的版本。
          ?
          1. 在 0x1F2 寫入要讀取的扇區(qū)數(shù)

          2. 在 0x1F3 ~ 0x1F6 這四個(gè)端口寫入計(jì)算好的起始 LBA 地址

          3. 在 0x1F7 處寫入讀命令的指令號(hào)

          4. 不斷檢測(cè) 0x1F7 (此時(shí)已成為狀態(tài)寄存器的含義)的忙位

          5. 如果第四步驟為不忙,則開始不斷從 0x1F0 處讀取數(shù)據(jù)到內(nèi)存指定位置,直到讀完

          ?
          看,是不是對(duì) CPU 最底層是如何與外設(shè)打交道有點(diǎn)感覺了?是不是也不難?就是按照人家的操作手冊(cè),然后無腦按照要求讀寫端口就行了。
          ?
          當(dāng)然,讀取硬盤的這個(gè)無腦循環(huán),可以 CPU 直接讀取并做寫入內(nèi)存的操作,這樣就會(huì)占用 CPU 的計(jì)算資源。

          也可以交給 DMA 設(shè)備去讀,解放 CPU,但和硬盤的交互,通通都是按照硬件手冊(cè)上的端口說明,來操作的,實(shí)際上也是做了一層封裝。
          ?
          好了,我們已經(jīng)學(xué)會(huì)了和一個(gè)外設(shè)打交道的基本玩法了。
          ?
          那我們代碼中要打交道的是哪個(gè)外設(shè)呢?就是 CMOS
          ?
          它是主板上的一個(gè)可讀寫的 RAM 芯片,你在開機(jī)時(shí)長(zhǎng)按某個(gè)鍵就可以進(jìn)入設(shè)置它的頁(yè)面。


          那我們的代碼,其實(shí)就是與它打交道,獲取它的一些數(shù)據(jù)而已。

          我們回過頭看代碼。
          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);
          }
          前面幾個(gè)賦值語(yǔ)句 CMOS_READ 就是通過讀寫 CMOS 上的指定端口,依次獲取年月日時(shí)分秒等信息。具體咋操作代碼上也寫了,也是按照 CMOS 手冊(cè)要求的讀寫指定端口就行了,我們就不展開了。
          ?
          所以你看,其實(shí)操作系統(tǒng)程序,也是要依靠與一個(gè)外部設(shè)備打交道,來獲取這些信息的,并不是它自己有什么魔力。操作系統(tǒng)最大的魅力,就在于它借力完成了一項(xiàng)偉大的事,借 CPU 的力,借硬盤的力,借內(nèi)存的力,以及現(xiàn)在借 CMOS 的力。

          至于 CMOS 又是如何知道時(shí)間的,這個(gè)就不在我們討論范圍了。
          ?
          接下來 BCD_TO_BIN 就是 BCD 轉(zhuǎn)換成 BIN,因?yàn)閺?CMOS 上獲取的這些年月日都是 BCD 碼值,需要轉(zhuǎn)換成存儲(chǔ)在我們變量上的二進(jìn)制數(shù)值,所以需要一個(gè)小算法來轉(zhuǎn)換一下,沒什么意思。
          ?
          最后一步 kernel_mktime 也很簡(jiǎn)單,就是根據(jù)剛剛的那些時(shí)分秒數(shù)據(jù),計(jì)算從 1970 年 1 月 1 日 0 時(shí)起到開機(jī)當(dāng)時(shí)經(jīng)過的秒數(shù),作為開機(jī)時(shí)間,存儲(chǔ)在 startup_time 這個(gè)變量里。
          ?
          想研究可以仔細(xì)看看這段代碼,不過我覺得這種細(xì)節(jié)不必看。
          startup_time?=?kernel_mktime(&time);

          //?kernel/mktime.c
          long?kernel_mktime(struct?tm?*?tm)
          {
          ????long?res;
          ????int?year;
          ????year?=?tm->tm_year?-?70;
          ????res?=?YEAR*year?+?DAY*((year+1)/4);
          ????res?+=?month[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;
          ????return?res;
          }
          就這。
          ?
          所以今天其實(shí)就是,計(jì)算出了一個(gè) startup_time 變量而已,至于這個(gè)變量今后會(huì)被誰(shuí)用,怎么用,那就是后話了。
          ?
          相信你逐漸也體會(huì)到了,此時(shí)操作系統(tǒng)好多地方都是用外設(shè)要求的方式去詢問,比如硬盤信息、顯示模式,以及今天的開機(jī)時(shí)間的獲取等。
          ?

          所以至少到目前來說,你還不應(yīng)該感覺操作系統(tǒng)有多么的“高端”,很多時(shí)候都是繁瑣地,讀人家的硬件手冊(cè),獲取到想要的的信息,拿來給自己用,或者對(duì)其進(jìn)行各種設(shè)置。

          ?

          但你一定要耐得住寂寞,真正體現(xiàn)操作系統(tǒng)的強(qiáng)大設(shè)計(jì)之處,還得接著往下讀。

          ?

          欲知后事如何,且聽下回分解。



          ------- 關(guān)于本系列?-------



          本系列的開篇詞看這

          閃客新系列!你管這破玩意叫操作系統(tǒng)源碼


          本系列的擴(kuò)展資料看這(也可點(diǎn)擊閱讀原文),這里有很多有趣的資料、答疑、互動(dòng)參與項(xiàng)目,持續(xù)更新中,希望有你的參與。

          https://github.com/sunym1993/flash-linux0.11-talk


          本系列全局視角



          最后,祝大家都能追更到系列結(jié)束,只要你敢持續(xù)追更,并且把每一回的內(nèi)容搞懂,我就敢讓你在系列結(jié)束后說一句,我對(duì) Linux 0.11 很熟悉。


          公眾號(hào)更新系列文章不易,閱讀量越來越低,希望大家多多傳播,不方便的話點(diǎn)個(gè)小小的我也會(huì)很開心,謝謝大家咯。


          另外,本系列完全免費(fèi),希望大家能多多傳播給同樣喜歡的人,同時(shí)給我的 GitHub 項(xiàng)目點(diǎn)個(gè) star,就在閱讀原文,這些就足夠讓我堅(jiān)持寫下去了!我們下回見。

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲国产AV天堂 | 国内在线三| 99999亚洲 | 天天日天天干天天搞 | 操老外小逼视频网 |