A. uc/os操作系統是怎樣啟動的
uc/os和uclinux操作系統是兩種性能優良源碼公開且被廣泛應用的的免費嵌入
式操作系統,可以作為研究實時操作系統和非實時操作系統的典範。本文通過對
uc/os和uclinux的對比,分析和總結了嵌入式操作系統應用中的若乾重要問題,
歸納了嵌入式系統開發中操作系統的選型依據。
兩種開源嵌入式操作系統介紹
uc/os和uclinux操作系統,是當前得到廣泛應用的兩種免費且公開源碼的嵌入
式操作系統。uc/os適合小型控制系統,具有執行效率高、佔用空間小、實時性
能優良和可擴展性強等特點,最小內核可編譯至2k。uclinux則是繼承標准linux
的優良特性,針對嵌入式處理器的特點設計的一種操作系統,具有內嵌網路協議、
支持多種文件系統,開發者可利用標准linux先驗知識等優勢。其編譯後目標文
件可控制在幾百k量級。
uc/os是一種免費公開源代碼、結構小巧、具有可剝奪實時內核的實時操作系統。
其內核提供任務調度與管理、時間管理、任務間同步與通信、內存管理和中斷服
務等功能。
uclinux是一種優秀的嵌入式linux版本。uclinux是micro-conrol-linux的縮寫。
同標准linux相比,它集成了標准linux操作系統的穩定性、強大網路功能和出
色的文件系統等主要優點。但是由於沒有mmu(內存管理單元),其多任務的實
現需要一定技巧。
兩種嵌入式操作系統主要性能比較
嵌入式操作系統是嵌入式系統軟硬體資源的控制中心,它以盡量合理的有效方法
組織多個用戶共享嵌入式系統的各種資源。其中用戶指的是系統程序之上的所有
軟體。所謂合理有效的方法,指的就是操作系統如何協調並充分利用硬體資源來
實現多任務。復雜的操作系統都支持文件系統,方便組織文件並易於對其規范化
操作。
嵌入式操作系統還有一個特點就是針對不同的平台,系統不是直接可用的,一般
需要經過針對專門平台的移植操作系統才能正常工作。
進程調度、文件系統支持和系統移植是在嵌入式操作系統實際應用中最常見的問
題,下文就從這幾個角度入手對uc/os和uclinux進行分析比較。
進程調度
任務調度主要是協調任務對計算機系統內資源(如內存、i/o設備、cpu)的爭奪
使用。進程調度又稱為cpu調度,其根本任務是按照某種原則為處於就緒狀態
的進程分配cpu。由於嵌入式系統中內存和i/o設備一般都和cpu同時歸屬於
某進程,所以任務調度和進程調度概念相近,很多場合不加區分,下文中提到的
任務其實就是進程的概念。
進程調度可分為"剝奪型調度"和"非剝奪型調度"兩種基本方式。所謂"非剝奪型
調度"是指:一旦某個進程被調度執行,則該進程一直執行下去直至該進程結束,
或由於某種原因自行放棄cpu進入等待狀態,才將cpu重新分配給其他進程。
所謂"剝奪型調度"是指:一旦就緒狀態中出現優先權更高的進程,或者運行的進
程已用滿了規定的時間片時,便立即剝奪當前進程的運行(將其放回就緒狀態),
把cpu分配給其他進程。
作為實時操作系統,uc/os是採用的可剝奪型實時多任務內核。可剝奪型的實時
內核在任何時候都運行就緒了的最高優先順序的任務。uc/os中最多可以支持64
個任務,分別對應優先順序0"63,其中0為最高優先順序。調度工作的內容可以分
為兩部分:最高優先順序任務的尋找和任務切換。
其最高優先順序任務的尋找是通過建立就緒任務表來實現的。uc/os中的每一個任
務都有獨立的堆棧空間,並有一個稱為任務控制塊tcb(task control block)數據
結構,其中第一個成員變數就是保存的任務堆棧指針。任務調度模塊首先用變數
ostcbhighrdy記錄當前最高級就緒任務的tcb地址,然後調用os_task_sw()
函數來進行任務切換。
uclinux的進程調度沿用了linux的傳統,系統每隔一定時間掛起進程,同時系
統產生快速和周期性的時鍾計時中斷,並通過調度函數(定時器處理函數)決定進
程什麼時候擁有它的時間片。然後進行相關進程切換,這是通過父進程調用fork
函數生成子進程來實現的。
uclinux系統fork調用完成後,要麼子進程代替父進程執行(此時父進程已經
sleep),直到子進程調用exit退出;要麼調用exec執行一個新的進程,這個時候
產生可執行文件的載入,即使這個進程只是父進程的拷貝,這個過程也不可避免。
當子進程執行exit或exec後,子進程使用wakeup把父進程喚醒,使父進程繼續
往下執行。
uclinux由於沒有mmu管理存儲器,其對內存的訪問是直接的,所有程序中訪
問的地址都是實際的物理地址。操作系統隊內存空間沒有保護,各個進程實際上
共享一個運行空間。這就需要實現多進程時進行數據保護,也導致了用戶程序使
用的空間可能佔用到系統內核空間,這些問題在編程時都需要多加註意,否則容
易導致系統崩潰。
由上述分析可以得知,uc/os內核是針對實時系統的要求設計實現的,相對簡單,
可以滿足較高的實時性要求。而uclinux則在結構上繼承了標准linux的多任務
實現方式,僅針對嵌入式處理器特點進行改良。其要實現實時性效果則需要使系
統在實時內核的控制下運行,rt-linux就是可以實現這一個功能的一種實時內
核。
文件系統
所謂文件系統是指負責存取和管理文件信息的機構,也可以說是負責文件的建
立、撤銷、組織、讀寫、修改、復制及對文件管理所需要的資源(如目錄表、存
儲介質等)實施管理的軟體部分。
uc/os是面向中小型嵌入式系統的,如果包含全部功能(信號量、消息郵箱、消
息隊列及相關函數),編譯後的uc/os內核僅有6"10kb,所以系統本身並沒有
對文件系統的支持。但是uc/os具有良好的擴展性能,如果需要的話也可自行
加入文件系統的內容。
uclinux則是繼承了linux完善的文件系統性能。其採用的是romfs文件系統,
這種文件系統相對於一般的ext2文件系統要求更少的空間。空間的節約來自於
兩個方面,首先內核支持romfs文件系統比支持ext2文件系統需要更少的代碼,
其次romfs文件系統相對簡單,在建立文件系統超級塊(superblock)需要更少的存
儲空間。romfs文件系統不支持動態擦寫保存,對於系統需要動態保存的數據采
用虛擬ram盤的方法進行處理(ram盤將採用ext2文件系統)。
uclinux還繼承了linux網路操作系統的優勢,可以很方便的支持網路文件系統
且內嵌tcp/ip協議,這為uclinux開發網路接入設備提供了便利。
由兩種操作系統對文件系統的支持可知,在復雜的需要較多文件處理的嵌入式系
統中uclinux是一個不錯的選擇。而uc/os則主要適合一些控制系統。
操作系統的移植
嵌入式操作系統移植的目的是指使操作系統能在某個微處理器或微控制器上運
行。uc/os和uclinux都是源碼公開的操作系統,且其結構化設計便於把與處理
器相關的部分分離出來,所以被移植到新的處理器上是可能的。
以下對兩種系統的移植分別予以說明。
(1)uc/os的移植
要移植uc/os,目標處理器必須滿足以下要求;
·處理器的c編譯器能產生可重入代碼,且用c語言就可以打開和關閉中斷;
·處理器支持中斷,並能產生定時中斷;
·處理器支持足夠的ram(幾k位元組),作為多任務環境下的任務堆棧;
·處理器有將堆棧指針和其他cpu寄存器讀出和存儲到堆棧或內存中的指令。
在理解了處理器和c編譯器的技術細節後,uc/os的移植只需要修改與處理器
相關的代碼就可以了。具體有如下內容:
·os_cpu.h中需要設置一個常量來標識堆棧增長方向;
·os_cpu.h中需要聲明幾個用於開關中斷和任務切換的宏;
·os_cpu.h中需要針對具體處理器的字長重新定義一系列數據類型;
·os_cpu_a.asm需要改寫4個匯編語言的函數;
·os_cpu_c.c需要用c語言編寫6個簡單函數;
·修改主頭文件include.h,將上面的三個文件和其他自己的頭文件加入。
(2)uclinux的移植
由於uclinux其實是linux針對嵌入式系統的一種改良,其結構比較復雜,相對
uc/os,uclinux的移植也復雜得多。一般而言要移植uclinux,目標處理器除了
應滿足上述uc/os應滿足的條件外,還需要具有足夠容量(幾百k位元組以上)外
部rom和ram。
uclinux的移植大致可以分為3個層次:
·結構層次的移植,如果待移植處理器的結構不同於任何已經支持的處理器結構,
則需要修改linux/arch目錄下相關處理器結構的文件。雖然uclinux內核代碼的
大部分是獨立於處理器和其體系結構的,但是其最低級的代碼也是特定於各個系
統的。這主要表現在它們的中斷處理上下文、內存映射的維護、任務上下文和初
始化過程都是獨特的。這些例行程序位於linux/arch/目錄下。由於linux所支持
體系結構的種類繁多,所以對一個新型的體系,其低級常式可以模仿與其相似的
體系常式編寫。
·平台層次的移植,如果待移植處理器是某種uclinux已支持體系的分支處理器,
則需要在相關體系結構目錄下建立相應目錄並編寫相應代碼。如mc68ez328就
是基於無mmu的m68k內核的。此時的移植需要創建
linux/arch/m68knommu/platform/ mc68ez328目錄並在其下編寫跟蹤程序(實現
用戶程序到內核函數的介面等功能)、中斷控制調度程序和向量初始化程序等。
·板級移植,如果你所用處理器已被uclinux支持的話,就只需要板級移植了。板
級移植需要在linux/arch/?platform/中建立一個相應板的目錄,再在其中建立相應
的啟動代碼crt0_rom.s或crt0_ram.s和鏈接描述文檔rom.ld或ram.ld就可以了。
板級移植還包括驅動程序的編寫和環境變數設置等內容。
通過對uc/os和uclinux的比較,可以看出這兩種操作系統在應用方面各有優劣。
uc/os佔用空間少,執行效率高,實時性能優良,且針對新處理器的移植相對簡
單。uclinux則佔用空間相對較大,實時性能一般,針對新處理器的移植相對復
雜。但是,uclinux具有對多種文件系統的支持能力、內嵌了tcp/ip協議,可
以借鑒linux豐富的資源,對一些復雜的應用,uclinux具有相當優勢。例如cisco
公司的 2500/3000/4000 路由器就是基於uclinux操作系統開發的。
總之,操作系統的選擇是由嵌入式系統的需求決定的。簡單的說就是,小型控制
系統可充分利用uc/os小巧且實時性強的優勢,如果開發pda和互聯網連接終
端等較為復雜的系統則uclinux是不錯的選擇。
B. 為什麼DEBUG版本正確,Release版本錯誤
一、Debug 和 Release 編譯方式的本質區別
Debug 通常稱為調試版本,它包含調試信息,並且不作任何優化,便於程序員調試程序。Release 稱為發布版本,它往往是進行了各種優化,使得程序在代碼大小和運行速度上都是最優的,以便用戶很好地使用。
Debug 和 Release 的真正秘密,在於一組編譯選項。下面列出了分別針對二者的選項(當然除此之外還有其他一些,如/Fd /Fo,但區別並不重要,通常他們也不會引起 Release 版錯誤,在此不討論)
Debug 版本:
/MDd /MLd 或 /MTd 使用 Debug runtime library(調試版本的運行時刻函數庫)
/Od 關閉優化開關
/D "_DEBUG" 相當於 #define _DEBUG,打開編譯調試代碼開關(主要針對
assert函數)
/ZI 創建 Edit and continue(編輯繼續)資料庫,這樣在調試過
程中如果修改了源代碼不需重新編譯
/GZ 可以幫助捕獲內存錯誤
/Gm 打開最小化重鏈接開關,減少鏈接時間
Release 版本:
/MD /ML 或 /MT 使用發布版本的運行時刻函數庫
/O1 或 /O2 優化開關,使程序最小或最快
/D "NDEBUG" 關閉條件編譯調試代碼開關(即不編譯assert函數)
/GF 合並重復的字元串,並將字元串常量放到只讀內存,防止
被修改
實際上,Debug 和 Release 並沒有本質的界限,他們只是一組編譯選項的集合,編譯器只是按照預定的選項行動。事實上,我們甚至可以修改這些選項,從而得到優化過的調試版本或是帶跟蹤語句的發布版本。
二、哪些情況下 Release 版會出錯
有了上面的介紹,我們再來逐個對照這些選項看看 Release 版錯誤是怎樣產生的
1. Runtime Library:鏈接哪種運行時刻函數庫通常只對程序的性能產生影響。調試版本的 Runtime Library 包含了調試信息,並採用了一些保護機制以幫助發現錯誤,因此性能不如發布版本。編譯器提供的 Runtime Library 通常很穩定,不會造成 Release 版錯誤;倒是由於 Debug 的 Runtime Library 加強了對錯誤的檢測,如堆內存分配,有時會出現 Debug 有錯但 Release 正常的現象。應當指出的是,如果 Debug 有錯,即使 Release 正常,程序肯定是有 Bug 的,只不過可能是 Release 版的某次運行沒有表現出來而已。
2. 優化:這是造成錯誤的主要原因,因為關閉優化時源程序基本上是直接翻譯的,而打開優化後編譯器會作出一系列假設。這類錯誤主要有以下幾種:
(1) 幀指針(Frame Pointer)省略(簡稱 FPO ):在函數調用過程中,所有調用信息(返回地址、參數)以及自動變數都是放在棧中的。若函數的聲明與實現不同(參數、返回值、調用方式),就會產生錯誤————但 Debug 方式下,棧的訪問通過 EBP 寄存器保存的地址實現,如果沒有發生數組越界之類的錯誤(或是越界「不多」),函數通常能正常執行;Release 方式下,優化會省略 EBP 棧基址指針,這樣通過一個全局指針訪問棧就會造成返回地址錯誤是程序崩潰。C++ 的強類型特性能檢查出大多數這樣的錯誤,但如果用了強制類型轉換,就不行了。你可以在 Release 版本中強制加入 /Oy- 編譯選項來關掉幀指針省略,以確定是否此類錯誤。此類錯誤通常有:
● MFC 消息響應函數書寫錯誤。正確的應為
afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam);
ON_MESSAGE 宏包含強制類型轉換。防止這種錯誤的方法之一是重定義 ON_MESSAGE 宏,把下列代碼加到 stdafx.h 中(在#include "afxwin.h"之後),函數原形錯誤時編譯會報錯
#undef ON_MESSAGE
#define ON_MESSAGE(message, memberFxn) { message, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
(2) volatile 型變數:volatile 告訴編譯器該變數可能被程序之外的未知方式修改(如系統、其他進程和線程)。優化程序為了使程序性能提高,常把一些變數放在寄存器中(類似於 register 關鍵字),而其他進程只能對該變數所在的內存進行修改,而寄存器中的值沒變。如果你的程序是多線程的,或者你發現某個變數的值與預期的不符而你確信已正確的設置了,則很可能遇到這樣的問題。這種錯誤有時會表現為程序在最快優化出錯而最小優化正常。把你認為可疑的變數加上 volatile 試試。
(3) 變數優化:優化程序會根據變數的使用情況優化變數。例如,函數中有一個未被使用的變數,在 Debug 版中它有可能掩蓋一個數組越界,而在 Release 版中,這個變數很可能被優化調,此時數組越界會破壞棧中有用的數據。當然,實際的情況會比這復雜得多。與此有關的錯誤有:
● 非法訪問,包括數組越界、指針錯誤等。例如
void fn(void)
{
int i;
i = 1;
int a[4];
{
int j;
j = 1;
}
a[-1] = 1;//當然錯誤不會這么明顯,例如下標是變數
a[4] = 1;
}
j 雖然在數組越界時已出了作用域,但其空間並未收回,因而 i 和 j 就會掩蓋越界。而 Release 版由於 i、j 並未其很大作用可能會被優化掉,從而使棧被破壞。
3. _DEBUG 與 NDEBUG :當定義了 _DEBUG 時,assert() 函數會被編譯,而 NDEBUG 時不被編譯。除此之外,VC++中還有一系列斷言宏。這包括:
ANSI C 斷言 void assert(int expression );
C Runtime Lib 斷言 _ASSERT( booleanExpression );
_ASSERTE( booleanExpression );
MFC 斷言 ASSERT( booleanExpression );
VERIFY( booleanExpression );
ASSERT_VALID( pObject );
ASSERT_KINDOF( classname, pobject );
ATL 斷言 ATLASSERT( booleanExpression );
此外,TRACE() 宏的編譯也受 _DEBUG 控制。
所有這些斷言都只在 Debug版中才被編譯,而在 Release 版中被忽略。唯一的例外是 VERIFY() 。事實上,這些宏都是調用了 assert() 函數,只不過附加了一些與庫有關的調試代碼。如果你在這些宏中加入了任何程序代碼,而不只是布爾表達式(例如賦值、能改變變數值的函數調用 等),那麼 Release 版都不會執行這些操作,從而造成錯誤。初學者很容易犯這類錯誤,查找的方法也很簡單,因為這些宏都已在上面列出,只要利用 VC++ 的 Find in Files 功能在工程所有文件中找到用這些宏的地方再一一檢查即可。另外,有些高手可能還會加入 #ifdef _DEBUG 之類的條件編譯,也要注意一下。
順便值得一提的是 VERIFY() 宏,這個宏允許你將程序代碼放在布爾表達式里。這個宏通常用來檢查 Windows API 的返回值。有些人可能為這個原因而濫用 VERIFY() ,事實上這是危險的,因為 VERIFY() 違反了斷言的思想,不能使程序代碼和調試代碼完全分離,最終可能會帶來很多麻煩。因此,專家們建議盡量少用這個宏。
4. /GZ 選項:這個選項會做以下這些事
(1) 初始化內存和變數。包括用 0xCC 初始化所有自動變數,0xCD ( Cleared Data ) 初始化堆中分配的內存(即動態分配的內存,例如 new ),0xDD ( Dead Data ) 填充已被釋放的堆內存(例如 delete ),0xFD( deFencde Data ) 初始化受保護的內存(debug 版在動態分配內存的前後加入保護內存以防止越界訪問),其中括弧中的詞是微軟建議的助記詞。這樣做的好處是這些值都很大,作為指針是不可能的(而且 32 位系統中指針很少是奇數值,在有些系統中奇數的指針會產生運行時錯誤),作為數值也很少遇到,而且這些值也很容易辨認,因此這很有利於在 Debug 版中發現 Release 版才會遇到的錯誤。要特別注意的是,很多人認為編譯器會用 0 來初始化變數,這是錯誤的(而且這樣很不利於查找錯誤)。
(2) 通過函數指針調用函數時,會通過檢查棧指針驗證函數調用的匹配性。(防止原形不匹配)
(3) 函數返回前檢查棧指針,確認未被修改。(防止越界訪問和原形不匹配,與第二項合在一起可大致模擬幀指針省略 FPO )
通常 /GZ 選項會造成 Debug 版出錯而 Release 版正常的現象,因為 Release 版中未初始化的變數是隨機的,這有可能使指針指向一個有效地址而掩蓋了非法訪問。
除此之外,/Gm /GF 等選項造成錯誤的情況比較少,而且他們的效果顯而易見,比較容易發現。
三、怎樣「調試」 Release 版的程序
遇到 Debug 成功但 Release 失敗,顯然是一件很沮喪的事,而且往往無從下手。如果你看了以上的分析,結合錯誤的具體表現,很快找出了錯誤,固然很好。但如果一時找不出,以下給出了一些在這種情況下的策略。
1. 前面已經提過,Debug 和 Release 只是一組編譯選項的差別,實際上並沒有什麼定義能區分二者。我們可以修改 Release 版的編譯選項來縮小錯誤范圍。如上所述,可以把 Release 的選項逐個改為與之相對的 Debug 選項,如 /MD 改為 /MDd、/O1 改為 /Od,或運行時間優化改為程序大小優化。注意,一次只改一個選項,看改哪個選項時錯誤消失,再對應該選項相關的錯誤,針對性地查找。這些選項在 Project\Settings... 中都可以直接通過列表選取,通常不要手動修改。由於以上的分析已相當全面,這個方法是最有效的。
2. 在編程過程中就要時常注意測試 Release 版本,以免最後代碼太多,時間又很緊。
3. 在 Debug 版中使用 /W4 警告級別,這樣可以從編譯器獲得最大限度的錯誤信息,比如 if( i =0 )就會引起 /W4 警告。不要忽略這些警告,通常這是你程序中的 Bug 引起的。但有時 /W4 會帶來很多冗餘信息,如 未使用的函數參數 警告,而很多消息處理函數都會忽略某些參數。我們可以用
#progma warning(disable: 4702) //禁止
//...
#progma warning(default: 4702) //重新允許
來暫時禁止某個警告,或使用
#progma warning(push, 3) //設置警告級別為 /W3
//...
#progma warning(pop) //重設為 /W4
來暫時改變警告級別,有時你可以只在認為可疑的那一部分代碼使用 /W4。
4.你也可以像 Debug 一樣調試你的 Release 版,只要加入調試符號。在 Project/Settings... 中,選中 Settings for "Win32 Release",選中 C/C++ 標簽,Category 選 General,Debug Info 選 Program Database。再在 Link 標簽 Project options 最後加上 "/OPT:REF" (引號不要輸)。這樣調試器就能使用 pdb 文件中的調試符號。但調試時你會發現斷點很難設置,變數也很難找到——這些都被優化過了。不過令人慶幸的是,Call Stack 窗口仍然工作正常,即使幀指針被優化,棧信息(特別是返回地址)仍然能找到。這對定位錯誤很有幫助。
C. 如果通了uC/OS II,對學習LINUX操作系統有多少幫助
uc/os和uclinux操作系統是兩種性能優良源碼公開且被廣泛應用的的免費嵌入 式操作系統,可以作為研究實時操作系統和非實時操作系統的典範。本文通過對 uc/os和uclinux的對比,分析和總結了嵌入式操作系統應用中的若乾重要問題, 歸納了嵌入式系統開發中操作系統的選型依據。 兩種開源嵌入式操作系統介紹 uc/os和uclinux操作系統,是當前得到廣泛應用的兩種免費且公開源碼的嵌入 式操作系統。uc/os適合小型控制系統,具有執行效率高、佔用空間小、實時性 能優良和可擴展性強等特點,最小內核可編譯至2k。uclinux則是繼承標准linux 的優良特性,針對嵌入式處理器的特點設計的一種操作系統,具有內嵌中國絡協議、 支持多種文件系統,開發者可利用標准linux先驗知識等優勢。其編譯後目標文 件可控制在幾百k量級。 uc/os是一種免費公開源代碼、結構小巧、具有可剝奪實時內核的實時操作系統。 其內核提供任務調度與管理、時間管理、任務間同步與通信、內存管理和中斷服 務等功能。 uclinux是一種優秀的嵌入式linux版本。uclinux是micro-conrol-linux的縮寫。 同標准linux相比,它集成了標准linux操作系統的穩定性、強大中國絡功能和出 色的文件系統等主要優點。但是由於沒有mmu(內存管理單元),其多任務的實 現需要一定技巧。 兩種嵌入式操作系統主要性能比較 嵌入式操作系統是嵌入式系統軟硬體資源的控制中心,它以盡量合理的有效方法 組織多個用戶共享嵌入式系統的各種資源。其中用戶指的是系統程序之上的所有 軟體。所謂合理有效的方法,指的就是操作系統如何協調並充分利用硬體資源來 實現多任務。復雜的操作系統都支持文件系統,方便組織文件並易於對其規范化 操作。 嵌入式操作系統還有一個特點就是針對不同的平台,系統不是直接可用的,一般 需要經過針對專門平台的移植操作系統才能正常工作。 進程調度、文件系統支持和系統移植是在嵌入式操作系統實際應用中最常見的問 題,下文就從這幾個角度入手對uc/os和uclinux進行分析比較。 進程調度 任務調度主要是協調任務對計算機系統內資源(如內存、i/o設備、cpu)的爭奪 使用。進程調度又稱為cpu調度,其根本任務是按照某種原則為處於就緒狀態 的進程分配cpu。由於嵌入式系統中內存和i/o設備一般都和cpu同時歸屬於 某進程,所以任務調度和進程調度概念相近,很多場合不加區分,下文中提到的 任務其實就是進程的概念。 進程調度可分為"剝奪型調度"和"非剝奪型調度"兩種基本方式。所謂"非剝奪型 調度"是指:一旦某個進程被調度執行,則該進程一直執行下去直至該進程結束, 或由於某種原因自行放棄cpu進入等待狀態,才將cpu重新分配給其他進程。 所謂"剝奪型調度"是指:一旦就緒狀態中出現優先權更高的進程,或者運行的進 程已用滿了規定的時間片時,便立即剝奪當前進程的運行(將其放回就緒狀態), 把cpu分配給其他進程。 作為實時操作系統,uc/os是採用的可剝奪型實時多任務內核。可剝奪型的實時 內核在任何時候都運行就緒了的最高優先順序的任務。uc/os中最多可以支持64 個任務,分別對應優先順序0"63,其中0為最高優先順序。調度工作的內容可以分 為兩部分:最高優先順序任務的尋找和任務切換。 其最高優先順序任務的尋找是通過建立就緒任務表來實現的。uc/os中的每一個任 務都有獨立的堆棧空間,並有一個稱為任務控制塊tcb(task control block)數據 結構,其中第一個成員變數就是保存的任務堆棧指針。任務調度模塊首先用變數 ostcbhighrdy記錄當前最高級就緒任務的tcb地址,然後調用os_task_sw() 函數來進行任務切換。 uclinux的進程調度沿用了linux的傳統,系統每隔一定時間掛起進程,同時系 統產生快速和周期性的時鍾計時中斷,並通過調度函數(定時器處理函數)決定進 程什麼時候擁有它的時間片。然後進行相關進程切換,這是通過父進程調用fork 函數生成子進程來實現的。 uclinux系統fork調用完成後,要麼子進程代替父進程執行(此時父進程已經 sleep),直到子進程調用exit退出;要麼調用exec執行一個新的進程,這個時候 產生可執行文件的載入,即使這個進程只是父進程的拷貝,這個過程也不可避免。 當子進程執行exit或exec後,子進程使用wakeup把父進程喚醒,使父進程繼續 往下執行。 uclinux由於沒有mmu管理存儲器,其對內存的訪問是直接的,所有程序中訪 問的地址都是實際的物理地址。操作系統隊內存空間沒有保護,各個進程實際上 共享一個運行空間。這就需要實現多進程時進行數據保護,也導致了用戶程序使 用的空間可能佔用到系統內核空間,這些問題在編程時都需要多加註意,否則容 易導致系統崩潰。 由上述分析可以得知,uc/os內核是針對實時系統的要求設計實現的,相對簡單, 可以滿足較高的實時性要求。而uclinux則在結構上繼承了標准linux的多任務 實現方式,僅針對嵌入式處理器特點進行改良。其要實現實時性效果則需要使系 統在實時內核的控制下運行,rt-linux就是可以實現這一個功能的一種實時內 核。 文件系統 所謂文件系統是指負責存取和管理文件信息的機構,也可以說是負責文件的建 立、撤銷、組織、讀寫、修改、復制及對文件管理所需要的資源(如目錄表、存 儲介質等)實施管理的軟體部分。 uc/os是面向中小型嵌入式系統的,如果包含全部功能(信號量、消息郵箱、消 息隊列及相關函數),編譯後的uc/os內核僅有6"10kb,所以系統本身並沒有 對文件系統的支持。但是uc/os具有良好的擴展性能,如果需要的話也可自行 加入文件系統的內容。 uclinux則是繼承了linux完善的文件系統性能。其採用的是romfs文件系統, 這種文件系統相對於一般的ext2文件系統要求更少的空間。空間的節約來自於 兩個方面,首先內核支持romfs文件系統比支持ext2文件系統需要更少的代碼, 其次romfs文件系統相對簡單,在建立文件系統超級塊(superblock)需要更少的存 儲空間。romfs文件系統不支持動態擦寫保存,對於系統需要動態保存的數據采 用虛擬ram盤的方法進行處理(ram盤將採用ext2文件系統)。 uclinux還繼承了linux中國絡操作系統的優勢,可以很方便的支持中國絡文件系統 且內嵌tcp/ip協議,這為uclinux開發中國絡接入設備提供了便利。 由兩種操作系統對文件系統的支持可知,在復雜的需要較多文件處理的嵌入式系 統中uclinux是一個不錯的選擇。而uc/os則主要適合一些控制系統。 操作系統的移植 嵌入式操作系統移植的目的是指使操作系統能在某個微處理器或微控制器上運 行。uc/os和uclinux都是源碼公開的操作系統,且其結構化設計便於把與處理 器相關的部分分離出來,所以被移植到新的處理器上是可能的。 以下對兩種系統的移植分別予以說明。 (1)uc/os的移植 要移植uc/os,目標處理器必須滿足以下要求; ·處理器的c編譯器能產生可重入代碼,且用c語言就可以打開和關閉中斷; ·處理器支持中斷,並能產生定時中斷; ·處理器支持足夠的ram(幾k位元組),作為多任務環境下的任務堆棧; ·處理器有將堆棧指針和其他cpu寄存器讀出和存儲到堆棧或內存中的指令。 在理解了處理器和c編譯器的技術細節後,uc/os的移植只需要修改與處理器 相關的代碼就可以了。具體有如下內容: ·os_cpu.h中需要設置一個常量來標識堆棧增長方向; ·os_cpu.h中需要聲明幾個用於開關中斷和任務切換的宏; ·os_cpu.h中需要針對具體處理器的字長重新定義一系列數據類型; ·os_cpu_a.asm需要改寫4個匯編語言的函數; ·os_cpu_c.c需要用c語言編寫6個簡單函數; ·修改主頭文件include.h,將上面的三個文件和其他自己的頭文件加入。 (2)uclinux的移植 由於uclinux其實是linux針對嵌入式系統的一種改良,其結構比較復雜,相對 uc/os,uclinux的移植也復雜得多。一般而言要移植uclinux,目標處理器除了 應滿足上述uc/os應滿足的條件外,還需要具有足夠容量(幾百k位元組以上)外 部rom和ram。 uclinux的移植大致可以分為3個層次: ·結構層次的移植,如果待移植處理器的結構不同於任何已經支持的處理器結構, 則需要修改linux/arch目錄下相關處理器結構的文件。雖然uclinux內核代碼的 大部分是獨立於處理器和其體系結構的,但是其最低級的代碼也是特定於各個系 統的。這主要表現在它們的中斷處理上下文、內存映射的維護、任務上下文和初 始化過程都是獨特的。這些例行程序位於linux/arch/目錄下。由於linux所支持 體系結構的種類繁多,所以對一個新型的體系,其低級常式可以模仿與其相似的 體系常式編寫。 ·平台層次的移植,如果待移植處理器是某種uclinux已支持體系的分支處理器, 則需要在相關體系結構目錄下建立相應目錄並編寫相應代碼。如mc68ez328就 是基於無mmu的m68k內核的。此時的移植需要創建 linux/arch/m68knommu/platform/ mc68ez328目錄並在其下編寫跟蹤程序(實現 用戶程序到內核函數的介面等功能)、中斷控制調度程序和向量初始化程序等。 ·板級移植,如果你所用處理器已被uclinux支持的話,就只需要板級移植了。板 級移植需要在linux/arch/?platform/中建立一個相應板的目錄,再在其中建立相應 的啟動代碼crt0_rom.s或crt0_ram.s和鏈接描述文檔rom.ld或ram.ld就可以了。 板級移植還包括驅動程序的編寫和環境變數設置等內容。 通過對uc/os和uclinux的比較,可以看出這兩種操作系統在應用方面各有優劣。 uc/os佔用空間少,執行效率高,實時性能優良,且針對新處理器的移植相對簡 單。uclinux則佔用空間相對較大,實時性能一般,針對新處理器的移植相對復 雜。但是,uclinux具有對多種文件系統的支持能力、內嵌了tcp/ip協議,可 以借鑒linux豐富的資源,對一些復雜的應用,uclinux具有相當優勢。例如cisco 公司的 2500/3000/4000 路由器就是基於uclinux操作系統開發的。 總之,操作系統的選擇是由嵌入式系統的需求決定的。簡單的說就是,小型控制 系統可充分利用uc/os小巧且實時性強的優勢,如果開發pda和互聯中國連接終 端等較為復雜的系統則uclinux是不錯的選擇。 本回答由電腦中國絡分類達人 李孝忠推薦
D. C或C++的a.out
方法非常的多,你可以閱讀下面這個地址的文章來了解可用的方法:
我下面把文本附在後面,但是是文本文件的,不然你的地址的文章看起來舒服
查看源文件預處理結果2010-06-05 19:16
編譯C/C++源代碼時,源代碼首先會被預處理器(preprocessor)進行預處理(preprocess)。
預處理器執行源代碼中的預處理指令,如:
——文件包含
#include
——條件編譯
#if、 #ifdef、 #ifndef、 #elif、 #else、 #endif
——宏
#define、 #undef、宏標識符、宏擴展
——其他
#error、#line、#pragma
……
預處理之後的結果(即將提交給編譯器)與程序員看到的源代碼也許會有很大的差異。
尤其在源代碼中含有許多錯綜復雜的宏與條件編譯時。
當我們被這些狂亂的宏與條件編譯折磨的時候, 如果能看到預處理的結果, 也許會有很大的幫助。
下面將以一個示例說明msvc與gcc中得到預處理結果的方式。
零、 示例
假設我們需要查看 _MSC_VER 與 __GUNC__ 兩個宏最終被擴展出的文本:
int main() {
int result =
#if defined(__GNUC__)
__GNUC__
#elif defined(_MSC_VER)
_MSC_VER
#else
#error unknown compiler
#endif
;
return result;
}
該程序很簡單, main函數返回一個result,然後立即退出。
而result的值, 根據條件編譯得到:
1. 如果是GCC編譯器, 那麼result賦值為__GNUC__
2. 否則如果是VC編譯器, 那麼result賦值為_MSC_VER
3. 否則是一個未知的編譯器, 錯誤
接下來, 我們來看看_MSC_VER與__GNUC__這2個宏最終到底被擴展為什麼文本。
--------------------------------------------------------------------------------
一、 GCC
1、 -E 選項
-E選項將把預處理的結果,寫入stdout。
也就是說, 執行如下命令:
gcc -E preporcess_only.c
就能在控制台中得到預處理後的結果,大致如下: # 1 "../preprocess_only.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "../preprocess_only.c"
int main() {
int result =3;
return result;
}
可以看到, __GUNC__ 宏最終被擴展為整數字面量3(GCC 3)。
如果源代碼很長, 輸出到命令行窗口中查看也許不方便。
如何將其輸出到一個文件中呢?
1.1、 重定向
因為-E是輸出到stdout, 顯然可以將其重定向到另一個文件, 如執行如下命令:
gcc -E preprocess_only.c >stdout.txt
那麼stdout.txt中, 就能得到剛才命令行窗口中的內容。
1.2、 -o (小寫) 選項
-o選項用於指定出文件名。
對於-c, -o指定的是目標文件名。
對於-S ,-o指定的是匯編文件名。
對於-E, -o自然也可以指定預處理文件名, 如執行如下命令:
gcc -E preprocess_only.c -o output.txt
那麼output.txt中會得到「一.1.1」中的stdout.txt和「一.1」中控制台窗口一樣的結果。
2、-save-temps 選項
-save-temps選項將保留中間文件:如預處理後的結果文件、匯編代碼文件與目標文件。
其中的預處理結果文件(通常有.i後綴)是我們所需要的。
舉例:
1、 gcc -save-temps -E preprocess_only.c
0個中間文件。
輸出預處理結果, 同「一.1」一樣, 輸出到控制台窗口中。
對比如下命令:
1.1、 gcc -save-temps -E preprocess_only.c -o temp_output.txt
1.2、 gcc -save-temps -E preprocess_only.c >temp_output.txt
可以看出, -E選項不產生中間文件。 預處理結果就是最終結果。
同時可以使用 -o選項或者重定向, 把結果保存到一個文件中。
2、 gcc -save-temps -S preprocess_only.c
1個中間文件: preprocess_only.i(預處理結果)
1個輸出文件:preprocess_only.s(匯編代碼)
對比如下命令:
2.1、 gcc -save-temps -S preprocess_only.c -o unknown
得到preprocess_only.i文件,內容是預處理結果,是中間文件。
得到unknown文件,內容是匯編代碼, 是最終結果文件。
3、 gcc -save-temps -c preprocess_only.c
2個中間文件: preprocess_only.i與preprocess_only.s。
1個輸出文件: preprocess_only.o(目標代碼)
對比如下命令:
3.1、 gcc -save-temps -c preprocess_only.c -o unknown
得到preprocess_only.i 與 preprocess_only.s文件,內容分別是預處理結果與匯編代碼,是中間結果。
unknown文件, 內容是目標代碼,是最終結果文件。
4、 gcc -save-temps preprocess_only.c
3個中間文件: preprocess_only.i、preprocess_only.s、preprocess_only.o。
1個輸出文件: a.out(a.exe with mingw)。
對比如下命令:
4.1、 gcc -save-temps preprocess_only.c -o what
得到上述3個文件, 是中間文件。
what文件(what.exe with mingw), 內容是可執行代碼, 是最終結果文件。
二、 MSVC
VC6、8、9中與查看預處理相關的選項可以通過如下命令查看:
cl /help
在輸出中, 找 -PREPROCESSOR- 這個類別。
其中與預處理結果相關的有如下一些選項:
二.1、/E 選項
/E preprocess to stdout
/E 將預處理定向到 stdout
顯然, 這和「一.1」是等價的, 如:
cl /E preprocess_only.c
在命令行窗口中將得到類似結果: #line 1 "preprocess_only.c"
int main() {
int result =
#line 6 "preprocess_only.c"
1200
#line 10 "preprocess_only.c" ;
return result;
}
可以看到, _MSC_VER宏最終被擴展為整數字面值1200(VC6)。
對於較長的源文件, 我們同樣希望將結果輸出到一個文件中。
二.1.1、重定向
執行:
cl /E preprocess_only.c >stdout.txt
stdout.txt將保存上面的結果。
注意: 在msvc中,沒有「一.1.2」的對應物。
執行:
cl /help
在輸出中找到-OUTPUT FILES-類別, 可以看到沒有命名預處理結果的方式。有兩個相似的選項:
/Fe 命名可執行文件。
/Fp 命名預編譯頭文件。
但不是我們需要的選項。
也許VC認為通過 「/E + 重定向」就可以達到命名輸出文件的目的。
所以就沒有設計達到此目的的另一種方法。
二.2、/P 選項
/P preprocess to file
/P 預處理到文件
執行:
cl /P preprocess_only.c
將得到 preprocess_only.i
/P會將對 xxx.suffix 的預處理結果輸出到 xxx.i 文件中。
沒有指定文件名的方式。 如果需要指定輸出文件名, 可以使用 「/E + 重定向」
二.3 /EP 選項
/E與/P選項都將保留一部分(源文件)行信息,如「二.1」所示。
如果這是不需要的, 可以使用 /EP選項。
/EP preprocess to stdout, no #line
/EP 預處理到標准輸出,沒有 #line
如:
cl /EP preprocess_only.c
將得到如下輸出:
int main() {
int result = 1200;
return result;
}
同樣, 如果需要輸出到指定文件, 可以使用重定向。
二.4 其他一些有趣的選項
1. /C (大寫)
don't strip comments(不抽出注釋)
如果保留注釋對理解預處理結果有幫助, 可以使用這個選項。
2. /U /u
/u remove all predefined macros
/u 移除所有預定義的宏
/U<name> remove predefined macro
/U<name> 移除預定義的宏
比如可以通過:
cl /u preprocess_only.c
cl /U_MSC_VER preprocess_only.c
來得到一個 unknown complier錯誤囧……
3. /D
/D<name><text> define macro
/D<name><text> 定義宏
可以通過:
cl /D__GUNC__=3 preprocess_only.c
來假裝gcc編譯器
E. Ubuntu編譯了新的內核,進入新內核時一直顯示載入Linux 5.6.7,載入初始化內存檔咋回事
概述====1)當內核配置了內存檔時, 內核在初始化時可以將軟盤載入到內存檔中作為根盤.當同時配置了初始化內存檔(Initail RAM Disk)時, 內核在初始化時可以在安裝主盤之前,通過引導程序所載入的initrd文件建立一個內存初始化盤, 首先將它安裝成根文件系統, 然後執行其根目錄下的linuxrc 文件,可用於在安裝主盤之前載入一些內核模塊. 等到linuxrc 程序退出後, 再將主盤安裝成根文件系統,並將內存初始化盤轉移安裝到其/initrd目錄下.2)當主盤就是initrd所生成的內存初始化盤時, 不再進行重新安裝,在DOS下用loadlin載入的搶救盤就是這種工作方式.3)引導程序所載入的initrd為文件系統的映象文件, 可以是gzip壓縮的, 也可以是不壓縮的.能夠識別的文件系統有minix,ext2,romfs三種.4)當內核的根盤為軟盤時,內核初始化時會測試軟盤的指定部位是否存在文件系統或壓縮文件映象, 然後將之載入或解壓到內存檔中作為根盤. 這是單張搶救軟盤的工作方式.有關代碼========; init/main.c#ifdef CONFIG_BLK_DEV_INITRDkdev_t real_root_dev; 啟動參數所設定的根盤設備#endifasmlinkage void __init start_kernel(void){ char * command_line; unsigned long mempages; extern char saved_command_line[]; lock_kernel(); printk(linux_banner); setup_arch(&command_line);arch/i386/kernel/setup.c中,初始化initrd_start和initrd_end兩個變數 ...#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && !initrd_below_start_ok && initrd_start < min_low_pfn << PAGE_SHIFT) { ; min_low_pfn為內核末端_end所開始的物理頁號,initrd_start,initrd_end在rd.c中定義 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - " "disabling it./n",initrd_start,min_low_pfn << PAGE_SHIFT); initrd_start = 0; }#endif ... kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL); 創建init進程 unlock_kernel(); current->need_resched = 1; cpu_idle();}static int init(void * unused){ lock_kernel(); do_basic_setup(); /* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ free_initmem(); unlock_kernel(); if (open("/dev/console", O_RDWR, 0) < 0) printk("Warning: unable to open an initial console./n"); (void) p(0); (void) p(0); /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) execve(execute_command,argv_init,envp_init); execve("/sbin/init",argv_init,envp_init); execve("/etc/init",argv_init,envp_init); execve("/bin/init",argv_init,envp_init); execve("/bin/sh",argv_init,envp_init); panic("No init found. Try passing init= option to kernel.");}static void __init do_basic_setup(void){#ifdef CONFIG_BLK_DEV_INITRD int real_root_mountflags;#endif ...#ifdef CONFIG_BLK_DEV_INITRD real_root_dev = ROOT_DEV; ROOT_DEV為所請求根文件系統的塊設備 real_root_mountflags = root_mountflags; if (initrd_start && mount_initrd) root_mountflags &= ~MS_RDONLY; else mount_initrd =0; #endif start_context_thread(); do_initcalls(); 會調用partition_setup()中載入內存檔 /* .. filesystems .. */ filesystem_setup(); /* Mount the root filesystem.. */ mount_root(); mount_devfs_fs ();#ifdef CONFIG_BLK_DEV_INITRD root_mountflags = real_root_mountflags; if (mount_initrd && ROOT_DEV != real_root_dev && MAJOR(ROOT_DEV) == RAMDISK_MAJOR && MINOR(ROOT_DEV) == 0) { ; 如果當前根盤為initrd所建立的內存檔 int error; int i, pid; pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); 創建新的任務去執行程序/linuxrc if (pid>0) while (pid != wait(&i)); 等待linuxrc進程退出 if (MAJOR(real_root_dev) != RAMDISK_MAJOR || MINOR(real_root_dev) != 0) { ; 如果原來的根盤不是0號內存檔,則使用原來的根文件系統, ; 並且將內存檔轉移到其/initrd目錄下 error = change_root(real_root_dev,"/initrd"); if (error) printk(KERN_ERR "Change root to /initrd: " "error %d/n",error); } }#endif}#ifdef CONFIG_BLK_DEV_INITRDstatic int do_linuxrc(void * shell){ static char *argv[] = { "linuxrc", NULL, }; close(0);close(1);close(2); setsid(); 設置新的session號 (void) open("/dev/console",O_RDWR,0); (void) p(0); (void) p(0); return execve(shell, argv, envp_init);}#endif; arch/i386/kernel/setup.c#define RAMDISK_IMAGE_START_MASK 0x07FF#define RAMDISK_PROMPT_FLAG 0x8000#define RAMDISK_LOAD_FLAG 0x4000 #define PARAM ((unsigned char *)empty_zero_page)#define RAMDISK_FLAGS (*(unsigned short *) (PARAM+0x1F8)) 可用rdev設置的參數#define LOADER_TYPE (*(unsigned char *) (PARAM+0x210))#define INITRD_START (*(unsigned long *) (PARAM+0x218)) 初始化盤映象起始物理地址#define INITRD_SIZE (*(unsigned long *) (PARAM+0x21c)) 初始化盤位元組數void __init setup_arch(char **cmdline_p){ ...#ifdef CONFIG_BLK_DEV_RAM rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK; 以塊為單位 rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0); rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);#endif ...#ifdef CONFIG_BLK_DEV_INITRD if (LOADER_TYPE && INITRD_START) { if (INITRD_START + INITRD_SIZE <= (max_low_pfn << PAGE_SHIFT)) { ; max_low_pfn表示內核空間1G范圍以下最大允許的物理頁號 reserve_bootmem(INITRD_START, INITRD_SIZE); initrd_start = INITRD_START ? INITRD_START + PAGE_OFFSET : 0; 轉變為內核邏輯地址 initrd_end = initrd_start+INITRD_SIZE; } else { printk("initrd extends beyond end of memory " "(0x%08lx > 0x%08lx)/ndisabling initrd/n", INITRD_START + INITRD_SIZE, max_low_pfn << PAGE_SHIFT); initrd_start = 0; } }#endif ...}; fs/partitions/check.c:int __init partition_setup(void){ device_init(); 包含ramdisk設備的初始化#ifdef CONFIG_BLK_DEV_RAM#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && mount_initrd) initrd_load(); ;如果啟動時載入了initrd文件,則用它去初始化根內存檔 else#endif rd_load(); 如果內核配置了內存檔並且根盤指定為軟盤則試圖將軟盤載入為根內存檔#endif return 0;}__initcall(partition_setup);; drivers/block/rd.c:int rd_doload; /* 1 = load RAM disk, 0 = don't load */int rd_prompt = 1; /* 1 = prompt for RAM disk, 0 = don't prompt */int rd_image_start; /* starting block # of image */#ifdef CONFIG_BLK_DEV_INITRDunsigned long initrd_start, initrd_end;int mount_initrd = 1; /* zero if initrd should not be mounted */int initrd_below_start_ok;void __init rd_load(void){ rd_load_disk(0); 載入到0號內存檔}void __init rd_load_secondary(void){ rd_load_disk(1); 載入到1號內存檔}static void __init rd_load_disk(int n){#ifdef CONFIG_BLK_DEV_INITRD extern kdev_t real_root_dev;#endif if (rd_doload == 0) return; if (MAJOR(ROOT_DEV) != FLOPPY_MAJOR 如果根盤是不軟盤#ifdef CONFIG_BLK_DEV_INITRD && MAJOR(real_root_dev) != FLOPPY_MAJOR#endif ) return; if (rd_prompt) {#ifdef CONFIG_BLK_DEV_FD floppy_eject();#endif#ifdef CONFIG_MAC_FLOPPY if(MAJOR(ROOT_DEV) == FLOPPY_MAJOR) swim3_fd_eject(MINOR(ROOT_DEV)); else if(MAJOR(real_root_dev) == FLOPPY_MAJOR) swim3_fd_eject(MINOR(real_root_dev));#endif printk(KERN_NOTICE "VFS: Insert root floppy disk to be loaded into RAM disk and press ENTER/n"); wait_for_keypress(); } rd_load_image(ROOT_DEV,rd_image_start, n); 將根軟盤載入到n號內存檔}void __init initrd_load(void){ ; 使用initrd設備盤作為源盤去建立內存根盤 rd_load_image(MKDEV(MAJOR_NR, INITRD_MINOR),rd_image_start,0);}static void __init rd_load_image(kdev_t device, int offset, int unit){ struct inode *inode, *out_inode; struct file infile, outfile; struct dentry in_dentry, out_dentry; mm_segment_t fs; kdev_t ram_device; int nblocks, i; char *buf; unsigned short rotate = 0; unsigned short devblocks = 0; char rotator[4] = { '|' , '/' , '-' , '//' }; ram_device = MKDEV(MAJOR_NR, unit); 建立輸出內存檔設備號 if ((inode = get_empty_inode()) == NULL) return; memset(&infile, 0, sizeof(infile)); memset(&in_dentry, 0, sizeof(in_dentry)); infile.f_mode = 1; /* read only */ infile.f_dentry = &in_dentry; in_dentry.d_inode = inode; infile.f_op = &def_blk_fops; init_special_inode(inode, S_IFBLK | S_IRUSR, kdev_t_to_nr(device)); if ((out_inode = get_empty_inode()) == NULL) goto free_inode; memset(&outfile, 0, sizeof(outfile)); memset(&out_dentry, 0, sizeof(out_dentry)); outfile.f_mode = 3; /* read/write */ outfile.f_dentry = &out_dentry; out_dentry.d_inode = out_inode; outfile.f_op = &def_blk_fops; init_special_inode(out_inode, S_IFBLK | S_IRUSR | S_IWUSR, kdev_t_to_nr(ram_device)); if (blkdev_open(inode, &infile) != 0) 打開輸入盤文件 goto free_inode; if (blkdev_open(out_inode, &outfile) != 0) 打開輸出內存檔文件 goto free_inodes; fs = get_fs(); set_fs(KERNEL_DS); nblocks = identify_ramdisk_image(device, &infile, offset); 鑒定輸入盤的文件類型 if (nblocks < 0) 出錯 goto done; if (nblocks == 0) { 表示輸入盤是gzip文件#ifdef BUILD_CRAMDISK if (crd_load(&infile, &outfile) == 0) 將輸入盤文件解壓到輸出盤文件中去 goto successful_load;#else printk(KERN_NOTICE "RAMDISK: Kernel does not support compressed " "RAM disk images/n");#endif goto done; } /* * NOTE NOTE: nblocks suppose that the blocksize is BLOCK_SIZE, so * rd_load_image will work only with filesystem BLOCK_SIZE wide! * So make sure to use 1k blocksize while generating ext2fs * ramdisk-images. */ if (nblocks > (rd_length[unit] >> BLOCK_SIZE_BITS)) { ; 如果輸入盤的尺寸超過了輸出內存檔的允許尺寸 printk("RAMDISK: image too big! (%d/%ld blocks)/n", nblocks, rd_length[unit] >> BLOCK_SIZE_BITS); goto done; } /* * OK, time to in the data */ buf = kmalloc(BLOCK_SIZE, GFP_KERNEL); if (buf == 0) { printk(KERN_ERR "RAMDISK: could not allocate buffer/n"); goto done; } if (blk_size[MAJOR(device)]) devblocks = blk_size[MAJOR(device)][MINOR(device)]; 取輸入盤的容量#ifdef CONFIG_BLK_DEV_INITRD if (MAJOR(device) == MAJOR_NR && MINOR(device) == INITRD_MINOR) devblocks = nblocks; 如果輸入是初始化內存檔,則盤的容量為它的實際尺寸#endif if (devblocks == 0) { printk(KERN_ERR "RAMDISK: could not determine device size/n"); goto done; } printk(KERN_NOTICE "RAMDISK: Loading %d blocks [%d disk%s] into ram disk... ", nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : ""); for (i=0; i < nblocks; i++) { if (i && (i % devblocks == 0)) { printk("done disk #%d./n", i/devblocks); rotate = 0; invalidate_buffers(device); 使輸入盤設備緩沖區無效 if (infile.f_op->release) infile.f_op->release(inode, &infile); printk("Please insert disk #%d and press ENTER/n", i/devblocks+1); wait_for_keypress(); if (blkdev_open(inode, &infile) != 0) { printk("Error opening disk./n"); goto done; } infile.f_pos = 0; printk("Loading disk #%d... ", i/devblocks+1); } infile.f_op->read(&infile, buf, BLOCK_SIZE, &infile.f_pos); outfile.f_op->write(&outfile, buf, BLOCK_SIZE, &outfile.f_pos);#if !defined(CONFIG_ARCH_S390) if (!(i % 16)) { printk("%c/b", rotator[rotate & 0x3]); rotate++; }#endif } printk("done./n"); kfree(buf);successful_load: invalidate_buffers(device); ROOT_DEV = MKDEV(MAJOR_NR, unit); 將根盤設備設置為當前載入的內存檔 if (ROOT_DEVICE_NAME != NULL) strcpy (ROOT_DEVICE_NAME, "rd/0");done: if (infile.f_op->release) infile.f_op->release(inode, &infile); set_fs(fs); return;free_inodes: /* free inodes on error */ iput(out_inode); blkdev_put(inode->i_bdev, BDEV_FILE);free_inode: iput(inode);}int __init identify_ramdisk_image(kdev_t device, struct file *fp, int start_block){ const int size = 512; struct minix_super_block *minixsb; struct ext2_super_block *ext2sb; struct romfs_super_block *romfsb; int nblocks = -1; unsigned char *buf; buf = kmalloc(size, GFP_KERNEL); if (buf == 0) return -1; minixsb = (struct minix_super_block *) buf; ext2sb = (struct ext2_super_block *) buf; romfsb = (struct romfs_super_block *) buf; memset(buf, 0xe5, size); /* * Read block 0 to test for gzipped kernel */ if (fp->f_op->llseek) fp->f_op->llseek(fp, start_block * BLOCK_SIZE, 0); fp->f_pos = start_block * BLOCK_SIZE; fp->f_op->read(fp, buf, size, &fp->f_pos); ; 讀取offset開始的512位元組 /* * If it matches the gzip magic numbers, return -1 */ if (buf[0] == 037 && ((buf[1] == 0213) || (buf[1] == 0236))) { printk(KERN_NOTICE "RAMDISK: Compressed image found at block %d/n", start_block); nblocks = 0; goto done; } /* romfs is at block zero too */ if (romfsb->word0 == ROMSB_WORD0 && romfsb->word1 == ROMSB_WORD1) { printk(KERN_NOTICE "RAMDISK: romfs filesystem found at block %d/n", start_block); nblocks = (ntohl(romfsb->size)+BLOCK_SIZE-1)>>BLOCK_SIZE_BITS; goto done; } /* * Read block 1 to test for minix and ext2 superblock */ if (fp->f_op->llseek) fp->f_op->llseek(fp, (start_block+1) * BLOCK_SIZE, 0); fp->f_pos = (start_block+1) * BLOCK_SIZE; fp->f_op->read(fp, buf, size, &fp->f_pos); /* Try minix */ if (minixsb->s_magic == MINIX_SUPER_MAGIC || minixsb->s_magic == MINIX_SUPER_MAGIC2) { printk(KERN_NOTICE "RAMDISK: Minix filesystem found at block %d/n", start_block); nblocks = minixsb->s_nzones << minixsb->s_log_zone_size; goto done; } /* Try ext2 */ if (ext2sb->s_magic == cpu_to_le16(EXT2_SUPER_MAGIC)) { printk(KERN_NOTICE "RAMDISK: ext2 filesystem found at block %d/n", start_block); nblocks = le32_to_cpu(ext2sb->s_blocks_count); goto done; } printk(KERN_NOTICE "RAMDISK: Couldn't find valid RAM disk image starting at %d./n", start_block);done: if (fp->f_op->llseek) fp->f_op->llseek(fp, start_block * BLOCK_SIZE, 0); fp->f_pos = start_block * BLOCK_SIZE; kfree(buf); return nblocks;}; fs/super.cvoid __init mount_root(void){ struct file_system_type * fs_type; struct super_block * sb; struct vfsmount *vfsmnt; struct block_device *bdev = NULL; mode_t mode; int retval; void *handle; char path[64]; int path_start = -1;#ifdef CONFIG_BLK_DEV_FD if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) { 當根盤還是軟盤,表示沒有載入過內存檔#ifdef CONFIG_BLK_DEV_RAM extern int rd_doload; extern void rd_load_secondary(void);#endif floppy_eject();#ifndef CONFIG_BLK_DEV_RAM printk(KERN_NOTICE "(Warning, this kernel has no ramdisk support)/n");#else /* rd_doload is 2 for a al initrd/ramload setup */ ; 只有當載入了initrd但沒有釋放到內存檔中(mount_inird=0)才有可能到這一步 if(rd_doload==2) rd_load_secondary(); 載入另一張軟盤到1號內存檔作為根盤 else#endif { printk(KERN_NOTICE "VFS: Insert root floppy and press ENTER/n"); wait_for_keypress(); } }#endif devfs_make_root (root_device_name); handle = devfs_find_handle (NULL, ROOT_DEVICE_NAME, MAJOR (ROOT_DEV), MINOR (ROOT_DEV), DEVFS_SPECIAL_BLK, 1); if (handle) /* Sigh: bd*() functions only paper over the cracks */ { unsigned major, minor; devfs_get_maj_min (handle, &major, &minor); ROOT_DEV = MKDEV (major, minor); } /* * Probably pure paranoia, but I'm less than happy about delving into * devfs crap and checking it right now. Later. */ if (!ROOT_DEV) panic("I have no root and I want to scream"); bdev = bdget(kdev_t_to_nr(ROOT_DEV)); if (!bdev) panic(__FUNCTION__ ": unable to allocate root device"); bdev->bd_op = devfs_get_ops (handle); path_start = devfs_generate_path (handle, path + 5, sizeof (path) - 5); mode = FMODE_READ; if (!(root_mountflags & MS_RDONLY)) mode |= FMODE_WRITE; retval = blkdev_get(bdev, mode, 0, BDEV_FS); if (retval == -EROFS) { root_mountflags |= MS_RDONLY; retval = blkdev_get(bdev, FMODE_READ, 0, BDEV_FS); } if (retval) { /* * Allow the user to distinguish between failed open * and bad superblock on root device. */ printk ("VFS: Cannot open root device /"%s/" or %s/n", root_device_name, kdevname (ROOT_DEV)); printk ("Please append a correct /"root=/" boot option/n"); panic("VFS: Unable to mount root fs on %s", kdevname(ROOT_DEV)); } check_disk_change(ROOT_DEV); sb = get_super(ROOT_DEV); 取根盤的超級塊 if (sb) { fs_type = sb->s_type; goto mount_it; } read_lock(&file_systems_lock); for (fs_type = file_systems ; fs_type ; fs_type = fs_type->next) { if (!(fs_type->fs_flags & FS_REQUIRES_DEV)) continue; 根文件系統必須依賴於塊設備 if (!try_inc_mod_count(fs_type->owner)) continue; 當文件系統模塊正在刪除過程中 read_unlock(&file_systems_lock); sb = read_super(ROOT_DEV,bdev,fs_type,root_mountflags,NULL,1);建立根盤的超級塊結構 if (sb) goto mount_it; read_lock(&file_systems_lock); put_filesystem(fs_type); 釋放對文件系統模塊的引用 } read_unlock(&file_systems_lock); panic("VFS: Unable to mount root fs on %s", kdevname(ROOT_DEV));mount_it: printk ("VFS: Mounted root (%s filesystem)%s./n", fs_type->name, (sb->s_flags & MS_RDONLY) ? " readonly" : ""); if (path_start >= 0) { devfs_mk_symlink (NULL, "root", DEVFS_FL_DEFAULT, path + 5 + path_start, NULL, NULL); memcpy (path + path_start, "/dev/", 5); vfsmnt = add_vfsmnt(NULL, sb->s_root, path + path_start); } else vfsmnt = add_vfsmnt(NULL, sb->s_root, "/dev/root"); 建立根盤的安裝結構 /* FIXME: if something will try to umount us right now... */ if (vfsmnt) { set_fs_root(current->fs, vfsmnt, sb->s_root); 設置當前進程的根盤和根目錄 set_fs_pwd(current->fs, vfsmnt, sb->s_root); 設置當前進程的當前盤和當前目錄 if (bdev) bdput(bdev); /* sb holds a reference */ return; } panic("VFS: add_vfsmnt failed for root fs");}#ifdef CONFIG_BLK_DEV_INITRDint __init change_root(kdev_t new_root_dev,const char *put_old){ 以new_root_dev作為根盤重新安裝根文件系統,原來的根轉移到put_old目錄下 struct vfsmount *old_rootmnt; struct nameidata devfs_nd, nd; int error = 0; read_lock(¤t->fs->lock); old_rootmnt = mntget(current->fs->rootmnt); 取當前進程的根盤安裝結構 read_unlock(¤t->fs->lock); /* First unmount devfs if mounted */ if (path_init("/dev", LOOKUP_FOLLOW|LOOKUP_POSITIVE, &devfs_nd)) error = path_walk("/dev", &devfs_nd); if (!error) { if (devfs_nd.mnt->mnt_sb->s_magic == DEVFS_SUPER_MAGIC && devfs_nd.dentry == devfs_nd.mnt->mnt_root) { dput(devfs_nd.dentry); down(&mount_sem); /* puts devfs_nd.mnt */ do_umount(devfs_nd.mnt, 0, 0); up(&mount_sem); } else path_release(&devfs_nd); } ROOT_DEV = new_root_dev; mount_root(); 改變根盤設備重新安裝根文件系統#if 1 shrink_dcache(); 清除目錄項緩沖中所有自由的目錄項 printk("change_root: old root has d_count=%d/n", atomic_read(&old_rootmnt->mnt_root->d_count));#endif mount_devfs_fs (); /* * Get the new mount directory */ error = 0; if (path_init(put_old, LOOKUP_FOLLOW|LOOKUP_POSITIVE|LOOKUP_DIRECTORY, &nd)) error = path_walk(put_old, &nd); 在新的根盤中尋找put_old目錄 if (error) { int blivet; printk(KERN_NOTICE "Trying to unmount old root ... "); blivet = do_umount(old_rootmnt, 1, 0); 卸載原始的根盤 if (!blivet) { printk("okay/n"); return 0; } printk(KERN_ERR "error %d/n", blivet); return error; } /* FIXME: we should hold i_zombie on nd.dentry */ move_vfsmnt(old_rootmnt, nd.dentry, nd.mnt, "/dev/root.old"); mntput(old_rootmnt); path_release(&nd); return 0;}#endifstatic struct vfsmount *add_vfsmnt(struct nameidata *nd, 在虛擬文件系統中的安裝點 struct dentry *root, 安裝盤的根目錄項 const char *dev_name) 安裝盤名稱{ struct vfsmount *mnt;
————————————————
版權聲明:本文為CSDN博主「huanghaibin」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/huanghaibin/java/article/details/478215
F. gcc 編譯優化做了哪些事求解答
用過gcc的都應該知道編譯時候的-O選項吧。它就是負責編譯優化。下面列出它的說明: -O -O1 Optimize. Optimizing compilation takes somewhat more time, and a lot more memory for a large function. With -O, the compiler tries to rece code size and execution time, without performing any optimizations that take a great deal of compilation time. -O turns on the following optimization flags: -fdefer-pop -fdelayed-branch -fguess-branch-probability -fcprop-registers -floop-optimize -fif-conversion -fif-conver- sion2 -ftree-ccp -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-ter -ftree-lrs -ftree-sra -ftree-rename -ftree-fre -ftree-ch -funit-at-a-time -fmerge-constants -O also turns on -fomit-frame-pointer on machines where doing so does not interfere with debugging. -O doesn』t turn on -ftree-sra for the Ada compiler. This option must be explicitly speci- fied on the command line to be enabled for the Ada compiler. -O2 Optimize even more. GCC performs nearly all supported optimizations that do not involve a space-speed tradeoff. The compiler does not perform loop unrolling or function inlining when you specify -O2. As compared to -O, this option increases both compilation time and the performance of the generated code. -O2 turns on all optimization flags specified by -O. It also turns on the following opti- mization flags: -fthread-jumps -fcrossjumping -foptimize-sibling-calls -fcse-follow-jumps -fcse-skip-blocks -fgcse -fgcse-lm -fexpensive-optimizations -fstrength-rece -fre- run-cse-after-loop -frerun-loop-opt -fcaller-saves -fpeephole2 -fschele-insns -fsched- ule-insns2 -fsched-interblock -fsched-spec -fregmove -fstrict-aliasing -fdelete-null-pointer-checks -freorder-blocks -freorder-functions -falign-functions -falign-jumps -falign-loops -falign-labels -ftree-vrp -ftree-pre Please note the warning under -fgcse about invoking -O2 on programs that use computed gotos. -O3 Optimize yet more. -O3 turns on all optimizations specified by -O2 and also turns on the -finline-functions, -funswitch-loops and -fgcse-after-reload options. -O0 Do not optimize. This is the default. -Os Optimize for size. -Os enables all -O2 optimizations that do not typically increase code size. It also performs further optimizations designed to rece code size. -Os disables the following optimization flags: -falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays -ftree-vect-loop-version If you use multiple -O options, with or without level numbers, the last such option is the one that is effective. Options of the form -fflag specify machine-independent flags. Most flags have both positive and negative forms; the negative form of -ffoo would be -fno-foo. In the table below, only one of the forms is listed---the one you typically will use. You can figure out the other form by either removing no- or adding it. The following options control specific optimizations. They are either activated by -O options or are related to ones that are. You can use the following flags in the rare cases when "fine-tuning" of optimizations to be performed is desired. -fno-default-inline Do not make member functions inline by default merely because they are defined inside the class scope (C++ only). Otherwise, when you specify -O, member functions defined inside class scope are compiled inline by default; i.e., you don』t need to add inline in front of the member function name. -fno-defer-pop Always pop the arguments to each function call as soon as that function returns. For machines which must pop arguments after a function call, the compiler normally lets argu- ments accumulate on the stack for several function calls and pops them all at once. Disabled at levels -O, -O2, -O3, -Os. -fforce-mem Force memory operands to be copied into registers before doing arithmetic on them. This proces better code by making all memory references potential common subexpressions. When they are not common subexpressions, instruction combination should eliminate the separate register-load. This option is now a nop and will be removed in 4.2. -fforce-addr Force memory address constants to be copied into registers before doing arithmetic on them. -fomit-frame-pointer Don』t keep the frame pointer in a register for functions that don』t need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra regis- ter available in many functions. It also makes debugging impossible on some machines. On some machines, such as the VAX, this flag has no effect, because the standard calling sequence automatically handles the frame pointer and nothing is saved by pretending it doesn』t exist. The machine-description macro "FRAME_POINTER_REQUIRED" controls whether a target machine supports this flag. Enabled at levels -O, -O2, -O3, -Os. -foptimize-sibling-calls Optimize sibling and tail recursive calls. Enabled at levels -O2, -O3, -Os. -fno-inline Don』t pay attention to the "inline" keyword. Normally this option is used to keep the com- piler from expanding any functions inline. Note that if you are not optimizing, no func- tions can be expanded inline. -finline-functions Integrate all simple functions into their callers. The compiler heuristically decides which functions are simple enough to be worth integrating in this way. If all calls to a given function are integrated, and the function is declared "static", then the function is normally not output as assembler code in its own right. Enabled at level -O3. -finline-functions-called-once Consider all "static" functions called once for inlining into their caller even if they are not marked "inline". If a call to a given function is integrated, then the function is not output as assembler code in its own right. Enabled if -funit-at-a-time is enabled. -fearly-inlining Inline functions marked by "always_inline" and functions whose body seems smaller than the function call overhead early before doing -fprofile-generate instrumentation and real inlining pass. Doing so makes profiling significantly cheaper and usually inlining faster on programs having large chains of nested wrapper functions. Enabled by default. -finline-limit=n By default, GCC limits the size of functions that can be inlined. This flag allows the control of this limit for functions that are explicitly marked as inline (i.e., marked with the inline keyword or defined within the class definition in c++). n is the size of func- tions that can be inlined in number of pseudo instructions (not counting parameter han- dling). The default value of n is 600. Increasing this value can result in more inlined code at the cost of compilation time and memory consumption. Decreasing usually makes the compilation faster and less code will be inlined (which presumably means slower programs). This option is particularly useful for programs that use inlining heavily such as those based on recursive templates with C++. Inlining is actually controlled by a number of parameters, which may be specified indivi- ally by using --param name=value. The -finline-limit=n option sets some of these parame- ters as follows: max-inline-insns-single is set to I<n>/2. max-inline-insns-auto is set to I<n>/2. min-inline-insns is set to 130 or I<n>/4, whichever is smaller. max-inline-insns-rtl is set to I<n>. See below for a documentation of the indivial parameters controlling inlining. Note: pseudo instruction represents, in this particular context, an abstract measurement of function』s size. In no way does it represent a count of assembly instructions and as such its exact meaning might change from one release to an another. -fkeep-inline-functions In C, emit "static" functions that are declared "inline" into the object file, even if the function has been inlined into all of its callers. This switch does not affect functions using the "extern inline" extension in GNU C. In C++, emit any and all inline functions into the object file. -fkeep-static-consts Emit variables declared "static const" when optimization isn』t turned on, even if the vari- ables aren』t referenced. GCC enables this option by default. If you want to force the compiler to check if the variable was referenced, regardless of whether or not optimization is turned on, use the -fno-keep-static-consts option. -fmerge-constants Attempt to merge identical constants (string constants and floating point constants) across compilation units. This option is the default for optimized compilation if the assembler and linker support it. Use -fno-merge-constants to inhibit this behavior. Enabled at levels -O, -O2, -O3, -Os. -fmerge-all-constants Attempt to merge identical constants and identical variables. This option implies -fmerge-constants. In addition to -fmerge-constants this considers e.g. even constant initialized arrays or initialized constant variables with integral or floating point types. Languages like C or C++ require each non-automatic variable to have distinct location, so using this option will result in non-conforming behavior. -fmolo-sched Perform swing molo scheling immediately before the first scheling pass. This pass looks at innermost loops and reorders their instructions by overlapping different itera- tions. -fno-branch-count-reg Do not use "decrement and branch" instructions on a count register, but instead generate a sequence of instructions that decrement a register, compare it against zero, then branch based upon the result. This option is only meaningful on architectures that support such instructions, which include x86, PowerPC, IA-64 and S/390. The default is -fbranch-count-reg, enabled when -fstrength-rece is enabled. -fno-function-cse Do not put function addresses in registers; make each instruction that calls a constant function contain the function』s address explicitly. This option results in less efficient code, but some strange hacks that alter the assembler output may be confused by the optimizations performed when this option is not used. The default is -ffunction-cse -fno-zero-initialized-in-bss If the target supports a BSS section, GCC by default puts variables that are initialized to zero into BSS. This can save space in the resulting code. This option turns off this behavior because some programs explicitly rely on variables going to the data section. E.g., so that the resulting executable can find the beginning of that section and/or make assumptions based on that. The default is -fzero-initialized-in-bss. -fmudflap -fmudflapth -fmudflapir For front-ends that support it (C and C++), instrument all risky pointer/array dereferenc- ing operations, some standard library string/heap functions, and some other associated con- structs with range/validity tests. Moles so instrumented should be immune to buffer overflows, invalid heap use, and some other classes of C/C++ programming errors. The instrumentation relies on a separate runtime library (libmudflap), which will be linked into a program if -fmudflap is given at link time. Run-time behavior of the instrumented program is controlled by the MUDFLAP_OPTIONS environment variable. See "env MUD- FLAP_OPTIONS=-help a.out" for its options. Use -fmudflapth instead of -fmudflap to compile and to link if your program is multi-threaded. Use -fmudflapir, in addition to -fmudflap or -fmudflapth, if instrumenta- tion should ignore pointer reads. This proces less instrumentation (and therefore faster execution) and still provides some protection against outright memory corrupting writes, but allows erroneously read data to propagate within a program. -fstrength-rece Perform the optimizations of loop strength rection and elimination of iteration vari- ables. Enabled at levels -O2, -O3, -Os. -fthread-jumps Perform optimizations where we check to see if a jump branches to a location where another comparison subsumed by the first is found. If so, the first branch is redirected to either the destination of the second branch or a point immediately following it, depending on whether the condition is known to be true or false. Enabled at levels -O2, -O3, -Os. -fcse-follow-jumps In common subexpression elimination, scan through jump instructions when the target of the jump is not reached by any other path. For example, when CSE encounters an "if" statement with an "else" clause, CSE will follow the jump when the condition tested is false. Enabled at levels -O2, -O3, -Os. -fcse-skip-blocks This is similar to -fcse-follow-jumps, but causes CSE to follow jumps which conditionally skip over blocks. When CSE encounters a simple "if" statement with no else clause, -fcse-skip-blocks causes CSE to follow the jump around the body of the "if". Enabled at levels -O2, -O3, -Os. -frerun-cse-after-loop Re-run common subexpression elimination after loop optimizations has been performed. Enabled at levels -O2, -O3, -Os. -frerun-loop-opt Run the loop optimizer twice. Enabled at levels -O2, -O3, -Os. -fgcse Perform a global common subexpression elimination pass. This pass also performs global constant and propagation. Note: When compiling a program using computed gotos, a GCC extension, you may get better runtime performance if you disable the global common subexpression elimination pass by adding -fno-gcse to the command line. Enabled at levels -O2, -O3, -Os. -fgcse-lm When -fgcse-lm is enabled, global common subexpression elimination will attempt to move loads which are only killed by stores into themselves. This allows a loop containing a load/store sequence to be changed to a load outside the loop, and a /store within the loop. Enabled by default when gcse is enabled. -fgcse-sm When -fgcse-sm is enabled, a store motion pass is run after global common subexpression elimination. This pass will attempt to move stores out of loops. When used in conjunction with -fgcse-lm, loops containing a load/store sequence can be changed to a load before the loop and a store after the loop. Not enabled at any optimization level. -fgcse-las When -fgcse-las is enabled, the global common subexpression elimination pass eliminates rendant loads that come after stores to the same memory location (both partial and full rendancies). Not enabled at any optimization level. -fgcse-after-reload When -fgcse-after-reload is enabled, a rendant load elimination pass is performed after reload. The purpose of this pass is to cleanup rendant spilling. -floop-optimize Perform loop optimizations: move constant expressions out of loops, simplify exit test con- ditions and optionally do strength-rection as well. Enabled at levels -O, -O2, -O3, -Os. -floop-optimize2 Perform loop optimizations using the new loop optimizer. The optimizations (loop unrolling, peeling and unswitching, loop invariant motion) are enabled by separate flags. -funsafe-loop-optimizations If given, the loop optimizer will assume that loop indices do not overflow, and that the loops with nontrivial exit condition are not infinite. This enables a wider range of loop optimizations even if the loop optimizer itself cannot prove that these assumptions are valid. Using -Wunsafe-loop-optimizations, the compiler will warn you if it finds this kind of loop. -fcrossjumping Perform cross-jumping transformation. This transformation unifies equivalent code and save code size. The resulting code may or may not perform better than without cross-jumping. Enabled at levels -O2, -O3, -Os. -fif-conversion Attempt to transform conditional jumps into branch-less equivalents. This include use of conditional moves, min, max, set flags and abs instructions, and some tricks doable by standard arithmetics. The use of conditional execution on chips where it is available is controlled by "if-conversion2". Enabled at levels -O, -O2, -O3, -Os. -fif-conversion2 Use conditional execution (where available) to transform conditional jumps into branch-less equivalents. Enabled at levels -O, -O2, -O3, -Os. -fdelete-null-pointer-checks Use global dataflow analysis to identify and eliminate useless checks for null pointers. The compiler assumes that dereferencing a null pointer would have halted the program. If a pointer is checked after it has already been dereferenced, it cannot be null. In some environments, this assumption is not true, and programs can safely dereference null pointers. Use -fno-delete-null-pointer-checks to disable this optimization for programs which depend on that behavior. Enabled at levels -O2, -O3, -Os. -fexpensive-optimizations Perform a number of minor optimizations that are relatively expensive. Enabled at levels -O2, -O3, -Os. -foptimize-register-move -fregmove Attempt to reassign register numbers in move instructions and as operands of other simple instructions in order to maximize the amount of register tying. This is especially helpful on machines with two-operand instructions. Note -fregmove and -foptimize-register-move are the same optimization. Enabled at levels -O2, -O3, -Os. -fdelayed-branch If supported for the target machine, attempt to reorder instructions to exploit instruction slots available after delayed branch instructions. Enabled at levels -O, -O2, -O3, -Os. -fschele-insns If supported for the target machine, attempt to reorder instructions to eliminate execution stalls e to required data being unavailable. This helps machines that have slow floating point or memory load instructions by allowing other instructions to be issued until the result of the load or floating point instruction is required. Enabled at levels -O2, -O3, -Os. -fschele-insns2 Similar to -fschele-insns, but requests an additional pass of instruction scheling after register allocation has been done. This is especially useful on machines with a rel- atively small number of registers and where memory load instructions take more than one cycle. Enabled at levels -O2, -O3, -Os. -fno-sched-interblock Don』t schele instructions across basic blocks. This is normally enabled by default when scheling before register allocation, i.e. with -fschele-insns or at -O2 or higher. -fno-sched-spec Don』t allow speculative motion of non-load instructions. This is normally enabled by default when scheling before register allocation, i.e. with -fschele-insns or at -O2 or higher. -fsched-spec-load Allow speculative motion of some load instructions. This only makes sense when scheling before register allocation, i.e. with -fschele-insns or at -O2 or higher. -fsched-spec-load-dangerous Allow speculative motion of more load instructions. This only makes sense when scheling before register allocation, i.e. with -fschele-insns or at -O2 or higher. -fsched-stalled-insns -fsched-stalled-insns=n Define how many insns (if any) can be moved prematurely from the queue of stalled insns into the ready list, ring the second scheling pass. -fno-fsched-stalled-insns and -fsched-stalled-insns=0 are equivalent and mean that no insns will be moved prematurely. If n is unspecified then there is no limit on how many queued insns can be moved prema- turely. -fsched-stalled-insns-dep -fsched-stalled-insns-dep=n Define how many insn groups (cycles) will be examined for a dependency on a stalled insn that is candidate for premature removal from the queue of stalled insns. This has an effect only ring the second scheling pass, and only if -fsched-stalled-insns is used and its value is not zero. +-fno-sched-stalled-insns-dep is equivalent to +-fsched-stalled-insns-dep=0. +-fsched-stalled-insns-dep without a value is equivalent to +-fsched-stalled-insns-dep=1. -fsched2-use-superblocks When scheling after register allocation, do use superblock scheling algorithm. Superblock scheling allows motion across basic block boundaries resulting on faster scheles. This option is experimental, as not all machine descriptions used by GCC model the CPU closely enough to avoid unreliable results from the algorithm. This only makes sense when scheling after register
G. 介紹幾種主流嵌入式操作系統的特點,並分析比較 哥們,我現在糾結這個問題,可以給點指點嗎
如果你是學習階段的話,那LINUX和UCOS-II是比較合適的
uc/os和uclinux操作系統是兩種性能優良源碼公開且被廣泛應用的的免費嵌入式操作系統,可以作為研究實時操作系統和非實時操作系統的典範。本文通過對 uc/os和uclinux的對比,分析和總結了嵌入式操作系統應用中的若乾重要問題,歸納了嵌入式系統開發中操作系統的選型依據。
兩種開源嵌入式操作系統介紹
uc/os和uclinux操作系統,是當前得到廣泛應用的兩種免費且公開源碼的嵌入式操作系統。uc/os適合小型控制系統,具有執行效率高、佔用空間小、實時性能優良和可擴展性強等特點,最小內核可編譯至2k。uclinux則是繼承標准linux 的優良特性,針對嵌入式處理器的特點設計的一種操作系統,具有內嵌網路協議、支持多種文件系統,開發者可利用標准linux先驗知識等優勢。其編譯後目標文件可控制在幾百k量級。
uc/os是一種免費公開源代碼、結構小巧、具有可剝奪實時內核的實時操作系統。其內核提供任務調度與管理、時間管理、任務間同步與通信、內存管理和中斷服務等功能。
uclinux是一種優秀的嵌入式linux版本。uclinux是micro-conrol-linux的縮寫。同標准linux相比,它集成了標准linux操作系統的穩定性、強大網路功能和出色的文件系統等主要優點。但是由於沒有mmu(內存管理單元),其多任務的實現需要一定技巧。
兩種嵌入式操作系統主要性能比較
嵌入式操作系統是嵌入式系統軟硬體資源的控制中心,它以盡量合理的有效方法組織多個用戶共享嵌入式系統的各種資源。其中用戶指的是系統程序之上的所有軟體。所謂合理有效的方法,指的就是操作系統如何協調並充分利用硬體資源來實現多任務。復雜的操作系統都支持文件系統,方便組織文件並易於對其規范化操作。
嵌入式操作系統還有一個特點就是針對不同的平台,系統不是直接可用的,一般需要經過針對專門平台的移植操作系統才能正常工作。進程調度、文件系統支持和系統移植是在嵌入式操作系統實際應用中最常見的問題,下文就從這幾個角度入手對uc/os和uclinux進行分析比較。
進程調度
任務調度主要是協調任務對計算機系統內資源(如內存、i/o設備、cpu)的爭奪使用。進程調度又稱為cpu調度,其根本任務是按照某種原則為處於就緒狀態的進程分配cpu。由於嵌入式系統中內存和i/o設備一般都和cpu同時歸屬於某進程,所以任務調度和進程調度概念相近,很多場合不加區分,下文中提到的任務其實就是進程的概念。
進程調度可分為"剝奪型調度"和"非剝奪型調度"兩種基本方式。所謂"非剝奪型調度"是指:一旦某個進程被調度執行,則該進程一直執行下去直至該進程結束,或由於某種原因自行放棄cpu進入等待狀態,才將cpu重新分配給其他進程。所謂"剝奪型調度"是指:一旦就緒狀態中出現優先權更高的進程,或者運行的進程已用滿了規定的時間片時,便立即剝奪當前進程的運行(將其放回就緒狀態),把cpu分配給其他進程
作為實時操作系統,uc/os是採用的可剝奪型實時多任務內核。可剝奪型的實時內核在任何時候都運行就緒了的最高優先順序的任務。uc/os中最多可以支持64 個任務,分別對應優先順序0~63,
其中0為最高優先順序。調度工作的內容可以分為兩部分:最高優先順序任務的尋找和任務切換。
其最高優先順序任務的尋找是通過建立就緒任務表來實現的。uc/os中的每一個任務都有獨立的堆棧空間,並有一個稱為任務控制塊tcb(task control block)數據結構,其中第一個成員變數就是保存的任務堆棧指針。任務調度模塊首先用變數 ostcbhighrdy記錄當前最高級就緒任務的tcb地址,然後調用os_task_sw() 函數來進行任務切換。
uclinux的進程調度沿用了linux的傳統,系統每隔一定時間掛起進程,同時系統產生快速和周期性的時鍾計時中斷,並通過調度函數(定時器處理函數)決定進程什麼時候擁有它的時間片。然後進行相關進程切換,這是通過父進程調用fork 函數生成子進程來實現的。
uclinux系統fork調用完成後,要麼子進程代替父進程執行(此時父進程已經 sleep),直到子進程調用exit退出;要麼調用exec執行一個新的進程,這個時候產生可執行文件的載入,即使這個進程只是父進程的拷貝,這個過程也不可避免。當子進程執行exit或exec後,子進程使用wakeup把父進程喚醒,使父進程繼續往下執行。
uclinux由於沒有mmu管理存儲器,其對內存的訪問是直接的,所有程序中訪問的地址都是實際的物理地址。操作系統隊內存空間沒有保護,各個進程實際上共享一個運行空間。這就需要實現多進程時進行數據保護,也導致了用戶程序使用的空間可能佔用到系統內核空間,這些問題在編程時都需要多加註意,否則容易導致系統崩潰。
由上述分析可以得知,uc/os內核是針對實時系統的要求設計實現的,相對簡單,可以滿足較高的實時性要求。而uclinux則在結構上繼承了標准linux的多任務實現方式,僅針對嵌入式處理器特點進行改良。其要實現實時性效果則需要使系統在實時內核的控制下運行,rt-linux就是可以實現這一個功能的一種實時內核。
文件系統
所謂文件系統是指負責存取和管理文件信息的機構,也可以說是負責文件的建立、撤銷、組織、讀寫、修改、復制及對文件管理所需要的資源(如目錄表、存儲介質等)實施管理的軟體部分。
uc/os是面向中小型嵌入式系統的,如果包含全部功能(信號量、消息郵箱、消息隊列及相關函數),編譯後的uc/os內核僅有6~10kb,所以系統本身並沒有對文件系統的支持。但是uc/os具有良好的擴展性能,如果需要的話也可自行加入文件系統的內容。
uclinux則是繼承了linux完善的文件系統性能。其採用的是romfs文件系統,這種文件系統相對於一般的ext2文件系統要求更少的空間。空間的節約來自於兩個方面,首先內核支持romfs文件系統比支持ext2文件系統需要更少的代碼,其次romfs文件系統相對簡單,在建立文件系統超級塊(superblock)需要更少的存儲空間。romfs文件系統不支持動態擦寫保存,對於系統需要動態保存的數據採用虛擬ram盤的方法進行處理(ram盤將採用ext2文件系統)。
uclinux還繼承了linux網路操作系統的優勢,可以很方便的支持網路文件系統且內嵌tcp/ip協議,這為uclinux開發網路接入設備提供了便利。
由兩種操作系統對文件系統的支持可知,在復雜的需要較多文件處理的嵌入式系統中uclinux是一個不錯的選擇。而uc/os則主要適合一些控制系統。
操作系統的移植
嵌入式操作系統移植的目的是指使操作系統能在某個微處理器或微控制器上運行。uc/os和uclinux都是源碼公開的操作系統,且其結構化設計便於把與處理器相關的部分分離出來,所以被移植到新的處理器上是可能的。
以下對兩種系統的移植分別予以說明。
(1)uc/os的移植
要移植uc/os,目標處理器必須滿足以下要求;
·處理器的c編譯器能產生可重入代碼,且用c語言就可以打開和關閉中斷;
·處理器支持中斷,並能產生定時中斷;
·處理器支持足夠的ram(幾k位元組),作為多任務環境下的任務堆棧;
·處理器有將堆棧指針和其他cpu寄存器讀出和存儲到堆棧或內存中的指令。
在理解了處理器和c編譯器的技術細節後,uc/os的移植只需要修改與處理器相關的代碼就可以了。
具體有如下內容:
·os_cpu.h中需要設置一個常量來標識堆棧增長方向;
·os_cpu.h中需要聲明幾個用於開關中斷和任務切換的宏;
·os_cpu.h中需要針對具體處理器的字長重新定義一系列數據類型;
·os_cpu_a.asm需要改寫4個匯編語言的函數;
·os_cpu_c.c需要用c語言編寫6個簡單函數;
·修改主頭文件include.h,將上面的三個文件和其他自己的頭文件加入。
(2)uclinux的移植
由於uclinux其實是linux針對嵌入式系統的一種改良,其結構比較復雜,相對 uc/os,uclinux的移植也復雜得多。一般而言要移植uclinux,目標處理器除了應滿足上述uc/os應滿足的條件外,還需要具有足夠容量(幾百k位元組以上)外部rom和ram。
uclinux的移植大致可以分為3個層次:
·結構層次的移植,如果待移植處理器的結構不同於任何已經支持的處理器結構,則需要修改linux/arch目錄下相關處理器結構的文件。雖然uclinux內核代碼的大部分是獨立於處理器和其體系結構的,但是其最低級的代碼也是特定於各個系統的。這主要表現在它們的中斷處理上下文、內存映射的維護、任務上下文和初始化過程都是獨特的。這些例行程序位於linux/arch/目錄下。由於linux所支持體系結構的種類繁多,所以對一個新型的體系,其低級常式可以模仿與其相似的體系常式編寫。
·平台層次的移植,如果待移植處理器是某種uclinux已支持體系的分支處理器,則需要在相關體系結構目錄下建立相應目錄並編寫相應代碼。如mc68ez328就是基於無mmu的m68k內核的。此時的移植需要創建 linux/arch/m68knommu/platform/ mc68ez328目錄並在其下編寫跟蹤程序(實現用戶程序到內核函數的介面等功能)、中斷控制調度程序和向量初始化程序等。
·板級移植,如果你所用處理器已被uclinux支持的話,就只需要板級移植了。板級移植需要在linux/arch/?platform/中建立一個相應板的目錄,再在其中建立相應的啟動代碼crt0_rom.s或crt0_ram.s和鏈接描述文檔rom.ld或ram.ld就可以了。板級移植還包括驅動程序的編寫和環境變數設置等內容。
結語
通過對uc/os和uclinux的比較,可以看出這兩種操作系統在應用方面各有優劣。 uc/os佔用空間少,執行效率高,實時性能優良,且針對新處理器的移植相對簡單。uclinux則佔用空間相對較大,實時性能一般,針對新處理器的移植相對復雜。但是,uclinux具有對多種文件系統的支持能力、內嵌了tcp/ip協議,可以借鑒linux豐富的資源,對一些復雜的應用,uclinux具有相當優勢。例如cisco 公司的 2500/3000/4000 路由器就是基於uclinux操作系統開發的。總之,操作系統的選擇是由嵌入式系統的需求決定的。簡單的說就是,小型控制系統可充分利用uc/os小巧且實時性強的優勢,如果開發pda和互聯網連接終端等較為復雜的系統則uclinux是不錯的選擇。
還有就是如果從開發的工具方便好用,易用的角度來看,那些收費的系統用起來更爽一些
H. UC/OS與Linux操作系統的區別
uc/os比較簡單一點,開始學的uc/os,感覺沒意思了就開始學linux,感覺ucos只是在單片機上跑跑,像arm9的一般是跑linux。其實先學哪個都差不多,因為學習方法大不相同,差別太大了,ucos太簡單,就一些信號量,郵箱什麼的,懂了也就會了,linux有點難,涉及知識太多,光是涉及內核以外的編程就需要大把大把的經典書籍去看。興趣很重要,都靠興趣過來的。
I. C++ int i[233];我直接這樣寫代表了什麼意思
C++ int i[233];直接這樣寫代表了,定義了一個整形的數組,共有233個整形元素,數組的名字叫做i。