块设备基础 >>Linux设备驱动程序
程序员文章站
2022-03-27 20:47:47
因为专注所以专业是很重要的事情,再还没有把事情做到极致的时候,不要在人前炫耀,那样就会恐惧就会紧张无助;不会逼自己就永远只能当别人的跟班;闪耀是经受痛苦的打磨而获得的,观者知道什么是灵魂的香气;文章目录[0x100] 概念与特征[0x110][0x200] 相关数据结构[0x300] 特性与接口[0x100] 概念与特征[0x110][0x200] 相关数据结构[0x300] 特......
因为专注所以专业是很重要的事情,再还没有把事情做到极致的时候,请不要在人前炫耀,那样就会恐惧就会紧张无助;
多么花哨的表演都是源于基础的执着,尽可能知其然必知其所以然;
文章目录
[0x100] 概念与特征
- 数据形成固定大小的数据块,以块大小为单位进行随机范围的数据访问;
- Linux内核扇区大小是 512 Btye,因此扇区编号 [存储空间字节数/512] 的结果;
- 根据不同系统架构,数据块大小不同,通常 4096 字节 也就是4KBtye;
[0x110]块设备属性信息
- 描述块设备的属性信息,包括设备标识,分区标识;
- IO请求事件管理;
- 块设备管理回调函数;
[0x120] 请求队列
[0x122] 作用
- 保存块设备的I/O请求的队列
- 需要块设备处理的请求参数:独立段数目、硬件扇区大小、对齐方式 等等;
- 最终期限I/O调度器:在确保效率的情况下,保证所有请求不会被超期执行;
- 预计I/O调度器:阻止了短间隔内相同类型块设备请求操作;
[0x122] 内容
- 设备参数:厂商ID、产品型号、容量、可用大小、诊断信息等等
- 读写IO请求
- 控制命令:记录介质写模式设定、切换磁道、返回审计报告等等;
[0x200] 相关数据结构
[0x210] 设备号链表结构[struct blk_major_name]
#include <linux/fs.h>
static struct blk_major_name {
struct blk_major_name *next; //链表下一条指针
int major; //主设备号
char name[16]; //设备名称
} *major_names[BLKDEV_MAJOR_HASH_SIZE];
[0x220] 块设备管理
[0x221] 块设备操作回调函数集
#include <linux/blkdev.h>
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,void **, unsigned long *);
unsigned int (*check_events) (struct gendisk *disk,unsigned int clearing);
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;
};
[0x222] 分区属性信息结构
#include <linux/genhd.h>
struct gendisk {
/*【只写参数】驱动程序主设备号 通常使用disk_devt()*/
int major;
/*【只写参数】第一个可用次设备号*/
int first_minor;
/*【只写参数】次设备号数量=1,磁盘不能分区;US=16,即分15个区,通常使用disk_max_parts()处理*/
int minors;
/* 主驱动器的标识,显示/proc/partions和sysfs中 */
char disk_name[DISK_NAME_LEN];
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;
/*块设备I/O请求队列*/
struct request_queue *queue;
/*块设备驱动私有数据指针*/
void *private_data;
/*块设备的驱动器类型 GENHD_FL_* */
int flags;
struct device *driverfs_dev; // FIXME: remove
/*sysfs 对象*/
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;
};
[0x230] 块设备请求结构
[0x231] 请求项结构[struct request]
struct request {
struct list_head queuelist; /*链表维护连接多个request结构*/
struct call_single_data csd;
struct request_queue *q; /*请求队列的入口绑定*/
unsigned int cmd_flags;
enum rq_cmd_type_bits cmd_type;
unsigned long atomic_flags;
int cpu;
struct bio *bio; /*维护的地址映射状态信息对象*/
struct bio *biotail;
struct hlist_node hash; /*合并请求hash 列表结点*/
union {
struct rb_node rb_node; /* sort/lookup */
void *completion_data;
};
union {
struct {
struct io_cq *icq;
void *priv[2];
} elv;
struct {
unsigned int seq;
struct list_head list;
rq_end_io_fn *saved_end_io;
} flush;
};
struct gendisk *rq_disk;
struct hd_struct *part;
unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
unsigned long long start_time_ns;
unsigned long long io_start_time_ns; /* when passed to hardware */
#endif
unsigned short nr_phys_segments; /*合并的物理页占用段数量*/
#if defined(CONFIG_BLK_DEV_INTEGRITY)
unsigned short nr_integrity_segments;
#endif
unsigned short ioprio;
int ref_count;
void *special; /* opaque pointer available for LLD use */
char *buffer; /* kaddr of the current segment if available */
int tag;
int errors;
unsigned char __cmd[BLK_MAX_CDB];
unsigned char *cmd;
unsigned short cmd_len;
unsigned int extra_len; /* length of alignment and padding */
unsigned int sense_len;
unsigned int resid_len; /* residual count */
void *sense;
unsigned long deadline;
struct list_head timeout_list;
unsigned int timeout;
int retries;
rq_end_io_fn *end_io;
void *end_io_data;
struct request *next_rq;
};
[0x232] 请求链式拆分物理页结构[struct bio]
- 块设备IO请求的执行顺序的组织管理信息
struct bio {
sector_t bi_sector; /* 需要传递的第一个512K 扇区地址 */
struct bio *bi_next; /* 需要链接的下一个IO请求结构 */
struct block_device *bi_bdev;
unsigned long bi_flags; /* 状态 命令标识 */
unsigned long bi_rw; /*低位为读写位,高位为优先级*/
unsigned short bi_vcnt; /* how many bio_vec's */
unsigned short bi_idx; /* current index into bvl_vec */
unsigned int bi_phys_segments; /*物理段数量*/
unsigned int bi_size; /*剩余需要传输传输的字节大小 */
/*最大可以合并的段大小, bi_seg_front_size~bi_seg_back_size*/
unsigned int bi_seg_front_size;
unsigned int bi_seg_back_size;
unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
atomic_t bi_cnt; /* pin count */
struct bio_vec
{
struct page *bv_page;
unsigned int bv_len;
unsigned int bv_offset;
} *bi_io_vec; /*当前结构bio矢量:用于IO请求页化反转,在每页的bv_offset位置 传输bv_len个字节*/
struct bio_vec bi_inline_vecs[0];
bio_end_io_t *bi_end_io;
void *bi_private;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
bio_destructor_t *bi_destructor; /* destructor */
};
[0x300] 特性与接口
[0x310] 块设备初始化
[0x311]注册块设备驱动程序 [struct blk_major_name]
#include <linux/fs.h>
/*implement kernel-dir/block/genhd.c */
static inline int major_to_index(unsigned major)
{
return major % BLKDEV_MAJOR_HASH_SIZE;
}
/*原子的将数据存入内核块设备结构中*/
int register_blkdev(unsigned int major, const char *name)
{
struct blk_major_name **n, *p;
int index, ret = 0;
mutex_lock(&block_class_lock);
/* 如果major设置为0,从设备结构数组中查找一个空闲的序号槽位 */
if (major == 0) {
for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
if (major_names[index] == NULL)
break;
}
/*index 递减为0, 说明没有空闲的槽位,返回错误设备忙*/
if (index == 0) {
printk("register_blkdev: failed to get major for %s\n",name);
ret = -EBUSY;
goto out;
}
/*否则乖乖的分配一个闺女给人家*/
major = index;
ret = major;
}
/*在内核空间给设备号结构分配一个空间,防止返回后栈释放*/
p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
if (p == NULL) {
ret = -ENOMEM;
goto out;
}
/*填充一个设备号结构小样,准备插入*/
p->major = major;
strlcpy(p->name, name, sizeof(p->name));
p->next = NULL;
/*取模转换,这里为了处理不是自动分配的设备号,保证相同的主设备号,一定存在对应的槽位中。一致性*/
index = major_to_index(major);
/*冲突检测,如果存在主设备号已经使用,返回设备忙,防止竞争*/
for (n = &major_names[index]; *n; n = &(*n)->next) {
if ((*n)->major == major)
break;
}
/*不存在将尾插*/
if (!*n)
*n = p;
else
ret = -EBUSY;
if (ret < 0) {
printk("register_blkdev: cannot get major %d for %s\n",major, name);
kfree(p);
}
out:
mutex_unlock(&block_class_lock);
return ret;
}
args1 : 需要分配的主设备号,如0 则自动分配,如N0 则静态分配
args2 : 显示与 /proc/dev/ 中的设备名称
retval : 成功 major,失败 errno;
[0x312]注销块设备驱动程序
#include <linux/fs.h>
/*implement kernel-dir/block/genhd.c */
void unregister_blkdev(unsigned int major, const char *name)
{
struct blk_major_name **n;
/*防止意外释放*/
struct blk_major_name *p = NULL;
/*通过主设备号获取index*/
int index = major_to_index(major);
mutex_lock(&block_class_lock);
/*遍历链表结构 major 相等退出*/
for (n = &major_names[index]; *n; n = &(*n)->next)
if ((*n)->major == major)
break;
/*如果没有找到或者名字不等则发出警告*/
if (!*n || strcmp((*n)->name, name)) {
WARN_ON(1);
} else { /*剔除要删除的数据结构*/
p = *n;
*n = p->next;
}
mutex_unlock(&block_class_lock);
/*释放*/
kfree(p);
}
args1 : 需要注销的主设备号;
args2 : 需要注销的设备名称;【显示于 /proc/dev/ 中的设备名称】
retval : 成功 major,失败 errno;
[0x313] 块设备添加准备工作
struc sbull_dev
{
int size; /*扇区数量*/
u8 *data;
short users;
spinlock_t lock; /*自旋锁结构*/
short media_change; /*介质切换*/
struct request_queue *queue; /*请求IO队列*/
struct gendisk *gd; /*块设备属性结构*/
struct timer_list timer; /*定时器结构*/
};
/*1.分配设备结构空间*/
struc sbull_dev * blk_dev =(struc sbull_dev *) kzalloc(sizeof(struc sbull_dev),GFP_KERNEL);
/*2.初始化一个自旋锁*/
spin_lock_init(spinlock_t *lock);
/*3.初始化请求队列,通常 node_id =-1*/
struct request_queue *blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
[0x314] 分配磁盘属性存储空间[struct gendisk]
/*implement kernel-3.4.39/block/genhd.c*/
/*调用接口*/
struct gendisk *alloc_disk(int minors) { return alloc_disk_node(minors, -1); }
/*核心函数 kmalloc清空分配后,填充结构体,sysfs的注册 */
struct gendisk *alloc_disk_node(int minors, int node_id)
/*注销结构 */
void del_gendisk(struct gendisk *disk)
- 注册磁盘的次设备的数量[minor],设置后将不能修改了;
- 动态结构不能手动分配空间,内核要保持它的引用计数的;
- 填充其内容[major \ first_minor \ fops \ queue \ disk_name];
[0x315] 添加块设备到内核列表
- 调用前必须确定驱动程序注册成功,调用返回前内核会访问块设备的分区表;
- 此函数可用于注册拓展分区的块设备;
void add_disk(struct gendisk *disk)
{
struct backing_dev_info *bdi;
dev_t devt;
int retval;
WARN_ON(disk->minors && !(disk->major || disk->first_minor));
/*如果次设备号为0,需要将flag 修改为GENHD_FL_EXT_DEVT,alloc_disk()才能分配拓展分区号 */
WARN_ON(!disk->minors && !(disk->flags & GENHD_FL_EXT_DEVT));
disk->flags |= GENHD_FL_UP;
/*分配主磁盘设备号*/
retval = blk_alloc_devt(&disk->part0, &devt);
if (retval) {
WARN_ON(1);
return;
}
disk_to_dev(disk)->devt = devt;
disk->major = MAJOR(devt);
disk->first_minor = MINOR(devt);
disk_alloc_events(disk);
/* 注册BDI */
bdi = &disk->queue->backing_dev_info;
bdi_register_dev(bdi, disk_devt(disk));
/*注册块设备*/
blk_register_region(disk_devt(disk), disk->minors, NULL,exact_match, exact_lock, disk);
/*注册磁盘*/
register_disk(disk);
/*添加块设备队列,释放的时候有据可循*/
blk_register_queue(disk);
WARN_ON_ONCE(!blk_get_queue(disk->queue));
/*设置好的分区 链接到sysfs*/
retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,"bdi");
WARN_ON(retval);
disk_add_events(disk);
}
[0x320] 请求队列[struct request_queue]
[0x321] 初始化队列结构
#include <linux/blkdev.h>
/*implement kernel-dir/block/blk-core.c*/
typedef void (request_fn_proc) (struct request_queue *q);
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
return blk_init_queue_node(rfn, lock, -1);
}
EXPORT_SYMBOL(blk_init_queue);
/*核心函数 初始化请求队列 并返回对应的队列结构*/
struct request_queue *blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
struct request_queue *uninit_q, *q;
uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);
if (!uninit_q)
return NULL;
q = blk_init_allocated_queue(uninit_q, rfn, lock);
if (!q)
blk_cleanup_queue(uninit_q);
return q;
}
Func :分配请求队列结构空间,并清空请求队列
args1:请求队列处理函数指针;
args2:块设备维护的自旋锁结构,说明这东西是原子操作;
args3:操作的节点ID 通常设置为 -1 就是所有的节点;
retval:成功返回 请求队列结构,失败返回 NULL;
[0x322] 队列管理接口
/*启动请求队列进程*/
void blk_start_request(struct request *req)
/*查看队列头部入口,并返回可用队列*/
struct request *blk_peek_request(struct request_queue *q)
/*将请求移出请求队列*/
void blk_dequeue_request(struct request *rq)
/*不可重试的请求*/
int blk_noretry_request(struct request *rq)
/*将请求重新放回请求队列*/
void blk_requeue_request(struct request_queue *q, struct request *rq)
/*停止请求队列进程*/
void blk_stop_request(struct request *req)
[0x330] 请求队列内存映射偏移
[0x331] 访问bio矢量参数
#define bio_page(bio) bio_iovec((bio))->bv_page
#define bio_offset(bio) bio_iovec((bio))->bv_offset
#define bio_segments(bio) ((bio)->bi_vcnt - (bio)->bi_idx)
#define bio_sectors(bio) ((bio)->bi_size >> 9)
[0x332] 虚拟地址映射
/*返回指向被传输的bio 的内核逻辑地址上数据,但是不应该使用在高端内存页上*/
static inline void *bio_data(struct bio *bio)
{
if (bio->bi_vcnt)
return page_address(bio_page(bio)) + bio_offset(bio);
return NULL;
}
static inline char *__bio_kmap_irq(struct bio *bio, unsigned short idx,unsigned long *flags)
{
return bvec_kmap_irq(bio_iovec_idx(bio, idx), flags);
}
#ifdef CONFIG_HIGHMEM
static inline char *bvec_kmap_irq(struct bio_vec *bvec, unsigned long *flags)
{
unsigned long addr;
/*
* might not be a highmem page, but the preempt/irq count
* balancing is a lot nicer this way
*/
local_irq_save(*flags);
addr = (unsigned long) kmap_atomic(bvec->bv_page);
BUG_ON(addr & ~PAGE_MASK);
return (char *) addr + bvec->bv_offset;
}
static inline void bvec_kunmap_irq(char *buffer, unsigned long *flags)
{
unsigned long ptr = (unsigned long) buffer & PAGE_MASK;
kunmap_atomic((void *) ptr);
local_irq_restore(*flags);
}
#else
static inline char *bvec_kmap_irq(struct bio_vec *bvec, unsigned long *flags)
{
return page_address(bvec->bv_page) + bvec->bv_offset;
}
static inline void bvec_kunmap_irq(char *buffer, unsigned long *flags)
{
*flags = 0;
}
#endif
#define __bio_kunmap_irq(buf, flags) bvec_kunmap_irq(buf, flags)
/*bio映射虚拟内存缓冲区与解除虚拟地址映射*/
#define bio_kmap_irq(bio, flags) __bio_kmap_irq((bio), (bio)->bi_idx, (flags))
#define bio_kunmap_irq(buf,flags) __bio_kunmap_irq(buf, flags)
有些地方看不懂了,还得总结一下,感觉都跟内存管理那块有连接!
回头在给自己打针鸡血,把内存管理那块总结下,等那块总结完再来更新吧!敲难受的QAQ
自己的时间不多了!
本文地址:https://blog.csdn.net/god1992/article/details/85909293