参考:【块设备】通用块层 struct bio 详解 | zzm (aliez22.github.io)

一、Linux 中块设备驱动框架

i.MX 6ULL 驱动开发 二十七:块设备-LMLPHP

二、块设备基本概念

1、扇区的概念来自硬件,扇区是硬件最小操作单位。

2、的概念来自文件系统,是文件系统数据处理的最小单位。

3、的概念来自操作系统,是内核对内存管理机制的最小单位。

4、的概念来自操作系统,是内核内存映射管理的最小单位。

三、磁盘分区相关概念

硬盘分区的相关概念(主分区,扩展分区,逻辑分区,MBR,DBR) - 假程序猿 - 博客园 (cnblogs.com)

四、块设备驱动框架中几个重要对象

1、逻辑块设备

struct block_device {
	dev_t			bd_dev;  /* not a kdev_t - it's a search key */
	int			bd_openers;
	struct inode *		bd_inode;	/* will die */
	struct super_block *	bd_super;
	struct mutex		bd_mutex;	/* open/close mutex */
	struct list_head	bd_inodes;
	void *			bd_claiming;
	void *			bd_holder;
	int			bd_holders;
	bool			bd_write_holder;
#ifdef CONFIG_SYSFS
	struct list_head	bd_holder_disks;
#endif
	struct block_device *	bd_contains;
	unsigned		bd_block_size;
	struct hd_struct *	bd_part;
	/* number of times partitions within this device have been opened. */
	unsigned		bd_part_count;
	int			bd_invalidated;
	struct gendisk *	bd_disk;
	struct request_queue *  bd_queue;
	struct list_head	bd_list;
	/*
	 * Private data.  You must have bd_claim'ed the block_device
	 * to use this.  NOTE:  bd_claim allows an owner to claim
	 * the same device multiple times, the owner must take special
	 * care to not mess up bd_private for that case.
	 */
	unsigned long		bd_private;

	/* The counter of freeze processes */
	int			bd_fsfreeze_count;
	/* Mutex for freeze */
	struct mutex		bd_fsfreeze_mutex;
};

1、块设备注册

/**
 * register_blkdev - register a new block device
 *
 * @major: the requested major device number [1..255]. If @major=0, try to
 *         allocate any unused major number.
 * @name: the name of the new block device as a zero terminated string
 *
 * The @name must be unique within the system.
 *
 * The return value depends on the @major input parameter.
 *  - if a major device number was requested in range [1..255] then the
 *    function returns zero on success, or a negative error code
 *  - if any unused major number was requested with @major=0 parameter
 *    then the return value is the allocated major number in range
 *    [1..255] or a negative error code otherwise
 */
int register_blkdev(unsigned int major, const char *name)

2、块设备注销

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

2、实际块设备

struct gendisk {
	/* major, first_minor and minors are input parameters only,
	 * don't use directly.  Use disk_devt() and disk_max_parts().
	 */
	int major;			/* major number of driver */
	int first_minor;
	int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

	char disk_name[DISK_NAME_LEN];	/* name of major driver */
	char *(*devnode)(struct gendisk *gd, umode_t *mode);

	unsigned int events;		/* supported events */
	unsigned int async_events;	/* async events, subset of all */

	/* Array of pointers to partitions indexed by partno.
	 * Protected with matching bdev lock but stat and other
	 * non-critical accesses use RCU.  Always access through
	 * helpers.
	 */
	struct disk_part_tbl __rcu *part_tbl;
	struct hd_struct part0;

	const struct block_device_operations *fops;
	struct request_queue *queue;
	void *private_data;

	int flags;
	struct device *driverfs_dev;  // FIXME: remove
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct blk_integrity *integrity;
#endif
	int node_id;
};

1、申请 gendisk

struct gendisk *alloc_disk(int minors)

2、删除 gendisk

void del_gendisk(struct gendisk *disk)

3、将 gendisk 添加到内核

