分析一個內核模塊。我後面就選了LED模塊分析,LED模塊分析不算難,但要說清楚其實還是很挑戰的。今天俺的文章被推薦到首頁了。挺有成就感的。我的文章雖然不登大雅之堂,但只要能給到大家一起指點,哪怕就一點點我就心滿意足了。好了,閒話不多說了,開始我們的linux內核之旅吧。
這一節是應群裡幾位兄弟的要求講LED模塊,我稍微看了一下,就挑了一個最軟的柿子來捏。怎麼樣挑到一個最軟的柿子呢?首先我們在分析這個模塊之前第一件事就是想辦法縮小范圍,如果不縮小范圍,你我的精力都是不夠的,可能會搞的非常痛苦,怎麼縮小。看法寶:Kconfig Makefile。我們首先進入/drivers/leds的目錄,然後查看Makefile,可以看到:
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o
。。。。
很明顯,前三個文件是逃不過的,這個玩意叫core。在內核中凡是叫core的東西基本是屬於必看的,沒事,才三個文件,哥們不怕(這都怕的就別玩了^_^)。哥幾個說要分析led的trigger,OK沒問題,俺就開始挑一個容易明白點的模塊了。ledtrig-heartbeat.c。哥為什麼要挑這個模塊講呢?在說原因之前我還是強調,有的東西只能見招拆招,要多動腦子,多找規律。我是這麼找的:打開Kconfig查看一下對應的信息。Kconfig給了我們什麼信息呢?首先就是注釋,其次是它本身依賴的模塊。(又見潛規則了,我們的計算機之所以能跑起來就是一大堆的潛規則。)這是Kconfig對trigger_heartbeat的描述:
config LEDS_TRIGGER_HEARTBEAT
tristate "LED Heartbeat Trigger"
depends on LEDS_TRIGGERS
help
This allows LEDs to be controlled by a CPU load average.
The flash frequency is a hyperbolic function of the 1-minute
load average.
If unsure, say Y.
我們可以發現這麼一句: depends on LEDS_TRIGGERS,這玩意就是我們前面看到的makefile中的那個core宏所包含的,哥心中暗爽了一把。撿了個現成的便宜。在對應的makefile中辦理入LEDS_TRIGGER_HEARTBEAT查找後得:
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
現在找准了我們要分析的對象後,就要打場硬仗了。
首先我們要有一個意識:像讀寫鎖等這些用於同步互斥等的手段對於我們理解模塊的體系結構的影響是非常小的,我們要先避重就輕,我現在在分析模塊的時候已經形成了一種語感,一眼掃過去,這種東西根本進不了我的腦袋中,可以較為快速的掃到重要的東西,從而加快自已分析代碼的速度,在實際寫或者改一個內核模塊的時候,
可以再仔細分析這些同步或互斥的手段。
很明顯,哥一眼掃過去後,發現這個函數做的事其實挺少的:
int led_trigger_register(struct led_trigger *trigger)
{
struct led_classdev *led_cdev;
struct led_trigger *trig;
rwlock_init(&trigger->leddev_list_lock);
INIT_LIST_HEAD(&trigger->led_cdevs);
down_write(&triggers_list_lock);
/* Make sure the trigger's name isn't already in use */
list_for_each_entry(trig, &trigger_list, next_trig) {
if (!strcmp(trig->name, trigger->name)) {
up_write(&triggers_list_lock);
return -EEXIST;
}
}
/* Add to the list of led triggers */
list_add_tail(&trigger->next_trig, &trigger_list);
up_write(&triggers_list_lock);
/* Register with any LEDs that have this as a default trigger */
down_read(&leds_list_lock);
list_for_each_entry(led_cdev, &leds_list, node) {
down_write(&led_cdev->trigger_lock);
if (!led_cdev->trigger && led_cdev->default_trigger &&
!strcmp(led_cdev->default_trigger, trigger->name))
led_trigger_set(led_cdev, trigger);
up_write(&led_cdev->trigger_lock);
}
up_read(&leds_list_lock);
return 0;
}
這個函數從哥的眼中看那就是四塊:
1. 初始化。
2. 做容錯判斷,看是否之前有被注冊過,如果注冊過,則返回-EEXIST;這樣我們用戶就可以得到一個已存在的提示信息。
3. 將其加入鏈表,方便管理。
4. 干活。
對於我們理解來說第3和4是最重要的。
第3很簡單,不分析了,看不懂的同志補補基礎知識。
第4塊是重點了。
down_read(&leds_list_lock);
list_for_each_entry(led_cdev, &leds_list, node) {
down_write(&led_cdev->trigger_lock);
if (!led_cdev->trigger && led_cdev->default_trigger &&
!strcmp(led_cdev->default_trigger, trigger->name))
led_trigger_set(led_cdev, trigger);
up_write(&led_cdev->trigger_lock);
}
up_read(&leds_list_lock);
前面如果大家有仔細看文章的話,應該發現了分析內核的一個小技巧,那就避重就輕。很明顯我們經過避重就輕後發現這一塊代碼最重要的也就是兩句:
if (!led_cdev->trigger && led_cdev->default_trigger &&
!strcmp(led_cdev->default_trigger, trigger->name))
led_trigger_set(led_cdev, trigger);
首先引出了一個新東西led_cdev。這個東西我等會再講,我們先繼續往下看。這一塊代碼的意思是:首先判斷led_cdev的觸發器存不存在?如果存在,就繼續看他是否有設置默認觸發器?如果有,則看他設置的默認觸發器的名字和我們模塊中觸發器的名字是否相同,這樣的判斷夠嚴謹吧?一來如果上一步的判斷不成功就直接退出,
二來又是下一步的基礎,防止空指針的引用等。如果成功的話就調用led_trigger_set。
void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
{
unsigned long flags;
/* Remove any existing trigger */
if (led_cdev->trigger) {
write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
list_del(&led_cdev->trig_list);
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
flags);
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
led_cdev->trigger = NULL;
led_brightness_set(led_cdev, LED_OFF);
}
if (trigger) {
write_lock_irqsave(&trigger->leddev_list_lock, flags);
list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
led_cdev->trigger = trigger;
if (trigger->activate)
trigger->activate(led_cdev);
}
}
看看,是不是有種似前相似的感覺?
哈哈。
1. 初始化。
2. 容錯。
3. 干活。
顯然,第一步很容易,只要是道上混的兄弟要看懂都不是問題。
接下來我們分析一下容錯這一步。
if (led_cdev->trigger) {
write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
list_del(&led_cdev->trig_list);
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
flags);
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
led_cdev->trigger = NULL;
led_brightness_set(led_cdev, LED_OFF);
}
如果有觸發器我們就先將其從led_cdev的trig_list中刪掉,然後判斷deactivate是否存在,如果存在則調用
deactivate()。然後再調用led_brightness_set()。
void led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
led_stop_software_blink(led_cdev);
led_cdev->brightness_set(led_cdev, brightness);
} static void led_stop_software_blink(struct led_classdev *led_cdev)
{
/* deactivate previous settings */
del_timer_sync(&led_cdev->blink_timer);
led_cdev->blink_delay_on = 0;
led_cdev->blink_delay_off = 0;
}
都是用led_cdev相關的,我們先忽略掉,繼續回到之前的地方分析。
if (trigger) {
write_lock_irqsave(&trigger->leddev_list_lock, flags);
list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
led_cdev->trigger = trigger;
if (trigger->activate)
trigger->activate(led_cdev);
}