天天看點

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

block子系統初始化

genhd_device_init為Linux核心中塊裝置驅動程式的整體架構進行了必要的初始化。

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構
static int __init genhd_device_init(void)
{
    int error;
    // 為block_class指定其dev_kobj
    // sysfs_dev_block_kobj是一個指向kobject的指針,devices_init中初始化
    block_class.dev_kobj = sysfs_dev_block_kobj;
    error = class_register(&block_class);
    if (unlikely(error))
        return error;
    // 初始化devmap,過程類似chardev_init
    // 用來把新的block裝置添加到這個hash表中
    bdev_map = kobj_map_init(base_probe, &block_class_lock);
    
    blk_dev_init();
    register_blkdev(BLOCK_EXT_MAJOR, "blkext");
    #ifndef CONFIG_SYSFS_DEPRECATED
    block_depr = kobject_create_and_add("block", NULL);
    #endif
    return 0;
}      

sysfs_dev_block_kobj的初始化

/dev/block

/dev/char

int __init devices_init(void)
{
    devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    if (!devices_kset)
        return -ENOMEM;
        
    dev_kobj = kobject_create_and_add("dev", NULL);
    if (!dev_kobj)
        goto dev_kobj_err;
        
    sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    if (!sysfs_dev_block_kobj)
        goto block_kobj_err;
        
    sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
    if (!sysfs_dev_char_kobj)
        goto char_kobj_err;
    return 0;
}      

blk_dev_init初始化(kblockd)

int __init blk_dev_init(void)
{
    BUILD_BUG_ON(__REQ_NR_BITS > 8 *
                    sizeof(((struct request *)0)->cmd_flags));
    // 建立block子系統的工作隊列                    
    kblockd_workqueue = create_workqueue("kblockd");
    if (!kblockd_workqueue)
        panic("Failed to create kblockd\n");
        
    // 建立request的緩沖
    request_cachep = kmem_cache_create("blkdev_requests",
                    sizeof(struct request), 0, SLAB_PANIC, NULL);
    // 建立request_queue的緩沖                    
    blk_requestq_cachep = kmem_cache_create("blkdev_queue",
                          sizeof(struct request_queue), 0, SLAB_PANIC, NULL);
                          
    return 0;
}      

一個block驅動執行個體 (ramdisk)

#include <linux/module.h>     //支援動态添加和解除安裝子產品  
#include <linux/kernel.h>    //驅動要寫入核心,與核心相關的頭檔案  
#include <linux/init.h>      //初始化頭檔案  
  
#include <linux/fs.h>        //包含了檔案操作相關struct的定義  
#include <linux/types.h>     //對一些特殊類型的定義  
#include <linux/fcntl.h>     //定義了檔案操作等所用到的相關宏  
#include <linux/vmalloc.h>  //vmalloc()配置設定的記憶體虛拟位址上連續,實體位址不連續  
#include <linux/blkdev.h>  //采用request方式 塊裝置驅動程式需要調用blk_init_queue 配置設定請求隊列  
#include <linux/hdreg.h>   //硬碟參數頭檔案,定義通路硬碟寄存器端口、狀态碼和分區表等資訊。  
  
#define RAMHD_NAME              "ramdisk"    //裝置名稱  
#define RAMHD_MAX_DEVICE        2           //最大裝置數  
#define RAMHD_MAX_PARTITIONS    4           //最大分區數  
  
#define RAMHD_SECTOR_SIZE       512        //扇區大小  
#define RAMHD_SECTORS           16         //扇區數  http://www.embedu.org/Column/Column863.htm  
#define RAMHD_HEADS             4         //磁頭數  
#define RAMHD_CYLINDERS         256      //磁道(柱面)數   
  
#define RAMHD_SECTOR_TOTAL      (RAMHD_SECTORS * RAMHD_HEADS * RAMHD_CYLINDERS)  //總大小  
#define RAMHD_SIZE              (RAMHD_SECTOR_SIZE * RAMHD_SECTOR_TOTAL) //8MB  
  
typedef struct{  
    unsigned char   *data;             //裝置資料空間首位址  
    struct request_queue *queue;       //裝置請求隊列  
    spinlock_t      lock;             //互斥自旋鎖  
    struct gendisk  *gd;              //通用磁盤結構體  
} RAMHD_DEV;  
  
