Linux下块驱动(总结)和源码解析

11 篇文章 2 订阅

一、简介

Linux三大驱动类型包括字符驱动、块驱动和网络驱动。
块设备是针对存储设备的,比如 SD卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等;
块驱动和字符驱动的区别如下:
1、字符设备是以字节为单位进行数据传输的,不需要缓冲;
2、块设备只能以块为单位进行读写访问,块是linux虚拟文件系统(VFS)基本的数据传输单位,块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,根据回写机制条件将缓冲区中的数据写入块设备中;
3、块设备驱动不可以通过节点直接访问,它需要通过文件系统挂载到目录下访问。当执行mount时则调用open,执行umount命令则调用release。

二、编写块驱动的总过程

1、注册块设备驱动;
2、分配并初始化gendisk;
3、分配、初始化请求队列,绑定请求队列和请求函数;
4、给gendisk的major、disk_name、fops、queue等成员赋值;
5、设置设备容量(单位为扇区);
6、将gendisk添加到内核用于系统调用。

在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。

由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。

Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。

在这里插入图片描述
编写块设备驱动时,使用的一些单位介绍:
1、扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512字节。(对设备而言)

2、块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)

3、段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。

三、具体函数源码详解

块驱动相对于字符设备驱动,块驱动中是没有read、write函数,而是通过申请request,然后用bio结构体描述具体储存信息,这些结构体都是gendisk结构体的成员。

request结构体:
在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交I/O请求,调度程序将磁盘资源分配给系统中所有挂起的块I/O请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。

bio结构体:
由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。

gendisk数据结构体:
Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。
3.1 block_device 结构体
内核使用 block_device 表示块设备,block_device 为一个结构体,定义在 include/linux/fs.h 文件中

struct block_device {
	dev_t bd_dev; 	/* not a kdev_t - it's a search key */
	int bd_openers;
	struct inode *bd_inode; 	/* will die */
	struct super_block *bd_super;
	struct mutex bd_mutex; 	/* open/close mutex */
	struct list_head bd_inodes;
	void * bd_claiming;
	void * bd_holder;
	int bd_holders;
	bool bd_write_holder;
#ifdef CONFIG_SYSFS
	struct list_head bd_holder_disks;
#endif
	struct block_device *bd_contains;
	unsigned bd_block_size;
	struct hd_struct *bd_part;
	/*number of times partitions within this device have been opened.*/
	unsigned bd_part_count;
	int bd_invalidated;
	/*  bd_disk成员变量为gendisk结构体指针类型,内核使用block_device来表示
	具体块设备对象(硬盘/分区),若是硬盘的话bd_disk就指向通用磁盘结构gendisk */
	struct gendisk *bd_disk;  
	struct request_queue *bd_queue;
	struct list_head bd_list;
	unsigned long bd_private;
	/* The counter of freeze processes */
	int bd_fsfreeze_count;
	/* Mutex for freeze */
	struct mutex bd_fsfreeze_mutex;
};

3.2 gendisk 结构体
linux 内核使用 gendisk 结构体来描述一个磁盘设备,定义在 include/linux/genhd.h 中

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;
};

