Linux驱动开发(十六)---块设备驱动

2023-10-27

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
《Linux驱动开发(十四)—USB驱动开发学习(键盘+鼠标)》
《Linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》

本章介绍及注意事项

今天来学习一下linux驱动中的另一种关键驱动,块设备驱动。
不过在我的树莓派系统下,5.15的内核中,这块的函数已经大幅度进行了修改,所以前半部分内容来自于网络的博客学习,后面有专门介绍高版本内核的ramdisk开发。在这里插入图片描述

块设备(blockdevice)

是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。

块设备中有如下几个大小概念的定义

  1. 扇区(Sectors)
    任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte。(对设备而言)
  2. 块 (Blocks)
    由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)
  3. 段(Segments)
    由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。

页、段、块、扇区之间的关系图如下:
在这里插入图片描述

块设备分类

这里我们按照驱动的不同实现,将块设备分成了两类:

  • 非机械设备
    比如EMMC、SD卡、NAND Flash这类没有任何机械设备的存储设备,可以任意读写任何的扇区(块设备物理存储单元)。
  • 机械设备
    机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动。

分类的原因就在于非机械设备,可以任意读取,不会有性能的差异。但是机械设备就需要内核实现专门的算法,将杂乱的访问按照一定的顺序进行排列,可以有效提高磁盘性能。
在这里插入图片描述

驱动过程

这里的驱动过程,也是低版本内核块设备驱动的写法。

Created with Raphaël 2.2.0 入口 注册块设备 register_blkdev 分配磁盘 alloc_disk 分配请求队列 设置并注册磁盘 add_disk 结束

那么在分配请求队列处理上,根据前面的设备分类不同,有两种处理方法,后面再讲。
先讲一下:请求队列结构request_queue

每个块设备都有一个请求队列,每个请求队列单独执行I/O调度。
每个请求又由多个bio组成。
每个bio对应1个I/O请求,

大概如下图所示
在这里插入图片描述

IO调度算法可将连续的bio合并成1个请求。
在这里插入图片描述

关键数据结构

块设备对象结构 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 */
	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;
	u8			bd_partno;
	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 backing_dev_info *bd_bdi;
	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;
} __randomize_layout;

磁盘结构体

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 short events;		/* supported events */
	unsigned short event_flags;	/* flags related to event processing */

	/* 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 rw_semaphore lookup_sem;
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct kobject integrity_kobj;
#endif	/* CONFIG_BLK_DEV_INTEGRITY */
	int node_id;
	struct badblocks *bb;
	struct lockdep_map lockdep_map;
};

磁盘结构体中的操作描述符,类似于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);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	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;
};

请求request结构

struct request {
    //用于挂在请求队列链表的节点,使用函数blkdev_dequeue_request访问它,而不能直接访struct list_head queuelist;
    struct list_head donelist;  /*用于挂在已完成请求链表的节点*/
    struct request_queue *q;   /*指向请求队列*/
    unsigned int cmd_flags;    /*命令标识*/
    enum rq_cmd_type_bits cmd_type;  /*命令类型*/
    /*各种各样的扇区计数*/
   /*为提交i/o维护bio横断面的状态信息,hard_*成员是块层内部使用的,驱动程序不应该改变
它们*/
    sector_t sector;     /*将提交的下一个扇区*/
    sector_t hard_sector;        /* 将完成的下一个扇区*/
    unsigned long nr_sectors;  /* 整个请求还需要传送的扇区数*/
    unsigned long hard_nr_sectors; /* 将完成的扇区数*/
 /*在当前bio中还需要传送的扇区数 */
    unsigned int current_nr_sectors;
    /*在当前段中将完成的扇区数*/
    unsigned int hard_cur_sectors;
    struct bio *bio;     /*请求中第一个未完成操作的bio*、
    struct bio *biotail; /*请求链表中末尾的bio*、
    struct hlist_node hash;  /*融合 hash */
    /* rb_node仅用在I/O调度器中,当请求被移到分发队列中时,
请求将被删除。因此,让completion_data与rb_node分享空间*/
    union {
        struct rb_node rb_node;   /* 排序/查找*/
        void *completion_data;
    }; 作者:补给站Linux内核 https://www.bilibili.com/read/cv17063262/ 出处:bilibili

bio结构

struct bio {
sector_t        bi_sector;//该bio结构所要传输的第一个(512字节)扇区:磁盘的位置
struct bio        *bi_next;    //请求链表
struct block_device    *bi_bdev;//相关的块设备
unsigned long        bi_flags//状态和命令标志
unsigned long        bi_rw; //读写
unsigned short        bi_vcnt;//bio_vesc偏移的个数
unsigned short        bi_idx;    //bi_io_vec的当前索引
unsigned short        bi_phys_segments;//结合后的片段数目
unsigned short        bi_hw_segments;//重映射后的片段数目
unsigned int        bi_size;    //I/O计数
unsigned int        bi_hw_front_size;//第一个可合并的段大小;
unsigned int        bi_hw_back_size;//最后一个可合并的段大小
unsigned int        bi_max_vecs;    //bio_vecs数目上限
struct bio_vec        *bi_io_vec;    //bio_vec链表:内存的位置
bio_end_io_t        *bi_end_io;//I/O完成方法
atomic_t        bi_cnt; //使用计数
void            *bi_private; //拥有者的私有方法
bio_destructor_t    *bi_destructor;    //销毁方法
};

写出这种框架的人,也是厉害,光看就头疼了
在这里插入图片描述

核心操作函数

注意,这里都是低版本内核的关键操作,高版本好多函数都找不到了。
在这里插入图片描述
注册块设备

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

申请磁盘

#define alloc_disk(minors) alloc_disk_node(minors, NUMA_NO_NODE)

设置磁盘容量

void set_capacity(struct gendisk *disk, sector_t size)

添加磁盘

void add_disk(struct gendisk *disk)

释放磁盘

void del_gendisk(struct gendisk *gd)

根据不同类型的块设备,请求队列的使用不相同