void add_disk(struct gendisk *disk)

4、设置 gendisk 容量

void set_capacity(struct gendisk *disk, sector_t size)

5、调整 gendisk 引用计数

truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)

3、block_device 和 gendisk 区别

i.MX 6ULL 驱动开发 二十七:块设备-LMLPHP
struct block_device:用来描述一个块设备或者块设备的一个分区。与文件系统关系密切。

struct gendisk:描述整个块设备的特性。块设备驱动程序的主要操作对象

对于一个包含多个分区的块设备,struct block_device 结构有多个,而 struct gendisk 结构只有一个。

4、块设备操作集

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 *, int rw);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	long (*direct_access)(struct block_device *, sector_t,
					void **, unsigned long *pfn, long size);
	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);
	struct module *owner;
};

5、请求队列

struct request_queue {
	/*
	 * Together with queue_head for cacheline sharing
	 */
	struct list_head	queue_head;
	struct request		*last_merge;
	struct elevator_queue	*elevator;
	int			nr_rqs[2];	/* # allocated [a]sync rqs */
	int			nr_rqs_elvpriv;	/* # allocated rqs w/ elvpriv */

	/*
	 * If blkcg is not used, @q->root_rl serves all requests.  If blkcg
	 * is used, root blkg allocates from @q->root_rl and all other
	 * blkgs from their own blkg->rl.  Which one to use should be
	 * determined using bio_request_list().
	 */
	struct request_list	root_rl;

	request_fn_proc		*request_fn;
	make_request_fn		*make_request_fn;
	prep_rq_fn		*prep_rq_fn;
	unprep_rq_fn		*unprep_rq_fn;
	merge_bvec_fn		*merge_bvec_fn;
	softirq_done_fn		*softirq_done_fn;
	rq_timed_out_fn		*rq_timed_out_fn;
	dma_drain_needed_fn	*dma_drain_needed;
	lld_busy_fn		*lld_busy_fn;

	struct blk_mq_ops	*mq_ops;

	unsigned int		*mq_map;

	/* sw queues */
	struct blk_mq_ctx __percpu	*queue_ctx;
	unsigned int		nr_queues;

	/* hw dispatch queues */
	struct blk_mq_hw_ctx	**queue_hw_ctx;
	unsigned int		nr_hw_queues;

	/*
	 * Dispatch queue sorting
	 */
	sector_t		end_sector;
	struct request		*boundary_rq;

	/*
	 * Delayed queue handling
	 */
	struct delayed_work	delay_work;

	struct backing_dev_info	backing_dev_info;

	/*
	 * The queue owner gets to use this for whatever they like.
	 * ll_rw_blk doesn't touch it.
	 */
	void			*queuedata;

	/*
	 * various queue flags, see QUEUE_* below
	 */
	unsigned long		queue_flags;

	/*
	 * ida allocated id for this queue.  Used to index queues from
	 * ioctx.
	 */
	int			id;

	/*
	 * queue needs bounce pages for pages above this limit
	 */
	gfp_t			bounce_gfp;

	/*
	 * protects queue structures from reentrancy. ->__queue_lock should
	 * _never_ be used directly, it is queue private. always use
	 * ->queue_lock.
	 */
	spinlock_t		__queue_lock;
	spinlock_t		*queue_lock;

	/*
	 * queue kobject
	 */
	struct kobject kobj;

	/*
	 * mq queue kobject
	 */
	struct kobject mq_kobj;

#ifdef CONFIG_PM
	struct device		*dev;
	int			rpm_status;
	unsigned int		nr_pending;
#endif

	/*
	 * queue settings
	 */
	unsigned long		nr_requests;	/* Max # of requests */
	unsigned int		nr_congestion_on;
	unsigned int		nr_congestion_off;
	unsigned int		nr_batching;

	unsigned int		dma_drain_size;
	void			*dma_drain_buffer;
	unsigned int		dma_pad_mask;
	unsigned int		dma_alignment;

