linux0.11的0號進程詳解


在執行main函數之前,其實計算機從上電到main執行了一系列操作,不過由於個人原因,迫不及待先理解了0號進程,不過在說0號進程之前,先說說main函數啟動到0號進程之間的事,也就是設備環境初始化的過程,這部分工作完成后系統進程怠速狀態。

首先進程的定義是計算機中的程序關於某數據集合上的一次運行活動。而且進程之間不能相互干擾,這就需要有一套管理進程的數據結構了,在這里先介紹三種:task_struct(標識了進程的屬性,包括剩余時間片,進程執行狀態,LDT局部數據描述符,TSS任務狀態描述表),task[64](這里存儲這task_struct結構體指針),GDT(存儲着一套針對所有進程的索引結構,通過索引項將進程和LDTTSS建立關系)。開展活動之間需要確保活動能夠正常展開,需要實現三個目標:

  1. 用戶程序能夠在主機上進行運算
  2. 用戶進程能夠和外設進行交互
  3. 用戶進程能夠讓用戶以他為媒介進行人機交互

接下來具體說明14個步驟(包含激活0號進程):

1.設置根設備和硬盤

...

#define DRIVE_INFO (*(struct drive_info *)0x90080) // 硬盤參數表基址。

#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)// 根文件系統所在設備號。

...

struct drive_info{char dummy[32];}drive_info;// 用於存放硬盤參數表信息。

void main(void)

{

    ROOT_DEV = ORIG_ROOT_DEV;//根設備

    drive_info = DRIVE_INFO;//硬盤

    ...

}

  

2.規划物理內存格局,設置緩沖區,虛擬盤,主內存

緩沖區:主機和外設進行數據交互的中轉站

虛擬盤:可選區域,如果選擇使用虛擬盤,可以將外設上的數據先復制到虛擬盤上,然后加以使用

主內存:進程代碼運行空間,也包括內核管理進程的數據結構。

 

 

 

 

設置了緩沖區的末端位置和主內存區的起始位置。

3.設置虛擬盤空間並初始化

虛擬盤是可選的,在Makefile中的選擇,在main.c中由RAMDISK宏來控制,為1就是選中了,表示設置了虛擬盤,虛擬空間的大小是可以設置的,大小也是由RAMDISK控制,本系統中設置為2MB,起始位置為緩沖區的結尾,這個時候主內存區后移2MB,后移的多少是由虛擬空間處理函數rd_init的返回值決定的,此過程中最重要的一步是將do_rd_request()blk_dev[7]的第二項掛接(掛接第二項,一共7個設備項,這樣第一項就為空,后面可以有6類設備),意味着內核可以調用do_rd_request()函數來處理虛擬盤相關請求項的操作。掛接后,虛擬盤所在的內存區域全部清零。

4.內存管理結構mem_map初始化

主內存起始位置的重新確認,標志着主內存和緩沖區的位置和大小已經全部確認了。 開始調用mem_init(),這里重點指出1MB以內和以外的內存分頁機制不同,0-1MB,可以通過線性地址獲取物理地址,由內核接管。大於1MB空間不可以通過線性地址獲取物理地址,沒有可遞推的邏輯關系。

這里的內存管理指的是對大於1MB地址位置的內存分頁管理,先將使用所有內存的使用計數為100,再將內存頁計數都清零,計數為0的內存頁視為空閑頁。

5.異常處理類中斷服務程序掛接

使用trap_init()函數將中斷和異常的服務程序和IDT掛接,重建中斷服務體系。也就是將idt[]數組中元素設置成中斷服務程序的地址,剩余的數組元素int0x11-int0x2f為保留項。這里的中斷是被動響應的,硬件產生信號並被CPU接收到,就會打斷正在執行的程序,保存現場,根據IDT找到中斷服務程序並執行,執行完恢復現場,繼續執行原來的程序,如果又收到中斷,就又執行,低優先級的中斷可以被高優先級的中斷打斷。早年的中斷時對所有硬件進行輪詢是否有中斷,大大浪費了CPU資源,輪詢完再去執行原來的程序,那就浪費了太多時間。由此可見主動輪詢到被動響應是計算機歷史上的一大進步。