  • 机械类块设备
    需要内核帮忙处理算法,所以接口采用
    blk_init_queue(ramdisk_request_fn, &ramdisk.lock);

  • 非机械类的
    首先申请队列
    struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
    然后制造请求函数
    void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

两类设备驱动例子

代码参考自 栋哥修炼日记的 《linux块设备驱动简述(Linux驱动开发篇)》
这里都是低版本内核之前的写法。
在这里插入图片描述

机械类

#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_des; 	/* 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_des = alloc_disk(RADMISK_MINOR);
	if(!ramdisk.gendisk_des) {
		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_des->major = ramdisk.major;		/* 主设备号 */
	ramdisk.gendisk_des->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	ramdisk.gendisk_des->fops = &ramdisk_fops; 		/* 操作函数 */
	ramdisk.gendisk_des->private_data = &ramdisk;	/* 私有数据 */
	ramdisk.gendisk_des->queue = ramdisk.queue;		/* 请求队列 */
	sprintf(ramdisk.gendisk_des->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk_des, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk_des);

	return 0;

blk_init_fail:
	put_disk(ramdisk.gendisk_des);
	//del_gendisk(ramdisk.gendisk_des);
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_des);
	put_disk(ramdisk.gendisk_des);

	/* 清除请求队列 */
	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");

非机械类

#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_des; 	/* 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_des = alloc_disk(RADMISK_MINOR);
	if(!ramdisk.gendisk_des) {
		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_des->major = ramdisk.major;		/* 主设备号 */
	ramdisk.gendisk_des->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	ramdisk.gendisk_des->fops = &ramdisk_fops; 		/* 操作函数 */
	ramdisk.gendisk_des->private_data = &ramdisk;	/* 私有数据 */
	ramdisk.gendisk_des->queue = ramdisk.queue;		/* 请求队列 */
	sprintf(ramdisk.gendisk_des->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk_des, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk_des);

	return 0;

blk_allo_fail:
	put_disk(ramdisk.gendisk_des);
	//del_gendisk(ramdisk.gendisk_des);
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_des);
	put_disk(ramdisk.gendisk_des);

	/* 清除请求队列 */
	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");

这个代码没有运行过,要是出错了也别来找我啊。
在这里插入图片描述

Ramdisk使用

  1. 格式化ramdisk
    需要将ramdisk用一种文件系统格式化,由于没有必要采用日志文件系统,因此仅用EXT2格式即可,以仅需要使用ram0为例:
    [root]# mke2fs -m 0 /dev/ramdisk

  2. 创建挂载点,挂载ramdisk
    在已经格式化了ramdisk之后,必须为其创建一个挂载点,然后将ramdisk挂载到该挂载点后使用。
    [root]# mkdir /mnt/rd; mount /dev/ramdisk /mnt/rd

  3. 查看验证挂载是否成功及文件系统信息
    [root]# mount | grep ramdisk
    [root]# df -h | grep ramdisk

