[轉貼]One CPU and HyperThreading and DuelCore
Hyper-Threading 的中文翻譯成『超執行緒』, 同樣簡稱為『H.T.』。
用比較淺顯的概念來說, Hyper-Threading 就是把1 個實體的CPU『模擬』成2 顆CPU、增加處理器運作效率, 藉以有效地善用資源、減少系統資源的浪費:
既然Hyper-Threading 是『模擬』2 顆CPU 的效果, 故其效能是無法等同 2 顆真正的CPU, 或1 顆雙核心的CPU;但比起原先單一架構的處理器, 確實可提高不少的執行效率。
雙核心
目前CPU 已經發展到了多核心的架構, 而雙核心的CPU 已經逐漸變成主流。雙核心相當於將兩個CPU (核心)包裝在單一個CPU 晶片上。
不同於剛剛提過的Hyper-Threading 技術, 是將單一核心的CPU 模擬成兩個核心, 而是真正雙實體核心的CPU, 其效能是遠超過使用Hyper-Threading 技術的單核心CPU 。
以簡單的概念來解釋, 若將CPU 比喻為廚房, 那麼單核心就像只有一位廚師, 一次只能做一道菜, 必須做完一道菜之後才能再做下一道。
若加上Hyper-Threading 技術, 那麼這個廚師就有能力一次做兩道菜, 只不過這個廚師必須分心輪流做這兩道菜。
而雙核心就像是兩個廚師, 可以同時專心做兩道菜, 當然速度要比一個廚師快得多了。若是再加上Hyper-Threading 技術, 那麼這兩位廚師就可以同時做四道菜了!
Intel 的介紹雙核心與Hyper-Threading(圖文語音並茂)
資料來源:
第二章 中央處理單元
2008年7月24日 星期四
2008年7月20日 星期日
EnhCommentify.vim : comment lines in a program
在做porting時常有大量註解一段程式的需要,每次用/* */一個一個打或是#if 0 #endif超辛苦。
如果能在vim裏能自動化這種功能就太棒了。
結果真的有人寫了這種功能的plugin for vim名字叫EnhancedCommentify^^
有興趣的人可以去下載來玩看看。
把EnhancedCommentify.vim copy到/usr/share/vim/vim62/ <==(請依自己的實際路徑修改)
安裝好之後,隨便開一個.c or .cpp檔案,先進visual mode選取一段文字,然後按下
"\x" (反斜線+x)
你就會發現這些文字全部被註解掉了!要恢復的話,同樣地選取起來,再按一下"\x"就可以了。
官網
下載網頁
如果能在vim裏能自動化這種功能就太棒了。
結果真的有人寫了這種功能的plugin for vim名字叫EnhancedCommentify^^
有興趣的人可以去下載來玩看看。
把EnhancedCommentify.vim copy到/usr/share/vim/vim62/ <==(請依自己的實際路徑修改)
安裝好之後,隨便開一個.c or .cpp檔案,先進visual mode選取一段文字,然後按下
"\x" (反斜線+x)
你就會發現這些文字全部被註解掉了!要恢復的話,同樣地選取起來,再按一下"\x"就可以了。
官網
下載網頁
2008年7月16日 星期三
大家一起來 網路標會錢滾錢
看起來似乎不錯^^不過當你得標後,銀行會取得標的總金額1.5~3%@@但感覺上比一般民間的標會有保障的感覺^^
大家一起來 網路標會錢滾錢
撰文:楊秀清 <我的錢會滾錢>
負薪資時代來臨,只有學會更多理財技能,才能為自己「加薪」本文用圖解方式,教你如何上網進行標會理財,炎炎夏日,節流之餘,一起來學開源術吧!
美莉畢業不到3年,由於不懂理財,也懶得理財,每個月乖乖把薪水三分之一放定存,原本以為自己是個小富婆的她,今年看到新聞一直報導負利率、薪資負成長的消息,周遭生活物資漲不斷,她這才驚覺再不理財,恐怕成不了「富女」,反而會成為財產縮水的「負女」。
近期她看媒體報導,有銀行推出保證履約的網路標會平台,腦中閃過童年時父母標會存錢賺利息的畫面,她想,既然銀行保證不會被倒會,不如用網路標會作為她理財的第一步,上網賺利息錢,再拿標會賺到的錢去投資理財,問題是怎樣才能參加網路標會?以及如何在網路標會平台上操作?
目前國內只有永豐銀行推出MMA標會理財網,因此本文以該銀行網路標會平台為例,用Step by Step方式一次告訴你。
步驟1 確認是否符合申請網路標會資格
首先,美莉須先看看她符不符合申請成為MMA標會理財網正式會員的資格,條件如下:
擁有中華民國身分證。
必須在中華民國境內申請。
年滿20歲以上。
現職滿半年。
有固定月收入。
年所得40萬以上。
每月收支結餘後,尚有能力進行儲蓄或投資者。
信用狀況良好,無不良紀錄者。
目前無使用現金卡者。
步驟2 線上申請成為正式會員
接著,美莉才能在MMA標會理財網線上申請成為正式會員,只有成為正式會員,才可以參與MMA標會理財網的標會組合、討論區和部落格互動,以及享有所有會員權利。
不管是否曾與永豐銀行往來,只要上網申請並填寫資料,就會有專人協助你完成後續的相關程序,不過,該網站將保留最終核准與否的權利。線上申請成為該網站正式會員的操作步驟如下:
首先,請上MMA標會理財網首頁(www.bidmoney.com.tw)→會員專區→加入會員→正式會員申請。
Step 1:設定你的暱稱和密碼
Step 2:填寫你的個人資料
Step 3:填寫進階資料
Step 4:填寫你的約定開戶資料
Step 5:核准成功,收到啟用e-mail
Step 6:登入網站、設定標會密碼
Step 7:申請完成!可以參與標會囉!
◎美莉也可以先申請一般會員
除了正式會員之外,如果美莉還不確認自己是否真的要參加網路標會,也可以先參加一般會員,先行體驗及暖身一下,待進一步了解網路標會的運作及功能之後,再申請升級為正式會員。
申請一般會員無條件限制,可享有在討論區及部落格的互動權利,以及「追蹤」標會組合的權利,只要在線上註冊即完成。申請一般會員路徑:進入MMA標會理財網首頁(www.bidmoney.com.tw)→會員專區→加入會員→一般會員申請。
步驟3 加入新的標會
→首頁→標會大街→加入標會組合→試算→試算結果→加入標會→首期競標出價選標金→確認加入
Step 1:在MMA標會理財網首頁(www.bidmoney.com.tw)首頁登入會員後進入「標會大街」,你會看到系統預設出現的畫面就是目前「幕集中標會」,如果需要觀摩一下其他人已完成募集的標會,則可以選擇「完成募集標會」。在左側並提供「查詢標會」功能選項,你可以依據查詢種類、公佈日、會期、會錢等項目查詢,找到更適合自己的標會,也可以直接用標會編號來查詢。
Step 2:選擇其中一組標會組合,並在「我要加入」之下點選「○ˇ 」。
Step 3:接著畫面會自動跳出標會試算功能,為了方便跟大家分享彼此的理財目的,需要填寫你加入該標會組合的目的,並以此目的名稱作為你在該標會組合中的名稱,方便你進行理財目標管理,再選擇希望得標的意願強度,填寫標金出價多少,即可試算你的得標總標金。
Step 4:參考試算結果,並加入標會
Step 5:選擇本期是否參與競標,如不參加,則在「選擇競標金額」中選「本期不競標」,如要參與競標,則在「選擇競標金額」中選出你想設定的標金金額,再按「確認競標」即可。
Step 6:即使你在Step 5選定競標出價金額,並確認競標,在此,你還可以修改競標金額,最後在確認競標金額無誤之後,點選「確認加入」即完成加入該標會組合的作業。另,只要在競標期間截止之前,已經競標出價的會員仍可以進行競標金額修改。
◎如何選擇速配的標會組合
美莉看到的標會組合很多,不知如何選擇,她可依據以下的步驟,找出適合自己的標會組合。
Step 1:登入正式會員後→標會大街→查詢,透過查詢功能尋找適合的標會
Step 2:選擇其中一組標會組合,並按「標會編號」進入該標會。
Step 3:輸入試算所需資料,並詳閱電子會單中標會組合的內容,也可以參考目前已加入會員的募集狀況:年齡、性別、投資目的、競標強度、是否已競標等,來判斷該標會組合的得標金水準,如果是想得標的人,應加入得標金可能會較低的標會組合,以降資金成本;如果是不想得標的人,應加入得標金可能會較高的標會組合,以賺多一點利息收入,這一點跟傳統標會是很類似的,都是你可以事前研判加入哪一標會對妳最有利的重要工具。
Step 4:依募集狀況產生的試算結果,估算可運用的總標金,再根據試算結果決定是否加入標會、加入追蹤或是重新試算。
大家一起來 網路標會錢滾錢
撰文:楊秀清 <我的錢會滾錢>
負薪資時代來臨,只有學會更多理財技能,才能為自己「加薪」本文用圖解方式,教你如何上網進行標會理財,炎炎夏日,節流之餘,一起來學開源術吧!
美莉畢業不到3年,由於不懂理財,也懶得理財,每個月乖乖把薪水三分之一放定存,原本以為自己是個小富婆的她,今年看到新聞一直報導負利率、薪資負成長的消息,周遭生活物資漲不斷,她這才驚覺再不理財,恐怕成不了「富女」,反而會成為財產縮水的「負女」。
近期她看媒體報導,有銀行推出保證履約的網路標會平台,腦中閃過童年時父母標會存錢賺利息的畫面,她想,既然銀行保證不會被倒會,不如用網路標會作為她理財的第一步,上網賺利息錢,再拿標會賺到的錢去投資理財,問題是怎樣才能參加網路標會?以及如何在網路標會平台上操作?
目前國內只有永豐銀行推出MMA標會理財網,因此本文以該銀行網路標會平台為例,用Step by Step方式一次告訴你。
首先,美莉須先看看她符不符合申請成為MMA標會理財網正式會員的資格,條件如下:
擁有中華民國身分證。
必須在中華民國境內申請。
年滿20歲以上。
現職滿半年。
有固定月收入。
年所得40萬以上。
每月收支結餘後,尚有能力進行儲蓄或投資者。
信用狀況良好,無不良紀錄者。
目前無使用現金卡者。
接著,美莉才能在MMA標會理財網線上申請成為正式會員,只有成為正式會員,才可以參與MMA標會理財網的標會組合、討論區和部落格互動,以及享有所有會員權利。
不管是否曾與永豐銀行往來,只要上網申請並填寫資料,就會有專人協助你完成後續的相關程序,不過,該網站將保留最終核准與否的權利。線上申請成為該網站正式會員的操作步驟如下:
首先,請上MMA標會理財網首頁(www.bidmoney.com.tw)→會員專區→加入會員→正式會員申請。
Step 1:設定你的暱稱和密碼
Step 2:填寫你的個人資料
Step 3:填寫進階資料
Step 4:填寫你的約定開戶資料
Step 5:核准成功,收到啟用e-mail
Step 6:登入網站、設定標會密碼
Step 7:申請完成!可以參與標會囉!
◎美莉也可以先申請一般會員
除了正式會員之外,如果美莉還不確認自己是否真的要參加網路標會,也可以先參加一般會員,先行體驗及暖身一下,待進一步了解網路標會的運作及功能之後,再申請升級為正式會員。
申請一般會員無條件限制,可享有在討論區及部落格的互動權利,以及「追蹤」標會組合的權利,只要在線上註冊即完成。申請一般會員路徑:進入MMA標會理財網首頁(www.bidmoney.com.tw)→會員專區→加入會員→一般會員申請。
→首頁→標會大街→加入標會組合→試算→試算結果→加入標會→首期競標出價選標金→確認加入
Step 1:在MMA標會理財網首頁(www.bidmoney.com.tw)首頁登入會員後進入「標會大街」,你會看到系統預設出現的畫面就是目前「幕集中標會」,如果需要觀摩一下其他人已完成募集的標會,則可以選擇「完成募集標會」。在左側並提供「查詢標會」功能選項,你可以依據查詢種類、公佈日、會期、會錢等項目查詢,找到更適合自己的標會,也可以直接用標會編號來查詢。
Step 2:選擇其中一組標會組合,並在「我要加入」之下點選「○ˇ 」。
Step 3:接著畫面會自動跳出標會試算功能,為了方便跟大家分享彼此的理財目的,需要填寫你加入該標會組合的目的,並以此目的名稱作為你在該標會組合中的名稱,方便你進行理財目標管理,再選擇希望得標的意願強度,填寫標金出價多少,即可試算你的得標總標金。
Step 4:參考試算結果,並加入標會
Step 5:選擇本期是否參與競標,如不參加,則在「選擇競標金額」中選「本期不競標」,如要參與競標,則在「選擇競標金額」中選出你想設定的標金金額,再按「確認競標」即可。
Step 6:即使你在Step 5選定競標出價金額,並確認競標,在此,你還可以修改競標金額,最後在確認競標金額無誤之後,點選「確認加入」即完成加入該標會組合的作業。另,只要在競標期間截止之前,已經競標出價的會員仍可以進行競標金額修改。
◎如何選擇速配的標會組合
美莉看到的標會組合很多,不知如何選擇,她可依據以下的步驟,找出適合自己的標會組合。
Step 1:登入正式會員後→標會大街→查詢,透過查詢功能尋找適合的標會
Step 2:選擇其中一組標會組合,並按「標會編號」進入該標會。
Step 3:輸入試算所需資料,並詳閱電子會單中標會組合的內容,也可以參考目前已加入會員的募集狀況:年齡、性別、投資目的、競標強度、是否已競標等,來判斷該標會組合的得標金水準,如果是想得標的人,應加入得標金可能會較低的標會組合,以降資金成本;如果是不想得標的人,應加入得標金可能會較高的標會組合,以賺多一點利息收入,這一點跟傳統標會是很類似的,都是你可以事前研判加入哪一標會對妳最有利的重要工具。
Step 4:依募集狀況產生的試算結果,估算可運用的總標金,再根據試算結果決定是否加入標會、加入追蹤或是重新試算。
2008年6月27日 星期五
Linux-Tiny
http://www.selenic.com/linux-tiny/
About Linux-Tiny
The -tiny tree is a series of patches against the 2.6 mainline Linux kernel to reduce its memory and disk footprint, as well as to add features to aid working on small systems. Target users are developers of embedded system and users of small or legacy machines such as 386s and handhelds.
Just about all features are option via the kernel configuration system and are available as separate patches. Linux-tiny by default will build a kernel practically identical to mainline, but custom configurations with full console, disk, and network support can be booted on standard hardware with as little as 2MB of RAM.
不知道情況是如何,改天要來試一下看看
About Linux-Tiny
The -tiny tree is a series of patches against the 2.6 mainline Linux kernel to reduce its memory and disk footprint, as well as to add features to aid working on small systems. Target users are developers of embedded system and users of small or legacy machines such as 386s and handhelds.
Just about all features are option via the kernel configuration system and are available as separate patches. Linux-tiny by default will build a kernel practically identical to mainline, but custom configurations with full console, disk, and network support can be booted on standard hardware with as little as 2MB of RAM.
不知道情況是如何,改天要來試一下看看
2008年6月8日 星期日
Save Me From Myself
Save Me From Myself歌詞-Christina Aguilera(克里斯汀娜.阿吉莱拉)
http://www.youtube.com/watch?v=HYxmS4UNrWY
It's not so easy loving me
愛上我不是件容易事
It gets so complicated
這變得如此複雜
All the things you've gotta be
你面對的一切
Everything's changin
事事都在變
But you're the truth
就你不變
I'm amazed by all your patience
我好驚訝你竟都能耐心等候
Everything I put you through
那些我讓你經歷的一切
And when I'm about to fall
當我快跌倒時
Somehow you're always waitin with
你總能在那等著抓牢我
Your open arms to catch me
用你打開的雙臂
You're gonna save me from myself
你將拯救我
from myself, yes
You're gonna save me from myself
你將拯救最深處的我
My love is tainted by your touch
你的輕觸感染了我愛戀的氛圍
Well some guys have shown me aces
是有些不錯的人
But you've got that royal flush
但你就是我的首選
I know it's crazy everyday
我知道每天都這麼瘋狂不定
Well tomorrow maybe shaky
也許明日搖搖欲墜
But you never turn away
但你從不轉身離去
Don't ask me why I'm cryin
別問我為什麼哭
'Cause when I start to crumble
因為每當我快崩潰時
You know how to keep me smilin
你總是讓我破涕為笑
You always save me from myself
你將拯救我
from myself, myself
You're gonna save me from myself
你將拯救最深處的我
I know it's hard, it's hard
我知道真的很難 很難
But you've broken all my walls
但你已攻進我的心牆
You've been my strength, so strong
你是我的力量
And don't ask me why I love you
也別問我為什麼那麼愛你
It's obvious your tenderness
顯然你的溫柔體貼
Is what I need to make me a better woman to myself
是使我成為一個更棒的女人
to myself, myself
讓我自己
You're gonna save me from myself
你將拯救最深處的我
http://www.youtube.com/watch?v=HYxmS4UNrWY
It's not so easy loving me
愛上我不是件容易事
It gets so complicated
這變得如此複雜
All the things you've gotta be
你面對的一切
Everything's changin
事事都在變
But you're the truth
就你不變
I'm amazed by all your patience
我好驚訝你竟都能耐心等候
Everything I put you through
那些我讓你經歷的一切
And when I'm about to fall
當我快跌倒時
Somehow you're always waitin with
你總能在那等著抓牢我
Your open arms to catch me
用你打開的雙臂
You're gonna save me from myself
你將拯救我
from myself, yes
You're gonna save me from myself
你將拯救最深處的我
My love is tainted by your touch
你的輕觸感染了我愛戀的氛圍
Well some guys have shown me aces
是有些不錯的人
But you've got that royal flush
但你就是我的首選
I know it's crazy everyday
我知道每天都這麼瘋狂不定
Well tomorrow maybe shaky
也許明日搖搖欲墜
But you never turn away
但你從不轉身離去
Don't ask me why I'm cryin
別問我為什麼哭
'Cause when I start to crumble
因為每當我快崩潰時
You know how to keep me smilin
你總是讓我破涕為笑
You always save me from myself
你將拯救我
from myself, myself
You're gonna save me from myself
你將拯救最深處的我
I know it's hard, it's hard
我知道真的很難 很難
But you've broken all my walls
但你已攻進我的心牆
You've been my strength, so strong
你是我的力量
And don't ask me why I love you
也別問我為什麼那麼愛你
It's obvious your tenderness
顯然你的溫柔體貼
Is what I need to make me a better woman to myself
是使我成為一個更棒的女人
to myself, myself
讓我自己
You're gonna save me from myself
你將拯救最深處的我
2008年5月13日 星期二
uClinux内核移植相關代碼分析
uClinux内核移植相關代碼分析
本文通過整理之前研發的一個項目(ARM7TDMI + uCLinux),分析内核啓動過程及需要修改的文件,以供内核移植者參考。整理過程中也同時參考了衆多網友的帖子,在此謝過。由于整理過程匆忙,難免錯誤及講解的不够清楚之處,請各位網友指正,這裏提前謝過。本文分以下部分進行介紹:
Bootloader及内核解壓
内核啓動方式介紹
内核啓動地址的確定
arch/armnommu/kernel/head-armv.S分析
start_kernel()函數分析
1. Bootloader及内核解壓
Bootloader將内核加載到内存中,設定一些寄存器,然後將控制權交由内核,該過程中,關閉MMU功能。通常,内核都是以壓縮的方式存放,如zImage,這裏有兩種解壓方法:
使用内核自解壓程序。
arch/arm/boot/compressed/head.S或arch/arm/boot/compressed/head-xxxxx.S
arch/arm/boot/compressed/misc.c
在Bootloader中增加解壓功能。
使用該方法時内核不需要帶有自解壓功能,而使用Bootloader中的解壓程序代替内核自解壓程序。其工作過程與内核自解壓過程相似:Bootloader把壓縮方式的内核解壓到内存中,然後跳轉到内核入口處開始執行。
2. 幾種内核啓動方式介紹
XIP (EXECUTE IN PLACE) 是指直接從存放代碼的位置上啓動運行。
2.1 非壓縮,非XIP
非XIP方式是指在運行之前需對代碼進行重定位。該類型的内核以非壓縮方式存放在Flash中,啓動時由Bootloader加載到内存後運行。
2.2 非壓縮,XIP
該類型的内核以非壓縮格式存放在ROM/Flash中,不需要加載到内存就能運行,Bootloader直接跳轉到其存放地址執行。Data段復制和BSS 段清零的工作由内核自己完成。這種啓動方式常用于内存空間有限的系統中,另外,程序在ROM/Flash中運行的速度相對較慢。
2.3 RAM自解壓
壓縮格式的内核由開頭一段自解壓代碼和壓縮内核數據組成,由于以壓縮格式存放,内核只能以非XIP方式運行。RAM自解壓過程如下:壓縮内核存放于 ROM/Flash中,Bootloader啓動後加載到内存中的臨時空間,然後跳轉到壓縮内核入口地址執行自解壓代碼,内核被解壓到最終的目的地址然後運行。壓縮内核所占據的臨時空間隨後被Linux回收利用。這種方式的内核在嵌入式産品中較為常見。
2.4 ROM自解壓
解壓縮代碼也能够以XIP的方式在ROM/Flash中運行。ROM自解壓過程如下:壓縮内核存放在ROM/Flash中,不需要加載到内存就能運行, Bootloader直接跳轉到其存放地址執行其自解壓代碼,將壓縮内核解壓到最終的目的地址并運行。ROM自解壓方式存放的内核解壓縮速度慢,而且也不能節省内存空間。
3. 内核啓動地址的確定
内核自解壓方式
Head.S/head-XXX.S獲得内核解壓後首地址ZREALADDR,然後解壓内核,并把解壓後的内核放在ZREALADDR的位置上,最後跳轉到ZREALADDR地址上,開始真正的内核啓動。
arch/armnommu/boot/Makefile,定義ZRELADDR和 ZTEXTADDR。ZTEXTADDR是自解壓代碼的起始地址,如果從内存啓動内核,設置為0即可,如果從Rom/Flash啓動,則設置 ZTEXTADDR為相應的值。ZRELADDR是内核解壓縮後的執行地址。
arch/armnommu/boot/compressed/vmlinux.ld,引用LOAD_ADDR和TEXT_START。
arch/armnommu/boot/compressed/Makefile, 通過如下一行:
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;
使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。
説明:
執行完decompress_kernel函數後,代碼跳回head.S/head-XXX.S中,檢查解壓縮之後的kernel起始地址是否緊挨着 kernel image。如果是,beq call_kernel,執行解壓後的kernel。如果解壓縮之後的kernel起始地址不是緊挨着kernel image,則執行relocate,將其拷貝到緊接着kernel image的地方,然後跳轉,執行解壓後的kernel。
Bootloader解壓方式
Bootloader把解壓後的内核放在内存的TEXTADDR位置上,然後跳轉到TEXTADDR位置上,開始内核啓動。
arch/armnommu/Makefile,一般設置TEXTADDR為PAGE_OFF+0x8000,如定義為0x00008000, 0xC0008000等。
arch/armnommu/vmlinux.lds,引用TEXTADDR
4. arch/armnommu/kernel/head-armv.S
該文件是内核最先執行的一個文件,包括内核入口ENTRY(stext)到start_kernel間的初始化代碼,主要作用是檢查CPU ID,Architecture Type,初始化BSS等操作,并跳到start_kernel函數。在執行前,處理器應滿足以下狀態:
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
/* 部分源代碼分析 */
/* 内核入口點 */
ENTRY(stext)
/* 程序狀態,禁止FIQ、IRQ,設定SVC模式 */
mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode
/* 置當前程序狀態寄存器 */
msr cpsr_c, r0 @ and all irqs disabled
/* 判斷CPU類型,查找運行的CPU ID值與Linux編譯支持的ID值是否支持 */
bl __lookup_processor_type
/* 跳到__error */
teq r10, #0 @ invalid processor?
moveq r0, #p @ yes, error p
beq __error
/* 判斷體系類型,查看R1寄存器的Architecture Type值是否支持 */
bl __lookup_architecture_type
/* 不支持,跳到出錯 */
teq r7, #0 @ invalid architecture?
moveq r0, #a @ yes, error a
beq __error
/* 創建核心頁表 */
bl __create_page_tables
adr lr, __ret @ return address
add pc, r10, #12 @ initialise processor
/* 跳轉到start_kernel函數 */
b start_kernel
__lookup_processor_type 這個函數根據芯片的ID從proc.info獲取proc_info_list結構,proc_info_list結構定義在include/asm- armnommu/proginfo.h中,該結構的數據定義在arch/armnommu/mm/proc-arm*.S文件中,ARM7TDMI系列芯片的proc_info_list數據定義在arch/armnommu/mm/proc-arm6,7.S文件中。函數 __lookup_architecture_type從arch.info獲取machine_desc結構,machine_desc結構定義在 include/asm-armnommu/mach/arch.h中,針對不同arch的數據定義在arch/armnommu/mach- */arch.c文件中。
在這裏如果知道processor_type和architecture_type,可以直接對相應寄存器進行賦值。
5. start_kernel()函數分析
下面對start_kernel()函數及其相關函數進行分析。
5.1 lock_kernel()
/* Getting the big kernel lock.
* This cannot happen asynchronously,
* so we only need to worry about other
* CPUs.
*/
extern __inline__ void lock_kernel(void)
{
if (!++current->lock_depth)
spin_lock(&kernel_flag);
}
kernel_flag 是一個内核大自旋鎖,所有進程都通過這個大鎖來實現向内核態的遷移。只有獲得這個大自旋鎖的處理器可以進入内核,如中斷處理程序等。在任何一對 lock_kernel/unlock_kernel函數裏至多可以有一個程序占用CPU。進程的lock_depth成員初始化為-1,在kerenl/fork.c文件中設置。在它小于0時(恒為 -1),進程不擁有内核鎖;當大于或等于0時,進程得到内核鎖。
5.2 setup_arch()
setup_arch()函數做體系相關的初始化工作,函數的定義在arch/armnommu/kernel/setup.c文件中,主要涉及下列主要函數及代碼。
5.2.1 setup_processor()
該函數主要通過
for (list = &__proc_info_begin; list < &__proc_info_end ; list++)
if ((processor_id & list->cpu_mask) == list->cpu_val)
break;
這様一個循環來在.proc.info段中尋找匹配的processor_id,processor_id在head_armv.S文件
中設置。
5.2.2 setup_architecture(machine_arch_type)
該函數獲得體系結構的信息,返回mach-xxx/arch.c 文件中定義的machine結構體的指針,包含以下内容:
MACHINE_START (xxx, “xxx”)
MAINTAINER ("xxx")
BOOT_MEM (xxx, xxx, xxx)
FIXUP (xxx)
MAPIO (xxx)
INITIRQ (xxx)
MACHINE_END
5.2.3内存設置代碼
if (meminfo.nr_banks == 0)
{
meminfo.nr_banks = 1;
meminfo.bank[0].start = PHYS_OFFSET;
meminfo.bank[0].size = MEM_SIZE;
}
meminfo 結構表明内存情况,是對物理内存結構meminfo的默認初始化。 nr_banks指定内存塊的數量,bank指定每塊内存的範圍,PHYS _OFFSET指定某塊内存塊的開始地址,MEM_SIZE指定某塊内存塊長度。PHYS _OFFSET和MEM_SIZE都定義在include/asm-armnommu/arch-XXX/memory.h文件中,其中PHYS _OFFSET是内存的開始地址,MEM_SIZE就是内存的結束地址。這個結構在接下來内存的初始化代碼中起重要作用。
5.2.4 内核内存空間管理
init_mm.start_code = (unsigned long) &_text; 内核代碼段開始
init_mm.end_code = (unsigned long) &_etext; 内核代碼段結束
init_mm.end_data = (unsigned long) &_edata; 内核數據段開始
init_mm.brk = (unsigned long) &_end; 内核數據段結束
每一個任務都有一個mm_struct結構管理其内存空間,init_mm 是内核的mm_struct。其中設置成員變量* mmap指向自己, 意味着内核只有一個内存管理結構,設置 pgd=swapper_pg_dir,
swapper_pg_dir是内核的頁目録,ARM體系結構的内核頁目録大小定義為16k。init_mm定義了整個内核的内存空間,内核綫程屬于内核代碼,同様使用内核空間,其訪問内存空間的權限與内核一様。
5.2.5 内存結構初始化
bootmem_init (&meminfo)函數根據meminfo進行内存結構初始化。bootmem_init(&meminfo)函數中調用 reserve_node_zero(bootmap_pfn, bootmap_pages) 函數,這個函數的作用是保留一部分内存使之不能被動態分配。這些内存塊包括:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext); /*内核所占用地址空間*/
reserve_bootmem_node(pgdat, bootmap_pfn << PAGE_SHIFT, bootmap_pages << PAGE_SHIFT)
/*bootmem結構所占用地址空間*/
5.2.6 paging_init(&meminfo, mdesc)
創建内核頁表,映射所有物理内存和IO空間,對于不同的處理器,該函數差彆比較大。下面簡單描述一下ARM體系結構的存儲系統及MMU相關的概念。
在ARM 存儲系統中,使用内存管理單元(MMU)實現虚擬地址到實際物理地址的映射。利用MMU,可把SDRAM的地址完全映射到0x0起始的一片連續地址空間,而把原來占據這片空間的FLASH或者ROM映射到其他不相衝突的存儲空間位置。例如,FLASH的地址從0x0000 0000~0x00FFFFFF,而SDRAM的地址範圍是0x3000 0000~0x3lFFFFFF,則可把SDRAM地址映射為0x0000 0000~0xlFFFFFF,而FLASH的地址可以映射到0x9000 0000~0x90FFFFFF(此處地址空間為空閑,未被占用)。映射完成後,如果處理器發生异常,假設依然為IRQ中斷,PC指針指向0xl8處的地址,而這個時候PC實際上是從位于物理地址的0x3000 0018處讀取指令。通過MMU的映射,則可實現程序完全運行在SDRAM之中。在實際的應用中.可能會把兩片不連續的物理地址空間分配給SDRAM。而在操作系統中,習慣于把SDRAM的空間連續起來,方便内存管理,且應用程序申請大塊的内存時,操作系統内核也可方便地分配。通過MMU可實現不連續的物理地址空間映射為連續的虚擬地址空間。操作系統内核或者一些比較關鍵的代碼,一般是不希望被用户應用程序訪問。通過MMU可以控制地址空間的訪問權限,從而保護這些代碼不被破壞。
MMU的實現過程,實際上就是一個查表映射的過程。建立頁表是實現MMU功能不可缺少的一步。頁表位于系統的内存中,頁表的每一項對應于一個虚擬地址到物理地址的映射。每一項的長度即是一個字的長度(在ARM中,一個字的長度被定義為4Bytes)。頁表項除完成虚擬地址到物理地址的映射功能之外,還定義了訪問權限和緩衝特性等。
MMU的映射分為兩種,一級頁表的變换和二級頁表變换。兩者的不同之處就是實現的變换地址空間大小不同。一級頁表變换支持1 M大小的存儲空間的映射,而二級可以支持64 kB,4 kB和1 kB大小地址空間的映射。
動態表(頁表)的大小=表項數*每個表項所需的位數,即為整個内存空間建立索引表時,需要多大空間存放索引表本身。
表項數=虚擬地址空間/每頁大小
每個表項所需的位數=Log(實際頁表數)+適當控制位數
實際頁表數 =物理地址空間/每頁大小
下面分析paging_init()函數的代碼。
在paging_init中分配起始頁(即第0頁)地址:
zero_page = 0xCXXXXXXX
memtable_init(mi); 如果當前微處理器帶有MMU,則為系統内存創建頁表;如果當前微處理器不支持MMU,比如ARM7TDMI上移植uCLinux操作系統時,則不需要此類步驟。可以通過如下一個宏定義實現靈活控制,對于帶有MMU的微處理器而言,memtable_init(mi)是paging_init()中最重要的函數。
#ifndef CONFIG_UCLINUX
/* initialise the page tables. */
memtable_init(mi);
……(此處省略若乾代碼)
free_area_init_node(node, pgdat, 0, zone_size,
bdata->node_boot_start, zhole_size);
}
#else /* 針對不帶MMU微處理器 */
{
/*****************************************************/
定義物理内存區域管理
/*****************************************************/
unsigned long zone_size[MAX_NR_ZONES] = {0,0,0};
zone_size[ZONE_DMA] = 0;
zone_size[ZONE_NORMAL] = (END_MEM - PAGE_OFFSET) >> PAGE_SHIFT;
free_area_init_node(0, NULL, NULL, zone_size, PAGE_OFFSET, NULL);
}
#endif
uCLinux與其它嵌入式Linux最大的區彆就是MMU管理這一塊,從上面代碼就明顯可以看到這點區彆。下面繼續討論針對帶MMU的微處理器的内存管理。
void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;
unsigned long address = 0;
int i;
init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);
/*******************************************************/
其中map_desc定義為:
struct map_desc {
unsigned long virtual;
unsigned long physical;
unsigned long length;
int domain:4, // 頁表的domain
prot_read:1, // 讀保護標志
prot_write:1, // 寫保護標志
cacheable:1, // 是否使用cache
bufferable:1, // 是否使用write buffer
last:1; //空
};init_maps /* map_desc是區段及其屬性的定義 */
下面代碼對meminfo的區段進行遍歷,在嵌入式系統中列舉所有可映射的内存,例如32M SDRAM, 4M FLASH等,用meminfo記録這些内存區段。同時填寫init_maps 中的各項内容。meminfo結構如下:
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/********************************************************/
for (i = 0; i < mi->nr_banks; i++)
{
if (mi->bank.size == 0)
continue;
p->physical = mi->bank.start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank.size;
p->domain = DOMAIN_KERNEL;
p->prot_read = 0;
p->prot_write = 1;
p->cacheable = 1; //使用Cache
p->bufferable = 1; //使用write buffer
p ++; //下一個區段
}
/* 如果系統存在FLASH,執行以下代碼 */
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->domain = DOMAIN_KERNEL;
p->prot_read = 1;
p->prot_write = 0;
p->cacheable = 1;
p->bufferable = 1;
p ++;
#endif
/***********************************************************/
接下來的代碼是逐個區段建立頁表
/***********************************************************/
q = init_maps;
do {
if (address < q->virtual || q == p) {
/*******************************************************************************/
由于内核空間是從某個地址開始,如0xC0000000,所以0xC000 0000 以前的頁表項全部清空
clear_mapping在mm-armv.c中定義,其中clear_mapping()是個宏,根據處理器的不同,可以被展開為如下代碼
cpu_XXX_set_pmd(((pmd_t *)(((&init_mm )->pgd+ (( virt) >> 20 )))),((pmd_t){( 0 )}));
其中init_mm為内核的mm_struct,pgd指向 swapper_pg_dir,在arch/arm/kernel/init_task.c中定義。cpu_XXX_set_pmd定義在 proc_armXXX.S文件中,參見ENTRY(cpu_XXX_set_pmd) 處代碼。
/*********************************************************************************/
clear_mapping(address);
/* 每個表項增加1M */
address += PGDIR_SIZE;
} else {
/* 構建内存頁表 */
create_mapping(q);
address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;
q ++;
}
} while (address != 0);
/ * create_mapping函數也在mm-armv.c中定義 */
static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_pte;
long off;
/*******************************************************************************/
大部分應用中均采用1級section模式的地址映射,一個section的大小為1M,也就是説從邏輯地址到物理地址的轉變是這様的一個過程:
一個32位的地址,高12位决定了該地址在頁表中的index,這個index的内容决定了該邏輯section對應的物理section;低20位决定了該地址在section中的偏移(index)。例如:從0x0~0xFFFFFFFF的地址空間總共可以分成0x1000(4K)個 section(每個section大小為1M),頁表中每項的大小為32個bit,因此頁表的大小為0x4000(16K)。
每個頁表項的内容如下:
bit: 31 20 19 12 11 10 9 8 5 4 3 2 1 0
content: Section對應的物理地址 NULL AP 0 Domain 1 C B 1 0
最低兩位(10)是section分頁的標識。
AP:Access Permission,區分只讀、讀寫、SVC&其它模式。
Domain:每個section都屬于某個Domain,每個Domain的屬性由寄存器控制。一般都只要包含兩個Domain,一個可訪問地址空間; 另一個不可訪問地址空間。
C、B:這兩位决定了該section的cache&write buffer屬性,這與該段的用途(RO or RW)有密切關系。不同的用途要做不同的設置。
C B 具體含義
0 0 無cache,無寫緩衝,任何對memory的讀寫都反映到總綫上。對 memory 的操作過程中CPU需要等待。
0 1 無cache,有寫緩衝,讀操作直接反映到總綫上。寫操作CPU將數據寫入到寫緩衝後繼續運行,由寫緩衝進行寫回操作。
1 0 有cache,寫通模式,讀操作首先考慮cache hit;寫操作時直接將數據寫入寫緩衝,如果同時出現cache hit,那麽也更新cache。
1 1 有cache,寫回模式,讀操作首先考慮cache hit;寫操作也首先考慮cache hit。
由于ARM中section表項的權限位和page表項的位置不同, 以下代碼根據struct map_desc 中的保護標志,分彆計算頁表項中的AP, Domain和CB標志位。
/*******************************************************************************/
prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
(md->prot_read ? L_PTE_USER : 0) |
(md->prot_write ? L_PTE_WRITE : 0) |
(md->cacheable ? L_PTE_CACHEABLE : 0) |
(md->bufferable ? L_PTE_BUFFERABLE : 0);
prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) |
(md->prot_read ? PMD_SECT_AP_READ : 0) |
(md->prot_write ? PMD_SECT_AP_WRITE : 0) |
(md->cacheable ? PMD_SECT_CACHEABLE : 0) |
(md->bufferable ? PMD_SECT_BUFFERABLE : 0);
/********************************************************************/
設置虚擬地址,偏移地址和内存length
/********************************************************************/
virt = md->virtual;
off = md->physical - virt;
length = md->length;
/********************************************************************/
建立虚擬地址到物理地址的映射
/********************************************************************/
while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
while (length >= PGDIR_SIZE) {
alloc_init_section(virt, virt + off, prot_sect);
virt += PGDIR_SIZE;
length -= PGDIR_SIZE;
}
while (length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
/*************************************************************************/
create_mapping的作用是設置虚地址virt 到物理地址virt + off_set的映射頁目録和頁表。
/*************************************************************************/
/* 映射中斷向量表區域 */
init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = vectors_base();
init_maps->length = PAGE_SIZE;
init_maps->domain = DOMAIN_USER;
init_maps->prot_read = 0;
init_maps->prot_write = 0;
init_maps->cacheable = 1;
init_maps->bufferable = 0;
create_mapping(init_maps);
中斷向量表的虚地址init_maps,是用alloc_bootmem_low_pages分配的,通常是在PAGE_OFF+0x8000前面的某一頁, vectors_base()是個宏,ARM規定中斷向量表的地址只能是0或0xFFFF0000,所以上述代碼映射一頁到0或0xFFFF0000,中斷處理程序中的部分代碼也被拷貝到這一頁中。
5.3 parse_options()
分析由内核引導程序發送給内核的啓動選項,在初始化過程中按照某些選項運行,并將剩餘部分傳送給init進程。這些選項可能已經存儲在配置文件中,也可能是由用户在系統啓動時敲入的。但内核并不關心這些,這些細節都是内核引導程序關注的内容,嵌入式系統更是如此。
5.4 trap_init()
這個函數用來做體系相關的中斷處理的初始化,在該函數中調用__trap_init((void *)vectors_base())函數將exception vector設置到vectors_base開始的地址上。__trap_init函數位于entry-armv.S文件中,對于ARM處理器,共有復位、未定義指令、SWI、預取終止、數據終止、IRQ和FIQ幾種方式。SWI主要用來實現系統調用,而産生了IRQ之後,通過exception vector進入中斷處理過程,執行do_IRQ函數。
armnommu的trap_init()函數在 arch/armnommu/kernel/traps.c文件中。vectors_base是寫中斷向量的開始地址,在include/asm- armnommu/proc-armv/system.h文件中設置,地址為0或0XFFFF0000。
ENTRY(__trap_init)
stmfd sp!, {r4 - r6, lr}
mrs r1, cpsr @ code from 2.0.38
bic r1, r1, #MODE_MASK @ clear mode bits /* 設置svc模式,disable IRQ,FIQ */
orr r1, r1, #I_BIT|F_BIT|MODE_SVC @ set SVC mode, disable IRQ,FIQ
msr cpsr, r1
adr r1, .LCvectors @ set up the vectors
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} /* 拷貝异常向量 */
add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 - r6, pc})
__stubs_start到__stubs_end的地址中包含了异常處理的代碼,因此拷貝到vectors_base+0x200的位置上。
5.5 init_IRQ()
void __init init_IRQ(void)
{
extern void init_dma(void);
int irq;
for (irq = 0; irq < NR_IRQS; irq++) {
irq_desc[irq].probe_ok = 0;
irq_desc[irq].valid = 0;
irq_desc[irq].noautoenable = 0;
irq_desc[irq].mask_ack = dummy_mask_unmask_irq;
irq_desc[irq].mask = dummy_mask_unmask_irq;
irq_desc[irq].unmask = dummy_mask_unmask_irq;
}
CSR_WRITE(AIC_MDCR, 0x7FFFE); /* disable all interrupts */
CSR_WRITE(CAHCNF,0x0);/*Close Cache*/
CSR_WRITE(CAHCON,0x87);/*Flush Cache*/
while(CSR_READ(CAHCON)!=0);
CSR_WRITE(CAHCNF,0x7);/*Open Cache*/
init_arch_irq();
init_dma();
}
這個函數用來做體系相關的irq處理的初始化,irq_desc數組是用來描述IRQ的請求隊列,每一個中斷號分配一個irq_desc結構,組成了一個數組。NR_IRQS代表中斷數目,這裏只是對中斷結構irq_desc進行了初始化。在默認的初始化完成後調用初始化函數init_arch_irq,先執行arch/armnommu/kernel/irq-arch.c文件中的函數genarch_init_irq(),然後就執行 include/asm-armnommu/arch-xxxx/irq.h中的inline函數irq_init_irq,在這裏對irq_desc進行了實質的初始化。其中mask用阻塞中斷;unmask用來取消阻塞;mask_ack的作用是阻塞中斷,同時還回應ack給硬件表示這個中斷已經被處理了,否則硬件將再次發生同一個中斷。這裏,不是所有硬件需要這個ack回應,所以很多時候mask_ack與mask用的是同一個函數。
接下來執行init_dma()函數,如果不支持DMA,可以設置include/asm-armnommu/arch-xxxx/dma.h中的 MAX_DMA_CHANNELS為0,這様在arch/armnommu/kernel/dma.c文件中會根據這個定義使用不同的函數。
5.6 sched_init()
初始化系統調度進程,主要對定時器機制和時鐘中斷的Bottom Half的初始化函數進行設置。與時間相關的初始化過程主要有兩步:(1)調用init_timervecs()函數初始化内核定時器機制;(2)調用 init_bh()函數將BH向量TIMER_BH、TQUEUE_BH和IMMEDIATE_BH所對應的BH函數分彆設置成timer_bh()、 tqueue_bh()和immediate_bh()函數
5.7 softirq_init()
内核的軟中斷機制初始化函數。調用tasklet_init初始化tasklet_struct結構,軟中斷的個數為32個。用于bh的tasklet_struct結構調用 tasklet_init()以後,它們的函數指針func全都指向bh_action()。bh_action就是tasklet實現bh機制的代碼,但此時具體的bh函數還没有指定。
HI_SOFTIRQ用于實現bottom half,TASKLET_SOFTIRQ用于公共的tasklet。
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); /* 初始化公共的tasklet_struct要用到的軟中斷 */
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); /* 初始化tasklet_struct實現的bottom half調用 */
這裏順便講一下軟中斷的執行函數do_softirq()。
軟中斷服務不允許在一個硬中斷服務程序内部執行,也不允許在一個軟中斷服務程序内部執行,所以通過in_interrupt()加以檢查。h-> action 就是串行化執行軟中斷,當bh 的tasklet_struct 鏈入的時候,就能在這裏執行,在bh裏重新鎖定了所有CPU,導致一個時間只有一個CPU可以執行bh 函數,但是do_softirq()是可以在多CPU 上同時執行的。而每個tasklet_struct在一個時間上是不會出現在兩個CPU上的。另外,只有當Linux初始化完成開啓中斷後,中斷系統才可以開始工作。
5.8 time_init()
這個函數用來做體系相關的timer的初始化,armnommu的在 arch/armnommu/kernel/time.c。這裏調用了在include/asm-armnommu/arch-xxxx/time.h中的inline函數setup_timer,setup_timer()函數的設計與硬件設計緊密相關,主要是根據硬件設計情况設置時鐘中斷號和時鐘頻率等。
void __inline__ setup_timer (void)
{
/*----- disable timer -----*/
CSR_WRITE(TCR0, xxx);
CSR_WRITE (AIC_SCR7, xxx); /* setting priority level to high */
/* timer 0: 100 ticks/sec */
CSR_WRITE(TICR0, xxx);
timer_irq.handler = xxxxxx_timer_interrupt;
setup_arm_irq(IRQ_TIMER, &timer_irq); /* IRQ_TIMER is the interrupt number */
INT_ENABLE(IRQ_TIMER);
/* Clear interrupt flag */
CSR_WRITE(TISR, xxx);
/* enable timer */
CSR_WRITE(TCR0, xxx);
}
5.9 console_init()
控制台初始化。控制台也是一種驅動程序,由于其特殊性,提前到該處完成初始化,主要是為了提前看到輸出信息,據此判斷内核運行情况。很多嵌入式Linux操作系統由于没有在/dev目録下正確配置console設備,造成啓動時發生諸如unable to open an initial console的錯誤。
/*******************************************************************************/
init_modules()函數到smp_init()函數之間的代碼一般不需要作修改,
如果平台具有特殊性,也只需對相關函數進行必要修改。
這裏簡單注明了一下各個函數的功能,以便了解。
/*******************************************************************************/
5.10 init_modules()
模塊初始化。如果編譯内核時使能該選項,則内核支持模塊化加載/卸載功能
5.11 kmem_cache_init()
内核Cache初始化。
5.12 sti()
使能中斷,這裏開始,中斷系統開始正常工作。
5.13 calibrate_delay()
近似計算BogoMIPS數字的内核函數。作為第一次估算,calibrate_delay計算出在每一秒内執行多少次__delay循環,也就是每個定時器滴答(timer tick)―百分之一秒内延時循環可以執行多少次。這種計算只是一種估算,結果并不能精確到納秒,但這個數字供内核使用已經足够精確了。
BogoMIPS的數字由内核計算并在系統初始化的時候打印。它近似的給出了每秒鐘CPU可以執行一個短延遲循環的次數。在内核中,這個結果主要用于需要等待非常短周期的設備驅動程序――例如,等待幾微秒并查看設備的某些信息是否已經可用。
計算一個定時器滴答内可以執行多少次循環需要在滴答開始時就開始計數,或者應該盡可能與它接近。全局變量jiffies中存儲了從内核開始保持跟踪時間開始到現在已經經過的定時器滴答數, jiffies保持异步更新,在一個中斷内——每秒一百次,内核暫時挂起正在處理的内容,更新變量,然後繼續剛才的工作。
5.14 mem_init()
内存初始化。本函數通過内存碎片的重組等方法標記當前剩餘内存, 設置内存上下界和頁表項初始值。
5.15 kmem_cache_sizes_init()
内核内存管理器的初始化,也就是初始化cache和SLAB分配機制。
5.16 pgtable_cache_init()
頁表cache初始化。
5.17 fork_init()
這裏根據硬件的内存情况,如果計算出的max_threads數量太大,可以自行定義。
5.18 proc_caches_init();
為proc文件系統創建高速緩衝
5.19 vfs_caches_init(num_physpages);
為VFS創建SLAB高速緩衝
5.20 buffer_init(num_physpages);
初始化buffer
5.21 page_cache_init(num_physpages);
頁緩衝初始化
5.22 signals_init();
創建信號隊列高速緩衝
5.23 proc_root_init();
在内存中創建包括根結點在内的所有節點
5.24 check_bugs();
檢查與處理器相關的bug
5.25 smp_init();
5.26 rest_init();
此函數調用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函數。
5.26.1 kernel_thread()函數分析
這裏調用了arch/armnommu/kernel/process.c中的函數kernel_thread,kernel_thread函數中通過 __syscall(clone) 創建新綫程。__syscall(clone)函數參見armnommu/kernel目録下的entry-common.S文件。
5.26.2 init()完成下列功能:
Init()函數通過kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回調函數執行,完成下列功能。
do_basic_setup()
在該函數裏,sock_init()函數進行網絡相關的初始化,占用相當多的内存,如果所開發系統不支持網絡功能,可以把該函數的執行注釋掉。
do_initcalls()實現驅動的初始化, 這裏需要與vmlinux.lds聯系起來看才能明白其中奥妙。
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
(*call)();
call++;
} while (call < &__initcall_end);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_tasks();
}
查看 /arch/i386/vmlinux.lds,其中有一段代碼
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
其含義是__initcall_start指向代碼節.initcall.init的節首,而__initcall_end指向.initcall.init的節尾。
do_initcalls所作的是系統中有關驅動部分的初始化工作,那麽這些函數指針數據是怎様放到了.initcall.init節呢?在include/linux/init.h文件中有如下3個定義:
1. #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
__attribute__的含義就是構建一個在.initcall.init節的指向初始函數的指針。
2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn
##意思就是在可變參數使用宏定義的時候構建一個變量名稱為所指向的函數的名稱,并且在前面加上__initcall_
3. #define module_init(x) __initcall(x);
很多驅動中都有類似module_init(usb_init)的代碼,通過該宏定義逐層解釋存放到.initcall.int節中。
blkmem相關的修改(do_initcalls()初始化驅動時執行此代碼)
在blkmem_init ()函數中,調用了blk_init_queue()函數,blk_init_queue()函數調用了blk_init_free_list()函數, blk_init_free_list()函數又調用了blk_grow_request_list()函數,在這個函數中會 kmem_cache_alloc出nr_requests個request結構體。
這裏如果nr_requests的值太大,則將占用過多的内存,將造成硬件内存不够,因此可以根據實際情况將其替换成了較小的值,比如32、16等。
free_initmem
這個函數在arch/armnommu/mm/init.c文件中,其作用就是對init節的釋放,也可以通過修改代碼指定為不釋放。
5.26.3 init執行過程
在内核引導結束并啓動init之後,系統就轉入用户態的運行,在這之後創建的一切進程,都是在用户態進行。這裏先要清楚一個概念:就是init進程雖然是從内核開始的,即在前面所講的init/main.c中的init()函數在啓動後就已經是一個核心綫程,但在轉到執行init程序(如 /sbin/init)之後,内核中的init()就變成了/sbin/init程序,狀態也轉變成了用户態,也就是説核心綫程變成了一個普通的進程。這様一來,内核中的init函數實際上只是用户態init進程的入口,它在執行execve("/sbin/init",argv_init, envp_init)時改變成為一個普通的用户進程。這也就是exec函數的乾坤大挪移法,在exec函數調用其他程序時,當前進程被其他進程“靈魂附體”。
除此之外,它們的代碼來源也有差彆,内核中的init()函數的源代碼在/init/main.c中,是内核的一部分。而/sbin/init程序的源代碼是應用程序。
init程序啓動之後,要完成以下任務:檢查文件系統,啓動各種後台服務進程,最後為每個終端和虚擬控制台啓動一個getty進程供用户登録。由于所有其它用户進程都是由init派生的,因此它又是其它一切用户進程的父進程。
init進程啓動後,按照/etc/inittab的内容進程系統設置。很多嵌入式系統用的是BusyBox的init,它與一般所使用的init不一様,會先執行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。
小結:
本想多整理一些相關資料,無奈又要開始新項目的奔波,start_kernel()函數也剛好差不多講完了,分析的不是很深入,希望對嵌入式Linux移植的網友們有一些幫助。最後列舉下面幾處未整理的知識點,有興趣的網友可作進一步探討。
text.init和data.init説明
__init 標示符在gcc編譯器中指定將該函數置于内核的特定區域。在内核完成自身初始化之後,就試圖釋放這個特定區域。實際上,内核中存在兩個這様的區域,. text.init和.data.init――第一個是代碼初始化使用的,另外一個是數據初始化使用的。另外也可以看到__initfunc和 __initdata標志,前者和__init類似,標志初始化專用代碼,後者則標志初始化專用數據。
System.map内核符號表
irq的處理過程
Linux内核調度過程
本文通過整理之前研發的一個項目(ARM7TDMI + uCLinux),分析内核啓動過程及需要修改的文件,以供内核移植者參考。整理過程中也同時參考了衆多網友的帖子,在此謝過。由于整理過程匆忙,難免錯誤及講解的不够清楚之處,請各位網友指正,這裏提前謝過。本文分以下部分進行介紹:
Bootloader及内核解壓
内核啓動方式介紹
内核啓動地址的確定
arch/armnommu/kernel/head-armv.S分析
start_kernel()函數分析
1. Bootloader及内核解壓
Bootloader將内核加載到内存中,設定一些寄存器,然後將控制權交由内核,該過程中,關閉MMU功能。通常,内核都是以壓縮的方式存放,如zImage,這裏有兩種解壓方法:
使用内核自解壓程序。
arch/arm/boot/compressed/head.S或arch/arm/boot/compressed/head-xxxxx.S
arch/arm/boot/compressed/misc.c
在Bootloader中增加解壓功能。
使用該方法時内核不需要帶有自解壓功能,而使用Bootloader中的解壓程序代替内核自解壓程序。其工作過程與内核自解壓過程相似:Bootloader把壓縮方式的内核解壓到内存中,然後跳轉到内核入口處開始執行。
2. 幾種内核啓動方式介紹
XIP (EXECUTE IN PLACE) 是指直接從存放代碼的位置上啓動運行。
2.1 非壓縮,非XIP
非XIP方式是指在運行之前需對代碼進行重定位。該類型的内核以非壓縮方式存放在Flash中,啓動時由Bootloader加載到内存後運行。
2.2 非壓縮,XIP
該類型的内核以非壓縮格式存放在ROM/Flash中,不需要加載到内存就能運行,Bootloader直接跳轉到其存放地址執行。Data段復制和BSS 段清零的工作由内核自己完成。這種啓動方式常用于内存空間有限的系統中,另外,程序在ROM/Flash中運行的速度相對較慢。
2.3 RAM自解壓
壓縮格式的内核由開頭一段自解壓代碼和壓縮内核數據組成,由于以壓縮格式存放,内核只能以非XIP方式運行。RAM自解壓過程如下:壓縮内核存放于 ROM/Flash中,Bootloader啓動後加載到内存中的臨時空間,然後跳轉到壓縮内核入口地址執行自解壓代碼,内核被解壓到最終的目的地址然後運行。壓縮内核所占據的臨時空間隨後被Linux回收利用。這種方式的内核在嵌入式産品中較為常見。
2.4 ROM自解壓
解壓縮代碼也能够以XIP的方式在ROM/Flash中運行。ROM自解壓過程如下:壓縮内核存放在ROM/Flash中,不需要加載到内存就能運行, Bootloader直接跳轉到其存放地址執行其自解壓代碼,將壓縮内核解壓到最終的目的地址并運行。ROM自解壓方式存放的内核解壓縮速度慢,而且也不能節省内存空間。
3. 内核啓動地址的確定
内核自解壓方式
Head.S/head-XXX.S獲得内核解壓後首地址ZREALADDR,然後解壓内核,并把解壓後的内核放在ZREALADDR的位置上,最後跳轉到ZREALADDR地址上,開始真正的内核啓動。
arch/armnommu/boot/Makefile,定義ZRELADDR和 ZTEXTADDR。ZTEXTADDR是自解壓代碼的起始地址,如果從内存啓動内核,設置為0即可,如果從Rom/Flash啓動,則設置 ZTEXTADDR為相應的值。ZRELADDR是内核解壓縮後的執行地址。
arch/armnommu/boot/compressed/vmlinux.ld,引用LOAD_ADDR和TEXT_START。
arch/armnommu/boot/compressed/Makefile, 通過如下一行:
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;
使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。
説明:
執行完decompress_kernel函數後,代碼跳回head.S/head-XXX.S中,檢查解壓縮之後的kernel起始地址是否緊挨着 kernel image。如果是,beq call_kernel,執行解壓後的kernel。如果解壓縮之後的kernel起始地址不是緊挨着kernel image,則執行relocate,將其拷貝到緊接着kernel image的地方,然後跳轉,執行解壓後的kernel。
Bootloader解壓方式
Bootloader把解壓後的内核放在内存的TEXTADDR位置上,然後跳轉到TEXTADDR位置上,開始内核啓動。
arch/armnommu/Makefile,一般設置TEXTADDR為PAGE_OFF+0x8000,如定義為0x00008000, 0xC0008000等。
arch/armnommu/vmlinux.lds,引用TEXTADDR
4. arch/armnommu/kernel/head-armv.S
該文件是内核最先執行的一個文件,包括内核入口ENTRY(stext)到start_kernel間的初始化代碼,主要作用是檢查CPU ID,Architecture Type,初始化BSS等操作,并跳到start_kernel函數。在執行前,處理器應滿足以下狀態:
r0 - should be 0
r1 - unique architecture number
MMU - off
I-cache - on or off
D-cache – off
/* 部分源代碼分析 */
/* 内核入口點 */
ENTRY(stext)
/* 程序狀態,禁止FIQ、IRQ,設定SVC模式 */
mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode
/* 置當前程序狀態寄存器 */
msr cpsr_c, r0 @ and all irqs disabled
/* 判斷CPU類型,查找運行的CPU ID值與Linux編譯支持的ID值是否支持 */
bl __lookup_processor_type
/* 跳到__error */
teq r10, #0 @ invalid processor?
moveq r0, #p @ yes, error p
beq __error
/* 判斷體系類型,查看R1寄存器的Architecture Type值是否支持 */
bl __lookup_architecture_type
/* 不支持,跳到出錯 */
teq r7, #0 @ invalid architecture?
moveq r0, #a @ yes, error a
beq __error
/* 創建核心頁表 */
bl __create_page_tables
adr lr, __ret @ return address
add pc, r10, #12 @ initialise processor
/* 跳轉到start_kernel函數 */
b start_kernel
__lookup_processor_type 這個函數根據芯片的ID從proc.info獲取proc_info_list結構,proc_info_list結構定義在include/asm- armnommu/proginfo.h中,該結構的數據定義在arch/armnommu/mm/proc-arm*.S文件中,ARM7TDMI系列芯片的proc_info_list數據定義在arch/armnommu/mm/proc-arm6,7.S文件中。函數 __lookup_architecture_type從arch.info獲取machine_desc結構,machine_desc結構定義在 include/asm-armnommu/mach/arch.h中,針對不同arch的數據定義在arch/armnommu/mach- */arch.c文件中。
在這裏如果知道processor_type和architecture_type,可以直接對相應寄存器進行賦值。
5. start_kernel()函數分析
下面對start_kernel()函數及其相關函數進行分析。
5.1 lock_kernel()
/* Getting the big kernel lock.
* This cannot happen asynchronously,
* so we only need to worry about other
* CPUs.
*/
extern __inline__ void lock_kernel(void)
{
if (!++current->lock_depth)
spin_lock(&kernel_flag);
}
kernel_flag 是一個内核大自旋鎖,所有進程都通過這個大鎖來實現向内核態的遷移。只有獲得這個大自旋鎖的處理器可以進入内核,如中斷處理程序等。在任何一對 lock_kernel/unlock_kernel函數裏至多可以有一個程序占用CPU。進程的lock_depth成員初始化為-1,在kerenl/fork.c文件中設置。在它小于0時(恒為 -1),進程不擁有内核鎖;當大于或等于0時,進程得到内核鎖。
5.2 setup_arch()
setup_arch()函數做體系相關的初始化工作,函數的定義在arch/armnommu/kernel/setup.c文件中,主要涉及下列主要函數及代碼。
5.2.1 setup_processor()
該函數主要通過
for (list = &__proc_info_begin; list < &__proc_info_end ; list++)
if ((processor_id & list->cpu_mask) == list->cpu_val)
break;
這様一個循環來在.proc.info段中尋找匹配的processor_id,processor_id在head_armv.S文件
中設置。
5.2.2 setup_architecture(machine_arch_type)
該函數獲得體系結構的信息,返回mach-xxx/arch.c 文件中定義的machine結構體的指針,包含以下内容:
MACHINE_START (xxx, “xxx”)
MAINTAINER ("xxx")
BOOT_MEM (xxx, xxx, xxx)
FIXUP (xxx)
MAPIO (xxx)
INITIRQ (xxx)
MACHINE_END
5.2.3内存設置代碼
if (meminfo.nr_banks == 0)
{
meminfo.nr_banks = 1;
meminfo.bank[0].start = PHYS_OFFSET;
meminfo.bank[0].size = MEM_SIZE;
}
meminfo 結構表明内存情况,是對物理内存結構meminfo的默認初始化。 nr_banks指定内存塊的數量,bank指定每塊内存的範圍,PHYS _OFFSET指定某塊内存塊的開始地址,MEM_SIZE指定某塊内存塊長度。PHYS _OFFSET和MEM_SIZE都定義在include/asm-armnommu/arch-XXX/memory.h文件中,其中PHYS _OFFSET是内存的開始地址,MEM_SIZE就是内存的結束地址。這個結構在接下來内存的初始化代碼中起重要作用。
5.2.4 内核内存空間管理
init_mm.start_code = (unsigned long) &_text; 内核代碼段開始
init_mm.end_code = (unsigned long) &_etext; 内核代碼段結束
init_mm.end_data = (unsigned long) &_edata; 内核數據段開始
init_mm.brk = (unsigned long) &_end; 内核數據段結束
每一個任務都有一個mm_struct結構管理其内存空間,init_mm 是内核的mm_struct。其中設置成員變量* mmap指向自己, 意味着内核只有一個内存管理結構,設置 pgd=swapper_pg_dir,
swapper_pg_dir是内核的頁目録,ARM體系結構的内核頁目録大小定義為16k。init_mm定義了整個内核的内存空間,内核綫程屬于内核代碼,同様使用内核空間,其訪問内存空間的權限與内核一様。
5.2.5 内存結構初始化
bootmem_init (&meminfo)函數根據meminfo進行内存結構初始化。bootmem_init(&meminfo)函數中調用 reserve_node_zero(bootmap_pfn, bootmap_pages) 函數,這個函數的作用是保留一部分内存使之不能被動態分配。這些内存塊包括:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext); /*内核所占用地址空間*/
reserve_bootmem_node(pgdat, bootmap_pfn << PAGE_SHIFT, bootmap_pages << PAGE_SHIFT)
/*bootmem結構所占用地址空間*/
5.2.6 paging_init(&meminfo, mdesc)
創建内核頁表,映射所有物理内存和IO空間,對于不同的處理器,該函數差彆比較大。下面簡單描述一下ARM體系結構的存儲系統及MMU相關的概念。
在ARM 存儲系統中,使用内存管理單元(MMU)實現虚擬地址到實際物理地址的映射。利用MMU,可把SDRAM的地址完全映射到0x0起始的一片連續地址空間,而把原來占據這片空間的FLASH或者ROM映射到其他不相衝突的存儲空間位置。例如,FLASH的地址從0x0000 0000~0x00FFFFFF,而SDRAM的地址範圍是0x3000 0000~0x3lFFFFFF,則可把SDRAM地址映射為0x0000 0000~0xlFFFFFF,而FLASH的地址可以映射到0x9000 0000~0x90FFFFFF(此處地址空間為空閑,未被占用)。映射完成後,如果處理器發生异常,假設依然為IRQ中斷,PC指針指向0xl8處的地址,而這個時候PC實際上是從位于物理地址的0x3000 0018處讀取指令。通過MMU的映射,則可實現程序完全運行在SDRAM之中。在實際的應用中.可能會把兩片不連續的物理地址空間分配給SDRAM。而在操作系統中,習慣于把SDRAM的空間連續起來,方便内存管理,且應用程序申請大塊的内存時,操作系統内核也可方便地分配。通過MMU可實現不連續的物理地址空間映射為連續的虚擬地址空間。操作系統内核或者一些比較關鍵的代碼,一般是不希望被用户應用程序訪問。通過MMU可以控制地址空間的訪問權限,從而保護這些代碼不被破壞。
MMU的實現過程,實際上就是一個查表映射的過程。建立頁表是實現MMU功能不可缺少的一步。頁表位于系統的内存中,頁表的每一項對應于一個虚擬地址到物理地址的映射。每一項的長度即是一個字的長度(在ARM中,一個字的長度被定義為4Bytes)。頁表項除完成虚擬地址到物理地址的映射功能之外,還定義了訪問權限和緩衝特性等。
MMU的映射分為兩種,一級頁表的變换和二級頁表變换。兩者的不同之處就是實現的變换地址空間大小不同。一級頁表變换支持1 M大小的存儲空間的映射,而二級可以支持64 kB,4 kB和1 kB大小地址空間的映射。
動態表(頁表)的大小=表項數*每個表項所需的位數,即為整個内存空間建立索引表時,需要多大空間存放索引表本身。
表項數=虚擬地址空間/每頁大小
每個表項所需的位數=Log(實際頁表數)+適當控制位數
實際頁表數 =物理地址空間/每頁大小
下面分析paging_init()函數的代碼。
在paging_init中分配起始頁(即第0頁)地址:
zero_page = 0xCXXXXXXX
memtable_init(mi); 如果當前微處理器帶有MMU,則為系統内存創建頁表;如果當前微處理器不支持MMU,比如ARM7TDMI上移植uCLinux操作系統時,則不需要此類步驟。可以通過如下一個宏定義實現靈活控制,對于帶有MMU的微處理器而言,memtable_init(mi)是paging_init()中最重要的函數。
#ifndef CONFIG_UCLINUX
/* initialise the page tables. */
memtable_init(mi);
……(此處省略若乾代碼)
free_area_init_node(node, pgdat, 0, zone_size,
bdata->node_boot_start, zhole_size);
}
#else /* 針對不帶MMU微處理器 */
{
/*****************************************************/
定義物理内存區域管理
/*****************************************************/
unsigned long zone_size[MAX_NR_ZONES] = {0,0,0};
zone_size[ZONE_DMA] = 0;
zone_size[ZONE_NORMAL] = (END_MEM - PAGE_OFFSET) >> PAGE_SHIFT;
free_area_init_node(0, NULL, NULL, zone_size, PAGE_OFFSET, NULL);
}
#endif
uCLinux與其它嵌入式Linux最大的區彆就是MMU管理這一塊,從上面代碼就明顯可以看到這點區彆。下面繼續討論針對帶MMU的微處理器的内存管理。
void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;
unsigned long address = 0;
int i;
init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);
/*******************************************************/
其中map_desc定義為:
struct map_desc {
unsigned long virtual;
unsigned long physical;
unsigned long length;
int domain:4, // 頁表的domain
prot_read:1, // 讀保護標志
prot_write:1, // 寫保護標志
cacheable:1, // 是否使用cache
bufferable:1, // 是否使用write buffer
last:1; //空
};init_maps /* map_desc是區段及其屬性的定義 */
下面代碼對meminfo的區段進行遍歷,在嵌入式系統中列舉所有可映射的内存,例如32M SDRAM, 4M FLASH等,用meminfo記録這些内存區段。同時填寫init_maps 中的各項内容。meminfo結構如下:
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/********************************************************/
for (i = 0; i < mi->nr_banks; i++)
{
if (mi->bank.size == 0)
continue;
p->physical = mi->bank.start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank.size;
p->domain = DOMAIN_KERNEL;
p->prot_read = 0;
p->prot_write = 1;
p->cacheable = 1; //使用Cache
p->bufferable = 1; //使用write buffer
p ++; //下一個區段
}
/* 如果系統存在FLASH,執行以下代碼 */
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->domain = DOMAIN_KERNEL;
p->prot_read = 1;
p->prot_write = 0;
p->cacheable = 1;
p->bufferable = 1;
p ++;
#endif
/***********************************************************/
接下來的代碼是逐個區段建立頁表
/***********************************************************/
q = init_maps;
do {
if (address < q->virtual || q == p) {
/*******************************************************************************/
由于内核空間是從某個地址開始,如0xC0000000,所以0xC000 0000 以前的頁表項全部清空
clear_mapping在mm-armv.c中定義,其中clear_mapping()是個宏,根據處理器的不同,可以被展開為如下代碼
cpu_XXX_set_pmd(((pmd_t *)(((&init_mm )->pgd+ (( virt) >> 20 )))),((pmd_t){( 0 )}));
其中init_mm為内核的mm_struct,pgd指向 swapper_pg_dir,在arch/arm/kernel/init_task.c中定義。cpu_XXX_set_pmd定義在 proc_armXXX.S文件中,參見ENTRY(cpu_XXX_set_pmd) 處代碼。
/*********************************************************************************/
clear_mapping(address);
/* 每個表項增加1M */
address += PGDIR_SIZE;
} else {
/* 構建内存頁表 */
create_mapping(q);
address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;
q ++;
}
} while (address != 0);
/ * create_mapping函數也在mm-armv.c中定義 */
static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_pte;
long off;
/*******************************************************************************/
大部分應用中均采用1級section模式的地址映射,一個section的大小為1M,也就是説從邏輯地址到物理地址的轉變是這様的一個過程:
一個32位的地址,高12位决定了該地址在頁表中的index,這個index的内容决定了該邏輯section對應的物理section;低20位决定了該地址在section中的偏移(index)。例如:從0x0~0xFFFFFFFF的地址空間總共可以分成0x1000(4K)個 section(每個section大小為1M),頁表中每項的大小為32個bit,因此頁表的大小為0x4000(16K)。
每個頁表項的内容如下:
bit: 31 20 19 12 11 10 9 8 5 4 3 2 1 0
content: Section對應的物理地址 NULL AP 0 Domain 1 C B 1 0
最低兩位(10)是section分頁的標識。
AP:Access Permission,區分只讀、讀寫、SVC&其它模式。
Domain:每個section都屬于某個Domain,每個Domain的屬性由寄存器控制。一般都只要包含兩個Domain,一個可訪問地址空間; 另一個不可訪問地址空間。
C、B:這兩位决定了該section的cache&write buffer屬性,這與該段的用途(RO or RW)有密切關系。不同的用途要做不同的設置。
C B 具體含義
0 0 無cache,無寫緩衝,任何對memory的讀寫都反映到總綫上。對 memory 的操作過程中CPU需要等待。
0 1 無cache,有寫緩衝,讀操作直接反映到總綫上。寫操作CPU將數據寫入到寫緩衝後繼續運行,由寫緩衝進行寫回操作。
1 0 有cache,寫通模式,讀操作首先考慮cache hit;寫操作時直接將數據寫入寫緩衝,如果同時出現cache hit,那麽也更新cache。
1 1 有cache,寫回模式,讀操作首先考慮cache hit;寫操作也首先考慮cache hit。
由于ARM中section表項的權限位和page表項的位置不同, 以下代碼根據struct map_desc 中的保護標志,分彆計算頁表項中的AP, Domain和CB標志位。
/*******************************************************************************/
prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
(md->prot_read ? L_PTE_USER : 0) |
(md->prot_write ? L_PTE_WRITE : 0) |
(md->cacheable ? L_PTE_CACHEABLE : 0) |
(md->bufferable ? L_PTE_BUFFERABLE : 0);
prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md->domain) |
(md->prot_read ? PMD_SECT_AP_READ : 0) |
(md->prot_write ? PMD_SECT_AP_WRITE : 0) |
(md->cacheable ? PMD_SECT_CACHEABLE : 0) |
(md->bufferable ? PMD_SECT_BUFFERABLE : 0);
/********************************************************************/
設置虚擬地址,偏移地址和内存length
/********************************************************************/
virt = md->virtual;
off = md->physical - virt;
length = md->length;
/********************************************************************/
建立虚擬地址到物理地址的映射
/********************************************************************/
while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
while (length >= PGDIR_SIZE) {
alloc_init_section(virt, virt + off, prot_sect);
virt += PGDIR_SIZE;
length -= PGDIR_SIZE;
}
while (length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, md->domain, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
/*************************************************************************/
create_mapping的作用是設置虚地址virt 到物理地址virt + off_set的映射頁目録和頁表。
/*************************************************************************/
/* 映射中斷向量表區域 */
init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = vectors_base();
init_maps->length = PAGE_SIZE;
init_maps->domain = DOMAIN_USER;
init_maps->prot_read = 0;
init_maps->prot_write = 0;
init_maps->cacheable = 1;
init_maps->bufferable = 0;
create_mapping(init_maps);
中斷向量表的虚地址init_maps,是用alloc_bootmem_low_pages分配的,通常是在PAGE_OFF+0x8000前面的某一頁, vectors_base()是個宏,ARM規定中斷向量表的地址只能是0或0xFFFF0000,所以上述代碼映射一頁到0或0xFFFF0000,中斷處理程序中的部分代碼也被拷貝到這一頁中。
5.3 parse_options()
分析由内核引導程序發送給内核的啓動選項,在初始化過程中按照某些選項運行,并將剩餘部分傳送給init進程。這些選項可能已經存儲在配置文件中,也可能是由用户在系統啓動時敲入的。但内核并不關心這些,這些細節都是内核引導程序關注的内容,嵌入式系統更是如此。
5.4 trap_init()
這個函數用來做體系相關的中斷處理的初始化,在該函數中調用__trap_init((void *)vectors_base())函數將exception vector設置到vectors_base開始的地址上。__trap_init函數位于entry-armv.S文件中,對于ARM處理器,共有復位、未定義指令、SWI、預取終止、數據終止、IRQ和FIQ幾種方式。SWI主要用來實現系統調用,而産生了IRQ之後,通過exception vector進入中斷處理過程,執行do_IRQ函數。
armnommu的trap_init()函數在 arch/armnommu/kernel/traps.c文件中。vectors_base是寫中斷向量的開始地址,在include/asm- armnommu/proc-armv/system.h文件中設置,地址為0或0XFFFF0000。
ENTRY(__trap_init)
stmfd sp!, {r4 - r6, lr}
mrs r1, cpsr @ code from 2.0.38
bic r1, r1, #MODE_MASK @ clear mode bits /* 設置svc模式,disable IRQ,FIQ */
orr r1, r1, #I_BIT|F_BIT|MODE_SVC @ set SVC mode, disable IRQ,FIQ
msr cpsr, r1
adr r1, .LCvectors @ set up the vectors
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} /* 拷貝异常向量 */
add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 - r6, pc})
__stubs_start到__stubs_end的地址中包含了异常處理的代碼,因此拷貝到vectors_base+0x200的位置上。
5.5 init_IRQ()
void __init init_IRQ(void)
{
extern void init_dma(void);
int irq;
for (irq = 0; irq < NR_IRQS; irq++) {
irq_desc[irq].probe_ok = 0;
irq_desc[irq].valid = 0;
irq_desc[irq].noautoenable = 0;
irq_desc[irq].mask_ack = dummy_mask_unmask_irq;
irq_desc[irq].mask = dummy_mask_unmask_irq;
irq_desc[irq].unmask = dummy_mask_unmask_irq;
}
CSR_WRITE(AIC_MDCR, 0x7FFFE); /* disable all interrupts */
CSR_WRITE(CAHCNF,0x0);/*Close Cache*/
CSR_WRITE(CAHCON,0x87);/*Flush Cache*/
while(CSR_READ(CAHCON)!=0);
CSR_WRITE(CAHCNF,0x7);/*Open Cache*/
init_arch_irq();
init_dma();
}
這個函數用來做體系相關的irq處理的初始化,irq_desc數組是用來描述IRQ的請求隊列,每一個中斷號分配一個irq_desc結構,組成了一個數組。NR_IRQS代表中斷數目,這裏只是對中斷結構irq_desc進行了初始化。在默認的初始化完成後調用初始化函數init_arch_irq,先執行arch/armnommu/kernel/irq-arch.c文件中的函數genarch_init_irq(),然後就執行 include/asm-armnommu/arch-xxxx/irq.h中的inline函數irq_init_irq,在這裏對irq_desc進行了實質的初始化。其中mask用阻塞中斷;unmask用來取消阻塞;mask_ack的作用是阻塞中斷,同時還回應ack給硬件表示這個中斷已經被處理了,否則硬件將再次發生同一個中斷。這裏,不是所有硬件需要這個ack回應,所以很多時候mask_ack與mask用的是同一個函數。
接下來執行init_dma()函數,如果不支持DMA,可以設置include/asm-armnommu/arch-xxxx/dma.h中的 MAX_DMA_CHANNELS為0,這様在arch/armnommu/kernel/dma.c文件中會根據這個定義使用不同的函數。
5.6 sched_init()
初始化系統調度進程,主要對定時器機制和時鐘中斷的Bottom Half的初始化函數進行設置。與時間相關的初始化過程主要有兩步:(1)調用init_timervecs()函數初始化内核定時器機制;(2)調用 init_bh()函數將BH向量TIMER_BH、TQUEUE_BH和IMMEDIATE_BH所對應的BH函數分彆設置成timer_bh()、 tqueue_bh()和immediate_bh()函數
5.7 softirq_init()
内核的軟中斷機制初始化函數。調用tasklet_init初始化tasklet_struct結構,軟中斷的個數為32個。用于bh的tasklet_struct結構調用 tasklet_init()以後,它們的函數指針func全都指向bh_action()。bh_action就是tasklet實現bh機制的代碼,但此時具體的bh函數還没有指定。
HI_SOFTIRQ用于實現bottom half,TASKLET_SOFTIRQ用于公共的tasklet。
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); /* 初始化公共的tasklet_struct要用到的軟中斷 */
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); /* 初始化tasklet_struct實現的bottom half調用 */
這裏順便講一下軟中斷的執行函數do_softirq()。
軟中斷服務不允許在一個硬中斷服務程序内部執行,也不允許在一個軟中斷服務程序内部執行,所以通過in_interrupt()加以檢查。h-> action 就是串行化執行軟中斷,當bh 的tasklet_struct 鏈入的時候,就能在這裏執行,在bh裏重新鎖定了所有CPU,導致一個時間只有一個CPU可以執行bh 函數,但是do_softirq()是可以在多CPU 上同時執行的。而每個tasklet_struct在一個時間上是不會出現在兩個CPU上的。另外,只有當Linux初始化完成開啓中斷後,中斷系統才可以開始工作。
5.8 time_init()
這個函數用來做體系相關的timer的初始化,armnommu的在 arch/armnommu/kernel/time.c。這裏調用了在include/asm-armnommu/arch-xxxx/time.h中的inline函數setup_timer,setup_timer()函數的設計與硬件設計緊密相關,主要是根據硬件設計情况設置時鐘中斷號和時鐘頻率等。
void __inline__ setup_timer (void)
{
/*----- disable timer -----*/
CSR_WRITE(TCR0, xxx);
CSR_WRITE (AIC_SCR7, xxx); /* setting priority level to high */
/* timer 0: 100 ticks/sec */
CSR_WRITE(TICR0, xxx);
timer_irq.handler = xxxxxx_timer_interrupt;
setup_arm_irq(IRQ_TIMER, &timer_irq); /* IRQ_TIMER is the interrupt number */
INT_ENABLE(IRQ_TIMER);
/* Clear interrupt flag */
CSR_WRITE(TISR, xxx);
/* enable timer */
CSR_WRITE(TCR0, xxx);
}
5.9 console_init()
控制台初始化。控制台也是一種驅動程序,由于其特殊性,提前到該處完成初始化,主要是為了提前看到輸出信息,據此判斷内核運行情况。很多嵌入式Linux操作系統由于没有在/dev目録下正確配置console設備,造成啓動時發生諸如unable to open an initial console的錯誤。
/*******************************************************************************/
init_modules()函數到smp_init()函數之間的代碼一般不需要作修改,
如果平台具有特殊性,也只需對相關函數進行必要修改。
這裏簡單注明了一下各個函數的功能,以便了解。
/*******************************************************************************/
5.10 init_modules()
模塊初始化。如果編譯内核時使能該選項,則内核支持模塊化加載/卸載功能
5.11 kmem_cache_init()
内核Cache初始化。
5.12 sti()
使能中斷,這裏開始,中斷系統開始正常工作。
5.13 calibrate_delay()
近似計算BogoMIPS數字的内核函數。作為第一次估算,calibrate_delay計算出在每一秒内執行多少次__delay循環,也就是每個定時器滴答(timer tick)―百分之一秒内延時循環可以執行多少次。這種計算只是一種估算,結果并不能精確到納秒,但這個數字供内核使用已經足够精確了。
BogoMIPS的數字由内核計算并在系統初始化的時候打印。它近似的給出了每秒鐘CPU可以執行一個短延遲循環的次數。在内核中,這個結果主要用于需要等待非常短周期的設備驅動程序――例如,等待幾微秒并查看設備的某些信息是否已經可用。
計算一個定時器滴答内可以執行多少次循環需要在滴答開始時就開始計數,或者應該盡可能與它接近。全局變量jiffies中存儲了從内核開始保持跟踪時間開始到現在已經經過的定時器滴答數, jiffies保持异步更新,在一個中斷内——每秒一百次,内核暫時挂起正在處理的内容,更新變量,然後繼續剛才的工作。
5.14 mem_init()
内存初始化。本函數通過内存碎片的重組等方法標記當前剩餘内存, 設置内存上下界和頁表項初始值。
5.15 kmem_cache_sizes_init()
内核内存管理器的初始化,也就是初始化cache和SLAB分配機制。
5.16 pgtable_cache_init()
頁表cache初始化。
5.17 fork_init()
這裏根據硬件的内存情况,如果計算出的max_threads數量太大,可以自行定義。
5.18 proc_caches_init();
為proc文件系統創建高速緩衝
5.19 vfs_caches_init(num_physpages);
為VFS創建SLAB高速緩衝
5.20 buffer_init(num_physpages);
初始化buffer
5.21 page_cache_init(num_physpages);
頁緩衝初始化
5.22 signals_init();
創建信號隊列高速緩衝
5.23 proc_root_init();
在内存中創建包括根結點在内的所有節點
5.24 check_bugs();
檢查與處理器相關的bug
5.25 smp_init();
5.26 rest_init();
此函數調用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函數。
5.26.1 kernel_thread()函數分析
這裏調用了arch/armnommu/kernel/process.c中的函數kernel_thread,kernel_thread函數中通過 __syscall(clone) 創建新綫程。__syscall(clone)函數參見armnommu/kernel目録下的entry-common.S文件。
5.26.2 init()完成下列功能:
Init()函數通過kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回調函數執行,完成下列功能。
do_basic_setup()
在該函數裏,sock_init()函數進行網絡相關的初始化,占用相當多的内存,如果所開發系統不支持網絡功能,可以把該函數的執行注釋掉。
do_initcalls()實現驅動的初始化, 這裏需要與vmlinux.lds聯系起來看才能明白其中奥妙。
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
(*call)();
call++;
} while (call < &__initcall_end);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_tasks();
}
查看 /arch/i386/vmlinux.lds,其中有一段代碼
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
其含義是__initcall_start指向代碼節.initcall.init的節首,而__initcall_end指向.initcall.init的節尾。
do_initcalls所作的是系統中有關驅動部分的初始化工作,那麽這些函數指針數據是怎様放到了.initcall.init節呢?在include/linux/init.h文件中有如下3個定義:
1. #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
__attribute__的含義就是構建一個在.initcall.init節的指向初始函數的指針。
2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn
##意思就是在可變參數使用宏定義的時候構建一個變量名稱為所指向的函數的名稱,并且在前面加上__initcall_
3. #define module_init(x) __initcall(x);
很多驅動中都有類似module_init(usb_init)的代碼,通過該宏定義逐層解釋存放到.initcall.int節中。
blkmem相關的修改(do_initcalls()初始化驅動時執行此代碼)
在blkmem_init ()函數中,調用了blk_init_queue()函數,blk_init_queue()函數調用了blk_init_free_list()函數, blk_init_free_list()函數又調用了blk_grow_request_list()函數,在這個函數中會 kmem_cache_alloc出nr_requests個request結構體。
這裏如果nr_requests的值太大,則將占用過多的内存,將造成硬件内存不够,因此可以根據實際情况將其替换成了較小的值,比如32、16等。
free_initmem
這個函數在arch/armnommu/mm/init.c文件中,其作用就是對init節的釋放,也可以通過修改代碼指定為不釋放。
5.26.3 init執行過程
在内核引導結束并啓動init之後,系統就轉入用户態的運行,在這之後創建的一切進程,都是在用户態進行。這裏先要清楚一個概念:就是init進程雖然是從内核開始的,即在前面所講的init/main.c中的init()函數在啓動後就已經是一個核心綫程,但在轉到執行init程序(如 /sbin/init)之後,内核中的init()就變成了/sbin/init程序,狀態也轉變成了用户態,也就是説核心綫程變成了一個普通的進程。這様一來,内核中的init函數實際上只是用户態init進程的入口,它在執行execve("/sbin/init",argv_init, envp_init)時改變成為一個普通的用户進程。這也就是exec函數的乾坤大挪移法,在exec函數調用其他程序時,當前進程被其他進程“靈魂附體”。
除此之外,它們的代碼來源也有差彆,内核中的init()函數的源代碼在/init/main.c中,是内核的一部分。而/sbin/init程序的源代碼是應用程序。
init程序啓動之後,要完成以下任務:檢查文件系統,啓動各種後台服務進程,最後為每個終端和虚擬控制台啓動一個getty進程供用户登録。由于所有其它用户進程都是由init派生的,因此它又是其它一切用户進程的父進程。
init進程啓動後,按照/etc/inittab的内容進程系統設置。很多嵌入式系統用的是BusyBox的init,它與一般所使用的init不一様,會先執行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。
小結:
本想多整理一些相關資料,無奈又要開始新項目的奔波,start_kernel()函數也剛好差不多講完了,分析的不是很深入,希望對嵌入式Linux移植的網友們有一些幫助。最後列舉下面幾處未整理的知識點,有興趣的網友可作進一步探討。
text.init和data.init説明
__init 標示符在gcc編譯器中指定將該函數置于内核的特定區域。在内核完成自身初始化之後,就試圖釋放這個特定區域。實際上,内核中存在兩個這様的區域,. text.init和.data.init――第一個是代碼初始化使用的,另外一個是數據初始化使用的。另外也可以看到__initfunc和 __initdata標志,前者和__init類似,標志初始化專用代碼,後者則標志初始化專用數據。
System.map内核符號表
irq的處理過程
Linux内核調度過程
致富之道
這則巴比倫最有錢人的故事, 看似簡單平常, 但在八千年前巴比倫人已經懂得理財致富之道, 這些原則到現在還是一樣可以適用。
根據巴比倫出土的陶磚土記載, 巴比倫最有錢的人叫做阿卡德, 很多人羨慕他的富有, 因此向他請教致富之道。
阿卡德原來是在擔任雕刻陶磚的工作, 有一天, 有一位有錢人歐格尼斯來向他訂購一塊刻有法律條文的陶磚, 阿卡德說, 他願意漏夜雕刻, 到天亮時就可以完成,但是唯一的條件是歐格尼斯要告訴他致富的秘訣。
歐格尼斯同意這個條件, 因此到天亮時, 阿卡德完成了陶磚的雕刻工作, 歐格尼斯實踐了他的諾言, 他告訴阿卡德 :
「 致富的秘訣是 : 你賺的錢中有一部份要存下來。」
「財富就像樹一樣, 從一粒微小的種耔開始成長, 第一筆你存下來的錢就是你財富成長的種籽, 不管你賺的多麼少, 你一定要存下十分之一。」
一年後, 當歐格尼斯再來的時候, 他問阿卡德是否有照他的話去做, 把賺來的錢省下十分之一。
阿卡德很驕傲的回答, 他確實照他的方法作了, 歐格尼斯就問 :
「那存下來的錢, 你如何使用呢?」
阿卡德說:「我把它給了磚匠阿盧瑪, 因為他要旅行到遠地買回菲利人稀有的珠寶, 當他回來的時候,我們將把這些珠寶賣很高的價格,然後平分這些錢。」
歐格尼斯責罵說: 「只有傻子才會這麼做, 為什麼買珠寶要信任磚匠的話呢? 你的存款已經泡湯了! 年輕人, 你把財富的樹連根都拔掉了, 下次你買珠寶應該去請教珠寶商, 買羊毛去請教羊毛商, 別和外行人做生意! 」
就如同歐格尼斯所說, 磚匠阿魯瑪被菲利人騙了, 買回來的是不值錢的玻璃,看起來像珠寶.
阿卡德再次下定決心存下所賺的錢的十分之一, 當第二年, 歐格尼斯再來的時候, 他又詢問阿卡德錢存的如何?
阿卡德回答 :「我把存下來的錢借給了鐵匠去買青銅原料, 然後他每四個月付我一次租金。」
歐格尼斯說 : 「很好, 那麼你如何使用賺來的租金呢? 」
阿卡德說 : 「 我把賺來的租金拿來吃一頓豐富大餐, 並買一件漂亮的衣服, 我還計劃買一頭驢子來騎。」
歐格尼斯笑了, 他說 : 「 你把存下的錢所衍生的子息吃掉了, 你如何期望他們以及他們的子孫能再為你工作, 賺更多的錢? 當你賺到足夠的財富時, 你才能盡情享用而無後顧之憂。」
又過了二年, 歐格尼斯問阿卡德 : 「 你是否達到夢想中的財富? 」
阿卡德說: 「還沒有, 但是我已存下了一些錢, 然後錢滾錢, 錢又滾錢。」
阿格尼斯又問 : 「那你是否還向磚匠請教事情? 」
阿卡德說 : 「有關造磚的工作請教他們能得到很好的建議。」
歐格尼斯說 :
「 你已學會了致富的秘訣。首先你學會了從賺來的錢省下錢, 其次你學會了向內行的人請教意見, 最後你學會了如何讓錢為你工作, 使錢賺錢。你已學會如何獲得財富, 保持財富, 運用財富。」
早在八千年前的巴比倫人就指出 : 成功的人都是善於管理、 維護、 運用創造財富。致富之道在於聽取專業的意見, 並且終生奉行不渝。
這則古老的智慧當中, 蘊含著金錢的五大金科定律。
金錢的第一定律 : 金錢是慢慢流向那些願意儲蓄的人。
每月至少存入十分之一的錢, 久而久之可以累積成一筆可觀的資產。
金錢的第二定律 : 金錢願意為懂得運用它的人工作。
那些願意打開心胸, 聽取專業的意見, 將金錢放在穩當的生利投資上, 讓錢滾錢, 利滾利, 將會源源不斷創造財富。
金錢的第三定律 : 金錢會留在懂得保護它的的人身邊。
重視時間報酬的意義, 耐心謹慎的維護它的財富, 讓它持續增值, 而不貪圖暴利。
金錢的第四定律 : 金錢會從那些不懂得管理的人身邊溜走。
對於擁有金錢而不善經營的人, 一眼望去, 四處都有投資獲利的機會, 事實上卻處處 隱藏陷阱, 由於錯誤的判斷, 它們常會損失金錢。
金錢的第五定律 : 金錢會從那些渴望獲得暴利的人身邊溜走。
金錢的投資報酬有一定的回收, 渴望投資獲得暴利的人常被愚弄, 因而失去金錢。
缺乏經驗或外行, 是造成投資損失的最主要原因。
摘自:「是的,我要成功」 / 陳偉航 / 麥田出版
根據巴比倫出土的陶磚土記載, 巴比倫最有錢的人叫做阿卡德, 很多人羨慕他的富有, 因此向他請教致富之道。
阿卡德原來是在擔任雕刻陶磚的工作, 有一天, 有一位有錢人歐格尼斯來向他訂購一塊刻有法律條文的陶磚, 阿卡德說, 他願意漏夜雕刻, 到天亮時就可以完成,但是唯一的條件是歐格尼斯要告訴他致富的秘訣。
歐格尼斯同意這個條件, 因此到天亮時, 阿卡德完成了陶磚的雕刻工作, 歐格尼斯實踐了他的諾言, 他告訴阿卡德 :
「 致富的秘訣是 : 你賺的錢中有一部份要存下來。」
「財富就像樹一樣, 從一粒微小的種耔開始成長, 第一筆你存下來的錢就是你財富成長的種籽, 不管你賺的多麼少, 你一定要存下十分之一。」
一年後, 當歐格尼斯再來的時候, 他問阿卡德是否有照他的話去做, 把賺來的錢省下十分之一。
阿卡德很驕傲的回答, 他確實照他的方法作了, 歐格尼斯就問 :
「那存下來的錢, 你如何使用呢?」
阿卡德說:「我把它給了磚匠阿盧瑪, 因為他要旅行到遠地買回菲利人稀有的珠寶, 當他回來的時候,我們將把這些珠寶賣很高的價格,然後平分這些錢。」
歐格尼斯責罵說: 「只有傻子才會這麼做, 為什麼買珠寶要信任磚匠的話呢? 你的存款已經泡湯了! 年輕人, 你把財富的樹連根都拔掉了, 下次你買珠寶應該去請教珠寶商, 買羊毛去請教羊毛商, 別和外行人做生意! 」
就如同歐格尼斯所說, 磚匠阿魯瑪被菲利人騙了, 買回來的是不值錢的玻璃,看起來像珠寶.
阿卡德再次下定決心存下所賺的錢的十分之一, 當第二年, 歐格尼斯再來的時候, 他又詢問阿卡德錢存的如何?
阿卡德回答 :「我把存下來的錢借給了鐵匠去買青銅原料, 然後他每四個月付我一次租金。」
歐格尼斯說 : 「很好, 那麼你如何使用賺來的租金呢? 」
阿卡德說 : 「 我把賺來的租金拿來吃一頓豐富大餐, 並買一件漂亮的衣服, 我還計劃買一頭驢子來騎。」
歐格尼斯笑了, 他說 : 「 你把存下的錢所衍生的子息吃掉了, 你如何期望他們以及他們的子孫能再為你工作, 賺更多的錢? 當你賺到足夠的財富時, 你才能盡情享用而無後顧之憂。」
又過了二年, 歐格尼斯問阿卡德 : 「 你是否達到夢想中的財富? 」
阿卡德說: 「還沒有, 但是我已存下了一些錢, 然後錢滾錢, 錢又滾錢。」
阿格尼斯又問 : 「那你是否還向磚匠請教事情? 」
阿卡德說 : 「有關造磚的工作請教他們能得到很好的建議。」
歐格尼斯說 :
「 你已學會了致富的秘訣。首先你學會了從賺來的錢省下錢, 其次你學會了向內行的人請教意見, 最後你學會了如何讓錢為你工作, 使錢賺錢。你已學會如何獲得財富, 保持財富, 運用財富。」
早在八千年前的巴比倫人就指出 : 成功的人都是善於管理、 維護、 運用創造財富。致富之道在於聽取專業的意見, 並且終生奉行不渝。
這則古老的智慧當中, 蘊含著金錢的五大金科定律。
金錢的第一定律 : 金錢是慢慢流向那些願意儲蓄的人。
每月至少存入十分之一的錢, 久而久之可以累積成一筆可觀的資產。
金錢的第二定律 : 金錢願意為懂得運用它的人工作。
那些願意打開心胸, 聽取專業的意見, 將金錢放在穩當的生利投資上, 讓錢滾錢, 利滾利, 將會源源不斷創造財富。
金錢的第三定律 : 金錢會留在懂得保護它的的人身邊。
重視時間報酬的意義, 耐心謹慎的維護它的財富, 讓它持續增值, 而不貪圖暴利。
金錢的第四定律 : 金錢會從那些不懂得管理的人身邊溜走。
對於擁有金錢而不善經營的人, 一眼望去, 四處都有投資獲利的機會, 事實上卻處處 隱藏陷阱, 由於錯誤的判斷, 它們常會損失金錢。
金錢的第五定律 : 金錢會從那些渴望獲得暴利的人身邊溜走。
金錢的投資報酬有一定的回收, 渴望投資獲得暴利的人常被愚弄, 因而失去金錢。
缺乏經驗或外行, 是造成投資損失的最主要原因。
摘自:「是的,我要成功」 / 陳偉航 / 麥田出版
訂閱:
文章 (Atom)
-
昨天差點昏倒, 因為Chrome Browser一開啟後居然一片白畫面. 雖然還有IE可以用, 但就是習慣Chrome了啊, 然後開始Google別人如何解決, 不過看起來不少人遇到相同的問題, 但都沒什麼解決方法. 什麼掃毒啦, 重新安裝Chrome啦, 砍掉Default啦....
-
股市之神是川銀藏 從30歲的70日元開始投資,到成為叱吒風雲的日本股神,是川銀藏僅小學畢業,卻憑著在股市的驚人業績設立「是川經濟研究所」,登上大學教壇。他以「只吃八分飽」、「烏龜三原則」與「股票投資五原則」的簡單哲學屢創股市奇蹟。 只吃八分飽 是川認為,投資股票賣出比買進要難得多...
-
I/O mapped I/O(port-mapped I/O或Direct I/O) I/O與memory均擁有自己的記憶體空間 需要特別的指令來處理I/O 好處是完全不用考慮記憶體空間被I/O佔用,缺點需要額外的指令專門處理I/O存取。 Memory Mapped I/O I/...
一個小故事讓我們明白資金流通的意義
“又是炎熱小鎮慵懶的一天。太陽高掛,街道無人,每個人都債台高築,靠信用度日。這時,從外地來了一位有錢的旅客,他進了一家旅館,拿出一張1000 元鈔票放在櫃檯,說想先看看房間,挑一間合適的過夜,就在此人上樓的時候---- 店主抓了這張1000 元鈔,跑到隔壁屠戶那裡支付了他欠的肉錢...