static char *sdisk[RAMHD_MAX_DEVICE];  //配置設定記憶體的首位址  
static RAMHD_DEV *rdev[RAMHD_MAX_DEVICE];  //配置設定記憶體的首位址  
  
static dev_t ramhd_major;   //主裝置号  
  
static int ramhd_space_init(void)  
{  
    int i;  
    int err = 0;  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){  
        sdisk[i] = vmalloc(RAMHD_SIZE);  //申請RAMBLK_SIZE記憶體  實體位址不連續,虛拟位址連續  
        if(!sdisk[i]){  
            err = -ENOMEM;  //errno:12 記憶體不足  
            return err;  
        }  
        memset(sdisk[i], 0, RAMHD_SIZE);  //用0來初始化配置設定的記憶體空間  
    }  
  
    return err;  
}  
  
static void ramhd_space_clean(void)  
{  
    int i;  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){  
        vfree(sdisk[i]);                   //釋放配置設定的記憶體  
    }  
}  
  
static int alloc_ramdev(void)  
{  
    int i;  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){  
        rdev[i] = kzalloc(sizeof(RAMHD_DEV), GFP_KERNEL); //向核心申請存放RAMHD_DEV結構體的記憶體空間  
        if(!rdev[i])  
            return -ENOMEM;   //errno:12  記憶體不足  
    }  
    return 0;  
}  
  
static void clean_ramdev(void)  
{  
    int i;  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){  
        if(rdev[i])  
            kfree(rdev[i]);   //釋放配置設定的記憶體  
    }     
}         
  
int ramhd_open(struct block_device *bdev, fmode_t mode)   //裝置打開用到  
{     
    return 0;  
}  
  
void ramhd_release(struct gendisk *gd, fmode_t mode)   //裝置關閉用到  
{     
      
}  
  
static int ramhd_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) //IO控制  
{  
    int err;  
    struct hd_geometry geo;  //hd_geometry結構體包含磁頭,扇區,柱面等資訊  
  
    switch(cmd)  
    {  
        case HDIO_GETGEO:  //擷取塊裝置的實體參數   
            err = !access_ok(VERIFY_WRITE, arg, sizeof(geo));//檢查指針所指向的存儲塊是否可寫  
            if(err) return -EFAULT;     //errno:14   位址錯   
          
            geo.cylinders = RAMHD_CYLINDERS;     //柱面數  
            geo.heads = RAMHD_HEADS;            //磁頭數  
            geo.sectors = RAMHD_SECTORS;        //扇區數  
            geo.start = get_start_sect(bdev);   //起始位址  
            if(copy_to_user((void *)arg, &geo, sizeof(geo)))  
             //把核心位址&geo訓示的資料複制到arg指代的使用者空間的位址上  
                return -EFAULT;   //errno:14   位址錯   
            return 0;  
    }  
              
    return -ENOTTY;      //errno:25     不适當的IO控制操作   
}  
  
static struct block_device_operations ramhd_fops =  //用來描述一個塊裝置的操作函數集  
{   .owner = THIS_MODULE,//“加點”這種方式稱為指定初始化  源自ISO C99标準 初始化不必嚴格按照定義時的順序  
    .open = ramhd_open,  
    .release = ramhd_release,  
    .ioctl = ramhd_ioctl,  
};  
  
void ramhd_req_func (struct request_queue *q)  //處理傳遞給這個裝置的請求  
{  
    struct request *req;  //用來提取req  
    RAMHD_DEV *pdev;  
    char *pData;  
    unsigned long addr, size, start;  
    req = blk_fetch_request(q); //從塊裝置隊列提取存儲的req;  
        //blk_fetch_request()可以多次調用,如果queue裡面沒有内容,req将傳回NULL  
    while (req) {   //判斷目前request是否合法  循環從請求隊列中擷取下一個要處理的請求  
        start = blk_rq_pos(req); // 擷取目前request結構的起始扇區   
        pdev = (RAMHD_DEV *)req->rq_disk->private_data; //獲得裝置結構體指針  
        pData = pdev->data;//裝置位址  
        addr = (unsigned long)pData + start * RAMHD_SECTOR_SIZE;//計算位址  
        size = blk_rq_cur_bytes(req); //通路 req 的下一段資料  
        if (rq_data_dir(req) == READ) //獲得資料傳送方向.傳回0表示從裝置讀取,否則表示寫向裝置.  
            memcpy(req->buffer, (char *)addr, size); //讀  
        else  
            memcpy((char *)addr, req->buffer, size); //寫  
      
        if(!__blk_end_request_cur(req, 0))  //這個函數處理完傳回false  
            req = blk_fetch_request(q);  //繼續取出請求隊列中的請求  
    }  
}  
  