  4. 进一步查看ram0的详细信息
    [root]# tune2fs -l /dev/ramdisk

  5. 修改挂载点的使用权限
    [root]# chown van:root /mnt/rd
    [root]# chmod 0770 /mnt/rd

  6. 验证并查看挂载点的权限是否修改
    [root]# ls -ald /mnt/rd

  7. 使用ramdisk
    完成以上工作后,就可以像在磁盘分区上一样在ramdisk上进创建、复制、移动、删除、编辑文件了。如果需要移除ramdisk,采用以下命令解除挂载即可:
    [root]# umount -v /mnt/rd

在这里插入图片描述

高版本

我用的树莓派版本是5.1.55。在内核的源码中,已经没有了那几个核心函数
在这里插入图片描述
思路已经改变了。
现在的ramdisk已经是通过块设备操作来进行读写操作了。代码可以参考brd.c。

/* 
 * 块设备操作函数 
 */
static struct block_device_operations ramdisk_fops =
{
	.owner =		THIS_MODULE,
	.open	 = 		myrd_open,
	.release = 		myrd_release,
	.submit_bio =	myrd_submit_bio,
	.rw_page =		myrd_rw_page,
	.getgeo  = 		myrd_getgeo,
};

我也写了一个类似前面的自定义方式产生myramdisk。
在这里插入图片描述

《代码下载地址》
厉不厉害,全网独一份的代码呢
在这里插入图片描述

结束语

昨天也是情人节,我和姐姐两家人去看古仔的新电影《明日战记》,场面还可以,不过情节单调,人物也单调,为什么还要找两个五六十岁的人来扮演两个战士,普通市民刘先生都快六十岁的人了。香港电影能拿得出来的,还是那些老面孔,真的是没落了好多。
在这里插入图片描述
不过最后没有人牺牲,也算是一个好的结尾,适合带着小孩子观看。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux驱动开发(十六)---块设备驱动 的相关文章

  • 如何通过替换为空页映射来取消映射 mmap 文件

    Linux 用户空间有没有办法用空页面 映射自 dev null 或者可能是一个空页面 重复映射到从文件映射的页面的顶部 对于上下文 我想找到这个 JDK bug 的修复 https bugs openjdk java net browse
  • 从 csv 文件中删除特定列,保持输出上的相同结构[重复]

    这个问题在这里已经有答案了 我想删除第 3 列并在输出文件中保留相同的结构 输入文件 12 10 10 10 10 1 12 23 1 45 6 7 11 2 33 45 1 2 1 2 34 5 6 I tried awk F 3 fil
  • 如何禁用 GNOME 桌面屏幕锁定? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何阻止 GNOME 桌面在几分钟空闲时间后锁定屏幕 我已经尝试过官方手册了在红帽 https access redhat com doc
  • nginx 上的多个网站和可用网站

    通过 nginx 的基本安装 您的sites available文件夹只有一个文件 default 怎么样sites available文件夹的工作原理以及如何使用它来托管多个 单独的 网站 只是为了添加另一种方法 您可以为您托管的每个虚拟
  • 如何根据 HTTP 请求使用 Python 和 Flask 执行 shell 命令并流输出?

    下列的这个帖子 https stackoverflow com questions 15092961 how to continuously display python output in a webpage 我能够tail f网页的日志
  • 如何在bash中使用jq从变量中包含的json中提取值

    我正在编写一个 bash 脚本 其中存储了一个 json 值 现在我想使用 Jq 提取该 json 中的值 使用的代码是 json val code lyz1To6ZTWClDHSiaeXyxg redirect to http examp
  • fopen 不返回

    我在 C 程序中使用 fopen 以只读模式 r 打开文件 但就我而言 我观察到 fopen 调用没有返回 它不返回 NULL 或有效指针 执行在 fopen 调用时被阻止 文件补丁绝对正确 我已经验证过 并且不存在与权限相关的问题 任何人
  • Linux 中的动态环境变量?

    Linux 中是否可以通过某种方式拥有动态环境变量 我有一个网络服务器 网站遵循以下布局 site qa production 我想要一个环境变量 例如 APPLICATION ENV 当我在 qa 目录中时设置为 qa 当我在生产目录中时
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 无法使用 wget 在 CentOS 机器上安装 oracle jdk

