#define SBULL_MINORS 16 /* 每个sbull设备所支持的次设备号的数量 */
#define KERNEL_SECTOR_SIZE 512 // 本地定义的常量,使用该常量进行内核512字节到实际
// 扇区大小的转换
*HZ
块设备的核心数据结构(the internal representation of our device):
struct sbull_dev{
int size; /* Device size in sectors */
u8 *data; /* The data array */
short users; /* How many users */
short media_change; /* Flag a media change? */
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
struct timer_list timer; /* For simulated media changes */
}
static struct sbull_dev *Devices = NULL;
; /* 块设备号,0就自动分配*/
;
; /* 硬件扇区数目 */
; /* 硬件扇区大小 */
/* The different "request modes " we can use. */
enum{
,
,
,
};
static int request_mode = RM_FULL;
块设备驱动都是从 init 函数开始的,所以从这里开始分析
static int __init sbull_init(void)
{
int i;
sbull_major = register_blkdev(sbull_major,"sbull");// 注册块设备,第一个是设备号,0为动态
) //分配,第二个是设备名
{
printk(KERN_WARNING "sbull:unable to get major number\n");
return -EBUSY;
}
/* 为块核心数据结构 sbull_dev 分配空间*/
Devices = kmalloc(ndevices *sizeof(struct sbull_dev),GFP_KERNEL);
if(Devices == NULL)
goto out_unregister;
;i < ndevices;i++) /* 初始化 sbull_dev 核心数据结构 */
setup_device(Devices + i,i);
return 0;
out_unregister:
unregister_blkdev(sbull_major,"sbd");
return -ENOMEM;
}
/*初始化 sbull_dev 数据结构的具体实现*/
static void setup_device(struct sbull_dev *dev,int which)
{
memset(,sizeof(struct sbull_dev)); /* 初始化 dev 所指内容为0*/
dev->size = nsectors * hardsect_size;
dev->data = vmalloc(dev->size);
if(dev->data == NULL)
{
printk(KERN_NOTICE "vmalloc failure.\n");
return ;
}
spin_lock_init(&dev->lock); /* 初始化自旋锁*/
/* 在分配请求队列前要先初始化自旋锁*/
/* The timer which "invalidates the device给内核定时器初始化 "*/
init_timer(&dev->timer); /*初始化定时器,实际将结构中的list成员初始化为空*/
dev->timer.data = (unsigned long)dev; /*被用作function函数的调用参数*/
dev->timer.function = sbull_invalidate; /* 当定时器到期时,就执行function指定的函数*/
/*
* The I/O queue, depending on whether we are using our own
* make_request function or not.
*/
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,sbull_make_request); /*绑定"制造请求"函数 */
break;
case RM_FULL:
dev->queue = blk_init_queue(sbull_full_request,&dev->lock); /*请求队列初始化*/
if(dev->queue == NULL)
goto out_vfree;
break;
case RM_SIMPLE:
dev->queue = blk_init_queue(sbull_request,&dev->lock); /*请求队列初始化*/
if(dev->queue == NULL)
goto out_vfree;
break;
default:
printk(KERN_NOTICE "Bad request mode %d,using simple\n",request_mode);
}
blk_queue_hardsect_size(dev->queue,hardsect_size); /* 硬件扇区尺寸设置 */
dev->queue->queuedata = dev;
dev->gd = alloc_disk(SBULL_MINORS); /* 动态分配 gendisk 结构体*/
if(!dev->gd)
{
printk(KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
/* 初始化 gendisk */
dev->gd->major = sbull_major; /* 主设备号 */
dev->gd->first_minor = which * SBULL_MINORS; /* 第一个次设备号 */
dev->gd->fops = &sbull_ops; /* 块设备操作结构体 */
dev->gd->queue = dev->queue; /* 请求队列 */
dev->gd->private_data = dev; /* 私有数据 */
,"sbull%c",which + 'a');
/* 每个请求的大小都是扇区大小的整数倍,内核总是认为扇区大小是512字节,因此必须进行转换*/
set_capacity(dev->gd,nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd); /* 完成以上初始化后,调用 add_disk 函数来注册这个磁盘设备 */
return ;
out_vfree:
if(dev->data)
vfree(dev->data); /* 释放用 vmalloc 申请的不连续空间*/
}
下面对上述函数初始化中涉及的函数进行分析
/*
* The device operations structure.
*/
static struct block_device_operations sbull_ops = {
.owner = THIS_MODULE,
.open = sbull_open,
.release = sbull_release,
.media_changed = sbull_media_changed, // 媒介改变
.revalidate_disk = sbull_revalidate, // 是介质有效
.ioctl = sbull_ioctl,
.getgeo = sbull_getgeo, // 得到几何信息
};
/*
* The "invalidte"function runs out of the device timer;it sets a flag to
* simulate the removal of the media.
*/
void sbull_invalidate(unsigned long ldev)
{
struct sbull_dev *dev = (struct sbull_dev *)ldev;
spin_lock(&dev->lock); /* 上锁 */
if(dev->users || !dev->data)
printk(KERN_WARNING "sbull:timer sanity check failed\n");
else
;
spin_unlock(&dev->lock); /* 解锁 */
}
/*
* The direct make request version.
*/
static int sbull_make_request(request_queue_t *q,struct bio *bio)
{
struct sbull_dev *dev = q->queuedata;
int status;
status = sbull_xfer_bio(dev,bio);
bio_endio(bio,bio->bi_size,status); // bio_endio()函数通知处理结束
return 0;
}
/*
* Smarter request function that "handles clustering".
*/
static void sbull_full_request(request_queue_t *q)
{
struct request *req;
int sectors_xferred;
struct sbull_dev *dev = q->queuedata;
/* 遍历每个请求 */
while((req = elv_next_request(q)) != NULL) // elv_next_request 获得队列中第
// 一个未完成的请求
{
if(!blk_fs_request(req)) //如果不是文件系统请求——移动块设备数据请求
{
printk(KERN_NOTICE "Skip non-fs request\n");
); 通知请求处理失败,为表示请求成功
continue;
}
sectors_xferred = sbull_xfer_request(dev,req); //调用请求处理函数
//下面函数告知块设备层已经完成 sectors_xferred(count) 个扇区的传送, end_that_request_first()
//的返回值是一标志,返回 0 表示所有的扇区已经传送并完成请求
,sectors_xferred))
{
blkdev_dequeue_request(req); //从队列中清除这个请求
//请求传给下面函数,通知所有正在等待这个请求完成的对象请求已经完成并回收这个请求结构体
end_that_request_last(req,sectors_xferred);
}
}
}
/*
* Transfer a full request.请求处理函数
*/
static int sbull_xfer_request(struct sbull_dev *dev,struct request *req)
{
struct bio *bio;
;
rq_for_each_bio(bio,req)//此宏遍历请求中的每个bio,传递用于sbull_xfer_bio()传输的指针
{
sbull_xfer_bio(dev,bio); //调用 bio 处理函数
nsect += bio->bi_size/KERNEL_SECTOR_SIZE; //传递的字节数/扇区大小等于扇区数
}
return nsect;
}
/*
* Transfer a single BIO. bio处理函数
*/
static int sbull_xfer_bio(struct sbull_dev *dev,struct bio *bio)
{
int i;
struct bio_vec *bvec; //定义实际的 vec 列表
sector_t sector = bio->bi_sector; //定义要传输的第一个扇区
//下面的宏遍历bio的每一段,获得一个内核虚拟地址来存取缓冲
bio_for_each_segment(bvec,bio,i)
{
char *buffer = __bio_kmap_atomic(bio,i,KM_USER0);//通过kmap_atomic()函数获得返
//回bio的第i个缓冲区的虚拟地址
sbull_transfer(dev,
sector, // 开始扇区的索引号
bio_cur_sectors(bio), // 需要传输的扇区数
buffer, // 传输数据的缓冲区指针
bio_data_dir(bio) // 传输方向,0表述从设备读,非0从设备写
== WRITE);
sector += bio_cur_sectors(bio); //返回扇区数
__bio_kunmap_atomic(bio,KM_USER0); //返回由 __bio_kmap_atomic()获得的内核虚拟地址
}
return 0;
}
/*
* Handle an I/O request. 处理 I/O 拷贝数据的 函数
*/
static void sbull_transfer(struct sbull_dev *dev,unsigned long sector,
unsigned long nsect,char *buffer,int write)
{
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);
else
memcpy(buffer,dev->data + offset,nbytes);
}
/*
* The simple form of the request function.
*/
static void sbull_request(request_queue_t *q)
{
struct request *req; //定义请求结构体
while((req = elv_next_request(q)) != NULL)//elv_next_request()获得队列中第一个未完成请求
{
struct sbull_dev *dev = req->rq_disk->private_data;
if(!blk_fs_request(req)) //判断是否为文件系统请求
{
printk(KERN_NOTICE "Skip non-fs request\n");
); //通知请求处理失败,0为失败,1为成功
continue;
}
sbull_transfer(dev,req->sector,req->current_nr_sectors,
req->buffer,rq_data_dir(req));
);
}
}
/*
* open()函数
*/
static int sbull_open(struct inode *inode,struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
del_timer_sync(&dev->timer); //去掉"介质移除"定时器
filp->private_data = dev;
spin_lock(&dev->lock);
if(!dev->users)
check_disk_change(inode->i_bdev);
dev->users++; // 使用计数加 1
spin_unlock(&dev->lock);
return 0;
}
/*
* release 关闭函数,减少用户基数,并启用介质移除定时器
*/
static int sbull_release(struct inode *inode,struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
spin_lock(&dev->lock);
dev->users--; // 使用计数减 1
if(!dev->users)
{
//30秒的定时器,如果这个时段内设备没有被打开则移除设备
dev->timer.expires = jiffies + INVALIDATE_DELAY;
add_timer(&dev->timer); //将定时器添加到定时器队列中
}
spin_unlock(&dev->lock);
return 0;
}
/*
* Look for a (simulated) media change.
*/
int sbull_media_changed(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
return dev->media_change;
}
/*
* Revalidate.we do not take the lock here,for fear of deadlocking with open.
* That needs to be reevaluated.
* 调用此函数内核将试着重新读取分区表,在这里这个函数这是简单的重置 media_change 的标志位,并
* 清除内存空间以模拟插入一张磁盘
*/
int sbull_revalidate(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
if(dev->media_change)
{
;
,dev->size);
}
return 0;
}
/*
* The ioctl() implementation.
* 在这里只处理了一个命令,对设备物理信息的查询请求,这里由于是虚拟设备,因此只提供了一虚
* 拟信息
*/
int sbull_ioctl(struct inode *inode,struct file *filp,
unsigned int cmd,unsigned long arg)
{
long size;
struct hd_geometry geo;
struct sbull_dev *dev = filp->private_data; // 通过 file->private 获得设备结构体
switch(cmd)
{
case HDIO_GETGEO:
/*
* Get geometry: since we are a virtual device, we have to make
* up something plausible. So we claim 16 sectors, four heads,
* and calculate the corresponding number of cylinders. We set the
* start of data at sector four.
*/
size = dev->size *(hardsect_size/KERNEL_SECTOR_SIZE);
/* 获得几何信息 */
;
;
;
;
if(copy_to_user((void __user *)arg,&geo,sizeof(geo)))
return -EFAULT;
return 0;
}
return -ENOTTY; // 不知道的命令
}
static int sbull_getgeo(struct block_device *bdev,struct hd_geometry *geo)
{
unsigned long size;
struct sbull_dev *pdev = bdev->bd_disk->private_data;
size = pdev->size;
;
;
;
;
return 0;
}
/*
* 卸载模块函数
*/
static void sbull_exit(void)
{
int i;
; i < ndevices;i++)
{
struct sbull_dev *dev = Devices + i;
del_timer_sync(&dev->timer); /* 去掉 "介质移除" 定时器*/
if(dev->gd)
{
del_gendisk(dev->gd); /* 释放 gendisk 结构体*/
put_disk(dev->gd); /* 释放对 gendisk 的引用 */
}
if(dev->queue)
{
if(request_mode == RM_NOQUEUE)
blk_put_queue(dev->queue);
else
blk_cleanup_queue(dev->queue); // 清除请求队列
}
if(dev->data)
vfree(dev->data);
}
unregister_blkdev(sbull_major,"sbull");
kfree(Devices);
}
module_init(sbull_init);
module_exit(sbull_exit);
- Linux中块设备驱动程序分析
基于<Linux设备驱动程序>书中的sbull程序以对Linux块设备驱动总结分析. 開始之前先来了解这个块设备中的核心数据结构: struct sbull_dev { i ...
- 嵌入式Linux驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入
字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申 ...
- 简单linux块设备驱动程序
本文代码参考<LINUX设备驱动程序>第十六章 块设备驱动程序 本文中的“块设备”是一段大小为PAGE_SIZE的内存空间(两个扇区,每个扇区512字节) 功能:向块设备中输入内容,从块设 ...
- linux系统下块设备驱动程序
顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例 如磁盘通常为一个sector,而字符设备的基本单元为字 ...
- 《Linux Device Drivers》第十六章 块设备驱动程序——note
基本介绍 块设备驱动程序通过主传动固定大小数据的随机访问设备 Linux核心Visual块设备作为基本设备和不同的字符设备类型 Linux块设备驱动程序接口,使块设备最大限度地发挥其效用.一个问题 一 ...
- 深入理解Linux内核-块设备驱动程序
扇区: 1.硬盘控制器将磁盘看成一大组扇区2.扇区就是一组相邻字节3.扇区按照惯例大小设置位512字节4.存放在块设备中的数据是通过它们在磁盘上的位置来标识,即首个扇区的下标和扇区的数目.5.扇区是硬 ...
- (linux)块设备驱动程序
1.4.1 Linux块设备驱动程序原理(1) 顾名思义,块设备驱动程序就是支持以块的方式进行读写的设备.块设备和字符设备最大的区别在于读写数据的基本单元不同.块设备读写数据的基本单元为块,例如 ...
- Linux 0.11源码阅读笔记-块设备驱动程序
块设备驱动程序 块设备驱动程序负责实现对块设备数据的读写功能.内核代码统一使用缓冲块间接和块设备(如磁盘)交换数据,缓冲区数据通过块设备驱动程序和块设备交换数据. 块设备的管理 块设备表 内核通过一张 ...
- 【DSP开发】【Linux开发】Linux下PCI设备驱动程序开发
PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以Inte ...
随机推荐
- qemu-img命令
qemu-img是QEMU的磁盘管理工具,在qemu-kvm源码编译后就会默认编译好qemu-img这个二进制文件.qemu-img也是QEMU/KVM使用过程中一个比较重要的工具,本节对其用法和实践 ...
- MySQL 存储 utf8mb4
1.如果是阿里云数据库 a.控制台->修改参数character_set_server参数为UTF8mb4 b.设置库的字符集为UTF8mb4 2.如果是自己mysql服务器 [client] ...
- NYOJ 203 三国志
三国志 时间限制:3000 ms | 内存限制:65535 KB 难度:5 描述 <三国志>是一款很经典的经营策略类游戏.我们的小白同学是这款游戏的忠实玩家.现在他把游戏简化一下, ...
- 70.打印所有Spring boot载入的bean【从零开始学Spring Boot】
[从零开始学习Spirng Boot-常见异常汇总] 问题的提出: 我们在开发过程当中,我们可能会碰到这样的问题:No qualifying bean 就是我们定义的bean无法进行注入,那到底是什 ...
- oracle 恢复中的switch datafile all是什么意思
使用rman进行恢复时,如果使用了set name修改文件路径,那么恢复后,控制文件里面的信息是没有修改该的,如果要同步控制文件的信息那么就需要使用 switch datafile allall这个可 ...
- 机器学习基础-Logistic回归1
利用Logistic回归进行分类的主要思想是:根据现有数据对分类边界线建立回归公式,以此进行分类. 训练分类器时的做法就是寻找最佳拟合参数,使用的时最优化算法. 优点:计算代价不高,利于理解和实现. ...
- POJ 2112: Optimal Milking【二分,网络流】
题目大意:K台挤奶机,C个奶牛,每台挤奶器可以供M头牛使用,给出奶牛和和机器间的距离矩阵,求所有奶牛走最大距离的最小值 思路:最大距离的最小值,明显提示二分,将最小距离二分之后问题转化成为:K台挤奶机 ...
- POJ1159:Palindrome【dp】
题目大意:给出一个字符串,问至少添加多少个字符才能使它成为回文串? 思路:很明显的方程是:dp[i][j]=min{dp[i+1][j],dp[i][j-1],dp[i+1][j-1](str[i]= ...
- jsonp跨域请求实现示例
网上看了很多关于jsonp的资料,发现在本机运行后实现不了,有的是有错漏,有的是说的比较含糊,接合自己的情况,整了一个可运行的示例: 前言: ajax请求地址:http://192.168.1.102 ...
- 牛客网 牛客网暑期ACM多校训练营(第三场)E KMP
链接:https://www.nowcoder.com/acm/contest/141/E 题目描述 Eddy likes to play with string which is a sequenc ...