int ramhd_init(void)   //初始化  
{  
    int i;  
    ramhd_space_init();  
    alloc_ramdev();  
      
    ramhd_major = register_blkdev(0, RAMHD_NAME); //塊裝置驅動注冊到核心中  
    //major為0,核心會自動配置設定一個新的主裝置号(ramhd_major )    
    for(i = 0; i < RAMHD_MAX_DEVICE; i++)  
    {  
        rdev[i]->data = sdisk[i];   
        rdev[i]->gd = alloc_disk(RAMHD_MAX_PARTITIONS);  
        spin_lock_init(&rdev[i]->lock);  //初始化自旋鎖  
        rdev[i]->queue = blk_init_queue(ramhd_req_func, &rdev[i]->lock);//初始化将ramhd_req_func函數與隊列綁定  
        rdev[i]->gd->major = ramhd_major;   
        rdev[i]->gd->first_minor = i * RAMHD_MAX_PARTITIONS;  
        rdev[i]->gd->fops = &ramhd_fops;  //關聯到這個裝置的方法集合  
        rdev[i]->gd->queue = rdev[i]->queue;  
        rdev[i]->gd->private_data = rdev[i]; //使用這個成員來指向配置設定的資料  
        sprintf(rdev[i]->gd->disk_name, "ram_MaoRi_%c", 'a'+i);  
        set_capacity(rdev[i]->gd, RAMHD_SECTOR_TOTAL);  
        add_disk(rdev[i]->gd);   //向系統中添加這個塊裝置  
    }  
          
    return 0;  
}  
  
void ramhd_exit(void)  //子產品解除安裝函數  
{  
    int i;  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++)  
    {  
        del_gendisk(rdev[i]->gd); //删除gendisk結構體     
        put_disk(rdev[i]->gd);   //減少gendisk結構體的引用計數  
        blk_cleanup_queue(rdev[i]->queue);  //清除請求對列  
    }  
    unregister_blkdev(ramhd_major,RAMHD_NAME); ; //登出塊裝置   
    clean_ramdev();  
    ramhd_space_clean();    
}  
  
module_init(ramhd_init);  
module_exit(ramhd_exit);  
  
MODULE_AUTHOR("MaoRi");  
MODULE_DESCRIPTION("The Ramdisk implementation with request function");  
MODULE_LICENSE("GPL");      

Makefile檔案如下:

ifneq ($(KERNELRELEASE),)
        obj-m += ramdisk.o
else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif      

編譯:

make -C /root/alicpp/built/kernel/sdks/3.10.0-327.ali2000.alios7.x86_64  M=$PWD
insmode
dmesg      

塊裝置的注冊與管理

這個函數很簡單:根據name在major_names查找是否有重複,并傳回第一個槽位做為主裝置号。

雖然這個函數看起來很像往系統中添加一個快裝置,但事實上他僅僅是用來跟蹤塊裝置号的使用情況。

int register_blkdev(unsigned int major, const char *name)
{
        struct blk_major_name **n, *p;
        int index, ret = 0;
        mutex_lock(&block_class_lock);
        if (major == 0) {
                for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
                        if (major_names[index] == NULL)
                                break;
                }
                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;
        return ret;
}      

block_device

核心用block_device表示一個邏輯塊裝置,這個結構可以表示一個完整的邏輯塊裝置,也可以表示邏輯塊裝置上的一個分區。

當塊裝置對應的裝置檔案被打開時,核心就會建立一個block_device對象。

block_device在核心中主要用來溝通檔案系統和裝置驅動程式,起到粘合劑的作用,使得塊裝置驅動程式很少與之直接打交道。

struct block_device {
        dev_t                        bd_dev;
        struct inode *                bd_inode;
        struct super_block *        bd_super;
        int                        bd_openers;
        struct mutex                bd_mutex;
        struct list_head        bd_inodes;
        void *                        bd_holder;
        int                        bd_holders;
        struct block_device *        bd_contains;
        unsigned                bd_block_size;
        struct hd_struct *        bd_part; // 指向分區資訊結構
        unsigned                bd_part_count;
        struct gendisk *        bd_disk;
        struct list_head        bd_list;
        unsigned long                bd_private;
        int                        bd_fsfreeze_count;
        struct mutex                bd_fsfreeze_mutex;
};      

