linux驱动总结
一.前言
做linux开发也有一段时间了,对整个系统已经熟悉了很多,linux是一个非常大的系统,现在对常见的驱动做一个总结,以此来加深记忆和理解。
二.常见驱动及其子系统分类
1.Linux设备分类
linux系统抽象出的设备可以分为三类:char_dev,block_dev,net_dev。字符设备是产品开发用的最多的设备,其总类繁多,所以linux抽象出了许多子系统来适配不同设备,大大减少了代码量。
三.设备模型
1.设备模型的概念
设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念。
设备(device) :
挂载在某个总线的物理设备;/sys/devices目录记录了系统中所有设备,实际上在sys目录下所有设备文件最终都会指向该目录对应的设备文件;此外还有另一个目录/sys/dev记录所有的设备节点, 但实际上都是些链接文件,同样指向了devices目录下的文件。
驱动(driver) :
与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
总线(bus) :
负责管理挂载对应总线的设备以及驱动;在“/sys/bus”目录下的每个子目录都是注册好了的总线类型,每个总线类型下包含两个子目录——devices和drivers文件夹;其中devices下是该总线类型下的所有设备(符号链接),它们分别指向真正的设备在"/sys/devices/"下;而drivers下是所有注册在这个总线上的驱动,每个driver子目录下 是一些可以观察和修改的driver参数。常见的总线类型参考如下:
类(class) :
对于具有相同功能的设备,归结到一种类别,进行分类管理;/sys/class目录下则是包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型, 我们知道每种设备都具有自己特定的功能,比如:鼠标的功能是作为人机交互的输入,按照设备功能分类无论它 挂载在哪条总线上都是归类到/sys/class/input下。
3.总线-设备-驱动工作原理
总线上面有device和driver两个链表,向系统注册一个驱动时,驱动的管理链表会被插入一个新的驱动节点,同理,向系统注册一个设备时,会向设备的管理连链表插入一个新的设备。在插入的同时总线会执行一个bus_type结构体中match的方法对新插入的设备/驱动进行匹配。 在匹配成功的时候会调用驱动device_driver结构体中probe方法(通常在probe中获取设备资源,具体的功能可由驱动编写人员自定义), 并且在移除设备或驱动时,会调用device_driver结构体中remove方法。match、probe、remove等方法需需要驱动coder实现。
四."总线-设备-驱动" 的数据结构
4.1 bus_type(内核源码/include/linux/device.h)
在实际编写linux驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线, 内核中提供了bus_register函数来注册总线,以及bus_unregister函数来注销总线,其函数原型如下:
点击查看代码
struct bus_type {
const char *name; //总线名称
const char *dev_name; //
struct device *dev_root; //
struct device_attribute *dev_attrs; /* use dev_groups instead */ //
const struct attribute_group **bus_groups; //总线的属性
const struct attribute_group **dev_groups; //设备属性
const struct attribute_group **drv_groups; //驱动属性
int (*match)(struct device *dev, struct device_driver *drv); //当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该回调函数主要负责判断是否有注册了的驱动适合新的设备,或者新的驱动能否驱动总线上已注册但没有驱动匹配的设备;
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); //总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策。
int (*probe)(struct device *dev); //当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数。
int (*remove)(struct device *dev); //当设备从总线移除时,调用该回调函数;
void (*shutdown)(struct device *dev); //
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state); //电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;
int (*resume)(struct device *dev); //电源管理的相关函数,当唤醒总线时,调用resume的回调函数;
const struct dev_pm_ops *pm; //电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与device_driver结构体中的pm_ops有关;
const struct iommu_ops *iommu_ops;
struct subsys_private *p; //该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动;
struct lock_class_key lock_key;
};
4.2device结构体(内核源码/include/linux/device.h)
点击查看代码
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name;
const struct device_type *type;
struct mutex mutex;
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
4.3 device_driver结构体(内核源码/include/linux/device.h)
点击查看代码
struct device_driver {
const char *name; //指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
struct bus_type *bus; //表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
struct module *owner; //表示该驱动的拥有者,一般设置为THIS_MODULE;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table; //指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较。
const struct acpi_device_id *acpi_match_table;//
int (*probe) (struct device *dev); //当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的。
int (*remove) (struct device *dev); //当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
void (*shutdown) (struct device *dev); //
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; //指定该驱动的属性;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
4.4数据结构关系图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通