歡迎您光臨本站 註冊首頁

從新手村開始,手把手帶你入門梳理核心程式碼

←手機掃碼閱讀     admin @ 2020-08-10 , reply:0
作者:姜亞華(@二如公子 ),《精通 Linux 核心——智慧裝置開發核心技術》的作者,一直從事與 Linux 內核和 Linux 程式設計相關的工作,研究核心程式碼十多年,對多數模組的細節如數家珍。曾負責華為手機 Touch、Sensor 的驅動和軟體最佳化(包括 Mate、榮耀等系列),以及 Intel 安卓平臺 Camera 和 Sensor 的驅動開發(包括 Baytrail、Cherrytrail、Cherrytrail CR、Sofia 等)。現負責 DMA、Interrupt、Semaphore 等模組的最佳化與驗證(包括 Vega、Navi 系列和多款 APU 產品)。

往期回顧:

Java 離內核有多遠?

在上一期內容中,我們介紹了從 JVM 到內核的編譯原理,告訴大家應用和系統工程師如何接觸到核心。本文將從一個簡單的底層硬體模組入手,一步步教大家如何梳理核心程式碼。適合精力集中在核心,不太需要關心使用者空間的工程師,比如驅動工程師、嵌入式工程師等,以及想往這方面學習發展的朋友。

初探核心

版本資訊與往期一致:

Ubuntu

(lsb_release -a)

Distributor ID: Ubuntu

Description:    Ubuntu 19.10

Release:        19.10

Linux

(uname -a)

Linux yahua 5.5.5 #1 SMP … x86_64 x86_64 x86_64 GNU/Linux

在往期的訪談中,我們討論過如何閱讀核心程式碼,在這裡按照之前討論的思路詳細擴充套件下。

在 drivers/input/keyboard 下面的檔案是鍵盤驅動,我們選擇 lm8333.c 吧(沒什麼特殊理由,其他的也可以)。

找到 module_init,xxx_init,module_xxx,這些就是模組(驅動也是一種模組)的入口(進階點 1,系統的啟動過程),lm8333.c 內對應的是 module_i2c_driver(lm8333_driver),註冊 driver。

lm8333_driver 定義如下:

static struct i2c_driver lm8333_driver = {
 .driver = {
 .name	= "lm8333",
 },
 .probe	= lm8333_probe,
 .remove	= lm8333_remove,
 .id_table	= lm8333_id,
 };

驅動和裝置匹配後,會回撥 probe(進階點 2,Linux Device Driver,LDD),也就是 lm8333_probe,它的關鍵程式碼如下:

static int lm8333_probe(struct i2c_client *client, const struct i2c_device_id *id) //1
 {
 const struct lm8333_platform_data *pdata =	dev_get_platdata(&client->dev);
 struct lm8333 *lm8333;
 struct input_dev *input;
 lm8333 = kzalloc(sizeof(*lm8333), GFP_KERNEL); //7
 input = input_allocate_device(); //8
 lm8333->client = client; //10
 lm8333->input = input; //11
 input->name = client->name; //13
 input->dev.parent = &client->dev; //14
 input->id.bustype = BUS_I2C; //15
 input_set_capability(input, EV_MSC, MSC_SCAN); //16
 err = matrix_keypad_build_keymap(pdata->matrix_data, …, input); //18
 if (pdata->debounce_time) {
 err = lm8333_write8(lm8333, LM8333_DEBOUNCE,
     pdata->debounce_time / 3); //22
 }
 if (pdata->active_time) {
 err = lm8333_write8(lm8333, LM8333_ACTIVE,
     pdata->active_time / 3); //27
 }
 err = request_threaded_irq(client->irq, NULL, lm8333_irq_thread,
    IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
    "lm8333", lm8333); //32
 err = input_register_device(input); //34
 i2c_set_clientdata(client, lm8333); //36
 return 0;
 }

probe 的任務是驅動的初始化和設定,初學階段,並不需要每一行程式碼都深入學習,可以先嚐試將程式碼分類,以 lm8333_probe 為例。

第 1 行,函式的引數型別是固定的,背後是 LDD。

第 7 行,申請記憶體,背後是記憶體管理。暫且把它當成c語言的 malloc 也無妨。

第 8/13~16/18/34,input 相關,背後是 input 子系統。

第 22/27 行,寫暫存器,背後是 i2c 匯流排。

第 32 行,request_threaded_irq,背後是中斷處理。

這些背後的機制每一個都是一個進階點。

初始化完畢,中斷產生後,會呼叫 request_threaded_irq 時傳遞的 lm8333_irq_thread,繼續梳理它的邏輯。

static irqreturn_t lm8333_irq_thread(int irq, void *data)
 {
 struct lm8333 *lm8333 = data;
 u8 status = lm8333_read8(lm8333, LM8333_READ_INT);
 if (!status)
 return IRQ_NONE;
 if (status & LM8333_ERROR_IRQ) {
 //省略
 }
 if (status & LM8333_KEYPAD_IRQ)
 lm8333_key_handler(lm8333);
 return IRQ_HANDLED;
 }

可以看到 lm8333_irq_thread 先讀暫存器來判斷產生中斷的原因,是 ERROR 還是 KEYPAD,如果是後者,呼叫 lm8333_key_handler。