6.初始化塊設備請求項結構

Linux0.11將外設分成塊設備(存儲)和字符設備(字節流IO通信)(后來有了網絡設備)。進程想要和塊設備溝通,必須經過緩沖區。

請求項管理結構request[32]就是OS管理緩沖區中的緩沖塊和塊設備上邏輯塊之間的讀寫關系數據結構,是鏈表。

struct request

{

      int dev; /* -1 if no request */// 使用的設備號。

      int cmd; /* READ or WRITE */// 命令(READ 或WRITE)。

      int errors; //操作時產生的錯誤次數。

      unsigned long sector; // 起始扇區。(1 塊=2 扇區)

      unsigned long nr_sectors; // 讀/寫扇區數。
    
      char *buffer; // 數據緩沖區。

      struct task_struct *waiting; // 任務等待操作執行完成的地方。

      struct buffer_head *bh; // 緩沖區頭指針(include/linux/fs.h,68)。

      struct request *next; // 指向下一請求項。

};

  

這樣就可以根據進程的讀寫任務的輕重緩急來決定緩沖塊和塊設備之間的讀寫操作,並把處理需求記錄在請求項中。

7.與監理人機交互界面相關的外設的中斷服務程序的掛接

初始化字符設備的是chr_dev_init(),但是linus在里面並沒有寫內容。tty_init()首先調用rs_init()來設置串行口(這里將串行口中斷服務程序和IDT掛接,然后初始化兩個串行口,初始化包括設置線路控制寄存器的DLAB位,波特率因子,DTR,RTS,最后是允許主控芯片的IRQ3IRQ4發送中斷請求),再調用con_init()來設置顯示器(設置顯存位置,另外初始化一些滾動屏幕的變量,索引寄存器端口被設置成0x3b4,數據寄存器端口被設置成0x3b5),對鍵盤進行設置(將鍵盤中斷服務程序和IDT掛接,取消8259A中對鍵盤的中斷屏蔽,允許IRQ1發送中斷信號,這里是通過先禁止鍵盤工作,再允許鍵盤工作,這樣鍵盤就可以使用了)

8.開機啟動時間設置

開機時間是計算機中大部分時間的基礎,這部分是重要的,大部分需要用到的時間都是根據開機時間推算出來的。具體操作步驟是:CMOS是主板上的歌小存儲芯片,通過調用time_init(),對芯片中記錄的時間進行采集,根據提取的時間要素,進行整合就可以計算出開機啟動時間了(開機時間是從1970110時開始計算的),開機時間存儲在內核數據區中。

9.初始化進程0

這部分主要包含三個內容:系統初始化進程0的資源,使進程具備多進程輪詢能力,具備處理系統調用的能力(系統調用有統一入口)。只有具備以上能力,0號進程才可以在主機是那個正常運行,並且這些能力具備遺傳的功能。這三點都是在sched_init()中實現的。

初始化0號進程:初始化0號進程所占有的TSSLDT(這兩個是GDT中的4,5兩項),0號進程的task_struct的母體(init_task={INIT_TASK},)是系統事先寫好的,使用INIT_TASK的指針初始化task[64]0項,接下將0號以外的63項清空,同時將TSS1LDT1往上的所有表項清零,最后就是將TR寄存器指向TSS0LDTR寄存器指向LDT0,這樣CPU就可以找到0號進程了。

設置時鍾中斷:對支持輪詢的8253定時器進行設置#define LATCH(1193180/HZ)。第二步是設置時鍾中斷,將timer_interrupt()中斷掛接,這樣IDT就可以找到具體的服務程序了。第三步是打開屏蔽碼,這樣時鍾中斷就可以產生了,現在開始每1/100秒就產生一次時鍾中斷,但這個時候CPU不會響應,因為此時處於CPU處於關閉中斷狀態,但是0號進程已經具備進程輪轉的能力了。

設置系統調用總入口:將system_call()int 0x80中斷描述符掛接。所以說系統調用由int0x80產生時調用,此時已經交給內核了,后面的流程就不需要用戶程序了,但是系統調用還有一個途徑,就是通過CPU中斷響應,翻轉權限30,通過IDT找到系統調用的端口,再通過系統調用來處理事務,然后再翻轉權限03,這樣進程就可以繼續執行原來的工作了。

