更詳細的講解和代碼調(diào)試演示過程,請參看視頻
Linux kernel Hacker, 從零構建自己的內(nèi)核
如果你對機器學習感興趣薛耻,請參看一下鏈接:
機器學習:神經(jīng)網(wǎng)絡導論
本節(jié)要實現(xiàn)的控制臺命令是dir, 熟悉該命令的同學都了解饼齿,它的作用是列舉出當前目錄下的文件信息蝙搔。問題在于吃型,我們當前的操作系統(tǒng)根本沒有硬盤,更沒有文件系統(tǒng)枉层,那么這個命令列舉的文件從哪里來呢赐写?由于我們的系統(tǒng)內(nèi)核是存儲在軟盤上的挺邀,因此,我們直接把軟盤當做系統(tǒng)硬盤泣矛,該命令列舉的是存儲在虛擬軟盤上的文件沦补。假設我們在虛擬軟盤上存儲了兩個文件夕膀,分別為abc.exe, efg.sys,文件的大小分別為256字節(jié)和128字節(jié),運行該命令后魂奥,控制臺顯示結果如下圖:
我們先看看FAT12文件系統(tǒng)是如何記錄文件信息的耻煤。每個存儲在FAT12系統(tǒng)上的文件都有一個文件目錄哈蝇,用于存儲文件的基本信息,這個目錄的數(shù)據(jù)結構如下:
struct FILEINFO {
unsigned char name[8], ext[3], type;
char reserve[10];
unsigned short time, date, clustno;
unsigned int size;
};
這個結構體的頭8個字節(jié)對應name,也就是文件名怜跑,也就是說存儲的文件性芬,它的名稱絕不能對于8個字符剧防。接下來三個字節(jié)對應的是文件擴展名峭拘,第12個字節(jié),也就是type, 它對應的是文件的類型棚唆,它的值意義如下:
0x01 只讀文件
0x02 隱藏文件
0x04 系統(tǒng)文件
0x08 非文件信息
0x10 目錄。
接下來的10字節(jié)為保留,這是微軟規(guī)定的瞎惫。跟著的兩個字節(jié)译株,首先是time 和 date 用于表示文件的生成時間歉糜,最后4個字節(jié)就是文件的大小。
當前伞辛,我們的虛擬軟盤所存儲的信息夯缺,其布局是這樣的踊兜,最開始的512字節(jié)是引導扇區(qū)代碼,接下來存儲的就是系統(tǒng)內(nèi)核于游,然后跟著是FAT12文件系統(tǒng)對應的文件目錄贰剥,也就是上面描述的數(shù)據(jù)結構鸠澈,存了幾個文件,就有幾個FILEINFO數(shù)據(jù)結構际度。由此先看看乖菱,我們當前的內(nèi)核到底有多大窒所,這樣我們才能計算出文件目錄在軟盤中的位置吵取,進而確定它加載到內(nèi)存后,所對應的位置皮官。
運行生成虛擬軟盤的java工程捺氢,通過輸出摄乒,我們可以看到残黑,當前系統(tǒng)內(nèi)核的大小總共是71個扇區(qū),每個扇區(qū)大小是512字節(jié)萍摊,因此內(nèi)核的總大小是71*512=0x8E00 字節(jié)冰木。我們的內(nèi)核加載到內(nèi)存時,是從起始地址0x8000開始的歇终,于是內(nèi)核在內(nèi)存中的末尾地址就是 0x10E00 = 0x8000 + 0x8E00, 由于我們把文件的目錄信息直接跟在內(nèi)核末尾的,因此文件目錄信息的起始地址就是0x10E00.
接著我們看看追葡,文件目錄信息是如何寫到虛擬磁盤上的:
import java.nio.ByteBuffer;
public class FileHeader {
private byte[] header = new byte[32];
public void setFileName(String s) {
int len = s.length() > 8 ? 8 : s.length();
for (int i = 0; i < len; i++) {
header[i] = (byte)s.charAt(i);
}
}
public void setFileExt(String s) {
int len = s.length() > 3 ? 3 : s.length();
for (int i = 0; i < len; i++) {
header[8+i] = (byte)s.charAt(i);
}
}
public void setFileType(Byte t) {
header[11] = t;
}
public void setFileTime(byte[] time) {
header[22] = time[0];
header[23] = time[1];
}
public void setFileDate(byte[] date) {
header[24] = date[0];
header[25] = date[1];
}
public void setFileClusterNo(byte[] no) {
header[26] = no[0];
header[27] = no[1];
}
public void setFileSize(int size) {
byte[] buf = ByteBuffer.allocate(4).putInt(size).array();
for (int i = 0; i < 4; i++) {
header[28+i] = buf[3 - i];
}
}
public byte[] getHeaderBuffer() {
return header;
}
}
一個FileHeader 類用來表示一個文件目錄,它對應前頭提到的FILEINFO數(shù)據(jù)結構谬返,它提供了幾個接口遣铝,用來設置文件名酿炸,擴展名等相關信息涨冀。
public class DiskFileSystem {
private Floppy floppyWriter;
private int beginSec;
private int fileHeaderCount = 0;
private byte[] buffer = new byte[512];
private int cylinder = 0;
public DiskFileSystem(Floppy disk, int cylinder, int sec) {
this.floppyWriter = disk;
this.beginSec = sec;
this.cylinder = cylinder;
}
public void addHeader(FileHeader header) {
if (fileHeaderCount >= 16) {
flashFileHeaders();
fileHeaderCount = 0;
buffer = new byte[512];
beginSec++;
}
byte[] headerBuf = header.getHeaderBuffer();
for (int i = 0; i < 32; i++) {
buffer[fileHeaderCount * 32 + i] = headerBuf[i];
}
fileHeaderCount++;
}
public void flashFileHeaders() {
floppyWriter.writeFloppy(Floppy.MAGNETIC_HEAD.MAGNETIC_HEAD_0 , cylinder, beginSec, buffer);
}
}
DiskFileSystem 用于把文件目錄結構寫入到磁盤的指定地方廷支。該類的初始函數(shù)需要傳入幾個參數(shù),disk 表示虛擬磁盤栓辜,cylinder 表示文件目錄所要寫入的柱面,sec 表示所在柱面的起始扇區(qū)垛孔。
上圖表示的是內(nèi)核寫入到磁盤的情況藕甩,可以看到,內(nèi)核最后寫入到虛擬軟盤的第4柱面周荐,第17扇區(qū)狭莱。由于我們的文件目錄緊跟著內(nèi)核存儲,因此概作,我們的文件目錄要寫入到第4柱面腋妙,第18扇區(qū),于是我們在生成虛擬軟盤時讯榕,在指定位置寫下文件目錄:
public void makeFllopy() {
writeFileToFloppy("kernel.bat", false, 1, 1);
//test file system
DiskFileSystem fileSys = new DiskFileSystem(floppyDisk, 4, 18);
FileHeader header = new FileHeader();
header.setFileName("abc");
header.setFileExt("exe");
byte[] date = new byte[2];
date[0] = 0x11;
date[1] = 0x12;
header.setFileTime(date);
header.setFileDate(date);
header.setFileSize(256);
fileSys.addHeader(header);
header = new FileHeader();
header.setFileName("efg");
header.setFileExt("sys");
header.setFileSize(128);
fileSys.addHeader(header);
fileSys.flashFileHeaders();
//test file system
floppyDisk.makeFloppy("system.img");
}
上面代碼先創(chuàng)建了兩個文件目錄骤素,這兩個文件名分別為abc.exe 和 efg.sys, 這兩個文件是虛擬的痕檬,我們只在磁盤上構造它們的目錄,在磁盤上并沒有這兩個文件的實際數(shù)據(jù)。磁盤上有了文件目錄后荒澡,我們要編寫內(nèi)核代碼,讓內(nèi)核能夠讀取顯示相關信息饥侵。
首先在global_define.h添加如下代碼:
#define ADR_DISKIMG 0x10E00
struct FILEINFO {
unsigned char name[8], ext[3], type;
char reserve[10];
unsigned short time, date, clustno;
unsigned int size;
};
其中ADR_DISKIMG 就是文件目錄在內(nèi)存中的起始地址狼忱,F(xiàn)ILEINFO對應的就是我們前頭提到過的FAT12文件系統(tǒng)對應的文件目錄信息佃却。當我們在控制臺輸入命令dir 后,控制臺從指定位置,把FILEINFO結構數(shù)據(jù)讀取出來赦邻,并把對應的文件名和文件大小顯示出來按声,代碼如下,在write_vga_desktop.c中:
void console_task(struct SHEET *sheet, int memtotal) {
....
struct FILEINFO* finfo = (struct FILEINFO*)(ADR_DISKIMG);
for(;;) {
....
else if (strcmp(cmdline, "dir") == 1) {
while (finfo->name[0] != 0) {
char s[13];
s[12] = 0;
int k;
for (k = 0; k < 8; k++) {
if (finfo->name[k] != 0) {
s[k] = finfo->name[k];
}else {
break;
}
}
int t = 0;
s[k] = '.';
k++;
for (t = 0; t < 3; t++) {
s[k] = finfo->ext[t];
k++;
}
showString(shtctl, sheet, 16, cursor_y, COL8_FFFFFF, s);
int offset = 16 + 8*15;
char* p = intToHexStr(finfo->size);
showString(shtctl, sheet, offset, cursor_y, COL8_FFFFFF, p);
cursor_y = cons_newline(cursor_y, sheet);
finfo++;
}
....
}
....
}
當控制臺收到dir命令時,它先從地址ADR_DISKIMG開始扛拨,讀取第一個文件目錄信息,如果文件名的起始第一個字符不是0计盒,那么表明接下來的32字節(jié)表示有效的文件目錄信息拔第,于是代碼先獲取文件名懈涛,然后獲取文件擴展名,最后得到文件大小,然后依次把這些信息顯示到控制臺上。接著越過32字節(jié)晨横,再判斷接下來的32個字節(jié)是否表示有效的文件目錄信息,如果是伙狐,再按照原有邏輯,繼續(xù)顯示相關信息唉侄。
我們在虛擬軟盤中寫入了兩個文件目錄信息候生,因此運行該命令后,控制臺正確顯示了相關文件的目錄信息唠粥。
本節(jié)內(nèi)容稍微有點燒腦,請參看視頻官份,以便獲得更詳細的講解和代碼調(diào)試演示。
歡迎關注公眾號,讓我們一起學習搁凸,交流,成長: