linux內核學習(轉1)

這是我的第一篇關於linux kernel的東西,接觸linux內核已經快一年的時間了,只是對內核有大體的瞭解,瞭解她實現的最基本的原理。現在想把我的學習的過程,或者是說對 linux內核的理解用文字的形式展現出來。一來檢測自己的學習效果,再者,如果有初學linux內核的朋友,我認爲這篇總結也可以給他們很好的指導。

寫這篇文章花了我大量的時間,寫之前以爲很快就能完成的。不過後來我發現我錯了,內核之龐大,之奧妙神奇,之複雜,不是一兩句話就能說清道明的。而又想把每個模塊儘量的講明白,儘量把當初自己學習過程中的體會寫出來,所以花了很長一段時間寫。有幾次想放棄寫下去,不過後來還是堅持寫完了全部文章,全文共近兩萬字。如果你是一個內核初學者,而且你還堅持看下去了,如果有難點,千萬不要放棄,多看幾遍你就能明白了。我會以我從初學內核的感受一步一步都寫出來。儘量把我當初很難看明白的東西,然後我是怎麼樣最後理解這些東西的,都例舉出來。在各個模塊的講解中,我把感覺非常重要的、最應該掌握的或是初學時難於理解的,都寫出來,供大家參考。

 

Linux內核源代碼到目前爲止已經有幾百萬行代碼了,我並沒有過多的去看源代碼(很大的原因是因爲內核代碼之龐大和它的複雜)。如果過多的追問這篇文章存在的價值,那就應該是我學習內核以來的想法了感受了。現在把它總結之後,就把內核的學習先放一放,還有更加重要的事情要去完成。由於是第一次寫東西,語言並不簡練,而且我感覺自己其實還有很多的東西要去學習,自己瞭解的也並不是很多。在寫之後的內容的時候,我多次懷疑自己是否有能力寫這樣的總結,這樣的懷疑是因爲感覺自己還有很多不足。不過還是堅持寫出來了,畢竟這是我的一個學習的見證,也是給我之後的在這方面的學習的一個新的起點。而且我感覺這樣的一個總結給一個內核新手來說,還很不錯的一個有引導性質的東西。

我打算按照內核各個功能模塊來講解linux內核,其中會涉及到內核的最核心的幾個模塊,linux內核的進程管理,進程調度,內存管理,進程地址空間,中斷異常,系統調用,定時器中斷,內核同步,虛擬文件系統,驅動程序,網絡,等等。都是自己學習過程的總結,按照章節用文字的形式展現出來,供自己和linux內核初學者參考。並且把自己的學習心得總結出來,或者對前面闡述的東西進行補充。寫這篇文章,首先會要更加細緻的瞭解這些內核模塊,畢竟看書和總結要求的知識面不是一樣的。通過寫這個總結,讓自己和他人(如果有其他人能看到)得到收穫。

 

文章的安排是這樣的:首先我打算用一個適用於所有學習的倒金字塔結構(why?what?how?)來闡述前半部分內容。之後會再把我對內核各個模塊的理解總結出來,不錯,倒金字塔結構同樣的適用於我的闡述。

1、爲什麼要有內核?內核存在的價值和意義。

2、什麼是內核?她實現了什麼樣的功能。

3、內核是如何設計實現的?怎樣實現對計算機系統的進行管理的。

4、內核各模塊簡介:對內核各個模塊進行簡單的介紹。

 

 

我認爲關於爲什麼要有內核和什麼是內核有着不可分割的聯繫,所以把他們放到一起,方便講訴。

什麼是linux內核?爲什麼要有內核?首先我們要先對內核有一定的瞭解,要不然再怎麼說明爲什麼學習內核也是空話。內核是操作系統實現的最核心的部分,完成對系統的硬件資源的管理和分配,同時又管理着系統的其他的軟件,對上層的應用程序提供訪問硬件資源的服務接口。重點在於理解:管理軟硬件,服務於上層應用程序。初學者可能對操作系統爲什麼要把硬件封裝起來,而只對應用程序提供相應的服務接口這點感到疑惑:軟件直接訪問硬件不是更加快捷嗎?或者說爲什麼要操作系統?