10.初始化緩沖區管理結構

緩沖區是內存和外設進行數據交互的媒介。這里要明白硬盤和內存的最大區別就是,硬盤可以使用很低的成本來斷電保存,而且CPU不可以對硬盤進行直接尋址,而內存除了保存數據之外,更重要的是要和CPU、總線配合進行計算,硬盤是不進行計算的。

操作系統是通過hash_table[NR_HASH],buffer_head雙向環裝鏈表來組成復雜的哈希表管理緩沖區,這里會對hash_table所有項設置為NULL。緩沖區的起始位置是由start_buffer控制的,start_buffer跟內核代碼末端地址有關。

11.初始化硬盤

這一步的目的是,為進程和硬盤建德IO通信建立了環境基礎。這里具體的操作是,將硬盤請求項的服務do_hd_request()blk_dev控制結構掛接,硬盤和請求項的交互工作交給do_hd_request(),然后將硬盤中斷服務程序和IDT掛接,最后復位主8259A int2的屏蔽位,這么做的意義就是允許中斷請求信號,還有一不是復位硬盤的中斷請求屏蔽位,這一步的意義是允許硬盤控制器發送中斷請求信號。

12.初始化軟盤

這里的流程和硬盤處理基本一樣,就是具體的函數不同了,因為硬件的驅動不同,具體的中斷也不同。

13.開啟中斷

這個時候所有中斷的服務程序已經和IDT正常掛接了,這意味着中斷服務體系已經構建完畢了,系統可以再32位保護模式下處理中斷了,中應該要的意義就是可以使用系統調用了。這里就是把EFLAGS(標志寄存器)中的中斷標志位從0變成1。

14.進程00特權級翻轉到3特權級,成為真正的進程

0號進程的建立是設計者提前寫好的,在linux操作系統中規定了,除了0進程,其他所有進程都是一個已有進程在3特權下創建的。

進程0的特級翻轉調用的是move_to_user_mode()函數,來模仿返回動作實現的。

//// 切換到用戶模式運行。

// 該函數利用iret 指令實現從內核模式切換到用戶模式(初始任務0)。

#define move_to_user_mode() \

__asm__ ( "movl %%esp,%%eax\n\t" \ // 保存堆棧指針esp 到eax 寄存器中。

"pushl $0x17\n\t" \ // 首先將堆棧段選擇符(SS)入棧。

  "pushl %%eax\n\t" \ // 然后將保存的堆棧指針值(esp)入棧。

  "pushfl\n\t" \ // 將標志寄存器(eflags)內容入棧。

  "pushl $0x0f\n\t" \ // 將內核代碼段選擇符(cs)入棧。

  "pushl $1f\n\t" \ // 將下面標號1 的偏移地址(eip)入棧。

  "iret\n" \ // 執行中斷返回指令,則會跳轉到下面標號1 處。

  "1:\tmovl $0x17,%%eax\n\t" \ // 此時開始執行任務0,

  "movw %%ax,%%ds\n\t" \ // 初始化段寄存器指向本局部表的數據段。

"movw %%ax,%%es\n\t" "movw %%ax,%%fs\n\t" "movw %%ax,%%gs":::"ax")

  

這里有一個問題就是函數調用和中斷的不同點在哪里,看樣子都是壓棧出棧。函數調用是預先設計好的,知道代碼在那個地方調用,編譯器預先設計好壓棧保護現場和出棧恢復現場的代碼;而中斷的發生是不可預測的,無法預先編譯出保護和恢復的代碼,所以只能由硬件來完成,核心就是int指令會引發CPU硬件完成SS,ESP,EFLAGS,CS,EIP的值按順序進棧,同理恢復現場時按順序恢復給這五個寄存器。

CPU響應中斷的時候,根據DPL的設置,可以實現指定的特權級之間的翻轉。0330的兩個操作是兩個不同的函數實現的,但都是通過系統調用實現的。匯編指令就是iret。特權翻轉就相當於進行了一次中斷。傳統意義上將3特權級下的進程才是真正的進程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM