linux驱动相关知识整理

由于工作的需要,需要去看emmc驱动的代码,根据我的学习流程,先总结一下对驱动架构的了解。


一、构造和运行模块

1.1、模块的加载和卸载
Linux有许多功能是通过模块的方式,在需要时才载入kernel,如此可使kernel较为精简,进而提高效率,以及保有较大的弹性。这类可载入的模块,通常是设备驱动程序。
insmod:加载模块,需要指定完整的路径和模块名字
modprobe:加载有依赖的模块,根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。
rmmod:卸载模块。

1.2、模块初始化和清除
module_init(init_function):模块被加载时被调用的函数。
module_exit(cleanup_function):模块卸载时被调用的函数。

当使用insmod加载模块时,module_init的init_function函数会被调用;
当使用module_exit加载模块时,module_init的cleanup_function函数会被调用;

二、总线
总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,以总线来管理设备和驱动函数。

2.1、总线结构:

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);
	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);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

介绍几个比较关键的成员变量:
name:总线的名字,总线注册后,该名字会出现在"/sys/bus/"目录下;

函数:http://blog.csdn.net/cppgp/article/details/6333359
match:判定设备和驱动是否匹配(根据设备和驱动结构体中的总线名是否一致进行判断),是总线体系相关的;
uevent:在发送热插拔事件消息到用户空间之前添加环境变量。
probe:当match成功后,设备通过此函数来执行设备相关的匹配探测、设备初始化、资源分配等。(http://blog.csdn.net/zirconsdu/article/details/8792184)
remove:移除设备;

2.1、总线注册和注销

int bus_register(struct bus_type *bus);

注册总线,总线注册后,在/sys/bus目录下会显示注册的总线的名称,就是bus_type中的name;

void bus_unregister(struct bus_type *bus)

注销总线,清除在/sys/bus下对应的目录。(如果已经在该总线上注册了设备或者驱动,需要先注销设备和驱动)

三、块设备

3.1、设备结构体

struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	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 */
	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;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */

	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 acpi_dev_node	acpi_node; /* associated ACPI 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;
};

这么长一个结构体,要搞清楚每个字段的意思也太难了,当前我们只关注两个字段:

const char *init_name; /* initial name of the device */// 设备的名称

struct bus_type *bus;/* type of bus device is on */// 设备的总线

3.2、块设备的注册和注销

int register_blkdev(unsigned int major, const char *name)
该函数用来向内核注册自己。

参数是该设备的主设备号及其名字(内核在/proc/devices中显示的名字)。如果传递的主设备号是0,内核将分派一个新的主设备号给设备,并将该设备号返回给调用者,如果返回一个负值,说明发生了错误。在linux设备驱动程序一书中指出:在内核2.6中,对register_blkdev的调用时完全可选的,该函数所执行的功能随时间的推移而越来越少。 事实上,应该比较核心的向内核注册设备的工作应该是放在add_disk函数中实现,在第3.3节中描述该函数。

register_blkdev对应的注销块设备的驱动程序的函数是:

void unregister_blkdev(unsigned int major, const char *name)
这里,参数必须传递与传递给register_blkdev的参数相匹配,否则函数返回-EINVAL,且不做任何注销工作。

3.3、注册磁盘

void add_disk(struct gendisk *disk)
一旦调用了add_disk,磁盘设备将被“激活”,并随时会调用 它提供的方法。实际上第一次对这些方法的调用可能在add_disk返回前就发生了,这是因为,内核可能会读取前面几个块的数据以获得分区表。因此在驱动程序完全被初始化并且能够影响应对磁盘的请求前,请不要调用add_disk.

那么它提供了哪些方法呢?此时我们看一下gendisk是个什么东西。

struct gendisk {
	/* major, first_minor and minors are input parameters only,
	 * don't use directly.  Use disk_devt() and disk_max_parts().
	 */
	int major;			/* major number of driver */
	int first_minor;
	int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

	char disk_name[DISK_NAME_LEN];	/* name of major driver */
	char *(*devnode)(struct gendisk *gd, umode_t *mode);

	unsigned int events;		/* supported events */
	unsigned int async_events;	/* async events, subset of all */

	/* Array of pointers to partitions indexed by partno.
	 * Protected with matching bdev lock but stat and other
	 * non-critical accesses use RCU.  Always access through
	 * helpers.
	 */
	struct disk_part_tbl __rcu *part_tbl;
	struct hd_struct part0;

	const struct block_device_operations *fops;
	struct request_queue *queue;
	void *private_data;

	int flags;
	struct device *driverfs_dev;  // FIXME: remove
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct blk_integrity *integrity;
#endif
	int node_id;
};

说实话,这个结构体太重要,也很复杂,内核使用它来表示一个独立的磁盘设备,实际上,内核还使用它表示分区,驱动程序必须对它进行初始化。

它的前面几个成员变量的作用,通过注释大概明白它的意思,这里,我们主要关注一下以下几个:

3.3.1、block_device_operations

const struct block_device_operations *fops;
该结构体源码如下:

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	void (*release) (struct gendisk *, fmode_t);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*direct_access) (struct block_device *, sector_t,
						void **, unsigned long *);
	unsigned int (*check_events) (struct gendisk *disk,
				      unsigned int clearing);
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
	int (*media_changed) (struct gendisk *);
	void (*unlock_native_capacity) (struct gendisk *);
	int (*revalidate_disk) (struct gendisk *);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);
	struct module *owner;
};
该结构体用来告诉系统块设备提供的操作接口,而字符设备对应的结构体为file_operations,有兴趣的自己理解。

