linux块设备驱动简述(Linux驱动开发篇)_linux设备驱动开发详解-程序员宅基地

技术标签: linux  Linux驱动-正点原子imx6ull  驱动开发  

一、块设备

  • 块设备驱动是Linux 三大驱动类型之一。
  • 块设备驱动要远比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统。
  • 块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。

块设备和字符设备的区别?

  • 块设备以块(是VFS基本数据传输单位)为单位进行读写,字符设备以字节的形式进行读写,
  • 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。(这样就减少了对块设备的擦除次数,提高了块设备寿命。)
  • 字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问
  • 总结一下:
    字符设备:实时、按照字节访问,无缓冲。
    块设备:带有缓冲区,按照块大小进行访问。(一种是随机访问。一种是按顺序访问)

块设备的类别

  • 针对不同的存储设备实现了不同的I/O调度算法。
  • 块设备结构的不同其 I/O算法也会不同,比如对于EMMC、SD卡、NAND Flash这类没有任何机械设备的存储设备就可以任意读写任何的扇区(块设备物理存储单元)。
  • 但是对于机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能。
  • ===这个后面的框架你会发现是有所不同的。

二、块设备驱动框架简述

  • 在了解具体的块设备驱动框架的大概,我们现需要了解一下其中的结构体和一些函数
/*
@	定义在include/linux/fs.h文件中
@	block_device 结构体 
@	linux 内核使用 block_device 表示块设备
*/
 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; //比如一个硬盘或者分区,如果是硬盘的话 bd_disk就指向通用磁盘结构 gendisk
    struct request_queue *bd_queue; 
    struct list_head      bd_list;
    
    unsigned long         bd_private; 
    /* The counter of freeze processes */ 
    int               bd_fsfreeze_count; 
    /* Mutex for freeze */ 
    struct mutex          bd_fsfreeze_mutex; 
 }; 
/*
@ 	向内核注册新的块设备、申请设备号
@	major:主设备号。 
@	name:块设备名字。 
@	返回值:如果参数 major 在 1~255 之间的话表示自定义主设备号,那么返回 0 表示注册成功,如果返回负值的话表示注册失败。如果 major 为 0 的话表示由系统自动分配主设备号,那么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败
*/
int register_blkdev(unsigned int    major, const char    *name) 
/*
@	如果不使用某个块设备了,那么就需要注销掉
@	major:要注销的块设备主设备号
@	name:要注销的块设备名字。
@	返回值:无
*/
void unregister_blkdev(unsigned int major, const char *name)





/*
@	定义在 include/linux/genhd.h中
@	gendisk 结构体 
@	linux内核使用gendisk来描述一个磁盘设备
*/
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;       /*磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一样,次设备号不同*/
 
    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; /*字符设备操作集 file_operations一样*/
    struct request_queue *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; 
}
/*************************************带有机械磁头的磁盘
@	使用gendisk之前要先申请
@	minors:次设备号数量,也就是gendisk对应的分区数量。
@	返回值:成功:返回申请到的 gendisk,失败:NULL。
*/
struct gendisk *alloc_disk(int    minors) 
/*
@	使用 add_disk 函数将申请到的gendisk添加到内核中
@	disk:要添加到内核的 gendisk。 
@	返回值:无。 
*/
void add_disk(struct gendisk    *disk) 
/*
@	在初始化 gendisk 的时候也需要设置其容量
@	disk:要设置容量的 gendisk。
@	size:磁盘容量大小,注意这里是扇区数量。块设备中最小的可寻址单元是扇区,一个扇区一般是 512字节比如一个 2MB 的磁盘,其扇区数量就是(2*1024*1024)/512=4096。
@	返回值:无。 
*/
void set_capacity(struct gendisk    *disk, sector_t    size) 
/*
@	内核会通过 get_disk 和 put_disk 这两个函数来调整 gendisk 的引用计数
@	get_disk是增加 gendisk的引用计数
@	put_disk是减少gendisk的引用计数
*/
truct kobject *get_disk(struct gendisk    *disk) 
void put_disk(struct gendisk    *disk) 
/*
@	删除gendisk的话可以使用函数del_gendisk
@	gp:要删除的gendisk。
@	返回值:无。 
*/
void del_gendisk(struct gendisk    *gp) 






