tag:blogger.com,1999:blog-65943601670105134752024-03-14T00:56:54.579+08:00Adrian's BlogWhat a fascinating Linux it is!Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.comBlogger53125tag:blogger.com,1999:blog-6594360167010513475.post-91520432274074103502015-03-04T15:34:00.000+08:002015-03-04T15:34:59.061+08:00[Linux Kernel] ACCESS_ONCE Macro最近看Kernel code,看到這個ACCESS_ONCE巨集,仔細看了它的定義發現挺有趣的,順便記錄一下。
<br />
<br />
ACCESS_ONCE顧名思義,就是確實地讀取所指定記憶體位址的內容值,且僅限這一次。所以在這個巨集肯定有volatile關鍵字,其原始定義如下:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
</code></pre>
<b><br /></b>
<b><u><span style="font-size: large;">使用情境
</span></u></b><br />
底下程式碼擷取於kernel/locking/mutex.c (linux-v4.0-rc1)<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> while (true) {
struct task_struct *owner;
...
/*
* If there's an owner, wait for it to either
* release the lock or go to sleep.
*/
owner = ACCESS_ONCE(lock->owner);
if (owner && !mutex_spin_on_owner(lock, owner))
break;
...
</code></pre>
<br />
然而,如果沒有加入ACCESS_ONCE macro,程式碼如下:<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> while (true) {
struct task_struct *owner;
...
/*
* If there's an owner, wait for it to either
* release the lock or go to sleep.
*/
owner = lock->owner;
if (owner && !mutex_spin_on_owner(lock, owner))
break;
...
</code></pre>
<br />
由於,編譯器優化關係[1]發現"owner = lock->owner"在這個while loop沒有被更改,所以編譯器把這行程式碼放到while loop外面,如此不用每次都實際地讀取lock->owner,其程式碼變成:<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> struct task_struct *owner;
owner = lock->owner;
while (true) {
...
if (owner && !mutex_spin_on_owner(lock, owner))
break;
...
</code></pre>
<br />
問題來了,"lock->owner"有可能被其它task修改,如此造成資料不一致。<br />
因此使用ACCESS_ONCE可以防止編譯器做相關優化工作,並確保每次都能到實體記憶體位址讀取。其做法便是將某參數暫時性地轉換成具volatile型態。如此,存取該參數在非引入ACESS_ONCE macro的地方 (不具volatile特性),仍可享用編譯器優化的好處。<br />
<br />
<span style="font-size: large;"><b><u>延伸閱讀</u></b></span><br />
2014 11月 <span style="background-color: white;">Christian</span><span style="background-color: white;"> </span><span style="background-color: white;">Borntraeger在lkml提出ACCESS_ONCE用gcc 4.6/4.7編繹所造成的問題,詳見<a href="http://lwn.net/Articles/624144/">compiler bug gcc4.6/4.7 with ACCESS_ONCE and workarounds</a>。其問題在於: 如果所傳入的參數是non-scalar型態,gcc 4.6/4.7會把volatile關鍵字自動拿掉,如下程式碼:</span><br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> typedef struct {
unsigned long pte;
} pte_t;
pte_t pte;
pte_t p = ACCESS_ONCE(pte);
</code></pre>
<br />
上述程式碼 (存取struct) 可能被編譯器優化 (因為gcc 4.6/4.7針對non-scalar型態會自動地去掉volatile)。<br />
<br />
最直覺的解法就是存取scalar-type的參數,如下所示:<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> unsigned long p = ACCESS_ONCE(pte->pte);
</code></pre>
<br />
但此方法需要更改所有使用ACCESS_ONCE的程式碼,這將是一件很無聊的工作。經過一番lkml討論,<span style="background-color: white;">Christian決定更改ACCESS_ONCE (參照<a href="http://lwn.net/Articles/624392/">lkml patch set</a>),其程式碼如下:</span><br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #define __ACCESS_ONCE(x) ({ \
__maybe_unused typeof(x) __var = (__force typeof(x)) 0; \
(volatile typeof(x) *)&(x); })
#define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))
</code></pre>
<br />
其解法就是限制ACCESS_ONCE只能傳入scalar type參數 (union也可以,不過,有些限制,詳見include/linux/compiler.h檔裡面的註解)。<br />
<br />
如果傳入non-scalar type參數,會發生編繹錯誤。示範程式碼與編繹結果如下:<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">1: #include <stdio.h>
2:
3: #define __maybe_unused __attribute__((unused))
4:
5: #define __ACCESS_ONCE(x) ({ \
6: __maybe_unused typeof(x) __var = 0; \
7: (volatile typeof(x) *)&(x); })
8:
9: #define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))
10:
11: typedef struct {
12: unsigned long pte;
13: } pte_t;
14:
15: int main(void)
16: {
17: pte_t pte;
18:
19: ACCESS_ONCE(pte);
20:
21: return 0;
22: }
23:
</code></pre>
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> $ gcc -o access_once access_once.c
access_once.c: In function ‘main’:
access_once.c:19:2: error: invalid initializer
ACCESS_ONCE(pte);
^
</code></pre>
<br />
其出錯原因在於這段程式碼 "__maybe_unused typeof(x) __var = 0;",它限制所傳入參數必須為scalar type參數 (因為 "__var = 0")。如果傳入一結構型態,則必須"__var = {0}",才能避免編繹錯誤。只能說Linux Kernel好多程式藝術在裡面啊!!!!!<br />
<br />
因此,如果要存取non-scalar type參數,請改用READ_ONCE與ASSIGN_ONCE,如此便能避免編繹錯誤。<br />
<br />
在linux-4.0-rc1原始碼,__ACCESS_ONCE導入一新patch,用以這個編繹警告 "Using plain integer as NULL pointer",詳見<a href="http://www.spinics.net/lists/kvm/msg112478.html">lkml - kernel: Fix sparse warning for ACCESS_ONCE</a>。<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9dZ3gXdI1edR74R74d8trefV5GpEpEdOlA93IyVv_BtFEjkL6pvwsIkvusbKhJqQuZqwPXOnGs5UncrEGCafrgi_0sP7oh8U24yvyV0ZcOnoH8fg-PCZ_NNr01Zmxk7iJbIRjBuECHHUl/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #define __ACCESS_ONCE(x) ({ \
__maybe_unused typeof(x) __var = (__force typeof(x)) 0; \
(volatile typeof(x) *)&(x); })
#define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))
</code></pre>
<br />
<b>[Reference]</b><br />
<a href="http://lwn.net/Articles/508991/">ACCESS_ONCE()</a><br />
<a href="http://lwn.net/Articles/624126/">ACCESS_ONCE() and compiler bugs</a><br />
<div>
<a href="http://lwn.net/Articles/624144/">compiler bug gcc4.6/4.7 with ACCESS_ONCE and workarounds</a></div>
<div>
<div>
<a href="http://lwn.net/Articles/623046/">LKML-ACCESS_ONCE and non-scalar accesses</a></div>
</div>
<div>
<a href="http://www.spinics.net/lists/kvm/msg112476.html">LKML-current ACCESS_ONCE patch queue</a></div>
<div>
<br /></div>
<div>
<br /></div>
Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-55330712136543009272012-03-08T22:55:00.003+08:002012-03-08T23:02:33.362+08:00Redirecting standard output and standard errorformal form:<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>$ ifconfig eth100 > msg.log 2>&1<br /></code></pre><br /><br />can be shortened like that:<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>$ ifconfig eth100 &> msg.log<br /></code></pre><br />or<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>$ ifconfig eth100 >& msg.log<br /></code></pre><br /><br />【Reference】<br /><a href="http://www.gnu.org/software/bash/manual/bashref.html">Bash Reference Manual</a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-17320225822459456972011-08-11T20:14:00.021+08:002011-08-20T00:13:02.937+08:00[C語言] 兩變數內容值互換技巧記得大學時, C語言課程一定會談到兩變數內容值互換技巧, 其方法利用暫存空間實現, 如底下程式碼所示:
<br />
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>void swap(int *a, int *b)
<br />{
<br /> int tmp;
<br />
<br /> tmp = *a;
<br /> *a = *b;
<br /> *b = tmp;
<br />}
<br /></code></pre>
<br />
<br />然而, 有幾個方法不使用暫存空間便能達到此目的.
<br />
<br /><span style="font-weight:bold;">方法一 使用三次互斥或 (Exclusive OR, XOR)
<br /></span><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>void swap(int *a, int *b)
<br />{
<br /> if(*a != *b) {
<br /> *a = *a ^ *b;
<br /> *b = *a ^ *b;
<br /> *a = *a ^ *b;
<br /> }
<br />}
<br /></code></pre>
<br />
<br /><b>演算式如下所示:</b>
<br /><a href="https://picasaweb.google.com/lh/photo/SZSM1H6Z-tjUkb8irXMjYg?feat=embedwebsite"><img src="https://lh6.googleusercontent.com/-kOGZ5Fw66VE/TkXcNLVEZyI/AAAAAAAACkc/yxmD091wBZw/s800/xor_swap.jpeg" height="108" width="391" /></a>
<br />
<br />簡化上段程式碼: 試想底下程式碼有沒有什麼問題?
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>#include <stdio.h>
<br />
<br />void swap(int *a, int *b)
<br />{
<br /> if(*a != *b)
<br /> *a ^= *b ^= *a ^= *b;
<br />}
<br />
<br />int main(void)
<br />{
<br /> int a = 11, b = 3;
<br />
<br /> printf("===== before swapping =====\n");
<br /> printf("a: %d, b: %d\n", a, b);
<br />
<br /> swap(&a, &b);
<br />
<br /> printf("===== after swapping =====\n");
<br /> printf("a: %d, b: %d\n", a, b);
<br />
<br /> return 0;
<br />}
<br />
<br /></code></pre>
<br /><b>直接看執行結果:</b>
<br /><a href="https://picasaweb.google.com/lh/photo/9n2tueFC2VR1RSpw_-o6pA?feat=embedwebsite"><img src="https://lh4.googleusercontent.com/--9HuTt6b-Z8/TkXsY0GPdwI/AAAAAAAACk0/pykT7Cpti0k/s640/swap_one_statement_result.jpeg" height="127" width="640" /></a>
<br />
<br />為什麼會這樣呢? 筆者將此C程式碼用gcc編譯成組合語言, 用以了解其運作邏輯, 底下為swap函式片段程式碼, 完整<a href="https://docs.google.com/uc?id=0ByaIwJ06Bi-2NzFiZTU0ZWEtZjJiNC00Mzc0LTk0NjYtODBhMDA4OTljYTk4&export=download&hl=en_US">組合語言程式碼</a>請按此連結下載.<a href="https://picasaweb.google.com/lh/photo/AYxP6j-O95-fpKUZt4BcuA?feat=embedwebsite"><img src="https://lh3.googleusercontent.com/-Q1AZrtDE3BA/TkX3IvwHJJI/AAAAAAAACl4/bMrwzakbaWg/s640/swap_one_statement_assembly.jpeg" height="403" width="640" /></a>
<br />
<br /><b>程式堆疊示意圖</b>
<br /><a href="https://picasaweb.google.com/lh/photo/2UyacUIQHZHZoQjkT4zXyA?feat=embedwebsite"><img src="https://lh3.googleusercontent.com/-FROdOtwLWA0/TkaiNADkK9I/AAAAAAAACm8/lD9pB4d-yYk/s640/swap_one_statement_stack.jpeg" height="312" width="640" /></a>
<br /><b>程式說明:</b><div>由於使用一行程式碼做三次XOR, gcc編譯器會先把*a, *b, *a, *b的內容放在edx(11), ecx(3), ebx(11), eax(3)暫存器 (line 9-16), 然後再分別對這四個暫存器做三次的XOR:</div><div><ul><li>line 18-20: 第一次XOR (eax=3 XOR ebx=11), 並將其結果 (ebx=8)回存到變數a的記憶體位址, 如下圖所示: <a href="https://picasaweb.google.com/lh/photo/AzwkOjQs1NFd-5z5bHhPDw?feat=embedwebsite"><img src="https://lh5.googleusercontent.com/-4wOOedX8rIo/TkaiMlO8hQI/AAAAAAAACmw/hVmesCuoX8w/s640/swap_one_statement_1stXOR.jpeg" height="383" width="640" /></a></li><li>line 21-25: 第二次XOR (eax=8 XOR ecx=3), <meta equiv="content-type" content="text/html; charset=utf-8">並將其結果 (ecx=11)回存到變數b的記憶體位址, <meta equiv="content-type" content="text/html; charset=utf-8">如下圖所示: <a href="https://picasaweb.google.com/lh/photo/nfsesTjOV5nLamrmbabhjQ?feat=embedwebsite"><img src="https://lh3.googleusercontent.com/-z-c5NRQ2y5U/TkaiMz39UeI/AAAAAAAACm4/KcIV1huADL8/s640/swap_one_statement_2ndXOR.jpeg" height="394" width="640" /></a></li><li>line 26-30: <meta equiv="content-type" content="text/html; charset=utf-8">第三次XOR (eax=11 XOR edx=11), 並將其結果 (edx=0)回存到變數a的記憶體位址<meta equiv="content-type" content="text/html; charset=utf-8">, 如下圖所示: <a href="https://picasaweb.google.com/lh/photo/XvlBXKDGw6ygM-JtM5SFKA?feat=embedwebsite"><img src="https://lh6.googleusercontent.com/-VnwU-kJq66k/TkaiMyGcNXI/AAAAAAAACm0/fH3yQg47mx0/s640/swap_one_statement_3rdXOR.jpeg" height="384" width="640" /></a></li></ul></div><div>所以問題在於, 程式設計師不能在一行程式碼中, 對同一變數做兩次的運算, 否則會造成不可預知的結果, 切記!
<br /><div>
<br /><span style="font-weight:bold;">方法二 使用加減法
<br /></span><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>void swap(int *a, int *b)
<br />{
<br /> if(*a != *b) {
<br /> *a = *a + *b;
<br /> *b = *a - *b;
<br /> *a = *a - *b;
<br /> }
<br />}
<br /></code></pre>
<br /><b>演算式如下所示:
<br /></b><a href="https://picasaweb.google.com/lh/photo/hniaFnES6CpFtuSNBNPGMA?feat=embedwebsite"><img src="https://lh3.googleusercontent.com/-_GvgDq8CoUQ/TkXcM1qCltI/AAAAAAAACkc/07as5Mn4bvg/s800/add_swap.jpeg" height="102" width="391" /></a>
<br /><meta equiv="content-type" content="text/html; charset=utf-8"><span class="Apple-style-span" style="color: rgb(51, 51, 51); line-height: 20px; font-size:14px;" ><b>
<br /></b></span></div><div><span class="Apple-style-span" style="color: rgb(51, 51, 51); line-height: 20px; "><span class="Apple-style-span">【Reference】</span></span>
<br />[1] <a href="http://www.i-programmer.info/babbages-bag/498-the-magic-swap.html">XOR - the magic swap</a>
<br />[2] <a href="http://everything2.com/title/XOR+swap">XOR swap </a></div></div>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com2tag:blogger.com,1999:blog-6594360167010513475.post-25175024157420876572011-08-05T19:14:00.005+08:002011-08-27T10:10:45.935+08:00簡介C語言volatile關鍵字及其陷阱volatile變數代表其所儲存的內容會不定時地被改變,宣告volatile變數用來告訴編譯器 (Compiler) 不要對該變數做任何最佳化操作,凡牽涉讀取該volatile變數的操作,保證會到該變數的實體位址讀取,而不會讀取CPU暫存器的內容 (提升效能) 。舉個例子,某一硬體狀態暫存器 (Status Register)就必須宣告volatile關鍵字,因為該狀態暫存器會隨時改變,宣告volatile便可確保每次讀取的內容都是最新的。
<br />
<br /><span style="font-weight:bold;">Volatile關鍵字的應用範疇</span>
<br /><blockquote>1. 硬體暫存器,如狀態暫存器。
<br />2. 多執行緒所共用的全域變數。
<br />3. 中斷服務函式 (Interrupt Service Rountie,ISR)所使用的全域變數。</blockquote>
<br />
<br /><span style="font-weight:bold;">Volatile陷阱</span>
<br />想想底下範例是否有問題?
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>
<br />#include <stdio.h>
<br />
<br />int square(volatile int *var)
<br />{
<br /> return *var **var;
<br />}
<br />
<br />int main(void)
<br />{
<br /> int var = 5;
<br />
<br /> printf("result: %d\n", square(&var));
<br /> return 0;
<br />}
<br /></code></pre>
<br />
<br />其問題在於square函式的平方算式,*var**var,此指令代表到var位址讀取其內容。然而,var位址可能儲存硬體暫存器,這些暫存器內容會隨時間而改變 (例如: 狀態暫存器),有可能第一次讀取的時候為4, 下一次讀取為5, 導致計算出來的值不正確。
<br />
<br />因此,避免此錯誤發生便是在square函式宣告一local變數,看底下程式範例較為清楚:
<br />
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>
<br />#include <stdio.h>
<br />
<br />int square(volatile int *var)
<br />{
<br /> <span style="font-weight:bold;">int local_var = *var;
<br />
<br /> return local_var * local_var;</span>
<br />}
<br />
<br />int main(void)
<br />{
<br /> int var = 5;
<br />
<br /> printf("result: %d\n", square(&var));
<br /> return 0;
<br />}
<br /></code></pre>
<br />
<br />【Reference】
<br />[1] <a href="http://www.netrino.com/node/80">How to Use C's volatile Keyword</a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-54118420950658492582011-07-26T21:07:00.005+08:002011-07-26T23:21:40.242+08:00[Linux Kernel] Allocate memory buffer whose starting address is aligned with the specific memory size撰寫裝置驅動程式時, 常常會因為硬體需求, 需配置一塊記憶體空間用來映射至硬體的I/O或記憶體空間, 且此已配置記憶體空間的起始位址往往都必須對齊某一固定大小的記憶體。舉例來說,欲配置4906位元的記憶體空間,且起始位址需對齊16位元。底下為C語言範例程式。
<br />
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>void *memory_align(unsigned int size, unsigned int aligned_size)
<br />{
<br /> void *ptr;
<br />
<br /> unsigned int mask = ~(aligned_size -1);
<br />
<br /> if((ptr = malloc(sizeof(size + (aligned_size - 1)))) == NULL) {
<br /> perror("memory allocation failed");
<br /> return NULL;
<br /> }
<br />
<br /> ptr = (void *) ((unsigned int) (ptr + (aligned_size - 1)) & mask);
<br />
<br /> return ptr;
<br />}
<br />
<br /></code></pre>
<br /><div><div><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>unsigned int mask = ~(aligned_size -1);</code></pre></div><div></div></div><div><meta equiv="content-type" content="text/html; charset=utf-8"><span class="Apple-style-span" style="border-collapse: collapse; font-family: arial, sans-serif; font-size: 13px; ">此mask變數為位元遮罩, 假設aligned_size=16, 則mask=0xFFFFFFF0</span>。</div><meta equiv="content-type" content="text/html; charset=utf-8"><div>
<br /></div><div><div><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>ptr = malloc(sizeof(size + (aligned_size - 1)))</code></pre></div><div></div></div><div><meta equiv="content-type" content="text/html; charset=utf-8"><span class="Apple-style-span" style="border-collapse: collapse; font-family: arial, sans-serif; font-size: 13px; ">由於我們欲讓所配置的記憶體起始位址對齊aligned_<wbr>size, 所以需向系統配置size+(aligned_size - 1)大小的記憶體空間</span>。</div><meta equiv="content-type" content="text/html; charset=utf-8"><div>
<br /></div><div><div><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code>ptr = (void *) ((unsigned int) (ptr + (aligned_size - 1)) & mask);</code></pre></div><div></div></div><div><meta equiv="content-type" content="text/html; charset=utf-8"><span class="Apple-style-span" style="border-collapse: collapse; font-family: arial, sans-serif; font-size: 13px; ">把所配置的記憶體起始位址加上(aligned_size - 1),然後跟mask變數做位元遮罩,<wbr>如此便能取得正確的記憶體起始位址</span>。</div><meta equiv="content-type" content="text/html; charset=utf-8">
<br />下圖為詳細範例計算<div><a href="https://picasaweb.google.com/lh/photo/bpYeUgib9mLKfcRxyI_jWg?feat=embedwebsite"><img src="https://lh3.googleusercontent.com/-DS8vp4zgdEw/Ti6-XrkOogI/AAAAAAAACiw/fqldpdOSoAI/s400/calulation.jpeg" height="400" width="302" /></a></div><div>
<br /></div><div><a href="https://docs.google.com/leaf?id=0ByaIwJ06Bi-2OGI0Y2QzNjAtZGRhZS00MGY4LTg2NWEtMjdhMjA1NjY3MzJh&hl=en_US">範例程式下載</a></div><div>
<br /></div>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-44930154306221693082010-12-28T18:16:00.004+08:002010-12-28T18:44:24.474+08:00解決"sudo: must be setuid root"的問題今天在自己的Linux桌機 (Ubuntu 10.10) 幹了一件傻事:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$ sudo chown -R adrian.adrian /usr/<br /></code></pre>結果導致使用sudo會有底下的問題:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$ sudo su<br />sudo: must be setuid root<br /></code></pre>這下可high了, Ubuntu在安裝的時候並沒有讓管理者設定root密碼, 所以無法使用root登入, 沒有root權限什麼事都不能做啊! 底下解決方式:<br /><br /><ol><li>重新啟動Ubuntu,在grub選單中選擇有recovery mode的選項, Ex: "Ubuntu, with Linux 2.6.35-23-server (recovery mode)"</li><li>以recovery模式開機後, 會出現"Recovery Menu"選單, 選擇"root Drop to root shell prompt"選項就可進入root shell prompt。</li><li>利用ls -l觀看sudo資訊,所以是擁有此程式使用者的問題。<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>-rwxr-xr-x 2 adrian adrian 147872 2010-09-01 04:39 /usr/bin/sudo<br /></code></pre></li><li>執行底下指令便可大功告成:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code># chwon root:root /usr/bin/sudo<br /># chmod 4755 /usr/bin/sudo<br /># reboot<br /></code></pre></li><li>最後還是乖乖地把/usr/重新設定回root。<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$ sudo chown root.root /usr/<br /></code></pre><br /></li></ol>【Reference】<br />[1] <a href="http://ohioloco.ubuntuforums.org/showthread.php?t=219767">Ubuntu forum</a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-84088523714688839052010-12-21T10:09:00.018+08:002015-06-05T20:26:32.734+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (七) 利用Call Gate與TSS (Task-State Segment)實現特權等級的轉換<a href="http://adrianhuang.blogspot.com/2010/12/gnu-assembler-call-gate.html">上篇文章</a>僅介紹如何利用Call Gate,文中並未提及如何實現特權等級的轉換,也就是從低特權等級進入高特權等級,就如同Linux的user space (Privilege 3或稱Ring 3)進入kernel space (Privilege 0或稱Ring 0)。<br />
<br />
<span style="font-weight: bold;">簡介TSS (Task-State Segment)</span><br />
本文僅簡單說明TSS在本文被使用的地方,如欲詳盡說明請參考[2]<a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw"><em></em></a>之第七章。<br />
<br />
<span style="font-weight: bold;">Task Structure</span><br />
Figure 1為一個工作任務 (Task)的結構圖,一個工作任務分為兩大部份: 1. 工作任務的執行空間 (Task Execution Space),其中包含CS、SS以及若干個DS,也就是Figure 1右上角的三個區段。2. TSS: 由工作任務執行空間與一個儲存空間 (用來儲存工作任務的狀態)所組成。Figure 1又說明另一件事: TSS的Segment Selector必須儲存在Task Register。因此在使用TSS之前,必須使用LTR指令將TSS之Segment Selector載入至Task Register。<br />
<br />
<div style="text-align: center;">
<a href="http://picasaweb.google.com/lh/photo/CtsxqECJlUNjdkwvxrOOcg?feat=embedwebsite"><img height="342" src="https://lh6.ggpht.com/_VP7dWGNif7M/TRQTHjxHviI/AAAAAAAACak/PX2sd_MTkC4/s640/structure_of_a_task.jpeg" width="640" /></a></div>
<div style="text-align: center;">
<span style="font-weight: bold;">Figure 1 Structure of a Task</span></div>
<span style="font-weight: bold;"><br />TSS Memory Layout</span><br />
Figure 2為32位元TSS記憶體空間配置圖,如下所述:<br />
<ul>
<li>ESP0、SS0、ESP1、SS1、ESP2與SS2: 分別為特權等級0、1與2的堆疊區段與堆疊指標,也就是Figure 1右下角三個 "Stack Seg. Priv. Level x"。本文利用ESP0與SS0實現特權等級的轉換 (Ring 3轉換至Ring 0),因此作者僅設定TSS的這兩個欄位,其它欄位則設為0。</li>
<li>其它欄位在此不進一步探討,有興趣的網友可以參考[2]。</li>
</ul>
<span style="font-weight: bold;"></span><br />
<div style="text-align: center;">
<div style="text-align: center;">
<a href="http://picasaweb.google.com/lh/photo/fhXs252Ib6Fogui1b5RS4w?feat=embedwebsite"><img height="640" src="https://lh5.ggpht.com/_VP7dWGNif7M/TRQTHu8W_uI/AAAAAAAACao/zUtGEv44HLI/s640/TSS_memory_layout.jpeg" width="493" /></a></div>
<span style="font-weight: bold;">Figure 2 TSS Memory Layout</span><br />
<br /></div>
<div>
<b><span class="Apple-style-span">原始碼下載</span></b></div>
由於篇幅的關係,往後該系列文章將不張貼程式碼,將提供連結下載方式,<a href="http://hpds.ee.ncku.edu.tw/~adrian/blog_archive/os_dev/pe-call-gate-tss.tar.gz">原始碼下載點</a>。<br />
<br />
<br />
<span style="font-weight: bold;">作業系統程式碼說明</span><br />
Figure 3為作業系統程式碼解說圖,此圖說明GDT與LDT的記憶體配置圖與程式流程圖,其流程圖並非以傳統方式表示。取而代之,改以紅色圈圈的數字代表程式流程並搭配GDT與LDT記憶體配置圖,如此更能清楚地明白程式執行流程。底下將針對每一步驟 (紅色圈圈的數字)做詳盡的解釋:<br />
<br />
<div style="text-align: center;">
<span style="text-decoration: underline;"><a href="http://picasaweb.google.com/lh/photo/B_6Ao9mjUHX5oBrtxQ5CNw?feat=embedwebsite"><img height="712" src="https://lh6.ggpht.com/_VP7dWGNif7M/TRRZPyR-OAI/AAAAAAAACbE/yOJxbM295bQ/s800/code_flow.jpeg" width="800" /></a></span></div>
<div style="text-align: center;">
<span style="font-weight: bold;">Figure 3 High Level Perspective of OS code</span></div>
<br />
<ol>
<li>使用ljmp指令由真實模式轉換至32位元保護模式 (Ring 0)。其片段程式碼如下所示:<br /><pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>
/* Jump to protected-mode OS code *
* ljmpl prototype: *
* ljmpl segment_selector_of_CS offset */
ljmpl $SegSelectorCode32, $0</code></pre>
<br />此區段程式碼將Msg1輸出至螢幕,並將LDT的segment selector載入至LDTR,接著使用lcall指令跳至LDT的CS,如下所示:<br /><pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> /* Load LDT selector */
mov $(SegSelectorLDT), %ax
/* Load LDT selector in GDT to LDT register */
lldt %ax
/* Jump to code segment in LDT */
lcall $(SegSelectorLDTCode32), $0
</code></pre>
</li>
<li>如第1點所述。</li>
<li>如第1點所述。</li>
<li>輸出Msg2,隨即使用lret指令返回LABEL_GDT_CODE。</li>
<li>通常,call與ret指令必須配合使用。執行call指令時,處理器會將SS、ESP、CS與EIP推入(Push) 該執行空間的堆疊 (Stack)。接著,執行ret指令時,處理器會將EIP、CS、ESP與SS從堆疊取出 (Pop)。為了實現從Ring 0返回Ring 3,底下程式碼模擬處理器的工作: 將Ring 3的SS、ESP、CS與EIP推入(Push) 該執行空間的堆疊 (Stack)並使用lret指令返回Ring 3。<br /><pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> pushl $(SegSelectorStackR3)
pushl $(TopOfStackR3)
pushl $(SegSelectorCodeR3)
pushl $0
lret
</code></pre>
</li>
<li>從Ring 0 (LABEL_GDT_CODE)返回 Ring 3 (LABEL_GDT_CODE_R3)。</li>
<li>此Ring 3程式區段將Msg1_R3輸出至螢幕,利用call gate並搭配TSS成功地返回Ring 0程式區段。因此,必須設定TSS之ESP0與SS0欄位,如下所示:<br /><pre style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); color: black; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>LABEL_TSS:
.4byte 0 /* Previous Task Link */
.4byte TopOfStackR0 /* ESP0 */
.4byte SegSelectorStackR0 /* SS0 */
.4byte 0 /* ESP1 */
.4byte 0 /* SS1 */
.4byte 0 /* ESP2 */
.4byte 0 /* SS2 */
.4byte 0 /* CR3 (PDBR) */
.4byte 0 /* EIP */
.4byte 0 /* EFLAGS */
.4byte 0 /* EAX */
.4byte 0 /* ECX */
.4byte 0 /* EDX */
.4byte 0 /* EBX */
.4byte 0 /* ESP */
.4byte 0 /* EBP */
.4byte 0 /* ESI */
.4byte 0 /* EDI */
.4byte 0 /* ES */
.4byte 0 /* CS */
.4byte 0 /* SS */
.4byte 0 /* DS */
.4byte 0 /* FS */
.4byte 0 /* GS */
.4byte 0 /* LDT Segment Selector */
.2byte 0 /* Reserved */
.2byte (. - LABEL_TSS + 2) /* I/O Map Base Address */
.set TSSLen, (. - LABEL_TSS)
</code></pre>
在此,特別對特權等級轉換做進一步解釋,以便讓讀者了解為什麼只要設定ESP0與SS0欄位即可。當存取目的程式區段 (Destination Code Segment)是被允許的,處理器便會根據Call Gate Descriptor的Segment Selector欄位找出對應的程式區段,如果又涉及特權等級轉換,處理器會將原本使用中的堆疊切換至目標特權等級的堆疊。舉例來說,假設目前程式區段運行於Ring 3,處理器便使用Ring 3的堆疊,當使用Call Gate欲轉換至Ring 0的程式區段。如果此要求是被允許的,則處理器會跳至Ring 0的程式區段並使用Ring 0的堆疊,因此程式設計員必須先設定Ring 0堆疊 (ESP0與SS0),這就是為什麼必須設定TSS的ESP與SS0欄位的原因。 </li>
<li>跳至GDT之Call Gate的描述子 (Descriptor)。</li>
<li>根據Call Gate描述子的Segment Selector欄位找出對應的CS。</li>
<li>將Msg3輸出至螢幕並返回。</li>
<li>返回Ring 3程式區段,並執行一無窮迴圈。</li>
</ol>
<span style="font-weight: bold;">QEMU測試結果</span><br />
<div style="text-align: center;">
<a href="http://picasaweb.google.com/lh/photo/IBsFbmqxad6PDGiLFgtiRQ?feat=embedwebsite"><img height="356" src="https://lh3.ggpht.com/_VP7dWGNif7M/TRRZP4uXGGI/AAAAAAAACbI/g1Q7njx1sIw/s640/result.jpeg" width="640" /></a></div>
<br />
【Reference】<br />
[1] <a href="http://share.solrex.org/WriteOS/index_cht.html">Solrex - 使用開源軟體-自己動手寫作業系統</a><br />
[2] <span style="text-decoration: underline;"></span><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a><br />
[3] <a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a><br />
[4] <a href="http://blog.linux.org.tw/~jserv/archives/002031.html">Jserv's Blog</a><br />
[5] <a href="http://www.cs.nctu.edu.tw/~huangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a><br />
[6] <span style="font-size: 100%;"><a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span><br />
[7] linux-source-2.6.31<br />
<div>
[8] <a href="https://docs.google.com/uc?authuser=0&id=0ByaIwJ06Bi-2WG84am9SdS1ySFk&export=download">本篇範例下載點</a><br />
<br /></div>
Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com1tag:blogger.com,1999:blog-6594360167010513475.post-65136913120053261302010-12-08T16:22:00.010+08:002010-12-09T11:14:41.198+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (六) 簡介Call GateCall Gate主要目的用來將一程式的特權等級 (Privilege Level) 轉換至另一個特權等級。舉例來說,Linux使用者利用ioctl()系統呼叫從user space (Privilege 3)進入kernel space (Privilege 0)。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">Note</span></span><br />本篇範例程式僅示範如何使用call gate,並如何利用call gate呼叫所對應的程式碼片段,所有的程式碼都運行於privilege 0。對於特權等級的轉換 (Privilege 3 -> Privilege 0),留待往後的文章再詳加探討。本篇文章僅簡單地介紹Call Gate,詳盡介紹請參考<a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a>之5.8節。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">Call-Gate Descriptor之介紹</span></span><br />參考Figure 1,其欄位如下所述:<br /><ol><li>Offset: 代表所指定的程式區段 (Code Segment)進入點 (Entry Point),通常都設為0。<br /></li><li>Segment Selector: 此區段選擇器指定所要存取的程式區段 (Code Segment)。如此說明或許有點模糊,請參考Figure 2,此圖紅色數字方塊展示出設定Call Gate的必要程序,因此設定Call Gate需要三道步驟:A. 設定Call Gate Descriptor相關欄位,其欄位設定值如Table 1所示。B. 設定對應之程式區段。C. 設定Call-Gate之區段選擇器。<br /></li><li>Param. Count: 指定呼叫程序 (Calling Procedure)欲傳遞幾個參數給被呼叫的程序 (Called Procedure)。</li><li>DPL (Descriptor Privilege Level): 此區段描述子之特權等級,其DPL與RPL特權等級之檢查請參考<span style="text-decoration: underline;"></span><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a>之Figure 5-11。</li><li>P: 此Call-Gate描述子是否有有效 (有效: P=1, 無效: P=0)。<br /></li></ol><div style="text-align: center;"><a href="http://picasaweb.google.com/lh/photo/HBTCCZm5QCOCiLTtmzzAZA?feat=embedwebsite"><img src="http://lh6.ggpht.com/_VP7dWGNif7M/TP9FJNsiB_I/AAAAAAAACS0/SH4fGMdw0I8/s640/call-gate-desc.jpeg" height="230" width="640" /></a><br /></div><div style="text-align: center;"><span style="font-weight: bold;">Figure 1. Call-Gate Descriptor</span><br /></div><br /><div style="text-align: center;"><a href="http://picasaweb.google.com/lh/photo/yJUpJUSZL6f-3h4pfmFKHQ?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TQA_lmve1PI/AAAAAAAACTE/inbhWbc8toY/s640/call-gate-conf.jpeg" height="454" width="640" /></a><br /></div><div style="text-align: center;"><span style="font-weight: bold;">Figure 2. Steps for Configuring Call Gate</span><br /></div><br /><div style="text-align: center;"><a href="http://picasaweb.google.com/lh/photo/2IASxYH_N20UKHdNEWdnvw?feat=embedwebsite"><img src="http://lh6.ggpht.com/_VP7dWGNif7M/TQA_l5Sq8kI/AAAAAAAACTI/Oy-bkGxamEQ/s400/call-gate-desc-filed-cfg.jpeg" height="195" width="400" /></a><br /></div><div style="text-align: center;"><span style="font-weight: bold;">Table 1. Field Value Configuration for Call-Gate Descriptor</span><br /></div><br /><span style="font-size:130%;"><span style="font-weight: bold;">Boot Loader程式碼</span></span><br />請參考<a href="http://adrianhuang.blogspot.com/2010/09/gnu-assembler-16-real-mode-32-protect.html">此篇文章</a>的"Boot Loader 程式碼"。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">作業系統程式碼</span></span><br />Figure 3為作業系統程式碼,此作業系統程式碼運行於32位元保護模式,程式說明如下:<br /><br /><ol><li>定義七個Segment Descriptor (LABEL_GDT_NULL、LABEL_GDT_CODE、LABEL_GDT_DATA、LABEL_GDT_VIDEO、LABEL_GDT_LDT、LABEL_GDT_CG_CODE與LABEL_GDT_CG)。其中VIDEO的基底位址為0xB8000,詳情請參考<a href="http://wiki.osdev.org/Printing_to_Screen">Printing to Screen</a>。接著定義GDT的長度、定義Code、Data、VIDEO與LDT的segment selector、定義輸出的字串、定義GDTPtr與定義LDT表。</li><li>LABEL_GDT_CG為Call Gate Descriptor,其儲存在GDT。此設定步驟為Figure 2的1號紅色方塊。<br /></li><li>LABEL_GDT_CG_CODE為Call Gate Descriptor欄位Segment Selector所指定的地方,也就是Call Gate所對應的區段程式碼。此設定步驟為Figure 2的2號紅色方塊。</li></ol><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* os.S<br />*<br />*/<br /><br />#include "pm.h"<br /><br />.code16<br />.text<br /> jmp os_main<br /><br /># Segment descritors for GDT<br />LABEL_GDT_NULL: SEG_DESC 0, 0, 0<br />LABEL_GDT_CODE: SEG_DESC 0, (PECode32Len - 1), (DESC_ATTR_TYPE_CD_ER | DESC_ATTR_D)<br />LABEL_GDT_DATA: SEG_DESC 0, (DataLen - 1), (DESC_ATTR_TYPE_CD_RW)<br />LABEL_GDT_VIDEO: SEG_DESC 0xB8000, 0xFFFF, (DESC_ATTR_TYPE_CD_RW)<br />LABEL_GDT_LDT: SEG_DESC 0, (LDTLen - 1), (DESC_ATTR_TYPE_LDT)<br />LABEL_GDT_CG_CODE: SEG_DESC 0, (CG_CODE32_LEN - 1), (DESC_ATTR_TYPE_CD_ER | DESC_ATTR_D)<br />LABEL_GDT_CG: CALL_GATE SegSelectorCGCODE, 0, 0, (GATE_CG_ATTR)<br /><br /># The length of GDT<br />.set GdtLen, (. - LABEL_GDT_NULL)<br /><br /># Segment selectors<br />.set SegSelectorCode32, (LABEL_GDT_CODE - LABEL_GDT_NULL)<br />.set SegSelectorData, (LABEL_GDT_DATA - LABEL_GDT_NULL)<br />.set SegSelectorVideo, (LABEL_GDT_VIDEO - LABEL_GDT_NULL)<br />.set SegSelectorLDT, (LABEL_GDT_LDT - LABEL_GDT_NULL)<br />.set SegSelectorCGCODE, (LABEL_GDT_CG_CODE - LABEL_GDT_NULL)<br />.set SegSelectorCG, (LABEL_GDT_CG - LABEL_GDT_NULL)<br /><br /># data segment<br />LABEL_DATA:<br />Msg1: .ascii "Welcome to Protect mode in GDT.\0"<br />Msg2: .ascii "Welcome to Protect mode in LDT.\0"<br />Msg3: .ascii "Welcome to Protect mode through Call Gate.\0"<br /><br />.set Msg1Offset, (Msg1 - LABEL_DATA)<br />.set Msg2Offset, (Msg2 - LABEL_DATA)<br />.set Msg3Offset, (Msg3 - LABEL_DATA)<br /><br />.set DataLen, (. - LABEL_DATA)<br /><br /># GDTR pointer<br />LABEL_GDTR:<br /> .2byte (GdtLen - 1) # Limit field<br /> .4byte 0 # base field<br /><br /># LDT information<br />LABEL_LDT:<br />LABEL_LDT_ENTRY: SEG_DESC 0, (LDT_CODE32_LEN - 1), (DESC_ATTR_TYPE_CD_E | DESC_ATTR_D)<br /><br /># length of LDT<br />.set LDTLen, (. - LABEL_LDT)<br /><br /># LDT selector<br />.set SegSelectorLDTCode32, (LABEL_LDT_ENTRY - LABEL_LDT + SA_TIL)<br /><br /><br /># real-mode OS code<br />os_main:<br /> mov %cs, %ax<br /> mov %ax, %ds<br /> mov %ax, %ss<br /> mov %ax, %es<br /><br /><br /> /* Set gdt for code segment */<br /> InitSegDescriptor LABEL_PE_CODE32, LABEL_GDT_CODE<br /> InitSegDescriptor LABEL_DATA, LABEL_GDT_DATA<br /> InitSegDescriptor LABEL_LDT, LABEL_GDT_LDT<br /> InitSegDescriptor LABEL_PE_LDT_CODE32, LABEL_LDT_ENTRY<br /> InitSegDescriptor LABEL_PE_CG_CODE32, LABEL_GDT_CG_CODE<br /><br /> /* Set GDTR */<br /> xor %ax, %ax<br /> mov %cs, %ax<br /> shl $4, %eax<br /> addl $LABEL_GDT_NULL, %eax<br /> movl %eax, (LABEL_GDTR + 2) <br /><br /> /* Enable A20 line */<br /> xor %ax, %ax<br /> in $0x92, %al<br /> or $2, %al<br /> out %al, $0x92<br /><br /> cli<br /><br /> /* Load the GDT base address and limit from memory into the GDTR register */<br /> lgdt LABEL_GDTR<br /><br /> /* Enable protect mode */<br /> movl %cr0, %eax<br /> orl $1, %eax<br /> movl %eax, %cr0<br /><br /> /* Jump to protected-mode OS code */<br /> ljmp $SegSelectorCode32, $0<br /><br />LABEL_PE_CG_CODE32:<br />.code32<br /> mov $(SegSelectorData), %ax<br /> mov %ax, %ds<br /> mov $(SegSelectorVideo), %ax<br /> mov %ax, %gs<br /><br /> xorl %esi, %esi<br /> xorl %edi, %edi<br /> movl $Msg3Offset, %esi<br /> movl $((80 * 11 + 0) * 2), %edi<br /> movb $0xC, %ah<br /><br />cg_dump_str:<br /> lodsb<br /> andb %al, %al<br /> jz cg_fin<br /> mov %ax, %gs:(%edi) <br /> addl $2, %edi<br /> jmp cg_dump_str<br /><br />cg_fin:<br /> lret <br /><br />.set CG_CODE32_LEN, (. - LABEL_PE_CG_CODE32)<br /><br />LABEL_PE_LDT_CODE32:<br />.code32<br /> # invoke a procedure call throught a call-gate.<br /> lcall $(SegSelectorCG), $0<br /><br /> mov $(SegSelectorData), %ax<br /> mov %ax, %ds<br /> mov $(SegSelectorVideo), %ax<br /> mov %ax, %gs<br /><br /> xorl %esi, %esi<br /> xorl %edi, %edi<br /> movl $Msg2Offset, %esi<br /> movl $((80 * 13 + 0) * 2), %edi<br /> movb $0xC, %ah<br /><br />ldt_dump_str:<br /> lodsb<br /> andb %al, %al<br /> jz ldt_fin<br /> mov %ax, %gs:(%edi) <br /> addl $2, %edi<br /> jmp ldt_dump_str<br /><br />ldt_fin:<br /> jmp .<br /><br />.set LDT_CODE32_LEN, (. - LABEL_PE_LDT_CODE32)<br /><br /># protected-mode OS code in GDT<br />LABEL_PE_CODE32:<br />.code32 <br /> /* Load data segment selector */<br /> mov $(SegSelectorData), %ax<br /> mov %ax, %ds<br /><br /> /* Load Video segment selector */<br /> mov $(SegSelectorVideo), %ax<br /> mov %ax, %gs<br /><br /> /* Output the data */<br /> xorl %esi, %esi<br /> xorl %edi, %edi<br /> movl $Msg1Offset, %esi<br /> movl $((80 * 10 + 0) * 2), %edi<br /> movb $0xC, %ah<br /><br />dump_str:<br /> lodsb<br /> andb %al, %al<br /> jz fin<br /> mov %ax, %gs:(%edi) <br /> addl $2, %edi<br /> jmp dump_str<br /><br />fin:<br /> /* Load LDT selector */<br /> mov $(SegSelectorLDT), %ax<br /><br /> /* Load LDT selector in GDT to LDT register */<br /> lldt %ax<br /><br /> /* Jump to code segment in LDT */<br /> ljmp $(SegSelectorLDTCode32), $0<br /><br />.set PECode32Len, (. - LABEL_PE_CODE32)<br /> <br /> .ascii "Welcome to OS context!"<br /> .byte 0 <br /><br /> .org 0x400, 0x41 # fill characters with 'A'. Sector 2<br /><br /></code></pre><span style="font-weight: bold;"><br /></span><div style="text-align: center;"><span style="font-weight: bold;">Figure 3. Operating System Code</span><br /><div style="text-align: left;"><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">pm.h標頭檔</span></span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* pm.h<br />*<br />* Adrian Huang (adrianhuang0701@gmail.com)<br />*/<br />.macro SEG_DESC Base, Limit, Attr<br /> .2byte (\Limit & 0xFFFF)<br /> .2byte (\Base & 0xFFFF)<br /> .byte ((\Base >> 16) & 0xFF)<br /> .2byte ((\Attr & 0xF0FF) | ((\Limit >> 8) & 0x0F00))<br /> .byte ((\Base >> 24) & 0xFF)<br />.endm<br /><br />.macro InitSegDescriptor OFFSET GDT_SEG_ADDR<br /> xor %ax, %ax<br /> mov %cs, %ax<br /> shl $4, %eax<br /> addl $(\OFFSET), %eax<br /> movw %ax, (\GDT_SEG_ADDR + 2)<br /> shr $16, %eax<br /> movb %al, (\GDT_SEG_ADDR + 4) <br /> movb %ah, (\GDT_SEG_ADDR + 7) <br /><br />.endm<br /><br />.macro CALL_GATE SegSelector, Offset, ParamCount, Attr<br /> .2byte (\Offset & 0xFFFF)<br /> .2byte (\SegSelector)<br /> .byte (\ParamCount)<br /> .byte (\Attr)<br /> .2byte ((\Offset >> 16) & 0xFFFF)<br />.endm<br /><br />.set DESC_ATTR_TYPE_LDT, 0x82 /* LDT Segment */<br />.set DESC_ATTR_TYPE_CG, 0x8C /* Call-Gate Segment */<br />.set DESC_ATTR_TYPE_CD_ER, 0x9A /* Code segment with Execute/Read */<br />.set DESC_ATTR_TYPE_CD_E, 0x98 /* Code segment with Execute Only */<br />.set DESC_ATTR_TYPE_CD_RW, 0x92 /* Data segment with R/W */<br />.set DESC_ATTR_D, 0x4000 /* 32-bit segment */<br /><br />/* Selector Attribute */<br />.set SA_TIL, 0x4<br />.set SA_RPL0, 0x0<br />.set SA_RPL1, 0x1<br />.set SA_RPL2, 0x2<br />.set SA_RPL3, 0x3<br /><br />/* The attribute of call gate */<br />.set GATE_CG_ATTR, 0x8C<br /></code></pre><br /><br /><br /></div></div><span style="font-size:130%;"><span style="font-weight: bold;"></span></span><span style="font-size:130%;"><span style="font-weight: bold;">編譯程式碼</span></span><br /><span class="Apple-style-span" style="font-size:medium;">下圖為編譯的Makefile。<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>LD=ld<br />CC=gcc<br /><br />all: boot_loader.bin<br /><br />boot_loader.bin: boot_loader.o os.o<br /> ${LD} -Ttext=0x7C00 -s $< -o $@ --oformat binary ${LD} -Ttext=0x0 -s os.o -o os.bin --oformat binary cat os.bin >> $@<br /><br />boot_loader.o:<br /> ${CC} -c boot_loader.S<br /><br />os.o:<br /> ${CC} -c os.S<br /><br />clean:<br /> rm -f boot_loader.o os.o os.bin boot_loader.bin<br /></code></pre></span><br /><span style="font-size:100%;">其編譯訊息如下所示:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/blog/pe-call-gate-same-priv$ make clean all<br />rm -f boot_loader.o os.o os.bin boot_loader.bin <br />gcc -c boot_loader.S<br />gcc -c os.S<br />ld -Ttext=0x7C00 -s boot_loader.o -o boot_loader.bin --oformat binary<br />ld -Ttext=0x0 -s os.o -o os.bin --oformat binary<br />ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000<br />cat os.bin >> boot_loader.bin<br />adrian@adrian-desktop:~/working/build_os/my_ex/blog/pe-call-gate-same-priv$<br /><br /></code></pre><br /></span><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">QEMU測試結果</span></span><br /><a href="http://picasaweb.google.com/lh/photo/BrgMRX8ZUUPMymP_tNrPqg?feat=embedwebsite"><img src="http://lh3.ggpht.com/_VP7dWGNif7M/TQBGZwLTqrI/AAAAAAAACTQ/4nVf2VyAhLs/s640/call-gate-result.jpeg" height="299" width="640" /></a><br /><br /><br />【Reference】<br />[1] <a href="http://share.solrex.org/WriteOS/index_cht.html">Solrex - 使用開源軟體-自己動手寫作業系統</a><br />[2] <span style="text-decoration: underline;"></span><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a><br />[3] <a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a><br />[4] <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a><br />[5] <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a><br />[6] <span style="font-size:100%;"><a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span><br />[7] linux-source-2.6.31Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com2tag:blogger.com,1999:blog-6594360167010513475.post-60581992781594481482010-11-02T14:06:00.001+08:002010-11-02T14:21:13.432+08:00現今最受歡迎的Linux Distribution今天在網路上看到很有興趣的Linux Distribution搜尋排名比較,請參考<a href="http://steamingopencup.blogspot.com/2010/11/what-is-todays-most-popular-linux.html">原文網址</a>,其中<a href="http://distrowatch.com/">DistroWatch</a>根據使用者點閱Linux Distribution網頁所得出的結果如下:<br /><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1. Ubuntu</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2. Fedora</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">3. Mint</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">4. OpenSUSE</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">5. Debian</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">6. PCLinuxOS</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">7. Mandriva</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">8. Sabayon</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">9. Arch</span><br /><span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-weight: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">10. Puppy</span><br /><br />然而,此統計數據以Linux Desktop OS Distribution為基礎,所以Android沒在排名之中。<br /><br />於是,作者利用Google Trend所得出的結果,則是Android排名第一。Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com2tag:blogger.com,1999:blog-6594360167010513475.post-54939879949480912232010-10-11T14:39:00.005+08:002010-10-16T16:34:36.355+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (五) 使用Local Descriptor Table (LDT)<a href="http://adrianhuang.blogspot.com/2010/09/gnu-assembler-16-real-mode-32-protect.html">上篇文章</a>使用Global Descriptor Table (GDT)儲存Code Segment (CS)與Data Segment (DS)的資訊,以便能在保護模式下成功地執行程式碼與存取資料。本篇說明如何設定LDT,以便能執行位於LDT的程式碼。<br /><br /><span style="font-weight: bold;font-size:130%;" >LDT之介紹</span><br />LDT實現作業系統多個程序功能 (multiple processes),其特色為各個程序有自己的位址空間彼此互不干擾,每個程序會有各自的LDT,當作業系統欲執行某一程序時,作業系統會找出其對應的LDT,以便能執行該程序。詳細資料請參考<a href="http://en.wikipedia.org/wiki/Local_Descriptor_Table">Local Descriptor Table</a>。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">LDT相關設定<br /></span></span>LDT設定分成兩大步驟如下所述:<br /><br /><span style="font-weight: bold;">1. 在GDT表裡設定一個LDT entry (Configure a LDT Entry in GDT)</span><br /> 參考Figure 1,其每個欄位所應設定值如Table 1所示:<br /><br /><a href="http://picasaweb.google.com/lh/photo/8ZJZkR1svLR34qstWqRyfg?feat=embedwebsite"><img src="http://lh6.ggpht.com/_VP7dWGNif7M/TKPnKk2Yx8I/AAAAAAAACQU/JNoCqV5YfPM/s800/seg_descriptor.jpeg" /></a><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 1. Segment Descriptor<br /><br /><a href="http://picasaweb.google.com/lh/photo/jsM4CsSPbU1sLo2DfqBj5Q?feat=embedwebsite"><img src="http://lh4.ggpht.com/_VP7dWGNif7M/TLLSgvULSNI/AAAAAAAACQ4/1ztjBaGU2W4/s800/LDT_entry.jpeg" height="341" width="387" /></a><br />Table 1. LDT entry configuration in GDT<br /></span></div><span style="font-weight: bold;"><br />2. 設定LDT表</span><br />LDT表設定code segment/data segment相關資訊,其設定值如Table 2所示。<br /><br /><div style="text-align: center;"><a href="http://picasaweb.google.com/lh/photo/qa24NgEK9j8Sp7-8iw1A4g?feat=embedwebsite"><img src="http://lh3.ggpht.com/_VP7dWGNif7M/TLLSghRqYEI/AAAAAAAACQ8/ypo7_uUZfo4/s800/LDT_table.jpeg" height="341" width="641" /></a><br /></div><div style="text-align: center; font-weight: bold;">Table 2. LDT entry configuration in LDT<br /><br /></div><span style="font-size:130%;"><span style="font-weight: bold;">Boot Loader程式碼</span></span><br />請參考<a href="http://adrianhuang.blogspot.com/2010/09/gnu-assembler-16-real-mode-32-protect.html">上篇文章</a>的"Boot Loader 程式碼"。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">作業系統程式碼<br /></span></span>Figure 2為作業系統程式碼,此作業系統程式碼運行於32位元保護模式,一開始定義三個Segment Descriptor (NULL、CODE32、VIDEO與LDT),其中VIDEO的基底位址為0xB8000,詳情請參考<a href="http://wiki.osdev.org/Printing_to_Screen">Printing to Screen</a>。接著定義GDT的長度、定義Code32、Data、VIDEO與LDT的segment selector、定義輸出的字串、定義GDTPtr與定義LDT表。<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* os.S<br />*<br />*/<br /><br />#include "pm.h"<br /><br />.code16<br />.text<br /> jmp os_main<br /><br /># Segment descritors for GDT<br />LABEL_GDT_NULL: SEG_DESC 0, 0, 0<br />LABEL_GDT_CODE: SEG_DESC 0, (PECode32Len - 1), (DESC_ATTR_TYPE_CD_ER | DESC_ATTR_D)<br />LABEL_GDT_DATA: SEG_DESC 0, (DataLen - 1), (DESC_ATTR_TYPE_CD_RW)<br />LABEL_GDT_VIDEO: SEG_DESC 0xB8000, 0xFFFF, (DESC_ATTR_TYPE_CD_RW)<br />LABEL_GDT_LDT: SEG_DESC 0, (LDTLen -1), (DESC_ATTR_TYPE_LDT)<br /><br /># The length of GDT<br />.set GdtLen, (. - LABEL_GDT_NULL)<br /><br /># Segment selectors<br />.set SegSelectorCode32, (LABEL_GDT_CODE - LABEL_GDT_NULL)<br />.set SegSelectorData, (LABEL_GDT_DATA - LABEL_GDT_NULL)<br />.set SegSelectorVideo, (LABEL_GDT_VIDEO - LABEL_GDT_NULL)<br />.set SegSelectorLDT, (LABEL_GDT_LDT - LABEL_GDT_NULL)<br /><br /># data segment<br />LABEL_DATA:<br />Msg1: .ascii "Welcome to Protect mode in GDT.\0"<br />Msg2: .ascii "Welcome to Protect mode in LDT.\0"<br />Msg3: .ascii "This is signed by Adrian.\0"<br /><br />.set Msg1Offset, (Msg1 - LABEL_DATA)<br />.set Msg2Offset, (Msg2 - LABEL_DATA)<br />.set Msg3Offset, (Msg3 - LABEL_DATA)<br /><br />.set DataLen, (. - LABEL_DATA)<br /><br /># GDTR pointer<br />LABEL_GDTR:<br /> .2byte (GdtLen - 1) # Limit field<br /> .4byte 0 # base field<br /><br /># LDT information<br />LABEL_LDT:<br />LABEL_LDT_ENTRY: SEG_DESC 0, (LDT_CODE32_LEN - 1), (DESC_ATTR_TYPE_CD_E | DESC_ATTR_D)<br /><br /># length of LDT<br />.set LDTLen, (. - LABEL_LDT)<br /><br /># LDT selector<br />.set SegSelectorLDTCode32, (LABEL_LDT_ENTRY - LABEL_LDT + SA_TIL)<br /><br /><br /># real-mode OS code<br />os_main:<br /> mov %cs, %ax<br /> mov %ax, %ds<br /> mov %ax, %ss<br /> mov %ax, %es<br /><br /><br /> /* Set gdt for code segment */<br /> InitSegDescriptor LABEL_PE_CODE32, LABEL_GDT_CODE<br /> InitSegDescriptor LABEL_DATA, LABEL_GDT_DATA<br /> InitSegDescriptor LABEL_LDT, LABEL_GDT_LDT<br /> InitSegDescriptor LABEL_PE_LDT_CODE32, LABEL_LDT_ENTRY<br /><br /> /* Set GDTR */<br /> xor %ax, %ax<br /> mov %cs, %ax<br /> shl $4, %eax<br /> addl $LABEL_GDT_NULL, %eax<br /> movl %eax, (LABEL_GDTR + 2) <br /><br /> /* Enable A20 line */<br /> xor %ax, %ax<br /> in $0x92, %al<br /> or $2, %al<br /> out %al, $0x92<br /><br /> cli<br /><br /> /* Load the GDT base address and limit from memory into the GDTR register */<br /> lgdt LABEL_GDTR<br /><br /> /* Enable protect mode */<br /> movl %cr0, %eax<br /> orl $1, %eax<br /> movl %eax, %cr0<br /><br /> /* Jump to protected-mode OS code */<br /> ljmp $SegSelectorCode32, $0<br /><br /><br />LABEL_PE_LDT_CODE32:<br />.code32<br /> mov $(SegSelectorData), %ax<br /> mov %ax, %ds<br /> mov $(SegSelectorVideo), %ax<br /> mov %ax, %gs<br /><br /> xorl %esi, %esi<br /> xorl %edi, %edi<br /> movl $Msg2Offset, %esi<br /> movl $((80 * 11 + 0) * 2), %edi<br /> movb $0xC, %ah<br /><br />ldt_dump_str:<br /> lodsb<br /> andb %al, %al<br /> jz ldt_fin<br /> mov %ax, %gs:(%edi) <br /> addl $2, %edi<br /> jmp ldt_dump_str<br /><br />ldt_fin:<br /> jmp .<br /><br />.set LDT_CODE32_LEN, (. - LABEL_PE_LDT_CODE32)<br /><br /># protected-mode OS code in GDT<br />LABEL_PE_CODE32:<br />.code32 <br /> /* Load data segment selector */<br /> mov $(SegSelectorData), %ax<br /> mov %ax, %ds<br /><br /> /* Load Video segment selector */<br /> mov $(SegSelectorVideo), %ax<br /> mov %ax, %gs<br /><br /> /* Output the data */<br /> xorl %esi, %esi<br /> xorl %edi, %edi<br /> movl $Msg1Offset, %esi<br /> movl $((80 * 10 + 0) * 2), %edi<br /> movb $0xC, %ah<br /><br />dump_str:<br /> lodsb<br /> andb %al, %al<br /> jz fin<br /> mov %ax, %gs:(%edi) <br /> addl $2, %edi<br /> jmp dump_str<br /><br />fin:<br /> /* Load LDT selector */<br /> mov $(SegSelectorLDT), %ax<br /><br /> /* Load LDT selector in GDT to LDT register */<br /> lldt %ax<br /><br /> /* Jump to code segment in LDT */<br /> ljmp $(SegSelectorLDTCode32), $0<br /><br />.set PECode32Len, (. - LABEL_PE_CODE32)<br /> <br />os_msg:<br /> .ascii "Welcome to OS context!"<br /> .byte 0 <br /><br /> .org 0x200, 0x41 # fill characters with 'A'. Sector 2<br /><br /></code></pre><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 2. Code for Operating System </span><br /></div><br />16位元real mode程式碼 (os_main)中,執行若干任務如下所述:<br /><ol><li>設定LABEL_GDT_CODE的基底位址為PE_CODE32的起始位址</li><li>設定LABEL_GDT_DATA的基底位址為LABEL_DATA的起始位址</li><li>設定LABEL_GDT_LDT的基底位址為LABEL_LDT的起始位址</li><li>設定LABEL_LDT_ENTRY的基底位址為LABEL_PE_LDT_CODE32的起始位址</li><li>設定GDTPtr的基底位址為GDT的起始位址(也就是GDT_DESC_NULL)</li><li>開啟A20線路 (<a href="http://en.wikipedia.org/wiki/A20_line">A20 Line</a>)</li><li>將GDT的起始位址載入至GDTR暫存器</li><li>設定cr0暫存器的bit 0以便進入保護模式</li><li>使用ljmp指令跳至PE_CODE32程式碼</li><li>使用lldt指令將LDT的segment selector載入至LDTR (Local Descriptor Table Register),並跳至SegSelectorLDTCode32 segment selector執行LDT的程式碼。<br /></li></ol>32位元保護模式程式碼 (LABEL_PE_CODE32)利用Video segment selector將"Welcome to Protect mode in GDT."顯示在螢幕上,而LABEL_PE_LDT_CODE32則將"Welcome to Protect mode in LDT."顯示在螢幕上,用以驗證程式運作之正確性。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">pm.h標頭檔</span></span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* pm.h<br />*<br />* Adrian Huang (adrianhuang0701@gmail.com)<br />*/<br />.macro SEG_DESC Base, Limit, Attr<br /> .2byte (\Limit & 0xFFFF)<br /> .2byte (\Base & 0xFFFF)<br /> .byte ((\Base >> 16) & 0xFF)<br /> .2byte ((\Attr & 0xF0FF) | ((\Limit >> 8) & 0x0F00))<br /> .byte ((\Base >> 24) & 0xFF)<br />.endm<br /><br />.macro InitSegDescriptor OFFSET GDT_SEG_ADDR<br /> xor %ax, %ax<br /> mov %cs, %ax<br /> shl $4, %eax<br /> addl $(\OFFSET), %eax<br /> movw %ax, (\GDT_SEG_ADDR + 2)<br /> shr $16, %eax<br /> movb %al, (\GDT_SEG_ADDR + 4) <br /> movb %ah, (\GDT_SEG_ADDR + 7) <br /><br />.endm<br /><br />.set DESC_ATTR_TYPE_LDT, 0x82 /* LDT Segment */<br />.set DESC_ATTR_TYPE_CD_ER, 0x9A /* Code segment with Execute/Read */<br />.set DESC_ATTR_TYPE_CD_E, 0x98 /* Code segment with Execute Only */<br />.set DESC_ATTR_TYPE_CD_RW, 0x92 /* Data segment with R/W */<br />.set DESC_ATTR_D, 0x4000 /* 32-bit segment */<br /><br />/* Selector Attribute */<br />.set SA_TIL, 0x4<br />.set SA_RPL0, 0x0<br />.set SA_RPL1, 0x1<br />.set SA_RPL2, 0x2<br />.set SA_RPL3, 0x3<br /><br /></code></pre><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">編譯程式碼</span></span><br /><span style="font-size:100%;">請參照<a href="http://adrianhuang.blogspot.com/2010/09/gnu-assembler-16-real-mode-32-protect.html">此篇的Makefile程式碼</a><br /><br />其編譯訊息如下所示: </span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/04day/blog/pe-multi-seg-ldt$ make clean all<br />rm -f boot_loader.o os.o boot_loader.bin <br />gcc -c boot_loader.S<br />gcc -c os.S<br />ld -Ttext=0x7C00 -s boot_loader.o -o boot_loader.bin --oformat binary<br />ld -Ttext=0x0 -s os.o -o os.bin --oformat binary<br />ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000<br />cat os.bin >> boot_loader.bin<br />adrian@adrian-desktop:~/working/build_os/my_ex/04day/blog/pe-multi-seg-ldt$<br /></code></pre><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">QEMU測試結果<br /></span><span style="font-size:100%;"><a href="http://picasaweb.google.com/lh/photo/PKDzQF_7U46K4exASKbHpQ?feat=embedwebsite"><img src="http://lh4.ggpht.com/_VP7dWGNif7M/TLLSg4i24UI/AAAAAAAACRA/jCoO9-m8TVQ/s800/qemu_result.jpeg" height="356" width="800" /></a></span><span style="font-weight: bold;"><br /><br /></span></span>【Reference】<br />[1] <a href="http://share.solrex.org/WriteOS/index_cht.html">Solrex - 使用開源軟體-自己動手寫作業系統</a><br />[2] <span style="text-decoration: underline;"></span><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a><br />[3] <a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a><br />[4] <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a><br />[5] <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a><br />[6] <span style="font-size:100%;"><a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span><br />[7] linux-source-2.6.31Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-65410167018090946222010-09-29T19:02:00.011+08:002010-12-13T16:31:28.124+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (四) 由16位元真實模式 (Real Mode) 進入32位元保護模式 (Protect Mode)前三篇文章所展示的程式碼都是CPU執行於真實模式 (Real Mode)。然而,一般作業系統運行於保護模式 (Protect Mode),其記憶體定址最高可至4GB (32位元)。故本文先介紹real mode與protect mode記憶體定址的方式。<br /><br /><span style="font-weight: bold;font-size:130%;" >Real mode與Protect mode記憶體定址介紹</span><br />Figure 1展示real mode記憶體定址方式,其觀念在於將邏輯位址(Logical Address)的區段(Segment)位址向左位移4個位元,再將其所得的位址加上位移值,如此便能轉換成線性位址 (Linear Address)。至於,邏輯位址該如何表示呢? 其表示法為"Address of Segment:Offset"<br />,例如: CS:IP、SS:SP、DS:SI和ES:DI,詳情請參考x86 Assembly Language。Figure 1描述一個簡單邏輯位址轉換線性位址的例子,因此不再贅述。<br /><a href="http://picasaweb.google.com/lh/photo/6MQCPa-j_5ALgP2MdVg6Jw?feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzhKSQv5js6mOkLVdZvKYkV3-URMZ8LHGXF17itDcLQmty8Yn6_LC82AELnCIUOg4jvnuMsWwf2DiSY-n9h_Icr8dkwsv0Tpi4DvcZvzTDv3ZG23E1E8S0qEQaK3WfpPr9nDCrFSxboCs/s800/real_mode_mem_seg.jpeg" /></a><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 1. Memory Segmentation in Real Mode</span><br /></div><br />Figure 2展示保護模式記憶體定址方式,其觀念在於將區段 (Segment)看成區段選擇器 (Segment Selector),用此選擇器索引出對應的Segment Descriptor,如此便能索引32位元的基底位址 (32-bit base address),然後再加上位移植,便能轉換成線性位址。<br /><a href="http://picasaweb.google.com/lh/photo/R-5X61JdsmFTKMJEfMg-Gg?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TKPjAl-PopI/AAAAAAAACQE/0FYUt4MeSwM/s800/protected_mode_mem_seg.jpeg" /></a><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 2. Memory Segment in Protect Mode</span><br /></div><br /><span style="font-size:130%;"><span style="font-weight: bold;">保護模式相關課題之介紹 </span></span><br />此段落將著重介紹保護模式相關課題之介紹,包含介紹GDT/LDT (GDTR/LDTR)、Segment Descriptor、Segment Selector、 和Memory Management Register<br /><span style="font-weight: bold;"><br />Global and Local Descriptor Table (GDT and LDT)<br /></span>當CPU運行於保護模式時,所有的記憶體存取都必須經由GDT或LDT,此表格 (GDT or LDT)存放最小單元便是Segment Descriptor。每一個Segment Descriptor都有對應的segment selector,用以索引出對應的Segment Descriptor。<br /><br /><span style="font-weight: bold;">GDTR and LDTR (GDT Register and LDT Register)</span><br />GDTR與LDTR用以儲存GDT與LDT的起始位置,此設定必須在進入保護模式完成設定。Figure 3展示GDTR與LDTR格式,其中GDTR包含32位元的基底位址與16位元的長度限制。而LDTR多增加了16位元的segment selector。<br /><br /><a href="http://picasaweb.google.com/lh/photo/Zd6XSETuPnDLSlCg4Btviw?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TKPnKthT2cI/AAAAAAAACQY/ix5IKym5mtg/s800/mem_mgmt_reg.jpeg" /></a><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 3. Memory Management Register</span><br /></div><br /><span style="font-size:100%;"><span style="font-weight: bold;">Segment Descriptor</span></span><br />Segment Descriptor為Descriptor Table組成的基本元素,其長度為8位元組。如Figure 4所示,可分為三大類: 1. 32位元的基底位址 (Base Address), 2. 20位元的區段限制 (Segment Limit), 3. 區段屬性。細節請參考<a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a><br /><br /><a href="http://picasaweb.google.com/lh/photo/8ZJZkR1svLR34qstWqRyfg?feat=embedwebsite"><img src="http://lh6.ggpht.com/_VP7dWGNif7M/TKPnKk2Yx8I/AAAAAAAACQU/JNoCqV5YfPM/s800/seg_descriptor.jpeg" /></a><br /><div style="text-align: center;"><span style="font-weight: bold;">Figure 4. Segment Descriptor [2]</span><br /></div><br /><span style="font-weight: bold;">Segment Selector</span><br />Figure 5展示Segment Selector示意圖,其目的用來索引對應的Segment Descriptor。因Descriptor Index有13個位元,故Descriptor個數最大可至8192。<br /><br /><a href="http://picasaweb.google.com/lh/photo/3-Imq7RuximxRb6bxVHbXQ?feat=embedwebsite"><img src="http://lh6.ggpht.com/_VP7dWGNif7M/TKPnKpXs6kI/AAAAAAAACQQ/GcmMK1YE4RM/s800/seg_selector.jpeg" /></a><br /><div style="text-align: center; font-weight: bold;">Figure 5. Segment Selector<br /></div><br /><span style="font-size:130%;"><span style="font-weight: bold;">Boot Loader程式碼</span></span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* boot_loader.S<br />*<br />* Copyright (C) 2010 Adrian Huang (adrianhuang0701@gmail.com)<br />*<br />* This code is intended to simulate a simplified boot loader. This boot<br />* loader loads 3 sectors into the physical memory and jumps the entry<br />* point of OS.<br />*<br />*/<br /><br />.code16<br /></code><code>.text<br /><br /></code><code>.set BOOT_SEG, 0x07C0 /* starting code segment (CS) of boot loader */<br />.set OS_SEG, 0x0900 /* code segment address of OS entry point */<br />.set OS_OFFSET, 0x0000 /* the offset address of OS entry point */<br /><br />.global _start<br />_start:<br /> # FAT12 file system format<br /> jmp start_prog # jmp instruction<br /><br />.byte 0x90<br />.ascii "ADRIAN " # OEM name (8 bytes)<br />.word 512 # Bytes per sector<br />.byte 1 # Sector per cluster<br />.word 1 # Reserved sector count: should be 1 for FAT12<br />.byte 2 # Number of file allocation tables.<br />.word 224 # Maximum number of root directory entries.<br />.word 2880 # Total sectors<br />.byte 0xf0 # Media descriptor:<br />.word 9 # Sectors per File Allocation Table<br />.word 18 # Sectors per track<br />.word 2 # Number of heads<br />.long 0 # Count of hidden sectors<br />.long 0 # Total sectors<br />.byte 0 # Physical driver number<br />.byte 0 # Reserved<br />.byte 0x29 # Extended boot signature<br />.long 0x12345678 # Serial Number<br />.ascii "HELLO-OS " # Volume Label<br />.ascii "FAT12 " # FAT file system type<br />.fill 18, 1, 0 # fill 18 characters with zero<br /><br />start_prog:<br /># initialize the register with cs register<br />movw %cs, %ax<br />movw %ax, %ds<br />movw %ax, %es<br />movw %ax, %ss<br />xorw %sp, %sp<br /><br />cld # clear direction flag<br />sti # set interrupt flag<br /><br /># The following code is loaded three sectors (2-4th sectors from boot.bin)<br /># into the physical memory 0x8000-0x85FF.<br />movw $OS_SEG, %ax<br />mov %ax, %es # ES:BX-> destination buffer address pointer<br />movw $OS_OFFSET, %bx<br />movb $2, %cl # sector<br /><br /><br />cont:<br />movb $0x02, %ah # Read sectors from drive<br />movb $0x1, %al # Sectors to read count<br />movb $0x0, %ch # track<br />movb $0x0, %dh # head<br />movb $0, %dl # drive<br /><br />int $0x13 # trigger a interrupt 0x13 service<br />jc fail # the clear flag is set if the operation is failed<br /><br />mov %es, %ax<br />addw $0x20, %ax # move to the next sector<br />movw %ax, %es # move to the next sector<br />incb %cl<br /><br />cmpb $3, %cl # has finished reading 3 sectors?<br />jbe cont # continue to read the sector<br /><br />jmp os_entry # jump to OS entry point<br /><br />fail:<br />movw $err_msg, %si<br />fail_loop:<br />lodsb<br />andb %al, %al<br />jz end<br />movb $0x0e, %ah<br />int $0x10<br />jmp fail_loop<br /><br /><br />os_entry:<br />ljmp $OS_SEG, $OS_OFFSET # jump to os context<br /><br />end:<br /> hlt<br /> jmp end<br /><br />err_msg:<br />.ascii "Reading sectors operation is failed!"<br />.byte 0<br /><br />.org 0x1FE, 0x41 # fill the rest of characters with zero until the 254th character<br /><br /> # Boot sector signature<br />.byte 0x55<br />.byte 0xaa<br /><br /></code></pre><br /><span style="font-size:130%;"><span style="font-weight: bold;">作業系統程式碼</span></span><br />此作業系統程式碼運行於32位元保護模式,一開始定義三個Segment Descriptor (NULL, CODE32與VIDEO),其中VIDEO的基底位址為0xB8000,詳情請參考<a href="http://wiki.osdev.org/Printing_to_Screen">Printing to Screen</a>。接著定義GDT的長度、定義Code32與VIDEO的segment selector、定義GDTPtr。<br /><br />16位元real mode程式碼 (os_main)中,執行若干任務如下所述:<br /><ol><li>設定Code32的基底位址為PE_CODE32的起始位址</li><li>設定GDTPtr的基底位址為GDT的起始位址(也就是GDT_DESC_NULL)</li><li>開啟A20線路 (<a href="http://en.wikipedia.org/wiki/A20_line">A20 Line</a>)</li><li>將GDT的起始位址載入至GDTR暫存器</li><li>設定cr0暫存器的bit 0以便進入保護模式</li><li>使用ljmp指令跳至PE_CODE32程式碼</li></ol>32位元保護模式程式碼 (PE_CODE32)利用Video segment selector將'H'字元顯示在螢幕,用以驗證程式運作之正確性。<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* os.S<br />*<br />* Adrian Huang (adrianhuang0701@gmail.com)<br />*<br />* This code is OS context for protected-mode.<br />*<br />*/<br />#include "pm.h"<br /><br />.code16<br />.text<br />jmp os_main<br /><br /># Segment descritors for GDT<br />GDT_DESC_NULL: SEG_DESC 0, 0, 0<br />GDT_DESC_CODE32: SEG_DESC 0, (PECode32Len - 1), (DESC_ATTR_TYPE_CD_ER | DESC_ATTR_D)<br />GDT_DESC_VIDEO: SEG_DESC 0xB8000, 0xFFFF, (DESC_ATTR_TYPE_CD_RW)<br /><br /># The length of GDT<br />.set GdtLen, (. - GDT_DESC_NULL)<br /><br /># Segment selectors for code segment and video output<br />.set SegSelectorCode32, (GDT_DESC_CODE32 - GDT_DESC_NULL)<br />.set SegSelectorVideo, (GDT_DESC_VIDEO - GDT_DESC_NULL)<br /><br /># GDTR pointer<br />GDTPtr:<br />.2byte (GdtLen - 1) # Limit field<br />.4byte 0 # base field<br /><br /># real-mode OS code<br />os_main:<br />mov %cs, %ax<br />mov %ax, %ds<br />mov %ax, %ss<br />mov %ax, %es<br /><br /><br />/* Set gdt for code segment */<br />InitSegDescriptor PE_CODE32, GDT_DESC_CODE32<br /><br />/* Set GDTR */<br />xor %ax, %ax<br />mov %cs, %ax<br />shl $4, %eax<br />addl $GDT_DESC_NULL, %eax<br />movl %eax, (GDTPtr + 2) <br /><br />/* Enable A20 line */<br />xor %ax, %ax<br />in $0x92, %al<br />or $2, %al<br />out %al, $0x92<br /><br />cli<br /><br />/* Load the GDT base address and limit from memory into the GDTR register */<br />lgdt GDTPtr<br /><br />/* Enable protect mode */<br />movl %cr0, %eax<br />orl $1, %eax<br />movl %eax, %cr0<br /><br />/* Jump to protected-mode OS code */<br />ljmp $SegSelectorCode32, $0<br /><br /><br /># protected-mode OS code<br />PE_CODE32:<br />.code32<br />/* Load Video segment selector */<br />mov $(SegSelectorVideo), %ax<br />mov %ax, %gs<br /><br />/* Output the data */<br />movl $((80 * 10 + 0) * 2), %edi<br />movb $0xC, %ah<br />movb $'H', %al<br />mov %ax, %gs:(%edi)<br /><br />jmp .<br /><br />.set PECode32Len, (. - PE_CODE32)<br /><br />os_msg:<br />.ascii "Welcome to OS context!"<br />.byte 0<br /><br />.org 0x200, 0x41 # fill characters with 'A'. Sector 2<br /><br /></code></pre><br /><span style="font-size:130%;"><span style="font-weight: bold;">pm.h標頭檔</span></span><br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* pm.h<br />*<br />* Adrian Huang (adrianhuang0701@gmail.com)<br />*/<br /><br /><br />.macro SEG_DESC Base, Limit, Attr<br />.2byte (\Limit & 0xFFFF)<br />.2byte (\Base & 0xFFFF)<br />.byte ((\Base >> 16) & 0xFF)<br />.2byte ((\Attr & 0xF0FF) | ((\Limit >> 8) & 0x0F00))<br />.byte ((\Base >> 24) & 0xFF)<br />.endm<br /><br />.macro InitSegDescriptor OFFSET GDT_SEG_ADDR<br />xor %ax, %ax<br />mov %cs, %ax<br />shl $4, %eax<br />addl $(\OFFSET), %eax<br />movw %ax, (\GDT_SEG_ADDR + 2)<br />shr $16, %eax<br />movb %al, (\GDT_SEG_ADDR + 4)<br />movb %ah, (\GDT_SEG_ADDR + 7)<br /><br />.endm<br /><br />.set DESC_ATTR_TYPE_LDT, 0x82 /* LDT Segment */<br />.set DESC_ATTR_TYPE_CD_ER, 0x9A /* Code segment with Execute/Read */<br />.set DESC_ATTR_TYPE_CD_E, 0x98 /* Code segment with Execute Only */<br />.set DESC_ATTR_TYPE_CD_RW, 0x92 /* Data segment with R/W */<br />.set DESC_ATTR_D, 0x4000 /* 32-bit segment */<br /><br />/* Selector Attribute */<br />.set SA_TIL, 0x4<br />.set SA_RPL0, 0x0<br />.set SA_RPL1, 0x1<br />.set SA_RPL2, 0x2<br />.set SA_RPL3, 0x3<br /></code></pre><br /><span style="font-size:130%;"><span style="font-weight: bold;">編譯程式碼</span></span><br /><span class="Apple-style-span" style="font-size:medium;">下圖為編譯的Makefile。<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>LD=ld<br />CC=gcc<br /><br />all: boot_loader.bin<br /><br />boot_loader.bin: boot_loader.o os.o<br />${LD} -Ttext=0x7C00 -s $< -o $@ --oformat binary<br />${LD} -Ttext=0x0 -s os.o -o os.bin --oformat binary<br />cat os.bin >> $@<br /><br />boot_loader.o:<br />${CC} -c boot_loader.S<br /><br />os.o:<br />${CC} -c os.S<br /><br />clean:<br />rm -f boot_loader.o os.o os.bin boot_loader.bin<br /><br /></code></pre><br /></span><span style="font-size:100%;">其編譯訊息如下所示: </span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/04day/pe-orig-makefile$ make all<br />gcc -c boot_loader.S<br />gcc -c os.S<br />ld -Ttext=0x7C00 -s boot_loader.o -o boot_loader.bin --oformat binary<br />ld -Ttext=0x0 -s os.o -o os.bin --oformat binary<br />ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000<br />cat os.bin >> boot_loader.bin<br /><br /></code></pre><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">QEMU測試結果</span></span><br /><a href="http://picasaweb.google.com/lh/photo/h4xkhlHExbFSKDfcf_OWtQ?feat=embedwebsite"><img src="http://lh4.ggpht.com/_VP7dWGNif7M/TKQ-OzdYlmI/AAAAAAAACQg/4BeXj_eQ3Qw/s800/qemu_result.jpeg" height="312" width="800" /></a><br /><br />【Reference】<br />[1] <a href="http://share.solrex.org/WriteOS/index_cht.html">Solrex - 使用開源軟體-自己動手寫作業系統</a><br />[2] <span style="text-decoration: underline;"></span><a href="http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBUQhgIwAA&url=http%3A%2F%2Fwww.intel.com%2FAssets%2Fja_JP%2FPDF%2Fmanual%2F253668.pdf&ei=8e2jTM7TLorEvQOAsrynDQ&usg=AFQjCNGQ-LN-fJMj1hQhxGaiDArpzKB15Q&sig2=Q0bhLPtu2mgT6V18u9XWBw">Intel 64 and <em>IA</em>-<em>32</em> Architectures. Software <em>Developer's Manual</em>. <em>Volume 3A</em></a><br />[3] <a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a><br />[4] <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a><br />[5] <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a><br />[6] <span style="font-size:100%;"><a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span><br />[7] linux-source-2.6.31Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com2tag:blogger.com,1999:blog-6594360167010513475.post-88505930428987056252010-08-31T18:58:00.005+08:002010-08-31T20:03:25.593+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (三) Boot Loader + 作業系統載入實例 (CF Card)<a href="http://adrianhuang.blogspot.com/2010/08/gnu-assembler-boot-loader.html">上篇</a>說明如何撰寫小型Boot Loader將作業系統載入至記憶體並執行該作業系統程式碼,並利用qemu實現。為了更真實性,本篇將boot loader及作業系統安裝在CF card並利用CF card開機,用以證明該boot loader及作業系統可以正確地在實體機器上運行。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">將DL暫存器更改為0x80</span></span><br />由於<a href="http://adrianhuang.blogspot.com/2010/08/gnu-assembler-boot-loader.html">上篇</a>是使用軟碟機開機,因此在使用BIOS中斷服務0x13時 (AH=02 Read Sectors from Driver),需將DL暫存器設定為0(0代表軟碟機0,1代表軟碟機1),但因為現在要從硬碟讀取,所以需將DL設定為0x80,即底下範例程式紅色部份。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">Boot Loader範例程式 (以FAT32為範例)</span></span><br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* boot_loader.S<br />*<br />* Copyright (C) 2010 Adrian Huang (adrianhuang0701@gmail.com)<br />*<br />* This code is intended to simulate a simplified boot loader. This boot<br />* loader loads 3 sectors into the physical memory and jumps the entry<br />* point of OS.<br />*<br />*/<br /><br />BOOT_SEG = 0x07C0 /* starting code segment (CS) of boot loader */<br />OS_SEG = 0x0800 /* code segment address of OS entry point */<br />OS_OFFSET = 0x0000 /* the offset address of OS entry point */<br /><br /> .code16<br /><br /> .section .text<br /><br /> .global _start<br />_start:<br /> # FAT12 file system format<br /> ljmp $BOOT_SEG, $start_prog # jmp instruction<br /><br /> .byte 0x90<br /> .ascii "ADRIAN " # OEM name (8 bytes)<br /> .word 512 # Bytes per sector<br /> .byte 1 # Sector per cluster<br /> .word 32 # Reserved sector count: should be 32 for FAT32<br /> .byte 2 # Number of file allocation tables. <br /> .word 0 # Maximum number of root directory entries. 0 for FAT32<br /> .word 0 # Total sectors<br /> .byte 0xf8 # Media descriptor: fix disk<br /> .word 9 # Sectors per File Allocation Table<br /> .word 18 # Sectors per track<br /> .word 2 # Number of heads<br /> .long 0 # Count of hidden sectors<br /> .long 2030112 # Total sectors<br /> .byte 0 # Physical driver number<br /> .byte 0 # Reserved<br /> .byte 0x29 # Extended boot signature<br /> .long 0x12345678 # Serial Number<br /> .ascii "HELLO-OS " # Volume Label<br /> .ascii "FAT12 " # FAT file system type<br /> .fill 18, 1, 0 # fill 18 characters with zero<br /><br />start_prog:<br /> # initialize the register with cs register<br /> movw %cs, %ax<br /> movw %ax, %ds<br /> movw %ax, %es<br /> movw %ax, %ss<br /> xorw %sp, %sp<br /><br /> cld # clear direction flag<br /> sti # set interrupt flag<br /><br /> # The following code is loaded three sectors (2-4th sectors from boot.bin)<br /> # into the physical memory 0x8000-0x85FF.<br /> movw $OS_SEG, %ax<br /> mov %ax, %es # ES:BX-> destination buffer address pointer<br /> movb $2, %cl # sector<br /><br /><br />cont:<br /> movw $0, %bx<br /> movb $0x02, %ah # Read sectors from drive<br /> movb $0x1, %al # Sectors to read count<br /> movb $0x0, %ch # track<br /> movb $0x0, %dh # head<br /><span style="color: rgb(255, 0, 0);"> movb $0x80, %dl # drive</span><br /><br /> int $0x13 # trigger a interrupt 0x13 service<br /> jc fail # the clear flag is set if the operation is failed<br /><br /> mov %es, %ax <br /> addw $0x20, %ax # move to the next sector<br /> movw %ax, %es # move to the next sector<br /> incb %cl<br /> cmpb $3, %cl # has finished reading 3 sectors?<br /> jbe cont # continue to read the sector<br /><br /> jmp os_entry # jump to OS entry point<br /><br />fail:<br /> movw $err_msg, %si<br />fail_loop:<br /> lodsb <br /> andb %al, %al<br /> jz end<br /> movb $0x0e, %ah<br /> int $0x10 <br /> jmp fail_loop<br /><br /><br />os_entry:<br /> ljmp $OS_SEG, $OS_OFFSET # jump to os context<br /><br />end:<br /> hlt<br /><br />err_msg:<br /> .ascii "Reading sectors operation is failed!"<br /> .byte 0<br /><br /> .org 0x1FE, 0x41 # fill the rest of characters with zero until the 254th character<br /><br /> # Boot sector signature<br /> .byte 0x55<br /> .byte 0xaa<br /><br /></code></pre><br /><span class="Apple-style-span" style="font-size:130%;"><b>作業系統程式碼與編譯</b></span><br />請參照<a href="http://adrianhuang.blogspot.com/2010/08/gnu-assembler-boot-loader.html">這篇</a>的"作業系統程式碼"與<span style="font-size:78%;"><span class="Apple-style-span" style="font-size:large;"></span></span>"編譯程式碼"。<br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">安裝boot_loader.bin安裝至CF card</span></span><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-mem1:~/img$ sudo dd if=./boot_loader.bin of=/dev/sda<br /></code></pre><br /><span style="font-size:130%;"><span style="font-weight: bold;">測試結果</span></span><br />將該系統重開並選擇CF Card開機,其畫面如下:<br /><a href="http://www.flickr.com/photos/adrianhuang/4944460647/" title="Flickr 上 adrianhuang0701 的 DSC00165"><img src="http://farm5.static.flickr.com/4138/4944460647_3a59b9dd54_z.jpg" alt="DSC00165" height="480" width="640" /></a><br /><br />【Reference】<br />1.<a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a><br />2. <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a><br />3. <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a><br />4.<span style="font-size:100%;"> <a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a><br />5. </span><span class="Apple-style-span" style="color: rgb(51, 51, 51); font-size: 100%;"><span class="Apple-style-span" style="line-height: 20px; font-size: 13px;"><span class="Apple-style-span" style="font-size: medium;">linux-source-2.6.31</span></span></span>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com2tag:blogger.com,1999:blog-6594360167010513475.post-77004345315148185012010-08-20T10:43:00.001+08:002010-08-20T10:47:07.972+08:00Google將推出Chrome作業系統的平板電腦Google將與HTC合作,以Google Chrome作業系統開發平板電腦,預計今年11月26發表。<br /><br />詳見:<a href="http://itmanagement.earthweb.com/features/article.php/3899401/First-Chrome-OS-Tablet-Set-for-Black-Friday-Debut.htm"><span style="font-size:85%;"> First Chrome OS Tablet Set for Black Friday Debut</span></a><br /><br />文中還提及為什麼不使用Android的原因。Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-58873823149019236062010-08-13T10:57:00.003+08:002010-08-13T13:46:45.284+08:00Google大神開始對Linux Kernel Source貢獻了Linux 2.6.35有Google大神的程式碼了!! (跪拜0rz......)<br />其主要提升網路封包處理速度<br />詳見: <a href="http://www.linuxplanet.com/linuxplanet/reports/7139/1/">Linux 2.6.35 Includes Speedy Google Code, Less Bloat</a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com1tag:blogger.com,1999:blog-6594360167010513475.post-42548147275875545082010-08-11T10:00:00.017+08:002010-08-31T20:00:23.208+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (二) Boot Loader + 作業系統載入實例 (QEMU)繼<a href="http://adrianhuang.blogspot.com/2010/08/gnu-assembler-hello-world.html">上篇</a>說明如何撰寫開機Hello World,本篇文章說明如何撰寫簡單的Boot Loader跟一個只會印出訊息的作業系統。<br /><br /><span class="Apple-style-span" style="font-size:large;"><b>小型Boot Loader設計概念</b></span><br />筆者所撰寫的小型Boot Loader於BIOS開機成功後,會被載入至實體記憶體位址0x7C00並跳至此位址執行boot loader的程式碼,此boot loader程式碼會將作業系統程式碼 (僅三個磁區),載入實體記憶體位址0x8000並跳至此位址執行作業系統的程式碼,然而此作業系統別無功能,僅會印出簡單的訊息。如此便能模擬一般boot loader載入作業系統的程序。<div><br /></div><div>圖一為筆者所編譯出來的plain binary file,此程式碼僅有四個磁區 (共2048 bytes),0x0-0x1ff為boot loader磁區,0x200-0x7ff為作業系統的三個磁區,雖然,真正的作業系統程式碼在0x200-0x3ff,其它兩個磁區僅填入字元'B'與'C',但筆者還是把這三個磁區稱為作業系統程式碼,因為boot loader會將這三個磁區載入實體記憶體位址0x8000。<br /><div><br /></div><div><table style="width: auto;"><tbody><tr><td><a href="http://picasaweb.google.com/lh/photo/8p3bHqe8N6QqnGTbg-dSrQ?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TGE5bKfnz8I/AAAAAAAACPA/Pl9WUK3-Yaw/s400/os_bl_bin_img.jpeg" /></a></td></tr></tbody></table></div></div><b><span class="Apple-style-span" style="font-size:large;">圖一、Boot + OS Binary Image Layout<br /></span></b><br />圖二為boot loader將作業系統載入實體記憶體位址示意圖,至於為什麼會選擇0x8000開始存放作業系統程式碼,其原因是x86系統規範位址<a href="http://wiki.osdev.org/Memory_Map_%28x86%29">0x7E00-0x7FFFF為conventional memory</a>,因此筆者就挑0x8000來存放程式碼。<div><br /><table style="width: auto;"><tbody><tr><td><a href="http://picasaweb.google.com/lh/photo/2e_NK3jlrGxbBoYTolLqzw?feat=embedwebsite"><img src="http://lh4.ggpht.com/_VP7dWGNif7M/TGE5bbrZ3TI/AAAAAAAACPE/_JSWRtVR5kY/s400/os_bl_mem_layout.jpeg" /></a></td></tr></tbody></table><span class="Apple-style-span"><b><span class="Apple-style-span"><span class="Apple-style-span" style="font-size:large;">圖二、Boot Loader + OS Physical Memory Layout</span></span></b></span><br /><br /><span class="Apple-style-span" style="font-size:large;"><b>Boot Loader程式碼</b></span></div>下圖為Boot Loader程式碼,其運作原理在此稍作描述。首先,boot_loader透過.byte、.word、.long跟.ascii等指令將此磁區描述為一個<a href="http://en.wikipedia.org/wiki/File_Allocation_Table">FAT12檔案系統</a>。接著,利用<a href="http://en.wikipedia.org/wiki/INT_13">中斷服務編號0x13</a>將作業系統的三個磁區讀入0x8000實體記憶體位址。如果讀取失敗的話,則利用<a href="http://en.wikipedia.org/wiki/INT_10">中斷服務編號0x010</a>印出錯誤訊息。特別要提出的是,程式碼使用兩次遠程跳躍 (Far Dump),其原型ljmp code_segment_address, relative_address,例如: ljmp $BOOT_SEG, $start_prog代表code segment設定為0x07C0加上start_prog標籤的位址,即0x7C00+start_prog位址,此為<a href="http://en.wikipedia.org/wiki/X86_memory_segmentation">Intel x86 CPU memory segmentation機制</a>。透過此設定,boot loader程式碼便能正確地在0x7C00位址執行。另一個ljmp,ljmp $OS_SEG, $OS_OFFSET,因為boot loader將作業系統程式碼放在0x8000實體記憶體位址,因此code segment必須設為0x0800,以便讓作業系統程式碼可以正確地執行。<div><div><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* boot_loader.S<br />*<br />* Copyright (C) 2010 Adrian Huang (adrianhuang0701@gmail.com)<br />*<br />* This code is intended to simulate a simplified boot loader. This boot<br />* loader loads 3 sectors into the physical memory and jumps the entry<br />* point of OS.<br />*<br />*/<br /><br />BOOT_SEG = 0x07C0 /* starting code segment (CS) of boot loader */<br />OS_SEG = 0x0800 /* code segment address of OS entry point */<br />OS_OFFSET = 0x0000 /* the offset address of OS entry point */<br /><br />.code16<br /><br />.section .text<br /><br />.global _start<br />_start:<br /># FAT12 file system format<br />ljmp $BOOT_SEG, $start_prog # jmp instruction<br /><br />.byte 0x90<br />.ascii "ADRIAN " # OEM name (8 bytes)<br />.word 512 # Bytes per sector<br />.byte 1 # Sector per cluster<br />.word 1 # Reserved sector count: should be 1 for FAT12<br />.byte 2 # Number of file allocation tables.<br />.word 224 # Maximum number of root directory entries.<br />.word 2880 # Total sectors<br />.byte 0xf0 # Media descriptor<br />.word 9 # Sectors per File Allocation Table<br />.word 18 # Sectors per track<br />.word 2 # Number of heads<br />.long 0 # Count of hidden sectors<br />.long 2880 # Total sectors: 18 (sectors per track) * 2 (heads) * 80 (sectors) = 2880<br />.byte 0 # Physical driver number<br />.byte 0 # Reserved<br />.byte 0x29 # Extended boot signature<br />.long 0x12345678 # Serial Number<br />.ascii "HELLO-OS " # Volume Label<br />.ascii "FAT12 " # FAT file system type<br />.fill 18, 1, 0 # fill 18 characters with zero<br /><br />start_prog:<br /># initialize the register with cs register<br />movw %cs, %ax<br />movw %ax, %ds<br />movw %ax, %es<br />movw %ax, %ss<br />xorw %sp, %sp<br /><br />cld # clear direction flag<br />sti # set interrupt flag<br /><br /># The following code is loaded three sectors (2-4th sectors from boot.bin)<br /># into the physical memory 0x8000-0x85FF.<br />movw $OS_SEG, %ax<br />mov %ax, %es # ES:BX-> destination buffer address pointer<br />movb $2, %cl # sector<br /><br /><br />cont:<br />movw $0, %bx<br />movb $0x02, %ah # Read sectors from drive<br />movb $0x1, %al # Sectors to read count<br />movb $0x0, %ch # track<br />movb $0x0, %dh # head<br />movb $0, %dl # drive<br /><br />int $0x13 # trigger a interrupt 0x13 service<br />jc fail # the clear flag is set if the operation is failed<br /><br />mov %es, %ax<br />addw $0x20, %ax # move to the next sector<br />movw %ax, %es # move to the next sector<br />incb %cl<br />cmpb $3, %cl # has finished reading 3 sectors?<br />jbe cont # continue to read the sector<br /><br />jmp os_entry # jump to OS entry point<br /><br />fail:<br />movw $err_msg, %si<br />fail_loop:<br />lodsb<br />andb %al, %al<br />jz end<br />movb $0x0e, %ah<br />int $0x10<br />jmp fail_loop<br /><br /><br />os_entry:<br />ljmp $OS_SEG, $OS_OFFSET # jump to os context<br /><br />end:<br />hlt<br /><br />err_msg:<br />.ascii "Reading sectors operation is failed!"<br />.byte 0<br /><br />.org 0x1FE, 0x41 # fill the rest of characters with zero until the 254th character<br /><br /># Boot sector signature<br />.byte 0x55<br />.byte 0xaa<br /><br /></code></pre><br /></div></div><div><span class="Apple-style-span" style="font-size:large;"><b>作業系統程式碼</b></span></div>此段程式碼僅將訊息輸出至螢幕上,所以不再贅述。<pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>/* os.S</code><div> * </div><div> * Copyright (C) 2010 Adrian Huang (adrianhuang0701@gmail.com)</div><div> *</div><div> * This code is OS context. </div><div> *</div><div> */</div><div> .code16</div><div> .section .text</div><div> .global main</div><div><br /></div><div>main:</div><div> movw %cs, %ax</div><div> movw %ax, %ds</div><div> movw %ax, %es</div><div> movw %ax, %ss</div><div> xorw %sp, %sp</div><div> </div><div> cld # clear direction flag </div><div> sti # set interrupt flag</div><div><br /></div><div> movw $os_msg, %si</div><div> </div><div>load_msg:</div><div> lodsb</div><div> andb %al, %al</div><div> jz os_fin</div><div> movb $0x0e, %ah</div><div> int $0x10</div><div> jmp load_msg</div><div><br /></div><div>os_fin:</div><div> hlt</div><div> jmp os_fin</div><div><br /></div><div>os_msg:</div><div> .ascii "Welcome to OS context!"</div><div> .byte 0 </div><div><br /></div><div> .org 0x200, 0x41 # fill characters with 'A'. Sector 1</div><div> .org 0x400, 0x42 # fill characters with 'B'. Sector 2</div><div> .org 0x600, 0x43 # fill characters with 'C'. Sector 3</div><div><br /></div><div></div></pre><b><span class="Apple-style-span" style="font-size:large;">編譯程式碼 </span></b><br /><span class="Apple-style-span" style="font-size:medium;">下圖為編譯的Makefile。</span><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>LD=ld</code><div>CC=gcc</div><div><br /></div><div>all: boot_loader.bin</div><div><br /></div><div>boot_loader.bin: boot_loader.o os.o</div><div> ${LD} -Ttext=0x0 -s $< -o $@ --oformat binary</div><div> ${LD} -Ttext=0x0 -s os.o -o os.bin --oformat binary</div><div> cat os.bin >> $@</div><div><br /></div><div>boot_loader.o:</div><div> ${CC} -c boot_loader.S</div><div><br /></div><div>os.o:</div><div> ${CC} -c os.S</div><div> </div><div><br /></div><div>clean:</div><div> rm -f boot_loader.o boot_loader.bin os.o</div><div><br /></div><div></div></pre> 其編譯訊息如下所示: <pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/boot_loader$ make clean all</code><div>rm -f boot_loader.o boot_loader.bin os.o</div><div>gcc -c boot_loader.S</div><div>gcc -c os.S</div><div>ld -Ttext=0x0 -s boot_loader.o -o boot_loader.bin --oformat binary</div><div>ld -Ttext=0x0 -s os.o -o os.bin --oformat binary</div><div>ld: warning: cannot find entry symbol _start; defaulting to 0000000000000000</div><div>cat os.bin >> boot_loader.bin</div><div>adrian@adrian-desktop:~/working/build_os/my_ex/boot_loader$ </div><div><br /></div><div></div></pre><div><br /></div><div><b><span class="Apple-style-span" style="font-size:large;">測試結果</span></b></div><div><br /></div><div><table style="width: auto;"><tbody><tr><td><a href="http://picasaweb.google.com/lh/photo/QJpSfrQcJAGP1TEYDpqXNw?feat=embedwebsite"><img src="http://lh3.ggpht.com/_VP7dWGNif7M/TGE5bUAIDOI/AAAAAAAACPI/brWVK2NMEnk/s800/qemu.jpeg" /></a></td></tr></tbody></table></div><div><br /></div><div>為了驗證作業系統的程式碼正確地載入實體記憶體位址0x8000,筆者利用xxd工具將boot_loader.bin dump出來,下圖為其結果。紅色框框為作業系統程式碼的十六進制碼。</div><div><table style="width: auto;"><tbody><tr><td><a href="http://picasaweb.google.com/lh/photo/mP8yZuGmO5ZFUiTRoyzxPg?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TGJdT_bHezI/AAAAAAAACPY/ERb56Mrd7js/s800/boot_loader_bin.jpeg" /></a></td></tr></tbody></table></div><div><br /></div><div>下圖中,筆者利用qemu提供的xp工具將0x8000-0x8010的內容dump出來,用以跟上圖0x200-0x210比對,比較上、下這兩張圖,可以證明作業系統程式碼正確地被載入至0x8000。</div><div><br /></div><div><table style="width: auto;"><tbody><tr><td><a href="http://picasaweb.google.com/lh/photo/cRCWjYhThYMjp4QOSsaDmA?feat=embedwebsite"><img src="http://lh5.ggpht.com/_VP7dWGNif7M/TGJjrKb-jZI/AAAAAAAACPg/6N6r0NKVRCk/s800/qemu_xp.jpeg" /></a></td></tr></tbody></table></div><div><br /></div><div><br /></div><div><span class="Apple-style-span" style="color: rgb(51, 51, 51);font-size:100%;" ><span class="Apple-style-span" style="line-height: 20px;font-size:13px;" ><div><span class="Apple-style-span" style="font-size:medium;">【Reference】</span></div><div><span class="Apple-style-span" style="font-size:medium;">1.<a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a></span></div><div><span class="Apple-style-span" style="font-size:medium;">2. <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a></span></div><div><span class="Apple-style-span" style="font-size:medium;">3. <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a></span></div><div><span class="Apple-style-span" style="font-size:medium;">4.<span style="font-size:100%;"> <a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span></span></div><div><span class="Apple-style-span" style="font-size:medium;">5. linux-source-2.6.31</span></div></span></span></div>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com1tag:blogger.com,1999:blog-6594360167010513475.post-66020708539477756792010-08-03T16:39:00.030+08:002011-08-23T21:05:36.042+08:00[打造簡易作業系統 - 以GNU Assembler組合語言撰寫] (一) 開機Hello World實例小弟最近想嘗試利用GAS(GNU Assembler)組合語言撰寫非常小型的作業系統,本篇文章說明如何利用GAS組合語言在終端機上印出Hello World.
<br />
<br /><span style="font-weight: bold;font-size:130%;" >簡介x86 CPU開機流程</span>
<br />x86 CPU開機後,首要之事會先跳至0xFFFF0執行BIOS ROM的程式,當BIOS測試程序通過後,BIOS便會把執行權交給下一個程式 (boot loader或一支小程式),BIOS會將該程式
<br />(通常為一個磁區[Sector]大小,即512 bytes)載入記憶體0x7C00位置,並跳至0x7C00執行該段程式碼。該磁區被稱為MBR,BIOS會檢查該磁區最後兩個位完組必須為0x55AA,否則該磁區該被視為無效的MBR。
<br />
<br />所以,首要之事就是利用GAS撰寫一支大小為512位元組的二進位檔 (Binary File),此檔需具備底下功能:<ul><li>檔案系統,如: <a style="font-weight: bold;" href="http://en.wikipedia.org/wiki/File_Allocation_Table">FAT12</a>, FAT16, FAT32, NTFS等等。</li><li>利用<a style="font-weight: bold;" href="http://en.wikipedia.org/wiki/INT_10">BIOS中斷號碼0x10</a>將資料寫至螢幕。</li><li>在最後兩個位元組寫入0x55AA以便通過BIOS識別。</li></ul><span style="font-weight: bold;">底下為原始碼:</span><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"><code> .code16
<br />
<br /> .section .text
<br /> .global main
<br />main:
<br /> # FAT12 file system format
<br /> jmp start_prog # jmp instruction
<br /> .byte 0x90
<br /> .ascii "ADRIAN " # OEM name (8 bytes)
<br /> .word 512 # Bytes per sector
<br /> .byte 1 # Sector per cluster
<br /> .word 1 # Reserved sector count: should be 1 for FAT12
<br /> .byte 2 # Number of file allocation tables.
<br /> .word 224 # Maximum number of root directory entries.
<br /> .word 2880 # Total sectors
<br /> .byte 0xf0 # Media descriptor:
<br /> .word 9 # Sectors per File Allocation Table
<br /> .word 18 # Sectors per track
<br /> .word 2 # Number of heads
<br /> .long 0 # Count of hidden sectors
<br /> .long 2880 # Total sectors: 18 (sectors per track) * 2 (heads) * 80 (sectors) = 2880
<br /> .byte 0 # Physical driver number
<br /> .byte 0 # Reserved
<br /> .byte 0x29 # Extended boot signature
<br /> .long 0x12345678 # Serial Number
<br /> .ascii "HELLO-OS " # Volume Label
<br /> .ascii "FAT12 " # FAT file system type
<br /> .fill 18, 1, 0 # fill 18 characters with zero
<br />
<br />start_prog:
<br /> movw $0, %ax # Initialize register
<br /> movw %ax, %ss
<br /> movw %ax, %ds
<br /> movw %ax, %es
<br />
<br /> movw $msg, %si # move the address of msg to SI
<br />
<br />
<br />loop:
<br /> movb $0xe, %ah
<br /> movb (%si), %al # move the first character of msg to AL register
<br /> cmpb $0, %al
<br /> je fin
<br /> int $0x10 # write the specific character to console
<br /> addw $1, %si
<br /> jmp loop
<br />
<br />fin:
<br /># do nothing
<br />
<br />msg:
<br /> .ascii "Hello, World! This is Adrian Huang."
<br /> .byte 0
<br />
<br /> .org 0x1FE, 0x00 # fill the rest of characters with zero until the 254th character
<br />
<br /># Boot sector signature
<br /> .byte 0x55
<br /> .byte 0xaa
<br /></code></pre>
<br /><span style="font-size:130%;"> <span style="font-weight: bold;">Compile and Link</span></span>
<br />以"gcc -c"將.S組合語言轉為成object file
<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 12px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/02day/helloos4$ ls
<br />hello.S
<br />adrian@adrian-desktop:~/working/build_os/my_ex/02day/helloos4$ gcc -c hello.S
<br />adrian@adrian-desktop:~/working/build_os/my_ex/02day/helloos4$ file hello.o
<br />hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
<br />adrian@adrian-desktop:~/working/build_os/my_ex/02day/helloos4$</code></pre>
<br />再經由ld連結器將此object轉換成plain binary file
<br /><pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 12px;padding: 5px; overflow: auto; width: 100%"><code>$ ld -Ttext=0x7C00 hello.o -o hello.bin --oformat binary
<br />$ file hello.bin
<br />hello.bin: DOS floppy 1440k, x86 hard disk boot sector</code></pre>
<br />
<br />利用xxd工具觀察helo.bin格式 (以十六進制)
<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 12px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-desktop:~/working/build_os/my_ex/02day/helloos4$ xxd hello.bin
<br />0000000: eb4e 9041 4452 4941 4e20 2000 0201 0100 .N.ADRIAN .....
<br />0000010: 02e0 0040 0bf0 0900 1200 0200 0000 0000 ...@............
<br />0000020: 400b 0000 0000 2978 5634 1248 454c 4c4f @.....)xV4.HELLO
<br />0000030: 2d4f 5320 2020 4641 5431 3220 2020 0000 -OS FAT12 ..
<br />0000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000050: b800 008e d08e d88e c0be 6b7c b40e 8a04 ..........k|....
<br />0000060: 3c00 7407 cd10 83c6 01eb f148 656c 6c6f <.t........Hello
<br />0000070: 2c20 576f 726c 6421 2054 6869 7320 6973 , World! This is
<br />0000080: 2041 6472 6961 6e20 4875 616e 672e 0000 Adrian Huang...
<br />0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000150: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />0000190: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<br />00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
<br />
<br /></code></pre>
<br />
<br />接著,使用qemu驗證hello.bin
<br />
<br /><a href="http://www.flickr.com/photos/adrianhuang/4861596201/" title="Flickr 上 adrianhuang0701 的 qemu"><img src="http://farm5.static.flickr.com/4135/4861596201_5343a8c136_z.jpg" alt="qemu" height="294" width="640" /></a>
<br />
<br />為了讓此範例更真實,筆者有一台機器備有CF Card,將hello.bin透過dd工具寫進此CF Card最前面的512 bytes, 命令如下:
<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 12px; padding: 5px; overflow: auto; width: 100%;"><code>adrian@adrian-mem1:~/img$ sudo dd if=./hello.bin of=/dev/sda
<br />[sudo] password for adrian:
<br />1+0 records in
<br />1+0 records out
<br />512 bytes (512 B) copied, 0.00124243 s, 412 kB/s
<br />adrian@adrian-mem1:~/img$
<br /></code></pre>
<br />
<br />將該系統重開並選擇CF Card開機,其畫面如下:
<br />
<br /><a href="http://www.flickr.com/photos/adrianhuang/4861596195/" title="Flickr 上 adrianhuang0701 的 DSC00153"><img src="http://farm5.static.flickr.com/4143/4861596195_ed30848882_z.jpg" alt="DSC00153" height="480" width="640" /></a>
<br />
<br />【Reference】
<br />1.<a href="http://goods.ruten.com.tw/item/show?21003267617319">30天打造OS!作業系統自作入門</a>
<br />2. <a href="http://blog.linux.org.tw/%7Ejserv/archives/002031.html">Jserv's Blog</a>
<br />3. <a href="http://www.cs.nctu.edu.tw/%7Ehuangmc/works/web/Boot_x86/Boot_x86.html"><span class="titledown">X86 開機流程小記</span></a>
<br />4.<span style="font-size:100%;"> <a href="http://www.ibm.com/developerworks/library/l-gas-nasm.html">Linux assemblers: A comparison of GAS and NASM</a></span>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com4tag:blogger.com,1999:blog-6594360167010513475.post-30880946038066617962010-03-19T11:25:00.002+08:002010-03-19T11:48:01.370+08:00簡介Linux Block I/O Layer (三) - I/O Path此系列文章最後講解Block I/O Layer I/O Path運作原理。圖一為I/O Path簡易圖,檔案系統核心經由submit_bio函式將該bio交給Block I/O Layer的generic_make_request和__generic_make_request函式,緊接著呼叫__make_request_fn callback函式 (對應至__make_request()),此函式主要目的用來檢查此bio是否可以跟尚未處理的request結構的bio成員結合在一起 (merge),如果不行的話,則另外配置一個新的request結構並將其bio安插至該reqeust結構,再將該request交給I/O Scheduler,最後Block I/O Layer經由request_fn() callback function將待處理的request交給SCSI子系統。<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VP7dWGNif7M/S6LqemBO5yI/AAAAAAAACMY/Z4j3PCTZADo/s1600-h/generic_block_dev_io_path.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 288px;" src="http://1.bp.blogspot.com/_VP7dWGNif7M/S6LqemBO5yI/AAAAAAAACMY/Z4j3PCTZADo/s320/generic_block_dev_io_path.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5450176310394414882" /></a><br />                               圖一、Block I/O Layer之I/O Path簡易圖<br /><br /><br />下圖二為Linux Block I/O Layer核心I/O Path示意圖,此圖以ReiserFS為例,此圖對每個函式有詳細的解釋,所以在此不再解釋,請參照圖二。<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_VP7dWGNif7M/S6LwotBWInI/AAAAAAAACMw/-4tU5m8JkeM/s1600-h/block_system_without_iosched.jpeg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 234px;" src="http://1.bp.blogspot.com/_VP7dWGNif7M/S6LwotBWInI/AAAAAAAACMw/-4tU5m8JkeM/s320/block_system_without_iosched.jpeg" border="0" alt=""id="BLOGGER_PHOTO_ID_5450183081142395506" /></a><br /><br />                               圖二、Block I/O Layer之I/O Path<br /><br /><br />【Reference】<br />1. Linux Device Driver, third edition<br />2. Linux Kernel Source 2.6.31<br />3. <a href="http://">Request-based Device-mapper multipath and Dynamic load balancing</a><br />4. Understanding the Linux Kernel, Third Edition - Chapter 14. Block Device DriversAdrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com1tag:blogger.com,1999:blog-6594360167010513475.post-64646812775498951672010-03-18T13:52:00.005+08:002010-03-18T16:13:46.258+08:00簡介Linux Block I/O Layer (二) - 探討BIO (Block I/O) and Request 結構上一篇文章簡單介紹page, bio和request結構的定位,本篇文章著重於探討bio與request結構是如何串起來的。底下將分別介紹reqeust queue、request、bio與bio_vec等資料結構。首先,下圖展示這四個資料結構的關係。<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VP7dWGNif7M/S6HDGnHSc3I/AAAAAAAACMM/9h_RTJzWq2Q/s1600-h/rq_struct.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 211px;" src="http://4.bp.blogspot.com/_VP7dWGNif7M/S6HDGnHSc3I/AAAAAAAACMM/9h_RTJzWq2Q/s320/rq_struct.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449851542441194354" /></a><br /><br /><span style="font-weight:bold;">Request Queue</span><br /> Request Queue用來將待處理的request串成一個<a href="http://adrianhuang.blogspot.com/2007/10/linux-kernel-listhead.html">雙向的鏈結串列</a>,此結構 (struct request_queue)定義在include/linux/blkdev.h檔頭檔。<br /><br /><span style="font-weight:bold;">Request</span><br />一個request資料結構,即表示一次block I/O傳輸請求。結構裡的queuelist便是將整個request串起來的成員,bio成員代表該request所欲傳輸block I/O個數,buffer成員代表當前資料傳輸的buffer區塊。request資料結構裡還有許多其它成員,詳見include/linux/blkdev.h標頭檔。<br /><br /><span style="font-weight:bold;">BIO (Block I/O)</span><br />當block I/O layer上層 (<a href="http://adrianhuang.blogspot.com/2010/03/linux-block-driver-layer-page-bio-block.html">可參考此篇文章的圖</a>,Ex: 檔案系統或虛擬記憶體子系統)欲傳輸某一區塊時,該層便會產生一個bio結構並交由Block I/O Layer,Block I/O Layer將新請求的bio併入現有的request結構裡 (前提是該request結構裡的bio所欲傳輸的區塊磁區剛好跟新請求的bio所欲傳輸的區塊磁區相近,如此變能發揮更大的傳輸效益),或者產生一個新的request結構並將此bio併入此request結構,此結構 (struct request_queue)定義在include/linux/bio.h標頭檔。<br /><br /><span style="font-weight:bold;">bio_vec (BIO Vector)</span><br />bio結構裡有一個稱為bi_io_vec一維陣列的成員,該陣列成員紀錄欲傳輸的資料緩衝區在記憶體何處。<br /><br />【Reference】<br />1. Linux Device Driver, third edition<br />2. Linux Kernel Source 2.6.31 <br />3. <a href="http://www.kernel.org/doc/ols/2007/ols2007v2-pages-235-244.pdf">Request-based Device-mapper multipath and Dynamic load balancing</a><br />4. Understanding the Linux Kernel, Third Edition - Chapter 14. Block Device DriversAdrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-20655296167641911512010-03-18T09:26:00.001+08:002010-03-18T14:56:09.996+08:00簡介Linux Block I/O Layer (一) - Page, BIO (Block I/O) and Request 結構意義Linux Block I/O Layer主要處理上層檔案系統的請求,進而將該請求往丟至低層的low-level device driver (例如: Linux SCSI Subsystem),下圖展示出這三層的關係,由圖中可知檔案系統驅動程式主要以page結構來描述所欲存取的資料在哪裡, 接著檔案系統驅動程式將page結構轉換bio (Block I/O),並經由submit_bio函式將該請求送至Block I/O Layer,該層主要任務便將bio結構轉換成request結構 (往後會有幾篇探討該層運作細節),並將該request結構丟至low-level device driver,以上是簡單的介紹.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_VP7dWGNif7M/S6Caa34qrvI/AAAAAAAACMA/aYMzTC8TLYU/s1600-h/page_bio_request.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 276px;" src="http://4.bp.blogspot.com/_VP7dWGNif7M/S6Caa34qrvI/AAAAAAAACMA/aYMzTC8TLYU/s320/page_bio_request.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5449525335587204850" /></a><br /><br />【Reference】<br />1. Linux Device Driver, third edition<br />2. Linux Kernel Source 2.6.31 <br />3. <a href="http://www.kernel.org/doc/ols/2007/ols2007v2-pages-235-244.pdf">Request-based Device-mapper multipath and Dynamic load balancing<br /></a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-89405740616883387042010-03-15T11:30:00.003+08:002010-03-15T11:39:42.554+08:00C語言malloc之sizeof使用技巧C語言程式設計師使用結構指標時,在配置一塊記憶體時通常都會使用下列宣告描述 (粗體字):<br /><blockquote><br />struct abc {<br />     char *ptr;<br />     int var[20];<br />     struct abc *next;<br />};<br /><br /><span style="font-weight:bold;">struct abc *ptr = (struct abc *) malloc(sizeof(struct abc));</span><br /></blockquote><br /><br />另一種寫法可以將程式碼簡化,將sizeof(struct abc)改成sizeof(*ptr),如下所示:<br /><blockquote><br /><span style="font-weight:bold;">struct abc *ptr = malloc(sizeof(*ptr));</span><br /></blockquote><br /><br />提供給各位參考.Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-65399193222990743832010-01-25T16:39:00.004+08:002010-01-26T15:15:02.197+08:00Linux Kernel: container_of 巨集Linux驅動程式裡很常看container_of巨集,其目的為何呢? 假設Linux驅動程式只知道某一結構成員的位址,該驅動程式便可使用container_of巨集,將已知某一結構成員的位址計算出該結構的起始位址,其原型如下:<br /><blockquote><br /><br />#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)<br /><br />#define container_of(ptr, type, member) ({ \<br />     const typeof( ((type *)0)->member ) *__mptr = (ptr); \<br />     (type *)( (char *)__mptr - offsetof(type,member) );})<br /></blockquote><br /><br /><br />假設有一結構定義如下,且我們只知道phone_num成員的位址,如此便能使用該巨集計算出該結構變數的起始位址:<br /><br /><blockquote><br />/* The student structure definition */<br />typedef struct student {<br />     char name[16];<br />     int id;<br />     char addr[64]; <br />     char phone_num[16];<br />}STUDENT, *STUDENT_PTR;<br /></blockquote><br /><br />該結構記憶體配置圖如下所示:<br /><br /><a href="http://picasaweb.google.com/lh/photo/etahs6A5d_906eDhI1SNNw?feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXWF-7jG8sxRrKjoElKPCErH8J1CUDIDCRGrcGwrKVjrHWrQ0sVCZ52EgGP2AIjTi73yDhiCaRbfvicGrVg-vI9vFX4hZ4UbrgL7ZcsUg3VjhIAOajGVlUl8Yv4wX4rpZFDbdk3A8svm4/s800/struct.jpg" /></a><br />Figure 1. 結構記憶體配置圖<br /><br />範例程式碼:<br /><a href="http://picasaweb.google.com/lh/photo/p9n6zNgKwDbpiFEJi3MP0g?feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhERHH9sNqG9M6lHSKDwDbFzzruqUpf_3Pl6-oIEZpp3uOpYxH7qNjF_pfVinDAa0jIvvR5qsuSU3Lz13lE9Gy_ABRDQvn6IbwBHozFQ1p7_FNlETlsVR4HYiS76UHS_xjGhr9EuiNt9CU/s400/code.jpeg" /></a><br />Figure 2. 範例程式碼<br /><br /><br />以此結構為範例來看containter_of巨集的兩行程式碼:<br /><blockquote><br />◎ const typeof( ((type *)0)->member ) *__mptr = (ptr); ==> 得到phone_num的位址 <span style="color: rgb(255, 0, 0);">(x+84) </span>,請參考Figure 1跟Figure 2。<br />◎ (type *)( (char *)__mptr - offsetof(type,member) ); → (type *)( (char *)__mptr - ((size_t) &((type *)0)->member) ); ==> 其中((size_t) &((type *)0)->member)這段敘述代表以零為起始位址算出member這個成員的相對位址,即為phone_num成員的相對位址,也就是<span style="color: rgb(255, 0, 0);">84 </span> (請參考Figure 1)。所以整個敘述變成 <span style="color: rgb(255, 0, 0);"> (x+84) - 84 = x </span>,如此便能取得該結構變數的起始位址。<br /></blockquote><br /><br /><span style="font-size:130%;"><span style="font-weight: bold;">範例程式</span></span><br /><a href="http://picasaweb.google.com/lh/photo/TCN7HY0T-8zZwUZrItsZPg?feat=embedwebsite"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8asPAapIuRou00kyC-UzEEawZ7cnoCtwn1BODjpUTjdvqXcSOBc1hiTjAgJEl7wBx3fJMe3SgRmD04QE_lG5p5x0nvdsZUIBjBVU6IaEqdloVMayopr3Xf0GUyrQTBmnZY3lJe5sDCGM/s800/code_sample.jpeg" /></a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com3tag:blogger.com,1999:blog-6594360167010513475.post-47074300239394257132010-01-22T11:32:00.002+08:002010-01-22T16:31:28.778+08:00Linux Kernel: Lookaside Cache (前瞻快取)驅動程式往往根據使用者請求一次又一次地配置相同記憶體大小的物件。為此,Linux核心提供此需求的機制 - Lookaside cache。而此機制稱為Slab Allocator。<br /><br />以SCSI驅動程式為例,當使用者要讀寫硬碟資料時,會由區塊裝置驅動程式 (Block Device Driver)向SCSI層發出請求,SCSI層將此請求轉換成SCSI command的結構 (struct scsi_cmnd),為了能快速配置與存取SCSI command,SCSI層驅動程式為每一個SCSI command結構配置lookaside cache。其用法如下:<br /><blockquote><br />1. 首先,必須先配置一個快取物件導向,其函式: struct kmem_cache *<br />kmem_cache_create (const char *name, size_t size, size_t align,<br /> unsigned long flags, void (*ctor)(void *))<br /><br /><br />    ◎ name: 此快取物件名稱,此名稱會出現在/proc/slabinfo。<br />    ◎ size: 物件大小。<br />    ◎ align: 對齊字元數大小,通常為零。<br />    ◎ flag: 配置物件的方式,詳見include/linux/slab.h。<br />    ◎ ctor: 建構子函式位址。當核心成功地配置物件後,便會呼叫此函式。<br /><br />2. 接著,呼叫kmem_cache_alloc,配置每一個物件,其原型: void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);<br /><br />    ◎ cachep: kmem_cache_create回傳的位址。<br />    ◎ flags: 詳見kmalloc的flags參數。<br /><br />3. 釋放記憶體: <br /> void kmem_cache_free(struct kmem_cache *cachep, void *objp)<br /> → 釋放物件,但並未釋放快取。<br /> void kmem_cache_destroy(struct kmem_cache *cachep) → 釋放快取。<br /><br /></blockquote><br /><br />範例 (取自Linux SCSI驅動程式):<br /><a href="http://picasaweb.google.com/lh/photo/yiLvmxU8LG3afc8oYb9XWw?feat=embedwebsite"><img src="http://lh3.ggpht.com/_VP7dWGNif7M/S1lXGw3GbBI/AAAAAAAACJ0/EHvM4CUHiDo/s800/scsi_kmem_cache_demo.jpeg" /></a><br /><br />【Reference】<br />1. Linux Device Driver, third edition<br />2. Linux Kernel Source 2.6.31Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-78814036117680976312010-01-15T16:57:00.003+08:002010-01-15T19:37:52.653+08:00Linux Kernel: __init, __initdata屬性在trace Linux核心原始碼很常看到__init和__initdata兩個屬性。其原型如下:<br /><blockquote>#define __init __section(.init.text) __cold notrace<br />#define __initdata __section(.init.data)<br /></blockquote><br /><br />只要函數被定義__init屬性,代表此函數的所有內容被編譯器放置在.init.text節區。此節區代表該函數只會執行一次,此後就不會再執行,因此執行完該函數,核心會將該函數所佔記憶體空間釋放出來。__init屬性非常適合裝置驅動程式的init_module函數,其範例宣告如下:<br /><blockquote>static int __init dm_init(void); (from linux-source-2.6.31/drivers/md/dm.c)</blockquote><br /><br />利用objdump觀察dm_init函數被放置在哪個節區:<br /><blockquote>adrian@adrian-laptop:/usr/src/linux-source-2.6.31$ objdump -x drivers/md/built-in.o | grep md_init<br /><font color="#0000FF">00000000 l F .init.text 000000de md_init </font><br />00000000 l O .initcall4.init 00000004 __initcall_md_init4<br /></blockquote><br /><br />__initdata屬性跟__init屬性的作用大同小異,唯一不同在於前者放置在.init.data節區。<br /><br />範例:<br /><blockquote>static int (*_inits[])(void) __initdata = {<br />     local_init,<br />     dm_target_init,<br />     dm_linear_init,<br />     dm_stripe_init,<br />     dm_kcopyd_init,<br />     dm_interface_init,<br />};</blockquote><br />利用objdump觀察_inits變數被放置在哪個節區:<br /><blockquote> adrian@adrian-laptop:/usr/src/linux-source-2.6.31$ objdump -x drivers/md/built-in.o | grep _inits<br /><font color="#0000FF">00000000 l O .init.data 00000018 _inits </font><br /></blockquote><br /><br />問題來了,釋放記憶體的工作由核心的哪一函數負責呢? 答案是: free_initmem (arch/x86/mm/init.c)<br /><br />【Reference】<br /><a href="http://kernelnewbies.org/FAQ/InitExitMacros">FAQ/InitExitMacros</a><br />Linux Kernel Source 2.6.31Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-83898231038791286872010-01-15T11:11:00.002+08:002010-01-15T11:48:41.063+08:00An Introduction to Linux Kernel Booting Sequence Part 3<a href="http://adrianhuang.blogspot.com/2010/01/introduction-to-linux-kernel-booting.html">上篇文章</a>提到go_to_protected_mode函式最後會執行由組合語言所撰寫的函式protected_mode_jump,此函式工作如下:<br /><br /><blockquote><br /> 函式呼叫原型:<br /> protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4));<br /><br /> 其程式碼:<br /> <br /> /*<br /> * void protected_mode_jump(u32 entrypoint, u32 bootparams);<br /> */<br />GLOBAL(protected_mode_jump)<br /> movl %edx, %esi # Pointer to boot_params table<br /><br /> xorl %ebx, %ebx <br /> movw %cs, %bx<br /> shll $4, %ebx<br /> addl %ebx, 2f<br /> jmp 1f # Short jump to serialize on 386/486<br />1:<br /><br /> movw $__BOOT_DS, %cx<br /> movw $__BOOT_TSS, %di <br /><br /> <span style="font-weight:bold;"> <span style="font-style:italic;">### 設定x86之cr0暫存器的PE位元,如此便能進入proctected-mode</span><span style="font-style:italic;"></span></span><br /> movl %cr0, %edx<br /> orb $X86_CR0_PE, %dl # Protected mode<br /> movl %edx, %cr0<br /><br /> # Transition to 32-bit mode<br /> .byte 0x66, 0xea # ljmpl opcode<br />2: .long in_pm32 # offset<br /> .word __BOOT_CS # segment<br />ENDPROC(protected_mode_jump)<br /><br /> .code32<br /> .section ".text32","ax"<br />GLOBAL(in_pm32)<br /> # Set up data segments for flat 32-bit mode<br /> movl %ecx, %ds<br /> movl %ecx, %es<br /> movl %ecx, %fs<br /> movl %ecx, %gs<br /> movl %ecx, %ss<br /> # The 32-bit code sets up its own stack, but this way we do have<br /> # a valid stack if some debugging hack wants to use it.<br /> addl %ebx, %esp<br /><br /> # Set up TR to make Intel VT happy<br /> ltr %di<br /><br /> # Clear registers to allow for future extensions to the<br /> # 32-bit boot protocol<br /> xorl %ecx, %ecx<br /> xorl %edx, %edx<br /> xorl %ebx, %ebx<br /> xorl %ebp, %ebp<br /> xorl %edi, %edi<br /><br /> # Set up LDTR to make Intel VT happy<br /> lldt %cx<br /><br /> <span style="font-weight:bold;"><span style="font-style:italic;">## 跳至code32_start的標籤</span></span><br /> jmpl *%eax # Jump to the 32-bit entrypoint<br />ENDPROC(in_pm32)<br /></blockquote><br /><br />至於code32_start在哪裡呢? 此標籤定義在arch/x86/boot/header.S,如下所示:<br /><br /><blockquote><br />code32_start: # here loaders can put a different<br /> # start address for 32-bit code.<br /> .long 0x100000 # 0x100000 = default for big kernel<br /><br /></blockquote><br /><br />也就是說,核心已做完real-mode Linux程式碼該做的事情,並跳至protected-mode Linux程式碼的起始位址 (1MB),請看下圖:<br /><br /><a href="http://www.flickr.com/photos/adrianhuang/4221596496/" title="Flickr 上 adrianhuang0701 的 linux kernel ram content after loading the image into the ram"><img src="http://farm5.static.flickr.com/4021/4221596496_052643f41d.jpg" width="500" height="369" alt="linux kernel ram content after loading the image into the ram" /></a><br /><br />[Reference]<br /><a href="http://duartes.org/gustavo/blog/post/kernel-boot-process">【The Kernel Boot Process】</a><br />【Linux Kernel Source 2.6.31】Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0tag:blogger.com,1999:blog-6594360167010513475.post-36600841257741683892010-01-12T14:11:00.006+08:002010-01-13T10:46:14.815+08:00An Introduction to Linux Kernel Booting Sequence Part 2<a href="http://adrianhuang.blogspot.com/2009/12/introduction-to-linux-kernel-booting.html">上篇文章</a>Linux核心被載入記憶體並執行start_of_setup標籤(arch/x86/boot/header.S),在start_of_setup標籤末段會執行由C語言撰寫的main函式 (arch/x86/boot/main.c),此函式工作如下所示:<br /><br /><blockquote>1. 複製開機標頭 (boot header) 至 <a href="http://en.wikipedia.org/wiki/Zero_page">zero page</a><br />2. 設定heap_end變數<br />3. 偵測記憶體layout<br />4. 設定<a href="http://www.pbdr.com/ostips/keyrrate.htm">鍵盤重複率 (Keyboard Repeat Rate)</a><br />5. 查詢<a href="http://www.mjmwired.net/kernel/Documentation/mca.txt">MCA (Micro Channel Architecture)</a> 資訊<br />6. 查詢<a href="http://en.wikipedia.org/wiki/SpeedStep">Intel SpeedStep</a><br />7. 設定video模式<br />8. 進入protected-mode (go_to_protected_mode,arch/x86/boot/pm.c)<br /></blockquote><br />其中3-6項,經由BIOS呼叫 (intcall,arch/x86/boot/bioscall.S),以便向BIOS取到相關的資訊。<br />main函式最後呼叫go_to_protected_mode函式,以便從real-mode轉換至protected-mode。<br /><br />在真正進入protected-mode之前,有幾項工作必須先被執行,也就是go_to_protected_mode函式裡所做的事情,如下所示:<br /><br /><blockquote> 1. 設定<a href="http://en.wikipedia.org/wiki/A20_line">A20位址線</a>,如此便能存取超過1MB以後的資料 (因為在real-mode,只能存取1MB以下的資料)。<br /><br />2. 設定idt (interrupt descriptor table)。在real-mode,interrupt vector table的起始位址是從記憶體零的位址開始算起。然而,在protected-mode,interrupt vector table是儲存於CPU的暫存器 (IDTR,Interrupt Descriptor Table Register),因此轉換至protected-mode之前,必須先設定idt。<br /><br />3. 設定gdt (global descriptor table)。由於,real-mode與proctected-mode位址轉換 (由邏輯位址 [logical address] 轉換成線性位址 [linear address]),詳見<a href="http://en.wikipedia.org/wiki/X86_memory_segmentation">x86_memory_segmentation</a>。因此轉換至protected-mode之前,必須先設定gdt,以便能正確地根據logical address轉換成linear address。<br /></blockquote><br /><br />在go_to_protected_mode函式最後會執行由組合語言所撰寫的函式protected_mode_jump,此函式便是設定CPU為protected-mode,細節留到下回做進一步解釋。<br /><br />【Reference】 <a href="http://duartes.org/gustavo/blog/post/kernel-boot-process">The Kernel Boot Process</a>Adrian Huang (黃圳柏)http://www.blogger.com/profile/05200420228495783060noreply@blogger.com0