这里简单介绍一下个函数的作用:

int (*open) (struct block_device *, fmode_t);
void (*release) (struct gendisk *, fmode_t);

当设备被打开或者关闭时调用它们。一个块设备驱动程序可能用旋转盘片、锁住仓门等来响应open调用。

如果用户将介质放入设备中锁住,那么在release函数中当然要进行解锁。

int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
实现ioctl系统调用的函数。块设备层会首先截取大量的标准请求,因此大多数设备的ioctl函数都十分短小。
int (*media_changed) (struct gendisk *);
判断驱动器内的介质(可移动介质)是否更换,如果更换返回一个非0值。

int (*revalidate_disk) (struct gendisk *);
当介质被更换时,调用该函数做出响应;它会告诉驱动程序完成必要的工作,以便使用新的介质。返回值被内核忽略。

struct module *owner;
一个指向拥有该结构的模块指针,通常它都被初始化为THIS_MODULE.


这里我们是不是有疑问,到底是哪个函数在负责读和写数据的功能呢?

再回到gendisk结构体,并关注以下成员变量

3.3.2、request_queue

struct request_queue *queue;
内核使用该结构为设备管理I/O请求;
关于块设备请求对列:包含块设备I/O请求的序列,保存了描述设备谁能处理的请求的参数:最大尺寸、在同一个请求中所能包含的独立段的数目、硬件扇区的大小、对齐需求等。请求对列还实现了插件接口,使得多个I/O调度器(用来优化请求,使得请求性能最佳)成为可能。
在《Linux设备驱动程序》第三版的第十六章的请求处理这一节的首句话是这么写的:
“每个块设备驱动程序的核心是它的请求函数。实际的工作,至少如设备的启动,都是在这个函数里完成的。...”

那么请求处理函数和请求对列又是怎么联系到一起的呢?

3.3.3、blk_init_queue

struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
该函数用来申请一个消息对列。
参数request_fn_proc *rfn为函数指针,该函数用来处理消息对列中的消息;
参数spinlock_t *lock为一个自旋锁,该锁为队列创建过程的一部分。当调用rfn函数时,该锁是由内核控制的。
与该函数对应的函数为:void mmc_cleanup_queue(struct mmc_queue *mq)。销毁队列。

3.3.4、单个请求介绍

单个请求可以是:
a、向磁盘读出写入数据的请求;
b、请求生产商信息;
c、底层诊断操作;
d、特殊设备模式相关的指令:对可记录介质的写模式的设定等。

结构体如下:

struct request {
	union {
		struct list_head queuelist;
		struct llist_node ll_list;
	};
	union {
		struct call_single_data csd;
		struct work_struct mq_flush_data;
	};

	struct request_queue *q;
	struct blk_mq_ctx *mq_ctx;

	u64 cmd_flags;
	enum rq_cmd_type_bits cmd_type;
	unsigned long atomic_flags;

	int cpu;

	/* the following two fields are internal, NEVER access directly */
	unsigned int __data_len;	/* total data len */
	sector_t __sector;		/* sector cursor */

	struct bio *bio;
	struct bio *biotail;

	struct hlist_node hash;	/* merge hash */
	/*
	 * The rb_node is only used inside the io scheduler, requests
	 * are pruned when moved to the dispatch queue. So let the
	 * completion_data share space with the rb_node.
	 */
	union {
		struct rb_node rb_node;	/* sort/lookup */
		void *completion_data;
	};

	/*
	 * Three pointers are available for the IO schedulers, if they need
	 * more they have to dynamically allocate it.  Flush requests are
	 * never put on the IO scheduler. So let the flush fields share
	 * space with the elevator data.
	 */
	union {
		struct {
			struct io_cq		*icq;
			void			*priv[2];
		} elv;

		struct {
			unsigned int		seq;
			struct list_head	list;
			rq_end_io_fn		*saved_end_io;
		} flush;
	};

	struct gendisk *rq_disk;
	struct hd_struct *part;
	unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
	struct request_list *rl;		/* rl this rq is alloced from */
	unsigned long long start_time_ns;
	unsigned long long io_start_time_ns;    /* when passed to hardware */
#endif
	/* Number of scatter-gather DMA addr+len pairs after
	 * physical address coalescing is performed.
	 */
	unsigned short nr_phys_segments;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
	unsigned short nr_integrity_segments;
#endif