/*
@	定义在include/linux/blkdev.h中
@	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 *,  int rw); /*读写指定页*/
    int (*ioctl) (struct block_device *, fmode_t, unsigned,  unsigned long); /*32-32块设备的IO控制*/
    int (*compat_ioctl) (struct block_device *, fmode_t, unsigned,  unsigned long); /*64-32块设备IO控制*/
    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; /*表示此结构属于哪个模块*/
 }; 
  • 我们上面的介绍的fops操作集里面不像字符设备一样里面有read和write函数,但是我们呢,是肯定要进行读写操作的。

  • 要想了解整个流程,首先就要引入块设备驱动中非常重要的三个角色。
    request_queue、
    request
    bio
    在这里插入图片描述

  • 内核将对块设备的读写都发送到请求队列 request_queue 中,request_queue 中是大量的request(请求结构体),而 request 又包含了 bio,bio 保存了读写相关数据。比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等。

  • 下面我们继续了解一下其中的结构体和一些函数目的就是可以了解requestqueue和quest。

/*
@	上面谈到过block_device 里面含有requet_queue.
@	因为在编写块设备驱动的时候,每个磁盘(gendisk)都要分配一个 request_queue
*/
/*============================================================机械磁头硬盘
@	这个一般用于像机械硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程
@	申请并初始化一个 request_queue
@	rfn:请求处理函数指针,每个 request_queue 都要有一个请求处理函数,
@	lock:自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。请求队列会使用这个自旋锁。
@	返回值:如果为NULL的话表示失败,成功的话就返回申请到的request_queue 地址。
*/
request_queue *blk_init_queue(request_fn_proc   *rfn, spinlock_t    *lock) 
/*
@	请求处理函数需要驱动编写人员自行实现
@	与上面依赖
@	q:请求队列
*/
void (request_fn_proc) (struct request_queue *q) /*我们只传入了request+_queue*/
/*===========================================================EMMC、SD卡非机械设备
@	但是对于 EMMC、SD 卡这样的非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了(分配请求队列+绑定制造请求函数) 
@	gfp_mask: 内存分配掩码
@	返回值:申请到的无 I/O调度的request_queue。
*/
struct request_queue *blk_alloc_queue(gfp_t    gfp_mask) 
/*
@ 	上面blk_alloc_queue函数申请到的请求队列绑定一个“制造请求”函数
@	q:需要绑定的请求队列,也就是 blk_alloc_queue申请到的请求队列。
@	mfn:需要绑定的“制造”请求函数
@	返回值:无
*/
void blk_queue_make_request(struct request_queue    *q, make_request_fn    *mfn) 
/*
@ 	与上面依赖
@	“制造请求”函数需要驱动编写人员实现
@	返回值:无。
*/
void (make_request_fn) (struct request_queue  *q, struct bio *bio) 


/*
@	当卸载块设备驱动的时候我们还需要删除掉前面申请到的 request_queue
@	q:需要删除的请求队列
@	返回值:无。
*/
void blk_cleanup_queue(struct request_queue    *q) 




/*
@	请求队列(request_queue)里面包含的就是一系列的请求(request)
*/
/*
@	从request_queue中依次获取每个request
@	q:指定request_queue。
@	返回值:request_queue 中下一个要处理的请求(request),如果没有要处理的请求就返回NULL
@
*/
request *blk_peek_request(struct request_queue   *q) 

/*
@	获取到下一个要处理的请求以后就要开始处理这个请求
@	req:要开始处理的请求
@	返回值:无。 
*/
void blk_start_request(struct request    *req) 

/*
@	此函数功能等是上面的两个函数的合体==一步到位处理请求
@	q:指定request_queue。
*/
struct request *blk_fetch_request(struct request_queue *q) 
{
     
    struct request *rq; 
 