3.3 block_device_operations结构体
块设备操作集,定义在 include/linux/blkdev.h 中

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);//打开
	void (*release) (struct gendisk *, fmode_t);//释放
	int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);//rw_page 函数用于读写指定的页
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);//ioctl 函数用于块设备的 I/O 控制
	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 *);//使介质有效	
	int (*getgeo)(struct block_device *, struct hd_geometry *);//getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息
	/* 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;//模块指针
};

3.4 注册块设备:向内核注册新的块设备、申请设备号

int register_blkdev(unsigned int major, const char *name)
//major:主设备号
//name:块设备名字
//返回值:若major在1~255之间,表示自定义主设备号,则返回0表示注册成功,返回负值表示注册失败
//若major为0,表示由系统自动分配主设备号,成功则返回分配的主设备号,返回负值表示注册失败

3.5 分配并初始化一个 gendisk

struct gendisk *alloc_disk(int minors)
//minors:次设备号数量,即 gendisk 对应的分区数量
//返回值:成功,返回申请到的 gendisk,失败,返回 NULL

3.6 设置gendisk容量

void set_capacity(struct gendisk *disk, sector_t size)
//disk:要设置容量的 gendisk
//size:磁盘容量大小,注意这里是扇区数量
//块设备中最小的可寻址单元是扇区,扇区一般是512字节
//因此该函数设置的大小就是实际容量除以521得到的扇区数量

3.7 将gendisk添加到内核

void add_disk(struct gendisk *disk)
//disk:要添加到内核的 gendisk

四、块驱动源码编写

1、注册块设备驱动;
2、分配并初始化gendisk;
3、分配、初始化请求队列,绑定请求队列和请求函数;
4、给gendisk的major、disk_name、fops、queue等成员赋值;
5、设置设备容量(单位为扇区);
6、将gendisk添加到内核用于系统调用。

#include <linux/module.h> 
#include <linux/blkdev.h> 
#include <linux/hdreg.h> 
#include <linux/version.h>
 

static int Tiny4412_block_major=0;
static struct request_queue *tiny4412_blkdev_queue; 
static struct gendisk *tiny4412_blkdev_disk;
 
static unsigned long long tiny4412_blkdev_bytes=1024*1024*10;//10M--空间容量
#define TINY4412_BLKDEV_BYTES_1        (1024*1024*10)  /*设置块设备的大小*/
static unsigned char tiny4412_blkdev_data_1[TINY4412_BLKDEV_BYTES_1]; /*用于测试块设备的数组大小*/
 
 
/*
* Handle an I/O request.
* 实现扇区的读写
unsigned long sector:  当前扇区位置
unsigned long nsect :  扇区读写数量
char *buffer        :  读写的缓冲区指针
int write           :  是读还是写
*/
static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write)
{
		/*块设备最小单位是一个扇区,一个扇区的字节数是512字节*/
		unsigned long offset = sector;  /*写入数据的位置*/
		unsigned long nbytes = nsect;   /*写入的长度*/
		if((offset + nbytes)>TINY4412_BLKDEV_BYTES_1)
		{
			printk("写超出范围,强制结束(%ld %ld)\n", offset, nbytes);
			return;
		}
		if(write) /*为真,表示是写*/
		memcpy(tiny4412_blkdev_data_1 + offset, buffer, nbytes);
		else      /*读操作*/
		memcpy(buffer,tiny4412_blkdev_data_1 + offset, nbytes);
}
 
 
/*“制造”请求函数*/
static int tiny4412_blkdev_make_request(struct request_queue *q, struct bio *bio) 
{ 
	int dir; 
	unsigned long long dsk_offset; 
	struct bio_vec *bvec; 
	int i; 
	void *iovec_mem;
	
	/*判断读写方向*/
	if(bio_data_dir(bio) == WRITE) dir = 1;
	else dir = 0;
	dsk_offset = bio->bi_sector << 9;
	bio_for_each_segment(bvec, bio, i) 
	{ 
		iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; 		
		
		Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir);//起始位置,长度,源数据,方向
		
		kunmap(bvec->bv_page);
		dsk_offset += bvec->bv_len; 
	}
	bio_endio(bio, 0); 
	return 0;
}
 
static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	/* 容量=heads*cylinders*sectors*512 
	 * 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数
	 */
	geo->heads     = 2;  /*磁头(一般一个盘面有两个磁头,正面一个/反面一个)*/
	geo->cylinders = 32; /*柱面(一般一个盘面上有32个柱面)每个盘片32个磁道)*/
	geo->sectors   = TINY4412_BLKDEV_BYTES_1/2/32/512; /*扇区,一般每个磁道上有12个扇区,这里需要根据前面柱面和磁头进行计算,不能乱填*/
	return 0;
}
 
struct block_device_operations tiny4412_blkdev_fops = 
{ 
    .owner= THIS_MODULE, 
	 /*fdisk命令分区时需要调用该函数,用于读取磁头、柱面、扇区等信息*/
	.getgeo	= tiny4412_blockdev_getgeo,
};
 