gendisk

gendisk表示一個實際磁盤裝置的抽象,它是直接被裝置驅動程式配置設定和操作。

struct gendisk {
        // 主裝置号,不要直接使用,通過disk_devt()使用,原因你懂得
        // 主裝置号也表示了目前裝置對應的驅動程式
        int major; 
        int first_minor;
        // minors表示目前裝置所能包含的最大的從裝置的分區數
        // minors=1表示不能被分區
        int minors; 
        // 塊裝置名稱,顯示與sysfs中
        char disk_name[DISK_NAME_LEN];
        
        char *(*devnode)(struct gendisk *gd, mode_t *mode);
        // 磁盤的分區資訊
        struct disk_part_tbl *part_tbl;
        // 目前裝置上的第一個分區,如果沒有分區則表示整個裝置
        struct hd_struct part0;
        // 操作集
        const struct block_device_operations *fops;
        // 塊裝置上的IO請求隊列
        struct request_queue *queue;
        
        void *private_data;
        int flags;
        struct device *driverfs_dev;
        struct kobject *slave_dir;
        struct timer_rand_state *random;
        atomic_t sync_io;
        struct work_struct async_notify;
        int node_id;
};      

hd_struct

核心使用該結構表示裝置上的一個分區

struct hd_struct {
        // 分區的起始扇區号
        sector_t start_sect;
        // 分區的扇區數
        sector_t nr_sects;
        sector_t alignment_offset;
        unsigned int discard_alignment;
        // 分區也是一個裝置
        struct device __dev;
        struct kobject *holder_dir;
        // 分區号
        int policy, partno;
        unsigned long stamp;
        int in_flight[2];
        struct disk_stats *dkstats;
        struct rcu_head rcu_head;
};      

alloc_disk配置設定gen_disk

struct gendisk *alloc_disk(int minors)
{
        return alloc_disk_node(minors, -1);
}
struct gendisk *alloc_disk_node(int minors, int node_id)
{
        struct gendisk *disk;
        disk = kmalloc_node(sizeof(struct gendisk),
                                GFP_KERNEL | __GFP_ZERO, node_id);
        if (disk) {
                if (!init_part_stats(&disk->part0)) {
                        kfree(disk);
                        return NULL;
                }
                disk->node_id = node_id;
                // 動态擴充分區,因為分區是變化的
                if (disk_expand_part_tbl(disk, 0)) {
                        free_part_stats(&disk->part0);
                        kfree(disk);
                        return NULL;
                }
                // gendisk中的part0是hd_struct結構
                // 用來辨別一個分區,同時辨別整個磁盤裝置
                disk->part_tbl->part[0] = &disk->part0;
                // 目前裝置所允許的最大分區數
                disk->minors = minors;
                
                rand_initialize_disk(disk);
                // 初始化裝置驅動模型
                // #define disk_to_dev(disk) (&(disk)->part0.__dev)
                // 可以看到,disk->part0是所屬裝置的代表
                disk_to_dev(disk)->class = &block_class;
                disk_to_dev(disk)->type = &disk_type;
                
                device_initialize(disk_to_dev(disk));
                INIT_WORK(&disk->async_notify,
                        media_change_notify_thread);
        }
        return disk;
}      

gendisk和分區表之間關系

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

添加一個塊裝置add_disk

核心用裝置号來辨別一個塊裝置。通常,主裝置号代表驅動程式,次裝置号對應裝置驅動程式所管理裝置上的一個分區。

磁盤上的一個獨立分區被看做一個裝置,對應/dev/下的一個裝置節點。

Linux裝置号的類型為dev_t。

假設将一個未分區的gendisk添加到核心時,核心将試圖讀取裝置上的分區資訊,對每個有效分區形成一個裝置模型中的device對象,并通過device_add添加到系統,但此時這些分區并不會産生block_device對象,直到分區裝置被打開。由于要加入的裝置沒有有效的分區,是以在add_disk時核心無法擷取到分區資訊,會列印一條:

[97144.328832]  ram_MaoRi_a: unknown partition table      

add_disk完成以下動作:

1) 把gendisk添加到bdev_map中;

2) 在/dev下生成裝置檔案節點(device_add完成);

3) 動态生成一個block_device對象,和隸屬于"bdev"檔案系統的inode節點。