    rq = blk_peek_request(q); 
    if (rq) 
          blk_start_request(rq); 
    return rq; 
} 
  • 每个request里面里面会有多个bio,bio保存着最终要读写的数据、地址等信息上层应用程序对于块设备的读写会被构造成一个或多个bio结构,bio结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio提交给I/O调度器, I/O 调度器会将这些bio构造成request结构,而一个物理存储设备对应一个 request_queue,request_queue里面顺序存放着一系列的 request。新产生的 bio可能被合并到 request_queue里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由I/O 调度器来完成的
    在这里插入图片描述

  • 下面我们继续了解一下其中的结构体和一些函数目的就是可以了解quest中的bio。

/*
@	定义在 include/linux/blk_types.h中
@	 bio 结构体 
*/
 struct bio {
     
    struct bio         *bi_next;     /* 请求队列的下一个 bio   */ 
    struct block_device  *bi_bdev;    /* 指向块设备      */ 
    unsigned long         bi_flags;    /* bio 状态等信息    */ 
    unsigned long         bi_rw;      /* I/O 操作,读或写    */ 
    struct bvec_iter      bi_iter;    /* I/O 操作,读或写=====    */ 
    unsigned int          bi_phys_segments; 
    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;  
 #endif 
    }; 
  
    unsigned short      bi_vcnt;       /* bio_vec 列表中元素数量  */ 
    unsigned short      bi_max_vecs;      /* bio_vec 列表长度      */ 
    atomic_t            bi_cnt;       /* pin count */ 
    struct bio_vec        *bi_io_vec;   /* bio_vec 列表 */ 
    struct bio_set        *bi_pool; 
    struct bio_vec        bi_inline_vecs[0]; 
 }; 
 /*
@	属于bio中
@	定义在 include/linux/blk_types.h中
@	 bio 结构体 
*/
 struct bvec_iter {
     
     sector_t       bi_sector;    /* I/O 请求的设备起始扇区(512 字节) */ 
     unsigned int    bi_size;      /* 剩余的 I/O 数量           */ 
     unsigned int  bi_idx;       /* blv_vec 中当前索引         */ 
     unsigned int  bi_bvec_done;   /* 当前 bvec 中已经处理完成的字节数  */ 
 }; 
/*
@	属于bio中
@	定义在 include/linux/blk_types.h中
@	bio 结构体 
*/
 struct bio_vec {
     
     struct page   *bv_page;    /* 页   */ 
      unsigned int   bv_len;    /* 长度 */ 
     unsigned int    bv_offset;   /* 偏移 */ 
 }; 
  • 上面的bio和bio_vec和bvec_iter的关系
    在这里插入图片描述
  • 我们对于物理存储设备的操作不外乎就是将 RAM 中的数据写入到物理存储设备中,或者、将物理设备中的数据读取到 RAM 中去处理。数据传输三个要求:数据源、数据长度以及数据目的地,也就是你要从物理存储设备的哪个地址开始读取、读取到 RAM 中的哪个地址处、读取的数据长度是多少。
    bi_iter这个结构体成员变量就用于描述物理存储设备地址信息,比如要操作的扇区地址
    bi_io_vec指向 bio_vec数组首地址,bio_vec数组就是RAM信息,比如页地址、页偏移以及长度

三、简单实例

  • 接下来我们使用开发板上的 RAM 模拟一段块设备,也就是ramdisk,然后编写块设备驱动
  • 我们上面也提到过我们在上面请求队列的时候分为机械硬盘比如SD卡非机械硬盘这两种不同的
  • 一般 blk_alloc_queue 和blk_queue_make_request 是搭配在一起使用的,用于那么非机械的存储设备、无需 I/O 调度器,比如 EMMC、SD 卡等。blk_init_queue 函数会给请求队列分配一个 I/O调度器,用于机械存储设备,比如机械硬盘等。

1、传统的使用请求队列的时候,也就是针对机械硬盘的时候如何编写驱动

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


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

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

};

struct ramdisk_dev ramdisk;		/* ramdisk设备 */