	struct blk_queue_tag	*queue_tags;
	struct list_head	tag_busy_list;

	unsigned int		nr_sorted;
	unsigned int		in_flight[2];
	/*
	 * Number of active block driver functions for which blk_drain_queue()
	 * must wait. Must be incremented around functions that unlock the
	 * queue_lock internally, e.g. scsi_request_fn().
	 */
	unsigned int		request_fn_active;

	unsigned int		rq_timeout;
	struct timer_list	timeout;
	struct list_head	timeout_list;

	struct list_head	icq_list;
#ifdef CONFIG_BLK_CGROUP
	DECLARE_BITMAP		(blkcg_pols, BLKCG_MAX_POLS);
	struct blkcg_gq		*root_blkg;
	struct list_head	blkg_list;
#endif

	struct queue_limits	limits;

	/*
	 * sg stuff
	 */
	unsigned int		sg_timeout;
	unsigned int		sg_reserved_size;
	int			node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
	struct blk_trace	*blk_trace;
#endif
	/*
	 * for flush operations
	 */
	unsigned int		flush_flags;
	unsigned int		flush_not_queueable:1;
	struct blk_flush_queue	*fq;

	struct list_head	requeue_list;
	spinlock_t		requeue_lock;
	struct work_struct	requeue_work;

	struct mutex		sysfs_lock;

	int			bypass_depth;
	int			mq_freeze_depth;

#if defined(CONFIG_BLK_DEV_BSG)
	bsg_job_fn		*bsg_job_fn;
	int			bsg_job_size;
	struct bsg_class_device bsg_dev;
#endif

#ifdef CONFIG_BLK_DEV_THROTTLING
	/* Throttle data */
	struct throtl_data *td;
#endif
	struct rcu_head		rcu_head;
	wait_queue_head_t	mq_freeze_wq;
	struct percpu_ref	mq_usage_counter;
	struct list_head	all_q_node;

	struct blk_mq_tag_set	*tag_set;
	struct list_head	tag_set_list;
};

request_queue 对象表示针对一个 gendisk 对象的所有请求的队列。

1、初始化请求队列

struct request_queue *blk_init_queue(request_fn_proc *, spinlock_t *);

2、删除请求队列

void blk_cleanup_queue(struct request_queue *);

3、制造请求函数

void blk_queue_make_request(struct request_queue *, make_request_fn *);

6、请求项

/*
 * Try to put the fields that are referenced together in the same cacheline.
 *
 * If you modify this structure, make sure to update blk_rq_init() and
 * especially blk_mq_rq_ctx_init() to take care of the added fields.
 */
struct request {
	struct list_head queuelist;
	union {
		struct call_single_data csd;
		unsigned long fifo_time;
	};

	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;

	/*
	 * The hash is used inside the scheduler, and killed once the
	 * request reaches the dispatch list. The ipi_list is only used
	 * to queue the request for softirq completion, which is long
	 * after the request has been unhashed (and even removed from
	 * the dispatch list).
	 */
	union {
		struct hlist_node hash;	/* merge hash */
		struct list_head ipi_list;
	};

	/*
	 * 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 */

	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;
};

1、获取请求

struct request *blk_peek_request(struct request_queue *q);

2、开启请求

void blk_start_request(struct request *req)

3、获取、开启请求

struct request *blk_fetch_request(struct request_queue *q)

7、bio

/*
 * main unit of I/O for the block layer and lower layers (ie drivers and
 * stacking drivers)
 */
struct bio {
	struct bio		*bi_next;	/* request queue link */
	struct block_device	*bi_bdev;
	unsigned long		bi_flags;	/* status, command, etc */
	unsigned long		bi_rw;		/* bottom bits READ/WRITE,
						 * top bits priority
						 */

	struct bvec_iter	bi_iter;

	/* Number of segments in this BIO after
	 * physical address coalescing is performed.
	 */
	unsigned int		bi_phys_segments;