4) 如果有分區,給每個分區生成一個device結構,device_add添加到/dev下面,但是并不生成block_device。

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構
void add_disk(struct gendisk *disk)
{
        struct backing_dev_info *bdi;
        dev_t devt;
        int retval;
        disk->flags |= GENHD_FL_UP;
        // 構造裝置号
        retval = blk_alloc_devt(&disk->part0, &devt);
        disk_to_dev(disk)->devt = devt;
        disk->major = MAJOR(devt);
        disk->first_minor = MINOR(devt);
        bdi = &disk->queue->backing_dev_info;
        bdi_register_dev(bdi, disk_devt(disk));
        // 把gendisk添加到bdev_map中
        blk_register_region(disk_devt(disk), disk->minors, NULL,
                            exact_match, exact_lock, disk);
                            
        // 裝置驅動模型的裝置對象的注冊
         // 在/dev/下面給這個裝置生成一個裝置節點比如,/dev/hda
        register_disk(disk);
        // 初始化IO隊列在sysfs中的檔案
        blk_register_queue(disk);
        WARN_ON_ONCE(blk_get_queue(disk->queue));
        retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
                                   "bdi");
        WARN_ON(retval);
}
void register_disk(struct gendisk *disk)
{
        // 擷取gendisk->part0對應的device對象
        struct device *ddev = disk_to_dev(disk);
        struct block_device *bdev;
        struct disk_part_iter piter;
        struct hd_struct *part;
        int err;
        ddev->parent = disk->driverfs_dev;
        dev_set_name(ddev, disk->disk_name);
        dev_set_uevent_suppress(ddev, 1);
        // 裝置驅動模型的裝置對象的操作
        // 在/dev下添加裝置節點檔案
        if (device_add(ddev))
                return;
                
        disk->part0.holder_dir = kobject_create_and_add("holders", &ddev->kobj);
        disk->slave_dir = kobject_create_and_add("slaves", &ddev->kobj);
        if (!disk_partitionable(disk))
                goto exit;
        if (!get_capacity(disk))
                goto exit;
        // 配置設定block_device結構和inode,關系如圖11-5
        bdev = bdget_disk(disk, 0);
        if (!bdev)
                goto exit;
        disk->flags |= GENHD_FL_INVALIDATED;
        // 初始化bdev和gendisk的關系
        // 掃描分區表,如果發現新分區就在/dev/下面生成裝置檔案節點,并不構造block_device結構
        err = blkdev_get(bdev, FMODE_READ);
        if (err < 0)
                goto exit;
        blkdev_put(bdev, FMODE_READ);
exit:
        dev_set_uevent_suppress(ddev, 0);
        kobject_uevent(&ddev->kobj, KOBJ_ADD);
        disk_part_iter_init(&piter, disk, 0);
        while ((part = disk_part_iter_next(&piter)))
                kobject_uevent(&part_to_dev(part)->kobj, KOBJ_ADD);
        disk_part_iter_exit(&piter);
}      
block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

裝置檔案的打開

static int blkdev_open(struct inode * inode, struct file * filp)
{
        struct block_device *bdev;
        int res;
        filp->f_flags |= O_LARGEFILE;
        if (filp->f_flags & O_NDELAY)
                filp->f_mode |= FMODE_NDELAY;
        if (filp->f_flags & O_EXCL)
                filp->f_mode |= FMODE_EXCL;
        if ((filp->f_flags & O_ACCMODE) == 3)
                filp->f_mode |= FMODE_WRITE_IOCTL;
        // 動态生成block_device和inode
        bdev = bd_acquire(inode);
        if (bdev == NULL)
                return -ENOMEM;
        filp->f_mapping = bdev->bd_inode->i_mapping;
        // 建立block_device和gendisk的關聯
        // gendisk一個磁盤裝置隻有一個,關系如圖11-7
        res = blkdev_get(bdev, filp->f_mode);
        if (res)
                return res;
        if (filp->f_mode & FMODE_EXCL) {
                res = bd_claim(bdev, filp);
                if (res)
                        goto out_blkdev_put;
        }
        return 0;
 out_blkdev_put:
        blkdev_put(bdev, filp->f_mode);
        return res;
}      
block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

裝置檔案打開過程如下:

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

blk_init_queue裝置IO請求隊列的初始化

先過目一下request_queue這個龐大的結構:

