由於一直想寫一個自己的操作系統,網上推薦了《linux內核完全注釋》。自學了一個星期,感覺這本書還是很好的,同時寫下關於內核代碼的理解,如果有什么不對的對方,歡迎大家一起來交流。
在內核引導啟動程序中,有3個文件,bootsec.s,setup.s head.s。關於這3個源代碼,網上有很多人都有詳細的解釋,但是有很多人的文章中都是對每行代碼的解釋,但是關於整個代碼的整體框架沒有很多的解釋。在這里我想提出自己對代碼的理解,我不會每行都解釋,只是對很重要的部分做出自己的理解。
關於bootsec.s主要做了如下的幾件事:
- 由於PC開機后,BIOS將可移動的設備的第一個扇區,讀入到了0x7c00處,總共512B,bootsec.s就在這里,它將自己移到了ox9000處來執行;
- 初始化堆棧;關於這個堆棧我對它的理解就是在后面的程序,比如read_track中就用到了pop和push之類的指令,因此在這設置了‘
- 將setup.s的模塊裝載在bootsec.s的后面;
- 獲取驅動器的參數,這里應該是值軟盤,主要獲取到每個磁道的扇區數量;
- 在屏幕中輸出“loading system.....”,然后裝載system模塊即內核模塊;
- 確定根文件系統設備;
- 段間跳轉,到setup.s中執行;
下面是我對bootsec.s中部分代碼的理解。
bootsec.s代碼移動到ox9000中
在這里主要是提醒大家,關於cx的值,由於是要移動一個扇區的數據,一個扇區的大小是512B,這里的cx=256,但是movsw表示移動兩個B,所以256*2=512B,這里大家會忽略,沒注意;關於load_setup
仔細看int 0x13這個中斷的各個參數,這里比較重要的是es:bx 將指向setup模塊的在內存中的位置。在移動bootsec.s的代碼最后有一個jmpi go,INITSEG這個指令,此時的cs=0x900不再時0x7c00了,es=cs,於是es:bx指向了0x900:0200處,從而實現了裝載setup模塊的功能。
關於ok_load_setup
這里主要是來談談這兩條指令。從代碼的第241行中可以看出sectors是個標量指向一個word長的地址。seg cs表明了sectors的段地址是cs,而不是ds。而且seg cs 的作用范圍只有下一行,不會延生到其他地方。
最后介紹這篇文章中最重要的read_it
不知道大家在讀這段代碼的時候是怎么理解的,反正我理解了很長時間,不知道它到底想干什么。網上很多都是注釋了每一行,但是沒有解釋總的在干什么,整體的流程是什么。標量間代碼作用
- ok1_read:主要的功能是確定了ax的值,其實嚴格的說是確定了al的值,因為al的值是代表了要讀取的扇區的值,關於最后的幾行代碼 這幾行代碼是我當時極度不能理解的因為當時我認為ax=0,bx=0,最后不都是0嗎?:),我想說這幾行代碼是以后執行的,到時后bx的值不再是0,而是代表了一個段內讀取了的數據,sub是不帶進位的0-bx正好是64k-段內已讀的數據(不得不佩服linus的基本功),從而得到了段內剩余的空間,從而可以求出還可以讀取最大的扇區數。
- ok2_read:調用了read_track(),read_track()的功能其實可以看成read_track(ax),根據ax,主要是al來確定一個磁道內從哪個扇區開始讀數據,從而讀取一個磁道的數據;然后ax=read_track(ax)(偽代碼而已),ax表示讀取的扇區的值,然后再加上已讀的扇區數后比較是否等於每個磁道的扇區數,如果不等,則調用ok3_read(),否則讀取下一個磁頭1,(軟盤有兩個磁頭,0和1,這和硬盤不一樣,硬盤磁頭比較多)
- ok4_read:這里為什么不從ok3_read寫起呢,主要是因為linus的代碼能力的厲害之處,大家慢慢也會懂的,只可意會。ok_read4的主要的功能是重新賦值磁頭和ax即扇區的起始地址。
- ok3_read:主要是確定了bx的值和es的值。
這里add bx,cx 是確定了bx的值,即段內已經讀取的數據大小,如果bx沒有溢出即超過0xffff,則跳轉到開始的循環,否則段基址es+=0x1000,再從新循環。
接下了主要講一下關於我對這段代碼的理解。由於軟盤只有兩個磁頭0和1,且只有18個扇區,每個扇區的大小是512B從而代碼開始處的流程應該是如下的
- 在ok1_read中從0磁頭讀取的數目最多(18-6)*512B,不會溢出,則會進入到ok2_read()中;如果bx不等於0,且數值比較接近0xffff時會溢出,則卻確定該段內剩余的地址可以讀取最大的扇區數;
- 到ok2_read中,從0磁頭讀取從能夠ax開始的整個磁道剩余的扇區數,cx=讀取的扇區數,在加上已讀的扇區數確定是否全部讀完,若是則下一步,否則到4;
- 到ok4_read,磁頭變成了1,(只有兩個磁頭,如果原磁頭是1,則增加磁道track),ax=0。
- 到ok3_read中,此時ok3_read的操作是針對下一次循環的,根據這一次的循環設置下一次循環的一些變量,bx=段內已經讀取的數據的大小,如果超出64k則增加段基址,由於add是無進位的(這里又體現了linus的高明了);
- 從新返回到1,執行循環,知道es的值>ENDSEG;
到這可以理解為什么我要先介紹ok4_read了,而不是ok3_read了,如果在源代碼中把ok3_read放在ok4_read之前那么代碼會多加幾個jmp命令,雖然便於我們理解,但是代碼長度變長了,原來的代碼體現了代碼的魅力。
轉載的時候請注明出處。