Linux块设备驱动_WDS
推荐书:《Linux内核源代码情景分析》
1.字符设备驱动和使用中等待某一事件的方法
①查询方式
②休眠唤醒,但是这种没有超时时间
③poll机制,在休眠唤醒基础上加一个超时时间
④异步通知,异步通知实际上就是发信号
⑤输入子系统,这样比较通用
2.块设备相对于字符设备驱动逻辑的变化
①对于硬盘对读写的优化
假如要读磁头0的扇区0,然后写磁头1的扇区0,然后读磁头0的扇区1,若像字符设备那样,就会机械山跳转2次,效率低。
优化:
先不执行,放入队列,优化后再执行,这里的优化是指调整顺序。
②对于flash
假如要写同一个块的扇区0,然后再写扇区1,若是字符设备的做法,写扇区0时需要先把整块读取出来,然后修改此块中
扇区0的数据,然后烧写整个这个块。写扇区1时也是需要先把整块读取出来,然后修改此块中扇区1的数据,然后烧写整个这个块。
优化:
先不执行,放入队列,优化后再执行,这里的优化是指合并相同块的写请求。
所以块设备不能向字符设备一样直接提供读写函数,而是需要先放入队列之中,优化后再执行。
3.块设备驱动程序框架
App:open, read, write "1.txt"
----------------------------------------------- 文件的读写
文件系统:vfat,tfat, ext2, ext3, yaffs2, jffs2 作用:把文件的读写转换为扇区的读写
------------统一的入口:ll_rw_block()---------- 扇区的读写
1.把“读写”放入队列中(可能这一步就有优化)
2.调用队列的处理函数(优化/调整顺序/合并)
块设备驱动程序: 主要是读写块设备函数的实现和属性的提供
-----------------------------------------------
硬件:硬盘,flash,eMMC
4.分析ll_rw_block()
ll_rw_block //fs/buffer.c, 位于fs下,说明是所有文件系统的一个通用的.c文件
for (i = ; i < nr; i++)
submit_bh(op, op_flags, bh); //fs/buffer.c
struct bio *bio;
通用的构造请求,使用bio来构造请求
submit_bio(bio);
generic_make_request(bio);
struct request_queue *q = bio->bi_disk->queue; //block/blk-core.c 找对队列
ret = q->make_request_fn(q, bio); //调用队列的构造请求函数,默认的设置函数是:make_request_fn
q->make_request_fn在blk_queue_make_request block/Blk-settings.c中被赋值,
blk_queue_make_request在blk_init_allocated_queue block/blk-core.c中被赋值为blk_queue_bio,即make_request_fn=blk_queue_bio
blk_queue_bio //默认是这个
elv_merge(q, &req, bio) //block/blk-core.c 以电梯调用算法尝试合并这个请求
如果合并不成功,调用get_request使用bio构造请求,将请求放入队列中
get_request(q, bio->bi_opf, bio, GFP_NOIO);
blk_init_request_from_bio(req, bio);
blk_flush_plug_list(plug, false);
if (q) queue_unplugged(q, depth, from_schedule);
__blk_run_queue
q->request_fn(q); //调用队列的处理函数,就是块设备驱动实际的读写函数
5.写块设备驱动
1.分配构造struct request_queue,用于提供读写能力
2.设备描述,提供属性
......
====>内核指定了一个结构体:gendisk
驱动框架:
1.分配gendisk: alloc_disk
2.设置
2.1 分配/设置struct request_queue结构,blk_init_queue
2.2 设置gendisk其它信息
3.注册gendisk
6.块设备的操作是以扇区为单位的,内核机制决定的,就算是使用内存模拟的块设备也不例外。
7.对于一块全0的内存模拟的磁盘,未格式化会报"unknow partation table",因为其分区表为空
8.测试
cat /proc/devices 查看设备号
格式化磁盘:# mkfs /dev/ramblock eg: mkdosfs /dev/ramdisk
挂载:# mount /dev/ramblock /tmp
之后就可以通过访问/tmp目录操作磁盘了
9.创建磁盘映像
# cat /dev/ramblock > /ramblock.bin 然后再格式化磁盘再echo进去应该也是可以的。
在Ubuntu里面:# sudo mount -o loop ramblock.bin /mnt loop选项可以把一个普通文件当作块设备进行挂载。loop把它当作一个回环设备。
10.把对内存的操作打印出来可以对比App的读写和实际的IO操作发生的时机的区别。cat挂载目录中刚才已经操作过的文件,发现并没有去读,而是使用缓存的。echo到挂载目录的文件中也是没有调用读写函数,而是先刷到缓冲中。
11.分区
# ls /dev/ramblock* -l 次设备号是0表示整个磁盘,不是分区。起始是0应该是first_minor=0决定的。
# fdisk /dev/ramblock m n p 1(此时显示1-32 sylinder就是驱动中在ramblock_getgeo()中配置的) 1 5,再创建一个分区n p 2 6 32 w
(w表示将配置写到分区表中,分区表就是第一个扇区)
# ls /dev/ramblock* -l 次设备号是0表示整个磁盘,次设备号是1表示第一个主分区,次设备号是2表示第二个主分区。
此时也可以分别格式化每一个主分区:
eg: # mkdosfs /dev/ramblock1
eg: # mkdosfs /dev/ramblock2
分别挂载:
eg: # mount /dev/ramblock1 /mnt
eg: # mount /dev/ramblock2 /tmp
使用# fdisk /dev/ramdisk 报错: Unknow value(s) for: Cylinder 不知道柱面数,fdisk是个老工具了,使用它需要驱动告诉它柱面数信
息(目前很多块设备都不使用这个储存方式了)
12.内存模拟块设备驱动代码
/*
参考: drivers\block\z2ram.c usage:
mkfs Formatted file system
mount fdisk Create partition
*/ #include <linux/major.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/hdreg.h> #include <asm/setup.h>
#include <asm/pgtable.h> static struct gendisk *ramblock_disk;
static struct request_queue *ramblock_queue; static int major; static DEFINE_SPINLOCK(ramblock_lock); #define RAMBLOCK_SIZE (1024*1024) /*使用1M内存来模拟磁盘*/
static unsigned char *ramblock_buf; /*这些信息在使用fdisk工具分区的时候会用到*/
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量 = heads * cylinders * sectors * 512 */
geo->heads = ;
geo->cylinders = ;
geo->sectors = RAMBLOCK_SIZE///;
return ;
} static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
}; #if 0
/*目前内核中elv_next_request已经不存在了*/
static void do_ramblock_request(struct request_queue * q)
{
static int r_cnt = ;
static int w_cnt = ;
struct request *req; //printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
unsigned long offset = req->sector * ; /* 目的/源: */
// req->buffer /* 长度: */
unsigned long len = req->current_nr_sectors * ; if (rq_data_dir(req) == READ) /*也就是: req->cmd_flags & 1*/
{
printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf+offset, len); /*这里是使用memcpy来模拟复杂的IO操作*/
}
else
{
/*加了这个打印,可以看出来当向设备进行写的时候没有立即调用,而是过来一小会才调用的,
与算法有关,先放到队列中,然后才执行。 # cp /etc/fstab /tmp(挂载目录),发现过来一会也没有调用这个函数
# sync 立即就打印了 # cp /etc/fstab /tmp 发现没有立即写
# umount /tmp/ 发现立即打印了
*/
printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, req->buffer, len);
} end_request(req, );
}
}
#endif /* for 4.14 kernel */
static void do_ramblock_request(struct request_queue *q)
{
struct request *req;
char *bio_buffer;
static int r_cnt = ;
static int w_cnt = ; req = blk_fetch_request(q); while (req) {
unsigned long offset = blk_rq_pos(req) << ; /*return rq->__sector; the current sector,右移9也即乘以512*/
unsigned long len = blk_rq_cur_bytes(req); /*bytes left in the current segment*/
blk_status_t err = BLK_STS_OK; if (offset + len > RAMBLOCK_SIZE) {
printk("do_ramblock_request read execteed\n");
len = RAMBLOCK_SIZE-offset;
} bio_buffer = bio_data(req->bio); /*拷贝到这里面返回给用户*/
if (rq_data_dir(req) == READ) {
printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(bio_buffer, ramblock_buf+offset, len);
} else {
printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, bio_buffer, len);
} if (!__blk_end_request_cur(req, err)) /*must with it,else will blocking when ismod*/
req = blk_fetch_request(q);
}
} static int ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(); /* 次设备号个数: 分区个数+1 */ /* 2. 设置 */
/* 2.1 分配/设置队列: 提供读写能力 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); /*arg1: 执行实际读写io操作的函数*/
ramblock_disk->queue = ramblock_queue; /* 2.2 设置其他属性: 比如容量 */
major = register_blkdev(, "ramblock"); /* cat /proc/devices ==> 254 ramblock*/
ramblock_disk->major = major;
ramblock_disk->first_minor = ; /*修改它试试*/
sprintf(ramblock_disk->disk_name, "ramblock_name"); /* /dev/ramblock_name */
ramblock_disk->fops = &ramblock_fops;
set_capacity(ramblock_disk, RAMBLOCK_SIZE / ); /*设置磁盘容量,是以扇区为单位的*/ /* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /*这个设备全为0,分区表为空,所以在insmod驱动的时候会报"unknow partation table",需要格式化*/ /* 4. 注册 */
add_disk(ramblock_disk); return ;
} static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue); kfree(ramblock_buf);
} module_init(ramblock_init);
module_exit(ramblock_exit); MODULE_LICENSE("GPL");
测试ok的。根据do_z2_request(), 就算是这个函数是void类型的也能向外部传递操作执行的状态。
附:
1. 给SD卡添加label:
给SD卡添加label:
$ sudo fatlabel /dev/sdc1 sfl_sd
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sdc1 .3G .0K .3G % /media/ubuntu/sfl_sd
Linux块设备驱动_WDS的更多相关文章
- linux块设备驱动之实例
1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major = register_blkdev(sbull_major, "sbull&quo ...
- Linux块设备驱动详解
<机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...
- Linux 块设备驱动 (一)
1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...
- Linux块设备驱动(一) _驱动模型
块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...
- Linux块设备驱动(二) _MTD驱动及其用户空间编程
MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...
- linux块设备驱动
块设备驱动程序<1>.块设备和字符设备的区别1.读取数据的单元不同,块设备读写数据的基本单元是块,字符设备的基本单元是字节.2.块设备可以随机访问,字符设备只能顺序访问. 块设备的访问:当 ...
- linux 块设备驱动 (三)块设备驱动开发
一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...
- linux块设备驱动(一)——块设备概念介绍
本文来源于: 1. http://blog.csdn.net/jianchi88/article/details/7212370 2. http://blog.chinaunix.net/uid-27 ...
- linux块设备驱动---程序设计(转)
块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigned i ...
随机推荐
- [Java学习] 再谈Java包
在Java中,为了组织代码的方便,可以将功能相似的类放到一个文件夹内,这个文件夹,就叫做包. 包不但可以包含类,还可以包含接口和其他的包. 目录以"\"来表示层级关系,例如 E:\ ...
- 20161210xlVBA一行数据转为四行
Sub NextSeven_CodeFrame() '应用程序设置 Application.ScreenUpdating = False Application.DisplayAlerts = Fal ...
- 大年三十。让字母在屏幕上奔跑:(sleep , system"clear")
system "clear",ruby清屏(osk系统上,window上用system "cls"). https://stackoverflow.com/qu ...
- Confluence 6 嵌套用户组的备注
潜在的性能影响.启用嵌套用户组可能会减慢用户查找的速度. 在 LDAP 中定义嵌套用户组.在 LDAP 中,一个嵌套用户组是 DN (Distinguished Name)的子用户组,这个字用户组将会 ...
- 利用nodeJs anywhere搭建本地服务器环境
1.npm install anywhere -g 如果是mac系统会提示你权限不够,需要在代码前加上 sudo获取管理员权限.即sudo npm install anywhere -g. 2.安装完 ...
- windows下进程与线程剖析
进程与线程的解析 进程:一个正在运行的程序的实例,由两部分组成: 1.一个内核对象,操作系统用它来管理进程.内核对象也是系统保存进程统计信息的地方. 2.一个地址空间,其中包含所有可执行文件或DLL模 ...
- dp练习(10)——拦截导弹
1044 拦截导弹 1999年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Descripti ...
- 对LOV中的值进行强制验证
当LOV之中只有一个LovMap返回当前ITEM时,修改了LOV输入框的值,会弹出验证窗口,若此时忽略此窗口,在进行下一步的时候不会去验证此LOV中的值是否一定在可选列表中. 解决方式, 1.在页面加 ...
- OC 内存管理之手动内存管理MRC
一.基本原理 1.什么是内存管理 内存管理的重要性: 移动设备的内存极其有限,每个app所能占用的内存是有限制的 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间. ...
- 时间序列预测——深度好文,ARIMA是最难用的(数据预处理过程不适合工业应用),线性回归模型简单适用,预测趋势很不错,xgboost的话,不太适合趋势预测,如果数据平稳也可以使用。
补充:https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-15-276 如果用arima的话,还不如使用随机森 ...