的確,如果在加了操作系統這一層,可能會帶來一些看上去很複雜的操作,但操作系統提供這樣的接口有很多好處。首先是方便,方便了程序員的編程,程序員不需要過多的瞭解硬件的實現細節。好比在寫C語言程序的時候,我們想在屏幕上輸出了某些內容,我們會去調用printf()函數,而不是去對連接到屏幕的io接口做一些輸出有關的彙編級編程操作。操作系統對應用程序提供了統一的接口函數,如果你需要訪問硬件,調用它們就可以了。第二是安全,如果多個進程都想用同一個硬件,如果他們在沒有任何的約束下,他們一旦並被調度了執行,那麼他們都會去搶這個硬件資源。如果這個資源是像打印機那樣不可以同時被佔用,那麼結果可想而知,肯定會去大亂子了。同時,操作系統內核還保證了應用程序不能訪問內核空間,既內核所在的地址空間。那樣做事不安全的,因爲那樣有可能會破壞內核的代碼或者數據。還有一個重要的原因,那就是方便移植。針對硬件的語言是很不方便移植的,而如果用像C語言這樣的高級程序,移植是非常方便的。以上幾點也正是操作系統存在的價值,你參透了嗎?

我在上李超老師的一節課中,他給了我們一個很好的比方:

如果把計算機系統比作是我們人類生活的大環境,那麼計算機硬件資源(內核的管理和分配對象)就好比是我們地球上的土地資源,那麼操作系統內核(管理計算硬件和軟件,服務於上層應用程序)就可以比作是我們生活中的政府(管理地球的土地資源和人們的日常生活,服務於人類),那麼應用程序(內核服務的對象)就可以比作是人類。希望這麼多比喻,沒有把你弄得頭暈。

政府管理着地球上的各種資源,併爲生活的人類服務。而在人類生活環境中,做很多什麼事情的時候都是要政府給我們提供相應的服務。你不是可以隨意的使用土地資源的,除非政府把它分配給了你。就像應用程序沒有直接使用內存或者硬件的權力,除非它申請並得到了內核的批准;古代人類的通信方式比較慢,而且不方便。但自從有了政府提供的通信服務基站,使得人與人之間的相互通信變得方便快捷。內核同樣提供方便的進程間通信機制,讓應用程序之間更加好的協調完成任務。

說到這裏相信大家對內核已經有了一個感性的認識:應用程序需要內核提供的服務接口纔可以訪問硬件資源,就好像人類需要政府提供的各種職能而生存着。

內核就是這樣簡單。

 

 

看到這邊你應該對內核存在的意義有了一定的瞭解,哦,內核,你就是做了那些事情呀。可是你可能又產生了新的問題:內核是如何實現的呢?或者說操作系統是如何實現的呢?

很多操作系統原理的初學者(爲什麼這邊說是操作系統原理初學者而不是內核初學者,是因爲內核原理就是操作系統原理,這點你在上面的內容裏面應該已經參透了),一定會對那些關於闡述原理的大堆大堆的段落感到反感:討厭死了,我知道這些,我想聽她是如何實現的,那纔是我想要的重點。

那麼內核是如何實現操作系統的各種功能的呢?這的確是非常有意思的,當你對他們瞭解之後。

在 linux內核中,內核是有各個不同的C語言函數組織(當然也會有一些涉及硬件操作要用到的彙編語句),他們相互的調用實現了操作系統各個最核心的功能。可能大家對這樣的話並不是很瞭解,因爲在大家心目中:操作系統應該不只是函數的組織和調用這麼簡單。但是我想說的是不管操作系統在你的心中有多麼的強大,內核就只是函數的組織和調用,就這麼簡單。只不過這是一個工程龐大的函數羣,有幾百萬行的代碼組織,如此龐大。只不過這堆代碼可以改變處理器的模式,有權利管理其他應用程序和系統資源。

如果你現在不能理解,不要急,在之後的學習中你會明白的。相信我,如果你深入學習,你會領略到內核的藝術所在。下面這幅圖是一些主要內核函數的調用關係:(當內核在學習的過程中可以對照這個圖看,查看他們之間的關係,對學習內核會有一定的幫助)

 

