<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wikidot="http://www.wikidot.com/rss-namespace">

	<channel>
		<title>lk:rss</title>
		<link>http://ccckmit.wikidot.com</link>
		<description>陳鍾誠的首頁 -- 金門大學 資訊工程系</description>
				<copyright></copyright>
		<lastBuildDate>Sun, 08 Mar 2026 00:35:36 +0000</lastBuildDate>
		
					<item>
				<guid>http://ccckmit.wikidot.com/lk:int</guid>
				<title>中斷機制</title>
				<link>http://ccckmit.wikidot.com/lk:int</link>
				<description>

&lt;ol&gt;
&lt;li&gt;内核随记(一)——理解中断(1)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.chinaunix.net/uid-25940216-id-3117663.html&quot;&gt;http://blog.chinaunix.net/uid-25940216-id-3117663.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内核随记(一)——理解中断(2)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.chinaunix.net/uid-25940216-id-3117663.html&quot;&gt;http://blog.chinaunix.net/uid-25940216-id-3117663.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内核随记(一)——理解中断(3)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.chinaunix.net/uid-25940216-id-3117659.html&quot;&gt;http://blog.chinaunix.net/uid-25940216-id-3117659.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内核随记(三)&lt;span style=&quot;text-decoration: line-through;&quot;&gt;同步(1)&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;text-decoration: line-through;&quot;&gt;&lt;a href=&quot;http://blog.chinaunix.net/uid-25940216-id-3117652.html&quot;&gt;http://blog.chinaunix.net/uid-25940216-id-3117652.html&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;text-decoration: line-through;&quot;&gt;内核随记(三)&lt;/span&gt;同步(2)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/tommy_wxie/article/details/7425668&quot;&gt;http://blog.csdn.net/tommy_wxie/article/details/7425668&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内核随记(四)&amp;#8212;文件系统(1)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.chinaunix.net/uid-25940216-id-3117645.html&quot;&gt;http://blog.chinaunix.net/uid-25940216-id-3117645.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内核随记(二)——内核抢占与中断返回&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 19 Apr 2012 01:32:07 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <ol> <li>内核随记(一)——理解中断(1) <ul> <li><a href="http://blog.chinaunix.net/uid-25940216-id-3117663.html">http://blog.chinaunix.net/uid-25940216-id-3117663.html</a></li> </ul> </li> <li>内核随记(一)——理解中断(2) <ul> <li><a href="http://blog.chinaunix.net/uid-25940216-id-3117663.html">http://blog.chinaunix.net/uid-25940216-id-3117663.html</a></li> </ul> </li> <li>内核随记(一)——理解中断(3) <ul> <li><a href="http://blog.chinaunix.net/uid-25940216-id-3117659.html">http://blog.chinaunix.net/uid-25940216-id-3117659.html</a></li> </ul> </li> <li>内核随记(三)<span style="text-decoration: line-through;">同步(1)</span> <ul> <li><span style="text-decoration: line-through;"><a href="http://blog.chinaunix.net/uid-25940216-id-3117652.html">http://blog.chinaunix.net/uid-25940216-id-3117652.html</a></span></li> </ul> </li> <li><span style="text-decoration: line-through;">内核随记(三)</span>同步(2) <ul> <li><a href="http://blog.csdn.net/tommy_wxie/article/details/7425668">http://blog.csdn.net/tommy_wxie/article/details/7425668</a></li> </ul> </li> <li>内核随记(四)&#8212;文件系统(1) <ul> <li><a href="http://blog.chinaunix.net/uid-25940216-id-3117645.html">http://blog.chinaunix.net/uid-25940216-id-3117645.html</a></li> </ul> </li> <li>内核随记(二)——内核抢占与中断返回</li> </ol> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:modify</guid>
				<title>Linux 作業系統核心 -- 最新修改</title>
				<link>http://ccckmit.wikidot.com/lk:modify</link>
				<description>


&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 11 Oct 2010 06:41:36 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:elf</guid>
				<title>目的檔格式 (ELF)</title>
				<link>http://ccckmit.wikidot.com/lk:elf</link>
				<description>

