编写块设备驱动程序

本文详细介绍了如何在Linux系统中编写一个块设备内核驱动模块,使用ramdisk作为虚拟块设备,并演示了文件系统的格式化和基本文件操作。涵盖了块设备与字符设备的区别,以及内核驱动涉及的关键数据结构和函数,如request、bio和block_device_operations的使用。
摘要由CSDN通过智能技术生成

写一个块设备内核驱动模块,利用ramdisk,即利用系统内存来构造一个虚拟的块设备,在这个块设备上实现文件系统的格式化和一些文件操作。(广州大学Linux操作系统实验四第二部分

Linux内核学习11——编写块设备驱动程序(下)_编写my_block块设备驱动程序_Backlight~~的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_45730790/article/details/122779814参考的是上的,代码也是上面大佬的

提前说明,这次使用的Liunx是14.04的,运行起来就不会出现错误,新版本可能很多路径函数都发生了变化,如果需要,要重新进行编写Index of /releases/trustyicon-default.png?t=N7T8http://old-releases.ubuntu.com/releases/trusty/

块设备和字符设备的区别

块设备和字符设备是 Linux 系统中用于访问硬件设备的两种主要方式

它们有一些显著的区别:

  1. 数据访问方式:

    • 块设备:以固定大小的数据块(通常为几 KB 到几 MB)进行访问。典型的块设备包括硬盘驱动器、固态硬盘等。
    • 字符设备:按照一个字符或多个字符的方式进行访问,没有固定的数据块大小。典型的字符设备包括键盘、鼠标、终端设备、串行端口等。
  2. 缓存:

    • 块设备:可以在内核中进行缓存,适合大量数据的批量读写操作,因此块设备具有较高的性能。
    • 字符设备:通常不进行缓存,每次对设备的读写都会直接传输到硬件设备中,适合于实时性较强的设备。
  3. 设备访问方式:

    • 块设备:可以随机访问,即可以通过指定的块号来读取或写入数据。
    • 字符设备:通常是顺序访问,数据按照输入顺序或输出顺序进行处理。
  4. 数据传输方式:

    • 块设备:通常以 DMA(直接内存存取)方式进行数据传输,效率较高。
    • 字符设备:通常使用 PIO(程序控制输入/输出)方式进行数据传输。

总的来说,块设备适合于需要高性能和大量数据批量读写的场景,而字符设备更适合于实时性要求高、按字符顺序进行输入输出的场景。在 Linux 中,这两种设备类型都通过文件系统的形式向用户空间提供访问接口。

常见的块设备有机械硬盘:磁头(head),磁道(track),柱面(cylinder),扇区(sector)

其他常见的块设备:光盘、ramdisk,SSD,RAID,存储网络,虚拟化存储等

块设备驱动架构

  1. 文件系统层:包括常见的文件系统EXT4 和虚拟文件系统VFS在linux 中,对块设备,不会像字符设备驱动那样在应用程序中直接打开和操作块设备,而是通过文件系统来访问块设备。
  2. 通用块层:为各种类型的块设备建立一个统一的模型,接收文件系统层发出的磁盘请求,并最终向磁盘设备发出I/O请求,所以它隐藏了很多底层硬件设备的细节,为块设备提供了一个抽象的模型。
  3. I/O调度层:接收通用块层发出的I/O请求和缓存请求,根据I/O调度算法来合并和排序相邻的请求,然后调用驱动层提供的处理函数发送这些I/O请求到硬件设备。
  4. 块设备驱动层:就是实现实际的块设备驱动程序,包括块设备注册、打开、关闭以及具体的I/O处理。
块设备驱动涉及的数据结构
  1. block_device数据结构:用来抽象和描述一个块设备。
struct block_device{
     dev_t    bd_dev;
     struct inode* bd_inode;
     struct super_block* bd_super;
     struct gendisk* bd_disk;
      struct request_queue* bd_queue;
};

2.gendisk 数据结构:Linux内核把磁盘类设备中关于磁盘通用那部分提取出来,使用一个gendisk 数据结构来描述它,它可以表示一个已经分区的磁盘,也可以表示一个未分区的磁盘

struct gendisk{
     int major;
     int first_minor;
     int minors;
     char disk_name[DISK_NAME_LEN];
     struct disk_part_tbl_rcu* part_tbl;
     const struct block_device_operations* fops;
     struct request_queue* queue;
};

3.block_device_operations 操作方法集:与字符设备的file_operations 结构类似

struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);//打开块设备
	void (*release) (struct gendisk *, fmode_t); //释放块设备
	int (*rw_page)(struct block_device *, sector_t, struct page *, unsigned int);//读写页面
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);//执行设备特定的I/O控制操作
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);//兼容模式下执行设备特定的I/O控制操作
	unsigned int (*check_events) (struct gendisk *disk,unsigned int clearing);//检查设备上的事件,并返回事件的状态
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
    
	int (*media_changed) (struct gendisk *);//检测媒体更改的状态
	void (*unlock_native_capacity) (struct gendisk *);//解锁原生容量。在某些情况下,可能需要解锁原生容量,这个函数指针用于执行相应的操作
	int (*revalidate_disk) (struct gendisk *);//重新验证磁盘。当设备状态发生变化时,内核会调用此函数指针。

	int (*getgeo)(struct block_device *, struct hd_geometry *);