雖然內核源代碼看上去是那麼的龐大,如野獸般不可駕馭。其實不然,內核在經過編譯之後只有很少的幾KB到幾MB之間,是不是不可思議?這麼龐大的內核源代碼被編譯成這麼小的體積,這麼小的內核卻管理了如此龐大的計算機系統。在我心中,這就是藝術。

什麼是藝術?藝術是一種文化現象,大多爲滿足主觀與情感的需求,亦是日常生活進行娛樂的特殊方式。其根本在於不斷創造新興之美,藉此宣泄內心的慾望與情緒,屬濃縮化和誇張化的生活。文字、繪畫、雕塑、建築、音樂、舞蹈、戲劇、電影等任何可以表達美的行爲或事物,皆屬藝術。計算機學是一門藝術,經濟管理學是一門藝術,發現美是一種藝術,人心美是一種藝術。

 

那爲什麼要學習linux內核?我認爲學習一樣知識,要問兩個爲什麼。第一個是這個知識產生的價值,爲什麼要有這個知識?它是用來解決什麼問題的?然後纔是我們爲什麼要學習這樣知識?學習了這樣知識是否對自己有幫助?

那麼理解內核的實現原理可以用來完成什麼?我們是否真的有必要學習內核?內核是操作系統最核心的部分,理解她的實現對我們理解操作系統原理及實現有很大的幫助,應用程序的開發也會有很大的幫助。如果你想向嵌入式系統開發的方向發展,學習內核也會有很大的幫助,比如當我們瞭解到了內核提供的某些特定的函數之後,對於之後學習linux驅動程序的開發帶來很大的幫助,驅動程序藉助內核提供的函數實現其對硬件的封裝。然後對配置內核也會有非常大的幫助,我們知道 linux內核是可裁減的,針對不同的硬件平臺裁減內核,是內核更加小巧。在一本書上看到:linux內核是由世界上最厲害的程序員編寫的,源代碼就是最好的證明。我們不過多的評論這句話,但從這句話中我們可以看出,內核源代碼的優秀,她的編碼技巧和風格都是值得很多程序員學習的。如果對底層原理性的東西有一定的瞭解之後,會對之後的學習過程有個很好的支撐作用。如果你被內核藝術迷住了,那就瞭解和學習他吧。

就我自身而言,我感覺學習內核是非常好的,是非常有幫助的。學習可以對計算機系統學習有很大的幫助。學習過程同樣是成長的過程,內核難於學習,可以磨礪一個人的心智。在學習內核的同時,同樣也提升了我們的學習能力。21世紀社麼最重要啊?顯然是能力。當然我的意思不是說學了內核就一定對自己或者對自身的提高有幫助,因人而異吧。

當我學到現在,我就感覺內核的學習對我的幫助很大。這種幫助不只是體現在了對於內核的掌握上面,更加是我對整個計算機系統的認識提到了一個新的高度。然後我感覺我自己的能力也得到了進一步的提高,在內核的學習中,我一直在思考,思考內核的實現路徑。經過這麼長時間的思考之後,我發現我還是收穫非常多的。真的能感覺到我在成長。

 

對也想學習內核的人,我感覺可以這樣:

我的建議是首先要對C語言、彙編語言、操作系統基本原理、數據結構、硬件有一定的瞭解,再開始接觸內核。(操作系統內核很龐大,而且要設計直接對硬件的管理,想要上手還有應該有一定的知識量儲備來支撐。)比如說你要對編譯、彙編、鏈接、加載、函數庫有一定的瞭解,瞭解了它們也是同樣很有用的。如果有做過linux程序設計的,那麼就會用到一些內核提供的接口函數,那麼在學習內核的時候,就會看到這些接口是如何實現的,對內核的學習會有很大的幫助。然後找一些內核的書籍看,先整體過一遍,在針對各個模塊開始看。這邊介紹我看過的幾本書,個人感覺還是不錯的。

