stackbd:在一个块设备上堆叠另一个块设备
stackbd 是一个虚拟的块设备,它作为另一个块设备的前端,如 USB 闪存盘或循环设备。它将I/O请求传递给底层设备,同时它打印请求信息用于调试。它还有可能修改请求。
堆叠块设备(stackbd)是基于 Linux 设备映射器的代码,它是 Linux 内核中的一个块设备,RedHat 支持,用于创建逻辑卷,或者说,修改 I/O 请求的地址值和目标设备。
stackbd,暂时不修改请求。它的作用是作为一个嗅探器,对每一个请求,都会打印出它的读/写状态,块地址,页数,以及总的字节大小。
除了调试目的外,这个简单的设备是学习Linux内核中块设备编程的好方法。

1. 下载源代码并构建
首先,最好在虚拟机上工作,因为内核出错会导致操作系统崩溃,虚拟机的重启速度要快得多。
1.1 下载源代码
从GitHub下载源代码(或以Git或SVN检出)代码:
git clone https://github.com/OrenKishon/stackbd.git
1.2 修改错误
实验环境如下:
操作系统:Ubuntu 14.04
内核版本:4.4.0-148-generic
下载后,由于内核版本问题,直接编译会报错。根据错误提示直接定位报错位置:
vim ~/stackbd/module/stackbd.c +65
修改为如下内容:
trace_block_bio_remap(bdev_get_queue(stackbd.bdev_raw), bio,
bio->bi_bdev->bd_dev, bio->bi_iter.bi_sector);
vim ~/stackbd/module/stackbd.c +106
printk("stackbd: make request %-5s block %-12llu #pages %-4hu total-size "
"%-10u\n", bio_data_dir(bio) == WRITE ? "write" : "read",
(long long )bio->bi_iter.bi_sector, bio->bi_vcnt, bio->bi_iter.bi_size);
vim ~/stackbd/module/stackbd.c +139
struct block_device *bdev_raw = lookup_bdev(dev_path, 0);
vim ~/stackbd/module/stackbd.c +173
printk("stackbd: Device real capacity: %llu\n", (long long)stackbd.capacity);
vim ~/stackbd/module/stackbd.c +264
blk_queue_make_request(stackbd.queue, (void *)stackbd_make_request);
如果是其它内核版本,报错可能不一样,需自行修改。
1.3 编译
在 "module" 目录中构建内核模块:
cd ~/stackbd/module
make
make -C /usr/src/linux-headers-4.4.0-148-generic SUBDIRS=/home/abin/stackbd/module modules
make[1]: Entering directory `/usr/src/linux-headers-4.4.0-148-generic'
CC [M] /home/abin/stackbd/module/stackbd.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/abin/stackbd/module/stackbd.mod.o
LD [M] /home/abin/stackbd/module/stackbd.ko
make[1]: Leaving directory `/usr/src/linux-headers-4.4.0-148-generic'
在 "util" 目录中构建用户端工具:
cd ~/stackbd/util
make
cc -c -o stackbd_util.o stackbd_util.c
gcc -o stackbd_util stackbd_util.c
2. 创建用于测试的回路设备
我们需要一种设备来充当基础的“真实”设备。最简单的方法是基于文件系统中的文件创建循环设备。
创建一个100 MB的文件 disk_file,它将用作设备存储:
cd ~/stackbd
dd if=/dev/urandom of=disk_file bs=1024 count=100000
在此文件上设置循环设备 / dev / loop0:
sudo losetup /dev/loop0 disk_file
确认已创建大小为200,000(512字节块)的设备:
sudo blockdev --getsize /dev/loop0
200000
注意,循环设备在重启后不会持久化,所以一旦创建了文件disk_file,重启后只需要重复执行losetup命令。
3. 跟踪内核调试打印
stackbd 模块使用 printk 命令打印调试信息,所以我们需要通过跟踪 syslog 文件来跟踪它们。新开一个 shell 窗口,输入如下命令:
tail -f /var/log/syslog
这篇文章中的以下所有命令都应该在这个文件中产生调试信息。
4. 初始化堆叠设备
将 stackbd.ko 模块加载进内核,该操作只会创建新设备 / dev / stackbd0,而不会将其与另一个设备关联:
cd ~/stackbd/module
sudo insmod ./stackbd.ko
内核 syslog 输出如下:
Jan 10 21:52:29 ubuntu kernel: [ 2873.847052] stackbd: loading out-of-tree module taints kernel.
Jan 10 21:52:29 ubuntu kernel: [ 2873.847118] stackbd: module verification failed: signature and/or required key missing - tainting kernel
Jan 10 21:52:29 ubuntu kernel: [ 2873.849754] stackbd: init done
使用用户端 util 使 stackbd 打开循环设备,它使用 ioctl命令来控制内核模块:
cd ~/stackbd/util
sudo stackbd_util /dev/loop0
do it... </dev/loop0>
OK
确认新设备 / dev / stackbd0 存在,并且大小与基础设备相同:
ls -l /dev/stackbd0
brw-rw---- 1 root disk 251, 0 Jan 10 21:54 /dev/stackbd0
sudo blockdev --getsize /dev/stackbd0
200000
执行上面命令后的 syslog 中的消息 应类似于:
Jan 10 21:54:25 ubuntu kernel: [ 2990.252739] *** DO IT!!!!!!! ***
Jan 10 21:54:25 ubuntu kernel: [ 2990.252739]
Jan 10 21:54:25 ubuntu kernel: [ 2990.252745] Opened /dev/loop0
Jan 10 21:54:25 ubuntu kernel: [ 2990.252761] stackbd: Device real capacity: 200000
Jan 10 21:54:25 ubuntu kernel: [ 2990.252763] stackbd: Max sectors: 255
Jan 10 21:54:25 ubuntu kernel: [ 2990.252870] stackbd: done initializing successfully
Jan 10 21:54:25 ubuntu kernel: [ 2990.254140] stackbd: make request read block 199808 #pages 1 total-size 4096
Jan 10 21:54:25 ubuntu kernel: [ 2990.254228] stackbd: make request read block 199984 #pages 1 total-size 4096
......
Jan 10 21:54:25 ubuntu kernel: [ 2990.258612] stackbd: make request read block 4096 #pages 1 total-size 4096
5. 安装设备并使用
首先,在主目录下创建一个目录mnt,用于挂载。该操作只需执行一次,因为重启后目录还会保留:
mkdir ~/mnt
在设备上创建一个文件系统,示例为 ext4:
sudo mkfs.ext4 /dev/stackbd0
mke2fs 1.42.9 (4-Feb-2014)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
25064 inodes, 100000 blocks
5000 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=67371008
13 block groups
8192 blocks per group, 8192 fragments per group
1928 inodes per group
Superblock backups stored on blocks:
8193, 24577, 40961, 57345, 73729
Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done
在目录 mnt上挂载文件系统:
sudo mount -t ext4 /dev/stackbd0 ~/mnt/
赋予非root用户在挂载点上的读写权限:
sudo chmod -R 777 ~/mnt/
创建文件并将其写入设备中,然后,读取文件。
echo test > ~/mnt/1.txt
cat ~/mnt/1.txt
在上述操作过程中,查看详细记录I/O请求的调试打印。举个例子:
Jan 10 22:04:33 ubuntu kernel: [ 3597.680126] stackbd: make request read block 518 #pages 1 total-size 1024
Jan 10 22:04:33 ubuntu kernel: [ 3597.680270] stackbd: make request write block 16902 #pages 1 total-size 1024
Jan 10 22:04:38 ubuntu kernel: [ 3602.816174] stackbd: make request write block 98344 #pages 1 total-size 1024
Jan 10 22:04:38 ubuntu kernel: [ 3602.816194] stackbd: make request write block 98346 #pages 1 total-size 1024
6. 取消挂载并卸载设备
为了重新测试设备(例如在修改代码后),可以将其卸载并重新安装。
卸载文件系统(取消挂载):
sudo umount /dev/stackbd0
删除模块,这将删除设备 / dev / stackbd0:
sudo rmmod stackbd
Jan 10 22:08:00 ubuntu kernel: [ 3804.559709] stackbd: exit
7. 有趣的内核代码片段
在这个块设备里面打开底层的块设备,使用它的路径(在这里的例子中,路径是/dev/loop0)。用于打开块设备的函数有 lookup_dev()、bdget() 和 blkdev_get():
struct block_device *bdev_raw = lookup_bdev(dev_path);
printk("Opened %s\n", dev_path);
if (IS_ERR(bdev_raw))
{
printk("stackbd: error opening raw device <%lu>\n", PTR_ERR(bdev_raw));
return NULL;
}
if (!bdget(bdev_raw->bd_dev))
{
printk("stackbd: error bdget()\n");
return NULL;
}
if (blkdev_get(bdev_raw, STACKBD_BDEV_MODE, &stackbd))
{
printk("stackbd: error blkdev_get()\n");
bdput(bdev_raw);
return NULL;
}
return bdev_raw;
实际上,只是将一个 I/O 请求从这个块设备重新映射到底层块设备。函数 trace_block_bio_remap() 只是简单地修改了请求的目标设备和地址,并将请求发送到另一个设备的队列中(使用 generic_make_request() 函数):
static void stackbd_io_fn(struct bio *bio)
{
bio->bi_bdev = stackbd.bdev_raw;
trace_block_bio_remap(bdev_get_queue(stackbd.bdev_raw), bio,
bio->bi_bdev->bd_dev, bio->bi_sector);
/* No need to call bio_endio() */
generic_make_request(bio);
}
块设备队列函数。块设备异步处理请求(与字符设备不同)。它们定义了一个请求回调并将其注册到队列中。内核调用这个回调来处理 I/O,这个函数作为一个生产者线程,因为它只将 I/O 请求添加到一个内部列表 (struct bio list) 中,而不处理它。它向作为消费者的另一个线程发出信号,让它实际执行 I/O 操作。
static void stackbd_make_request(struct request_queue *q, struct bio *bio)
{
spin_lock_irq(&stackbd.lock);
if (!stackbd.bdev_raw)
{
printk("stackbd: Request before bdev_raw is ready, aborting\n");
goto abort;
}
if (!stackbd.is_active)
{
printk("stackbd: Device not active yet, aborting\n");
goto abort;
}
bio_list_add(&stackbd.bio_list, bio);
wake_up(&req_event);
spin_unlock_irq(&stackbd.lock);
return;
abort:
spin_unlock_irq(&stackbd.lock);
printk("<%p> Abort request\n\n", bio);
bio_io_error(bio);
}
块设备 "消费者 "线程函数--等待 "生产者 "线程(也就是实际的队列线程)发出信号,表示有请求被添加到列表中,wait_event_interruptible() 是睡眠等待队列线程发出信号唤醒的函数。
static int stackbd_threadfn(void *data)
{
struct bio *bio;
while (!kthread_should_stop())
{
/* wake_up() is after adding bio to list. No need for condition */
wait_event_interruptible(req_event, kthread_should_stop() ||
!bio_list_empty(&stackbd.bio_list));
spin_lock_irq(&stackbd.lock);
if (bio_list_empty(&stackbd.bio_list))
{
spin_unlock_irq(&stackbd.lock);
continue;
}
bio = bio_list_pop(&stackbd.bio_list);
spin_unlock_irq(&stackbd.lock);
stackbd_io_fn(bio);
}
return 0;
}