struct request_queue
{
        // 雙向隊清單頭,所有的request被串在這個list上
        struct list_head        queue_head;
        struct request                *last_merge;
        struct elevator_queue        *elevator;
        struct request_list        rq;
        // 請求處理函數
        // 當檔案系統需要發起讀或寫時,會調用這個函數(request方式)
        request_fn_proc                *request_fn;
        // 這個函數為request_queue産生新的request
        make_request_fn                *make_request_fn;
        
        prep_rq_fn                *prep_rq_fn;
        unprep_rq_fn                *unprep_rq_fn;
        unplug_fn                *unplug_fn;
        merge_bvec_fn                *merge_bvec_fn;
        prepare_flush_fn        *prepare_flush_fn;
        softirq_done_fn                *softirq_done_fn;
        rq_timed_out_fn                *rq_timed_out_fn;
        dma_drain_needed_fn        *dma_drain_needed;
        lld_busy_fn                *lld_busy_fn;
        sector_t                end_sector;
        struct request                *boundary_rq;
        struct timer_list        unplug_timer;
        int                        unplug_thresh;
        unsigned long                unplug_delay;
        struct work_struct        unplug_work;
        struct backing_dev_info        backing_dev_info;
        void                        *queuedata;
        gfp_t                        bounce_gfp;
        // 目前請求隊列的狀态 
        unsigned long                queue_flags;
        spinlock_t                __queue_lock;
        spinlock_t                *queue_lock;
        struct kobject kobj;
        unsigned long                nr_requests;
        unsigned int                nr_congestion_on;
        unsigned int                nr_congestion_off;
        unsigned int                nr_batching;
        void                        *dma_drain_buffer;
        unsigned int                dma_drain_size;
        unsigned int                dma_pad_mask;
        unsigned int                dma_alignment;
        struct blk_queue_tag        *queue_tags;
        struct list_head        tag_busy_list;
        unsigned int                nr_sorted;
        unsigned int                in_flight[2];
        unsigned int                rq_timeout;
        struct timer_list        timeout;
        struct list_head        timeout_list;
        struct queue_limits        limits;
        unsigned int                sg_timeout;
        unsigned int                sg_reserved_size;
        int                        node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
        struct blk_trace        *blk_trace;
#endif
        /*
         * DEPRECATED: please use "for flush operations" members below!
         * These members (through orig_bar_rq) are preserved purely
         * to maintain kABI.
         */
        unsigned int                ordered, next_ordered, ordseq;
        int                        orderr, ordcolor;
        struct request                pre_flush_rq, bar_rq, post_flush_rq;
        struct request                *orig_bar_rq;
        struct mutex                sysfs_lock;
        /* For future extensions */
        void        *pad;
#ifndef __GENKSYMS__
        /*
         * for flush operations
         */
        unsigned int                flush_flags;
        unsigned int                flush_not_queueable:1;
        unsigned int                flush_queue_delayed:1;
        unsigned int                flush_pending_idx:1;
        unsigned int                flush_running_idx:1;
        unsigned long                flush_pending_since;
        struct list_head        flush_queue[2];
        struct list_head        flush_data_in_flight;
        struct request                flush_rq;
        struct throtl_data *td;
};      

隊列中存放塊裝置的IO請求,每個請求是request對象,先看一下request結構:

struct request {
        // 用來把request加到blk_plug連結清單
        struct list_head queuelist;
        struct call_single_data csd;
        
        // 目前request對應的IO隊列
        struct request_queue *q;
        // 請求除了可以讀寫之外,還可以是一些控制類的指令
        unsigned int cmd_flags;
        enum rq_cmd_type_bits cmd_type;
        unsigned long atomic_flags;
        int cpu;
        // 目前請求要傳輸的資料的總量
        unsigned int __data_len;
        // 目前請求在磁盤上的起始扇區
        sector_t __sector;
        // 核心将一個bio資訊轉存在一個request上,或者是合并到已經有的bio上,request就是通過*bio把這些bio串起來
        // rq_for_each_segment可以周遊這些bio
        // blk_rq_map_sg為DMA建立分散/聚集映射
        struct bio *bio;
        struct bio *biotail;
        struct hlist_node hash;        /* merge hash */
        union {
                struct rb_node rb_node;        /* sort/lookup */
                void *completion_data;
        };
        union {
                void *elevator_private[3];
                struct {
                        unsigned int                seq;
                        struct list_head        list;
                } flush;
        };
        