//获取磁盘几何信息
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);//通知交换插槽的释放状态
	int (*report_zones)(struct gendisk *, sector_t sector,
			    struct blk_zone *zones, unsigned int *nr_zones);
//报告区域状态
	struct module *owner;//指向拥有该结构体的模块的指针
	const struct pr_ops *pr_ops;//用于持久保留(Persistent Reservations)的相关操作
};

request 数据结构:对于来自通用块层的请求,在提交到块设备驱动的时候,需要构造一个请求request,并把这些request请求插入到块设备的请求队列request_queue中。

struct request {
	union {
		struct list_head queuelist;
		struct llist_node ll_list;
	};
	union {
		struct call_single_data csd;
		struct work_struct mq_flush_data;
	};

	struct request_queue *q;
	struct blk_mq_ctx *mq_ctx;

	u64 cmd_flags;
	enum rq_cmd_type_bits cmd_type;
	unsigned long atomic_flags;

	int cpu;

	/* the following two fields are internal, NEVER access directly */
	unsigned int __data_len;	/* total data len */
	sector_t __sector;		/* sector cursor */

	struct bio *bio;
	struct bio *biotail;

	struct hlist_node hash;	/* merge hash */
	/*
	 * The rb_node is only used inside the io scheduler, requests
	 * are pruned when moved to the dispatch queue. So let the
	 * completion_data share space with the rb_node.
	 */
	union {
		struct rb_node rb_node;	/* sort/lookup */
		void *completion_data;
	};

	/*
	 * Three pointers are available for the IO schedulers, if they need
	 * more they have to dynamically allocate it.  Flush requests are
	 * never put on the IO scheduler. So let the flush fields share
	 * space with the elevator data.
	 */
	union {
		struct {
			struct io_cq		*icq;
			void			*priv[2];
		} elv;

		struct {
			unsigned int		seq;
			struct list_head	list;
			rq_end_io_fn		*saved_end_io;
		} flush;
	};

	struct gendisk *rq_disk;
	struct hd_struct *part;
	unsigned long start_time;
#ifdef CONFIG_BLK_CGROUP
	struct request_list *rl;		/* rl this rq is alloced from */
	unsigned long long start_time_ns;
	unsigned long long io_start_time_ns;    /* when passed to hardware */
#endif
	/* Number of scatter-gather DMA addr+len pairs after
	 * physical address coalescing is performed.
	 */
	unsigned short nr_phys_segments;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
	unsigned short nr_integrity_segments;
#endif

	unsigned short ioprio;

	void *special;		/* opaque pointer available for LLD use */
	char *buffer;		/* kaddr of the current segment if available */

	int tag;
	int errors;

	/*
	 * when request is used as a packet command carrier
	 */
	unsigned char __cmd[BLK_MAX_CDB];
	unsigned char *cmd;
	unsigned short cmd_len;

	unsigned int extra_len;	/* length of alignment and padding */
	unsigned int sense_len;
	unsigned int resid_len;	/* residual count */
	void *sense;

