本篇分析Linux设备驱动程序第三版的第十六章,块设备驱动程序的代码,主要是其sbull模块在内核2.6.35.6-45版本下编译有错,这篇文章就是讲sbull模块迁移到了2.6.35.6-45这个模块下了,实现了正确编译,加载。修改之后的代码如下所示,相关函数的功能也在代码中进行说明了。
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h> // printk()
#include <linux/slab.h> // kmalloc()
#include <linux/fs.h> // everything...
#include <linux/errno.h> // error codes
#include <linux/timer.h>
#include <linux/types.h> // size_t
#include <linux/fcntl.h> // O_ACCMODE
#include <linux/hdreg.h> // HDIO_GETGEO
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h> // invalidate_bdev
#include <linux/bio.h>
typedef struct request_queue request_queue_t;
//在用户态下编程可以通过main()的来传递命令行参数,而编写一个内核模块则通过module_param()
//参数用 module_param 宏定义来声明, 它定义在 moduleparam.h
static int bdev_major = 0; //主设备号默认设为0,表示动态分配
module_param(bdev_major, int, 0);
static int hardsect_size = 512; //请求队列描述符中的字段,表示扇区大小
module_param(hardsect_size, int , 0);
static int nsectors = 1024; //块设备的大小 //131072,64MB
module_param(nsectors, int, 0);
static int ndevices = 4; //???
module_param(ndevices, int, 0);
//不同的 request mode
enum {
RM_SIMPLE = 0, //简单模式,使用简单的请求处理函数
RM_FULL = 1, //完全模式,使用了bio
RM_NOQUEUE = 2, //使用make_request,不使用请求队列
};
static int request_mode = RM_SIMPLE;
module_param(request_mode, int, 0);
//次设备号与分区管理
/*
现在的Linux使用了定义在<linxu/kdev_t.h>中的一个新类型kdev_t来保存设备类型。
MAJOR(kdev_t dev)从kdev_t结构中获取主设备号
MINOR(kdev_t dev)得到次设备号
MKDEV(int ma,int mi) 通过主设备号和次设备号创建kdev_t
kdev_t_to_nr(kdev_t_dev) 将kdev_t转化为一个整数(一个dev_t)
*/
#define BDEV_MINORS 16
#define MINOR_SHIFT 4
#define DEVNUM(kdevnum) (MINIOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT
//我们可以修改我们的设备的扇区大小,但是内核依然认为一个扇区是512B
#define KERNEL_SECTOR_SIZE 512
// After this much idle time, the driver will simulate a media change.
#define INVALIDATE_DELAY 30*HZ
//描述块设备的结构体
struct my_bdev {
int size; //设备大小,以扇区为单位
u8 *data; //数据数组
short users; //用户数目
short media_change; //介质改变标志
spinlock_t lock; //用于互斥
request_queue_t *queue; //设备请求队列
struct gendisk *gd; //gendisk结构
struct timer_list timer; //用来模拟介质改变
};
//这就是我们硬盘的数据结构,有该硬盘在内核的表示gd, 硬盘的操作请求队列queue,
//设备操作自旋锁,以及操作延时的定时器,通过定时器模拟一个可更换介质的块设备
static struct my_bdev *Devices = NULL;
//=================================================================================================
//请求处理,传输数据的方式
/*************************************
*处理一个I/O请求,读或者写
**************************************/
static void bdev_transfer(struct my_bdev *dev, unsigned long sector, //sector为扇区号,表示要传输的第一个扇区;nsect为该次请求所要传输的扇区数
unsigned long nsect, char *buffer, int write)
{
printk("begin of bdev_transfer\n");
unsigned long offset = sector*KERNEL_SECTOR_SIZE;
unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
if( (offset + nbytes) > dev->size ){
printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if(write) {
memcpy(dev->data + offset, buffer, nbytes);
printk("write %ld bytes\n", nbytes); ///
}
else {
memcpy(buffer, dev->data + offset, nbytes);
printk("read %ld bytes\n", nbytes);
}
printk("end of bdev_transfer\n");
}
//=================================================================================================//
//request_mode为0时,请求队列的处理
//从请求队列中逐个取出请求,使用bdev_transfer处理。
/*********************************************************************************************
*块设备的驱动程序的核心是它的请求函数,至少如设备的启动都是在这个函数里面完成的。
*内核需要驱动程序处理读取写入以及其他的设备操作时,就会调用该函数。
*request是在一个原子上下文运行的,该锁是由内核控制的,拥有锁时防止内核为设备安排其他请求。
**********************************************************************************************/
// 每一个设备都需要一个请求队列,因为对磁盘数据实际的传入和传出时间与内核请求的时间相差很大。
// 驱动程序所需要知道的任何信息都保存在通过请求队列传给我们的结构中
// 请求队列保存了表述设备所能处理的请求的参数:最大尺寸,在同一个请求所能包含的独立段的数目、硬件扇区大小、对齐需求等等
// 请求队列还实现了插件接口,使多规格IO调度器的使用称为可能
static void bdev_request(request_queue_t *q)
{
struct request *req;
printk("mode 0:begin of bdev_request\n");
req = blk_fetch_request(q);
while( req != NULL ) {
//blk_fetch_request函数获得请求队列中的下一个请求,并不从队列中删除请求
struct my_bdev *dev = req->rq_disk->private_data;
//req->rq_disk为磁盘描述符
//该请求是否是一个文件系统请求——移动块设备数据请求
if( ! req->cmd_type == REQ_TYPE_FS ) {
printk( KERN_NOTICE "Skip non-fs request\n");
//end_request用在简单的驱动程序中,已完成(或部分完成)一个请求
//当处理非文件系统请求时,传递0表示不能成功地完成该请求
__blk_end_request_all(req,-EIO);
continue;
}
printk("mode 0:bdev_transfer some sectors\n");
//bdev_transfer实现数据的真正移动
//dev - Devices
//req->sector,开始扇区的索引号
//req->current_nr_sectors, 需要传输的扇区数
//buffer,传输的数据的缓冲区指针
//rq_data_dir(req),传输方向,0表述从设备读,非0表示写
bdev_transfer( dev, blk_rq_pos(req), blk_rq_cur_sectors(req),
req->buffer, rq_data_dir(req) );
//end_request(req, 1);
if( ! __blk_end_request_cur(req, 0) ) {
req = blk_fetch_request(q);
}
}
printk("mode 0:end of bdev_request\n");
}
//==================================================================================================
//request_mode为1时,请求队列的处理
//bdev_full_request从请求队列逐个取出请求,每个请求由bdev_xfer_request处理
//bdev_xfer_request遍历每一个请求的bio链表,对每个bio结构使用bdev_xfer_bio处理
/***********************************************************
*该函数遍历bio结构中的每个段,获得内存虚拟地址以访问缓冲区,
*然后调用bdev_transfer函数,已完成数据的拷贝
************************************************************/
static int bdev_xfer_bio(struct my_bdev *dev, struct bio *bio)
{
int i;
struct bio_vec *bvec;
sector_t sector = bio->bi_sector; //bi_sector该bio结构所要传输的第一个扇区
printk("mode 1: begin of xfer_bio\n");
//对每个段独立操作
bio_for_each_segment(bvec, bio, i) {
//为buffer返回内核虚拟地址
char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
printk("mode 1 in a xfer_bio:bdev_transfer some sectors\n");
bdev_transfer(dev, sector, bio_cur_bytes(bio) >> 9,
buffer, bio_data_dir(bio) == WRITE);
sector += bio_cur_bytes(bio) >> 9;
//取消缓冲区映射
__bio_kunmap_atomic(bio, KM_USER0);
}
printk("mode 1:end of xfer_bio\n");
return 0; //总是返回成功
}
/************************************************
*实际执行一个请求的工作由bdev_xfer_request完成
************************************************/
static int bdev_xfer_request(struct my_bdev *dev, struct request *req)
{
struct bio *bio;
int nsect = 0;
printk("mode 1: begin of xfer_request\n");
//rq_for_each_bio宏,只是遍历请求中的每一个bio结构,并且提供了可以传递给bdev_xfer_bio函数用于传输的指针
__rq_for_each_bio(bio, req) {
printk("mode 1 in a xfer_request:xfer_bio some sectors\n");
bdev_xfer_bio(dev, bio);
nsect += bio->bi_size / KERNEL_SECTOR_SIZE; //bi_size以字节为单位
}
printk("mode 1: end of xfer_request\n");
return nsect;
}
/***************************************************************************************************
//如果request_mode参数设置为1,加载my_bdev驱动程序时,将注册一个bio请求函数代替简单函数,该函数如下
//该函数获得每一个请求,把它们传递给bdev_xfer_request,然后使用end_that_request_first完成请求
//该函数可以处理部分高级队列和请求管理问题,实际执行一个请求的工作交给bdev_xfer_request完成
*****************************************************************************************************/
static void bdev_full_request( request_queue_t *q)
{
struct request *req;
int sectors_xferred;
struct my_bdev *dev = q->queuedata;
printk("mode 1: begin of full_request\n");
req = blk_fetch_request(q);
while( req != NULL) {
if( ! req->cmd_type == REQ_TYPE_FS ) {
printk( KERN_NOTICE "Skip non-fs request\n");
__blk_end_request_all(req, -EIO);
continue;
}
printk("mode 1 in a full_request:xfer_request some sectors\n");
//bdev_xfer_request实际执行一个请求
sectors_xferred = bdev_xfer_request(dev, req);
if (!__blk_end_request_cur(req, 0)) {
req = blk_fetch_request(q);
}
}
printk("mode 1: end of xfer_bio\n");
}
//=================================================================================================
//request_mode为2时,请求队列的处理
//没有请求队列,对bio逐个处理。
/*********************************************************************************************
*当不使用请求队列时,即设备处于“无队列”模式工作,request_mode为2,驱动程序将使用make_request。
*虽然从不拥有一个请求,但是依然提供一个请求队列,make_request函数的主要参数是bio结构,
*表示要被传输的一个或多个缓冲区。该函数功能完成:直接进行传输,或者把请求重定向给其他设备。
***********************************************************************************************/
static int bdev_make_request(request_queue_t *q, struct bio *bio)
{
struct my_bdev *dev = q->queuedata;
int status;
printk("mode 2: begin of make_request\n");
printk("mode 2 in a make_requset:xfer_bio some sectors\n");
//利用前面的方法通过bio进行传输
status = bdev_xfer_bio(dev, bio);
//由于没有request结构进行操作,因此函数需要能够调用bio_endio,告诉bio结构的创建者请求的完成情况
bio_endio(bio, status);
printk("mode 2: end of make_request\n");
return;
}
//=========================================================================================================
//块设备操作,为了模拟移动介质,bdev必须知道最后一个用户何时关闭了设备。
//驱动程序维护了一个用户计数,open和close的一个任务就是更新用户计数。
//注意新的linux内核的open、release和ioctl等函数的参数发生了变化(参照blkdev.h)
static int bdev_open(struct block_device *device, fmode_t mode)
{
printk("begin of open\n");
//当一个inode指向一个块设备时,i_bdev->bd_disk成员包含了指向相应gendisk结构的指针
//该指针可用于获得驱动程序内部的数据结构
struct my_bdev *dev = NULL;
dev = device->bd_disk->private_data;
//如果上次关闭时设置了“介质移除”定时器,而在30秒内,你又使用了该设备,就删除定时器
del_timer_sync(&dev->timer);
spin_lock(&dev->lock);
//查看是否更换了介质
if( ! dev->users )
check_disk_change(device);
dev->users++;
spin_unlock(&dev->lock);
printk("end of open\n");
return 0;
}
//减少用户计数,并启动介质移除定时器
static int bdev_release(struct gendisk *disk, fmode_t mode)
{
printk("begin of release\n");
struct my_bdev *dev = NULL;
dev = disk->private_data;
spin_lock(&dev->lock);
dev->users--;
if( ! dev->users ) {
dev->timer.expires = jiffies + INVALIDATE_DELAY; //30秒的定时器
//add_timer(&dev->timer);
}
spin_unlock(&dev->lock);
printk("end of release\n");
printk("===================end of release=============================\n");
return 0;
}
//调用media_changed函数以检查介质是否被改变
int bdev_media_changed(struct gendisk *gd)
{
printk("begin of media_changed\n");
struct my_bdev *dev = NULL;
dev = gd->private_data;
printk("end of media_changed\n");
return dev->media_change;
}
//在介质改变后将调用revalidate函数,为了让驱动程序能操作新的介质,该函数要完成所有必需的工作。
//调用此函数内核将试着重新读取分区表,在这里这个函数只是简单地重置了media_change的标志位,并清除内存空间以模拟插入一张新磁盘
int bdev_revalidate(struct gendisk *gd)
{
printk("begin of revalidate\n");
struct my_bdev *dev = NULL;
dev = gd->private_data;
if(dev->media_change) {
dev->media_change = 0;
memset(dev->data, 0, dev->size);
}
printk("end of revalidate\n");
return 0;
}
/*
* The "invalidate" function runs out of the device timer; it sets
* a flag to simulate the removal of the media.
*/
void bdev_invalidate(unsigned long ldev)
{
printk("begin of invalidate\n");
struct my_bdev *dev = NULL;
dev = (struct my_bdev *) ldev;
spin_lock(&dev->lock);
if (dev->users || !dev->data)
printk (KERN_WARNING "bdev: timer sanity check failed\n");
else
dev->media_change = 1;
spin_unlock(&dev->lock);
printk("end of invalidate\n");
}
//块设备驱动程序提供了ioctl函数执行设备的控制功能。
//ioctl函数在这里只处理了一个命令,对设备物理信息的查询请求
int bdev_ioctl(struct block_device *device, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
int ret = 0;
long size;
struct hd_geometry geo;
struct my_bdev *dev = NULL;
dev = device->bd_disk->private_data;
printk("begin of ioctl\n");
switch(cmd) {
case HDIO_GETGEO:
//获得物理信息:由于是虚拟设备,因此不得不提供一些虚拟的信息。
//因此这里声明有16个扇区,4个磁头,并且计算相应的柱面数。
//这里,我们设置数据开始的位置在第四扇区
size = dev->size * (hardsect_size / KERNEL_SECTOR_SIZE);
geo.cylinders = (size & ~0x3f) >> 6;
geo.heads = 4;
geo.sectors = 16;
geo.start = 4;
if( copy_to_user( (void __user *)arg, &geo, sizeof(geo) ) )
return -EFAULT;
printk("end of ioctl\n");
return 0;
}
printk("end of ioctl\n");
return -ENOTTY; //未知命令
}
//设备操作的数据结构
static struct block_device_operations bdev_ops = {
.owner = THIS_MODULE,
.open = bdev_open,
.release = bdev_release,
.media_changed = bdev_media_changed,
.revalidate_disk = bdev_revalidate,
.ioctl = bdev_ioctl,
};
//===============================================================================================
//初始化函数
//初始化my_bdev结构
static void setup_device(struct my_bdev *dev, int which)
{
printk("begin of setup_device\n");
printk("setup_device: memset\n");
//申请分配内存
memset(dev, 0, sizeof(struct my_bdev));
dev->size = nsectors * hardsect_size;
printk("setup_device: dev->data = vmalloc\n");
//kmalloc对应于kfree,可以分配连续的物理内存;
//vmalloc对应于vfree,分配连续的虚拟内存,但是物理上不一定连续。
dev->data = vmalloc(dev->size);//设备的数据空间
if(dev->data == NULL){
printk(KERN_NOTICE "vmalloc failure\n");
return ;
}
spin_lock_init(&dev->lock);//初始化自旋锁
init_timer(&dev->timer);
dev->timer.data = (unsigned long)dev;
dev->timer.function = bdev_invalidate;
printk("setup_device: choose the request_mode\n");
//I/O队列使用的请求模式
switch(request_mode) {
case RM_NOQUEUE:
dev->queue = blk_alloc_queue(GFP_KERNEL);
if(dev->queue == NULL){
goto out_vfree;
}
blk_queue_make_request(dev->queue, bdev_make_request);
break;
case RM_FULL:
dev->queue = blk_init_queue(bdev_full_request, &dev->lock);
if(dev->queue == NULL){
goto out_vfree;
}
break;
default:
printk(KERN_NOTICE "Bad request mode %d, using simple\n",request_mode);
case RM_SIMPLE:
dev->queue = blk_init_queue(bdev_request, &dev->lock);
if(dev->queue == NULL){
goto out_vfree;
}
break;
}
blk_queue_logical_block_size(dev->queue, hardsect_size);//设置扇区大小
dev->queue->queuedata = dev;
printk("setup_device: dev->gd = alloc_disk\n");
//拥有了设备内存和请求序列,就可以分配、初始化及安装相应的gendisk结构了
//BDEV_MINORS是每个bdev设备所支持的次设备号的数量
dev->gd = alloc_disk(BDEV_MINORS);
if(! dev->gd){
printk(KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = bdev_major;
dev->gd->first_minor = which * BDEV_MINORS;
dev->gd->fops = &bdev_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf(dev->gd->disk_name, 32, "my_bdev%c", which + 'a');// /dev中显示的名字
//每个请求的大小都是扇区大小的整数倍,内核总是认为扇区大小是512字节,因此必须进行转换
set_capacity(dev->gd, nsectors * (hardsect_size / KERNEL_SECTOR_SIZE));
printk("setup_device: add_disk\n");
//结束设置过程,add_disk一定要放在初始化设备的最后一步
add_disk(dev->gd);
printk("end of setup_device\n");
return;
out_vfree:
if(dev->data)
vfree(dev->data);
}
static int __init bdev_init(void)
{
int i;
printk("begin of bdev_init\n");
//注册,动态分配主设备号
bdev_major = register_blkdev(bdev_major, "my_bdev");
if(bdev_major <= 0){
printk(KERN_WARNING "bdev: unable to get major number\n");
return -EBUSY;
}
printk("bdev_init: register\n");
printk("bdev_init: Devices = kmalloc\n");
Devices = kmalloc(ndevices * sizeof(struct my_bdev), GFP_KERNEL);
if(Devices == NULL){
goto out_unregister;
}
printk("bdev_init: setup_device\n");
for(i = 0; i < ndevices; ++i){
setup_device(Devices + i, i);//初始化my_bdev结构
}
printk("end of bdev_init\n");
return 0;
out_unregister:
printk(KERN_NOTICE "kmalloc failure.\n");
unregister_blkdev(bdev_major, "sbd");
return -ENOMEM;
}
static void __exit bdev_exit(void)
{
int i;
printk("begin of bdev_exit\n");
for(i = 0; i < ndevices; ++i){
struct my_bdev *dev = Devices + i;
del_timer_sync(&dev->timer);
if(dev->gd){
del_gendisk(dev->gd);
put_disk(dev->gd);
printk("bdev_exit: del_gendisk: release gendisk success\n");
}
if(dev->queue){
if(request_mode == RM_NOQUEUE){
blk_put_queue(dev->queue);
}else{
blk_cleanup_queue(dev->queue);//删除请求队列
}
printk("bdev_exit: blk_cleanup_queue: rmmod success\n");
}
if(dev->data){
vfree(dev->data);
printk("bdev_exit: release disk data success\n");
}
}
unregister_blkdev(bdev_major, "my_bdev");
printk("bdev_exit: unregister success\n");
kfree(Devices);
printk("end of bdev_exit\n");
}
MODULE_AUTHOR("Fang Xieyun");
MODULE_LICENSE("GSL");
module_init(bdev_init);
module_exit(bdev_exit);