zzfrom水木-Linux环境学习和开发心得(作者:lunker)
轉自水木lunker,非常好的文章,在此鳴謝之。
?
本人水平有限,如果有錯誤和遺漏,或者有更好的建議,請大家認真的拍。
強烈建議: 文中涉及的圖書最好入手一個英文版的,如果實在閱讀有困難,可以在電腦中準備一個中文版的進行參考,但要強迫自己循序漸進的脫離使用中文版。
1. 書目
注:文中對所有書籍的引用都會使用開頭的編號, 或者簡稱。
<1> "The C Programming Language", 2nd edition, by Brian Kernighan & Dennis Ritchie
-- C語言最經典的教材,閱讀前最好有一點編程的基礎。本書簡稱K&R。
<2> "Computer Systems: A Programmer's Perspective", 2nd edition, by David R. O'Hallaron
-- 以匯編和C程序作為例子,從程序員而不是設計者的角度,講解計算機系統軟硬件體系結構的書。很有想法的書。
<3> "Advanced Programming in the UNIX environment", 2nd edition, by W. Richard Stevens and Stephen A. Rago
-- Unix/Linux 環境編程的最好教材,把操作系統的原理,unix的接口和設計思路,以及C程序實例這三者很好的結合在了一起,不可多得的好書。本書簡稱 APUE2
<4> "Linux Kernel Development", 3rd edition, by robert love
-- Linux kernel 入門的書籍,深入淺出,很適合新人入門。簡稱 LKD3
<5> "Linux Device Drivers", 3rd Edition, by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman
-- 教給你寫linux驅動的書,有實例,有框架,有細節,把和驅動沾邊的知識都講得很細致。簡稱LDDR3。 下載: http://lwn.net/Kernel/LDD3/
<6> "Understanding the Linux Kernel", 3rd edition, by Marco Cesati Ph.D.
-- 講解linux kernel的實現,包括設計思路,調用關系圖,數據結構,算法,以及一些kernel編程技巧。簡稱 ULK3。
<7> "TCP/IP Illustrated, Vol. 1,2,3", by W. Richard Stevens
-- 講解TCP/IP協議的經典書,第一卷介紹協議,必讀,有大量的tcpdump實例,跟著操作和分析很有樂趣。第二卷介紹BSD上面的實現,第三卷介紹了HTTP等其他的協議。
<8> "Unix Network Programming, Vol. 1,2", 3rd edition, by W. Richard Stevens, Bill Fenner, Andrew M. Rudoff
-- 第一卷介紹了unix環境下的socket編程,還是有實例,很好的結合了協議,還涉及了一些實現原理。第二卷介紹了unix IPC,似乎和APUE有重復之嫌,我沒有看過這卷。
<9> "Understanding Linux Network Internals", Christian Benvenuti
-- 詳細介紹了linux網絡協議棧的實現細節。可惜沒有涉及tcp的實現和netfilter。
?
2. 目標
本文假設讀者要成為linux環境系統程序員,涉及linux用戶空間編程,網絡編程和linux 驅動編程。
本文講述的是我自己的學習和工作體會,希望大家能批判的理解,最終找到自己的路。
?
3. K&R 和 開發環境
K&R 當然是一切的基礎,必須認真學好,對于沒有編程經驗的人來說,拿這本書入門有點難度,必須認真完成書里的習題,能額外找些習題練手當然更好。有C語言經驗的同學學起來輕松一些,抓重點難點看,書中關于指針的介紹很好,言簡意賅。學通透之后,K&R 還可以成為手冊,忘記了翻看一下還是必要的,這書要一直留著。
在學習 K&R 的時候,同學就可以弄一個linux的主機玩一下了,(推薦debian, ubuntu)裝個基本系統,把開發包裝一下,這些教程網上都有,自己查查吧,K&R里邊的例子最好在linux上面跑,學學gcc,make,vim的基本用法,不要太深入的學,先夠用就可以了。
到這里,總體上還是輕松愉快的,我們開始找到一點入門的感覺了,但其實和入門還有很大的距離,繼續努力吧。
?
4. APUE 的考驗?
經常看到一些同學沒有看過APUE就開始學寫driver,這其實是不太合適的,如果說K&R是基礎的話,那么 APUE就是我們學習過程的核心,APUE不單純介紹接口,編程這些東西,書里邊涉及的思想,方法,會貫穿程序員的整個生涯,比如,書里邊談到了policy和mechanism這兩個unix系統重要的概念,并且用實例闡述它們,對我們理解unix系統有重要的意義。因此,我們要好好讀,反復讀這本書。
當然,書里邊涉及過多的知識,新手往往感覺很困難;因此,我們要注意方法。
(1) 必須把書里的實例在linux上面跑一下,Richard的書都是很有操作性的,建議同學最好用手敲的方式,把里邊一些短小的程序編譯執行一下,如果能改改功能,調試調試那就更好了。APUE里的許多知識點必須是在實踐中才能理解和掌握的。
(2) 切忌貪多,貪快,不要想一口氣把書都讀完,最好先攻讀一些重點的章節: File I/O, Process, Signal, IPC等等,適當展開,多練習練習,對于不太懂的概念,多google百度一下。
(3) 產生迷惑和疑問的時候,多和別人討論一下,發貼子問一下,交流很重要。
當我們在學習APUE的過程中,感到應接不暇比較苦惱的時候,我建議同學要放慢節奏,弄一弄 gcc makefile gdb vim linux命令 bash編程這些, google一些教程來學習,學寫一些復雜的例子。同時,google一些簡單的項目, 或者復雜的C練習題做做,盡量用到上面的工具,增加熟練度。
這個過程是一個略顯漫長的學習過程,是程序員面臨的第一個坎,需要堅持不懈。等跨過去之后,就海闊天空了。
另外,就我個人而言,我會在閱讀和學習的過程中,把一些重要的知識點,tips, trick,重要的例程都整理成一個文檔,方便將來查閱使用,這個習慣保持整個學習和開發生活始終。?
5. 學習實際項目的源碼
經常看到有人建議通過閱讀一些簡單的開源項目來提高自己,我覺得這個做法很好,但是要注意時機:
(1) 至少精讀過一遍APUE的重要章節
(2) 能看懂makefile,了解autoconf,比較熟練的使用vim, gcc, gdb, strace等工具(不會的google一下教程)。
好吧,這個時候,有些同學已經辛苦的精讀過APUE所有的重要章節了(注意,不一定是全部的章節),看看,這個時候,堅持手敲APUE實例代碼的同學就有優勢了,他們vim相對會熟練一些:)那我們還等什么,趕緊開始吧,其實,我們還差一個事情需要準備,
(3) 學會使用 cscope, 不知道這個是什么?google去吧。,vim+cscope 看代碼,誰用誰知道。本文不想涉及工具之爭(vim或emacs), IDE之爭,無論你選什么都可以,只要好用就行。
準備就緒,選擇什么項目開始呢?我建議從簡單的開始,coreutils
源碼可以從這里下載: http://www.gnu.org/software/coreutils/
是什么呢?對,部分linux命令的源碼,從簡單的(一般size比較小)開始學起,看看庫函數和系統調用怎樣相互配合,完成一個linux命令的功能。
我們可能發現源碼里邊宏定義特別多,影響閱讀,或者縮進風格詭異,看著費勁,沒有辦法,慢慢適應吧,vim有命令可以收起展開宏里邊的代碼,或者美化代碼縮進,自己查查怎么用。還有一些函數沒見過,setlocal()等等,沒關系查查APUE,或者 google,能把一個簡單的源文件徹底搞明白,你會收獲很多。同時,你對APUE的理解也會更進一步,你越來越體會出這個書的寶貴,也越來越離不開它。
經過一段時間的探索,你也許會感覺這些源碼也不過如此,不那么神秘了,恭喜你,現在你算是入門了。從現在開始,你已經可以開始干活了。?
?6. 開始干活
我覺得,只學不干是嘴把式,只干不學是傻把式,兩者必須結合。
有項目的同學或者上班族可以去爭取一些簡單的工作了,沒有項目的同學,只能自己給自己找活干了。
在干活之前,有必要進行一下準備:
(1) 把工具和開發環境準備好,有些項目需要特定的工具,比如subversion, git, SecureCRT, Cuteftp, 等等,一定要弄會了,多問問學長同事什么的。
(2) 確定代碼風格,無論是linux kernel的,還是windows的,都可以,但要風格統一。
(3) 搞清楚任務目標,需求,還有期限。這點是最重要的,由于新手一般接手的工作比較簡單,別人分配給你任務的時候往往不會特別用心,這時需要你多主動提問,把細節問清楚。
干活的幾個重要階段:
(1) 分析和設計階段,這個時候不要著急編寫代碼,先分析一下需求,確定一下方法,畫畫流程圖,最好找個有經驗的人帶著做,會少走一些彎路,讓他幫著審核最終方案。
(下段 WaterMonster 網友補充)
其實unix下大部分的你能想到的程序都有人寫過一遍了,問題只是別人寫出來的軟件未必符合你工作下的實際需要。 設計之前,在開源界搜一搜,有沒有和你想法差不多的人,他們的實現是怎么樣的,整體的架構是否合理,對比自己的思路走一走。
(2) 開始寫代碼了,不要一開始就想寫個高大全,先根據設計方案寫個盡量簡單的程序,把框架搭起來,主要流程理順,最好能夠編譯執行。無法完成功能也無所謂,我們再慢慢改進它。
(3) 添加細部的功能,最好有適量的改動就編譯執行一下看看,這樣可以省去最后完整調試的時間。
(4) 調試(看log,gdb,strace,這里就不展開了),修改bug, 完善代碼。
改代碼盡量做到一下幾點:
(a) 合理規劃文件, .h 文件放什么內容,.c 文件放什么內容,是用多個.c文件,還是只用一個。這些問題需要經驗,不能閉門造車,該問別人就問,或者找一些項目已有的代碼來參考,或者google,或者論壇發帖,不要指望一次能夠搞定,發現不合適的地方,隨時調整。
(b) 檢查代碼風格,添加合理的注釋,一定要養成這個習慣。
(c) 添加合理的debug log,要有debug開關,比如通過 -DDEBUG 編譯選項開打開debug。
OK, 到這里,我們主要的任務就完成了,如果驗收通過了,是不是很有成就感?
等等,其實有些有追求的同學發現,他的代碼和項目已有的代碼或者開源項目的代碼還是有差距,但是具體怎么改進呢?從另外的角度講,都驗收通過了,還改進它干什么?
我認為,"一個優秀程序員的基本特質就是,始終不遺余力的完善自己的代碼",我們不能以驗收作為自己的最終目標,我們要成為優秀的程序員,這個才是我們的目標。
如果能達成這個共識,請繼續看下去。
?
7 反思,梳理,制定計劃
我們有了項目經驗了,我們開始干活了,往往這個時候,我們也開始膨脹了。具體表現是,等著上面放任務干活,沒活可干的時候就玩,也不看書了,覺得自己就是大牛了,在bbs上還能給新新手回答一些問題了(注意,這時候往往會給出錯誤的答案),一些程序員就是在這個階段停滯不前的,很可惜。
這個時候,我覺得可以開始給自己新的任務了,學習kernel也許是不錯的選擇,不過,下一步怎么做不是最重要的,最重要的是,要在思想上清楚的認識到,自己其實是剛剛入門,對于linux kernel/driver 還沒有入門,我還有很多的坎需要邁。這個心態需要同學們好好體會.
另外, 在繼續學習之前,有必要梳理一下之前的學習心得,整理筆記,甚至重讀一下APUE,往往你還會有新的收獲:你會發現,在linux環境下面學習和工作的方法逐漸清晰了起來;你也許會偶而靈光一閃一個念頭,然后迫不及待的寫個小程序驗證它。這些方法和習慣會在今后的學習和工作中慢慢完善,形成linux程序員特有的風格。
這個時候,由于我們也許參與大的項目了,下一步的學習計劃需要結合自己的實際情況,并沒有固定的套路。對于本文涉及的領域來說,有兩個方向: linux driver和網絡編程。對于其他的方向,肯定還有,但是本文并不涉及。
后文會根據這兩個方向分別進行闡述。
8. 網絡協議和搭建實驗環境
在進行網絡編程之前,先要了解tcp/ip協議;想學習網絡協議棧的實現,一定要把網絡編程搞熟;想學習alsa驅動,先要熟悉使用asla庫調用;類似這些學習的順序,大家要注意。
在展開對<7>"TCP/IP Illustrated, Vol. 1"學習之前,我們需要考慮搭建網絡環境的問題,找一臺內存大些的PC,裝虛擬機就搞定了(最好VMware,其他的我不確定是否可行), 具體的拓撲結構參考書里開頭就給出的網絡,不需要百分百復制,體會精神就行。有2個router,2-3個host,1個gateway,這是必須的吧,等學習內容變化的時候,拓撲可以跟著改變。
這里再強調一下Richard書的學習方法,如果不能跟上實驗操作,單純看書效果差多了。即使有條件接觸實際復雜網絡的同學也要建一套虛擬機的環境,不能影響別人是不。
書沒啥好說的,講得很好,我們關注一下 tcpdump:這是學習網絡的核心工具,大家要隨著書中的例子好好學習這個工具的使用,爭取在看完之后機能熟練掌握tcpdump的用法。對于書里稍復雜一些的知識點,比如 tcp 的慢啟動,超時重傳等等要用 tcpdump 的結果去印證,加深印象。
另外,wireshark 也是一個不錯的工具,提供了圖形化的界面,比tcpdump友好一些,而且用tcpdump保存下來的raw data是可以用 wireshark 進行分析的,這兩個工具可以配合著用。
(annals網友: 還ethereal,用wireshark已經很久了)
經過APUE的煎熬,讀協議簡直就是一種享受,各種念頭通達,稍微復雜的例子也可以配合tcpdump來理解,可這只是開始,重頭戲在后面。
9. 網絡編程 UNP
學習UNP的方法和APUE差不多,看書,練習例子。如果能加上tcpdump的分析,那就更好了。經過一段時間,你會發現UNP就是對APUE的復習嘛,信號,線程,系統調用這些都要用到,而且比APUE上的講法要更實際,更有操作性。
書里經常會用不同的方法去實現同一個功能,或者把一個簡單的例子逐漸添加血肉,變成復雜的例子。這些都是Linux著作常用的方法,注意體會,將來自己寫文章或許可以用到;寫程序的時候,也可以遵循先簡單后復雜的過程,符合認識規律。
書里邊的例子也很經典,比如 select(), poll() 的例子,同學可以敲一遍多跑跑,整理到文檔里,將來要寫這種程序的時候,拿來就用,方便快哉。
有了APUE的基礎,UNP的進展應該比較順利,下面就是用具體的項目代碼入手了。
?
10. hping 上
我們通過學習hping的用法及其源碼,鞏固對協議的認識和熟悉網絡編程。hping3可以在這里得到: http://www.hping.org/hping3-20051105.tar.gz
看看這個工具是干嘛的,注意README里邊提到,這是一個學習tcp/ip絕佳工具(It's also really a good didactic tool to learn TCP/IP), 這個版本還支持tcl腳本了,比我那個時候強大了嘛:)
哎,同學,不要著急看源碼啊,忘了順序了嘛?不先熟悉一下hping3的命令,怎么能上來就看代碼呢?還是先build一下把,這次講得稍微復雜一些:
一般 linux 源碼包的編譯過程一般是
$ ./configure
$ make
$ make install
hping也不例外
$ ./configure
$ make
這個時候,我這邊報錯了
gcc -c -O2 -Wall -g main.c
main.c:29:18: error: pcap.h: No such file or directory
main.c:169: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘*’ token
main.c:170: error: ‘PCAP_ERRBUF_SIZE’ undeclared here (not in a function)
make: *** [main.o] Error 1
有經驗的同學可能知道,這是缺少pcap開發包了,如果不知道也沒關系,google一下就能找到答案了,另外,或許還會缺少tcl-dev包,請在下面的url找到相應的源碼包,或者自行尋找和發行版對應的二進制包,這里就不詳細展開了。
http://www.tcpdump.org/release/libpcap-1.1.1.tar.gz
http://www.tcl.tk/
繼續make,開頭過去了,不過又一個錯誤:
libpcap_stuff.c:20:21: error: net/bpf.h: No such file or directory
libpcap_stuff.c: In function ‘pcap_recv’:
libpcap_stuff.c:61: warning: pointer targets in assignment differ in signedness
make: *** [libpcap_stuff.o] Error 1
這次又需要google一下了,用關鍵自 net/bpf.h 搜索一下,很快找到解決方法:
$ ln -snf /usr/include/pcap-bpf.h /usr/include/net/bpf.h
然后順利通過:
$ make
$ make install
大功告成。
11. hping 下
可以看看 hping3 的文檔,如果感覺不太直觀,google一下別人的教程(推薦 http://wiki.hping.org)。練習一些簡單的命令, 如果在教程里發現什么idle scanning, Packet crafting等字眼,請大家自動過濾,我什么都沒說過, 什么都沒說過......
hping有兩種工作模式,script和command line模式。
這里給一個command line模式簡單的例子,用hping發一個icmp包到指定的host
$ hping -V -1 www.google.com
這個時候,也可以用tcpdump抓包看看:
$ tcpdump -n -i ethX icmp
下面是script模式的例子,用hping發一個icmp包到指定的服務器。
$ hping # 進入腳本解釋器
hping3> hping resolve wiki.hping.org
109.74.203.151
hping3> hping send {ip(daddr=109.74.203.151)+icmp(type=8,code=0)}
我們也可先編寫腳本(例如xxx.tcl),然后用下面的命令執行:
$ hping exec xxx.tcl
wiki.hping.org上面有很復雜的腳本示例,這種方法本讓hacker從C程序的繁重工作解脫出來,通過簡單易用的腳本可以實現靈活強大的功能。
看源碼的話,可以從main()函數看起,看看command line模式下的代碼流程。
從hping_script()這個函數調用開始,會進入到script模式的處理code,不建議新手學習。
hping使用SOCK_RAW發送各種類型的包tcp/udp/icmp等等,并統一使用pcap庫來捕捉返回包,要注意和使用SOCK_STREAM時的思路是不同的,前者需要自己構造ip包頭,對照著協議閱讀整個過程是必要的。
當同學對hping的腳本和源碼有了一定的了解,你也就對網絡編程開始入門了,同時,隨著Linux環境下的學習和工作方法的逐步成熟,你會發現學習效率在提高,信心在增強,通過耐心的分析研究,這些開源項目的源碼不在話下。
補:等寫完了發現,并沒有給出一個client/server類的例子供大家學習,iperf 不知是否合適,請大家自行編譯執行分析。如果大家有時間,可以考慮自行設計一個復雜一點的程序,client/server模式的,用到信號,多線程,socket,select,文件I/O,IPC 等等,算是一個總結,后邊開始進入kernel space的世界。
12. 從LKD開始
我們打開參考書<4>"Linux Kernel Development", 書的作者Robert love是kernel的設計者之一,但他并沒有把書寫得深奧難懂,分寸把握的很好,僅僅400頁的篇幅,就講解了Kernel的絕大多數要點,重點突出,細枝末節一筆帶過,決不拖泥帶水。書的篇幅少也和引用的代碼少有關,不湊字數,相當有料。
盡管篇幅有限,但本書開頭還是不厭其煩的給出了一些如何build kernel的說明,還對源碼樹各部分進行了說明;這從側面表明了作者的態度,看書的時候也得動手啊。
在 http://www.kernel.org/ 得到kernel的源碼,然后編譯
$ cd linux-source-dir/
$ make menuconfig
$ make
關于menuconfig選項,升級kernel image,grub install的方法,這里就不贅述了,請同學自行解決,多練習練習沒壞處。
盡管書寫得比較易懂,但可能對于剛開始接觸kernel的人還是有點難度,尤其是對于非科班出身的同學,沒學習過體系結構和操作系統原理,先自行了解基本概念還是必要的。
橫看成嶺側成峰,讓我們從幾個不同的角度來認識認識kernel同學:
1) 從原教旨主義的角度看,linux kernel就是一大坨文件目錄樹,主要由C/匯編源文件,腳本,配置文件,文檔,以及某些二進制文件組成。由全球的志愿者(包括商業公司)在Linux社區開發維護。
2) 從存在主義的角度看,linux kernel就是一個二進制文件,從上面這些源文件編譯生成,一般長期保存在PC的硬盤中,使用時,這個二進制文件會被加載到內存,CPU會執行里邊的機器代碼。
3) 從經典講義的角度看,linux kernel上安application,下撫hardware:給app提供api讓其訪問hardware,同時給hardware提供driver來讓其能正常工作。
4) 從發行版的角度看,linux kernel就是個悲劇,連自己的名分都保不住,用戶只知道redhat, ubuntu不知道kernel是神馬玩意。
5) 從我的角度看,kernel就是一個進程而已。
?
13 Kernel是一個進程
為什么這么講呢?咱們看一下系統啟動的過程:PC上電了,bootloader首先被執行,做完一些初始化工作,它把kernel image加載到了內存里,讓CPU執行kernel的代碼,這個時候kernel是PC上跑的第一個,也是唯一的一個進程,它一下就奪過對CPU的控制權,把CPU,內存,clock,中斷控制器等等先調教了一番,又初始化了內部的一些算法和數據結構,它給自己起名字叫"swapper", 數字0是他的終身代號,然后,它就寂寞了......
swapper從硬盤上面又找了一個二進制文件,叫做init,加載到內存,代號1,允許CPU執行他的代碼。然后它又創建了幾個內核線程來分擔它的工作,這些線程有自己獨特的名字和代號。而kernel也從單獨的一個進程,變成了由多個內核線程組成的大進程。
init并不是這個大進程的成員,但它努力產崽,有了許多子子孫孫進程,我們統稱它們叫用戶進程,這些進程訪問資源的權限受到嚴格限制,盡管優先級有時可以調整到很高。
kernel線程也不停創建同類,但他們互相不稱父子,因為他們有共同的身體,他們的權限不受限制。
這些用戶進程和kernel線程在調度算法的指揮下,輪番取得對cpu的控制權,執行自己的代碼。
而swapper呢?他其實算是第一個內核線程,不過它低調的調低自己的優先級,深居簡出,當所有的進程線程都無事可做的時候,才會出來主持局面,取得cpu控制權,這時候系統處于idle狀態。只要它發現其他進程有事可做,就會立刻交出CPU控制權。
面對swapper前輩轉身離去時那落寞的背影,眾kernel線程都是唏噓不已,只有init進程在背后默默豎起中指,表達當初在權限上受到不公待遇的憤恨, 待續......
評論:
不考慮中斷異常以及技術細節,本文從進程的角度讓同學體會了kernel的存在感,kernel沒有那么神秘,不是超然的造物主,僅僅是一個進程而已,只不過子線程們承擔了很多特殊的管理任務而已,這些管理任務的詳情和省掉的技術細節我們可以慢慢了解,找尋答案,但是對kernel的整體把握要有,頭腦中始終有清晰的認識,無論系統跑到何時,我們都要知道身處何地(處于哪個進程或者中斷服務程序)。
中斷和異常表面上看起來優先級太高,執行過程過于霸道,但其實并沒有破壞進程間調度輪番取得CPU控制權的工作模式,這點我不打算詳細闡述了,書里邊對中斷異常描述的很好,同學們可以自己好好感悟一下。
從現在開始越來越不好寫了啊,壓力很大,不過我會加油的。
?
14 閱讀思路
上文描述的啟動過程涉及的源文件如下:
init/main.c
arch/x86/kernel/process_32.c
其實應該還包括更多的源文件,但 init/main.c 里的start_kernel()是啟動過程的核心,只要抓住這個地方,我們很容易就可以按圖索驥找到其他的源文件,同學要體會這個思路。
在看LKD的時候,同學除了要把概念原理算法弄清楚,還要像上面這樣,根據書中提供的線索,把每個知識點對應的源文件名找出來,記錄到筆記里,等將來需要的時候,我們再慢慢研究細節。
我們再舉系統調用(system call)的例子:
看過LKD里關于system call的描述,有如下的問題:
1) 為什么要有system call,system call有什么功能?
2) Usersapce通過什么指令調用kernel的system call,傳遞了什么信息?
3) kernel怎么建立system call number和具體實現函數的對應關系?
4) system call在執行的時候處于什么上下文?
5) 實現system call handler的主要源文件在哪里?
6) 除了書中給出的例子,再找一個system call的實現。
對于初次閱讀的我們,知道這些已經不錯了,最好把答案整理到筆記里。等讀完這本書,我們應該對基本概念有了了解,整體上對kernel有了把握。盡管對算法,實現細節不太了解,但是我們大體知道努力的方向了:后續深入的研究源碼。對于開發人員,優先選擇和我們的工作關系密切的部分深入鉆研吧。?
15 內存管理
想要從整體上理解linux的內存管理,我們需要理解內存管理的兩個大的功能:
1) 幫助MMU正常工作,實現物理地址和虛擬地址的轉換(paging)
2) 對物理內存進行管理,分配,回收,共享等等
相應的,這兩個功能分別對應下面兩個核心table:
1) page table
2) page frame descriptor table, 源碼中的名稱 mem_map
我們順著這個脈絡,對兩者進行比較,同時也就理順了內存管理的大體思路:
從分配回收方面比較:
1) page table 用來作paging,每個進程各有一份(我們把kernel看作一個大進程), kernel 的 page table 叫做 swapper_pg_dir, 是在啟動的時候創建的,每個進程在出生的時候會 以swapper_pg_dir為藍本建立各自的page table,進程死亡的時候kernel會回收他的page table,page table 一般采用多級結構,最后一級page table的每個entry都對應一個page大小的內存,每個entry的size一般是4個字節。(arch/x86/kernel/head_32.S, setup_arch()@init/main.c, paging_init()@arch/x86/mm/init_32.c)
2) mem_map 用來管理物理內存,在啟動的時候創建的,只有一個,永不釋放,mem_map和物理內存一一對應,每個entry管理了一個page的物理內存,每個entry的size為40個字節左右。(free_area_init_nodes()@mm/page_alloc.c)
具體過程:以x86體系結構為例
1) Linux啟動之后,指令中的地址必須是虛擬的,MMU負責轉為物理地址,轉化的過程就是查詢更新TLB緩存,檢索page table的過程。進程在運行時,會把頂級page table的首地址保存在CR3寄存器中,MMU根據CR3的值才得以找到正確的page table。進程切換的時候,需要更新CR3。
2) Kernel使用buddy算法對mem_map進行管理,申請釋放內存的size必須是2的n次冪page大小,在buddy之上,kernel還使用了slab算法(或slub)對內存進行再次管理,可以提供更小單位的申請釋放和cache功能,讓內存的使用更有效率,速度更快。(mm/page_alloc.c)
理解了這兩個table,我們就抓住了內存管理的核心,其中涉及的技術細節請自行了解,其他一些概念也很重要,如memory layout,zone,mm_struct, memory area management等,這里不展開了。
16 驅動程序概述
同學通過LKD對linux kernel進行了基礎的理解,對于驅動方向的同學來講,可以開始看LDD3,進行下一步對重點方向和任務的學習,本文給出一些重點需要關注的知識點。
從最簡單的模型上看,kernel的職責就是管理hardware,給app提供訪問接口。CPU, 內存,外設這些都是hardware;每個外設都有相應的驅動進行管理;從某種意義上講,CPU和內存也有著相應的驅動進行管理。
* 把驅動的功能抽象一下看,無非要完成下面的工作:
1) 初始化工作:檢測/PnP,供電,設置clock,設置register,及其他操作。
2) I/O工作:讓app能夠讀,寫,或控制hardware, 或許同時要提供多用戶共享,訪問保護,狀態恢復等功能。
3) power management
* 驅動程序框架
LDD 的14章詳細闡述了linux kernel model,這個是看驅動代碼,寫驅動程序的基礎,需要好好揣摩,核心是這3個概念:device,driver,bus
大多數的驅動都要實現driver里邊的函數指針(open, read, write等等),然后注冊給上層,提供app訪問的接口。這是kernel里邊常見的思路:定義結構,實現指針,注冊接口;我們也可借鑒到自己平時的程序中。
另外還要了解kobj,sysfs的一些用法,我們在寫程序的時候盡量用這個接口替換procfs。
另外,為了讓我們的driver越來越簡單,同時減少冗余的代碼,kernel往往會把不同廠商的驅動中一些公共的代碼抽出來,建立一套框架和公共接口。具體例子有:
input設備的驅動框架(drivers/input/input.c);
Video設備的驅動框架V4L2(drivers/media/video/videobuf-core.c);
音頻設備的框架(sound/sound_core.c)。
* 在驅動編程中,我們要熟悉下面這些東西:
kernel的重要概念:
irq, irq handler, dma
softirq, tasklet, timer
work queue, wait queue, kernel thread
debug相關
printk, oprofile, ftrace, oops, kdb, procfs, sysfs, errno
一些常用的api:
list操作 include/linux/list.h
lock操作 include/linux/spinlock.h
mutex操作 include/linux/mutex.h
semaphore操作 include/linux/semaphore.h
bit操作 include/asm-generic/bitops/atomic.h
atomic操作 include/asm-generic/atomic.h
kfifo操作 include/linux/kfifo.h
alloc_page操作 include/linux/gfp.h
readX/writeX include/asm-generic/io.h
ioremap(), remap_pfn_range(), kmalloc(), vmalloc()
schedule_timeout()
在書中對上面的知識點有大量的講解,源碼中也有很好的示例,我們要深刻理解,這里就不是掌握整體了,而是熟練掌握。
17 驅動開發任務
了解知識只是基礎,還是要以任務為核心,找到解決問題的思路。我們在做驅動開發的時候,一般會面臨下面幾種任務:
1) 維護現成的驅動代碼,改bug;負責寫用戶空間的接口庫或者測試程序。
2) 從別的平臺上移植驅動到linux上。
3) 從頭開發一個驅動,我們可能只有硬件手冊上面的偽代碼參考。
對于1),任務簡單,往往是給新手的活,前幾章的學習過程往往也是在執行這個任務的時候進行的,例如:組長給新員工分配了寫測試程序的活,他需要學學類似APUE這樣的書,分配修bug的任務時,他需要學學kernel和driver基礎。
當我們經過了前幾章的大量練習,可以很輕松的完成測試程序的任務,但是我們是有追求的程序員,我們需要給自己更高的要求,就像第六章提到的那樣,不停的改進自己的測試程序,多參考開源代碼成熟的例子,體會更好的編程技巧。
通過測試程序,有了對驅動接口直觀的了解之后,我們開始詳細的學習驅動的代碼,只有完整的了解了驅動的方方面面,后邊解bug的時候才能游刃有余。我們需要做到:
a) 仔細閱讀硬件參考手冊,了解硬件工作的原理,流程,時序圖,寄存器介紹。
b) 了解對應的驅動框架,比如前文提到的input,alsa,V4L2等,看看你負責的驅動是否工作于一個框架下,涉及的內核基本知識要確保都能搞懂。
c) 分析驅動代碼,不光是看,要把debug打開,跑跑測試程序,看看log,流程自然就清晰了,分析代碼會快上許多,如果用示波器或者邏輯分析儀抓抓波形,可更好的加深印象。
能做到以上這些,解bug就是很容易的事情,把前文提到的debug方法熟悉一下是必要的。debug工作本身還是有一些技巧的,需要在實踐中多總結,注意觀察有經驗同事的做法。有時驅動的bug是硬件造成的,接線錯了,少了個電阻電容,這些都是常有的事,大家不要抱怨,溝通的時候注意技巧,想辦法證明自己的代碼沒有問題,這樣才能讓硬件工程師甘心改板子。
為什么對任務1)費這么多口舌呢?因為,這個例子契合了本系列寫作的初衷:完事開頭難,我們要花大力氣把開頭的工作做好做精,基礎打牢,掌握好的方法,樹立信心,培養興趣,后邊的工作就會水到渠成,游刃有余。
對于任務2),步驟和1)幾乎一樣,只需要額外注意2點:
a) 分析好原平臺的代碼,移植的時候多參考linux里邊現有的例子
b) 測試程序盡早編寫,集成和修bug工作離不開它。
對于任務3),我覺得這里不需要再給出任何提示,一路走來的同學你自己琢磨吧。
有意思,講驅動的文章,卻不討論如何從頭編寫驅動。其實,任何看起來復雜的任務都經不起推敲和分解,當分解成一個個小塊的時候,我們就看到了當年做過的大量基礎練習。我們要好好練習推敲和分解,掌握自己的思路。
18 網絡進階
為了內容結構的完整性,寫一下網絡的問題,我已經很久不搞了,別說細節,給輪廓都很難了。
網絡始終是一個實踐性特別強的部分,理論如果不聯系著實際理解,會學得非常痛苦。
一般我們有了kernel和網絡編程的基礎之后,可以開始學習協議棧的實現,也就是參考書<9>,在虛擬機上面把kernel 網絡部分的log打開,跟著書里描述的過程,大致了解一下網絡協議棧。觀察協議棧如何傳遞skbuf這個指針是很有意思的一個看點。
要想深入了解,可以從netfilter入手,它是目前國內網絡方向的重點。
先從練習iptables命令開始,在虛擬機上面實現一個NAT網關還是不難的吧,最好把iproute這個包也安裝一下,了解一下ip命令,這是配置路由表的工具,有了這兩個工具的使用基礎,你就會有了直觀的概念。 如夠看看iptable和ip的源碼,了解一下網絡層的用戶接口,當然更好了。
進入到了解netfilter具體框架的階段,我們要在頭腦中深刻的建立這個圖的印象:
http://upload.wikimedia.org/wikipedia/commons/8/8f/Diagrama_linux_netfilter_iptables.gif
閱讀代碼,找到圖中每個關鍵點的位置,了解相關概念。當然,還要實踐一下,添加一個自己的netfilter module,這都是必須的。
linux的路由選擇過程也要徹底理解,這和netfiler也是息息相關的。
netfilter往往還需要配合流量控制,先學習tc這個用戶空間的工具,然后在看kernel的實現。
ebtables是設置網橋規則的工具,也需要了解。
這章也得簡單潦草,算是一個草稿,等將來再改進吧。
19 自我認知
曾經一位很很有經驗的工程師和我講過要學會對事情分類:
1) 重要且緊急的事情
2) 不重要且緊急的事情
3) 重要不緊急的事情
4) 不重要不緊急的事情
要按照這個順序展開工作,這使我第一次思考除了技術以外的一些事情。
在工作開頭的幾年,我在打基礎的同時,也經歷著一個自我認知和行業認知的過程。
在浮躁的社會背景下,開發工作不是一個經濟效益很高的選擇,而且行業整體的競爭比較激烈,新陳代謝比較快,而且業內普遍追求急功近利,重營銷,不重技術等等。
在這樣的背景下,開發人員對未來進行選擇很不容易,有些人天生對社會有很好的適應能力,他們很容易轉型到非技術崗位,找到合適自己的方向,經濟效益也不錯,這是一個值得考慮的選擇。
有些人不喜歡這個行業和自己的工作,于是選擇了轉行,
有些人也不喜歡但選擇了硬挺,為了生存和家庭。
不管怎樣,經歷了最初的幾年,每個人都要思考自己未來的職業生涯了,該怎么選擇自己的道路?這是沒有標準答案的問題,因為每個人的追求和價值觀是不同的。
但對于那些踏實,勤奮,熱愛開發的geek工程師們,你們不能迷茫,你們要好好的把握住自己的未來。你們需要迫切解決下面這兩個問題:
成為一名優秀的工程師
在一個好的團隊中找到適合自己的位置。
達成了這兩個目標,你的職業生涯豁然開朗,你的生活既充實有愜意,這也會給你帶來出色的表現,其他方方面面的問題也會迎刃而解。
?
20 成為牛人
剛走向社會的同學往往喜歡打聽別人的薪水,羨慕別人的職位,但并不關注別人奮斗的過程和成長的體會;羨慕朋友所在的公司上市,卻不關心他的團隊運作和管理。我們是不是應該豐富一下關注的點?
我遇到過輾轉進入心儀的大公司,拿到豐厚薪水,但抱怨生活不如意的
我遇到過拿到高薪,高職位,空有才干,卻整天無所事事的
我也遇到過畢業就留在二線城市的小公司,至今做大,成為公司高管的
我也遇到過勤勤肯肯鉆研技術,筆耕不輟,成為IT行業知名作家和創業公司CTO的
我也遇到過從編程轉行做買賣發財的
上面這些人都是傳統意義上的"牛人",都有自己的性格特點和興趣點,每個人的軌跡都不能簡單的復制,但總體來說:牛人需要付出很大的辛苦,同時在工作中表現得也很好,其中比較幸運的在付出的同時也在享受自己的事業。
我不想做什么熱愛工作的勵志教育,我只是想說牛人是沒有模板的,誰都有機會,希望同學們在打聽薪水之余,能夠多了解一下背后的故事,體會一下別人的辛勤付出,找到自己的路,即使你要混黑社會也要敬業不是。?
轉載于:https://www.cnblogs.com/uhasms/archive/2011/11/15/2250474.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的zzfrom水木-Linux环境学习和开发心得(作者:lunker)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VSS (Visual Source S
- 下一篇: watir添加新标签