    我想在CentOS上安装oracle java jdk 8 我无法安装 java jdk 因为当我尝试使用命令安装 java jdk 时 root ADARSH PROD1 wget no cookies no check certific
  • 两种情况或 if 哪个更快? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我必须制作一个 非常 轻的脚本 它将接受用户的选项并调用脚本中的函数来执行一些任务 现在我可以使用 IF 和 CASE 选项 但我想知道两
  • PHP 从命令行启动 gui 程序,但 apache 不启动

    首先 我阅读了有类似问题的人的一些帖子 但所有答案都没有超出导出 DISPLAY 0 0 和 xauth cookies 这是我的问题 提前感谢您的宝贵时间 我开发了一个小库 它使用 OpenGL 和 GLSL 渲染货架 过去几天我将它包装
  • 如何将目录及其子目录中的所有 PDF 文件复制到一个位置?

    如何全部复制PDF文件从目录及其子目录到单个目录 实际上还有更多的文件 并且深度有些任意 假设四个目录的最大深度是公平的 我想这些文件需要重命名 如果a pdf例如 位于多个目录中 因为我会adding https ebooks stack
  • Linux 内核标识符中前导和尾随下划线的含义是什么?

    我不断遇到一些小约定 比如 KERNEL Are the 在这种情况下 是内核开发人员使用的命名约定 还是以这种方式命名宏的语法特定原因 整个代码中有很多这样的例子 例如 某些函数和变量以 甚至 这有什么具体原因吗 它似乎被广泛使用 我只需
  • NPTL 和 POSIX 线程有什么区别?

    NPTL 和 POSIX 线程之间的基本区别是什么 这两者是如何演变的 POSIX 线程 pthread 不是一个实现 它是几个函数的 API 规范 纸上的标准 英文 其名称以pthread 以及定义在
  • os.Mkdir 和 os.MkdirAll 权限

    我正在尝试在程序开始时创建一个日志文件 我需要检查是否 log如果不创建目录 则目录存在 然后继续创建日志文件 好吧 我尝试使用os Mkdir 也os MkdirAll 但无论我在第二个参数中输入什么值 我都会得到一个没有权限的锁定文件夹
  • 如何授予 apache 使用 NTFS 分区上的目录的权限?

    我在一台带有 20GB 硬盘的旧机器上运行 Linux Lubutu 12 10 我有一个 1 TB 外部硬盘 上面有一个 NTFS 分区 在该分区上 有一个 www 目录 用于保存我的网页内容 它在启动时自动安装为 media t515
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • 查找哪些页面不再与写入时复制共享

    假设我在 Linux 中有一个进程 我从中fork 另一个相同的过程 后forking 因为原始进程将开始写入内存 Linux写时复制机制将为进程提供与分叉进程使用的不同的唯一物理内存页 在执行的某个时刻 我如何知道原始进程的哪些页面已被写

随机推荐

  • muduo日志3

    日志滚动 日志滚动条件 1 文件大小 例如每写满1G换下一个文件 2 时间 每天零点新建一个日志文件 不论前一个文件是否写满 一个典型的日志文件名 logfile test 20130411 115604 popo 7743 log Log
  • 第一天学java

    1 java是什么 java是一门面向对象的编程语言 java是一门准动态编程语言 2编写java的过程 编写 编译 运行 编译会产生class 文件 3配置java运行环境 1安装jdk gt gt 我的是 E environment j
  • DOCKER 相关笔记

    Docker 镜像使用的 rootfs 往往由多个 层 layes 组成 而在使用镜像时 Docker 会把这些增量联合挂载在一个统一的挂载点上 等价于前面例子里的 C 目录 这个挂载点为 var lib docker aufs mnt x
  • SpringBoot工作原理

    SpringBoot工作原理 Spring Boot是由Pivotal团队提供的全新框架 其设计目的是用来简化新Spring应用的初始搭建以及开发过程 该框架使用了特定的方式来进行配置 从而使开发人员不再需要定义样板化的配置 通过这种方式
  • (四)JSP语法详细介绍--脚本元素

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 通过scriptlet可以在jsp中嵌入java代码 可以定义全局变量 方法 类 可以定义局部变量 方法 类 输出一个变量或具体内容 等同于 也可以用以下XML语句代替
  • java中枚举类将属性传值前台(枚举类前台接收问题)

    最近做的这个项目中 用到了大量的枚举类 今天来记录一下我遇到的问题 如果能帮到大家就更好了 1 枚举类如何转为json 在一个类的属性中 这个枚举类属性如何直接使用在接收参数和向数据库传递参数时需要自动转化 这里需要用到两个注解 JSONT
  • linux学习笔记(1)--基础介绍