	unsigned short ioprio;

	void *special;		/* opaque pointer available for LLD use */
	char *buffer;		/* kaddr of the current segment if available */

	int tag;
	int errors;

	/*
	 * when request is used as a packet command carrier
	 */
	unsigned char __cmd[BLK_MAX_CDB];
	unsigned char *cmd;
	unsigned short cmd_len;

	unsigned int extra_len;	/* length of alignment and padding */
	unsigned int sense_len;
	unsigned int resid_len;	/* residual count */
	void *sense;

	unsigned long deadline;
	struct list_head timeout_list;
	unsigned int timeout;
	int retries;

	/*
	 * completion callback.
	 */
	rq_end_io_fn *end_io;
	void *end_io_data;

	/* for bidi */
	struct request *next_rq;
};

引出这个结构体的目的是为了重点说明该结构体里面有一个重要的变量:

struct bio *bio;

该结构体是linux内核中通用块层的一个核心数据结构,它描述了块设备的I/O操作。它联系了内存缓冲区与块设备。

详细介绍请参考: http://blog.chinaunix.net/uid-13245160-id-84372.html

四、驱动

在前面的块设备那一节中,后面讲的关于block_device_operations和请求对列已经输入驱动的范畴,在这里主要谈一下关于驱动的注册和注销以及驱动结构体。

4.1、驱动结构体

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	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;
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	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;
};

name:驱动的名字,如果要和设备匹配,该字段的值一般需要与设备结构体的init_name名称相匹配(其实,取决于总线的match函数)。

bus:注册到哪条总线上

其它请参考:http://www.cnblogs.com/armlinux/archive/2010/09/20/2396909.html

4.2、驱动的注册和注销

int driver_register(struct device_driver *drv)
void driver_unregister(struct device_driver *drv)

五、总线、设备、驱动的关联

前面描述了总线、设备和驱动的一些基本的知识,现在我们来理清一下三者之间的关系。
5.1、首先注册总线
通过第二节我们知道,总线注册后,会在/sys/bus下生成一个总线名称对应的文件夹,下面会有设备、驱动的空文件夹。
5.2、注册驱动或者注册设备
注册设备和注册驱动没有严格的先后次序,无论哪一方注册时,总线总会去调用match函数(参考2.1),检测是否有与之对应的驱动或者设备,如果匹配成功,内核会调用驱动结构体中定义的probe函数(参考4.1)来查询设备能否被该驱动操作,如果可以,驱动就会对该设备进行相应的操作,如初始化。
所以说,真正的驱动函数入口是在probe函数中。
5.3、驱动卸载

当该驱动或者驱动函数正在操作的设备被移除时,内核会调用驱动函数中的remove函数调用,进行一些设备卸载相应的操作。

该函数定义(参考4.1)。

关于match和probe的执行流程请参考:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28758680&id=4561036


更多资料请参考:http://blog.chinaunix.net/uid-25014876-id-110295.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为一个Linux驱动工程师,面试题的整理非常重要,以下是一些常见的Linux驱动工程师面试题整理: 1. 请介绍一下Linux设备驱动的基本原理和工作过程。 答:Linux设备驱动的基本原理是通过访问设备文件接口来与硬件设备进行通信。驱动程序负责管理设备的硬件资源,将硬件抽象为逻辑设备,并提供设备文件接口让用户层程序与设备交互。 2.请简述Linux设备驱动的加载过程。 答:Linux设备驱动的加载过程包括以下几个步骤:模块加载、设备注册、资源分配、中断注册、驱动绑定。 3.请问在Linux驱动开发中,如何实现中断处理? 答:中断处理可以通过注册中断处理函数来实现。首先通过request_irq()函数申请中断,并指定中断处理函数。当中断触发时,中断处理函数会被调用。 4.请简述Linux驱动程序的通信方式。 答:Linux驱动程序的通信方式有多种,常见的有: a. 基于文件接口的通信:通过打开设备文件,使用read(), write()等系统调用与设备进行数据读写 b. 基于ioctl()的通信:使用ioctl()系统调用向设备发送控制命令 c. 基于字符设备驱动的通信:通过字符设备驱动提供的read(), write()等驱动程序提供的接口进行通信 5.请问在Linux驱动开发中,如何进行调试? 答:在Linux驱动开发中,可以通过以下方式进行调试: a. 使用printk()函数打印调试信息到内核日志 b. 使用kdb或kgdb等工具进行内核调试 c. 使用程序调试器(如gdb)对驱动程序进行用户层调试 这只是一些常见的Linux驱动工程师面试题,希望可以帮助你更好地准备面试。对于每个问题,可以进一步深入学习和了解相关知识

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值