實驗目的
1、了解Linux塊設備管理機制
2、學習塊設備的基本管理
3、編寫一個簡單的塊設備驅動程序sbull,實現一套內存中的虛擬磁盤驅動器
4、通過操作驗證塊設備驅動器
5、實驗內容:
編寫一個簡單的塊設備驅動程序:
- 該塊設備包括sbull_open()、sbull_ioctl()和sbull_release()等基本操作。
- 對每個驅動器,sbull分配一個內存數組,然后使這個數組可通過塊操作來訪問。
- sbull驅動可通過在該驅動器上進行分區、建立文件系統、以及加載到系統層級中來測試。
- sbull設備被定義為一個可移出的設備。
通過實際操作驗證塊設備驅動
實驗記錄
我的虛擬機版本CenOS 7.8 x64,內核版本3.10.0-1127.el7.x86_64。
切換到root權限,隨后編寫sbull.c和Makefile
sbull.c
點擊查看詳細內容
#include <linux/init.h>//module_init/exit
#include <linux/module.h>//MODULE_AUTHOR,MODULE_LICENSE等
#include <linux/genhd.h>//alloc_disk
#include <linux/blkdev.h>//blk_init_queue
#include <linux/fs.h>//register_blkdev,unregister_blkdev
#include <linux/types.h>//u_char,u_short
#include <linux/vmalloc.h>
#include <linux/hdreg.h>
#include <linux/bio.h>
#include <linux/moduleparam.h>
#include <linux/major.h>
#include <linux/highmem.h> //kmap kunmap
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define RAMBLK_SIZE (1024*1024*2)//分配的內存2MB大小空間
/*
bio代表一個io請求,里面有io請求的所有信息
request是bio提交給io調度器產生的數據,一個request放着順序排列的bio
request_queue代表着一個物理設備,順序的放着request
*/
static struct gendisk * ramblk_disk = NULL;/*gendisk表示一個獨立的磁盤設備,內核還可以用它來表示分區*/
static struct request_queue * ramblk_request_queue = NULL;
static int major = 0;//塊設備的主設備號
static DEFINE_SPINLOCK(ramblk_spinlock);//定義並初始化一個自旋鎖
static char * ramblk_buf = NULL;//申請的內存起始地址
/*
上面定義地址是用到char *,是十分有用的。類似於list中container_of一樣
*/
int ramblk_getgeo(struct block_device * blk_Dev, struct hd_geometry * hg)
{
printk("ramblk_getgeo\n");
hg->cylinders = 64;
hg->heads = 8;
hg->sectors = (RAMBLK_SIZE/8/64/512);
return 0;
}
/*
如果說file_operation結構是連接虛擬的VFS文件的操作與具體文件系統的文件操作之間的樞紐,那么block_device_operations就是連接抽象的塊設備操作與具體塊設備操作之間的樞紐。
*/
static const struct block_device_operations ramblk_fops = {
.owner = THIS_MODULE,
.getgeo = ramblk_getgeo,
};
static void ramblk_make_request(struct request_queue *q, struct bio *bio)
{
printk("do_ramblk_request\n");
// struct block_device *bdev = bio->bi_bdev;
int rw;
struct bio_vec *bvec;
bvec = bio->bi_io_vec;
// sector_t sector;
int i;
//int err = -EIO;
//struct request *req;
void *disk_mem;
void *bvec_mem;
if((bio->bi_sector << 9) + bio->bi_size > RAMBLK_SIZE)
return -EIO;
disk_mem = ramblk_buf + (bio->bi_sector << 9);
// sector = bio->bi_sector;
// if(bio_end_sector(bio) > get_capacity(bdev->db_disk))
// goto out;
rw = bio_rw(bio);
if(rw == READA)
rw = READ;
/*bio中的每個段是由一個bio_vec數據結構描述的*/
/*
bio_vec結構體中的字段
struct page* bv_page 指向段的頁框中頁描述符的指針
unsigned int bv_len 段的字節長度
unsigned int bv_offset 頁框中段數據的偏移量
*/
// bvec_mem = kmap_atomic(bvec->bv_page) + bvec->bv_offset;
/*高端內存映射
允許睡眠:kmap(永久映射)
不允許睡眠:kmap_atomic(臨時映射)會覆蓋以前到映射
*/
/*因bio_vec中的內存地址是使用page*描述的,故在高端內存中需要使用kmap進行映射才能訪問,再加上
在bio_vec中的偏移量,才是高端地址內存中的實際位置*/
bvec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
/*bio_for_each_segment宏定義bio.h
#define bio_for_each_segment(bvl, bio, i) for(i=0; bvl = bio_iovec_idx((bio),(i)), i< (bio)->bi_vcnt; i++)
bio_iovec_idx宏定義bio.h
#define bio_iovec_idx(bio, idx) (&((bio)->bi_io_vec[(idx)]))
*/
bio_for_each_segment(bvec, bio, i)
{
/*判斷bio請求處理的方向*/
switch(rw)
{
case READ:
memcpy(bvec_mem, disk_mem, bvec-> bv_len);
break;
case WRITE :
memcpy(disk_mem, bvec_mem, bvec-> bv_len);
break;
default :
// kunmap_atomic(bvec->bv_page);
kunmap(bvec->bv_page);
}
kunmap(bvec->bv_page);
disk_mem += bvec->bv_len;
}
bio_endio(bio, 0);//bio中所有的bio_vec處理完后報告處理結束
}
static int ramblk_init(void)
{
printk("ramblk_init\n");
struct gendisk *disk;
// 1.分配gendisk結構體,使用alloc_disk函數
/*
gendisk結構是一個動態分配的結構, 它需要一些內核的特殊處理來進行初始化,驅動程序不能自己動態分配該架構
而使用struct gendisk *alloc_disk(int mimors) 參數minors是該磁盤使用的次設備號的數目
*/
// 2.設置
// 2.1 分配/設置隊列,提供讀寫能力.使用函數blk_init_queue(request_fn_proc *rfn,spin_lock_t *lock)
// ramblk_request_queue = blk_init_queue(ramblk_make_request,&ramblk_spinlock);
major = register_blkdev(0,"sbull");//注冊主設備
if(major < 0){//檢查是否成功分配一個有效的主設備號
printk(KERN_ALERT "register_blkdev error.\n");
return -1;
}
/*使用制造請求的方式,先分配queue*/
ramblk_request_queue = blk_alloc_queue(GFP_KERNEL);
/*在綁定請求制造函數*/
blk_queue_make_request(ramblk_request_queue, ramblk_make_request);
disk = ramblk_disk = alloc_disk(16);//minors=分區+1
// 2.2 設置disk的其他信息,比如容量、主設備號等
//設置主設備號
ramblk_disk->major = major;
ramblk_disk->first_minor = 0;//設置第一個次設備號
ramblk_disk->minors=1;//設置最大的次設備號,=1表示磁盤不能被分區
sprintf(ramblk_disk->disk_name, "sbull%c", 'a');//設置設備名
ramblk_disk->fops = &ramblk_fops;//設置fops 設置前面表述的各種設備操作
ramblk_disk->queue = ramblk_request_queue;//設置請求隊列
set_capacity(ramblk_disk, RAMBLK_SIZE/512);//設置容量
// 3.硬件相關的操作
ramblk_buf = (char*)vmalloc(RAMBLK_SIZE);//申請RAMBLK_SIZE內存
// 4.注冊
add_disk(ramblk_disk);//add partitioning information to kernel list
printk("ramblk_init.\n");
return 0;
}
static void ramblk_exit(void)
{
del_gendisk(ramblk_disk);
put_disk(ramblk_disk);
unregister_blkdev(major,"sbull");//注銷設備驅動
blk_cleanup_queue(ramblk_request_queue);//清除隊列
vfree(ramblk_buf);//釋放申請的內存
printk("ramblk_exit.\n");
}
module_init(ramblk_init);//入口
module_exit(ramblk_exit);//出口
MODULE_AUTHOR("hustcs");
MODULE_LICENSE("Dual BSD/GPL");
Makefile
點擊查看詳細內容
ifneq ($(KERNELRELEASE),)
obj-m += sbull.o
else
PWD := $(shell pwd)
KVER ?= $(shell uname -r)
KDIR := /usr/src/kernels/$(KVER)
all:
@$(MAKE) -C $(KDIR) M=$(PWD)
clean:
@rm -rf .*.cmd *.o *.mod.c *.ko *.symvers *.ko.unsigned *.order
endif
實驗過程
執行make命令
安裝內核模塊sbull.ko,然后在已安裝的模塊中查找sbull
使用指令dmesg,查看內核輸出信息
查看模塊信息
獲取設備列表
查看sbulla文件夾
查看sbull設備信息
格式化sbull設備
創建掛載點並掛載該設備
進入目錄/mnt/sbull,並嘗試創建一個文件