Linux内核LED模块分析

分析一个内核模块。我后面就选了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);
 }

相关推荐