static void lm8333_key_handler(struct lm8333 *lm8333)
 {
 struct input_dev *input = lm8333->input;
 u8 keys[LM8333_FIFO_TRANSFER_SIZE];
 u8 code, pressed;
 int i, ret;
 ret = lm8333_read_block(lm8333, LM8333_FIFO_READ,
 LM8333_FIFO_TRANSFER_SIZE, keys);
 for (i = 0; i < LM8333_FIFO_TRANSFER_SIZE && keys[i]; i++) {
 pressed = keys[i] & 0x80;
 code = keys[i] & 0x7f;
 input_event(input, EV_MSC, MSC_SCAN, code);
 input_report_key(input, lm8333->keycodes[code], pressed);
 }
 input_sync(input);
 }

lm8333_key_handler 讀暫存器,然後根據暫存器的值判斷實際的按鍵,呼叫 input_report_key 報告資料。

好了,lm8333.c 的邏輯我們清楚了:初始化、設定中斷、讀取資料並 report。

我們從 lm8333 的硬體角度看看,它是一個比較簡單的晶片,datasheet(資料手冊,下載地址)也並不複雜,摘取其中一段。

n ACCESS.bus (I2C-compatible) communication interface to the host
n Four general purpose host programmable I/O pins with two optional (slow) external Interrupts
n 16 byte FIFO buffer to store key pressed and key released events
n Host programmable active time and debounce time

相容 i2c 匯流排,支援中斷,16 位元組的 buffer,主機可程式設計有效時間和去抖時間。

再看看暫存器(這個文件稱之為 command)表。

CMD

Data Bits

Description

0x20 FIFO_READ

128

Read an event from the FIFO.

Maximum 14 event codes stored in the FIFO.

MSB = 1: key pressed.

MSB = 0: key released.

0x22 DEBOUNCE

8

Default is 10 ms. Valid range 1255.

Time ~ n x 4 ms

再看看程式碼裡出現的 i2c 讀寫的地址 LM8333_DEBOUNCE(0x22)和 LM8333_FIFO_READ(0x20)這些,這個表就是依據。

驅動做的事情可以分為兩個方面,一方面是處理晶片本身的邏輯,比如中斷、i2c、暫存器和時序等;另一方面是系統方面的,驅動和裝置匹配、中斷處理、資料傳遞(報告)等。

lm8333 比較簡單,但複雜的晶片多如牛毛,所以驅動工程師也可以分為兩類,一類比較專注於晶片本身的邏輯,另一類游到內核的大海中去了。

複雜的晶片本身就是一個完整的系統,成千上萬的暫存器,錯綜複雜的模組,能將這些弄清楚也是有很大挑戰的。除此之外,複雜的晶片很多都有配套的軟體架構,比如 ISP(Camera)相關的 V4L2(Video For Linux 2),GPU 相關的 DRM(Direct Rendering Manager)。

很明顯,晶片本身的邏輯並不是本文的重點,我們更關心如何游到核心。

再看 lm8333.c,大概清楚它的主要流程後,我們基本就算脫離新手村了,就像網遊一樣可以進入到下一階副本了。回憶一下,在新手村,我們只需要識別出哪些函式屬於其他模組,瞭解它們的基本原理,熟悉本身模組的邏輯即可。

 

進入第二個階段,最好先從與日常工作關係最密切的模組入手。比如 lm8333,連線在 i2c 匯流排上,獲取資料後透過 input 子系統 report,就可以從 i2c 和 input 入手。

學習 i2c 的過程中,還要解決 i2c 匯流排和 lm8333 的關係,這就涉及到 LDD。

深入 input 子系統的過程中,如果你對使用者空間得到資料的過程感興趣,就涉及到檔案系統、poll/epoll 等。

當然了,在這個階段,最好還是把檔案系統這些複雜的模組當作黑盒。小碎步前進,不斷有收穫。

稍微複雜些的驅動可能還會有電源管理、工作佇列和等待佇列等機制,也可以在這個階段內梳理它們的原理,至於它們背後的程序管理這些也可以先放放。

有了這一身裝備,應付副本里的小 BOSS 也綽綽有餘了,相比新手村那會也更有成就感,可以仗劍天涯了。

 

第三個階段就是解決之前遺留的疑問了,將記憶體管理、檔案系統和程序管理等一一拿下,比如 lm8333_probe 呼叫的 kzalloc、input 子系統涉及的 sysfs 檔案系統、工作佇列和中斷處理相關的程序排程,一步步深入挖掘。

在之前的問答活動裡我曾說過,“我已經把自己看過的程式碼的截圖放在隨書資料中了,算是一小段捷徑吧。這些截圖裡面,某函式、它呼叫的函式等函式呼叫關係使用紅線標示(如下圖),內容包括記憶體管理、檔案系統和程序管理三大模組。”

這些截圖是隨書資料,但並不是光碟那種。想要獲取資料的朋友歡迎在下面評論留言,或者郵件(linux_kernel_os@163.com)聯絡我,有任何疑問也可以找我共同探討。

 

往期回顧:

Java 離內核有多遠?

雲端計算時代,容器底層 cgroup 如何使用

雲端計算時代,容器底層 cgroup 的程式碼實現分析

雲端計算時代,容器底層 cgroup 如何實現資源分組?

 

 


[admin ]

來源:OsChina
連結:https://www.oschina.net/question/2918182_2318001
從新手村開始,手把手帶你入門梳理核心程式碼已經有231次圍觀

http://coctec.com/news/soft/show-post-248108.html