应群里几位聊的好的哥们的邀请,要我分析一个内核模块。我后面就选了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);
}
首先将其加入链表,再将trigger挂在led_cdev的trigger成员中,然后查看trigger->activate是否存在,如果存
在就调用trigger->activate()。
我们回到最首先注册的trigger的定义:
static struct led_trigger heartbeat_led_trigger = {
.name = "heartbeat",
.activate = heartbeat_trig_activate,
.deactivate = heartbeat_trig_deactivate,
};
我们继续分析heartbeat_trig_activate()。
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data;
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
if (!heartbeat_data)
return;
led_cdev->trigger_data = heartbeat_data;
setup_timer(&heartbeat_data->timer,
led_heartbeat_function, (unsigned long) led_cdev);
heartbeat_data->phase = 0;
led_heartbeat_function(heartbeat_data->timer.data);
}
很明显这里面最重要的东西就是初始化一个定时器然后调用led_heartbeat_function(),另外请注意定时器中
定义的钩子函数也是这个。
static void led_heartbeat_function(unsigned long data)
{
struct led_classdev *led_cdev = (struct led_classdev *) data;
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
/* acts like an actual heart beat -- ie thump-thump-pause... */
switch (heartbeat_data->phase) {
case 0:
/*
* The hyperbolic function below modifies the
* heartbeat period length in dependency of the
* current (1min) load. It goes through the points
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
*/
heartbeat_data->period = 300 +
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
heartbeat_data->period =
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
case 1:
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
heartbeat_data->phase++;
break;
case 2:
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
brightness = led_cdev->max_brightness;
break;
default:
delay = heartbeat_data->period - heartbeat_data->period / 4 -
msecs_to_jiffies(70);
heartbeat_data->phase = 0;
break;
}
led_set_brightness(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
我们又“避重就轻”的看到了这个函数最重要的一个两个函数:
led_set_brightness(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
led_set_brightness(led_cdev, brightness)这个函数从传入的参数看哥猜测是和led_cdev内核的成员函数相关
的,前面我们一直先没有分析这个,所以跟起来有点麻烦,点进去一看,果不其然:
static inline void led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value > led_cdev->max_brightness)
value = led_cdev->max_brightness;
led_cdev->brightness = value;
if (!(led_cdev->flags & LED_SUSPENDED))
led_cdev->brightness_set(led_cdev, value);
}
是和led_cdev相关的,行,哥先忍着你,不理你。
mod_timer(&heartbeat_data->timer, jiffies + delay);
这个就开始干活了,定时器一设定我们就开始干活罗。
我们之前一直绕过了一个东西那就是led_cdev这一块。
好我们找一找。哥在/drivers/leds中查找"heartbeat"。为什么要找这个呢?
还记得之前分析的地方:
if (!led_cdev->trigger && led_cdev->default_trigger &&
!strcmp(led_cdev->default_trigger, trigger->name))
led_trigger_set(led_cdev, trigger);
这里绑定的条件就是name。所以我们要以此为线索查找。
找了一下得到如下结果:
./leds-sunfire.c: .default_trigger= "heartbeat",
./leds-sunfire.c: .default_trigger= "heartbeat",
./ledtrig-heartbeat.c: .name = "heartbeat",
我们进入leds-sunfire.c中看一看。
static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "fhc-left",
.handler = fhc_left_set,
},
{
.name = "fhc-middle",
.handler = fhc_middle_set,
},
{
.name = "fhc-right",
.handler = fhc_right_set,
.default_trigger= "heartbeat",
},
};
找到一个这样的玩意。很明显是和led相关的,这时候哥有50%的把握这个文件中包含了我们想找的东西。快速浏
览后哥就有100%的把握了。
platform_driver_register(&sunfire_fhc_led_driver);
这是平台总线那个门派的东西,我们先不分析平台总线那一门派的技巧了,只告诉大家一个事,调用这个函数后
sunfire_fhc_led_driver中的probe成员函数将会被调用。(分析平台总线的文章很多,也不难,就算对这个不懂
也不会影响我们的分析,只要记住我上面那句话就行了)
static struct platform_driver sunfire_fhc_led_driver = {
.probe = sunfire_fhc_led_probe,
.remove = __devexit_p(sunfire_led_generic_remove),
.driver = {
.name = "sunfire-fhc-leds",
.owner = THIS_MODULE,
},
};
static int __devinit sunfire_fhc_led_probe(struct platform_device *pdev)
{
return sunfire_led_generic_probe(pdev, fhc_led_types);
}
static int __devinit sunfire_led_generic_probe(struct platform_device *pdev,
struct led_type *types)
{
struct sunfire_drvdata *p;
int i, err = -EINVAL;
if (pdev->num_resources != 1) {
printk(KERN_ERR PFX "Wrong number of resources %d, should be 1/n",
pdev->num_resources);
goto out;
}
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p) {
printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata/n");
goto out;
}
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) {
struct led_classdev *lp = &p->leds[i].led_cdev;
p->leds[i].reg = (void __iomem *) pdev->resource[0].start;
lp->name = types[i].name;
lp->brightness = LED_FULL;
lp->brightness_set = types[i].handler;
lp->default_trigger = types[i].default_trigger;
err = led_classdev_register(&pdev->dev, lp);
if (err) {
printk(KERN_ERR PFX "Could not register %s LED/n",
lp->name);
goto out_unregister_led_cdevs;
}
}
dev_set_drvdata(&pdev->dev, p);
err = 0;
out:
return err;
out_unregister_led_cdevs:
for (i--; i >= 0; i--)
led_classdev_unregister(&p->leds[i].led_cdev);
goto out;
}
看到没有?
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) {
struct led_classdev *lp = &p->leds[i].led_cdev;
p->leds[i].reg = (void __iomem *) pdev->resource[0].start;
lp->name = types[i].name;
lp->brightness = LED_FULL;
lp->brightness_set = types[i].handler;
lp->default_trigger = types[i].default_trigger;
err = led_classdev_register(&pdev->dev, lp);
if (err) {
printk(KERN_ERR PFX "Could not register %s LED/n",
lp->name);
goto out_unregister_led_cdevs;
}
}
这个函数:led_classdev_register(&pdev->dev, lp)说是我们想要的东西。
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
led_update_brightness(led_cdev);
init_timer(&led_cdev->blink_timer);
led_cdev->blink_timer.function = led_timer_function;
led_cdev->blink_timer.data = (unsigned long)led_cdev;
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
printk(KERN_DEBUG "Registered led device: %s/n",
led_cdev->name);
return 0;
}
初始化,加入链表,初始化定时器,如果打开了CONFIG_LEDS_TRIGGERS这个宏,就调用led_trigger_set_default
。显然我们是打开了的,否则就不会有这篇文章了。
void led_trigger_set_default(struct led_classdev *led_cdev)
{
struct led_trigger *trig;
if (!led_cdev->default_trigger)
return;
down_read(&triggers_list_lock);
down_write(&led_cdev->trigger_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
if (!strcmp(led_cdev->default_trigger, trig->name))
led_trigger_set(led_cdev, trig);
}
up_write(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
}
看到没有led_trigger_set(led_cdev, trig);
所以说,不管谁先来谁后来,都可以起到作用。
这就是内核是的lcd_dev和trigger的关系了。
谁先谁后都没关系的,大家是平等的,失去谁都不行。
OK,分析就到这里了。
写文章真不容易啊,我看的话也就半天,可是构思怎么样写文章,写文章花了两天。