	unsigned long deadline;
	struct list_head timeout_list;
	unsigned int timeout;
	int retries;

	/*
	 * completion callback.
	 */
	rq_end_io_fn *end_io;
	void *end_io_data;

	/* for bidi */
	struct request *next_rq;
};

5.bio数据结构:用于块设备数据传输的数据结构,它类似于一个网络的协议包,在文件系统和块设备子系统里来回地流动,把要读的数据和要写的数据来回地搬移。

struct bio {
    /*要传送的第一个扇区*/
    sector_t        bi_sector;  /* device address in 512 byte sectors */
    /*下一个扇区*/
    struct bio      *bi_next;   /* request queue link */
    struct block_device *bi_bdev;   /*bio对应的块设备*/
    unsigned long       bi_flags;   /* status, command, etc */
    unsigned long  bi_rw;  /* bottom bits READ/WRITE,  * top bits priority */
    ……..
};

上述数据结构在一个文件打开时与磁盘关联的操作过程:

在用户空间打开一个文件open, 返回一个文件句柄fd, 在内核空间就会有一个file数据结构和它对应,操作如下:

fd->struct file: f_mapping-> struct address_space: host->struct inode: i_bdev->struct block_device: bd_disk->struct gendisk:queue->struct request_queue: request->struct request:sector

块设备驱动程序常用的函数

register_blkdev() 块设备注册函数

int register_blkdev(unsigned int major, const char* name)

    unregister_blkdev() 块设备注销函数

int unregister_blkdev(unsigned int major, const char* name)

   

blk_init_queue() 初始化请求队列函数

struct request_queue* blk_init_queue(request_fn_proc* rfn, spinlock_t* lock)

    

blk_cleanup_queue()注销请求队列函数

void blk_cleanup_queue(struct request_queue* q)

alloc_disk() 分配gendisk对象函数

struct gendisk* alloc_disk(int minors)

add_disk() 注册gendisk对象函数

void add_disk(struct gendisk* disk)

del_gendisk() 注销gendisk对象函数

void del_gendisk(struct gendisk* disk)

bio_init() 初始化BIO函数

void bio_init(struct bio* bio)

submit_bio()提交BIO函数

void submit_bio(int rw, struct bio* bio)

具体代码

ramdisk_driver.c

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/version.h>
 
#define MY_DEVICE_NAME "myramdisk"
 
static int mybdrv_ma_no, diskmb = 256, disk_size;     //定义ramdisk的大小是256MB
static char *ramdisk;
static struct gendisk *my_gd;
static spinlock_t lock;
static unsigned short sector_size = 512;
static struct request_queue *my_request_queue;
 
module_param_named(size, diskmb, int, 0);
static void my_request(struct request_queue *q)
{
	struct request *rq;
	int size, res = 0;
	char *ptr;
	unsigned nr_sectors, sector;
	pr_info("start handle request\n");
 
	rq = blk_fetch_request(q);    //从请求队列中拉一个request出来
	while (rq) {
		nr_sectors = blk_rq_cur_sectors(rq);   //这个请求需要多少个sector
		sector = blk_rq_pos(rq);
 		//因为我们的块设备是一个简单的以连续内存作为存储的一个虚拟的块设备,所以可以通过一个简单的计算算出当前的request它的存储地址和大小
		ptr = ramdisk + sector * sector_size;   
		size = nr_sectors * sector_size;
 
		if ((ptr + size) > (ramdisk + disk_size)) {
			pr_err("end of device\n");
			goto done;
		}
 
		if (rq_data_dir(rq)) {
			pr_info("writing at sector %d, %u sectors\n",
				sector, nr_sectors);
			memcpy(ptr, bio_data(rq->bio), size);   //数据做一个拷贝,将bio_data请求的数据拷贝到ptr指针里面
		} else {
			pr_info("reading at sector %d, %u sectors\n",
				sector, nr_sectors);
			memcpy(bio_data(rq->bio), ptr, size);
		}
done:
		if (!__blk_end_request_cur(rq, res))
			rq = blk_fetch_request(q);
	}
	pr_info("handle request done\n");
}
 
