jewel+ 版本支持 rbd-nbd 的特性,需要 map 支持较多特性的 rbd image 时,可以使用该 nbd driver
NBD(Network Block Device) 可以将一个远程主机的磁盘空间,当作一个块设备来使用,就像一块硬盘一样。NBD是一个内核模块,大部分Linux发行版都包含它
与 RBD 内核驱动程序相比,NBD有许多优点:
- RBD-KO开发和特性的添加必须要经过稳定的内核
- RBD-KO开发慢于librbd,如果要与librbd开发保持同步,需要时间和努力
- NBD 已经很好地集成到内核多年,是内核的一部分
Linux块设备驱动知识
(1)当一个用户程序要向磁盘写入数据时,会发发出write()系统调用给内核。
(2)内核会调用虚拟文件系统相应的函数,将需要写入发文件描述符和文件内容指针传递给该函数。
(3)内核需要确定写入磁盘的位置,通过映射层知道需要写入磁盘的哪一块。
(4)根据磁盘的文件系统的类型,调用不同文件格式的写入函数,江苏数据发送给通用块层(比如ext2和ext3文件系统的写入函数是不同的,这些函数由内核开发者实现,驱动开发者不用实现这类函数)
(5)数据到达通用块层后,就对块设备发出写请求。内核利用通用块层的启动I/O调度器,对数据进行排序。
(6)同用块层下面是"I/O调度器"。调度器作用是把物理上相邻的读写合并在一起,这样可以加快访问速度。
(7)最后快设备驱动向磁盘发送指令和数据,将数据写入磁盘。
参考: https://www.cnblogs.com/big-devil/p/8590007.html
结构体 nbd_device
关注队列,gendisk
struct nbd_device {
int flags;
int harderror; /* Code of hard error */
struct socket * sock; /* If == NULL, device is not ready, yet */
int magic;
spinlock_t queue_lock;
struct list_head queue_head; /* Requests waiting result */
struct request *active_req;
wait_queue_head_t active_wq;
struct list_head waiting_queue; /* Requests to be sent */
wait_queue_head_t waiting_wq;
struct mutex tx_lock;
struct gendisk *disk;
int blksize;
u64 bytesize;
pid_t pid; /* pid of nbd-client, if attached */
int xmit_timeout;
int disconnect; /* a disconnect has been requested by user */
};
1. nbd_init
1.1 kcalloc 分配物理内存与高端内存映射
可以获取以字节为单位的一块内核内存,GFP_KERNEL flag 一种常规的分配方式, 可能会阻塞. 这个标志在睡眠安全时用在进程的长下文代码中. 为了获取调用者所需的内存, 内核会尽力而为. 这个标志应该是首选标志
1.2 alloc_disk 函数分配通用磁盘gendisk的结构体
1.3 blk_init_queue 分配请求队列,并初始化
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
第一个参数是指向"请求处理函数"的指针,该函数直接和硬盘打交道,用来处理数据在内存和硬盘之间的传输。
1.4 register_blkdev 注册块设备
作用:分局major分配一个块设备号,在/proc/devices中新增加一行数据,表示块设备的信息
#define NBD_MAJOR 43 /* Network block device */
初始化gendisk结构体的数据成员,包括major,fops,queue 等赋初值
for (i = 0; i < nbds_max; i++) {
struct gendisk *disk = nbd_dev[i].disk;
nbd_dev[i].magic = NBD_MAGIC;
INIT_LIST_HEAD(&nbd_dev[i].waiting_queue);
spin_lock_init(&nbd_dev[i].queue_lock);
INIT_LIST_HEAD(&nbd_dev[i].queue_head);
mutex_init(&nbd_dev[i].tx_lock);
init_waitqueue_head(&nbd_dev[i].active_wq);
init_waitqueue_head(&nbd_dev[i].waiting_wq);
nbd_dev[i].blksize = 1024;
nbd_dev[i].bytesize = 0;
disk->major = NBD_MAJOR;
disk->first_minor = i << part_shift;
disk->fops = &nbd_fops;
disk->private_data = &nbd_dev[i];
sprintf(disk->disk_name, "nbd%d", i);
set_capacity(disk, 0);
add_disk(disk);
}
1.5 add_disk 函数激活磁盘设备
(当调用该函数后就可以对磁盘进行操作(访问),所以调用该函数之前必须所有的准备工作就绪)
nbd_init 总结:
参考:https://cloud.tencent.com/developer/article/1380637
2. nbd_fops 注册
根据 disk->fops = &nbd_fops;,可以看到只关注 ioctl 函数
static const struct block_device_operations nbd_fops =
{
.owner = THIS_MODULE,
.ioctl = nbd_ioctl,
};
2.1 nbd_ioctl 函数
从nbd server 端收到调用 ioctl,实现在 ceph 代码中的 rbd-nbd,直接关键函数 __nbd_ioctl
static int nbd_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
error = __nbd_ioctl(bdev, nbd, cmd, arg);
}
3. __nbd_ioctl 函数
static int __nbd_ioctl(struct block_device *bdev, struct nbd_device *nbd,
unsigned int cmd, unsigned long arg)
{
根据 cmd 区分不同的操作类型
3.1 NBD_DO_IT
3.1.1 kthread_create
在后台执行一些操作,这种任务就可以通过内核线程(kernle thread)完成独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm 指针被设置为 NULL;它只在内核空间运行,从来不切换到用户空间去
3.1.2 wake_up_process(thread)
线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。
3.1.3 nbd_do_it
4. do_nbd_request 函数
参考:
https://blog.sourcerer.io/writing-a-simple-linux-kernel-module-d9dc3762c234
http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/