調試知識
1、通過核心格式化列印函數printk()
- 該函數健壯性比較強,幾乎可以随時随地調用它,隻有在系統啟動過程中,終端還沒有初始化之前,在某些地方不能使用
- 如果要調試啟動過程中最開始的那些步驟,有些時候可以依靠能夠工作的硬體裝置與外界通信
- printk()函數可以指定日志級别,核心把級别比某個特定值低的所有消息顯示在終端上,預設等級是 KERN_WARNING
記錄等級 | 描述 |
---|---|
KERN_EMERG | 緊急情況 |
KERN_ALERT | 警惕性錯誤 |
KERN_CRIT | 臨界情況 |
KERN_ERR | 錯誤 |
KERN_WARNING | 警告 |
KERN_NOTICE | 通知資訊 |
KERN_INFO | 正常資訊 |
KERN_DEBUG | 調試資訊 |
- 終端記錄等級console——loglevel也可以控制列印輸出,預設是DEFAULT_MESSAGE_LOGLEVEL
- 核心消息都被儲存在一個LOG_BUF_LEN大小的環形隊列中,可以通過設定CONFIG_LOG_BUF_SHIFT進行調整,如果消息隊列滿,新消息将覆寫隊列中的老消息
- 使用者空間的守護程序klogd(會阻塞,直到有新的核心消息可供讀出)從記錄緩沖區中(/proc/kmsg檔案中或通過syslog()系統調用)擷取核心消息,再通過syslogd守護程序将它們儲存在系統日志檔案(預設/var/log/messages)中,可以通過/etc/syslog.conf配置檔案重新指定輸出檔案
2、核心釋出oops
- 向終端輸出錯誤消息,輸出寄存器中儲存的資訊并輸出可供跟蹤的回溯線索
- ksymoops指令可以對oops進行解碼,程式設計我們易懂的資訊
- 2.5核心引入kallsyms特性,通過定義CONFIG_KAIISYMS配置選項啟用(核心變大一些),核心可以列印解碼好的跟蹤線索
3、核心調試配置選項
- 在編譯的時候,為了友善調試和測試核心代碼,核心提供了許多配置選項:slab層調試選項、高端記憶體調試選項、I/O映射調試選項、自旋鎖調試選項、棧溢出檢查選項等
4、引發BUG并列印資訊
- 提供斷言并輸出資訊,常用的有BUG()和BUG_ON()
- BUILD_BUG_ON()僅在編譯時使用
- panic()引發更嚴重的錯誤,列印錯誤消息,挂起整個系統
- 列印一下棧的回溯資訊來幫忙調試,可以使用dump_stack()
5、SysRq(系統請求)鍵
- 通過定義CONFIG_MAGIC_SYSRQ配置選項啟用
- 該鍵是調試和挽救垂危系統所必需的一種工具,該功能對終端上任何使用者都提供服務,要慎重使用
6、gdb
- gdb vmlinux /proc/kcore //vmlinux 未壓縮核心映像
- 使用-g參數在編譯時提供調試資訊
- kgdb更新檔,支援遠端主機上通過序列槽利用gdb的所有功能對核心進行調試
7、使用探測系統的小技巧調試
- 利用選擇條件實作執行不同分支或算法
- 使用全局變量作為條件選擇開關
- 使用統計量
- 重複頻率限制控制輸出,控制列印間隔,控制列印次數等
8、二分搜尋比較擷取BUG變更分支
9、社群求助:Linux核心郵件清單(LKML)
可移植作業系統知識
1、字長和資料類型
- 能夠由機器一次完成處理的資料稱為字,處理器通用寄存器(GPR)的大小和它的字長是相同的,c語言定義的long類型總是對等于機器的字長
- 一般有如下準則:char類型長度是1位元組 int類型長度是32位,short類型是16位,指針不應假定為long的長度,sizeof(int)不應假設等于sizeof(long)
- 不透明類型:不要假設該類型的長度,該類型的大小可能被任意修改;不要将該類型轉化回其對應的C标準類型使用;程式設計時要保證該類型的實際存儲空間或格式變化時代碼不受影響
- 指定資料類型:注意對應的類型後再使用
- 長度明确的類型:對标準的C類型進行映射得到
- char類型的符号問題:char某些體系結構上是預設帶符号,有些事預設不帶符号,如果能明确使用哪個,直接聲明好
2、資料對齊
- 如果一個變量的記憶體位址正好是它長度的整數倍,那麼是自然對齊。編寫高可移植性代碼要避免對齊問題,保證所有的類型都能夠自然對齊
- 一個資料類型長度較小,本來是對齊的,如果你用一個指針進行類型轉換,轉換後的類型長度較大,那麼通過改指針進行資料通路時就可能會引發對齊問題
- 非标準類型C資料類型對齊:對于數組,隻要按照基本資料類型進行對齊即可;對于聯合體,隻要它包含的長度最大的資料類型對齊即可;對于結構體,隻要結構體中每個元素能夠正确對齊即可
- 結構體填補:為了保證結構體中每一個成員都能夠自然對齊,結構體要被填補,不同體系結構之間所需的填補也不盡相同(結構體重新排序有時可以避免填補,但是某些有固定次序要求的會有問題)
3、位元組順序
- 位元組順序是指一個字中各個位元組的順序,處理器對字取值時既可能将最低有效位所在的位元組當做第一個位元組,也可能将其當做最後一個位元組,這就是大端小端問題。
- Linux支援每一種體系結構,定義了一組宏指令用于位元組順序之間的轉換
4、時間
時間測量是一個核心概念,絕對不要假定時鐘中斷發生的頻率,應該使用HZ(每秒産生的jiffies數目)來正确計量時間
5、頁長度
絕對不能假設頁長度,當處理用頁組織管理的記憶體時,通過PAGE_SIZE以位元組數來表示頁長度
6、處理器排序
有些處理器嚴格限制指令排序,有些體系結構對排序要求很弱(可以用記憶體屏障來保證一定的指定執行順序)
7、SMP、核心搶占、高端記憶體
- 假設代碼在SMP系統上運作,要正确選擇和使用鎖
- 假設代碼在支援核心搶占情況下運作,要正确選擇和使用鎖和核心搶占語句
- 假設代碼運作在使用高端記憶體(非永久映射記憶體)的系統上,必要時使用kmap()
Linux編碼風格
1、縮進
- 縮進風格是用制表位每次縮進8個字元長度
- switch語句下的case标記應該縮進到和switch聲明對齊
- 空格放在關鍵字周圍,函數名和圓括号之間無空格;函數、宏以及與函數相像的關鍵字在關鍵字和圓括号之間沒有空格;對于大多數二進制或者三元操作符,在操作符的兩邊加上空格,對于大多數一進制操作符,在操作符和操作數之間不加空格
- 花括号的左括号緊跟在語句的最後,與語句在相同的一行,而右括号要新起一行,作為該行的第一個字元,函數不采用這樣的書寫方式,重起一行;不需要一定使用括号的語句可以忽略
- 每行代碼的長度不超過80個字元
2、 命名規範
- 名稱中不容許使用混合的大小寫字元
- 局部變量能清楚表明它的用途即可,不接受冗長繁複的名字,在變量名稱中加入變量的類型是不必要的
- 全局變量和函數應該選擇包含描述性内容的名稱,并且使用小寫字母,必要時加上下劃線區分單詞
3、函數
- 函數代碼長度不應該超過兩屏,局部變量不應超過10個
- 一個函數應該功能單一并且實作精準,擔心函數調用開銷,可以使用inline關鍵字
4、注釋
-c風格注釋 代碼注釋一般描述代碼要做什麼和為什麼要做即可
/*
*
*/
5、typedef
盡量少用typedef,除非有必要,比如隐藏變量與體系結構相關的實作細節,某種類型将來有可能發生變化等
6、多使用核心本身提供的一些通用函數,盡量少自己重複實作,減少一些問題比如移植性問題等
7、要在合适的地方使用ifdef預處理指令
8、結構初始化
結構初始化的時候必須在它的成員前加上結構辨別符
9、代碼格式化
使用一些工具按照核心編碼風格對代碼進行格式化