內核入門級的書籍可以是《Linux Kernel Development》,翻譯到國內的書籍叫《Linux內核設計與實現》,目前有第二版了。這本書主要起到了很好的提綱挈領的作用,是內核入門的好書。先把這本書整體過一遍,對內核有個大致的瞭解之後,再針對你所感興趣或者是想研究的地方,再次看各個章節。這個時候你就應該找一些更加詳細的書籍來做參考對比了,畢竟每本書都會講到不同的地方。針對某個模塊,幾本書一起看,會過理解有幫助。這時可以參考《Understanding The Linux Kernel》(中文名:《深入理解Linux內核》)和《Professional Linux Kernel Architecture》(中文名:《深入Linux內核構架》),這兩本書較之《Linux內核設計與實現》,多了很多的細節。在看書的同時,建議你去網上(www.kernel.org)下載到linux內核的源碼,可以用Source Insight這個軟件去組織和查看內核這個龐大的項目。這個軟件方便我們很好的閱讀內核源代碼。如果你感覺你真的對內核的代碼實現,或者想深入瞭解她的實現,那就看源碼吧,畢竟源碼纔是最好的老師。當然如果你的編程功底不是很好,那有這樣兩本書可以幫助你更好的閱讀源代碼。一本是《Linux內核完全剖析——基於0.12內核》,另一本是《Linux內核源代碼情景分析》。這兩本書都是非常不錯的,對於想深入瞭解源代碼是由非常大的幫助的。

插句嘴:內核源代碼真的很難看,我感覺最重要的原因是因爲內核的龐大,跳來跳去,還有一個原因是要對編程語言非常理解。如果只是爲了更好的編程,或者爲了編寫設備驅動,那麼能瞭解她的實現和提供的接口就可以了。這裏所說的提供的接口沒有強調是對“上層”或者說“應用程序”提供的接口。因爲內核提供的函數接口不只是給應用程序調用,同時內核也在用這樣的接口。最普遍的例子就是設備驅動,驅動程序運行在內核空間中,調用內核提供的各種功能接口。所以學習內核的實現的原理,以及她所提供的接口不失爲一個好辦法。我想畢竟不是每個人都有天賦能開發內核的,起碼我就不適合——我感覺太難了。當然不是說源碼不看,而是要有針對性的看,看個別重要的模塊還是蠻好的。

 

 

接下來我就想把內核的各個模塊做一個簡單的介紹,這邊先給出一個關於內核的架構,有助於我們的瞭解內核。每個模塊都是我精心安放的,至於什麼會那樣放置,以後你就會明白的。

 

 

在理解上幅圖和講解各個模塊之前,有必要交代下讓初學者對頭痛的東西,那就是用戶空間和內核空間。操作系統內核是獨立於普通應用程序的,她一般處於系統態,擁有受到保護的內存空間和訪問硬件設備的所有權限。這種系統態和被保護起來的內存空間,統稱爲內核空間。相應的,應用程序在用戶空間執行。它們只能看到允許它們使用的部分系統資源,並且不能使用某些特定的系統功能,不能直接訪問硬件,還有一些其他的使用限制。內核態和用戶態使用了不同的地址空間,不同的保護級別。那麼爲什麼要這樣分開呢?是爲了安全。因爲如果應用程序和內核在同一個保護級別,那麼應用程序就有可能有意或者不小心進入了內核空間,破壞了內核空間的代碼和數據。處理器硬件提供了這樣的保護級別,內核代碼和數據被放到了內核空間,而應用程序的地址空間(進程地址空間,稍後講解)則由內核管理分配。因爲內核只相信她自己。我們上面說到用戶空間沒有直接訪問硬件的權力,如果應用程序要訪問硬件(此時還在用戶空間),那麼請你調用一個系統調用吧。之後硬件會自動的進入內核空間,並完成此次系統調用要完成的操作。也就是說系統調用是一個陷入的動作,使程序進入了內核空間。我們說內核運行在進程上下文中,或者內核代表進程執行動作。懂了嗎?

這邊我例舉一個例子:你需要在屏幕上輸出:hello world!這樣幾個字符。因爲你沒有直接訪問硬件的權力,所以你會調用printf()函數(實事是你也只會這麼調用)。這顯然是一個庫函數,printf()封裝了一個系統調用,編譯器和鏈接加載器會幫我們完成,實事上printf()做了很多事情:數據的緩衝和格式化等操作,然後再執行的末期通過write()系統調用把處理後的最終數據輸出到屏幕上面。在系統調用之前,一直是出於用戶態的,系統調用之後,陷入了內核,內核代表這個程序完成對硬件的訪問操作,之後退出內核態,繼續做printf()之後的事情。

