Linux驱动_块设备驱动
块设备是Linux驱动三大设备之一。与字符设备有很大的区别。块设备是针对存储设备的,比如 SD 卡、 EMMC、 NAND Flash、 Nor Flash、 SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动。
一、块设备和字符设备的区别
摘自Linux驱动设备开发详解(宋宝华)
二、块设备驱动结构
1、block_device_operations
block_device_operations是和file_operations字符设备操作集类似的块设备操作集。它的具体定义如下:
-
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);
-
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 *);
-
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;
-
};
主要用到的的函数:
open:当块设备打开的时候会调用此函数
release:当块设备关闭的时候会调用此函数
getgeo:此函数用来根据驱动器的几何信息填充一个hd_geometry结构体,该结构体包含磁头、扇区、柱面等信息。
2、gendisk结构体
Linux内核使用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;
-
-
struct blk_integrity *integrity;
-
-
int node_id;
-
};
major:为磁盘设备的主设备号。
first_minor:为磁盘的第一个次设备号。
minors:为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一
样, 次设备号不同。
part_tbl:为磁盘对应的分区表,为结构体 disk_part_tbl 类型, disk_part_tbl 的核心是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息。
part0:disk->part_tbl->part[0] = &part0;
fops:块设备操作集,为 block_device_operations 结构体类型。
queue:磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求
3、request_queue、request、bio
request_queue直译为请求队列,定义在gendisk中,因此每个块设备都对应于有一个请求队列。而请求队列中包含很多request直译为请求队列项。请求队列项又由一个或者多个bio结构体组成。
上层应用程序对于块设备的读写会被构造成一个或多个 bio 结构, bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio 提交给 I/O 调度器, I/O 调度器会将这些 bio 构造成 request 结构,而一个物理存储设备对应一个request_queue,request_queue 里面顺序存放着一系列的 request。新产生的 bio 可能被合并到 request_queue 里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由 I/O 调度器来完成的。 request_queue、 request 和 bio 之间的关系如图:
bio结构体的定义如下:
-
struct bio {
-
struct bio *bi_next; /* request queue link */
-
struct block_device *bi_bdev;
-
unsigned long bi_flags; /* status, command, etc */
-
unsigned long bi_rw; /* bottom bits READ/WRITE,
-
* top bits priority
-
*/
-
-
struct bvec_iter bi_iter;
-
-
/* Number of segments in this BIO after
-
* physical address coalescing is performed.
-
*/
-
unsigned int bi_phys_segments;
-
-
/*
-
* To keep track of the max segment size, we account for the
-
* sizes of the first and last mergeable segments in this bio.
-
*/
-
unsigned int bi_seg_front_size;
-
unsigned int bi_seg_back_size;
-
-
atomic_t bi_remaining;
-
-
bio_end_io_t *bi_end_io;
-
-
void *bi_private;
-
-
/*
-
* Optional ioc and css associated with this bio. Put on bio
-
* release. Read comment on top of bio_associate_current().
-
*/
-
struct io_context *bi_ioc;
-
struct cgroup_subsys_state *bi_css;
-
-
union {
-
-
struct bio_integrity_payload *bi_integrity; /* data integrity */
-
-
};
-
-
unsigned short bi_vcnt; /* how many bio_vec's */
-
-
/*
-
* Everything starting with bi_max_vecs will be preserved by bio_reset()
-
*/
-
-
unsigned short bi_max_vecs; /* max bvl_vecs we can hold */
-
-
atomic_t bi_cnt; /* pin count */
-
-
struct bio_vec *bi_io_vec; /* the actual vec list */
-
-
struct bio_set *bi_pool;
-
-
/*
-
* We can inline a number of vecs at the end of the bio, to avoid
-
* double allocations for a small number of bio_vecs. This member
-
* MUST obviously be kept at the very end of the bio.
-
*/
-
struct bio_vec bi_inline_vecs[0];
-
};
主要的成员变量如下:
bvec_iter:描述了要操作的设备扇区等信息,其结构体定义如下:
-
struct bvec_iter {
-
sector_t bi_sector; /* I/O 请求的设备起始扇区(512 字节) */
-
unsigned int bi_size; /* 剩余的 I/O 数量 */
-
unsigned int bi_idx; /* blv_vec 中当前索引 */
-
unsigned int bi_bvec_done; /* 当前 bvec 中已经处理完成的字节数 */
-
};
bio_vec:page 指定了所在的物理页, offset 表示所处页的偏移地址, len 就是数据长度。
-
struct bio_vec {
-
struct page *bv_page; /* 页 */
-
unsigned int bv_len; /* 长度 */
-
unsigned int bv_offset; /* 偏移 */
-
};
其中 bi_iter 这个结构体成员变量就用于描述物理存储设备地址信息,比如要操作的扇
区地址。 bi_io_vec 指向 bio_vec 数组首地址, bio_vec 数组就是 RAM 信息,比如页地址、页偏
移以及长度。
三、块设备驱动编写函数
1、注册和注销块设备
int register_blkdev(unsigned int major, const char *name)
void unregister_blkdev(unsigned int major, const char *name)
major: 主设备号。
name: 块设备名字。
2、gendisk
struct gendisk *alloc_disk(int minors) // 注册gendisk
void del_gendisk(struct gendisk *gp) //注销gendisk
void add_disk(struct gendisk *disk) //向内核添加gendisk
void set_capacity(struct gendisk *disk, sector_t size) //设置gendisk容量
3、请求队列 request_queue
申请并初始化:
-
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
-
//初始化请求队列,并绑定rfn请求处理函数
rfn: 请求处理函数指针,每个 request_queue 都要有一个请求处理函数,请求处理函数
request_fn_proc 原型如下:
void (request_fn_proc) (struct request_queue *q)
请求处理函数需要驱动编写人员自行实现。
lock: 自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用
这个自旋锁。
删除请求队列:
void blk_cleanup_queue(struct request_queue *q)
分配请求队列并绑定制造请求函数:
blk_init_queue 函数完成了请求队列的申请已经请求处理函数的绑定,这个一般用于像机械
硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程。但是对于 EMMC、 SD 卡这样的
非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了。对于非机械设备可以先申请 request_queue,然后将申请到的 request_queue 与“制造请求”函数绑定在一起。
分配一个request_queue
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
将request_queue和制造请求函数绑定 、
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
mfn:制造请求函数,该函数需要自己编写,函数原型如下:
void (make_request_fn) (struct request_queue *q, struct bio *bio)
四、 使用blk_init_queue进行试验
本实验利用开发板上的RAM模拟一段内存,编写块设备驱动,代码如下:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/*定义磁盘大小*/
-
-
-
-
-
struct ramdisk_dev {
-
int major; /* major主设备号*/
-
unsigned char *ramdiskbuffer; /*ramdisk内存空间,用来模拟块设备*/
-
spinlock_t spinlock; /*spinlock自旋锁*/
-
struct gendisk *gendisk; /*gendisk*/
-
struct request_queue *queue; /*request_queue请求队列*/
-
};
-
-
struct ramdisk_dev ramdisk; /*定义一个ramdisk设备*/
-
-
-
/*块设备操作函数集合*/
-
int ramdisk_open(struct block_device *dev, fmode_t mode)
-
{
-
printk("ramdisk open\r\n");
-
return 0;
-
}
-
-
void ramdisk_release(struct gendisk *disk, fmode_t mode)
-
{
-
printk("ramdisk release\r\n");
-
}
-
-
-
int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
-
{
-
/* 这是相对于机械硬盘的概念 */
-
geo->heads = 2; /* 磁头 */
-
geo->cylinders = 32; /* 柱面 */
-
geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
-
return 0;
-
}
-
-
-
static struct block_device_operations ramdisk_fops =
-
{
-
.owner = THIS_MODULE,
-
.open = ramdisk_open,
-
.release = ramdisk_release,
-
.getgeo = ramdisk_getgeo,
-
};
-
-
/*
-
* @description : 处理传输过程
-
* @param-req : 请求
-
* @return : 无
-
*/
-
static void ramdisk_transfer(struct request *req)
-
{
-
unsigned long start = blk_rq_pos(req) << 9; /* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
-
unsigned long len = blk_rq_cur_bytes(req); /* 大小 */
-
-
/* bio中的数据缓冲区
-
* 读:从磁盘读取到的数据存放到buffer中
-
* 写:buffer保存这要写入磁盘的数据
-
*/
-
void *buffer = bio_data(req->bio);
-
-
if(rq_data_dir(req) == READ) /* 读数据 */
-
memcpy(buffer, ramdisk.ramdiskbuffer start, len);
-
else if(rq_data_dir(req) == WRITE) /* 写数据 */
-
memcpy(ramdisk.ramdiskbuffer start, buffer, len);
-
-
}
-
-
/*
-
* @description : 请求处理函数
-
* @param-q : 请求队列
-
* @return : 无
-
*/
-
void ramdisk_request_fn(struct request_queue *q)
-
{
-
int err = 0;
-
struct request *req;
-
-
/* 循环处理请求队列中的每个请求 */
-
req = blk_fetch_request(q);
-
while(req != NULL) {
-
-
/* 针对请求做具体的传输处理 */
-
ramdisk_transfer(req);
-
-
/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
-
* 循环处理完请求队列中的所有请求。
-
*/
-
if (!__blk_end_request_cur(req, err))
-
req = blk_fetch_request(q);
-
}
-
}
-
-
-
static int __init ramdisk_init(void)
-
{
-
int ret = 0;
-
printk("ramdisk init\r\n");
-
-
/*1、给准备模拟块设备的内存申请空间*/
-
ramdisk.ramdiskbuffer = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
-
if(ramdisk.ramdiskbuffer == NULL) { /*内存申请失败*/
-
ret = -EINVAL;
-
goto failed_ram;
-
}
-
-
/*2、初始化自旋锁*/
-
spin_lock_init(&ramdisk.spinlock);
-
-
/*3、注册块设备*/
-
ramdisk.major = register_blkdev(0, RAMDISK_NAME); /*自动分配设备号*/
-
if(ramdisk.major < 0) { /*块设备注册失败*/
-
goto failed_register;
-
}
-
-
/*4、分配初始化gendisk*/
-
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR); /*初始化3个分区*/
-
if(!ramdisk.gendisk) { /*初始化gendisk失败*/
-
ret = -EINVAL;
-
goto failed_gendisk;
-
}
-
-
/*5、分配并初始化请求队列*/
-
ramdisk.queue = blk_init_queue(&ramdisk_request_fn, &ramdisk.spinlock);
-
if(!ramdisk.queue) {
-
ret = -EINVAL;
-
goto failed_queue;
-
}
-
-
/*6、注册添加gendisk*/
-
ramdisk.gendisk->major = ramdisk.major; /*主设备号*/
-
ramdisk.gendisk->first_minor = 0; /*起始次设备号*/
-
ramdisk.gendisk->fops = &ramdisk_fops; /*块设备操作函数*/
-
ramdisk.gendisk->private_data = &ramdisk; /* 私有数据 */
-
ramdisk.gendisk->queue = ramdisk.queue; /* 请求队列 */
-
sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME);/* 名字 */
-
set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); /* 设备容量(单位为扇区)*/
-
-
add_disk(ramdisk.gendisk);
-
-
return 0;
-
failed_queue:
-
put_disk(ramdisk.gendisk);
-
failed_gendisk:
-
unregister_blkdev(ramdisk.major, RAMDISK_NAME);
-
failed_register:
-
kfree(ramdisk.ramdiskbuffer);
-
failed_ram:
-
return ret;
-
}
-
-
-
static void __exit ramdisk_exit(void)
-
{
-
printk("ramdisk exit\r\n");
-
-
}
-
-
module_init(ramdisk_init);
-
module_exit(ramdisk_exit);
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("ZYC");
-
实验验证:
可以看出, ramdisk 已经识别出来了,大小为 2MB,但是同时也提示/dev/ramdisk
没有分区表,因为我们还没有格式化/dev/ramdisk。
格式化/dev/ramdisk
mkfs.vfat /dev/ramdisk
格式化完成后如下图所示:
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhchbb
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24