        struct gendisk *rq_disk;
        unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
        unsigned long long start_time_ns;
        unsigned long long io_start_time_ns;
#endif
        unsigned short nr_phys_segments;
        unsigned short ioprio;
        int ref_count;
        void *special;
        char *buffer;
        int tag;
        int errors;
        unsigned char __cmd[BLK_MAX_CDB];
        unsigned char *cmd;
        unsigned short cmd_len;
        unsigned int extra_len;
        unsigned int sense_len;
        unsigned int resid_len;
        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;
        void *pad;
};      

下面看看如何初始化request_queue

struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
        struct request_queue *uninit_q, *q;
        
        // 配置設定request_queue結構體,并做一些基本的初始化
        uninit_q = blk_alloc_queue_node(GFP_KERNEL, node_id);
        // 初始化request_fn等重要成員
        q = blk_init_allocated_queue_node(uninit_q, rfn, lock, node_id);
        return q;
}
struct request_queue *
blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,
                              spinlock_t *lock, int node_id)
{
        if (!q)
                return NULL;
        q->node = node_id;
        if (blk_init_free_list(q))
                return NULL;
        // 初始化請求處理函數
        q->request_fn                = rfn;
        q->prep_rq_fn                = NULL;
        q->unprep_rq_fn                = NULL;
        q->unplug_fn                = generic_unplug_device;
        q->queue_flags                = QUEUE_FLAG_DEFAULT;
        if (lock)
                q->queue_lock                = lock;
        // 提供一個預設的make_request: blk_queue_bio()
        blk_queue_make_request(q, blk_queue_bio);
        q->sg_reserved_size = INT_MAX;
        // 選擇一個排程算法
        // 通過 cat /sys/block/sda/queue/scheduler 一個裝置的io排程算法
        if (!elevator_init(q, NULL)) {
                blk_queue_congestion_threshold(q);
                return q;
        }
        return NULL;
}      

blk_queue_make_request

送出請求

當檔案系統需要與塊裝置進行資料傳輸或者控制時,核心需要向裝置的request_queue發送請求對象,這個任務由submit_io完成:

struct request_queue *
blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,
                              spinlock_t *lock, int node_id)
{
        if (!q)
                return NULL;
        q->node = node_id;
        if (blk_init_free_list(q))
                return NULL;
        // 初始化請求處理函數
        q->request_fn                = rfn;
        q->prep_rq_fn                = NULL;
        q->unprep_rq_fn                = NULL;
        q->unplug_fn                = generic_unplug_device;
        q->queue_flags                = QUEUE_FLAG_DEFAULT;
        if (lock)
                q->queue_lock                = lock;
        // 提供一個預設的make_request: blk_queue_bio()
        blk_queue_make_request(q, blk_queue_bio);
        q->sg_reserved_size = INT_MAX;
        // 選擇一個排程算法
        // 通過 cat /sys/block/sda/queue/scheduler 一個裝置的io排程算法
        if (!elevator_init(q, NULL)) {
                blk_queue_congestion_threshold(q);
                return q;
        }
        return NULL;
}      

