欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

块设备基础 >>Linux设备驱动程序

程序员文章站 2022-03-27 20:47:47
因为专注所以专业是很重要的事情,再还没有把事情做到极致的时候,不要在人前炫耀,那样就会恐惧就会紧张无助;不会逼自己就永远只能当别人的跟班;闪耀是经受痛苦的打磨而获得的,观者知道什么是灵魂的香气;文章目录[0x100] 概念与特征[0x110][0x200] 相关数据结构[0x300] 特性与接口[0x100] 概念与特征[0x110][0x200] 相关数据结构[0x300] 特......

因为专注所以专业是很重要的事情,再还没有把事情做到极致的时候,请不要在人前炫耀,那样就会恐惧就会紧张无助;
多么花哨的表演都是源于基础的执着,尽可能知其然必知其所以然;

[0x100] 概念与特征

  1. 数据形成固定大小的数据块,以块大小为单位进行随机范围的数据访问;
  2. Linux内核扇区大小是 512 Btye,因此扇区编号 [存储空间字节数/512] 的结果;
  3. 根据不同系统架构,数据块大小不同,通常 4096 字节 也就是4KBtye;

[0x110]块设备属性信息

  1. 描述块设备的属性信息,包括设备标识,分区标识;
  2. IO请求事件管理;
  3. 块设备管理回调函数;

[0x120] 请求队列

[0x122] 作用

  • 保存块设备的I/O请求的队列
  • 需要块设备处理的请求参数:独立段数目、硬件扇区大小、对齐方式 等等;
  • 最终期限I/O调度器:在确保效率的情况下,保证所有请求不会被超期执行;
  • 预计I/O调度器:阻止了短间隔内相同类型块设备请求操作;

[0x122] 内容

  1. 设备参数:厂商ID、产品型号、容量、可用大小、诊断信息等等
  2. 读写IO请求
  3. 控制命令:记录介质写模式设定、切换磁道、返回审计报告等等;

[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请求的执行顺序的组织管理信息
    块设备基础 >>Linux设备驱动程序
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