1.块设备驱动块设备驱动是Linux用于存储和访问数据的重要设备类型与字符设备不同块设备以固定大小的数据块为单位进行交换块设备对数据请求有缓冲区因此可以调整响应请求的顺序。块设备驱动架构文件系统层将读写请求转化为bio结构体对象并调用submit_bio将请求提交到下一层submit中会将bio整合到request结构体中并形成request_queue队列并且使用调度算法对队列中的请求进行优化调度最后调用块设备驱动的make_request_fn处理请求,块设备驱动从队列中取出请求并通过硬件接口执行读写操作。映射层虚拟地址转换为逻辑块地址有以下几个过程虚拟地址首先经过MMU机制转换为物理地址然后查找对应的物理页是否存在于页缓存基数树中不存在就在基数树中新建一个页缓存在页缓存中会保存address_space的地址通过address_space从而找到文件本身inode将文件逻辑块转换为物理块号最后依据文件系统超级块所记录的扇区数将物理块号转化为设备LBA。将请求简单地进行合并后由IO调度器负责提交IO请求。有I/O调度的块设备:(I/O调度意思就是在应用层对块设备进行操作时候这个调度模块会把对块设备的操作转化为请求request然后放到请求队列中因此在处理时候主要对请求队列中的request进行操作)通过blk_init_queue初始化请求队列由调度器管理request再分发给驱动。没有I/O调度的设备EMMC以及SD卡当其使用时相当于直接对结构体bio进行操作此时的bio不在request结构体中了绕过传统的I/O调度器每个bio直接传递给驱动处理不再合并为request随机进行访问不需要进行控制。对于块设备的操作相当于存储设备操作有三个要清楚的地方1物理存储器的地址在哪2内存中的地址在哪 3存储长度是多少。由bio来控制其中bi_sector即物理设备比如EMMCbi_vec即内存。块设备驱动相关结构体block_device_operations结构体在块设备驱动中该结构体类似于字符设备驱动中file_operation结构体,定义了对块设备操作的集合。struct block_device_operations { int (*open) (struct block_device *, fmode_t); void (*release) (struct gendisk *, fmode_t); //rw_page 函数用于读写指定的页 int (*rw_page)(struct block_device *, sector_t, struct page *, int rw); //ioctl 函数用于块设备的 I/O 控制 int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); long (*direct_access)(struct block_device *, sector_t, void **, unsigned long *pfn, long size); 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 *); //getgeo 函数用于获取磁盘信息包括磁头、柱面和扇区等信息 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; };gendisk结构体:在内核中使用gendisk(通用磁盘)结构体来表示1个独立的磁盘设备或分区。struct gendisk { int major; /* 磁盘设备的主设备号 */ int first_minor; /* 磁盘的第一个次设备号 */ int minors; /* 磁盘的次设备号数量即磁盘的分区数量 */ 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 */ /* 磁盘对应的分区表 */ 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; };major、first_minor和minors共同表征了磁盘的主、次设备号同一个磁盘的各个分区共享1个主设备号而次设备号则不同。fops为block_device_operations即上节描述的块设备操作集合。queue是内核用来管理这个设备的 I/O请求队列的指针。capacity表明设备的容量以512个字节为单位。private_data可用于指向磁盘的任何私有数据用法与字符设备驱动file结构体的private_data类似。2.字符设备驱动字符设备驱动管理的核心对象是以字符为数据流的设备。在Linux内核中通过cdev数据结构进行了抽象。struct cdev { struct kobject kobj; // 内核对象用于设备模型层级管理 struct module *owner; // 所属模块通常为THIS_MODULE const struct file_operations *ops; // 设备操作函数集核心 struct list_head list; // 内核链表节点用于全局设备管理 dev_t dev; // 设备号主次设备号 unsigned int count; // 管理的设备数量连续次设备号个数 };kobj:用于Linux设备驱动模型。owner:字符设备驱动所在内核模块对象指针。ops:字符设备驱动中最关键的一个操作函数用于与应用程序进行交互。list:用来将字符设备串成一个链表。dev:字符设备的设备号由主设备号和次设备号组成。count:同属某个主设备号的此设备号的个数。设备驱动可以通过两种方式产生cdev数据结构:一种是使用全局静态变量另一种是通过内核提供的cdev_alloc()接口函数。cdev_init()函数:初始化cdev数据结构并且建立设备与驱动操作方法集file_operations之间的连接关系。cdev_add()函数:把一个字符设备添加到系统中通常在驱动的probe()函数中会调用该接口函数来注册字符设备。cdev_del()函数:从系统中删除cdev数据结构通常在设备的卸载函数里会调用该接口函数。设备节点Linux中一切皆文件如果应用程序想使用驱动提供的服务或操作设备那么需要通过访问设备文件来完成。设备文件使得用户程序操作硬件设备就像操作普通文件一样。主设备号代表一类设备次设备号代表同一类设备的不同个体每个次设备号都有一个不同的设备节点。系统中所有的设备节点都存放在/dev/目录中。dev是动态生成的、使用devtmpfs虚拟文件系统挂载的、基于RAM的虚拟文件系统。在Linux内核中使用cdev结构体来描述字符设备使用其成员来定义设备号通过主设备号次设备号的方式确定字符设备的唯一性。 通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数如read()、write()等。在字符驱动设备中模块加载函数通过 register_chrdev_region() 或 alloc_chrdev_region() 来静态或者动态获取设备号通过 cdev_init() 建立 cdev 与 file_operations 之间的连接通过 cdev_add() 向系统添加一个 cdev 以完成注册模块卸载函数通过 cdev_del() 来注销 cdev通过 unregister_chrdev_region() 来释放设备号文件私有数据每个硬件设备都有一些属性比如主设备号(dev_t)类(class)、设备(device)在编写驱动的时候可以将这些属性全部写成变量的形式但对于一个设备的所有属性信息最好将其做成一个结构体,编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中。在 open 函数里面设置好私有数据后在 write、 read、 close 函数中直接读取 private_data即可得到设备结构体。