block子系統初始化
genhd_device_init為Linux核心中塊裝置驅動程式的整體架構進行了必要的初始化。
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和分區表之間關系
添加一個塊裝置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。
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);
}
裝置檔案的打開
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;
}
裝置檔案打開過程如下:
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 = ¤t->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進行合并。
差別如圖:
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;
};
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))