/*
 * @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) 		/* 读数据 */	
		memcpy(buffer, ramdisk.ramdiskbuf + start, len);
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(ramdisk.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内存 */
	ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(ramdisk.ramdiskbuf == NULL) {
    
		ret = -EINVAL;
		goto ram_fail;
	}

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

	/* 3、注册块设备 */
	ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(ramdisk.major < 0) {
    
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", ramdisk.major);

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

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

	/* 6、添加(注册)disk */
	ramdisk.gendisk->major = ramdisk.major;		/* 主设备号 */
	ramdisk.gendisk->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	ramdisk.gendisk->fops = &ramdisk_fops; 		/* 操作函数 */
	ramdisk.gendisk->private_data = &ramdisk;	/* 私有数据 */
	ramdisk.gendisk->queue = ramdisk.queue;		/* 请求队列 */
	sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk);

	return 0;

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

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

	/* 清除请求队列 */
	blk_cleanup_queue(ramdisk.queue);

	/* 注销块设备 */
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);

	/* 释放内存 */
	kfree(ramdisk.ramdiskbuf); 
}

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

  • 当我们把上面细心阅读后,会发现块设备驱动框架还是有些字符设备的影子的,只是在具体实现上要复杂一下。

2、对于EMMC、SD、ramdisk这样没有机械结构的存储设备,我们可以直接访问任意一个扇区,因此可以不需要 I/O 调度器,也就不需要请求队列了

  • 这里主要贴出区别的地方
/*
@	驱动函数入口
*/
 static int __init ramdisk_init(void) 
 {
     
.... 
    /* 5、分配请求队列 */ 
    ramdisk.queue = blk_alloc_queue(GFP_KERNEL); 
    if(!ramdisk.queue){
     
        ret = -EINVAL; 
        goto blk_allo_fail; 
    } 
  
    /* 6、设置“制造请求”函数 */ 
    blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);
  
      /* 7、添加(注册)disk */ 
    ramdisk.gendisk->major = ramdisk.major;   /* 主设备号 */ 
    ramdisk.gendisk->first_minor = 0;        /* 起始次设备号 */ 
    ramdisk.gendisk->fops = &ramdisk_fops;        /* 操作函数 */ 
    ramdisk.gendisk->private_data = &ramdisk;     /* 私有数据 */ 
    ramdisk.gendisk->queue = ramdisk.queue;       /* 请求队列 */ 
    sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */ 
    set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);  /* 设备容量*/ 
    add_disk(ramdisk.gendisk); 
.... 
    return ret; 
 } 

 /* 
  * @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)   /* 读数据 */ 
              memcpy(ptr, ramdisk.ramdiskbuf + offset, len); 
        else if(bio_data_dir(bio) == WRITE) /* 写数据 */ 
              memcpy(ramdisk.ramdiskbuf + offset, ptr, len); 
        offset += len; 
    } 
    set_bit(BIO_UPTODATE, &bio->bi_flags); 
    bio_endio(bio, 0); 
 }   
  • (分配请求队列+绑定制造请求函数) 这部分代替了原有的初始化请求队列

整体例子代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

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

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

};

struct ramdisk_dev ramdisk;		/* ramdisk设备 */

/*
 * @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-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)	/* 读数据 */
			memcpy(ptr, ramdisk.ramdiskbuf + offset, len);
		else if(bio_data_dir(bio) == WRITE)	/* 写数据 */
			memcpy(ramdisk.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内存 */
	ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(ramdisk.ramdiskbuf == NULL) {
    
		ret = -EINVAL;
		goto ram_fail;
	}

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

	/* 3、注册块设备 */
	ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */
	if(ramdisk.major < 0) {
    
		goto register_blkdev_fail;
	}  
	printk("ramdisk major = %d\r\n", ramdisk.major);

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

	/* 5、分配请求队列 */
	ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
	if(!ramdisk.queue){
    
		ret = -EINVAL;
		goto blk_allo_fail;
	}

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

	/* 7、添加(注册)disk */
	ramdisk.gendisk->major = ramdisk.major;		/* 主设备号 */
	ramdisk.gendisk->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	ramdisk.gendisk->fops = &ramdisk_fops; 		/* 操作函数 */
	ramdisk.gendisk->private_data = &ramdisk;	/* 私有数据 */
	ramdisk.gendisk->queue = ramdisk.queue;		/* 请求队列 */
	sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk);

	return 0;

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

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

	/* 清除请求队列 */
	blk_cleanup_queue(ramdisk.queue);

	/* 注销块设备 */
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);

	/* 释放内存 */
	kfree(ramdisk.ramdiskbuf); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47397155/article/details/123010601

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签