還有一種辦法可以進入內核空間,那就是中斷。就只有這兩種辦法。這邊是有點饒頭的,不過當深入學習之後就可以慢慢理解了。慢慢來。

 

 

在學習內核之前我還希望大家對list_head這個雙向鏈表數據結構對深入的瞭解,這對之後的學習有大的幫助。這個雙向鏈表不同於我們C語言中一般的雙向鏈表,這個結構實現的非常巧妙。你同樣可以把他用在你的應用程序中,但是不能直接的引用,因爲內核空間的數據或者函數是不可以直接調用的(你也不可能調用的到)。把這個數據結構放到開頭,大家就應該知道它的重要性了。

 

·進程管理 

進程是佔用了一定的系統資源的一個程序的實體,或者說是正處於執行期的程序。進程不僅僅是一段可執行的代碼,它還包括一些其他系統資源,如打開的文件、掛起的信號、內核內部數據、處理器的狀態、地址空間以及一個或多個執行線程,當然還包括用來存放全局變量的數據段。記住,進程不只是一段可執行代碼,更是一組系統資源的集合。在這重點理解爲什麼要有這些資源,如果這點都不瞭解,下面的內容你也是看不懂的。

每個進程都有它的進程控制塊(PCB),在linux系統中,用進程描述符task_struct這個結構體來描述一個進程。它概括進程的所有完整的信息,所以這個結構體非常大,大約有 1.7KB,可以說是相當的大了(要知道,內核分給每個進程的內核棧只有8KB)。每產生一個進程或者線程分配一個進程描述符。請不要爲內核產生這麼大的一個結構體所花的時間擔心,因爲稍後我們會講到slab分配器,一種快速分配內核數據結構的高速數據緩存池。Linux不特別區分線程和進程,線程被看做特殊的進程,線程共享進程的資源,就這樣。那爲什麼要給線程也分配一個進程描述符?因爲需要線程併發的執行任務,只有有了進程描述符,線程纔可以作爲一個調度的實體了。關於調度,稍後會講到。

關於進程的狀態的一些說明:有趣的是,linux並沒有設置任務就緒這個狀態,task_running既是就緒狀態,又是執行狀態。因需要等待資源的情況不同,linux有兩個任務掛起狀態。當資源到了,進程會再次被選擇並執行。那麼誰選中它們呢?是內核。那麼內核是怎麼選得處理器的執行權力並選中一個進程的呢?爲什麼要有兩種任務掛起的狀態呢?我希望大家在看書的時候,經常提出一些問題。關於調度,我們還是下面再講解吧。

接下來是關於進程的創建和刪除。一個進程被產生出來,是爲了完成某個任務。當task結束之後,它也就沒有存在的價值了,那麼釋放它得到的資源。那麼進程由誰創建的呢?內核和應用程序都可以創建。內核直接調用創建的函數do_fork(),而應用程序只能通過fork()系統調用來創建一個進程,當然fork()最終還是調用了do_fork()。當創建一個進程時,要給進程分配一個進程描述符(直接拷貝父進程的,當然之後會做相應的修改),分配一個內核棧(當進程進入內核態時,訪問的是內核棧。那麼這個棧的切換是怎麼完成的呢?很簡單,就是改變堆棧指針)。之後進程調用exec()函數族,完成進程地址空間的分配,分配一個用戶態堆棧,並對代碼的加載。雖然讓人傷感,但進程終歸是要終結的。此時調用進程退出exit()函數族,釋放一些資源,做一些善後操作。這是每個進程都需要做的事情,有些人產生疑問:我編的C程序中一般都沒有這個函數呀。那是因爲C語言的編譯器在編譯的時候已經幫你加上了。她還幫我們做了很多事情,所以編譯器真是個好東西呀。

關於進程管理就想說這麼多,那麼你需要知道什麼呢?Linux是如何進行進程抽象的。當內核訪問一個任務,她需要獲得指向其tsak_struct的指針。實際上,內核中大部分處理進程的代碼都是通過task_struct進行的。所以找到當前的進程描述符的速度很重要,所以產生了thread_info這個數據結構,使得更加方便的查找到進程描述符。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章