Linux文件系统(三)

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即物理设备,比如EMMC,bi_vec即内存。

块设备驱动相关结构体

block_device_operations结构体

在块设备驱动中,该结构体类似于字符设备驱动中file_operation结构体,定义了对块设备操作的集合。

cpp 复制代码
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个独立的磁盘设备(或分区)。

cpp 复制代码
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数据结构进行了抽象。

cpp 复制代码
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即可得到设备结构体。

相关推荐
A_humble_scholar10 小时前
Linux(九) 进程管理完全指南:从入门到实战
linux·运维·chrome
江华森10 小时前
Linux 操作命令完全指南
linux·运维
fakerth11 小时前
【OpenHarmony】communication_ipc模块
操作系统·openharmony
rjszcb11 小时前
Linux,sensor调试笔记1,修改帧率,以及曝光上不去问题
linux
源图客11 小时前
【AI向量数据库】Weaviate介绍与部署
运维·docker·容器
用什么都重名11 小时前
Git分支合并与远程服务器同步实战:保留关键配置文件
运维·服务器·git
C++ 老炮儿的技术栈11 小时前
Ubuntu root账号自动登陆
linux·运维·服务器·c语言·c++·ubuntu·visual studio
2301_7807896611 小时前
零信任架构中,身份感知防火墙(IAFW)的部署要点与最佳实践
linux·运维·服务器·人工智能·tcp/ip·架构
2401_8685347811 小时前
2025下半年网络规划设计师真题(选择题、案例分析)
运维·服务器·网络
Urbano12 小时前
22 道工序、核心难点与自动化升级方案
运维·自动化