	/*
	 * To keep track of the max segment size, we account for the
	 * sizes of the first and last mergeable segments in this bio.
	 */
	unsigned int		bi_seg_front_size;
	unsigned int		bi_seg_back_size;

	atomic_t		bi_remaining;

	bio_end_io_t		*bi_end_io;

	void			*bi_private;
#ifdef CONFIG_BLK_CGROUP
	/*
	 * Optional ioc and css associated with this bio.  Put on bio
	 * release.  Read comment on top of bio_associate_current().
	 */
	struct io_context	*bi_ioc;
	struct cgroup_subsys_state *bi_css;
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
		struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
	};

	unsigned short		bi_vcnt;	/* how many bio_vec's */

	/*
	 * Everything starting with bi_max_vecs will be preserved by bio_reset()
	 */

	unsigned short		bi_max_vecs;	/* max bvl_vecs we can hold */

	atomic_t		bi_cnt;		/* pin count */

	struct bio_vec		*bi_io_vec;	/* the actual vec list */

	struct bio_set		*bi_pool;

	/*
	 * We can inline a number of vecs at the end of the bio, to avoid
	 * double allocations for a small number of bio_vecs. This member
	 * MUST obviously be kept at the very end of the bio.
	 */
	struct bio_vec		bi_inline_vecs[0];
};

1、遍历 bio

#define __rq_for_each_bio(_bio, rq)

2、遍历 bio 中所有段

#define bio_for_each_segment(bvl, bio, iter)

3、通知 bio 处理结束

bvoid bio_endio(struct bio *bio, int error)

8、硬件信息(bvec_iter)

struct bvec_iter {
	sector_t		bi_sector;	/* device address in 512 byte
						   sectors */
	unsigned int		bi_size;	/* residual I/O count */

	unsigned int		bi_idx;		/* current index into bvl_vec */

	unsigned int            bi_bvec_done;	/* number of bytes completed in
						   current bvec */
};

9、bio_vec

/*
 * was unsigned short, but we might as well be ready for > 64kB I/O pages
 */
struct bio_vec {
	struct page	*bv_page;
	unsigned int	bv_len;
	unsigned int	bv_offset;
};

10、bio逻辑架构

i.MX 6ULL 驱动开发 二十七:块设备-LMLPHP

11、request_queue、request和bio关系

i.MX 6ULL 驱动开发 二十七:块设备-LMLPHP

五、内存模拟硬盘驱动编写(使用内核请求队列)

参考:drivers\block\z2ram.c

1、编写思路

1、从 RAM 中分配内存。

2、注册逻辑块设备,为应用层提供操作对象。

3、初始化请求队列。

4、添加、初始化实际块设备,为驱动提供操作对象。

5、通过注册的请求队列函数进行数据传输(可以使用内核提供,也可以自己进行构造)。

2、驱动实现

#include "linux/init.h"
#include "linux/module.h"
#include "linux/slab.h"
#include "linux/spinlock_types.h"
#include "linux/fs.h"
#include "linux/genhd.h"
#include "linux/hdreg.h"
#include "linux/blkdev.h"

#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */
#define RAMDISK_NAME	"ramdisk"			/* 名字 */
#define RADMISK_MINOR	3					/* 表示有三个磁盘分区!不是次设备号为3! */

typedef struct{
    unsigned char *ramdiskbuf;	/* ramdisk内存空间,用于模拟块设备 */
    spinlock_t lock;			/* 自旋锁 */
    int major;					/* 主设备号 */
    struct request_queue *queue;/* 请求队列 */
    struct gendisk *gendisk; 	/* gendisk */
}newchrdev_t;
newchrdev_t newchrdev;

/*
 * @description		: 打开块设备
 * @param - dev 	: 块设备
 * @param - mode 	: 打开模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

/*
 * @description		: 释放块设备
 * @param - disk 	: gendisk
 * @param - mode 	: 模式
 * @return 			: 0 成功;其他 失败
 */