static int __init tiny4412_blkdev_init(void) 
{ 
	/*动态分配请求队列*/
	tiny4412_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
	
	/*tiny4412_blkdev_queue:需要绑定的请求队列,tiny4412_blkdev_make_request:需要绑定的“制造”请求函数*/
	blk_queue_make_request(tiny4412_blkdev_queue,tiny4412_blkdev_make_request);
	
	/*动态分配次设备号结构*/
	/*每一个磁盘(分区)都是使用一个gendisk结构保存*/
	tiny4412_blkdev_disk = alloc_disk(64); 
	
	/*磁盘名称赋值*/
	strcpy(tiny4412_blkdev_disk->disk_name, "tiny4412_blkdev"); 
 
	/*注册一个块设备,自动分配主设备号*/
	Tiny4412_block_major = register_blkdev(0,"Tiny4412_block");
	printk("Tiny4412_block_major=%d\n",Tiny4412_block_major);
	
	tiny4412_blkdev_disk->major=Tiny4412_block_major; 	  /*主设备号*/
	tiny4412_blkdev_disk->first_minor = 0; 				  /*次设备号*/
	tiny4412_blkdev_disk->fops = &tiny4412_blkdev_fops;   /*文件操作结合*/
	tiny4412_blkdev_disk->queue = tiny4412_blkdev_queue;  /*处理数据请求的队列*/
	
	/*设置磁盘结构 capacity 的容量*/
	/*注意: 块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。
	  cat /sys/block/xxxx/size 可以查看到设置的大小
	  把字节为单位的大小转换为以扇区为单位时,我们需要除以512,或者右移9位
	*/
	set_capacity(tiny4412_blkdev_disk,tiny4412_blkdev_bytes>>9); 	
	
	add_disk(tiny4412_blkdev_disk);//添加磁盘信息到内核
	return 0;
}
 
static void __exit tiny4412_blkdev_exit(void) 
{ 
	del_gendisk(tiny4412_blkdev_disk);//删除磁盘
	
	put_disk(tiny4412_blkdev_disk); //从内核减去gendisk
	
	blk_cleanup_queue(tiny4412_blkdev_queue);//清除队列

	unregister_blkdev(Tiny4412_block_major, "Tiny4412_block");/*注销块设备*/
	
}
 
module_init(tiny4412_blkdev_init); 
module_exit(tiny4412_blkdev_exit);
MODULE_LICENSE("GPL");

其他储存相关链接:

1、Flash闪存储存原理以及NAND flash、NOR flash总结

