檔案系統
檔案系統是建立在硬碟上的一個程式,是以由2部分組成:驅動和管理檔案系統的程序FS。
首先看一下驅動是如何工作的:
通常,主機闆上面有2個IDE插槽,分别叫做IDE0/IDE1。每個IDE通道又可以接2個裝置。驅動程序的目的就是要隐藏硬體細節,向FS程序提供統一的接口,具體到這裡,驅動為FS提供的接口就是打開,讀取,寫入,關閉等接口。下面是硬碟驅動程式:
Task_hd()
{
Recv(any, msg) //接受任何程序傳來的消息
If(msg == DEV_OPEN) //如果是打開裝置消息,則對硬碟寄存器做一些操作,比如擷取硬碟資訊等。
{
Out_byte(***) //向硬碟寄存器寫入資料
Recv(interrupt, msg) //阻塞等待硬碟中斷發生(表示需要的資料已經得到)
}
Send(src, msg) //将擷取的硬碟資訊傳回給發送者程序
}
上面的程式之添加了一個讀取硬碟資訊的消息,其他比如讀取寫入資料的消息和這個都是類似的。
有一點需要注意,驅動程式task_hd在讀取硬碟資訊或者寫入資料到硬碟時候的操作其實比上面的情況略微複雜,下面來看一個完成的調用過程:
1 task_fs 檔案系統程序 需要操作硬碟,給硬碟驅動程序task_hd() 發送一個msg。
2 假設這時,task_hd()處于空閑狀态,即等待在recv(any,msg)處,則會接受這個消息,并向硬碟發送指令。
3 硬碟完成工作後,會觸發中斷, Recv(interrupt, msg)傳回,并給task_fs 檔案系統程序發送硬碟資料消息。
4 task_fs 檔案系統程序 得到CPU時間時,會收到硬碟資料消息。
最簡單的硬碟驅動看來是告一段落了。下面就可以在此基礎上實作一個檔案系統,首先需要明白一個檔案系統的幾個基本要素:
1) 有地方存放metadata(一般為硬碟第二個扇區,因為第一個扇區為引導分區)
2) 有地方記錄扇區使用情況(位圖法)
3) 有地方記錄任一檔案資訊,包括檔案名,修改時間,占用哪些扇區等(一個i-node數組,每個元素包含了檔案名,屬性等資訊。同時也需要一個位圖來表示i-node數組的使用情況)
4) 檔案索引
以後就按照這個方法來組織硬碟結構,建立/删除/寫入/讀取檔案不過是按照這種格式來組織硬碟而已
為了實作多系統在硬碟上共存,必須将硬碟劃分為多個分區。每個系統占用一個分區。通過一個硬碟的引導扇區可以設定硬碟分區,硬碟最多可以分為4個實體分區。其中每個分區還可以繼續劃分為多個邏輯分區。
一般Linux的裝置分為主裝置号和次裝置号,主裝置号表示不同的實體裝置(硬碟,軟碟);次裝置号表示實體裝置上的分區。歸納一下就是:主裝置号告訴OS用哪個驅動程式來處理,次裝置号告訴驅動程式這是哪個裝置。
下面看一下硬碟驅動讀取和寫入資料的過程:
1 首先還是一樣task_fs() 檔案系統程序通過前面實作的IPC機制将讀取(寫入)硬碟資料消息發送(即複制)給task_hd()
2 task_hd()
…
Case:DEV_OPEN //如果某使用者程序需要打開一個檔案
為檔案内容配置設定扇區
配置設定一個i-node
配置設定inode-map
配置設定sector-map
建立檔案索引
Case: DEV_READ //讀檔案
首先找到将要讀取的檔案的檔案描述符和位置
根據檔案描述符通過驅動擷取的該檔案在硬碟的具體位置(即i-node)。
将硬碟内容複制到緩存
這一部分寫的不是很清楚,理一下思路,我的了解就是硬碟首先有一個檔案系統結構(fat32,ntfs,ext2等等),相對于的就有一個硬碟驅動程式用來管理硬碟使用的檔案系統并對上層提供檔案操作的接口。上層使用者需要讀取硬碟資料的時候,首先将消息發送給FS,然後FS會将該消息發送給硬碟驅動程式,硬碟驅動程式根據硬碟所使用的檔案格式來找到相應的資料并吧資料複制到OS提供的一個緩存中拱使用者程序使用。
記憶體管理
到了現在,還沒有完成了就是和記憶體相關的一些東西,首先看看如何建立一個程序(fork),每個shell就是一個子程序。
一個新的程序需要的條件有:
1 代碼,資料,堆棧(從父程序複制過來)
2 在程序表中占據一個位置
3 在GDT中有一個位置,指向了該程序對應的LTD,也就是指向了程序的資料和堆棧等
看一下linux中fork用法:
First_proc()
Int id = fork();
If(pid != 0) //這是父程序
Else 子程序運作
父子程序公用一個代碼段
可以看到,記憶體管理主要需要在OS中增加2個程序,分别為所有使用者的祖先程序(INIT0)和MM程序(memory Management process)。
Init0程序有些特殊,首先一般的程序的虛拟記憶體空間對使用者來說是0-4GB,但是init0的記憶體在這裡大緻等于核心占用記憶體的大小。
Fork的大概步驟如下:
1 使用者調用fork
2 通過消息機制發送FORK消息給MM
3 MM主循環完成建立程序的工作,主要流程為:找一個空閑的程序表項作為新程序的程序表項;讀取父程序的表示記憶體占用的LDT,從中取出父程序的代碼,資料和堆棧段記憶體首位址和範圍。
3 根據父程序的記憶體占用情況,配置設定相同大小的記憶體給子程序。(配置設定記憶體需要注意的就是記憶體不要重複使用),然後将父程序的記憶體中的内容複制給子程序。
4 如果父程序有打開檔案,要給FS程序發送一個消息來處理父子程序間的共享檔案。(FS利用計數器實作)
銷毀一個程序也差不多,唯一需要注意的就是MM在銷毀一個程序的時候,需要首先銷毀他的所有子程序。
接下來說明一下幾個概念,大多是複制過來的:
CRT:一個應用程式隻能調用2種東西:屬于自己的函數,以及中斷(系統調用就是軟中斷)。但是實際上,OS還為普通應用程式提供了一個CRT,這個庫裡面有已經編譯好的庫函數代碼。使用者應用程式可以很友善的使用CRT,而免去了很多中斷調用。
OS自帶應用程式:每個OS都可以附帶和安裝很多應用程式,簡單來說目的就是為了把應用程式從CD光牒中安裝到OS認識的硬碟中并知道位置,這樣在需要用時,OS可以自己找到該應用程式。例如,一個最簡單的應用程式:
_start
Main(){printf(***);}
把他安裝到OS中的步驟大緻如下:
1 編譯連接配接該應用程式并打包(設為inst.tar)
2 将打包的應用程式二進制檔案寫入OS安裝盤的某扇區(設該扇區号為X)。
3 啟動系統時,在mkfs()建立檔案系統時建立一個新的檔案cmd.tar,他對應的起始檔案扇區号為X。
4 在init程序中,将cmd.tar解壓,将其中包含的檔案(即為inst.tar)存入檔案系統。
Exec才等同于windows裡面的CreartProcess.
Exec() //參數即為新的代碼段
Exec的執行流程:
1 檢查參數個數并将它們依次存放到某連續記憶體處
2 向MM發送消息,消息體就是上面存放參數的連續記憶體。
3 MM消息接收到這個消息,将參數取出(由于exec所在程序和MM不是同一個程序,所有他們的虛拟位址不一樣,MM需要通過實體位址複制的方式來擷取消息)
4 根據exec傳來的參數,MM将要執行檔案複制到自己的緩沖區中
5 被執行檔案是elf格式,MM根據格式将被執行檔案的各個段放置到合适的空閑記憶體中。
6 建立棧
OK all done with simplest mode..
2011.1.15 AM8:45
i love joliet - teddy_xiong in ZNUFE
sylar MAIL: [email protected]