void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release\r\n");
}

/*
 * @description		: 获取磁盘信息
 * @param - dev 	: 块设备
 * @param - geo 	: 模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
	/* 这是相对于机械硬盘的概念 */
	geo->heads = 2;			/* 磁头 */
	geo->cylinders = 32;	/* 柱面 */
	geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
	return 0;
}

/* 
 * 块设备操作函数 
 */
static struct block_device_operations ramdisk_fops =
{
	.owner	 = THIS_MODULE,
	.open	 = ramdisk_open,
	.release = ramdisk_release,
	.getgeo  = ramdisk_getgeo,
};

/*
 * @description	: 处理传输过程
 * @param-req 	: 请求
 * @return 		: 无
 */
static void ramdisk_transfer(struct request *req)
{	
	unsigned long start = blk_rq_pos(req) << 9;  	/* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
	unsigned long len  = blk_rq_cur_bytes(req);		/* 大小   */

	/* bio中的数据缓冲区
	 * 读:从磁盘读取到的数据存放到buffer中
	 * 写:buffer保存这要写入磁盘的数据
	 */
	void *buffer = bio_data(req->bio);		
	
	if(rq_data_dir(req) == READ) 		/* 读数据 */
        /* 不同块设备,具体操作不同,RAM才可以使用memcpy进行处理 */
		memcpy(buffer, newchrdev.ramdiskbuf + start, len);
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(newchrdev.ramdiskbuf + start, buffer, len);

}

/*
 * @description	: 请求处理函数
 * @param-q 	: 请求队列
 * @return 		: 无
 */