void submit_bio(int rw, struct bio *bio)
{
        int count = bio_sectors(bio);
        bio->bi_rw |= rw;
        
        ...
        ...
        ...
        generic_make_request(bio);
}
void generic_make_request(struct bio *bio)
{
        // 加入到目前程序的bio_tail
        if (current->bio_tail) {
                *(current->bio_tail) = bio;
                bio->bi_next = NULL;
                current->bio_tail = &bio->bi_next;
                return;
        }
        // 周遊bio
        do {
                current->bio_list = bio->bi_next;
                if (bio->bi_next == NULL)
                        current->bio_tail = &current->bio_list;
                else
                        bio->bi_next = NULL;
                __generic_make_request(bio);
                bio = current->bio_list;
        } while (bio);
        current->bio_tail = NULL;
}
static inline void __generic_make_request(struct bio *bio)
{
        struct request_queue *q;
        do {
                char b[BDEVNAME_SIZE];
                // 通過bio->bi_bdev->gendisk->request_queue擷取到bio對應的queue
                q = bdev_get_queue(bio->bi_bdev);
                // 重新映射bio的偏移量,因為向/dev/sda2寫入資料,要轉換成/dev/sda的偏移量
                // 一個裝置隻有一個request_queue
                blk_partition_remap(bio);
                
                trace_block_bio_queue(q, bio);
                // 調用系統預設的make_request或者驅動程式自定義的make_request()
                // 這個函數在哪裡找呢?blk_init_queue_node的時候設定為blk_queue_bio()
                
                ret = q->make_request_fn(q, bio);
        } while (ret);
        return;
end_io:
        bio_endio(bio, err);
}
// 調用系統預設的make_request
int blk_queue_bio(struct request_queue *q, struct bio *bio)
{
        spin_lock_irq(q->queue_lock);
        if (bio->bi_rw & (BIO_FLUSH | BIO_FUA)) {
                where = ELEVATOR_INSERT_FLUSH;
                goto get_rq;
        }
        if (elv_queue_empty(q))
                goto get_rq;
        // 調用IO排程算法做merge
        el_ret = elv_merge(q, &req, bio);
        switch (el_ret) {
        case ELEVATOR_BACK_MERGE:
                if (!ll_back_merge_fn(q, req, bio))
                        break;
        case ELEVATOR_FRONT_MERGE:
                if (!ll_front_merge_fn(q, req, bio))
                        break;
        default:
                ;
        }
get_rq:
        // 把bio轉換成request
        init_request_from_bio(req, bio);
        spin_lock_irq(q->queue_lock);
        if (queue_should_plug(q) && elv_queue_empty(q))
                blk_plug_device(q);
                
        // 把request加入到q中        
        __elv_add_request(q, req, where, 0);
        return 0;
}      

可以看到塊裝置驅動處理請求有兩種方式: requst方式和make_request方式。

如果是request方式,bio->__make_request->request。核心會在調用驅動中的request之前調用__make_request,對bio進行合并。

差別如圖:

block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

bio結構

bio在檔案系統與block子系統之間來回流動,把要讀寫的帶來帶去。

通過bio對象,塊裝置驅動在執行IO的過程中無須和建立這個bio的程序相關聯。

bio對象隻表示從一個扇區開始若幹連續的扇區。

struct bio {
        // 本次傳輸扇區的起始号
        sector_t                bi_sector;
        // 下一個bio
        struct bio                *bi_next;
        // 與請求相關量的塊裝置,引導submit_bio将請求發送到哪個裝置上
        struct block_device        *bi_bdev;
        
        unsigned long                bi_flags;
        unsigned long                bi_rw;
        // bi_io_vec中數組的個數
        unsigned short                bi_vcnt;
        // 目前處理的io_vec的下标
        unsigned short                bi_idx;
        unsigned int                bi_phys_segments;
        unsigned int                bi_size;
        unsigned int                bi_seg_front_size;
        unsigned int                bi_seg_back_size;
        unsigned int                bi_max_vecs;
        unsigned int                bi_comp_cpu;
        atomic_t                bi_cnt;
        // 指向一個IO向量的數組,每個元素對應一個page
        struct bio_vec                *bi_io_vec;
        bio_end_io_t                *bi_end_io;
        void                        *bi_private;
        bio_destructor_t        *bi_destructor;
        struct bio_vec                bi_inline_vecs[0];
};
// IO向量結構
struct bio_vec {
        struct page        *bv_page;
        unsigned int        bv_len;
        unsigned int        bv_offset;
};      
block裝置驅動之核心機制block子系統初始化一個block驅動執行個體 (ramdisk)塊裝置的注冊與管理block_devicegendiskhd_structalloc_disk配置設定gen_diskgendisk和分區表之間關系添加一個塊裝置add_disk裝置檔案的打開blk_init_queue裝置IO請求隊列的初始化blk_queue_make_request送出請求blk_queue_make_request送出請求bio結構

bio在request以連結清單的形式存在,為什麼是連結清單呢?這是IO排程器合并的結果。

每個bio對應若幹個page上一段待寫入磁盤的資料。每個page上的一段資料成為segment。

核心提供了一些周遊函數:

bio_for_each_segment(bvl, bio, i): 周遊一個bio上的segment
#define bio_iovec_idx(bio, idx)        (&((bio)->bi_io_vec[(idx)]))
#define bio_iovec(bio)                bio_iovec_idx((bio), (bio)->bi_idx)
#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)
#define bio_empty_barrier(bio)        (bio_rw_flagged(bio, BIO_RW_BARRIER) && !bio_has_data(bio) && !bio_rw_flagged(bio, BIO_RW_DISCARD))      

繼續閱讀