文章目录
一,前言
LINUX 驱动针对的对象是存储器和外设,而不是针对cpu内核。存储器和外设分为3个基础大类:
- 字符设备
- 块设备
- 网络设备
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、鼠标等。
块设备可以按任意顺序进行访问,以块为单位进行操作,如硬盘、eMMC等。字符设备和块设备的驱动设计有很大的差异,但对于用户(应用程序)而言,它们都使用文件系统的接口(open()、write()、read()、close())来进行访问和操作。
网络设备面向数据的接收和发送设计,它不对应文件系统的节点。内核和网络设备的通信与内核和字符设备、块设备的通信方式完全不同,前者主要还是使用套接字接口。在这里学习块设备驱动的基本架构,以及构建一个块设备驱动的基本步骤,最后通过内存模拟了一个块设备,并构建了其块设备驱动。
二,块设备和字符设备
- 块设备只能以块为单位接收输入和返回输出,而字符设备则以字节为单位。大多数设备是字符设备,如触摸屏、鼠标等,它们不需要缓冲而且不以固定的块大小进行操作。
- 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无需缓冲被直接读写。对于存储设备而言,调整读写的顺序dd作用巨大,因为在读写连续的扇区的存储速度比分离的扇区更快。
- 字符设备只能被顺序读写,而块设备可以随机访问。虽然块设备可以随机访问,但是对于磁盘这类机械设备而言,顺序地组织块设备的访问可以提高性能(减少了磁盘机械磁头的跳转)。
三,块设备子系统
在Linux中,通常通过磁盘文件系统EXT4、UBIFS、原始块设备(直接访问/dev/sdb1)等访问磁盘。所有的EXT4、UBIFS、原始块设备都工作在VFS(虚拟文件系统)之下,而EXT4、UBIFS、原始块设备之下又包含I/O调度层以对I/O请求进行排序和合并。I/O调度层的基本目的是将请求按照它们对应在块设备上的扇区号进行排序,以减少磁头的移动,提高效率。
四,块设备驱动结构
4.1 block_device_operations 结构体
struct block_device_operations {
// 当设备打开和关闭时被调用
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
// I/O控制 64位系统内32位进程调用ioctl时,内核调用compat_ioctl
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
long (*compat_ioctl) (struct file *, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t, unsigned long *);
// 介质改变,被内核调用以检查驱动器中的介质是否发送改变,如果是返回一个非0值,否则返回0.
int (*media_changed) (struct gendisk *);
// 使介质有效
int (*revalidate_disk) (struct gendisk *);
// 获得驱动器信息
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
4.2 gendisk 结构体
struct gendisk {
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[32]; /* name of major driver */
struct hd_struct **part; /* [indexed by minor] */
int part_uevent_suppress;
struct block_device_operations *fops; // 块设备操作函数集合
struct request_queue *queue; // 内核用来管理这个设备的i/o请求队列指针
void *private_data;
sector_t capacity;
int flags;
struct device *driverfs_dev;
struct kobject kobj;
struct kobject *holder_dir;
struct kobject *slave_dir;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
unsigned long stamp;
int in_flight;
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
struct work_struct async_notify;
};
4.3 操作gendisk的接口
4.3.1 分配gendisk
gendisk *alloc_disk(int minors);
minors : 这个磁盘使用的次设备号的数量,一般就是磁盘分区的数量,此后miniors不能修改。
4.3.2 增加gendisk
void add_disk(struct gendisk *disk);
disk : gendisk结构体指针
该函数的调用必须在驱动程序的初始化工作完成(设置gendisk结构体)并能响应磁盘的请求(初始化request_queue)之后。
4.3.3 释放gendisk
void del_gendisk(struct gendisk *disk);
4.3.4 gendisk的引用计数
struct kobject *get_disk(struct gendisk *disk);
void put_disk(struct gendisk *disk);
get_disk一般不需要驱动程序调用。在调用del_gendisk接口释放gendisk后,需要调用put_disk接口。
4.4 操作请求队列接口
4.4.1 初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
rfn: 请求处理函数指针,该函数一般需要驱动程序实现。
lock: 控制访问队列权限的自旋锁。
4.4.2 清除请求队列
void blk_cleanup_queue(request_queue_t * q);
在块设备驱动卸载时调用。
五,块设备驱动初始化
5.1 注册块设备
int register_blkdev(unsigned int major, constchar *name);
与字符设备驱动类似,块设备驱动要注册到内核,申请主设备号。
major:该块设备要使用的主设备号,为0则由内核分配,返回主设备号。
name:设备名。
对应一个卸载接口:int unregister_blkdev(unsigned int major, const char *name);
if (register_blkdev(Z2RAM_MAJOR, DEVICE_NAME))
goto err;
5.2 初始化请求队列
请求队列初始化
static void do_z2_request(request_queue_t *q)
{
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
unsigned long start = req->sector << 9;
unsigned long len = req->current_nr_sectors << 9;
if (start + len > z2ram_size) {
printk( KERN_ERR DEVICE_NAME ": bad access: block=%lu, count=%u\n",
req->sector, req->current_nr_sectors);
end_request(req, 0);
continue;
}
while (len) {
unsigned long addr = start & Z2RAM_CHUNKMASK;
unsigned long size = Z2RAM_CHUNKSIZE - addr;
if (len < size)
size = len;
addr += z2ram_map[ start >> Z2RAM_CHUNKSHIFT ];
if (rq_data_dir(req) == READ)
memcpy(req->buffer, (char *)addr, size);
else
memcpy((char *)addr, req->buffer, size);
start += size;
len -= size;
}
end_request(req, 1);
}
}
z2_queue = blk_init_queue(do_z2_request, &z2ram_lock);
if (!z2_queue)
goto out_queue;
5.3 gendisk初始化
static struct block_device_operations z2_fops =
{
.owner = THIS_MODULE,
.open = z2_open,
.release = z2_release,
};
// 分配gendisk
z2ram_gendisk = alloc_disk(1);
if (!z2ram_gendisk)
goto out_disk;
z2ram_gendisk->major = Z2RAM_MAJOR; // 设置主设备号
z2ram_gendisk->first_minor = 0; // 设置初始次设备号
z2ram_gendisk->fops = &z2_fops; // 块设备操作函数集合
sprintf(z2ram_gendisk->disk_name, "z2ram");
z2ram_gendisk->queue = z2_queue; // 请求队列
//增加gendisk
add_disk(z2ram_gendisk);
六,使用内存模拟块设备构建块设备驱动
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
static struct gendisk *ramblock_disk;
static request_queue_t *ramblock_queue;
static int major;
static DEFINE_SPINLOCK(ramblock_lock);
#define RAMBLOCK_SIZE (1024*1024)
static unsigned char *ramblock_buf;
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量 = heads*cylinders*sectors*512 */
geo->heads = 2; //磁头数量,不重要但必须有,自定义
geo->cylinders = 32; //柱面数量,不重要但必须有,自定义
geo->sectors = RAMBLOCK_SIZE/2/32/512; // 扇区数量,一个扇区512字节
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
static void do_ramblock_request(request_queue_t * q)
{
static int r_cnt = 0;
static int w_cnt = 0;
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
unsigned long offset = req->sector * 512;
/* 目的/源: */
// req->buffer
/* 长度: */
unsigned long len = req->current_nr_sectors * 512;
if (rq_data_dir(req) == READ)
{
//printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf+offset, len);
}
else
{
//printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1);
}
}
static int ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
/* 2. 设置 */
/* 2.1 分配/设置队列: 提供读写能力 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;
/* 2.2 设置其他属性: 比如容量 */
major = register_blkdev(0, "ramblock"); /* cat /proc/devices */
ramblock_disk->major = major;
ramblock_disk->first_minor = 0;
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops;
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);
/* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
/* 4. 注册 */
add_disk(ramblock_disk);
return 0;
}
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
kfree(ramblock_buf);
}
module_init(ramblock_init);
module_exit(ramblock_exit);
MODULE_LICENSE("GPL");
七,测试
// 加载驱动
# insmod ramblock.ko
#
#
// insmod 加载驱动后,/dev 下有个ramblock设备节点,brw -- b表示块设备
# ls -la /dev/ramblock
brw-rw---- 1 0 0 254, 0 Jan 1 00:14 /dev/ramblock
// 挂载之前需要格式化
# mkdosfs /dev/ramblock
mkdosfs 2.11 (12 Mar 2005)
#
// 挂载/dev/ramblock 到/tmp
# mount /dev/ramblock /tmp/
#
#
// 进入/tmp就是在磁盘中操作
# cd /tmp/
# ls
# echo "test" > test.txt
# ls
test.txt
# cat test.txt
test
# cd ..
// 卸载 /dev/ramblock
# umount /tmp
// 操作ramblock,设置分区
# fdisk /dev/ramblock
Command (m for help): n // n 添加分区
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 1 // 分区号
First cylinder (1-32, default 1): 1 // 该分区初始柱面
Last cylinder or +size or +sizeM or +sizeK (1-32, default 32): 3 // 该分区结束柱面 // geo->cylinders = 32;
Command (m for help): p // 查看分区
Disk /dev/ramblock: 1 MB, 1048576 bytes
2 heads, 32 sectors/track, 32 cylinders
Units = cylinders of 64 * 512 = 32768 bytes
Device Boot Start End Blocks Id System
/dev/ramblock1 1 3 80 83 Linux
Command (m for help): w //将设置的分区生效
// 查看分配的分区
# ls -la /dev/ramblock*
brw-rw---- 1 0 0 254, 0 Jan 1 00:23 /dev/ramblock
brw-rw---- 1 0 0 254, 1 Jan 1 00:25 /dev/ramblock1