Linux下块驱动(总结)和源码解析
一、简介Linux三大驱动类型包括字符驱动、块驱动和网络驱动。块设备是针对存储设备的,比如 SD卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等;块驱动和字符驱动的区别如下:1、字符设备是以字节为单位进行数据传输的,不需要缓冲;2、块设备只能以块为单位进行读写访问,块是linux虚拟文件系统(VFS)基本的数据传输单位,块设备在结构上是可以进行随机访
一、简介
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");
其他储存相关链接:
更多推荐
所有评论(0)