写在前面
- 内核版本:2.6.35
- 文章中贴出的源码会省略一些琐碎的、对分析无益的细节
- 结构体中包含其他结构体,我称之为包含关系,类似面向对象中的继承;结构体中包含其他结构体的指针,我称之为绑定关系
- 软件层面的框架就是提取出一个任务中整体的、通用的、固定的逻辑,通过对外暴露一定的接口来获取运行所必需的数据和具体操作逻辑(函数)
- led框架由内核开发者提供,位于
/drivers/leds/led-class.c
,其中比较重要的两个函数是leds_init
和led_classdev_register
1. leds_init函数分析
static struct class *leds_class;
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds"); //创建leds类设备
leds_class->suspend = led_suspend; //填充函数指针
leds_class->resume = led_resume; //填充函数指针
leds_class->dev_attrs = led_class_attrs; //填充device_attribute数组
return 0;
}
该函数主要工作就是创建并填充一个名为leds
的class
实体,更加细致的分析如下
1.1 关于class
// include/linux/device.h
struct class {
const char *name; //类的名字
struct class_attribute *class_attrs; //class_attribute数组
struct device_attribute *dev_attrs; //dev_attribute数组,用来描述属于该类设备所共有的属性
//----一些函数指针----
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
};
class
结构在语义上用来描述一类设备所具有的通用的属性和操作,一个class
实体用来描述一类设备,所以leds_init
函数中创建了一个名为leds
的class
实体来描述led
设备的共有属性和操作逻辑。class
结构不同于面向对象语言中的类的概念,在这里它只是一个普通的结构。
想要更完整地了解class
,还需要进一步地理解attribute
结构。class
结构中绑定了class_attribute
类型和device_attribute
类型的数组,前者用来描述该类属性,后者用来描述属于该类的设备的共有属性;这两个结构体内部都包含了attribute
结构,可以认为这两个结构是attribute
的子类。
// include/linux/device.h
struct class_attribute {
struct attribute attr;
//-----对类属性的读写方法-------
ssize_t (*show)(struct class *class, struct class_attribute *attr,
char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr,
const char *buf, size_t count);
};
struct device_attribute {
struct attribute attr;
//对设备属性的读写方法
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
// include/linux/sysfs.h
struct attribute {
const char *name; //属性名,在sysfs中表现为文件名
mode_t mode; // 文件权限
};
从attribute
的声明位置可以看出,attribute
设计为用来在sysfs中使用,即用来提供用户和内核的交互接口,attribute
实体会表现为sysfs中的一个文件,用户对其的读写操作会追踪到内核中具体的show
和store
方法。
所以,如果希望通过读写attribute
文件来控制硬件设备,驱动中就需要填充对应attribute
中的函数指针,leds_init
函数中使用的led_class_attrs
数组中就实现了函数指针的填充,代码如下:
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store), //brightness属性,填充读写方法
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL), //max_brightness属性,填充读方法,相应的文件权限也设置为了只读
...
};
1.2 class_create函数分析
class_create
内部调用__class_create
,所以我们直接分析后者;
// driver/base/class.c
struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
{
//---分配堆空间---
struct class *cls;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
//---结构体填充---
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release; //填充类的释放函数,刚出生就准备好怎么死了
__class_register(cls, key); //向内核注册方才创建的class实体
return cls;
}
标准的先分配空间后填充流程,最后调用__class_register
向内核注册创建好的类,至于具体如何注册这里就不分析了,我也没进去看,但无非是将创建的类的信息填充到内核维护的某个数组中。
至此,leds_init
函数的分析就完毕了,我们再整体描述一遍其执行过程:
内核在启动过程中的某个阶段会调用leds_int
函数,该函数首先使用class_create
创建并注册leds
类;之后填充suspend
和resume
函数;再使用文件内定义的led_class_attrs
数组填充leds
类中的device_attribute
数组,实现attribute
文件和读写函数的绑定。
2. led_classdev_register函数分析
/*
* parent: 要创建设备的父设备
* led_cdev: 需要框架使用者提供的结构体
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
//创建device实体,填充到传入的led_classdev实体中
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, "%s", led_cdev->name);
return 0;
}
该函数主要工作为创建device
实体,将其绑定到leds_class
,当然device_create
函数内部一定对device
实体进行了注册。
// include/linux/leds.h
struct led_classdev {
const char *name;
int brightness; //亮度
int max_brightness; //最大亮度
int flags;
struct device *dev; //绑定dev结构,也应该可以理解为父类
void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness);
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
};
led_classdev
语义上可以理解为dev
的子类,是对led
设备的描述。框架的使用者应当创建led_classdev
实体,将其传入led_classdev_register
函数。
综上,led框架的源码就算大致分析完毕了。该框架暴露给开发者的接口是led_classdev_register
,开发者准备好led_classdev
实体,该实体就提供了框架需要的数据(比如设备名、亮度)和操作逻辑(设置亮度等)。当然还有led_classdev_unregister
接口,比较简单,这里就不分析了。