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;
};
当我们成功注册总线时,会在/sys/bus/目录下创建一个新目录,目录名为我们新注册的总线名。bus目录中包含了当前系统中已经注册了的所有总线,例如i2c,spi,platform等。我们看到每个总线目录都拥有两个子目录devices和drivers, 分别记录着挂载在该总线的所有设备以及驱动。

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数据结构关系图

posted @   Charles_hui  阅读(185)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示