void ramdisk_request_fn(struct request_queue *q)
{
	int err = 0;
	struct request *req;

	/* 循环处理请求队列中的每个请求 */
	req = blk_fetch_request(q);
	while(req != NULL) {

		/* 针对请求做具体的传输处理 */
		ramdisk_transfer(req);

		/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
		 * 循环处理完请求队列中的所有请求。
		 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ramdisk_init(void)
{
    int ret = 0;
	printk("ramdisk init\r\n");
    /* 1、申请用于ramdisk内存 */
	newchrdev.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(newchrdev.ramdiskbuf == NULL) {
		ret = -EINVAL;
		goto ram_fail;
	}

    /* 2、初始化自旋锁 */
	spin_lock_init(&newchrdev.lock);

    /* 3、注册块设备(逻辑块设备:为应用层提供一个操作对象) */
	newchrdev.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(newchrdev.major < 0) {
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", newchrdev.major);

    /* 4、分配并初始化gendisk */
	newchrdev.gendisk = alloc_disk(RADMISK_MINOR);
	if(!newchrdev.gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}

    /* 5、分配并初始化请求队列 */
	newchrdev.queue = blk_init_queue(ramdisk_request_fn, &newchrdev.lock);
	if(!newchrdev.queue) {
		ret = EINVAL;
		goto blk_init_fail;
	}

    /* 6、添加(注册)disk
     * (1)、关联逻辑块设备和物理块设备
     * (2)、为物理块设备添加操作集和请求队列
     * (3)、为物理块设备设置属性
     */
	newchrdev.gendisk->major = newchrdev.major;		/* 主设备号 */
	newchrdev.gendisk->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	newchrdev.gendisk->fops = &ramdisk_fops; 		/* 操作函数 */
	newchrdev.gendisk->private_data = &newchrdev;	/* 私有数据 */
	newchrdev.gendisk->queue = newchrdev.queue;		/* 请求队列 */
	sprintf(newchrdev.gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(newchrdev.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(newchrdev.gendisk);

    return 0;
blk_init_fail:
	put_disk(newchrdev.gendisk);
	//del_gendisk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(newchrdev.major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(newchrdev.ramdiskbuf); /* 释放内存 */
ram_fail:
    return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ramdisk_exit(void)
{
    printk("ramdisk exit\r\n");
	/* 释放gendisk */
	del_gendisk(newchrdev.gendisk);
	put_disk(newchrdev.gendisk);

	/* 清除请求队列 */
	blk_cleanup_queue(newchrdev.queue);
    /* 注销块设备 */
	unregister_blkdev(newchrdev.major, RAMDISK_NAME);
    /* 释放内存 */
	kfree(newchrdev.ramdiskbuf); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");

3、测试

# ls
ramdisk.ko
#
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
#
# ls -l /dev/ramdisk
brw-rw----    1 root     root      254,   0 Jan  1 00:13 /dev/ramdisk
#
# rmmod ramdisk.ko
ramdisk exit
#
# ls -l /dev/ramdisk
ls: /dev/ramdisk: No such file or directory
#
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
#
# rmmod ramdisk.ko
ramdisk exit
#
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
# ls -l /dev/ramdisk
brw-rw----    1 root     root      254,   0 Jan  1 00:14 /dev/ramdisk
#
# fdisk -l
Disk /dev/mmcblk0: 15 GB, 15931539456 bytes, 31116288 sectors
1936 cylinders, 255 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       ramdisk open
Boot StartCHS    EndCHS        Stramdisk release
artLBA     EndLBA    Sectors  Sizramdisk open
e Id Type
/dev/mmcblk0p1    0,13ramdisk release
0,3     1023,254,63       8192   31116287   31108096 14.8G  c Win95 FAT32 (LBA)
Disk /dev/mmcblk1: 7456 MB, 7818182656 bytes, 15269888 sectors
238592 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk1p1 *  0,32,33     4,52,48           2048      67583      65536 32.0M  c Win95 FAT32 (LBA)
/dev/mmcblk1p2    4,52,49     950,129,11       67584   15269887   15202304 7423M 83 Linux
Disk /dev/mmcblk1boot1: 4 MB, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/mmcblk1boot1 doesn't contain a valid partition table
Disk /dev/mmcblk1boot0: 4 MB, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/mmcblk1boot0 doesn't contain a valid partition table
Disk /dev/ramdisk: 2 MB, 2097152 bytes, 4096 sectors
32 cylinders, 2 heads, 64 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/ramdisk doesn't contain a valid partition table
#

六、内存模拟硬盘驱动编写(自定义请求队列)

参考:drivers\block\zram\zram_drv.c

1、编写思路

1、从 RAM 中分配内存。

2、注册逻辑块设备,为应用层提供操作对象。

3、设置“制造请求”函数。

4、添加、初始化实际块设备,为驱动提供操作对象。

5、通过注册的请求队列函数进行数据传输(可以使用内核提供,也可以自己进行构造)。

2、驱动实现

#include "linux/init.h"
#include "linux/module.h"
#include "linux/slab.h"
#include "linux/spinlock_types.h"
#include "linux/fs.h"
#include "linux/genhd.h"
#include "linux/hdreg.h"
#include "linux/blkdev.h"

#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */
#define RAMDISK_NAME	"ramdisk"			/* 名字 */
#define RADMISK_MINOR	3					/* 表示有三个磁盘分区!不是次设备号为3! */

typedef struct{
    unsigned char *ramdiskbuf;	/* ramdisk内存空间,用于模拟块设备 */
    spinlock_t lock;			/* 自旋锁 */
    int major;					/* 主设备号 */
    struct request_queue *queue;/* 请求队列 */
    struct gendisk *gendisk; 	/* gendisk */
}newchrdev_t;
newchrdev_t newchrdev;

/*
 * @description		: 打开块设备
 * @param - dev 	: 块设备
 * @param - mode 	: 打开模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

/*
 * @description		: 释放块设备
 * @param - disk 	: gendisk
 * @param - mode 	: 模式
 * @return 			: 0 成功;其他 失败
 */
void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release\r\n");
}

/*
 * @description		: 获取磁盘信息
 * @param - dev 	: 块设备
 * @param - geo 	: 模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
	/* 这是相对于机械硬盘的概念 */
	geo->heads = 2;			/* 磁头 */
	geo->cylinders = 32;	/* 柱面 */
	geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
	return 0;
}

/* 
 * 块设备操作函数 
 */
static struct block_device_operations ramdisk_fops =
{
	.owner	 = THIS_MODULE,
	.open	 = ramdisk_open,
	.release = ramdisk_release,
	.getgeo  = ramdisk_getgeo,
};

#if 0
/*
 * @description	: 处理传输过程
 * @param-req 	: 请求
 * @return 		: 无
 */
static void ramdisk_transfer(struct request *req)
{	
	unsigned long start = blk_rq_pos(req) << 9;  	/* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
	unsigned long len  = blk_rq_cur_bytes(req);		/* 大小   */

	/* bio中的数据缓冲区
	 * 读:从磁盘读取到的数据存放到buffer中
	 * 写:buffer保存这要写入磁盘的数据
	 */
	void *buffer = bio_data(req->bio);		
	
	if(rq_data_dir(req) == READ) 		/* 读数据 */
        /* 不同块设备,具体操作不同,RAM才可以使用memcpy进行处理 */
		memcpy(buffer, newchrdev.ramdiskbuf + start, len);
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(newchrdev.ramdiskbuf + start, buffer, len);

}

/*
 * @description	: 请求处理函数
 * @param-q 	: 请求队列
 * @return 		: 无
 */
void ramdisk_request_fn(struct request_queue *q)
{
	int err = 0;
	struct request *req;

	/* 循环处理请求队列中的每个请求 */
	req = blk_fetch_request(q);
	while(req != NULL) {

		/* 针对请求做具体的传输处理 */
		ramdisk_transfer(req);

		/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
		 * 循环处理完请求队列中的所有请求。
		 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}
#endif

/*
 * @description	: “制造请求”函数
 * @param-q 	: 请求队列
 * @return 		: 无
 */
void ramdisk_make_request_fn(struct request_queue *q, struct bio *bio)
{
	int offset;
	struct bio_vec bvec;
	struct bvec_iter iter;
	unsigned long len = 0;

	offset = (bio->bi_iter.bi_sector) << 9;	/* 获取要操作的设备的偏移地址 */

	/* 处理bio中的每个段 */
	bio_for_each_segment(bvec, bio, iter){
		char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;
		len = bvec.bv_len;

		if(bio_data_dir(bio) == READ)	/* 读数据 */
			/* 不同块设备,具体操作不同,RAM才可以使用memcpy进行处理 */
			memcpy(ptr, newchrdev.ramdiskbuf + offset, len);
		else if(bio_data_dir(bio) == WRITE)	/* 写数据 */
			memcpy(newchrdev.ramdiskbuf + offset, ptr, len);
		offset += len;
	}
	set_bit(BIO_UPTODATE, &bio->bi_flags);
	bio_endio(bio, 0);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ramdisk_init(void)
{
    int ret = 0;
	printk("ramdisk init\r\n");
    /* 1、申请用于ramdisk内存 */
	newchrdev.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(newchrdev.ramdiskbuf == NULL) {
		ret = -EINVAL;
		goto ram_fail;
	}

    /* 2、初始化自旋锁 */
	spin_lock_init(&newchrdev.lock);

    /* 3、注册块设备(逻辑块设备:为应用层提供一个操作对象) */
	newchrdev.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(newchrdev.major < 0) {
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", newchrdev.major);

    /* 4、分配并初始化gendisk */
	newchrdev.gendisk = alloc_disk(RADMISK_MINOR);
	if(!newchrdev.gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}

    /* 5、分配并初始化请求队列 */
#if 0
	newchrdev.queue = blk_init_queue(ramdisk_request_fn, &newchrdev.lock);
	if(!newchrdev.queue) {
		ret = EINVAL;
		goto blk_init_fail;
	}
#endif
	newchrdev.queue = blk_alloc_queue(GFP_KERNEL);
	if(!newchrdev.queue){
		ret = -EINVAL;
		goto blk_allo_fail;
	}

	/* 6、设置“制造请求”函数 */
	blk_queue_make_request(newchrdev.queue, ramdisk_make_request_fn);

    /* 7、添加(注册)disk
     * (1)、关联逻辑块设备和物理块设备
     * (2)、为物理块设备添加操作集和请求队列
     * (3)、为物理块设备设置属性
     */
	newchrdev.gendisk->major = newchrdev.major;		/* 主设备号 */
	newchrdev.gendisk->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	newchrdev.gendisk->fops = &ramdisk_fops; 		/* 操作函数 */
	newchrdev.gendisk->private_data = &newchrdev;	/* 私有数据 */
	newchrdev.gendisk->queue = newchrdev.queue;		/* 请求队列 */
	sprintf(newchrdev.gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(newchrdev.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(newchrdev.gendisk);

    return 0;
blk_allo_fail:
	put_disk(newchrdev.gendisk);
	//del_gendisk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(newchrdev.major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(newchrdev.ramdiskbuf); /* 释放内存 */
ram_fail:
    return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ramdisk_exit(void)
{
    printk("ramdisk exit\r\n");
	/* 释放gendisk */
	del_gendisk(newchrdev.gendisk);
	put_disk(newchrdev.gendisk);

	/* 清除请求队列 */
	blk_cleanup_queue(newchrdev.queue);
    /* 注销块设备 */
	unregister_blkdev(newchrdev.major, RAMDISK_NAME);
    /* 释放内存 */
	kfree(newchrdev.ramdiskbuf); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");

3、测试

# ls
ramdisk.ko
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
# ls -l /dev/ramdisk
brw-rw----    1 root     root      254,   0 Jan  1 00:51 /dev/ramdisk
#
# rmmod ramdisk.ko
ramdisk exit
#
# ls -l /dev/ramdisk
ls: /dev/ramdisk: No such file or directory
#
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
#
# ls -l /dev/ramdisk
brw-rw----    1 root     root      254,   0 Jan  1 00:51 /dev/ramdisk
#
# rmmod ramdisk.ko
ramdisk exit
#
# random: nonblocking pool is initialized
# ls -l /dev/ramdisk
ls: /dev/ramdisk: No such file or directory
#
#
#
# insmod ramdisk.ko
ramdisk init
ramdisk major = 254
ramdisk open
ramdisk release
#
# fdisk -l
Disk /dev/mmcblk0: 15 GB, 15931539456 bytes, 31116288 sectors
1936 cylinders, 255 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        Sramdisk open
tartLBA     EndLBA    Sectors  Siramdisk release
ze Id Type
/dev/mmcblk0p1    0,1ramdisk open
30,3     1023,254,63       8192   31116287   31108096 14.8G  c Wiramdisk release
n95 FAT32 (LBA)
Disk /dev/mmcblk1: 7456 MB, 7818182656 bytes, 15269888 sectors
238592 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk1p1 *  0,32,33     4,52,48           2048      67583      65536 32.0M  c Win95 FAT32 (LBA)
/dev/mmcblk1p2    4,52,49     950,129,11       67584   15269887   15202304 7423M 83 Linux
Disk /dev/mmcblk1boot1: 4 MB, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/mmcblk1boot1 doesn't contain a valid partition table
Disk /dev/mmcblk1boot0: 4 MB, 4194304 bytes, 8192 sectors
128 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/mmcblk1boot0 doesn't contain a valid partition table
Disk /dev/ramdisk: 2 MB, 2097152 bytes, 4096 sectors
32 cylinders, 2 heads, 64 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Disk /dev/ramdisk doesn't contain a valid partition table
#
12-01 20:09