&lt;p&gt;目的檔ELF 格式(Executable and Linking Format) 是 UNIX/Linux 系統中較先進的目的檔格式。這種格式是 AT&amp;amp;T 公司在設計第五代UNIX (UNIX System V) 時所發展出來的。因此，ELF格式的主要文件被放在規格書 -『System V Application Binary Interface』的第四章的 Object Files當中 ，該文件詳細的介紹了 UNIX System V 中二進位檔案格式的存放方式。並且在第五章的 Program Loading and Dynamic Linking 當中，說明了動態連結與載入的設計方法。&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 18 Mar 2010 06:39:36 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>目的檔ELF 格式(Executable and Linking Format) 是 UNIX/Linux 系統中較先進的目的檔格式。這種格式是 AT&amp;T 公司在設計第五代UNIX (UNIX System V) 時所發展出來的。因此，ELF格式的主要文件被放在規格書 -『System V Application Binary Interface』的第四章的 Object Files當中 ，該文件詳細的介紹了 UNIX System V 中二進位檔案格式的存放方式。並且在第五章的 Program Loading and Dynamic Linking 當中，說明了動態連結與載入的設計方法。</p> <p>雖然該規格書當中並沒有介紹與機器結構相關的部分，但由於各家CPU廠商都會自行撰寫與處理器有關的規格書，以補充該文件的不足之處，因此，若要查看與處理器相關的部分，可以查看各個廠商的補充文件 。</p> <p>ELF可用來記錄目的檔 (object file)、執行檔 (executable file)、動態連結檔 (share object)、與核心傾印 (core dump) 檔等格式，並且支援較先進的動態連結與載入等功能。因此，ELF 格式在 UNIX/Linux 的設計上具有相當關鍵性的地位。</p> <p>為了支援連結與執行等兩種時期的不同用途，ELF 格式可以分為兩種不同觀點，第一種是連結時期觀點 (Linking View)，第二種是執行時期觀點 (Execution View)。圖 4 顯示了這兩種不同觀點的結構圖。在連結時期，是以分段 (Section) 為主的結構，如圖 4 (a) 所示，但在執行時期，則是以分區 (Segment) 為主的結構，如圖 4 (b) 所示。其中，一個區通常是數個分段的組合體，像是與程式有關的段落，包含程式段、程式重定位等，在執行時期會被組合為一個分區。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:elf/ELF2view.jpg" alt="ELF2view.jpg" class="image" /></div> <p style="text-align: center;">圖 4. 目的檔 ELF 的兩種不同觀點</p> <p>因此，ELF 檔案有兩個不同用途的表頭，第一個是程式表頭 (Program Header Table)，這個表頭記載了分區資訊，因此也可稱為分區表頭 (Segment Header Table)。程式表頭是執行時期的主要結構。而第二個表頭是分段表頭 (Section Header Table)，記載了的分段資訊，是連結時期的主要結構。</p> <p>程式片段 2 顯示了ELF 的檔頭結構 Elf32_Ehdr，其中的 e_phoff 指向程式表頭，而 e_shoff 指向分段表頭，透過這兩個欄位，我們可以取得兩種表頭資訊。</p> <p>程式片段 2 : 目的檔 ELF 的檔頭結構 (Elf32_Ehdr)</p> <div class="code"> <pre><code>typedef struct { unsigned char e_ident[EI_NIDENT]; // ELF 辨識代號區 Elf32_Half e_type; // 檔案類型代號 Elf32_Half e_machine; // 機器平台代號 Elf32_Word e_version; // 版本資訊 Elf32_Addr e_entry; // 程式的起始位址 Elf32_Off e_phoff; // 程式表頭的位址 Elf32_Off e_shoff; // 分段表頭的位址 Elf32_Word e_flags; // 與處理器有關的旗標值 Elf32_Half e_ehsize; // ELF檔頭的長度 Elf32_Half e_phentsize; // 程式表頭的記錄長度 Elf32_Half e_phnum; // 程式表頭的記錄個數 Elf32_Half e_shentsize; // 分段表頭的記錄長度 Elf32_Half e_shnum; // 分段表頭的記錄個數 Elf32_Half e_shstrndx; // 分段字串表 .shstrtab 的分段代號 } Elf32_Ehdr;</code></pre></div> <p>在連結時期，連結器會以 ELF 的分段結構為主，利用分段表頭讀出各個分段。ELF 檔可支援任意數目的分段 (當然有上限，必須可以用16 位元整數表達)。而且，每個分段可以具有不同的結構，常見的分段有程式段 (.text) , 資料段 (.data), 唯讀資料段 (.rodata) , 未設定變數段 (.bss), 字串表 (.strtab), 符號表 (.symtab)等。但是，ELF為了支援較先進的連結載入方式，還包含了許多其他類型的段落，像是動態連結相關的區段等，表格 1 顯示了 ELF 中的常見分段名稱與其用途。</p> <p style="text-align: center;">表格 1. 目的檔ELF 中的常見分段列表</p> <table class="wiki-content-table"> <tr> <td>分段名稱</td> <td>說明</td> </tr> <tr> <td>.text</td> <td>程式段</td> </tr> <tr> <td>.data</td> <td></td> </tr> <tr> <td>.data1</td> <td>資料段</td> </tr> <tr> <td>.bss</td> <td>未設初值的全域變數</td> </tr> <tr> <td>.rodata</td> <td></td> </tr> <tr> <td>.rodata1</td> <td>唯讀資料段</td> </tr> <tr> <td>.dynamic</td> <td>動態連結資訊</td> </tr> <tr> <td>.dynstr</td> <td>動態連結用字串表</td> </tr> <tr> <td>.dynsym</td> <td>動態連結用符號表</td> </tr> <tr> <td>.got</td> <td>動態連結用的全域位移表 (Global Offset Table)</td> </tr> <tr> <td>.plt</td> <td>動態連結用的程序連結表 (Porcedure Linkage Table)</td> </tr> <tr> <td>.interp</td> <td>記錄程式解譯器的路徑 (program interpreter file)</td> </tr> <tr> <td>.ctors</td> <td>物件導向中的建構函數 (constructor) (C++可用)</td> </tr> <tr> <td>.dtors</td> <td>物件導向中的解構函數 (destructor) (C++可用)</td> </tr> <tr> <td>.hash</td> <td>雜湊表</td> </tr> <tr> <td>.init</td> <td>在主程式執行前會執行此段落</td> </tr> <tr> <td>.fini</td> <td>在主程式執行後會執行此段落</td> </tr> <tr> <td>.rel&lt;name&gt;</td> <td></td> </tr> <tr> <td>.rela&lt;name&gt;</td> <td>重定位資訊，例如： rel.text 是程式段的重定位資訊，rel.data 則是資料段的重定位資訊。</td> </tr> <tr> <td>.shstrtab</td> <td>儲存分段 (Section) 名稱</td> </tr> <tr> <td>.strtab</td> <td>字串表</td> </tr> <tr> <td>.symtab</td> <td>符號表</td> </tr> <tr> <td>.debug</td> <td>除錯資訊 (保留給未來用)</td> </tr> <tr> <td>.line</td> <td>除錯時的行號資訊</td> </tr> <tr> <td>.comment</td> <td>版本控制訊息</td> </tr> <tr> <td>.note</td> <td>附註資訊</td> </tr> </table> <p>由於 ELF 的分段眾多，我們將不詳細介紹每的段落的資料結構，只針對較重要或常見的資料結構進行說明。圖 5 顯示了ELF 檔案的分段與對應的資料結構，其中，檔頭結構是 Elf32_Ehdr、程式表頭結構是 Elf32_Phdr、分段表頭結構是 Elf32_Shdr。而在分段中，符號記錄 (Elf32_Sym) 、重定位記錄 (Elf32_Rel、Elf32_Rela)、與動態連結記錄 (Elf32_Dyn)，是較重要的結構。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:elf/ELFstructure.jpg" alt="ELFstructure.jpg" class="image" /></div> <p style="text-align: center;">圖 5. 目的檔ELF的資料結構</p> <p>分段表頭記錄了各分段 (Section) 的基本資訊，包含分段起始位址等，因此可以透過分段表頭讀取各分段，圖 6 顯示了如何透過分段表頭讀取分段的方法。程式片段 3 則顯示了分段表頭的結構定義程式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:elf/ELFsectionheader.jpg" alt="ELFsectionheader.jpg" class="image" /></div> <p style="text-align: center;">圖 6. 目的檔ELF的分段表頭</p> <p>程式片段 3 : ELF 的分段表頭記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Word sh_name; // 分段名稱代號 Elf32_Word sh_type; // 分段類型 Elf32_Word sh_flags; // 分段旗標 Elf32_Addr sh_addr; // 分段位址 (在記憶體中的位址) Elf32_Off sh_offset; // 分段位移 (在目的檔中的位址) Elf32_Word sh_size; // 分段大小 Elf32_Word sh_link; // 連結指標 (依據分段類型而定) Elf32_Word sh_info; // 分段資訊 Elf32_Word sh_addralign; // 對齊資訊 Elf32_Word sh_entsize; // 分段中的結構大小 (分段包含子結構時使用) } Elf32_Shdr;</code></pre></div> <p>程式表頭指向各個分區 (Segment) ，包含分區的起始位址，因此可以透過程式表頭取得各分區的詳細內容，<br /> 圖 7 顯示了如何透過程式表頭取得各分區的方法。 程式片段 4 則顯示了程式表頭的結構定義程式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:elf/ELFprogramHeader.jpg" alt="ELFprogramHeader.jpg" class="image" /></div> <p style="text-align: center;">圖 7. 目的檔ELF的程式表頭</p> <p>程式片段 4. 目的檔 ELF 的程式表頭結構</p> <div class="code"> <pre><code>typedef struct { Elf32_Word p_type; // 分區類型 Elf32_Off p_offset; // 分區位址 (在目的檔中) Elf32_Addr p_vaddr; // 分區的虛擬記憶體位址 Elf32_Addr p_paddr; // 分區的實體記憶體位址 Elf32_Word p_filesz; // 分區在檔案中的大小 Elf32_Word p_memsz; // 分區在記憶體中的大小 Elf32_Word p_flags; // 分區旗標 Elf32_Word p_align; // 分區的對齊資訊 } Elf32_Phdr;</code></pre></div> <p>在靜態連結的情況之下，ELF的連結器同樣會合併 .text, .data, .bss 等段落，也會利用修改記錄 Elf32_Rel 與 Elf32_Rela，進行合併後的修正動作。而且，不同類型的分段會被組合成分區，像是.text, .rodata, .hash, .dynsym, .dynstr, .plt, .rel.got 等分段會被併入到內文區 (Text Segment) 當中。而 .data, .dynamic, .got, .bss 等分段則會被併入到資料區 (Data Segemnt) 當中。</p> <p>Elf32_Sym 儲存了符號記錄，包含名稱 (st_name)、值 (st_value)、大小 (st_size)、資訊 (st_info)、其他 (st_other)、分段代號 (st_shndx) 等，其中 st_info 欄位又可細分為兩個子欄位，前四個位元是 bind 欄，用來記錄符號的屬性，後四個位元是 type欄，用來記錄符號的類型。</p> <p>程式片段 5. 目的檔ELF的符號記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Word st_name; // 符號名稱的代號 Elf32_Addr st_value; // 符號的值，通常是位址 Elf32_Word st_size; // 符號的大小，以 byte為單位 unsigned char st_info; // 細分為bind與type兩欄位 unsigned char st_other; // 目前為 0，保留未來使用 Elf32_Half st_shndx; // 符號所在的分段 (Section) 代號 } Elf32_Sym; #define ELF32_ST_BIND(i) ((i) &gt;&gt; 4) // 取出 st_info 中的bind欄位 #define ELF32_ST_TYPE(i) ((i)&amp;0xf) // 取出 st_info 中的type欄位 #define ELF32_ST_INFO(b,t) (((b)&lt;&lt;4)+((t)&amp;0xf)) // 將 bind 與 type 組成 info</code></pre></div> <p>Elf32_Rel 與 Elf32_Rela 是 ELF 檔的兩種重定位記錄，兩者均包含位址欄 (r_offset) 與資訊欄 (r_info)，其中資訊欄又可分為兩個子欄位，前面的 byte 是符號代號，後面的 byte 記錄符號類型。另外，在 Elf32_Rela 中，多了一個外加的數值欄位 (r_addend)，可用來儲存重定位的位移值。</p> <p>程式片段 6. 目的檔ELF的重定位記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Addr r_offset; // 符號的位址 Elf32_Word r_info; // r_info可分為sym與type兩欄 } Elf32_Rel; typedef struct { Elf32_Addr r_offset; // 符號的位址 Elf32_Word r_info; // r_info可分為sym與type兩欄 Elf32_Sword r_addend; // 外加的數值 } Elf32_Rela; #define ELF32_R_SYM(i) ((i)&gt;&gt;8) #define ELF32_R_TYPE(i) ((unsigned char) (i)) #define ELF32_R_INFO(s,t) (((s)&lt;&lt;8) + (unsigned char) (t))</code></pre></div> <p>重定位記錄 Elf32_rel 的 r_info 欄中的 sym 子欄位，會儲存符號表的索引值，因此，程式可以透過 sym 子欄位取得符號記錄。然後，在符號記錄 Elf32_Sym 中的 st_name 欄位，會儲存字串表中的索引值，因此，可以透過 st_name 取得符號的名稱。透過 sym 與 st_name 欄位，可將重定位表、符號表與字串表關連起來，圖 8 顯示了這三個表格的關連狀況圖。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:elf/ELFsymstringtable.jpg" alt="ELFsymstringtable.jpg" class="image" /></div> <p style="text-align: center;">圖 8. 目的檔ELF中的重定位表、符號表與字串表的關連性</p> <p>雖然分段結構主要式為了連結時使用的，但是，如果不考慮動態連結的情況，載入器也可以利用分段結構直接進行載入。只要載入 .text, .data, .data2, .bss等區段，然後利用 .rel.text, .rel.data, .rel.data2, .rela.text, .rela.data, .rela.data2 等分段進行修改的動作，就能載入 ELF目的檔了。</p> <p>但是，為了支援動態連結與載入 的技術，ELF 當中多了許多相關的分段，包含解譯段 (.interp)、動態連結段 (.dynamic)、全域位移表 (Global Offset Table : .got)、程序連結表 (Porcedure Linkage Table : .plt) 等，另外還有動態連結專用的字串表 (.dynstr) 、符號表 (.dynsym)、映射表 (.hash)、全域修改記錄 (rel.got) 等作為輔助。</p> <p>執行ELF載入動作時，使用的是以區塊為主的執行時期觀點，常見的區塊包含程式表頭 (PHDR)、解譯區塊 (INTERP)、載入區塊(LOAD)、動態區塊 (DYNAMIC)、註解區塊 (NOTE)、共用函式庫區塊 (SHLIB) 等。其中，載入區塊通常有兩個以上，如此才能容納程式區塊 (TEXT) 與資料區塊 (DATA) 等不同屬性的區域。</p> <p style="text-align: center;">表格 2 目的檔ELF 的常見區塊列表</p> <table class="wiki-content-table"> <tr> <td>Segment (區塊型態)</td> <td>Sections (分段)</td> <td>說明</td> </tr> <tr> <td>PT_PHDR</td> <td>Program Header</td> <td>表頭段，用來計算基底位址 (base address)</td> </tr> <tr> <td>PT_INTERP</td> <td>.interp</td> <td>動態載入區段。</td> </tr> <tr> <td>PT_LOAD</td> <td>.interp .note .hash .dynsym .dynstr .rel.dyn .rel.plt .init .plt .text .fini .rodata …</td> <td>載入器將此區塊載入程式段。</td> </tr> <tr> <td>PT_LOAD</td> <td>.data .dynamic .ctors .dtors .jcr .got .bss</td> <td>載入器將此區塊載入資料段。</td> </tr> <tr> <td>PT_DYNAMIC</td> <td>.dynamic</td> <td>由動態載入器處理</td> </tr> </table> <p>在 Linux 當中，一般目的檔的附檔名是 .o (Object File)，而動態連結函式庫的附檔名是 .so (Shared Object)。當程式被編譯為 .so 檔時，ELF目的檔中才會有INTERP 區塊，這個區塊中記錄了動態載入器的相關資訊，ELF載入器可透過這些資訊找到動態載入器 (ELF 文件中稱為Program Interpreter，但若稱為 Dynamic Loader 或許更恰當)。然後，當目的檔載入完成後，就可以開始執行，一但需要使用到動態函數時，才能利用動態載入器將動態函式庫載入。</p> <p>通常，載入的動作是由作業系統的核心 (Kernel) 所負責的，載入器是作業系統的一部分。例如，Linux 作業系統的核心就會負責載入 ELF 格式的檔案，ELF 檔案的載入過程大致如下所示：</p> <p>1. Kernel 將 ELF 檔案中的所有 PT_LOAD 型態的區塊載入到記憶體，這些區塊包含程式區塊與資料區塊。<br /> 2. Kernel 將載入的區塊映射到該行程的虛擬位址空間中 (例如使用 linux 的 mmap 系統呼叫)。<br /> 3. Kernel 找到 PT_INTERP 型態的區塊，並根據區塊內的資訊找到動態連結器 (Dynamic Linker ) 的ELF檔 。<br /> 4. Kernel 將動態連結器載入到記憶體，並將其映射到該行程的虛擬位址空間中，然後啟動『動態連結器』。<br /> 5. 目的程式開始執行，在呼叫動態函數時，『動態連結器』根據需要，決定出正確的連結順序，然後對該程式與動態函數進行重定位的動作，再將控制權轉移到動態函數中。</p> <p>ELF 檔案的載入過程，會因 CPU 的結構不同而有差異，因此，在 ELF 文件中這些與 CPU 有關的主題都被分離出來，由各家 CPU 廠商自行撰寫。舉例而言，動態函數的呼叫就是一個與 CPU 有關的主題，不同的 CPU實作方法會有所不同。</p> <p>程式片段 7. IA32 處理器中的靜態函數呼叫與動態函數呼叫方式</p> <div class="code"> <pre><code>C語言程式 靜態函數呼叫 動態函數呼叫 extern int var; pushl var movl var@GOT(%ebx) extern int func(int); call func pushl (%eax) call func@PLT int call_func(void) { return func(var); }</code></pre></div> <p>程式片段 8. IA32 處理器中的動態連結函數區 (Stub) 的程式</p> <div class="code"> <pre><code>.PLT0: pushl 4(%ebx) Jmp *8(%ebx) nop nop .PLT1: jmp *name1@GOT(%ebx) pushl $offset1 jmp .PLT0@PC .PLT2 jmp *name2@GOT(%ebx) pushl $offset2 jmp .PLT0@PC</code></pre></div> <p>程式片段 9 顯示了 ELF目的檔的動態連結記錄 Elf32_Dyn，這些記錄會被儲存在一個名為 _DYNAMIC[] 的陣列中，以便讓動態連結器使用。</p> <p>程式片段 9. 目的檔ELF的動態連結 (重定位) 記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Sword d_tag; // 標記 union { Elf32_Word d_val; // 值 (用途很多樣) Elf32_Addr d_ptr; // 指標 (程式的虛擬位址) } d_un; } Elf32_Dyn; extern Elf32_Dyn _DYNAMIC[]; // 動態連結陣列</code></pre></div> <p>有關 ELF 目的檔的進一步資訊，有興趣的讀者可以參考規格書 System V Application Binary Interface 中的第四章與第五章 。</p> <h1><span>說明與參考文獻</span></h1> <ol> <li>讀者可於下列網址下載到System V 的二進位應用介面規格書『System V Application Binary Interface』, <a href="http://www.caldera.com/developers/devspecs/gabi41.pdf">http://www.caldera.com/developers/devspecs/gabi41.pdf</a> ，其中的第四章 (Chapter 4) Object Files 即是 ELF 檔案格式的規格書。</li> <li>在維基百科的 ELF 主題中，列有 ELF 的詳細相關資訊，包含 System V 的規格書與各家處理器廠商的補充文件，讀者可參閱<a href="http://en.wikipedia.org/wiki/Executable_and_Linkable_Format">http://en.wikipedia.org/wiki/Executable_and_Linkable_Format</a>，其中 IA32 處理器的補充文件位於<a href="http://www.caldera.com/developers/devspecs/abi386-4.pdf">http://www.caldera.com/developers/devspecs/abi386-4.pdf</a>，而 ARM 的補充文件位於<a href="http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf">http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf</a></li> <li>有關 Linux 的動態連結技術可以參考 Ulrich Drepper , How To Write Shared Libraries, Red Hat, Inc., August 20, 2006, 其網址為<a href="http://people.redhat.com/drepper/dsohowto.pdf">http://people.redhat.com/drepper/dsohowto.pdf</a>。</li> <li>Jollen’s Blog, Program Loading 觀念介紹, last update 2007/03/13, <a href="http://www.jollen.org/EmbeddedLinux/Program_Loading.html">http://www.jollen.org/EmbeddedLinux/Program_Loading.html</a></li> <li>動態連結器的英文名稱為Dynamic Linker，但在 ELF 文件中被稱為 Program Interpreter。</li> <li>動態連結器 (Dynamic Linker ) 的ELF檔，在Linux 中通常被儲存的檔名為ld.so。</li> <li>a.out 執行文件格式 &#8212; <a href="http://www.cnscn.org/htm_data/56/0903/21669.html">http://www.cnscn.org/htm_data/56/0903/21669.html</a></li> </ol> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:aout</guid>
				<title>目的檔格式 (A.out)</title>
				<link>http://ccckmit.wikidot.com/lk:aout</link>
				<description>

&lt;h1&gt;&lt;span&gt;Linux 的目的檔&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 18 Mar 2010 06:39:09 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>Linux 的目的檔</span></h1> <p>為了效能的緣故，目的檔通常不會儲存為文字格式，而是儲存為二進位格式。像是 DOS 當中的 .com 檔案，Windows 當中的 .exe 檔案，與 Linux 早期的 a.out格式，還有近期的ELF格式，都是常見的目的檔格式。</p> <p>為了讓讀者更清楚目的檔的格式，在本節中，我們將以Linux 早期的 a.out ，與近期的 ELF 格式，作為範例，以便詳細說明目的檔的格式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:aout/a.outELFformat.jpg" alt="a.outELFformat.jpg" class="image" /></div> <p style="text-align: center;">圖 1. 兩種目的檔的格式 – a.out 與 ELF 之比較</p> <h1><span>目的檔格式 a.out</span></h1> <p>在早期的 Linux，像是 Linux 0.12 版，採用的是較簡單的目的檔格式，稱為 a.out。這是由於 UNIX與 Linux 的預設編譯執行檔名稱為 a.out 的原因。圖 1 顯示了a.out 的檔案格式 ，並且與目前 Linux 所使用的 ELF 格式進行對比。</p> <p>目的檔 a.out 的格式相當簡單，總共分為 7 的段落，包含三種主要的資料結構，也就是 1. 檔頭結構 (exec)、2. 重定位結構 (relocation_info)、3. 字串表結構 (nlist)。這三個結構的定義如下程式所示。</p> <p>程式片段 1 : 目的檔 a.out 的資料結構 (以C語言定義)</p> <div class="code"> <pre><code>struct exec { // a.out 的檔頭結構 unsigned long a_magic; // 執行檔魔數 // OMAGIC:0407, NMAGIC:0410,ZMAGIC:0413 unsigned a_text; // 程式段長度 unsigned a_data; // 資料段長度 unsigned a_bss; // 檔案中的未初始化資料區長度 unsigned a_syms; // 檔案中的符號表長度 unsigned a_entry; // 執行起始位址 unsigned a_trsize; // 程式重定位資訊長度 unsigned a_drsize; // 資料重定位資訊長度 }; struct relocation_info { // a.out 的重定位結構 int r_address; // 段內需要重定位的位址 unsigned int r_symbolnum:24; // r_extern=1時:符號的序號值, // r_extern=0時:段內需要重定位的位址 unsigned int r_pcrel:1; // PC 相關旗標 unsigned int r_length:2; // 被重定位的欄位長度 (2 的次方) // 2^0=1,2^1=2,2^2=4,2^3=8 bytes)。 unsigned i4nt r_extern:1; // 外部引用旗標。 1-以外部符號重定位 // 0-以段的地址重定位。 unsigned int r_pad:4; // 最後補滿的 4 個位元 (沒有使用到的部分)。 }; struct nlist { // a.out 的符號表結構 union { // char *n_name; // 字串指標， struct nlist *n_next; // 或者是指向另一個符號項結構的指標， long n_strx; // 或者是符號名稱在字串表中的位元組偏移值。 } n_un; // unsigned char n_type; // 符號類型N_ABS/N_TEXT/N_DATA/N_BSS等。 char n_other; // 通常不用 short n_desc; // 保留給除錯程式用 unsigned long n_value; // 含有符號的值，對於代碼、資料和BSS符號， // 通常是一個位址。 };</code></pre></div> <p>請讀者對照圖 1 與程式片段 1，很容易看出『檔頭部分』使用的是 exec 結構。『程式碼部分』與『資料部分』則直接存放二進位目的碼，不需定義特殊的資料結構。而在『程式碼重定位的部分』與『資料重定位的部分』，使用的是 relocation_info 的結構。最後，在『符號表』與『字串表』的部分，使用的是 nlist 的結構。圖 2 顯示了此種對照關係，讀者可以很容易的辨認各分段的資料結構。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:aout/a.outFormat.jpg" alt="a.outFormat.jpg" class="image" /></div> <p style="text-align: center;">圖 2. 目的檔 a.out 各區段所對應的資料結構</p> <p>當 a.out 檔案被載入時，載入器首先會讀取檔頭部分，接著根據檔頭保留適當的大小的記憶體空間 (包含程式段、資料段、BSS段、堆疊段、堆積段等)。然後，載入器會讀取程式段與資料段的目的碼，放入對應的記憶體中。接著，利用重定位資訊修改對應的記憶體資料。最後，才把程式計數器設定為 exec.a_entry所對應的位址，開始執行該程式，圖 3 顯示了 a.out 檔被載入的過程。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:aout/a.outLoad.jpg" alt="a.outLoad.jpg" class="image" /></div> <p style="text-align: center;">圖 3. 目的檔 a.out 的載入過程</p> <p>目的檔格式a.out 是一種比較簡單而直接的格式，但其缺點是檔案格式太過固定，因此無法支援較為進階的功能，像是動態連結與載入等。目前，UNIX/Linux 普遍都已改用 ELF 格式作為標準的目的檔格式，在 Linux 2.6 當中就支援了動態載入的功能。在下一節當中，我們將介紹較為先進的目的檔格式 - ELF。</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:objfile</guid>
				<title>Linux 的目的檔格式 - a.out 與 ELF 之設計原理</title>
				<link>http://ccckmit.wikidot.com/lk:objfile</link>
				<description>

&lt;h1&gt;&lt;span&gt;Linux 的目的檔&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 12 Jan 2010 09:15:37 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>Linux 的目的檔</span></h1> <p>為了效能的緣故，目的檔通常不會儲存為文字格式，而是儲存為二進位格式。像是 DOS 當中的 .com 檔案，Windows 當中的 .exe 檔案，與 Linux 早期的 a.out格式，還有近期的ELF格式，都是常見的目的檔格式。</p> <p>為了讓讀者更清楚目的檔的格式，在本節中，我們將以Linux 早期的 a.out ，與近期的 ELF 格式，作為範例，以便詳細說明目的檔的格式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/a.outELFformat.jpg" alt="a.outELFformat.jpg" class="image" /></div> <p style="text-align: center;">圖 1. 兩種目的檔的格式 – a.out 與 ELF 之比較</p> <h1><span>目的檔格式 a.out</span></h1> <p>在早期的 Linux，像是 Linux 0.12 版，採用的是較簡單的目的檔格式，稱為 a.out。這是由於 UNIX與 Linux 的預設編譯執行檔名稱為 a.out 的原因。圖 1 顯示了a.out 的檔案格式 ，並且與目前 Linux 所使用的 ELF 格式進行對比。</p> <p>目的檔 a.out 的格式相當簡單，總共分為 7 的段落，包含三種主要的資料結構，也就是 1. 檔頭結構 (exec)、2. 重定位結構 (relocation_info)、3. 字串表結構 (nlist)。這三個結構的定義如下程式所示。</p> <p>程式片段 1 : 目的檔 a.out 的資料結構 (以C語言定義)</p> <div class="code"> <pre><code>struct exec { // a.out 的檔頭結構 unsigned long a_magic; // 執行檔魔數 // OMAGIC:0407, NMAGIC:0410,ZMAGIC:0413 unsigned a_text; // 程式段長度 unsigned a_data; // 資料段長度 unsigned a_bss; // 檔案中的未初始化資料區長度 unsigned a_syms; // 檔案中的符號表長度 unsigned a_entry; // 執行起始位址 unsigned a_trsize; // 程式重定位資訊長度 unsigned a_drsize; // 資料重定位資訊長度 }; struct relocation_info { // a.out 的重定位結構 int r_address; // 段內需要重定位的位址 unsigned int r_symbolnum:24; // r_extern=1時:符號的序號值, // r_extern=0時:段內需要重定位的位址 unsigned int r_pcrel:1; // PC 相關旗標 unsigned int r_length:2; // 被重定位的欄位長度 (2 的次方) // 2^0=1,2^1=2,2^2=4,2^3=8 bytes)。 unsigned i4nt r_extern:1; // 外部引用旗標。 1-以外部符號重定位 // 0-以段的地址重定位。 unsigned int r_pad:4; // 最後補滿的 4 個位元 (沒有使用到的部分)。 }; struct nlist { // a.out 的符號表結構 union { // char *n_name; // 字串指標， struct nlist *n_next; // 或者是指向另一個符號項結構的指標， long n_strx; // 或者是符號名稱在字串表中的位元組偏移值。 } n_un; // unsigned char n_type; // 符號類型N_ABS/N_TEXT/N_DATA/N_BSS等。 char n_other; // 通常不用 short n_desc; // 保留給除錯程式用 unsigned long n_value; // 含有符號的值，對於代碼、資料和BSS符號， // 通常是一個位址。 };</code></pre></div> <p>請讀者對照圖 1 與程式片段 1，很容易看出『檔頭部分』使用的是 exec 結構。『程式碼部分』與『資料部分』則直接存放二進位目的碼，不需定義特殊的資料結構。而在『程式碼重定位的部分』與『資料重定位的部分』，使用的是 relocation_info 的結構。最後，在『符號表』與『字串表』的部分，使用的是 nlist 的結構。圖 2 顯示了此種對照關係，讀者可以很容易的辨認各分段的資料結構。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/a.outFormat.jpg" alt="a.outFormat.jpg" class="image" /></div> <p style="text-align: center;">圖 2. 目的檔 a.out 各區段所對應的資料結構</p> <p>當 a.out 檔案被載入時，載入器首先會讀取檔頭部分，接著根據檔頭保留適當的大小的記憶體空間 (包含程式段、資料段、BSS段、堆疊段、堆積段等)。然後，載入器會讀取程式段與資料段的目的碼，放入對應的記憶體中。接著，利用重定位資訊修改對應的記憶體資料。最後，才把程式計數器設定為 exec.a_entry所對應的位址，開始執行該程式，圖 3 顯示了 a.out 檔被載入的過程。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/a.outLoad.jpg" alt="a.outLoad.jpg" class="image" /></div> <p style="text-align: center;">圖 3. 目的檔 a.out 的載入過程</p> <p>目的檔格式a.out 是一種比較簡單而直接的格式，但其缺點是檔案格式太過固定，因此無法支援較為進階的功能，像是動態連結與載入等。目前，UNIX/Linux 普遍都已改用 ELF 格式作為標準的目的檔格式，在 Linux 2.6 當中就支援了動態載入的功能。在下一節當中，我們將介紹較為先進的目的檔格式 - ELF。</p> <h1><span>目的檔格式 – ELF</span></h1> <p>目的檔ELF 格式(Executable and Linking Format) 是 UNIX/Linux 系統中較先進的目的檔格式。這種格式是 AT&amp;T 公司在設計第五代UNIX (UNIX System V) 時所發展出來的。因此，ELF格式的主要文件被放在規格書 -『System V Application Binary Interface』的第四章的 Object Files當中 ，該文件詳細的介紹了 UNIX System V 中二進位檔案格式的存放方式。並且在第五章的 Program Loading and Dynamic Linking 當中，說明了動態連結與載入的設計方法。</p> <p>雖然該規格書當中並沒有介紹與機器結構相關的部分，但由於各家CPU廠商都會自行撰寫與處理器有關的規格書，以補充該文件的不足之處，因此，若要查看與處理器相關的部分，可以查看各個廠商的補充文件 。</p> <p>ELF可用來記錄目的檔 (object file)、執行檔 (executable file)、動態連結檔 (share object)、與核心傾印 (core dump) 檔等格式，並且支援較先進的動態連結與載入等功能。因此，ELF 格式在 UNIX/Linux 的設計上具有相當關鍵性的地位。</p> <p>為了支援連結與執行等兩種時期的不同用途，ELF 格式可以分為兩種不同觀點，第一種是連結時期觀點 (Linking View)，第二種是執行時期觀點 (Execution View)。圖 4 顯示了這兩種不同觀點的結構圖。在連結時期，是以分段 (Section) 為主的結構，如圖 4 (a) 所示，但在執行時期，則是以分區 (Segment) 為主的結構，如圖 4 (b) 所示。其中，一個區通常是數個分段的組合體，像是與程式有關的段落，包含程式段、程式重定位等，在執行時期會被組合為一個分區。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/ELF2view.jpg" alt="ELF2view.jpg" class="image" /></div> <p style="text-align: center;">圖 4. 目的檔 ELF 的兩種不同觀點</p> <p>因此，ELF 檔案有兩個不同用途的表頭，第一個是程式表頭 (Program Header Table)，這個表頭記載了分區資訊，因此也可稱為分區表頭 (Segment Header Table)。程式表頭是執行時期的主要結構。而第二個表頭是分段表頭 (Section Header Table)，記載了的分段資訊，是連結時期的主要結構。</p> <p>程式片段 2 顯示了ELF 的檔頭結構 Elf32_Ehdr，其中的 e_phoff 指向程式表頭，而 e_shoff 指向分段表頭，透過這兩個欄位，我們可以取得兩種表頭資訊。</p> <p>程式片段 2 : 目的檔 ELF 的檔頭結構 (Elf32_Ehdr)</p> <div class="code"> <pre><code>typedef struct { unsigned char e_ident[EI_NIDENT]; // ELF 辨識代號區 Elf32_Half e_type; // 檔案類型代號 Elf32_Half e_machine; // 機器平台代號 Elf32_Word e_version; // 版本資訊 Elf32_Addr e_entry; // 程式的起始位址 Elf32_Off e_phoff; // 程式表頭的位址 Elf32_Off e_shoff; // 分段表頭的位址 Elf32_Word e_flags; // 與處理器有關的旗標值 Elf32_Half e_ehsize; // ELF檔頭的長度 Elf32_Half e_phentsize; // 程式表頭的記錄長度 Elf32_Half e_phnum; // 程式表頭的記錄個數 Elf32_Half e_shentsize; // 分段表頭的記錄長度 Elf32_Half e_shnum; // 分段表頭的記錄個數 Elf32_Half e_shstrndx; // 分段字串表 .shstrtab 的分段代號 } Elf32_Ehdr;</code></pre></div> <p>在連結時期，連結器會以 ELF 的分段結構為主，利用分段表頭讀出各個分段。ELF 檔可支援任意數目的分段 (當然有上限，必須可以用16 位元整數表達)。而且，每個分段可以具有不同的結構，常見的分段有程式段 (.text) , 資料段 (.data), 唯讀資料段 (.rodata) , 未設定變數段 (.bss), 字串表 (.strtab), 符號表 (.symtab)等。但是，ELF為了支援較先進的連結載入方式，還包含了許多其他類型的段落，像是動態連結相關的區段等，表格 1 顯示了 ELF 中的常見分段名稱與其用途。</p> <p style="text-align: center;">表格 1. 目的檔ELF 中的常見分段列表</p> <table class="wiki-content-table"> <tr> <td>分段名稱</td> <td>說明</td> </tr> <tr> <td>.text</td> <td>程式段</td> </tr> <tr> <td>.data</td> <td></td> </tr> <tr> <td>.data1</td> <td>資料段</td> </tr> <tr> <td>.bss</td> <td>未設初值的全域變數</td> </tr> <tr> <td>.rodata</td> <td></td> </tr> <tr> <td>.rodata1</td> <td>唯讀資料段</td> </tr> <tr> <td>.dynamic</td> <td>動態連結資訊</td> </tr> <tr> <td>.dynstr</td> <td>動態連結用字串表</td> </tr> <tr> <td>.dynsym</td> <td>動態連結用符號表</td> </tr> <tr> <td>.got</td> <td>動態連結用的全域位移表 (Global Offset Table)</td> </tr> <tr> <td>.plt</td> <td>動態連結用的程序連結表 (Porcedure Linkage Table)</td> </tr> <tr> <td>.interp</td> <td>記錄程式解譯器的路徑 (program interpreter file)</td> </tr> <tr> <td>.ctors</td> <td>物件導向中的建構函數 (constructor) (C++可用)</td> </tr> <tr> <td>.dtors</td> <td>物件導向中的解構函數 (destructor) (C++可用)</td> </tr> <tr> <td>.hash</td> <td>雜湊表</td> </tr> <tr> <td>.init</td> <td>在主程式執行前會執行此段落</td> </tr> <tr> <td>.fini</td> <td>在主程式執行後會執行此段落</td> </tr> <tr> <td>.rel&lt;name&gt;</td> <td></td> </tr> <tr> <td>.rela&lt;name&gt;</td> <td>重定位資訊，例如： rel.text 是程式段的重定位資訊，rel.data 則是資料段的重定位資訊。</td> </tr> <tr> <td>.shstrtab</td> <td>儲存分段 (Section) 名稱</td> </tr> <tr> <td>.strtab</td> <td>字串表</td> </tr> <tr> <td>.symtab</td> <td>符號表</td> </tr> <tr> <td>.debug</td> <td>除錯資訊 (保留給未來用)</td> </tr> <tr> <td>.line</td> <td>除錯時的行號資訊</td> </tr> <tr> <td>.comment</td> <td>版本控制訊息</td> </tr> <tr> <td>.note</td> <td>附註資訊</td> </tr> </table> <p>由於 ELF 的分段眾多，我們將不詳細介紹每的段落的資料結構，只針對較重要或常見的資料結構進行說明。圖 5 顯示了ELF 檔案的分段與對應的資料結構，其中，檔頭結構是 Elf32_Ehdr、程式表頭結構是 Elf32_Phdr、分段表頭結構是 Elf32_Shdr。而在分段中，符號記錄 (Elf32_Sym) 、重定位記錄 (Elf32_Rel、Elf32_Rela)、與動態連結記錄 (Elf32_Dyn)，是較重要的結構。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/ELFstructure.jpg" alt="ELFstructure.jpg" class="image" /></div> <p style="text-align: center;">圖 5. 目的檔ELF的資料結構</p> <p>分段表頭記錄了各分段 (Section) 的基本資訊，包含分段起始位址等，因此可以透過分段表頭讀取各分段，圖 6 顯示了如何透過分段表頭讀取分段的方法。程式片段 3 則顯示了分段表頭的結構定義程式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/ELFsectionheader.jpg" alt="ELFsectionheader.jpg" class="image" /></div> <p style="text-align: center;">圖 6. 目的檔ELF的分段表頭</p> <p>程式片段 3 : ELF 的分段表頭記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Word sh_name; // 分段名稱代號 Elf32_Word sh_type; // 分段類型 Elf32_Word sh_flags; // 分段旗標 Elf32_Addr sh_addr; // 分段位址 (在記憶體中的位址) Elf32_Off sh_offset; // 分段位移 (在目的檔中的位址) Elf32_Word sh_size; // 分段大小 Elf32_Word sh_link; // 連結指標 (依據分段類型而定) Elf32_Word sh_info; // 分段資訊 Elf32_Word sh_addralign; // 對齊資訊 Elf32_Word sh_entsize; // 分段中的結構大小 (分段包含子結構時使用) } Elf32_Shdr;</code></pre></div> <p>程式表頭指向各個分區 (Segment) ，包含分區的起始位址，因此可以透過程式表頭取得各分區的詳細內容，<br /> 圖 7 顯示了如何透過程式表頭取得各分區的方法。 程式片段 4 則顯示了程式表頭的結構定義程式。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/ELFprogramHeader.jpg" alt="ELFprogramHeader.jpg" class="image" /></div> <p style="text-align: center;">圖 7. 目的檔ELF的程式表頭</p> <p>程式片段 4. 目的檔 ELF 的程式表頭結構</p> <div class="code"> <pre><code>typedef struct { Elf32_Word p_type; // 分區類型 Elf32_Off p_offset; // 分區位址 (在目的檔中) Elf32_Addr p_vaddr; // 分區的虛擬記憶體位址 Elf32_Addr p_paddr; // 分區的實體記憶體位址 Elf32_Word p_filesz; // 分區在檔案中的大小 Elf32_Word p_memsz; // 分區在記憶體中的大小 Elf32_Word p_flags; // 分區旗標 Elf32_Word p_align; // 分區的對齊資訊 } Elf32_Phdr;</code></pre></div> <p>在靜態連結的情況之下，ELF的連結器同樣會合併 .text, .data, .bss 等段落，也會利用修改記錄 Elf32_Rel 與 Elf32_Rela，進行合併後的修正動作。而且，不同類型的分段會被組合成分區，像是.text, .rodata, .hash, .dynsym, .dynstr, .plt, .rel.got 等分段會被併入到內文區 (Text Segment) 當中。而 .data, .dynamic, .got, .bss 等分段則會被併入到資料區 (Data Segemnt) 當中。</p> <p>Elf32_Sym 儲存了符號記錄，包含名稱 (st_name)、值 (st_value)、大小 (st_size)、資訊 (st_info)、其他 (st_other)、分段代號 (st_shndx) 等，其中 st_info 欄位又可細分為兩個子欄位，前四個位元是 bind 欄，用來記錄符號的屬性，後四個位元是 type欄，用來記錄符號的類型。</p> <p>程式片段 5. 目的檔ELF的符號記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Word st_name; // 符號名稱的代號 Elf32_Addr st_value; // 符號的值，通常是位址 Elf32_Word st_size; // 符號的大小，以 byte為單位 unsigned char st_info; // 細分為bind與type兩欄位 unsigned char st_other; // 目前為 0，保留未來使用 Elf32_Half st_shndx; // 符號所在的分段 (Section) 代號 } Elf32_Sym; #define ELF32_ST_BIND(i) ((i) &gt;&gt; 4) // 取出 st_info 中的bind欄位 #define ELF32_ST_TYPE(i) ((i)&amp;0xf) // 取出 st_info 中的type欄位 #define ELF32_ST_INFO(b,t) (((b)&lt;&lt;4)+((t)&amp;0xf)) // 將 bind 與 type 組成 info</code></pre></div> <p>Elf32_Rel 與 Elf32_Rela 是 ELF 檔的兩種重定位記錄，兩者均包含位址欄 (r_offset) 與資訊欄 (r_info)，其中資訊欄又可分為兩個子欄位，前面的 byte 是符號代號，後面的 byte 記錄符號類型。另外，在 Elf32_Rela 中，多了一個外加的數值欄位 (r_addend)，可用來儲存重定位的位移值。</p> <p>程式片段 6. 目的檔ELF的重定位記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Addr r_offset; // 符號的位址 Elf32_Word r_info; // r_info可分為sym與type兩欄 } Elf32_Rel; typedef struct { Elf32_Addr r_offset; // 符號的位址 Elf32_Word r_info; // r_info可分為sym與type兩欄 Elf32_Sword r_addend; // 外加的數值 } Elf32_Rela; #define ELF32_R_SYM(i) ((i)&gt;&gt;8) #define ELF32_R_TYPE(i) ((unsigned char) (i)) #define ELF32_R_INFO(s,t) (((s)&lt;&lt;8) + (unsigned char) (t))</code></pre></div> <p>重定位記錄 Elf32_rel 的 r_info 欄中的 sym 子欄位，會儲存符號表的索引值，因此，程式可以透過 sym 子欄位取得符號記錄。然後，在符號記錄 Elf32_Sym 中的 st_name 欄位，會儲存字串表中的索引值，因此，可以透過 st_name 取得符號的名稱。透過 sym 與 st_name 欄位，可將重定位表、符號表與字串表關連起來，圖 8 顯示了這三個表格的關連狀況圖。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:objfile/ELFsymstringtable.jpg" alt="ELFsymstringtable.jpg" class="image" /></div> <p style="text-align: center;">圖 8. 目的檔ELF中的重定位表、符號表與字串表的關連性</p> <p>雖然分段結構主要式為了連結時使用的，但是，如果不考慮動態連結的情況，載入器也可以利用分段結構直接進行載入。只要載入 .text, .data, .data2, .bss等區段，然後利用 .rel.text, .rel.data, .rel.data2, .rela.text, .rela.data, .rela.data2 等分段進行修改的動作，就能載入 ELF目的檔了。</p> <p>但是，為了支援動態連結與載入 的技術，ELF 當中多了許多相關的分段，包含解譯段 (.interp)、動態連結段 (.dynamic)、全域位移表 (Global Offset Table : .got)、程序連結表 (Porcedure Linkage Table : .plt) 等，另外還有動態連結專用的字串表 (.dynstr) 、符號表 (.dynsym)、映射表 (.hash)、全域修改記錄 (rel.got) 等作為輔助。</p> <p>執行ELF載入動作時，使用的是以區塊為主的執行時期觀點，常見的區塊包含程式表頭 (PHDR)、解譯區塊 (INTERP)、載入區塊(LOAD)、動態區塊 (DYNAMIC)、註解區塊 (NOTE)、共用函式庫區塊 (SHLIB) 等。其中，載入區塊通常有兩個以上，如此才能容納程式區塊 (TEXT) 與資料區塊 (DATA) 等不同屬性的區域。</p> <p style="text-align: center;">表格 2 目的檔ELF 的常見區塊列表</p> <table class="wiki-content-table"> <tr> <td>Segment (區塊型態)</td> <td>Sections (分段)</td> <td>說明</td> </tr> <tr> <td>PT_PHDR</td> <td>Program Header</td> <td>表頭段，用來計算基底位址 (base address)</td> </tr> <tr> <td>PT_INTERP</td> <td>.interp</td> <td>動態載入區段。</td> </tr> <tr> <td>PT_LOAD</td> <td>.interp .note .hash .dynsym .dynstr .rel.dyn .rel.plt .init .plt .text .fini .rodata …</td> <td>載入器將此區塊載入程式段。</td> </tr> <tr> <td>PT_LOAD</td> <td>.data .dynamic .ctors .dtors .jcr .got .bss</td> <td>載入器將此區塊載入資料段。</td> </tr> <tr> <td>PT_DYNAMIC</td> <td>.dynamic</td> <td>由動態載入器處理</td> </tr> </table> <p>在 Linux 當中，一般目的檔的附檔名是 .o (Object File)，而動態連結函式庫的附檔名是 .so (Shared Object)。當程式被編譯為 .so 檔時，ELF目的檔中才會有INTERP 區塊，這個區塊中記錄了動態載入器的相關資訊，ELF載入器可透過這些資訊找到動態載入器 (ELF 文件中稱為Program Interpreter，但若稱為 Dynamic Loader 或許更恰當)。然後，當目的檔載入完成後，就可以開始執行，一但需要使用到動態函數時，才能利用動態載入器將動態函式庫載入。</p> <p>通常，載入的動作是由作業系統的核心 (Kernel) 所負責的，載入器是作業系統的一部分。例如，Linux 作業系統的核心就會負責載入 ELF 格式的檔案，ELF 檔案的載入過程大致如下所示：</p> <p>1. Kernel 將 ELF 檔案中的所有 PT_LOAD 型態的區塊載入到記憶體，這些區塊包含程式區塊與資料區塊。<br /> 2. Kernel 將載入的區塊映射到該行程的虛擬位址空間中 (例如使用 linux 的 mmap 系統呼叫)。<br /> 3. Kernel 找到 PT_INTERP 型態的區塊，並根據區塊內的資訊找到動態連結器 (Dynamic Linker ) 的ELF檔 。<br /> 4. Kernel 將動態連結器載入到記憶體，並將其映射到該行程的虛擬位址空間中，然後啟動『動態連結器』。<br /> 5. 目的程式開始執行，在呼叫動態函數時，『動態連結器』根據需要，決定出正確的連結順序，然後對該程式與動態函數進行重定位的動作，再將控制權轉移到動態函數中。</p> <p>ELF 檔案的載入過程，會因 CPU 的結構不同而有差異，因此，在 ELF 文件中這些與 CPU 有關的主題都被分離出來，由各家 CPU 廠商自行撰寫。舉例而言，動態函數的呼叫就是一個與 CPU 有關的主題，不同的 CPU實作方法會有所不同。</p> <p>程式片段 7. IA32 處理器中的靜態函數呼叫與動態函數呼叫方式</p> <div class="code"> <pre><code>C語言程式 靜態函數呼叫 動態函數呼叫 extern int var; pushl var movl var@GOT(%ebx) extern int func(int); call func pushl (%eax) call func@PLT int call_func(void) { return func(var); }</code></pre></div> <p>程式片段 8. IA32 處理器中的動態連結函數區 (Stub) 的程式</p> <div class="code"> <pre><code>.PLT0: pushl 4(%ebx) Jmp *8(%ebx) nop nop .PLT1: jmp *name1@GOT(%ebx) pushl $offset1 jmp .PLT0@PC .PLT2 jmp *name2@GOT(%ebx) pushl $offset2 jmp .PLT0@PC</code></pre></div> <p>程式片段 9 顯示了 ELF目的檔的動態連結記錄 Elf32_Dyn，這些記錄會被儲存在一個名為 _DYNAMIC[] 的陣列中，以便讓動態連結器使用。</p> <p>程式片段 9. 目的檔ELF的動態連結 (重定位) 記錄</p> <div class="code"> <pre><code>typedef struct { Elf32_Sword d_tag; // 標記 union { Elf32_Word d_val; // 值 (用途很多樣) Elf32_Addr d_ptr; // 指標 (程式的虛擬位址) } d_un; } Elf32_Dyn; extern Elf32_Dyn _DYNAMIC[]; // 動態連結陣列</code></pre></div> <p>有關 ELF 目的檔的進一步資訊，有興趣的讀者可以參考規格書 System V Application Binary Interface 中的第四章與第五章 。</p> <h1><span>說明與參考文獻</span></h1> <ol> <li>讀者可於下列網址下載到System V 的二進位應用介面規格書『System V Application Binary Interface』, <a href="http://www.caldera.com/developers/devspecs/gabi41.pdf">http://www.caldera.com/developers/devspecs/gabi41.pdf</a> ，其中的第四章 (Chapter 4) Object Files 即是 ELF 檔案格式的規格書。</li> <li>在維基百科的 ELF 主題中，列有 ELF 的詳細相關資訊，包含 System V 的規格書與各家處理器廠商的補充文件，讀者可參閱<a href="http://en.wikipedia.org/wiki/Executable_and_Linkable_Format">http://en.wikipedia.org/wiki/Executable_and_Linkable_Format</a>，其中 IA32 處理器的補充文件位於<a href="http://www.caldera.com/developers/devspecs/abi386-4.pdf">http://www.caldera.com/developers/devspecs/abi386-4.pdf</a>，而 ARM 的補充文件位於<a href="http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf">http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf</a></li> <li>有關 Linux 的動態連結技術可以參考 Ulrich Drepper , How To Write Shared Libraries, Red Hat, Inc., August 20, 2006, 其網址為<a href="http://people.redhat.com/drepper/dsohowto.pdf">http://people.redhat.com/drepper/dsohowto.pdf</a>。</li> <li>Jollen’s Blog, Program Loading 觀念介紹, last update 2007/03/13, <a href="http://www.jollen.org/EmbeddedLinux/Program_Loading.html">http://www.jollen.org/EmbeddedLinux/Program_Loading.html</a></li> <li>動態連結器的英文名稱為Dynamic Linker，但在 ELF 文件中被稱為 Program Interpreter。</li> <li>動態連結器 (Dynamic Linker ) 的ELF檔，在Linux 中通常被儲存的檔名為ld.so。</li> <li>a.out 執行文件格式 &#8212; <a href="http://www.cnscn.org/htm_data/56/0903/21669.html">http://www.cnscn.org/htm_data/56/0903/21669.html</a></li> </ol> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:main</guid>
				<title>免費電子書 -- Linux 作業系統核心</title>
				<link>http://ccckmit.wikidot.com/lk:main</link>
				<description>

&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 11 Oct 2010 06:36:08 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:file</guid>
				<title>Linux 的檔案系統</title>
				<link>http://ccckmit.wikidot.com/lk:file</link>
				<description>

&lt;h1&gt;&lt;span&gt;Linux 的檔案系統簡介&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;在 UNIX/Linux 的使用者的腦海中，檔案系統是一種邏輯概念，而非實體的裝置。這種邏輯概念包含『檔案』、『目錄』、『路徑』、『檔案屬性』等等。我們可以用物件導向的方式將這些邏輯概念視為物件，表格 1 就顯示了這些物件的範例與意義。&lt;/p&gt;
表格 1. 檔案系統中的基本邏輯概念&lt;br /&gt;
&lt;table class=&quot;wiki-content-table&quot;&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;範例&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;路徑&lt;/td&gt;
&lt;td&gt;/home/ccc/hello.txt&lt;/td&gt;
&lt;td&gt;檔案在目錄結構中的位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目錄&lt;/td&gt;
&lt;td&gt;/home/ccc/&lt;/td&gt;
&lt;td&gt;資料夾中所容納的項目索引 (包含子目錄或檔案之屬性與連結)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;檔案&lt;/td&gt;
&lt;td&gt;Hello!\n…&lt;/td&gt;
&lt;td&gt;檔案的內容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;屬性&lt;/td&gt;
&lt;td&gt;-rwxr-xr&amp;#8212; &amp;#8230;ccc None 61 Jun 25&amp;#160;12:17 README.txt&lt;/td&gt;
&lt;td&gt;檔案的名稱、權限、擁有者、修改日期等資訊&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Linux 檔案系統的第一層目錄如表格 2 所示，認識這些目錄才能有效的運用 Linux 的檔案系統，通常使用者從命令列登入後會到達個人用戶的主目錄，像是使用者 ccc 登入後就會到 /home/ccc 目錄當中。目錄 /dev 所代表的是裝置 (device)，其中每一個子目錄通常代表一個裝置，這些裝置可以被掛載到 /mount 資料夾下，形成一顆邏輯目錄樹。&lt;/p&gt;
表格 2. Linux 檔案系統的第一層目錄&lt;br /&gt;
&lt;table class=&quot;wiki-content-table&quot;&gt;
&lt;tr&gt;
&lt;th&gt;目錄&lt;/th&gt;
&lt;th&gt;全名&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/bin&lt;/td&gt;
&lt;td&gt;Binary&lt;/td&gt;
&lt;td&gt;存放二進位的可執行檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/dev&lt;/td&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;代表設備，存放裝置相關檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/etc&lt;/td&gt;
&lt;td&gt;Etc…&lt;/td&gt;
&lt;td&gt;存放系統管理與配置檔案，像是服務程式 httpd 與 host.conf 等檔案。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/home&lt;/td&gt;
&lt;td&gt;Home&lt;/td&gt;
&lt;td&gt;用戶的主目錄，每個使用者在其中都會有一個子資料夾，例如用戶 ccc 的資料夾為 /home/ccc/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/lib&lt;/td&gt;
&lt;td&gt;Library&lt;/td&gt;
&lt;td&gt;包含系統函式庫與動態連結函式庫&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/sbin&lt;/td&gt;
&lt;td&gt;System binary&lt;/td&gt;
&lt;td&gt;系統管理程式，通常由系統管理員使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/tmp&lt;/td&gt;
&lt;td&gt;Temp&lt;/td&gt;
&lt;td&gt;暫存檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/root&lt;/td&gt;
&lt;td&gt;Root directory&lt;/td&gt;
&lt;td&gt;系統的根目錄，通常由系統管理員使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/mnt&lt;/td&gt;
&lt;td&gt;Mount&lt;/td&gt;
&lt;td&gt;用戶所掛載上去的檔案系統，通常放在此目錄下&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/proc&lt;/td&gt;
&lt;td&gt;Process&lt;/td&gt;
&lt;td&gt;一個虛擬的目錄，代表整個記憶體空間的映射區，可以透過存取此目錄取得系統資訊。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/var&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;存放各種服務的日誌等檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/usr&lt;/td&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;龐大的目錄，所有的使用者程式與檔案都放在底下，像是 /usr/src 中就存放了 Linux 核心的原始碼， 而 /usr/bin 則存放所有的開發工具環境，像是 javac, java, gcc, perl 等。(若類比到 MS. Windows，此資料夾就像是 C:\Program Files)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;然而，檔案畢竟是儲存在區塊裝置中的資料，要如何將這些概念化為區塊資料的組合，必須依賴某些資料結構。為了能將目錄、檔案、屬性、路徑這些物件儲存在區塊當中。這些區塊必須被進一步組織成更巨大的單元，這種巨型單元稱為分割 (Partition)。&lt;/p&gt;
&lt;p&gt;在 MS. Windows 中，分割是以 A: B: C: D: 這樣的概念形式呈現的。一個分割概念再 Windows 中通常稱為『槽』。由於歷史的因素，通常 A: B: 槽代表軟碟機，而 C:槽代表第一顆硬碟，D: E: …. 槽則可能是光碟、硬碟、或隨身碟等等。&lt;/p&gt;
&lt;p&gt;但是並非一個槽就代表單一的裝置，有時一個裝置會包含好幾個分割，像是許多人都會將主硬碟進一步分割成兩個 Partition，形成 C: D: 兩個槽，但實際上卻儲存在同一個硬碟裝置中，Linux 中的 Partition 的概念也與 Windows 類似，但是命名方式卻有所不同。&lt;/p&gt;
&lt;p&gt;在Linux 中並沒有槽的概念，而是直接將裝置映射到 /dev 資料夾的某個檔案路徑中。舉例而言，第一顆硬碟的第一個分割通常稱為 /dev/hda1，第二個分割則為 /dev/hda2, …。而第二顆硬碟的第一個分割區則為 /dev/hdb1，…。軟碟則被映射到 /dev/sda1, /dev/sda2, …./dev/sdb1, ….，以此類推。&lt;/p&gt;
&lt;p&gt;在 Linux 中，我們可以利用 mount 這個指令，將某個分割 (槽) 掛載到檔案系統的某個節點中，這樣就不需要知道某個資料夾 (像是 /mnt) 到底是何種檔案系統，整個檔案系統形成一顆與硬體無關的樹狀結構。舉例而言，mount -t ext2 /dev/hda3 /mnt 這個指令可以將Ext2格式的硬碟分割區 /dev/hda3 掛載到 /mnt 目錄下。而 mount -t iso9600 -o ro /dev/cdrom /mnt/cdrom 這樣的指令則可將 iso9600 格式的光碟 /dev/cdrom 以唯讀的方式掛載到 /mnt/cdrom路徑當中。當然，我們也可以用 unmount 指令將這些掛載上去的裝置移除。&lt;/p&gt;
&lt;p&gt;當我們使用 ls -all 這樣的指令以列出資料夾中的目錄結構時，看到的就是這些概念所反映出的資訊。圖 1 就顯示了 ls 指令所呈現出的資訊與所對應的概念 。&lt;/p&gt;
&lt;div class=&quot;image-container aligncenter&quot;&gt;&lt;img src=&quot;http://ccckmit.wdfiles.com/local--files/lk:file/LinuxFileDirectory.jpg&quot; alt=&quot;LinuxFileDirectory.jpg&quot; class=&quot;image&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;圖 1 UNIX/Linux 中的檔案與目錄概念&lt;/p&gt;
&lt;p&gt;圖 1 中的權限欄位，第一個字元代表項目標記，目錄會以d標記，而檔案會以 - 標記，後面九個字元分為三群，分別是檔案擁有者權限、群組成員權限、一般人權限等等。例如，檔案 README.txt 的權限欄為 -rwxr-x-r&lt;span style=&quot;text-decoration: line-through;&quot;&gt;，可以被分為三個一組，改寫為 owner(rwx)+group(r-x)+others(r&lt;/span&gt;)。這代表該檔案的使用者具有讀取 Read (r)、寫入 Write (w) 與執行 eXecute (x) 的權限，群組成員則只能讀取與執行，而其他人則只能讀取。&lt;/p&gt;
&lt;p&gt;擁有權限者可以修改檔案的屬性，像是 chown 指令可以改變檔案的擁有者，chgrp 指令可以改變檔案的所屬群組，chmod 可以改變檔案的存取權限等。&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;Linux 的檔案程式設計&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;而對於 Linux 程式設計師而言，關心的則是如何用程式對這些『物件』進行操作，像是開檔、讀檔、關檔、改變屬性等動作。表格 3 顯示了如何利用程式對這些物件進行操作的一些基本方法。&lt;/p&gt;
表格 3. 程式對檔案系統的基本操作&lt;br /&gt;
&lt;table class=&quot;wiki-content-table&quot;&gt;
&lt;tr&gt;
&lt;th&gt;物件&lt;/th&gt;
&lt;th&gt;範例&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;檔案&lt;/td&gt;
&lt;td&gt;fd = open(“/myfile”…)&lt;/td&gt;
&lt;td&gt;開關檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;檔案&lt;/td&gt;
&lt;td&gt;write, read, lseek&lt;/td&gt;
&lt;td&gt;讀寫檔案&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;屬性&lt;/td&gt;
&lt;td&gt;stat(“/myfile”, &amp;amp;mybuf)&lt;/td&gt;
&lt;td&gt;修改屬性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目錄&lt;/td&gt;
&lt;td&gt;DIR *dh = opendir(“/mydir”)&lt;/td&gt;
&lt;td&gt;開啟目錄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目錄&lt;/td&gt;
&lt;td&gt;struct dirent *ent = readdir(dh)&lt;/td&gt;
&lt;td&gt;讀取目錄&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;作業系統必須支援程式設計師對這些物件的操作，程式可以透過系統呼叫 (像是 open(), read(), write(), lseek(), stat(), opendir(), readdir(), 等函數) 操控檔案系統。而檔案系統的主要任務也就是支持這些操作，讓應用程式得以不需要處理區塊的問題，而是處理『路徑』、『目錄』與『檔案』。&lt;/p&gt;
&lt;p&gt;早期的 Linux 直接將檔案系統 (像是 Ext2檔案系統) 放入核心當中，但是在這樣的方式下，Linux Kernel 與檔案系統綁得太緊密了，會造成無法抽換檔案系統的困境，因此在後來的 Linux 增加了一個稱為 VFS 的虛擬檔案系統，以便讓檔案系統可以很容易的抽換。&lt;/p&gt;
&lt;p&gt;接著，我們先看看 Linux 真實檔案系統的一個經典範例 - Ext2檔案系統，然後再來介紹這個虛擬檔案系統 VFS 的運作方式。&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;Ext2 檔案系統&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;Ext2 檔案系統的結構如圖 2 所示，一個 Ext2 檔案系統可被分為數個區塊群 (Block Group)，每個區塊群都由超級區塊 (superblock) 所引導，超級區塊會透過索引結構 (inode) 連結到資料區塊，Ext2 檔案系統的任務就是有效的組織 inode，讓尋找與更新的動作得以快速的進行。&lt;/p&gt;
&lt;div class=&quot;image-container aligncenter&quot;&gt;&lt;img src=&quot;http://ccckmit.wdfiles.com/local--files/lk:file/LinuxExt2.jpg&quot; alt=&quot;LinuxExt2.jpg&quot; class=&quot;image&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;圖 2. Ext2 檔案系統的儲存結構&lt;/p&gt;
&lt;p&gt;索引節點inode 是從 UNIX 早期就使用的檔案系統組織結構，Ext2 也使用inode組織區塊，以形成樹狀的區塊結構。inode 是一種相當奇特的樹狀結構，除了記錄目錄的相關資訊之外，其直接連結會連接到目標的資料區塊，而間接連結則可連結到其他的 inode，包含雙層與三層的連結區域，因此可以很快速的擴展開來。有利於減少磁碟的讀取次數。圖 3 顯示了 inode 的索引方式，而圖 4 則顯示了 inode 索引節點的內部結構。&lt;/p&gt;
&lt;div class=&quot;image-container aligncenter&quot;&gt;&lt;img src=&quot;http://ccckmit.wdfiles.com/local--files/lk:file/LinuxInodeIndex.jpg&quot; alt=&quot;LinuxInodeIndex.jpg&quot; class=&quot;image&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;圖 3 使用inode對裝置中的區塊進行索引&lt;/p&gt;
&lt;div class=&quot;image-container aligncenter&quot;&gt;&lt;img src=&quot;http://ccckmit.wdfiles.com/local--files/lk:file/LinuxInode.jpg&quot; alt=&quot;LinuxInode.jpg&quot; class=&quot;image&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;圖 4 索引節點inode的內部結構&lt;/p&gt;
&lt;p&gt;Ext2 利用 inode 建構出磁碟的目錄結構與索引系統，於是可以從超級區塊連接到裝置的資料區塊。在 Linux 原始碼中，Ext2檔案系統的函數都撰寫成 ext2_xxx() 的格式，因此很容易辨認。Ext2的資料結構定義在 include/linux/ext2_fs.h 標頭檔中，而其函數則定義在include/linux/ext2.h 可以在 Linux 原始碼中的 fs/ext2/ 資料夾中找到。&lt;/p&gt;
&lt;p&gt;Ext2 的主要物件有區塊群 (block group), 超級區塊 (super_block), 索引結構 (inode) 與目錄項 (dir_entry) 等。您可以在 Linux 的原始碼中找到這些資料結構的定義，表格 4 顯示了這些物件與其在 Linux 原始碼中的位置，有興趣的讀者可以自行參考。&lt;/p&gt;
表格 4 Ext2 檔案系統中的重要物件&lt;br /&gt;
&lt;table class=&quot;wiki-content-table&quot;&gt;
&lt;tr&gt;
&lt;th&gt;物件&lt;/th&gt;
&lt;th&gt;資料結構&lt;/th&gt;
&lt;th&gt;宣告檔案 (Linux 2.6.29.4原始碼)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;區塊群&lt;/td&gt;
&lt;td&gt;struct ext2_group_desc {…}&lt;/td&gt;
&lt;td&gt;/include/linux/ext2_fs.h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;超級區塊&lt;/td&gt;
&lt;td&gt;struct ext2_super_block {…}&lt;br /&gt;
struct ext2_sb_info {…}&lt;/td&gt;
&lt;td&gt;/include/linux/ext2_fs.h&lt;br /&gt;
/include/linux/ext2_fs_sb.h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;索引結構&lt;/td&gt;
&lt;td&gt;struct ext2_inode {…}&lt;br /&gt;
struct ext2_inode_info {…}&lt;/td&gt;
&lt;td&gt;/include/linux/ext2_fs.h&lt;br /&gt;
/fs/ext2/ext2.h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目錄項&lt;/td&gt;
&lt;td&gt;struct ext2_dir_entry {…}&lt;br /&gt;
struct ext2_dir_entry2 {…}&lt;/td&gt;
&lt;td&gt;/include/linux/ext2_fs.h&lt;br /&gt;
/include/linux/ext2_fs.h&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h1&gt;&lt;span&gt;Linux 當中的 VFS 虛擬檔案系統&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;在傳統的UNIX系統中，檔案系統是固定的實體檔案系統。但在Linux當中，為了統合各個檔案系統，因而加上了一層虛擬檔案系統 (Virtual File System: VFS) ，VFS 是一組檔案操作的抽象介面，我們可以將任何的真實檔案系統，透過 VFS 掛載到 Linux 中。&lt;/p&gt;
&lt;p&gt;由於 VFS只是個虛擬的檔案系統，並不負責組織磁碟結構，因此，所有的組織動作都交由真實的檔案系統處理。VFS 所負責的操作都是在記憶體當中的部分，包含目錄結構的快取等功能，這樣就能增快存取的速度。&lt;/p&gt;
&lt;p&gt;VFS 是一個軟體層的介面，負責處理檔案的處理請求，像是讀取、寫入、複製、刪除等。由於各種檔案系統的格式並不相同，所以 VFS 並不直接處理檔案格式，而是規定這些處理請求的介面及操作語意，然後交由真實的檔案系統 (像是 EXT2) 去處理。圖 5 顯示了 Linux 核心、VFS 與真實檔案系統之間的架構關係。&lt;/p&gt;
&lt;div class=&quot;image-container aligncenter&quot;&gt;&lt;img src=&quot;http://ccckmit.wdfiles.com/local--files/lk:file/LinuxVFS.jpg&quot; alt=&quot;LinuxVFS.jpg&quot; class=&quot;image&quot; /&gt;&lt;/div&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;圖 5 Linux 的檔案系統結構&lt;/p&gt;
&lt;p&gt;真實檔案系統是在檔案結構的組織下，利用區塊裝置驅動模組所建構出來的。在Linux 作業系統中，允許安裝各式各樣的檔案系統，像是 BSD、FAT32、NTFS、EXT2、EXT3、JFS、JFS2、ReiserFS 等，這些檔案系統透過統一的虛擬檔案系統介面 (Virtual File System : VFS)，就能被掛載到 Linux 作業系統中。&lt;/p&gt;
&lt;p&gt;甚至，有些非實體檔案系統，也可以透過 VFS 掛載進來，像是根檔案系統 (rootfs), 記憶體對應檔案系統 (proc), 網路檔案系統 (sockfs) 等，都可以透過 VFS掛載進來，由 VFS 進行管理與操控。&lt;/p&gt;
&lt;p&gt;為了要將檔案系統掛載到 Linux 當中，Linux 仍然利用『註冊與反向呼叫機制』 (Register and Callback) 作為 VFS 的主要設計方式。實體檔案系統 (像是Ext2) 會向 VFS 進行註冊，以便將反向呼叫用的函數指標傳遞給 Linux 系統，接著 Linux 系統就可以在是當的時機，透過這些函數指標呼叫實體檔案系統的函數。&lt;/p&gt;
&lt;p&gt;為了組織 VFS 的這些註冊函數，Linux 採用了一個類似物件導向的方式，將函數以物件的方式組織起來。由於VFS 的設計深受UNIX與 Ext2 的影響，因此在使用的術語及結構安排上都使用由UNIX/Ext2 所遺留下來的檔案系統之術語。&lt;/p&gt;
&lt;p&gt;受到 UNIX/Ext2 的影’響，VFS系統也是由超級區塊與 inode 物件所構成的，另外還有目錄項 (dentry) 與檔案 (file) 等物件，分別對應到目錄中的子項與檔案等物件 。&lt;/p&gt;
&lt;p&gt;超級區塊 (Superblock) 在邏輯上對應到一個檔案系統分割區 (Partition)，而索引節點 (inode) 則對應到目錄結構 (directory structure)，目錄中包含很多子項目，可能是檔案或資料夾，這些子項目稱為目錄項 (dentry)，這些項目其中有些代表檔案 (file)，透過 inode 與 dentry 就可以取得磁碟中的資料區塊 (data block)，這些區塊就是檔案的內容。&lt;/p&gt;
&lt;p&gt;於是，VFS 可以再 inode 中新增、修改、刪除、查詢 dentry (mkdir, rmdir, …)，然後取得或設定 dentry 的屬性 (chmod, chowner, …)，或者利用inode找到檔案 (open)，然後再對檔案進行操作 (read, write, …)。表格 5 顯示了 VFS 系統中的重要函數原型，讀者應可看出這些物件間的關係。&lt;/p&gt;
表格 5. VFS 中的代表性函數原型&lt;br /&gt;
&lt;table class=&quot;wiki-content-table&quot;&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;函數原型&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;開檔&lt;/td&gt;
&lt;td&gt;int (*open) (struct inode *, struct file *);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;建立資料夾&lt;/td&gt;
&lt;td&gt;int (*mkdir) (struct inode *,struct dentry *,int);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;設定屬性&lt;/td&gt;
&lt;td&gt;int (*setattr) (struct dentry *, struct iattr *);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;追蹤連結&lt;/td&gt;
&lt;td&gt;void * (*follow_link) (struct dentry *, struct nameidata *);&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;所有掛載後的分割都會被放在 vfsmntlist 這個 Linux Kernel 的串列變數中，串列中的每一個元素為 vfsmount結構，該結構代表一個分割，其中的 mnt_sb 欄即是該分割的超級區塊 (super_block)，超級區塊中有個 s_inodes 欄位指向 inode 節點串列，inode 也透過 i_sb 欄位指回超級區塊。&lt;/p&gt;
&lt;p&gt;當檔案系統想要掛載到 Linux 當中時，會將一個稱為 read_super 的函數，傳遞給 VFS，於是 VFS 就可以透過下列的結構，將檔案系統串接起來，形成檔案系統串列。在需要使用該檔案系統時，再透過read_super 將 super_block載入記憶體中 ，&lt;/p&gt;
&lt;p&gt;super_block, inode, file, dentry 結構中都有個 op 欄位，該欄位儲存了一堆反向呼叫函數，可以用來呼叫真實檔案系統 (像是 Ext2) 中的操作函數，以便取得硬碟中的資料，或者將資料寫回硬碟當中。範例 1 顯示了 Linux 原始碼當中這些物件的結構與操作函數，詳細閱讀有助於進一步理解 VFS 檔案系統的設計方式。&lt;/p&gt;
&lt;p&gt;範例 1. 虛擬檔案系統VFS的Linux 原始碼節錄&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;pre&gt;&lt;code&gt;         檔案：Linux 2.6.29.4 原始檔 include/linux/fs.h
649   struct inode {                                                       索引節點inode結構       
650    struct hlist_node i_hash;                                           雜湊表格
651    struct list_head i_list;                                            inode 串列
652    struct list_head i_sb_list;                                         超級區塊串列
653    struct list_head i_dentry;                                          目錄項串列
      …                                                                    …
675    const struct inode_operations *i_op;                                inode 的操作函數
676    const struct file_operations *i_fop;                                檔案的操作函數
677    struct super_block *i_sb;                                           指向超級區塊
…     …                                                                    
714   };                                                                   
…     …                                                                    
839   struct file {                                                        檔案物件的結構
…     …                                                                    
847       struct path        f_path;                                            路徑
848   #define f_dentry    f_path.dentry                                        
849   #define f_vfsmnt    f_path.mnt                                         
850       const struct file_operations    *f_op;                             檔案的操作函數
…     …                                                                    
875   };                                                                   
…     …                                                                    
1132  struct super_block {                                                 超級區塊的結構
1133   struct list_head s_list;                                            區塊串列
1134   dev_t s_dev;                                                        設備代號
1135   unsigned long s_blocksize;                                          區塊大小
…     …                                                                    …
1139   struct file_system_type *s_type;                                    真實檔案系統
1140   const struct super_operations *s_op;                                超級區塊操作函數
…     …                                                                    …
1157   struct list_head s_inodes;                                          整個inode串列
1158   struct list_head s_dirty;                                           修改過的inode串列
…     …                                                                    
1206  };                                                                   
…     …                                                                    
1310  struct file_operations {                                             檔案的操作函數
1311   struct module *owner;                                                 檔案擁有者
…     …包含一群檔案操作的函數指標，列舉如下…                               
…     llseek(), read(), write(), aio_read(), aio_write(),                  
…     readdir(), poll(), ioctl(), unlocked_ioctl(),                        
…     compat_ioctl(), mmap(), open(), flush(), release(),                  
…     fsync(), aio_fsync(), lock(), sendpage(),                            
…     get_unmapped_area(), check_flags(), flock(),                         
…     splice_write(), splice_read(), setlease()                            
1337  };                                                                   
1338                                                                       
1339  struct inode_operations {                                            inode 的操作函數
…     …包含一群inode操作的函數指標，列舉如下…                              …
…     create(), lookup(), link(), unlink(), symlink(), mkdir(),            
…     rmdir(), mknod(), rename(), readlink(), follow_link(),               
…     put_link(), truncate(), permission(), setattr(),                     
…     setxattr(), getxattr(), listxattr(), removexattr(),                  
…     trunctate_range(), fallocate(), filemap()                            
1366  };                                                                   

1382  struct super_operations {                                            超級區塊操作函數
…     …包含一群超級區塊操作的函數指標，列舉如下…                           
…     alloc_inode(), destroy_inode(), dirty_inode(),                       
…      write_inode(), drop_inode(), delete_inode(),                        
…     put_super(), write_super(), sunc_fs(), freeze_fs(),                  
…     unfreeze_fs(), statfs(), remount_fs(), clear_inode(),                
…     unmount_begin(), show_options(), show_stats(),                       
…     quota_read(),quota_write(),bdev_try_to_free_page()                   
1407  };                                                                   
…     ….                                                                   
1565  struct file_system_type {                                            檔案系統物件
1566    const char *name;                                                  檔案系統名稱
1567    int fs_flags;                                                      旗標
1568    int (*get_sb) (struct file_system_type *, int,                     超級區塊取得函數
1569       const char *, void *, struct vfsmount *);                       
1570    void (*kill_sb) (struct super_block *);                            超級區塊刪除函數
1571    struct module *owner;                                              
1572    struct file_system_type * next;                                    下一個檔案系統
1573    struct list_head fs_supers;                                        超級區塊串列
…     …                                                                    
1582  };&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;以下顯示了 dentry 的操作函數的相關檔案結構&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;pre&gt;&lt;code&gt;…    檔案：Linux 2.6.29.4 原始檔 include/linux/fs.h …
89   struct dentry {                                            目錄項dentry物件      
…    …                                                          
94       struct inode *d_inode;                                  指向 inode
…    …                                                          
100      struct hlist_node d_hash;                               雜湊表
101      struct dentry *d_parent;                                父目錄
102      struct qstr d_name;                                     目錄項名稱
103                                                             
104      struct list_head d_lru;        /* LRU list */              最近最少使用串列
…    …                                                          (快取的取代策略)
115      struct dentry_operations *d_op;                         dentry 的操作函數
116      struct super_block *d_sb;                               指向超級區塊
…    …                                                          
120  };                                                         
…    …                                                          
134  struct dentry_operations {                                 dentry的操作函數
…    …包含一群 dentry 操作的函數指標，列舉如下…                 
…    d_revalidate(), d_hash(), d_compare(), d_delete(),
…    d_release(), d_iput(), ddname()
142  };&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h1&gt;&lt;span&gt;VFS 如何接上真實檔案系統&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;由於 VFS 的設計與 Ext2 相當類似，因此在實作對應上相當容易，像是在 ext2 檔案系統中，就直接將超級區塊相關的所有函數指標，全數塞入到 ext2_sops 這個型態為 super_operations 的變數中。然後在ext2_fill_super 這個函數中，將些函數塞入到 Linux VFS 超級區塊的 s_op 欄位中，就完成了連接的動作，於是 Linux 核心就可以透過超級區塊中的這些函數指標，操作 Ext2 檔案系統的超級區塊了。&lt;/p&gt;
&lt;p&gt;範例 2. 將 Ext2 連接到 VFS 上的程式原始碼片段&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;pre&gt;&lt;code&gt;    檔案：Linux 2.6.29.4 原始檔 fs/ext2/super.c
…    …
300    static const struct super_operations ext2_sops = {
301     .alloc_inode = ext2_alloc_inode,
302     .destroy_inode = ext2_destroy_inode,
303     .write_inode = ext2_write_inode,
304     .delete_inode = ext2_delete_inode,
305     .put_super = ext2_put_super,
306     .write_super = ext2_write_super,
307     .statfs  = ext2_statfs,
308     .remount_fs = ext2_remount,
309     .clear_inode = ext2_clear_inode,
310     .show_options = ext2_show_options,
311    #ifdef CONFIG_QUOTA
312     .quota_read = ext2_quota_read,
313     .quota_write = ext2_quota_write,
314    #endif
315    };
…    …
739    static int ext2_fill_super(struct super_block *sb, void *data, int silent)
…    …
…     sb-&amp;gt;s_op = &amp;amp;ext2_sops;
1050&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;於是，Linux 就可以利用 VFS 中的物件結構，對任何的檔案系統 (像是 Ext2, NTFS等) 進行操作。必須注意的是，掛載到 Linux VFS 上的檔案系統未必像 Ext2 這樣與 VFS 在設計理念上完全吻合，但是只要經過適當的封裝之後，仍然可以順利的掛載上去。像是 NTFS 就使用了 B+ Tree 的結構，並沒有使用 inode 結構，但是仍然可以被掛載到 VFS 中。&lt;/p&gt;
&lt;p&gt;透過VFS中的物件，Linux 可以呼叫真實檔案系統 (像是 Ext2) 的 VFS 介面函數，以便完成檔案操作的功能。舉例而言，Linux 中的系統呼叫 sys_stat() 函數，其實作方法如範例 3 所示，但是為了簡短起見，該函數的資料宣告部分已被去除，程式也被簡化了。&lt;/p&gt;
&lt;p&gt;範例 3. Linux 使用 VFS 操作檔案系統的範例&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;pre&gt;&lt;code&gt;sys_stat(path, buf) {                              取得檔案屬性
  dentry = namei(path);                            透過路徑取得目錄項
  if ( dentry == NULL ) return -ENOENT;            

  inode = dentry-&amp;gt;d_inode;                         透過目錄項取得 inode
  rc =inode-&amp;gt;i_op-&amp;gt;i_permission(inode);            執行i_permission() 操作
  if  ( rc ) return -EPERM;                        取得操作權

  rc = inode-&amp;gt;i_op-&amp;gt;i_getattr(inode, buf);         執行 i_getattr() 
  dput(dentry);                                    取得目錄項屬性傳回
  return rc;                                       
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 12 Jan 2010 04:23:18 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>Linux 的檔案系統簡介</span></h1> <p>在 UNIX/Linux 的使用者的腦海中，檔案系統是一種邏輯概念，而非實體的裝置。這種邏輯概念包含『檔案』、『目錄』、『路徑』、『檔案屬性』等等。我們可以用物件導向的方式將這些邏輯概念視為物件，表格 1 就顯示了這些物件的範例與意義。</p> 表格 1. 檔案系統中的基本邏輯概念<br /> <table class="wiki-content-table"> <tr> <th>概念</th> <th>範例</th> <th>說明</th> </tr> <tr> <td>路徑</td> <td>/home/ccc/hello.txt</td> <td>檔案在目錄結構中的位置</td> </tr> <tr> <td>目錄</td> <td>/home/ccc/</td> <td>資料夾中所容納的項目索引 (包含子目錄或檔案之屬性與連結)</td> </tr> <tr> <td>檔案</td> <td>Hello!\n…</td> <td>檔案的內容</td> </tr> <tr> <td>屬性</td> <td>-rwxr-xr&#8212; &#8230;ccc None 61 Jun 25&#160;12:17 README.txt</td> <td>檔案的名稱、權限、擁有者、修改日期等資訊</td> </tr> </table> <p>Linux 檔案系統的第一層目錄如表格 2 所示，認識這些目錄才能有效的運用 Linux 的檔案系統，通常使用者從命令列登入後會到達個人用戶的主目錄，像是使用者 ccc 登入後就會到 /home/ccc 目錄當中。目錄 /dev 所代表的是裝置 (device)，其中每一個子目錄通常代表一個裝置，這些裝置可以被掛載到 /mount 資料夾下，形成一顆邏輯目錄樹。</p> 表格 2. Linux 檔案系統的第一層目錄<br /> <table class="wiki-content-table"> <tr> <th>目錄</th> <th>全名</th> <th>說明</th> </tr> <tr> <td>/bin</td> <td>Binary</td> <td>存放二進位的可執行檔案</td> </tr> <tr> <td>/dev</td> <td>Device</td> <td>代表設備，存放裝置相關檔案</td> </tr> <tr> <td>/etc</td> <td>Etc…</td> <td>存放系統管理與配置檔案，像是服務程式 httpd 與 host.conf 等檔案。</td> </tr> <tr> <td>/home</td> <td>Home</td> <td>用戶的主目錄，每個使用者在其中都會有一個子資料夾，例如用戶 ccc 的資料夾為 /home/ccc/</td> </tr> <tr> <td>/lib</td> <td>Library</td> <td>包含系統函式庫與動態連結函式庫</td> </tr> <tr> <td>/sbin</td> <td>System binary</td> <td>系統管理程式，通常由系統管理員使用</td> </tr> <tr> <td>/tmp</td> <td>Temp</td> <td>暫存檔案</td> </tr> <tr> <td>/root</td> <td>Root directory</td> <td>系統的根目錄，通常由系統管理員使用</td> </tr> <tr> <td>/mnt</td> <td>Mount</td> <td>用戶所掛載上去的檔案系統，通常放在此目錄下</td> </tr> <tr> <td>/proc</td> <td>Process</td> <td>一個虛擬的目錄，代表整個記憶體空間的映射區，可以透過存取此目錄取得系統資訊。</td> </tr> <tr> <td>/var</td> <td>Variable</td> <td>存放各種服務的日誌等檔案</td> </tr> <tr> <td>/usr</td> <td>User</td> <td>龐大的目錄，所有的使用者程式與檔案都放在底下，像是 /usr/src 中就存放了 Linux 核心的原始碼， 而 /usr/bin 則存放所有的開發工具環境，像是 javac, java, gcc, perl 等。(若類比到 MS. Windows，此資料夾就像是 C:\Program Files)</td> </tr> </table> <p>然而，檔案畢竟是儲存在區塊裝置中的資料，要如何將這些概念化為區塊資料的組合，必須依賴某些資料結構。為了能將目錄、檔案、屬性、路徑這些物件儲存在區塊當中。這些區塊必須被進一步組織成更巨大的單元，這種巨型單元稱為分割 (Partition)。</p> <p>在 MS. Windows 中，分割是以 A: B: C: D: 這樣的概念形式呈現的。一個分割概念再 Windows 中通常稱為『槽』。由於歷史的因素，通常 A: B: 槽代表軟碟機，而 C:槽代表第一顆硬碟，D: E: …. 槽則可能是光碟、硬碟、或隨身碟等等。</p> <p>但是並非一個槽就代表單一的裝置，有時一個裝置會包含好幾個分割，像是許多人都會將主硬碟進一步分割成兩個 Partition，形成 C: D: 兩個槽，但實際上卻儲存在同一個硬碟裝置中，Linux 中的 Partition 的概念也與 Windows 類似，但是命名方式卻有所不同。</p> <p>在Linux 中並沒有槽的概念，而是直接將裝置映射到 /dev 資料夾的某個檔案路徑中。舉例而言，第一顆硬碟的第一個分割通常稱為 /dev/hda1，第二個分割則為 /dev/hda2, …。而第二顆硬碟的第一個分割區則為 /dev/hdb1，…。軟碟則被映射到 /dev/sda1, /dev/sda2, …./dev/sdb1, ….，以此類推。</p> <p>在 Linux 中，我們可以利用 mount 這個指令，將某個分割 (槽) 掛載到檔案系統的某個節點中，這樣就不需要知道某個資料夾 (像是 /mnt) 到底是何種檔案系統，整個檔案系統形成一顆與硬體無關的樹狀結構。舉例而言，mount -t ext2 /dev/hda3 /mnt 這個指令可以將Ext2格式的硬碟分割區 /dev/hda3 掛載到 /mnt 目錄下。而 mount -t iso9600 -o ro /dev/cdrom /mnt/cdrom 這樣的指令則可將 iso9600 格式的光碟 /dev/cdrom 以唯讀的方式掛載到 /mnt/cdrom路徑當中。當然，我們也可以用 unmount 指令將這些掛載上去的裝置移除。</p> <p>當我們使用 ls -all 這樣的指令以列出資料夾中的目錄結構時，看到的就是這些概念所反映出的資訊。圖 1 就顯示了 ls 指令所呈現出的資訊與所對應的概念 。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:file/LinuxFileDirectory.jpg" alt="LinuxFileDirectory.jpg" class="image" /></div> <p style="text-align: center;">圖 1 UNIX/Linux 中的檔案與目錄概念</p> <p>圖 1 中的權限欄位，第一個字元代表項目標記，目錄會以d標記，而檔案會以 - 標記，後面九個字元分為三群，分別是檔案擁有者權限、群組成員權限、一般人權限等等。例如，檔案 README.txt 的權限欄為 -rwxr-x-r<span style="text-decoration: line-through;">，可以被分為三個一組，改寫為 owner(rwx)+group(r-x)+others(r</span>)。這代表該檔案的使用者具有讀取 Read (r)、寫入 Write (w) 與執行 eXecute (x) 的權限，群組成員則只能讀取與執行，而其他人則只能讀取。</p> <p>擁有權限者可以修改檔案的屬性，像是 chown 指令可以改變檔案的擁有者，chgrp 指令可以改變檔案的所屬群組，chmod 可以改變檔案的存取權限等。</p> <h1><span>Linux 的檔案程式設計</span></h1> <p>而對於 Linux 程式設計師而言，關心的則是如何用程式對這些『物件』進行操作，像是開檔、讀檔、關檔、改變屬性等動作。表格 3 顯示了如何利用程式對這些物件進行操作的一些基本方法。</p> 表格 3. 程式對檔案系統的基本操作<br /> <table class="wiki-content-table"> <tr> <th>物件</th> <th>範例</th> <th>說明</th> </tr> <tr> <td>檔案</td> <td>fd = open(“/myfile”…)</td> <td>開關檔案</td> </tr> <tr> <td>檔案</td> <td>write, read, lseek</td> <td>讀寫檔案</td> </tr> <tr> <td>屬性</td> <td>stat(“/myfile”, &amp;mybuf)</td> <td>修改屬性</td> </tr> <tr> <td>目錄</td> <td>DIR *dh = opendir(“/mydir”)</td> <td>開啟目錄</td> </tr> <tr> <td>目錄</td> <td>struct dirent *ent = readdir(dh)</td> <td>讀取目錄</td> </tr> </table> <p>作業系統必須支援程式設計師對這些物件的操作，程式可以透過系統呼叫 (像是 open(), read(), write(), lseek(), stat(), opendir(), readdir(), 等函數) 操控檔案系統。而檔案系統的主要任務也就是支持這些操作，讓應用程式得以不需要處理區塊的問題，而是處理『路徑』、『目錄』與『檔案』。</p> <p>早期的 Linux 直接將檔案系統 (像是 Ext2檔案系統) 放入核心當中，但是在這樣的方式下，Linux Kernel 與檔案系統綁得太緊密了，會造成無法抽換檔案系統的困境，因此在後來的 Linux 增加了一個稱為 VFS 的虛擬檔案系統，以便讓檔案系統可以很容易的抽換。</p> <p>接著，我們先看看 Linux 真實檔案系統的一個經典範例 - Ext2檔案系統，然後再來介紹這個虛擬檔案系統 VFS 的運作方式。</p> <h1><span>Ext2 檔案系統</span></h1> <p>Ext2 檔案系統的結構如圖 2 所示，一個 Ext2 檔案系統可被分為數個區塊群 (Block Group)，每個區塊群都由超級區塊 (superblock) 所引導，超級區塊會透過索引結構 (inode) 連結到資料區塊，Ext2 檔案系統的任務就是有效的組織 inode，讓尋找與更新的動作得以快速的進行。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:file/LinuxExt2.jpg" alt="LinuxExt2.jpg" class="image" /></div> <p style="text-align: center;">圖 2. Ext2 檔案系統的儲存結構</p> <p>索引節點inode 是從 UNIX 早期就使用的檔案系統組織結構，Ext2 也使用inode組織區塊，以形成樹狀的區塊結構。inode 是一種相當奇特的樹狀結構，除了記錄目錄的相關資訊之外，其直接連結會連接到目標的資料區塊，而間接連結則可連結到其他的 inode，包含雙層與三層的連結區域，因此可以很快速的擴展開來。有利於減少磁碟的讀取次數。圖 3 顯示了 inode 的索引方式，而圖 4 則顯示了 inode 索引節點的內部結構。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:file/LinuxInodeIndex.jpg" alt="LinuxInodeIndex.jpg" class="image" /></div> <p style="text-align: center;">圖 3 使用inode對裝置中的區塊進行索引</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:file/LinuxInode.jpg" alt="LinuxInode.jpg" class="image" /></div> <p style="text-align: center;">圖 4 索引節點inode的內部結構</p> <p>Ext2 利用 inode 建構出磁碟的目錄結構與索引系統，於是可以從超級區塊連接到裝置的資料區塊。在 Linux 原始碼中，Ext2檔案系統的函數都撰寫成 ext2_xxx() 的格式，因此很容易辨認。Ext2的資料結構定義在 include/linux/ext2_fs.h 標頭檔中，而其函數則定義在include/linux/ext2.h 可以在 Linux 原始碼中的 fs/ext2/ 資料夾中找到。</p> <p>Ext2 的主要物件有區塊群 (block group), 超級區塊 (super_block), 索引結構 (inode) 與目錄項 (dir_entry) 等。您可以在 Linux 的原始碼中找到這些資料結構的定義，表格 4 顯示了這些物件與其在 Linux 原始碼中的位置，有興趣的讀者可以自行參考。</p> 表格 4 Ext2 檔案系統中的重要物件<br /> <table class="wiki-content-table"> <tr> <th>物件</th> <th>資料結構</th> <th>宣告檔案 (Linux 2.6.29.4原始碼)</th> </tr> <tr> <td>區塊群</td> <td>struct ext2_group_desc {…}</td> <td>/include/linux/ext2_fs.h</td> </tr> <tr> <td>超級區塊</td> <td>struct ext2_super_block {…}<br /> struct ext2_sb_info {…}</td> <td>/include/linux/ext2_fs.h<br /> /include/linux/ext2_fs_sb.h</td> </tr> <tr> <td>索引結構</td> <td>struct ext2_inode {…}<br /> struct ext2_inode_info {…}</td> <td>/include/linux/ext2_fs.h<br /> /fs/ext2/ext2.h</td> </tr> <tr> <td>目錄項</td> <td>struct ext2_dir_entry {…}<br /> struct ext2_dir_entry2 {…}</td> <td>/include/linux/ext2_fs.h<br /> /include/linux/ext2_fs.h</td> </tr> </table> <h1><span>Linux 當中的 VFS 虛擬檔案系統</span></h1> <p>在傳統的UNIX系統中，檔案系統是固定的實體檔案系統。但在Linux當中，為了統合各個檔案系統，因而加上了一層虛擬檔案系統 (Virtual File System: VFS) ，VFS 是一組檔案操作的抽象介面，我們可以將任何的真實檔案系統，透過 VFS 掛載到 Linux 中。</p> <p>由於 VFS只是個虛擬的檔案系統，並不負責組織磁碟結構，因此，所有的組織動作都交由真實的檔案系統處理。VFS 所負責的操作都是在記憶體當中的部分，包含目錄結構的快取等功能，這樣就能增快存取的速度。</p> <p>VFS 是一個軟體層的介面，負責處理檔案的處理請求，像是讀取、寫入、複製、刪除等。由於各種檔案系統的格式並不相同，所以 VFS 並不直接處理檔案格式，而是規定這些處理請求的介面及操作語意，然後交由真實的檔案系統 (像是 EXT2) 去處理。圖 5 顯示了 Linux 核心、VFS 與真實檔案系統之間的架構關係。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:file/LinuxVFS.jpg" alt="LinuxVFS.jpg" class="image" /></div> <p style="text-align: center;">圖 5 Linux 的檔案系統結構</p> <p>真實檔案系統是在檔案結構的組織下，利用區塊裝置驅動模組所建構出來的。在Linux 作業系統中，允許安裝各式各樣的檔案系統，像是 BSD、FAT32、NTFS、EXT2、EXT3、JFS、JFS2、ReiserFS 等，這些檔案系統透過統一的虛擬檔案系統介面 (Virtual File System : VFS)，就能被掛載到 Linux 作業系統中。</p> <p>甚至，有些非實體檔案系統，也可以透過 VFS 掛載進來，像是根檔案系統 (rootfs), 記憶體對應檔案系統 (proc), 網路檔案系統 (sockfs) 等，都可以透過 VFS掛載進來，由 VFS 進行管理與操控。</p> <p>為了要將檔案系統掛載到 Linux 當中，Linux 仍然利用『註冊與反向呼叫機制』 (Register and Callback) 作為 VFS 的主要設計方式。實體檔案系統 (像是Ext2) 會向 VFS 進行註冊，以便將反向呼叫用的函數指標傳遞給 Linux 系統，接著 Linux 系統就可以在是當的時機，透過這些函數指標呼叫實體檔案系統的函數。</p> <p>為了組織 VFS 的這些註冊函數，Linux 採用了一個類似物件導向的方式，將函數以物件的方式組織起來。由於VFS 的設計深受UNIX與 Ext2 的影響，因此在使用的術語及結構安排上都使用由UNIX/Ext2 所遺留下來的檔案系統之術語。</p> <p>受到 UNIX/Ext2 的影’響，VFS系統也是由超級區塊與 inode 物件所構成的，另外還有目錄項 (dentry) 與檔案 (file) 等物件，分別對應到目錄中的子項與檔案等物件 。</p> <p>超級區塊 (Superblock) 在邏輯上對應到一個檔案系統分割區 (Partition)，而索引節點 (inode) 則對應到目錄結構 (directory structure)，目錄中包含很多子項目，可能是檔案或資料夾，這些子項目稱為目錄項 (dentry)，這些項目其中有些代表檔案 (file)，透過 inode 與 dentry 就可以取得磁碟中的資料區塊 (data block)，這些區塊就是檔案的內容。</p> <p>於是，VFS 可以再 inode 中新增、修改、刪除、查詢 dentry (mkdir, rmdir, …)，然後取得或設定 dentry 的屬性 (chmod, chowner, …)，或者利用inode找到檔案 (open)，然後再對檔案進行操作 (read, write, …)。表格 5 顯示了 VFS 系統中的重要函數原型，讀者應可看出這些物件間的關係。</p> 表格 5. VFS 中的代表性函數原型<br /> <table class="wiki-content-table"> <tr> <th>操作</th> <th>函數原型</th> </tr> <tr> <td>開檔</td> <td>int (*open) (struct inode *, struct file *);</td> </tr> <tr> <td>建立資料夾</td> <td>int (*mkdir) (struct inode *,struct dentry *,int);</td> </tr> <tr> <td>設定屬性</td> <td>int (*setattr) (struct dentry *, struct iattr *);</td> </tr> <tr> <td>追蹤連結</td> <td>void * (*follow_link) (struct dentry *, struct nameidata *);</td> </tr> </table> <p>所有掛載後的分割都會被放在 vfsmntlist 這個 Linux Kernel 的串列變數中，串列中的每一個元素為 vfsmount結構，該結構代表一個分割，其中的 mnt_sb 欄即是該分割的超級區塊 (super_block)，超級區塊中有個 s_inodes 欄位指向 inode 節點串列，inode 也透過 i_sb 欄位指回超級區塊。</p> <p>當檔案系統想要掛載到 Linux 當中時，會將一個稱為 read_super 的函數，傳遞給 VFS，於是 VFS 就可以透過下列的結構，將檔案系統串接起來，形成檔案系統串列。在需要使用該檔案系統時，再透過read_super 將 super_block載入記憶體中 ，</p> <p>super_block, inode, file, dentry 結構中都有個 op 欄位，該欄位儲存了一堆反向呼叫函數，可以用來呼叫真實檔案系統 (像是 Ext2) 中的操作函數，以便取得硬碟中的資料，或者將資料寫回硬碟當中。範例 1 顯示了 Linux 原始碼當中這些物件的結構與操作函數，詳細閱讀有助於進一步理解 VFS 檔案系統的設計方式。</p> <p>範例 1. 虛擬檔案系統VFS的Linux 原始碼節錄</p> <div class="code"> <pre><code> 檔案：Linux 2.6.29.4 原始檔 include/linux/fs.h 649 struct inode { 索引節點inode結構 650 struct hlist_node i_hash; 雜湊表格 651 struct list_head i_list; inode 串列 652 struct list_head i_sb_list; 超級區塊串列 653 struct list_head i_dentry; 目錄項串列 … … 675 const struct inode_operations *i_op; inode 的操作函數 676 const struct file_operations *i_fop; 檔案的操作函數 677 struct super_block *i_sb; 指向超級區塊 … … 714 }; … … 839 struct file { 檔案物件的結構 … … 847 struct path f_path; 路徑 848 #define f_dentry f_path.dentry 849 #define f_vfsmnt f_path.mnt 850 const struct file_operations *f_op; 檔案的操作函數 … … 875 }; … … 1132 struct super_block { 超級區塊的結構 1133 struct list_head s_list; 區塊串列 1134 dev_t s_dev; 設備代號 1135 unsigned long s_blocksize; 區塊大小 … … … 1139 struct file_system_type *s_type; 真實檔案系統 1140 const struct super_operations *s_op; 超級區塊操作函數 … … … 1157 struct list_head s_inodes; 整個inode串列 1158 struct list_head s_dirty; 修改過的inode串列 … … 1206 }; … … 1310 struct file_operations { 檔案的操作函數 1311 struct module *owner; 檔案擁有者 … …包含一群檔案操作的函數指標，列舉如下… … llseek(), read(), write(), aio_read(), aio_write(), … readdir(), poll(), ioctl(), unlocked_ioctl(), … compat_ioctl(), mmap(), open(), flush(), release(), … fsync(), aio_fsync(), lock(), sendpage(), … get_unmapped_area(), check_flags(), flock(), … splice_write(), splice_read(), setlease() 1337 }; 1338 1339 struct inode_operations { inode 的操作函數 … …包含一群inode操作的函數指標，列舉如下… … … create(), lookup(), link(), unlink(), symlink(), mkdir(), … rmdir(), mknod(), rename(), readlink(), follow_link(), … put_link(), truncate(), permission(), setattr(), … setxattr(), getxattr(), listxattr(), removexattr(), … trunctate_range(), fallocate(), filemap() 1366 }; 1382 struct super_operations { 超級區塊操作函數 … …包含一群超級區塊操作的函數指標，列舉如下… … alloc_inode(), destroy_inode(), dirty_inode(), … write_inode(), drop_inode(), delete_inode(), … put_super(), write_super(), sunc_fs(), freeze_fs(), … unfreeze_fs(), statfs(), remount_fs(), clear_inode(), … unmount_begin(), show_options(), show_stats(), … quota_read(),quota_write(),bdev_try_to_free_page() 1407 }; … …. 1565 struct file_system_type { 檔案系統物件 1566 const char *name; 檔案系統名稱 1567 int fs_flags; 旗標 1568 int (*get_sb) (struct file_system_type *, int, 超級區塊取得函數 1569 const char *, void *, struct vfsmount *); 1570 void (*kill_sb) (struct super_block *); 超級區塊刪除函數 1571 struct module *owner; 1572 struct file_system_type * next; 下一個檔案系統 1573 struct list_head fs_supers; 超級區塊串列 … … 1582 };</code></pre></div> <p>以下顯示了 dentry 的操作函數的相關檔案結構</p> <div class="code"> <pre><code>… 檔案：Linux 2.6.29.4 原始檔 include/linux/fs.h … 89 struct dentry { 目錄項dentry物件 … … 94 struct inode *d_inode; 指向 inode … … 100 struct hlist_node d_hash; 雜湊表 101 struct dentry *d_parent; 父目錄 102 struct qstr d_name; 目錄項名稱 103 104 struct list_head d_lru; /* LRU list */ 最近最少使用串列 … … (快取的取代策略) 115 struct dentry_operations *d_op; dentry 的操作函數 116 struct super_block *d_sb; 指向超級區塊 … … 120 }; … … 134 struct dentry_operations { dentry的操作函數 … …包含一群 dentry 操作的函數指標，列舉如下… … d_revalidate(), d_hash(), d_compare(), d_delete(), … d_release(), d_iput(), ddname() 142 };</code></pre></div> <h1><span>VFS 如何接上真實檔案系統</span></h1> <p>由於 VFS 的設計與 Ext2 相當類似，因此在實作對應上相當容易，像是在 ext2 檔案系統中，就直接將超級區塊相關的所有函數指標，全數塞入到 ext2_sops 這個型態為 super_operations 的變數中。然後在ext2_fill_super 這個函數中，將些函數塞入到 Linux VFS 超級區塊的 s_op 欄位中，就完成了連接的動作，於是 Linux 核心就可以透過超級區塊中的這些函數指標，操作 Ext2 檔案系統的超級區塊了。</p> <p>範例 2. 將 Ext2 連接到 VFS 上的程式原始碼片段</p> <div class="code"> <pre><code> 檔案：Linux 2.6.29.4 原始檔 fs/ext2/super.c … … 300 static const struct super_operations ext2_sops = { 301 .alloc_inode = ext2_alloc_inode, 302 .destroy_inode = ext2_destroy_inode, 303 .write_inode = ext2_write_inode, 304 .delete_inode = ext2_delete_inode, 305 .put_super = ext2_put_super, 306 .write_super = ext2_write_super, 307 .statfs = ext2_statfs, 308 .remount_fs = ext2_remount, 309 .clear_inode = ext2_clear_inode, 310 .show_options = ext2_show_options, 311 #ifdef CONFIG_QUOTA 312 .quota_read = ext2_quota_read, 313 .quota_write = ext2_quota_write, 314 #endif 315 }; … … 739 static int ext2_fill_super(struct super_block *sb, void *data, int silent) … … … sb-&gt;s_op = &amp;ext2_sops; 1050</code></pre></div> <p>於是，Linux 就可以利用 VFS 中的物件結構，對任何的檔案系統 (像是 Ext2, NTFS等) 進行操作。必須注意的是，掛載到 Linux VFS 上的檔案系統未必像 Ext2 這樣與 VFS 在設計理念上完全吻合，但是只要經過適當的封裝之後，仍然可以順利的掛載上去。像是 NTFS 就使用了 B+ Tree 的結構，並沒有使用 inode 結構，但是仍然可以被掛載到 VFS 中。</p> <p>透過VFS中的物件，Linux 可以呼叫真實檔案系統 (像是 Ext2) 的 VFS 介面函數，以便完成檔案操作的功能。舉例而言，Linux 中的系統呼叫 sys_stat() 函數，其實作方法如範例 3 所示，但是為了簡短起見，該函數的資料宣告部分已被去除，程式也被簡化了。</p> <p>範例 3. Linux 使用 VFS 操作檔案系統的範例</p> <div class="code"> <pre><code>sys_stat(path, buf) { 取得檔案屬性 dentry = namei(path); 透過路徑取得目錄項 if ( dentry == NULL ) return -ENOENT; inode = dentry-&gt;d_inode; 透過目錄項取得 inode rc =inode-&gt;i_op-&gt;i_permission(inode); 執行i_permission() 操作 if ( rc ) return -EPERM; 取得操作權 rc = inode-&gt;i_op-&gt;i_getattr(inode, buf); 執行 i_getattr() dput(dentry); 取得目錄項屬性傳回 return rc; }</code></pre></div> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:introduction</guid>
				<title>Linux 作業系統簡介</title>
				<link>http://ccckmit.wikidot.com/lk:introduction</link>
				<description>

&lt;p&gt;Linux作業系統 是Linus Torvalds於芬蘭赫爾辛基大學當學生時，希望在IBM PC 個人電腦上實作出類似UNIX系統的一個專案。在 Linux 剛發展時主要參考的對象是荷蘭阿姆斯特丹大學教授 Andrew S. Tanenbaum 的 Minix 系統，後來 Torvalds 決定利用 GNU 工具全面改寫，於是發展出一個全新的作業系統，後來該作業系統被稱為Linux。&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 11 Jan 2010 09:22:51 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Linux作業系統 是Linus Torvalds於芬蘭赫爾辛基大學當學生時，希望在IBM PC 個人電腦上實作出類似UNIX系統的一個專案。在 Linux 剛發展時主要參考的對象是荷蘭阿姆斯特丹大學教授 Andrew S. Tanenbaum 的 Minix 系統，後來 Torvalds 決定利用 GNU 工具全面改寫，於是發展出一個全新的作業系統，後來該作業系統被稱為Linux。</p> <p>Linux 的系統架構大致分為『硬體』、『核心』、『函式庫』、『使用者程式』等四層，<br /> 硬體層主要包含許多硬體裝置的驅動程式、核心層乃是由 Linus Torvalds 所維護的 Linux 作業系統，而函式庫層則對作業系統的功能進行封裝後，提供給使用者程式呼叫使用。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:introduction/LinuxArchitecture.jpg" alt="LinuxArchitecture.jpg" class="image" /></div> <p style="text-align: center;">圖 10.12 Linux 的基本架構</p> <p>當行程需要作業系統服務 (例如讀取檔案) 時，可以利用『系統呼叫』請求作業系統介入，此時處理器會由使用者模式 (User Mode) 切換到核心模式 (Kernel Mode)，核心模式具有最高的權限，可以執行任何的動作。圖 10.12中的系統呼叫界面所扮演的，正是這樣一個中介的角色。</p> <p>Linux 所支援的硬體模組眾多，這些模組必須被掛載到作業系統當中，當然不可能由 Torvalds 一個人包辦寫出所有的驅動程式。所以 Linux 定訂了一整套輸出入介面規格，透過註冊機制與反向呼叫函數，讓驅動程式得以掛載到作業系統中。作業系統會在適當的時機呼叫這些驅動函數，以便取得輸出入資料。而這正是硬體模組介面的功能。這個介面可以載入 Loadable Kernel Module (大部分都是驅動程式)，以進行裝置輸出入的動作。</p> <p>Linux 2.6 版的核心包含『行程』、『記憶體』、『檔案』、『輸出入』、『網路』等五大子系統。行程系統是支援行程與執行緒等功能，實作了排程、行程切換等程式。記憶體系統可利用硬體的 MMU 單元支援虛擬記憶體等機制。檔案系統的最上層稱為虛擬檔案系統 (Virtual File System: VFS) ，VFS 是一組檔案操作的抽象介面，我們可以將任何的真實檔案系統，像是 FAT32, EXT2, JFS 等，透過 VFS 掛載到 Linux 中。真實檔案系統是在檔案結構的組織下，利用區塊裝置驅動模組所建構出來的。網路系統也是透過網路裝置驅動模組與 TCP/IP 相關程式碼所堆疊出來的。而輸出入系統則統合了『區塊、字元、網路』等三類裝置，以支援檔案、網路與虛擬記憶體等子系統。</p> <p>Linux 是一個注重速度與實用性的系統，因此沒有採用微核心的技術，以避免因為行程切換次數過多而減慢執行速度。目前圍繞著 Linux 作業系統已經形成了一個龐大的產業，幾乎沒有任何一家公司能主導 Linux 的發展方向，因為 Linux 是開放原始碼社群被工業化後的結果。</p> <p>由於開放原始碼的影響，Linux擁有眾多的版本，像是 Red Hat、Ubuntu、Fedora、Debian 等，但是這些版本幾乎都利用 Tovarlds 所維護的核心，整合其他開放原始碼軟體後所形成的，因此雖然版本眾多卻有統一的特性。</p> <p>由於 Linux 原本是以 GNU 工具所開發的，因此也被稱為 GNU/Linux。由於 GNU工具支援 IEEE 所制定的POSIX 標準，該標準對 UNIX 平台的函式庫進行了基本的統一動作，因此Linux 自然也就屬於POSIX 標準的成員之一。</p> <p>雖然 Tovarlds 最早是利用 IBM PC 開發 Linux 作業系統的，但是目前Linux 已經被移值到各種平台上。因此 Linux 所支援的處理器非常眾多，包含 IA32、MIPS、ARM、Power PC 等。當您想要將 Linux 移植到新的處理器上時，必須重新編譯 Linux 核心，您可以利用 GNU 的 gcc, make 等工具編譯 Linux 核心與大部分的 Linux 程式。</p> <p>Linux 並不是一個小型的作業系統，因此在啟動時通常必須透過啟動載入器載入 Linux 。在桌上型電腦中，常被使用的Linux啟動載入器有 GRUB、LILO 等。但是在高階的嵌入式系統當中，Linux 最常用的啟動程式則是U-Boot，這是因為 U-Boot 所支援的處理器非常眾多，因此成為目前最廣為使用的嵌入式啟動載入器。</p> <p>在本系列的文章中，我們將分別就 Linux 中的行程、記憶體、輸出入、檔案等四大子系統，分別進行說明，以便讓讀者能更進一步的理解 Linux 作業系統。</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:process</guid>
				<title>Linux 的行程管理</title>
				<link>http://ccckmit.wikidot.com/lk:process</link>
				<description>

&lt;h1&gt;&lt;span&gt;行程結構&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 11 Jan 2010 09:24:51 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>行程結構</span></h1> <p>Linux 中的行程 (Process) 被稱為任務 (Task)，其資料結構是一個稱為 task_struct 的 C 語言結構，該結構所記錄的欄位相當多，在 Linux 2.6.29.4 版當中光是該結構的宣告就占了 306 行的程式碼，範例 1 顯示了該結構的開頭與結束部分，由此可見要實作一個作業系統是相當不容易的工程。</p> <p>範例 1 Linux 中的 Task 之結構</p> <div class="code"> <pre><code>行號 Linux 2.6.29.4 版核心原始碼 include/linux/sched.h 檔案 … … 1115 struct task_struct { 1116 volatile long state; /* -1 unrunnable, 0 runnable, &gt;0 stopped */ 1117 void *stack; 1118 atomic_t usage; 1119 unsigned int flags; /* per process flags, defined below */ 1120 unsigned int ptrace; … … 1263 /* CPU-specific state of this task */ 1264 struct thread_struct thread; 1265 /* filesystem information */ 1266 struct fs_struct *fs; 1267 /* open file information */ 1268 struct files_struct *files; 1269 /* namespaces */ 1270 struct nsproxy *nsproxy; 1271 /* signal handlers */ 1272 struct signal_struct *signal; 1273 struct sighand_struct *sighand; … … 1417 #ifdef CONFIG_TRACING 1418 /* state flags for use by tracers */ 1419 unsigned long trace; 1420 #endif 1421 }; … …</code></pre></div> <p>Linux 的行程結構中包含了一個 thread 欄位 (1264 行)，該欄位用來儲存與 CPU 相關的暫存器、分段表等資訊，由於暫存器資訊是與處理器密切相關的，所以每一種 CPU 都會擁有不同的執行緒結構，IA32 (x86) 的thread_struct之程式片段如範例 10.3所示。</p> <p>範例 2. Linux 中的IA32 (x86) 處理器的Thread 之結構</p> <div class="code"> <pre><code>行號 Linux 2.6.29.4 版核心原始碼 arch/x86/include/asm/processor.h 檔案 … … 391 struct thread_struct { 392 /* Cached TLS descriptors: */ 393 struct desc_struct tls_array[GDT_ENTRY_TLS_ENTRIES]; 394 unsigned long sp0; 395 unsigned long sp; … …</code></pre></div> <h1><span>行程狀態</span></h1> <p>Linux 行程的狀態有 Running, Interruptible, Uninterruptible, Zombie, Stopped 等五種，但後來又增加了 Traced, EXIT_DEAD, TASK_DEAD, TASK_WAKEKILL 等四種，形成了九種狀態 (2.6.29.4 版)，如範例 10.4所示。</p> <p>範例 3. Linux 中的行程狀態</p> <div class="code"> <pre><code>行號 Linux 2.6.29.4 版核心原始碼 include/linux/sched.h 檔案 … … 174 #define TASK_RUNNING 0 175 #define TASK_INTERRUPTIBLE 1 176 #define TASK_UNINTERRUPTIBLE 2 177 #define __TASK_STOPPED 4 178 #define __TASK_TRACED 8 179 /* in tsk-&gt;exit_state */ 180 #define EXIT_ZOMBIE 16 181 #define EXIT_DEAD 32 182 /* in tsk-&gt;state again */ 183 #define TASK_DEAD 64 184 #define TASK_WAKEKILL 128 … …</code></pre></div> <h1><span>行程切換</span></h1> <p>行程切換 (內文切換) 是與處理器密切相關的程式碼，每個處理器的實作方式均有相當大的差異，但基本原理都是將上一個行程 (prev, 以下稱為舊行程) 的暫存器保存後，再將程式計數器設定給下一個行程 (next, 以下稱為新行程)。在 IA32 (x86) 的處理器中，Linux 的行程切換程式碼 如範例 4 所示，該行程切換函數 switch_to(prev, next, last) 是一個內嵌於 C 語言的組合語言巨集，採用 GNU 的內嵌組合語言語法 。</p> <p>首先，switch_to 最外層是一個 do { … } while (0) 的迴圈，這個語法很奇怪，因為該迴圈根本永遠都只會執行一次，那又為何要用迴圈呢？這純粹只是為了要把中間的組合語言用區塊結構 {…} 包起來而已，但卻很容易誤導讀者，以為那是一個無窮迴圈 。</p> <p>範例 4. Linux在 IA32 (x86) 處理器上的行程切換程式碼</p> <div class="code"> <pre><code>行號 Linux 2.6.29.4 檔案 arch/x86/include/asm/system.h … … 30 #define switch_to(prev, next, last) \ 31 do {\ 32 /* \ 33 * Context-switching clobbers all registers, so we clobber\ 34 * them explicitly, via unused output variables.\ 35 * (EAX and EBP is not listed because EBP is saved/restored \ 36 * explicitly for wchan access and EAX is the return value of \ 37 * __switch_to()) \ 38 */ \ 39 unsigned long ebx, ecx, edx, esi, edi; \ 40 \ 41 asm volatile(&quot;pushfl\n\t&quot; /* save flags */ \ 42 &quot;pushl %%ebp\n\t&quot; /* save EBP */ \ 43 &quot;movl %%esp,%[prev_sp]\n\t&quot;/* save ESP */ \ 44 &quot;movl %[next_sp],%%esp\n\t&quot;/* restore ESP */ \ 45 &quot;movl $1f,%[prev_ip]\n\t&quot; /* save EIP */ \ 46 &quot;pushl %[next_ip]\n\t&quot; /* restore EIP */ \ 47 &quot;jmp __switch_to\n&quot; /* regparm call */ \ 48 &quot;1:\t&quot; \ 49 &quot;popl %%ebp\n\t&quot; /* restore EBP */ \ 50 &quot;popfl\n&quot; /* restore flags */ \ 51 \ 52 /* output parameters */ \ 53 [prev_sp] &quot;=m&quot; (prev-&gt;thread.sp), \ 54 [prev_ip] &quot;=m&quot; (prev-&gt;thread.ip), \ 55 &quot;=a&quot; (last), \ 56 \ 57 /* clobbered output registers: */ \ 58 &quot;=b&quot; (ebx), &quot;=c&quot; (ecx), &quot;=d&quot; (edx), \ 59 &quot;=S&quot; (esi), &quot;=D&quot; (edi) \ 60 \ 61 /* input parameters: */ \ 62 : [next_sp] &quot;m&quot; (next-&gt;thread.sp), \ 63 [next_ip] &quot;m&quot; (next-&gt;thread.ip), \ 64 \ 65 /* regparm parameters for __switch_to(): */ \ 66 [prev] &quot;a&quot; (prev), \ 67 [next] &quot;d&quot; (next) \ 68 \ 69 : /* reloaded segment registers */ \ 70 &quot;memory&quot;); \ 71 } while (0) … …</code></pre></div> <p>第41行開始才是行程切換的動作，指令pushfl 用來儲存旗標暫存器到堆疊中，pushl %%ebp 用來儲存框架暫存器 (ebp) 到堆疊中，movl %%esp, %[prev_sp] 則用來儲存舊行程的堆疊暫存器 (esp) 到 (prev-&gt;thread.sp) 欄位中，而 pushl %[next_sp], %%esp 則是將新行程的堆疊暫存器 (next-&gt;thread.sp) 取出後，放到CPU的esp暫存器中，於是建構好新行程的堆疊環境。接著，第45行的movl $1f, %[prev_ip] 將標記 1 的位址放入舊行程的 prev-&gt;thread.ip 欄位中。接著46行用指令pushl %[next_ip] 將新行程的程式計數器 next-&gt;thread.ip 推入堆疊中，然後利用指令jmp __switch_to跳入C語言的 switch_to() 函數 中，當該函數的返回指令被執行時，將會發生一個奇妙的結果。</p> <p>由於 switch_to() 函數是一個C語言函數，原本應該被其他C語言函數呼叫的，呼叫前原本上層函數會先將下一個指令的位址存入堆疊中，然後才進行呼叫。C語言函數在返回前會從堆疊中取出返回點，以返回上一層函數繼續執行 。雖然我們是利用組合語言指令 jmp __switch_to 跳入該函數的，但C語言的編譯器仍然會以同樣的方式編譯，於是返回時仍然會從堆疊中取出 pushl %[next_ip] 指令所推入的位址，因而在 switch_to() 函數返回時，就會將程式計數器設為 next-&gt;thread.ip，於是透過函數返回的過程，間接的完成了行程切換的動作。</p> <p>既然新行程已經在 switch_to() 函數返回時就開始執行了，那麼內文切換的動作不就已經完成了嗎？既然如此為何又需要第 49-50 兩行的程式呢？我們必須進一步回答這個問題。</p> <p>第45行之所以將標記1放入prev-&gt;thread.ip中，是為了讓舊行程在下次被喚醒時，可以回到標記 1 的位置。當下次舊行程被喚醒後，就會從標記 1 的位址開始執行，舊行程可以利用第49-50行的popl %%ebp; popfl 兩個指令，恢復其 ebp (框架指標) 與旗標暫存器，然後再度透過 switch_to()，切換回舊行程 (只不過這次舊行程變成了函數switch_to(prev, next, last) 中的 next 角色，不再是『舊行程』了。</p> <p>或許我們可以說 switch_to() 函數其實並不負責切換行程，因為該函數會將處理器中各種需要保存的值存入舊行程 prev 的 task_struct 結構中，以便下次 prev 行程被喚醒前可以回存這些暫存器值，其實並沒有切換或執行新行程的功能，但因為 jmp __switch_to 指令前的 pushl %[next_ip] 指令，導致該函數在返回時順便做了行程切換的動作，這種隱含性是作業系統設計時一種相當弔詭的技巧，也是學習 Linux 時對程式人員最大的挑戰之一。</p> <h1><span>排程</span></h1> <p>解說完行程切換的原理後，我們就可以來看看排程系統了。Linux 採用的排程機制較為複雜，該排程式建構在一個稱為 goodness 的行程 (或執行緒) 評估值上，goodness 是一個從 0 到 1999 之間的整數。一般執行緒的 goodness 小於 1000，但即時執行緒的 goodness 則會從 1000 開始，因此保證了任何即時執行緒的 goodness 都會高於一般執行緒。</p> <div class="code"> <pre><code>#include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;unistd.h&gt; #include &lt;sys/types.h&gt; int spawn(char *prog, char **arg_list) { // Spawn為生育的意思 pid_t child; child = fork(); // 用fork()函數分枝出子行程 if (child != 0) { // 如果不成功 return child; // 傳回失敗的行程代碼 } else { // 否則 execvp(prog, arg_list); // 將 prog 參數所指定的 fprintf(stderr, &quot;spawn error\n&quot;); // 程式載入到子行程中 return -1; } } int main() { // 主程式開始 char *arg_list[] = { &quot;ls&quot;, &quot;-l&quot;, &quot;/etc&quot;, NULL }; // ls -l /etc spawn(&quot;ls&quot;, arg_list); // 開始分支 printf(&quot;The end of program.\n&quot;); // 印出主程式結束訊息 return 0; }</code></pre></div> <p>執行過程與結果</p> <div class="code"> <pre><code>$ gcc fork.c -o fork $ ./fork The end of program. $ total 94 -rwxr-x--- 1 ccc Users 2810 Jun 13 2008 DIR_COLORS drwxrwx---+ 2 ccc Users 0 Oct 7 2008 alternatives -rwxr-x--- 1 ccc Users 28 Jun 13 2008 bash.bashrc drwxrwx---+ 4 ccc Users 0 Oct 7 2008 defaults -rw-rw-rw- 1 ccc Users 716 Oct 7 2008 group ….</code></pre></div> <h1><span>執行緒</span></h1> <p>POSIX標準中支援的執行緒函式庫稱為 pthread，我們可以透過 pthread 結構與 pthread_create() 函數執行某個函數指標，以建立新的執行緒。範例 6 顯示了一個利用 pthread 建立兩個執行緒，分別不斷印出 George 與 Mary 的程式。該程式中總共有三個執行主體，第一個是 print_george()、第二個是 print_mary()、第三個是主程式本身。由於每隔 1 秒印一次 George ，但每隔 0.5 秒就印一次 Mary，因此執行結果會以 George, Mary, George, George,Mary 的形式印出。</p> <p>範例 6 利用pthread 函式庫建立執行緒的範例</p> <div class="code"> <pre><code>#include &lt;pthread.h&gt; // 引用 pthread 函式庫 #include &lt;stdio.h&gt; void *print_george(void *argu) { // 每隔一秒鐘印出一次 George 的函數 while (1) { printf(&quot;George\n&quot;); sleep(1); } return NULL; } void *print_mary(void *argu) { // 每隔一秒鐘印出一次 Mary 的函數 while (1) { printf(&quot;Mary\n&quot;); sleep(2); } return NULL; } int main() { // 主程式開始 pthread_t thread1, thread2; // 宣告兩個執行緒 pthread_create(&amp;thread1, NULL, &amp;print_george, NULL); // 執行緒 print_george pthread_create(&amp;thread2, NULL, &amp;print_mary, NULL); // 執行緒 print_mary while (1) { // 主程式每隔一秒鐘 printf(&quot;----------------\n&quot;); // 就印出分隔行 sleep(1); // 停止一秒鐘 } return 0; }</code></pre></div> <p>執行過程與結果</p> <div class="code"> <pre><code>$ gcc thread.c -o thread $ ./thread George Mary ------------------------- George ------------------------- George Mary ------------------------- George ------------------------- …</code></pre></div> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:memory</guid>
				<title>Linux 的記憶體管理</title>
				<link>http://ccckmit.wikidot.com/lk:memory</link>
				<description>

&lt;h1&gt;&lt;span&gt;簡介&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 12 Jan 2010 04:13:15 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>簡介</span></h1> <p>Linux 作業系統原本是在 IA32 (x86) 處理器上設計的，由於 IA32 具有 MMU 單元，因此大部分的 Linux 都預設支援虛擬記憶體機制。然而，在許多的嵌入式處理器中，並沒有 MMU 單元，於是有 Jeff Dionne 等人於 1998 年開始將 Linux 中的 MMU 機制去除並改寫，後來釋出了不具有 MMU 的 µClinux 版本。</p> <p>曾經有一段時間，嵌入式的系統開發者必須決定應使用具有 MMU 的 Linux 或不具 MMU 的 µClinux，但是後來在2.5.46版的 Linux 中，決定將 µClinux 納入核心中，所以後來的 Linux 核心已經包含了 µClinux 的功能，可以選擇是否要支援 MMU 單元。</p> <p>由於 Tovarlds 最早是在 IA32 (x86) 中發展出 Linux 作業系統的，因此 Linux 的記憶體管理機制深受 x86 處理器的影響。要瞭解 Linux 的記憶體管理機制，首先必須先理解 x86 的MMU 記憶體管理單元。</p> <h1><span>IA32 (x86) 的記憶體管理單元</span></h1> <p>Intel 的 IA32 (Pentium處理器) 採用了 GDT 與 LDT 兩種表格，其中的 LDT 分段表 (Local Descriptor Table) 是給一般行程使用的，而 GDT 分段表 (Global Descriptor Table) 則包含各行程共享的分段，通常由作業系統使用。</p> <p>IA32 同時具有分段與分頁單元，可以支援『純粹分段』、『單層分頁式分段』與『雙層分頁式分段』等三種組合。其『邏輯位址』 (Logical Address) 經過分段單位轉換後，稱為『線性位址』 (Linear Address)，再經過分頁單位轉換後，稱為真實位址 (Physical Address)。其簡要的轉換過程如圖 1 所示。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:memory/IA32MMU.jpg" alt="IA32MMU.jpg" class="image" /></div> <p style="text-align: center;">圖 1. IA32的兩階段位址轉換過程</p> <p>IA32 邏輯位址 (虛擬位址) 的長度是 48 位元，分為選擇器 S (selector : 16 bits) 與偏移量 D (offset : 32bits) 兩部分。其中的選擇器欄位中的pr兩個位元用來記錄保護屬性，g位元記錄表格代碼 (可指定目標表格為 GDT 或 LDT)，另外13個位元則記錄分段碼 (s)。</p> <p>IA32的分段表LDT與GDT各自包含4096個項目，每個項目都含有 『分段起始位址』 (base)、『分段長度』 (limit) 與數個『輔助位元』 (aux) 等三種欄位。其中 base 與 limit 的功能與一般分段表相同，而輔助位元則用來記錄分段屬性。這兩個表格都可以用來將邏輯位址轉換成線性位址，是 IA32 中的分段單元。詳細的轉換過程如圖 2 所示。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:memory/IA32segment.jpg" alt="IA32segment.jpg" class="image" /></div> <p style="text-align: center;">圖 2. IA32分段模式</p> <p>除了選擇器 (S) 的分段轉換過程之外，位移 (D) 部分會經過分頁表轉換成真實位址。位移 (D) 可再細分為三段 (p1, p2, d)，其中的 p1 式分頁目錄代號，p2式分頁表代號，而 d 則是分頁內位移。IA32 可以不採用分頁機制，或採用單層分頁機制，甚至可以採用雙層分頁機制。其中的分頁目錄 PD 是第一層分頁，分頁表 PT 則是第二層分頁。當採用單層分頁時，每頁的大小為 4-MB。而採用雙層分頁時，每頁的大小為 4-KB。圖 3 顯示了IA32 的分頁機制之轉換過程。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:memory/IA32page.jpg" alt="IA32page.jpg" class="image" /></div> <p style="text-align: center;">圖 3. IA32的分頁模式</p> <p>IA32 可以不使用分頁機制，直接將分段後的線性位址輸出，此時相當於一般的分段模式，其輸出位址如圖 4 中的 L 所示。如果使用單層分頁機制，則相當於一般的分段式分頁，其輸出位址如圖 4 中的 M1 所示。如果採用兩層分頁機制，則形成雙層分頁式分段體系，其輸出位址如圖 4 中的 M2所示。圖 4 顯示了IA32 MMU單元完整的轉換過程。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:memory/IA32pagesegment.jpg" alt="IA32pagesegment.jpg" class="image" /></div> <p style="text-align: center;">圖 4. IA32的分頁式分段模式</p> <p>從 IA32 的 MMU 單元設計中，我們可以看到 IA32 處理器留下了相當大的選擇空間給作業系統，作業系統可以自行選擇使用哪一種分段分頁機制，這與作業系統的設計有密切的關係。</p> <h1><span>Linux 的記憶體管理機制</span></h1> <p>X86 版本的 Linux 利用 GDT 指向核心的分頁，然後用 LDT 指向使用者行程的分頁。LDT 中所記載的是各個行程的分段表，以及行程的狀態段 (Task State Segment : TSS)。而 GDT 中則會記載這些分段表的起始點，TSS 起始點，以及核心的各分段起點。圖 5 顯示了 x86 版的 Linux 的分段記憶體管理機制。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:memory/LinuxMMU.jpg" alt="LinuxMMU.jpg" class="image" /></div> <p style="text-align: center;">圖 5. Linux 的記憶體管理機制</p> <p>透過圖 5 的分段機制，Linux 可為每一個行程的段落分配一塊大小不等的區段。其中每一個區段又可能佔據許多個分頁，x86 版本的Linux 採用 IA32 的延伸分頁模式，利用 IA32『分段+雙層分頁』的延伸記憶體管理模式，因此每個頁框的大小為4KB。</p> <p>當需要進行分段配置 (例如載入行程) 時，Linux會使用對偶式記憶體管理演算法 (Buddy System Algorithm) 配置分頁，在 Buddy 系統中，一個頁框代表一段連續的分頁，該演算法將的頁框區分為十種區塊大小，分別包含了 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 個連續的頁框，其中每個區塊的第一的頁框位置一定是區塊大小的倍數。舉例而言，一個包含 32 個頁框的區塊之起始位址一定是 32 * 4KB 的倍數。</p> <h1><span>Buddy 頁框分配系統</span></h1> <p>Buddy 系統的運作方法，乃是利用一個名為 free_area[10] 的陣列，該陣列中儲存了對應大小的位元映像圖 (bitmap)，以記錄區塊的配置狀況。當有分頁配置需求時，Linux 會尋找大小足夠的最小區塊，舉例而言，如果需要13 個頁框，則 Linux 會從大小為 16 的頁框區中取出一個可用頁框，分配給需求者。但是如果大小為 16 的頁框區沒有可用頁框，則會從大小為 32 的頁框區取得，然後分成兩半，一半分配給需求者，另一半則放入大小為 16 的可用頁框區中。</p> <p>當某頁框被釋放時，Buddy 系統會試圖檢查其兄弟頁框是否也處於可用狀態，若是則將兩個頁框合併以形成一個更大的可用頁框，放入可用頁框串列中。</p> <h1><span>Slab 記憶體配置器</span></h1> <p>當 Linux 需要配置的是小量的記憶體 (像是 malloc 所需的記憶體) 時，採用的是一種稱為 Slab Allocator 的配置器，其中被配置的資料稱為物件 (Object)。 Slab中的物件會被儲存在 Buddy 系統所分配的頁框中，假如要分配一個大小為 30 bytes 的物件時，Slab 會先向 Buddy 系統要求取得一個最小的分頁 (大小為4KB)，然後分配個 Slab 配置器。然後 Slab 配置器會保留一些位元以記錄配置資訊，然後將剩下的空間均分為大小30 的物件。於是當未來再有類似的配置請求時，就可以直接將這些空的物件配置出去。</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:io</guid>
				<title>Linux 的輸出入系統</title>
				<link>http://ccckmit.wikidot.com/lk:io</link>
				<description>

&lt;p&gt;Linux 的輸出入系統會透過硬體模組介面，以管理各式各樣的驅動程式。Linux 將硬體裝置分為『區塊、字元、網路』等三種類型，這三種類型的驅動程式都必須支援檔案存取的介面，因為在 Linux 當中裝置是以檔案的方式呈現的。像是 /dev/hda1, /dev/sda1, /dev/tty1 等，程式可以透過開檔 open()、讀檔 read()、寫檔 write() 的方式存取裝置，就像存取一個檔案一樣。&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 12 Jan 2010 07:36:19 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Linux 的輸出入系統會透過硬體模組介面，以管理各式各樣的驅動程式。Linux 將硬體裝置分為『區塊、字元、網路』等三種類型，這三種類型的驅動程式都必須支援檔案存取的介面，因為在 Linux 當中裝置是以檔案的方式呈現的。像是 /dev/hda1, /dev/sda1, /dev/tty1 等，程式可以透過開檔 open()、讀檔 read()、寫檔 write() 的方式存取裝置，就像存取一個檔案一樣。</p> <p>因此，所有的驅動程式必須支援檔案 (file) 的操作 (file_operations)，以便將裝置偽裝成檔案，以供作業系統與應用程式進行呼叫。這種將裝置偽裝成檔案的的方式，是從 UNIX 所承襲下來的一種相當成功的模式。</p> <p>字元類的裝置 (Character Device) 是較為簡單的，常見的字元裝置有鍵盤、滑鼠、印表機等，這些裝置所傳遞的並非一定要是字元訊息，只要可以用串流型式表式即可。因此字元裝置又被稱為串流裝置 (Stream Device)。字元裝置必須支援基本的檔案操作，像是 open(), read(), ioctl() 等。</p> <p>區塊裝置式形成檔案系統的基礎，除了基本的檔案操作外，區塊裝置還必須支援區塊性的操作 (block_device_operations)。而網路裝置由於必須支援網路定址等特性，因此成為一類獨立的裝置。舉例而言，網路裝置通常必須支援 TCP/IP，以形成網路子系統，因此具有獨特且複雜的操作。像是必須支援封包傳送機制、網路位址的ARP, RARP 協定、MAC Address 等，所以網路裝置的驅動程式也是最複雜的。</p> <p>由於2.6 版的 Linux 採用模組 (module) 的方式掛載驅動程式，因此必須先透過 module_init(xxx_init_module) 與 module_exit(xxx_cleanup_module) 的方式，將驅動程式掛載到 Linux 中。這種掛載的機制仍然是一種『註冊-反向呼叫機制』，但由於核心程式乃是先編譯好的，因此必須透過一個動態載入器將模組掛載到核心中，原理並不困難，但細節卻很繁瑣。範例 1 顯示了一個最基本的模組 HelloModule.c，該模組透過 module_init(hello_init) 與 module_exit(hello_exit) 兩行巨集，將hello_init() 與 hello_exit() 函數包裝成 linux 可使用的模組介面形式，讓模組載入器得以順利載入該模組。</p> <p>範例 1. 最基本的模組 (Module) 程式範例 – HelloModule.c</p> <div class="code"> <pre><code>#include &lt;linux/init.h&gt; 引用檔案 #include &lt;linux/module.h&gt; #include &lt;linux/kernel.h&gt; static int __init hello_init(void) { 模組起始函數 printk(KERN_ALERT &quot;hello_init()&quot;); 印出 hello_init() return 0; } static void __exit hello_exit(void) { 模組結束函數 printk(KERN_ALERT &quot;hello_exit()&quot;); 印出 hello_exit() } module_init(hello_init); 模組掛載(巨集) module_exit(hello_exit); 模組清除(巨集)</code></pre></div> <p>當您在 Linux 中撰寫了一個模組織之後，可以利用 gcc 編譯該模組，編譯成功後再利用 insmod指令將該模組掛載到核心中，最後透過 rmmod 指令將該模組從核心中移除。範例 2 顯示了這個操作過程。</p> <p>範例 2 模組的編譯、掛載與清除過程</p> <div class="code"> <pre><code>gcc -c -O -W -DMODULE -D__KERNEL__ HelloModule.c -o HelloModule.ko insmod ./helloModule.ko hello_init() rmmod ./helloModule hello_exit()</code></pre></div> <p>Linux 的驅動程式是一個具有特定結構的模組，必需將裝置的存取函數包裝成檔案存取的形式，讓裝置的存取就像檔案的存取一般。在驅動程式的內部，仍然必須採用『註冊-反向呼叫』機制，將這些存取函數掛載到檔案系統當中，然後就可以透過檔案操作的方式讀取 (read) 或寫入 (write) 這些裝置，或者透過 ioctl() 函數操控這些裝置。範例 3 顯示了 Linux 當中裝置驅動程式的結構，該範例是一個字元裝置 device1 的驅動程式片段，其中的 Linux 中的cdev 是字元裝置結構 (struct)，該驅動程式必需實作出 device1_read(), device1_write(), device1_ioctl(), device1_open(), device1_release() 等檔案操作函數，然後封裝在 file_operations 結構的 device1_fops 變數中，透過指令 cdev_init(&amp;dev-&gt;cdev, &amp;device1_fops) 向 Linux 註冊，以將這些實作函數掛載到 Linux 系統中。</p> <p>範例 3 Linux 中裝置驅動程式的結構範例</p> <div class="code"> <pre><code>int device1_init_module(void) { 模組掛載函數 register_chrdev_region(dev, 1, &quot;device1&quot;); 設定裝置代號 … device1_devices = kmalloc(…); 分配記憶體(slab) … cdev_init(&amp;dev-&gt;cdev, &amp;device1_fops); 註冊檔案操作函數群fops dev-&gt;cdev.owner = THIS_MODULE; dev-&gt;cdev.ops = &amp;device1_fops; err = cdev_add (&amp;dev-&gt;cdev, devno, 1); … fail: device1_cleanup_module(); 若掛載失敗則執行清除函數 } int device1_open(struct inode *inode, struct file *filp) {...} 裝置開啟函數 int device1_release(struct inode *inode, struct file *filp) {...} 裝置釋放函數 ssize_t device1_read(struct file *filp, …){...} 裝置讀取函數 ssize_t device1_write(struct file *filp,…) {...} 裝置寫入函數 int device1_ioctl(struct inode *inode, struct file *filp…) {…} 裝置控制函數 struct file_operations device1_fops = { 裝置的檔案操作函數群， .owner = THIS_MODULE, 包含 read(), write(), ioctl(), .read = device1_read, open(), release() 等。 .write = device1_write, .ioctl = device1_ioctl, .open = device1_open, .release = device1_release, }; module_init(device1_init_module); 模組掛載(巨集) module_exit(device1_cleanup_module); 模組清除(巨集)</code></pre></div> <p>走筆至此，我們已經介紹完 Linux 中的行程管理、記憶體管理、輸出入系統與檔案系統，完成了我們對 Linux 作業系統的介紹。但是 Linux 是個龐大的系統，筆者無法進行太詳細與深入的介紹，有興趣的讀者請進一步參考相關書籍。</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://ccckmit.wikidot.com/lk:dynamiclinking</guid>
				<title>Linux 的動態連結與載入 (Dynamic Linking)</title>
				<link>http://ccckmit.wikidot.com/lk:dynamiclinking</link>
				<description>

&lt;h1&gt;&lt;span&gt;簡介&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=296763&amp;amp;amp;size=small&amp;amp;amp;timestamp=1772930136&quot; alt=&quot;ccckmit&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=296763)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/ccckmit&quot;  &gt;ccckmit&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 14 Jan 2010 03:59:27 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>簡介</span></h1> <p>傳統的連結器會將所有程式連結成單一的執行檔，在執行時只需要該執行檔就能順利執行。但是，使用動態連結機制時，函式庫可以先不需要被連結進來，而是在需要的時候才透過動態連結器 (Dynamic Linker) 尋找並連結函式庫，這種方式可以不用載入全部的程式，因此可以節省記憶體。當很多執行中的程式共用到函式庫時，動態連結所節省的記憶體數量就相當可觀。</p> <p>除了節省記憶體之外，動態連結技術還可以節省編譯、組譯、連結所花費的時間。這是因為動態連結函式庫 (Dynamic Linking Libraries: DLLs) 可以單獨被編譯、組譯與連結，程式設計師不需要在改了某個函式庫後就重新連結所有程式。因此，對程式開發人員而言，動態連結技術可以節省程式開發的時間，因為程式設計人員使用編譯、組譯與連結器的次數往往非常頻繁，有些人甚至不到一分鐘就會編譯一次。</p> <p>除此之外，動態連結函式庫由於可以單獨重新編譯，因此，一但編譯完新版本後，就可以直接取代舊版本。這讓舊程式得以不需重新編譯就可以連結到新函式庫，因此，只要我們將動態連結函式庫換掉，即使功能不完全相同，只要函式庫名稱一樣，舊程式仍可順利執行該新版的函數，這讓動態函式庫變成可任意抽換的。這種可抽換性對程式開發人員而言，同時具有優點與缺點。</p> <p>動態連結器的任務，就是在需要的時候才載入動態函式庫，並且進行連結 (linking) 與重新定位 (relocation) 的動作。然後再執行該函式庫中的函數。</p> <p>當程式第一次執行到動態函數時，動態連結器會搜尋看看該函數是否已經在記憶體中，如果有則會跳到該函數執行，如果沒有則會呼叫載入器，動態的將該函式庫載入到記憶體，然後才執行該函數。這種函式庫被稱為動態連結函式庫 (Dynamic Linking Library)，在 MS. Windows 中，這種函式庫的附檔名為 .dll ，而在 Linux 中，這種函式庫的附檔名通常為 .so (Shared Object)。</p> <p>使用動態連結機制呼叫函數時，通常會利用間接跳轉的方式，先跳入一個稱為 Stub 的程式中，然後在第一次呼叫時，該Stub 會呼叫動態載入器載入該函數，而在第二次以後，則會直接跳入該函數。</p> <div class="image-container aligncenter"><img src="http://ccckmit.wdfiles.com/local--files/lk:dynamiclinking/LinuxDLLstub.jpg" alt="LinuxDLLstub.jpg" class="image" /></div> <p style="text-align: center;">圖 1 動態連結機制的實作方式</p> <p>圖 1 所顯示的動態跳轉機制，其關鍵是利用動態連函數區 (Stub) 作為跳轉點。在主程式中，呼叫動態函數是透過 Stub 區中的 f1, f2, f3 等函數標記，但是，這些標記區域包含了一段段的Stub小程式，這些小程式會決定是要直接跳轉，或者是呼叫動態載入器。</p> <p>在程式剛載入之時，Ptr_f1,Ptr_f2,Ptr_f3 等用來儲存動態函數位址的變數，會被填入 DL_f1, DL_f2, DL_f3 等位址。當主程式執行 CALL f2@PLT 指令時，會跳到Stub區的 f2 標記，此時，會執行 LD PC, Ptr_f2@GOT 這個指令，但是由於 Ptr_f2 當中儲存的是 DL_f2 的位址，因此，該LD跳轉指令相當於沒有作用，於是會繼續呼叫動態連結器 (Dlinker) 去載入 f2 對應的函數到記憶體中，f2_in_memory 顯示了載入完成後的動態函數。</p> <p>一但 f2 的動態函數f2_in_memory被載入後，Dlinker 會將 f2_in_memory 填入到 Ptr_f2 當中。於是，當下一次主程式再呼叫 CALL f2@PLT 時，Stub 區中的 LD PC, Ptr_f2@GOT 就會直接跳入動態函數 f2_in_memory 中，而不會再透過載入器了。</p> <h1><span>動態連結的優缺點</span></h1> <p>動態連結函式庫通常是與位置無關的程式碼 (Position Independent Code)，使用相對定址的方式。否則，如果在動態連結時利用修改記錄修正函式庫的記憶體內容，會造成每個程式都在修正函式庫，就可能造成不一致的窘境。</p> <p>動態函式庫的優點是可任意抽換，不需刻意更新舊程式，系統隨時保持最新狀態。但是，這也可能造成『動態連結地獄』 (DLL hell) 的困境，因為，如果新的函式庫有錯，或者與舊的主程式不相容，那麼，原本執行正常的程式會突然出現錯誤，甚至無法使用。</p> <p>一但有了『動態連結技術』，就能很容易的實作出『動態載入技術』。所位的動態載入技術，是在程式中再決定要載入哪些函數的方法。舉例而言，我們可以讓使用者在程式中輸入某個參數，然後立刻用『動態載入技術』載入該函式庫執行。這會使得程式具有較大的彈性，因為，我們可以在必要的時候呼叫動態載入器，讓使用者決定要載入哪些函式庫。</p> <p>必須提醒讀者的是，雖然動態連結 已經是相當常見的功能，但是在UNIX/Linux 與 Windows中卻有不同的稱呼，在 Windows 中直接稱為 DLLs (Dynamic Linking Libraries)，其附檔名通常為 .dll，而在UNIX/Linux 中的動態連結函式庫則被稱為 Share Objects，其附檔名通常是 .so。</p> <h1><span>動態載入技術</span></h1> <p>動態連結雖然不需要事先載入函式庫，但是在編譯時就已經知道動態函數的名稱與參數類型，因此，編譯器可以事先檢查函數型態的相容性。但是，有一種稱為動態載入的技術，允許程式設計人員在程式執行的過程中，動態決定要載入哪個函式庫，要執行哪個函數，這種技術比動態連結更具有彈性，靈活度也更高。其方法是讓程式可以呼叫載入器，以便動態的載入程式，因此才被稱為動態載入技術 。</p> <p>舉例而言，Linux 當中的系統呼叫 execve()，就是動態載入技術的一個簡單範例。當我們呼叫 execve() 以載入程式時，就是利用了載入器將某個程式載入記憶體當中執行。範例 1 就顯示了一個使用 execve() 載入 ls 檔案的程式，該程式會顯示etc資料夾中passwd的檔案屬性。</p> <p>範例 1. 使用 execve 呼叫載入器的範例</p> <div class="code"> <pre><code>#include &lt;unistd.h&gt; int main() { char *argv[]={&quot;ls&quot;,&quot;-al&quot;,&quot;/etc/passwd&quot;,(char *)0}; char *envp[]={&quot;PATH=/bin&quot;,0}; execve(&quot;/bin/ls&quot;,argv,envp); }</code></pre></div> <p>然而，範例 1 只是一個簡單的動態載入功能，execve 函數能做到的功能相當的有限，如果我們使用 UNIX/Linux 中的 libdl.so 這的函式庫，那可以做到較為先進的動態載入功能，像是連續呼叫同一個函式庫，或取得函式庫中的變數值等等。</p> <p>表格 1 顯示了這兩個平台的動態載入函式庫對照表。在 UNIX/Linux 當中，『dl』 函式庫可支援動態載入功能，其引用檔為 dlfcn.h，函式庫的目的檔為 libdl.so，可以用 dlopen() 載入動態函式庫，然後用 dlsym() 取得函數指標，最後用 dlclose() 函數關閉函式庫。</p> <p>而在 MS. Windows 當中，動態函式庫直接內建在核心當中，其引用檔為 windows.h，動態連結的目的檔為 Kernel32.dll，可以使用 LoadLibrary() 與 LoadLibraryEx() 等函數載入動態函式庫，然後用 GetProcAddress 取得函數位址，最後用 FreeLibrary() 關閉動態函式庫。</p> <p style="text-align: center;">表格 1 Linux 與 MS. Windows 中動態載入函式庫的對照比較表</p> <table class="wiki-content-table"> <tr> <td>使用方法</td> <td>~ UNIX/Linux</td> <td>~Windows</td> </tr> <tr> <td>引入檔</td> <td>#include &lt;dlfcn.h&gt;</td> <td>#include &lt;windows.h&gt;</td> </tr> <tr> <td>函式庫檔</td> <td>libdl.so</td> <td>Kernel32.dll</td> </tr> <tr> <td>載入功能</td> <td>dlopen</td> <td>LoadLibrary, LoadLibraryEx</td> </tr> <tr> <td>取得函數</td> <td>dlsym</td> <td>GetProcAddress</td> </tr> <tr> <td>關閉功能</td> <td>dlclose</td> <td>FreeLibrary</td> </tr> </table> <p>範例 2 顯示了 Linux 當中使用動態載入函式庫的程式範例，該程式使用 dlopen 載入數學函式庫 libm.so 目的檔，然後用 dlsym 取得 cos() 函數的指標，接著呼叫該函數印出 cos(2.0) 的值，最後用 dlclose() 關閉函式庫。</p> <p>範例 2. Linux 動態載入函式庫的使用範例</p> <div class="code"> <pre><code>// 程式：dlcall.c , 編譯指令：gcc -o dl_call dl_call.c –ldl #include &lt;dlfcn.h&gt; // 引用dlfcn.h動態函式庫 int main(void) { void *handle = dlopen (&quot;libm.so&quot;, RTLD_LAZY); // 開啟 shared library 'libm' double (*cosine)(double); // 宣告cos()函數的變數 cosine = dlsym(handle, &quot;cos&quot;); // 找出cos()函數的記憶體位址 printf (&quot;%f\n&quot;, (*cosine)(2.0)); // 呼叫cos()函數 dlclose(handle); // 關閉函式庫 return 0; }</code></pre></div> <p>為了支援『動態連結』與『動態載入』的功能，動態函式庫的目的檔當中，通常不會使用絕對定址等方式，而會使用與位置無關的編碼方式(Position-independent code)，對於支援動態重定位 (具有虛擬位址) 的機器而言，可以使用基底暫存器 (base register) 作為定址基底，然後利用基底定址法 (base addressing mode)達成與位置無關的編碼方式。透過適當的分段，以及虛擬位址技術，還可以保護這些區段不被竄改，由於虛擬位址的主題與作業系統的設計密切相關，有興趣者請進一步閱讀作業系統的主題。</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/ccckmit" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=296763&amp;amp;size=small&amp;amp;timestamp=1772930136" alt="ccckmit" style="background-image:url(http://www.wikidot.com/userkarma.php?u=296763)" /></a><a href="http://www.wikidot.com/user:info/ccckmit" >ccckmit</a></span></p> 
				 	]]>
				</content:encoded>							</item>
				</channel>
</rss>