    目录 linux的初步介绍 1 linux的特点 2 初步介绍 2 1谁挺linux 2 2 linux的历史 linux的初步介绍 1 linux的特点 1 免费的 开源 2 支持多线程 多用户 3 安全性好 4 对内存和文件管理优越 5
  • scala学习-scala中:: , +:, :+, :::, +++的区别

    4种操作符的区别和联系 该方法被称为cons 意为构造 向队列的头部追加数据 创造新的列表 用法为 x list 其中x为加入到头部的元素 无论x是列表与否 它都只将成为新生成列表的第一个元素 也就是说新生成的列表长度为list的长度 1
  • Java Stream API

    之前写过函数表达式介绍过stream的创建和一些基本使用方法 但是后来发现除了简单map filter distinct等API方法 实际上这些可以API组合在一起使用 有时候会有特别的思路 比如最近看的一个写法 for int i 0 i
  • 使用 TensorFlow 和 Flask 部署 Keras 图像分类卷积神经网络模型

    通常需要抽象出您的机器学习模型细节 然后将其与易于使用的 API 端点部署或集成 例如 我们可以提供一个 URL 端点 任何人都可以使用它来发出 POST 请求 他们将获得模型推断的 JSON 响应 而不必担心其技术问题 在本教程中 我们将
  • kafka消费者模式

    一 单线程消费者模式 package nj zb kb23 kafka import org apache kafka clients consumer ConsumerConfig import org apache kafka clie
  • 关闭Windows Defender实时保护,暂时关闭和永久关闭方法

    暂时关闭Windows Defender实时保护 点击开始 设置 更新和安全 Windows安全中心 打开Windows安全中心 点击主页 病毒和威胁防护或管理设置 关闭实时保护 这样就暂时关闭了实时保护 就算不重启也可能某个时候又自动打开
  • 结构化查询语言之 SQL 视图定义(以 MySQL 为例)

    文章目录 1 视图介绍 2 视图定义 3 视图更新 查询使用的数据库文件下载 1 视图介绍 虚关系 并不预先计算并存储关系 而是在使用虚关系时才通过执行查询被计算出来 概念上包含查询的结果 任何不是逻辑模型的一部分 但作为虚关系对用户可见的
  • 中国1949至2019年的gdp图表_成都市1949至2019年,70年来历年GDP数据信息公布

    新中国成立时至此已经整整70年了 在这70年里成都市的经济发展可谓是非常亮眼 最近成都市发布的 成都市统计年鉴2019 受到了不少人的关注 其中最受关注的还是成都历年地区生产总值 也就是我们常说的GDP 具体名单见文末 在 成都统计年鉴20
  • Java中方法的学习

    目录 Java中的方法定义 设计方法的原则 方法的命名规则 代码实现 方法调用 方法的重载 方法学习不知死过多少次 还让我学是吧 你没完了哈 来 来 来 咱们一起来分析 老师 前面的关键字我讲过吧 数据类型还用说嘛 方法的定义格式我说过吧
  • 2023推免夏令营末班车

    南航 清华大学预推免全面开放 目录 曲阜师范大学 活动内容 哈工大预推免 学校 学院 网址 ddl result schedule 河海大学 人工智能与自动化学院 河海大学人工智能与自动化学院2023年全国优秀大学生夏令营活动有关安排的通知
  • React Hooks 入门下

    前面的话 上篇介绍了 useState 和 useEffect 两个钩子函数 这篇将接着介绍其他常用的钩子函数 1 useCallback 作用 该 hooks 返回一个 memoized 回调函数 根据依赖项来决定是否更新函数 其依赖项可
  • VsCode写Python代码!这代码简直和大神一样规范!太漂亮了!

    VsCode虽然没有Pycharm的功能齐全 但是也是有他的独特之处 今天就让大家见识一下 用VsCode写出的代码是怎么样的吧
  • 【Shell编程】Shell中Bash变量-位置参数变量

    目录 系列文章 位置参数变量 实例 理解参数 实例 剩余参数 实例 区别整体对待和单独对待 系列文章 Shell编程 Shell基本概述与脚本执行方式 Shell编程 Shell中Bash基本功能 Shell编程 Bash变量 用户自定义变
  • Linux驱动开发(十六)---块设备驱动

    前文回顾 Linux驱动开发 一 环境搭建与hello world Linux驱动开发 二 驱动与设备的分离设计 Linux驱动开发 三 设备树 Linux驱动开发 四 树莓派内核编译 Linux驱动开发 五 树莓派设备树配合驱动开发 Li