2、SSD硬盘SATA接口和M.2接口区别总结

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux常见驱动源码分析(kernel hacker修炼之道)--李万鹏 李万鹏 IBM Linux Technology Center kernel team 驱动资料清单内容如下: Linux设备模型(中)之上层容器.pdf Linux设备模型(上)之底层模型.pdf Linux驱动修炼之道-驱动中一些常见的宏.pdf Linux驱动修炼之道-内存映射.pdf Linux驱动修炼之道-看门狗框架源码分析.pdf Linux驱动修炼之道-触摸屏驱动之s3c2410_ts源码分析.pdf Linux驱动修炼之道-SPI驱动框架源码分析(中).pdf Linux驱动修炼之道-SPI驱动框架源码分析(下).pdf Linux驱动修炼之道-SPI驱动框架源码分析(上).pdf Linux驱动修炼之道-RTC子系统框架与源码分析.pdf Linux驱动修炼之道-platform.pdf Linux驱动修炼之道-LCD背光与gpio控制.pdf Linux驱动修炼之道-INPUT子系统(下).pdf Linux驱动修炼之道-INPUT子系统(上).pdf Linux驱动修炼之道-framebuffer(中).pdf Linux驱动修炼之道-framebuffer(下).pdf Linux驱动修炼之道-framebuffer(上).pdf Linux驱动修炼之道-DMA框架源码分析(下).pdf Linux驱动修炼之道-DMA框架源码分析(上).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(中).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(下).pdf Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(上).pdf Linux驱动修炼之道-clock框架.pdf Linux驱动修炼之道-ADC驱动.pdf Linux内核访问外设I O资源的方式.pdf LINUX内核USB子系统学习笔记之初识USB.pdf kernel hacker修炼之道之驱动-流水灯.pdf kernel hacker修炼之道之驱动-混杂设备.pdf kernel hacker修炼之道之驱动-按键.pdf kernel hacker修炼之道之PCI subsystem(五).pdf kernel hacker修炼之道之PCI subsystem(四).pdf kernel hacker修炼之道之PCI subsystem(三).pdf kernel hacker修炼之道之PCI subsystem(六).pdf kernel hacker修炼之道之PCI subsystem(二).pdf
linuxdriver_code_tool .....................\03 .....................\..\2.6内核升级工具 .....................\..\...............\device-mapper-1.00.19-2.i386.rpm .....................\..\...............\lvm2-2.00.25-1.01.i386.rpm .....................\..\...............\mkinitrd-4.2.0.3.tar.tar .....................\..\...............\module-init-tools-3.2.2.tar.bz2 .....................\..\...............\modutils-2.4.5-1.src.rpm .....................\04 .....................\..\内核模块参数范例 .....................\..\................\book.c .....................\..\内核模块导出符号 .....................\..\................\export_symb.c .....................\..\最简单的内核模块 .....................\..\................\hello.c .....................\05 .....................\..\udev源代码 .....................\..\..........\udev-114.tar.gz .....................\06 .....................\..\globalmem驱动 .....................\..\.............\globalmem.c .....................\..\linux内核container_of宏_Linux技术文章_Linux_操作系统.mht .....................\..\【转】container_of函数简介 - 嵌入式linux - 斯是陋室,惟吾德馨.htm .....................\..\【转】container_of函数简介 - 嵌入式linux - 斯是陋室,惟吾德馨_files .....................\..\..................................................................\bg_art_bottom.gif .....................\..\..................................................................\bg_art_left.gif .....................\..\..................................................................\bg_art_left_bottom.gif .....................\..\..................................................................\bg_art_left_top.gif .....................\..\..................................................................\bg_art_right.gif .....................\..\..................................................................\bg_art_right_bottom.gif .....................\..\..................................................................\bg_art_right_top.gif .....................\..\..................................................................\bg_art_top.gif .....................\..\..................................................................\bg_menu.gif .....................\..\..................................................................\comment.htm .....................\..\..................................................................\comment_files .....................\..\..................................................................\.............\base.css .....................\..\..................................................................\.............\index.css .....................\..\..................................................................\.............\num.png .....................\..\..................................................................\img_menu_left.gif .....................\..\..................................................................\index.css .....................\..\..................................................................\tophem1.gif .....................\..\..................................................................\userstar.gif .....................\..\包含2个globalmem设备的驱动 .....................\..\..........................\globalmem_two.c .....................\07 .....................\..\含并发控制的globalmem驱动 .....................\..\.........................\globalmem_lock.c .....................\08 .....................\..\globalfifo驱动 .....................\..\..............\globalfifo.c .....................\..\poll应用程序范例 .....................\..\................\pollmonitor.c .....................\09 .....................\..\异步通知应用程序范例 .....................\..\....................\asyncmonitor.c .....................\..\支持异步通知的globalfifo .....................\..\........................\globalfifo_async.c .....................\10 .....................\..\S3C2410实时钟驱动 .....................\..\.................\s3c2410-rtc.c .....................\..\秒设备驱动与应用程序 .....................\..\....................\second.c .....................\..\....................\second_test.c .....................\11 .....................\..\DMA范例 .....................\..\.......\3c505.c .....................\..\.......\3c505.h .....................\..\.......\dma.h .....................\..\静态映射范例 .....................\..\............\mach-smdk2440.c .....................\12 .....................\..\NVRAM驱动 .....................\..\.........\generic_nvram.c .....................\..\平台设备 .....................\..\........\devs.c .....................\..\看门狗驱动 .....................\..\..........\s3c2410_wdt.c .....................\..\触摸屏驱动 .....................\..\..........\作为input设备 .....................\..\..........\.............\s3c2410_ts.c .....................\..\..........\.............\s3c2410_ts.h .....................\..\..........\作为普通字符设备 .....................\..\..........\................\s3c2410-ts.c .....................\13 .....................\..\IDE驱动 .....................\..\.......\ide-disk.c .....................\..\.......\ide-h8300.c .....................\..\RAMDISK驱动 .....................\..\...........\rd.c .....................\14 .....................\..\S3C2410串口驱动 .....................\..\...............\regs-gpio.h .....................\..\...............\regs-serial.h .....................\..\...............\s3c2410.c .....................\..\串口核心层 .....................\..\..........\serial_core.c .....................\..\..........\serial_core.h .....................\15 .....................\..\S3C2410 I2C主机驱动
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值