static int my_ioctl(struct block_device *bdev, fmode_t mode,
		    unsigned int cmd, unsigned long arg)
{
	long size;
	struct hd_geometry geo;
 
	pr_info("cmd=%d\n", cmd);
 
	switch (cmd) {
	case HDIO_GETGEO:
		pr_info("HIT HDIO_GETGEO\n");
		/*
		 * get geometry: we have to fake one...
		 */
		size = disk_size;
		size &= ~0x3f;
		geo.cylinders = size>>6;
		geo.heads = 2;
		geo.sectors = 16;
		geo.start = 4;
 
		if (copy_to_user((void __user *)arg, &geo, sizeof(geo)))
			return -EFAULT;
 
		return 0;
	}
	pr_warn("return -ENOTTY\n");
 
	return -ENOTTY;
}
 
static const struct block_device_operations mybdrv_fops = {
	.owner = THIS_MODULE,
	.ioctl = my_ioctl,
};
 
static int __init my_init(void)
{
	disk_size = diskmb * 1024 * 1024;    //简单给ramdisk设定一个大小256MB。
	spin_lock_init(&lock);
 
	ramdisk = vmalloc(disk_size);
	if (!ramdisk)
		return -ENOMEM;
 
	my_request_queue = blk_init_queue(my_request, &lock);  //初始化请求队列,第一个参数是函数指针
	if (!my_request_queue) {
		vfree(ramdisk);
		return -ENOMEM;
	}
	blk_queue_logical_block_size(my_request_queue, sector_size);
 
	mybdrv_ma_no = register_blkdev(0, MY_DEVICE_NAME);  //注册一个块设备,第一个参数为0的话就是让系统自动分配一个主设备号,第二个参数是设备名称,注册成功会返回一个主设备号
	if (mybdrv_ma_no < 0) {
		pr_err("Failed registering mybdrv, returned %d\n",
		       mybdrv_ma_no);
		vfree(ramdisk);
		return mybdrv_ma_no;
	}
 
	my_gd = alloc_disk(16);   //分配一个gendisk
	if (!my_gd) {
		unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
		vfree(ramdisk);
		return -ENOMEM;
	}
 	//初始化
	my_gd->major = mybdrv_ma_no;  //主设备号
	my_gd->first_minor = 0;		  //次设备号
	my_gd->fops = &mybdrv_fops;    //方法集
	strcpy(my_gd->disk_name, MY_DEVICE_NAME);   //名字
	my_gd->queue = my_request_queue;    //请求队列
	set_capacity(my_gd, disk_size / sector_size);    //设置容量
    
	add_disk(my_gd);  //把gendisk添加到Linux系统的块设备子系统里   
 
	pr_info("device successfully   registered, Major No. = %d\n",
		mybdrv_ma_no);
	pr_info("Capacity of ram disk is: %d MB\n", diskmb);
 
	return 0;
}
 
static void __exit my_exit(void)
{
	del_gendisk(my_gd);
	put_disk(my_gd);
	unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
	pr_info("module successfully unloaded, Major No. = %d\n", mybdrv_ma_no);
	blk_cleanup_queue(my_request_queue);
	vfree(ramdisk);
}
 
module_init(my_init);     //指定了驱动的入口,入口函数是my_init
module_exit(my_exit);
 
MODULE_AUTHOR("Benshushu");
MODULE_LICENSE("GPL v2");

Makefile

BASEINCLUDE ?= /lib/modules/$(shell uname -r)/build
 
oops-objs := ramdisk-driver.o 
KBUILD_CFLAGS +=-g -O0
 
 
obj-m	:=   ramdisk_driver.o
all : 
	$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) modules;
 
install:
	$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) modules_install;
	
	
 
clean:
	$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
	rm -f *.ko;

具体命令操作

make

sudo insmod ramdisk_driver.ko

dmesg

ls /dev

 sudo mkfs.ext4 /dev/myramdisk

dmesg

可以看到dmesg里面打印了很多信息

每一次request处理的时候,入口会打印一个start handle request

writing at sector 409600,8 sectors 这里409600是一个sector在整个ramdisk的一个其实的指针,相当于文件的一个指针,8是这次操作操作了8个sector

sudo mount /dev/myramdisk /mnt/

cd mnt

ls

df

dmesg

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值