stackbd:在一个块设备上堆叠另一个块设备的更多相关文章
- mac 无法写入设备的最后一个块 格式化
硬盘,U盘,装在硬盘盒通过USB连接到电脑.但是无法格式化硬盘 失败的页面显示: 正在卸载磁盘 无法写入设备的最后一个块 操作失败 建议您这样做: 1.切换进Windows系统,或者找一台安装有Win ...
- MYSQL一个设备上的主从复制实现-windows
只记录一次在一个设备上实现mysql主从复制的过程,很详细,建议收藏,用到的时候照着步骤做就可以,会记录所有需要注意到的细节和一些容易遇到的坑以及解决办法! 如果需要在同一台电脑(服务器)上实现mys ...
- Apache错误:[error] (OS 10038)在一个非套接字上尝试了一个操作
Apache错误:[error] (OS 10038)在一个非套接字上尝试了一个操作 博客分类: vb2005xu软件学习 OSApache防火墙PHPWindows 日志如下:[ ...
- 关于OSError: [WinError 10038] 在一个非套接字上尝试了一个操作。
在使用socket的时候,写了一个while循环,就报错了.结果如下: OSError: [WinError 10038] 在一个非套接字上尝试了一个操作. 代码 import socket impo ...
- centos8上使用lsblk查看块设备
一,查看lsblk命令所属的rpm包 [root@yjweb ~]# whereis lsblk lsblk: /usr/bin/lsblk /usr/share/man/man8/lsblk.8.g ...
- 在设备上启用 adb 调试,有一个小秘密
要在通过 USB 连接的设备上使用 adb,您必须在设备的系统设置中启用 USB 调试(位于开发者选项下). 在搭载 Android 4.2 及更高版本的设备上,“开发者选项”屏幕默认情况下处于隐藏状 ...
- 嵌入式设备上的 Linux 系统开发
转载:http://www.ibm.com/developerworks/cn/linux/embed/embdev/index.html 如果您刚接触嵌入式开发,那么大量可用的引导装载程序(bo ...
- 如何在小型pcb的移动设备上获得更好的无线性能
如何在小型pcb的移动设备上获得更好的无线性能 How to get better wireless performance for mobile devices with small PCBs 小型 ...
- 在ios android设备上使用 Protobuf (使用dll方式)
http://game.ceeger.com/forum/read.php?tid=13479 如果你的工程可以以.Net 2.0 subset模式运行,请看这个帖子中的方法. 地址:http://g ...
- 优化移动设备上SharePoint 2013网站
优化移动设备上SharePoint 2013网站 本文由SPFarmer翻译自Waldek Mastykarz的文章 移动市场在持续的增长.在不远的将来,使用移动设备浏览站点将会超过电脑.为了保证用户 ...
随机推荐
- Spring Boot项目设置跨域
一.跨域设置 新建一个配置类 import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterC ...
- C盘扩展卷碰到的那些事-->不是同一块物理磁盘操作扩展卷是有坑的
自己电脑上面用过win10系统资源管理器扩展卷的功能,用过几次都成功扩容了磁盘空间,简单说一下原理: 就是将剩余未分配的磁盘空间划给要扩展的磁盘. 这天公司的电脑C盘老是红色提示空间不足,那就扩充容量 ...
- 『Python底层原理』--Python属性的工作原理
Python中的属性操作(如获取.设置和删除属性)是我们日常编程中非常常见的操作. 但你有没有想过,当我们写下obj.attr或obj.attr = value时,Python 内部究竟发生了什么? ...
- 开启 Typecho 的 gzip 压缩
简介 GZip压缩,是一种网站速度优化技术,也是一把SEO优化利器,许多网站都采用了这种技术,以达到提升网页打开速度.缩短网页打开时间的目的. 网站采用Gzip压缩,还有一个好处,就是让你少了一份流量 ...
- Shell - 脚本案例
题记部分 一.节点状态监控脚本(nodeStatusCheck.sh) [脚本名称]nodeStatusCheck.sh [监控规则]通过ping的方式监控集群节点状态,检查节点是否失联 [实现方式] ...
- 启动hive,报错 Name node is in safe mode.
在学习过程中,过了几天再启动虚拟机,启动hadoop后再启动别的框架会报错: Exception in thread "main" java.lang.RuntimeExcepti ...
- 宝塔导入mysql数据库后,phpmyadmin可以登录,本地Navicat无法登录
问题描述:宝塔导入mysql数据库后,phpmyadmin可以登录,本地Navicat无法登录 问题排查:1.检查服务器3306端口是否开启,如果为云服务器,需要登录云服务器后台安全组设置开启: 2. ...
- 为什么 退出登录 或 修改密码 无法使 token 失效
前文说过 token 由 3 个部分组成:分别是 token metadata,payload,signature, 其中 signature 部分是对 payload 的加密,而 payload 当 ...
- Cordova基本使用(二)
cordova的打包发布版app流程简介 除了第一遍官网给的打包发布版的方法,我们可以自己多敲几次命令来实现. 基本上使用如下的几个命令就完成这个过程,先列出整个过程: 1.cordova选定ando ...
- Qt 给窗口绘制阴影
文章目录 Qt 给窗口绘制阴影 前言 重载`paintEvent`法 QGraphicsDropShadowEffect方法 使用九图拼凑法 九宫格缩放阴影法 Qt 给窗口绘制阴影 前言 最近自定义一 ...