块设备驱动
1、块设备也和字符设备一样可以通过/dev目录下的设备文件来访问。此外快设备(例如磁盘)上能够容纳文件系统。我们来看一下/dev目录下的一些成员。
访问权限之前的字母是b或c,分别表示块设备和字符设备。 设备文件没有文件长度,而增加了主设备号和从设备号。二者共同形成一个唯一的号码,内核可由此查找对应的设备驱动程序
驱动程序则负责将用户的一组功能调用映射作用于实际硬件设备的特有操作上,是系统软件与硬件设备沟通的桥梁。
编写简单驱动程序主要包含以下步骤。
2、注册
2.1 块设备驱动程序注册
块设备要想被内核知道其存在,必须使用内核提供的一系列注册函数进行注册。驱动程序的第一步就是向内核注册自己,提供该功能的函数是<fs.h>
int register_blkdev(unsigned int major,const char *name);
- 参数是该设备使用的主设备号及其名字,name通常与设备文件名称相同,但也可以是任意有效的字符串。
- 如果传递的主设备号是0,内核将分派一个新的主设备号给设备,并将该设备号返回给调用者。
- 使用该函数。块设备将会显示/proc/devices
对应的注销函数为
int unregister_blkdev(unsigned int major,const char *name);
2、2磁盘注册
通过注册驱动程序我们获得了主设备号,但是现在还不能对磁盘进行操作。内核对于磁盘的表示是使用的gendisk结构体, gendisk结构中的许多成员必须由驱动程序进行初始化。
gendisk结构是一个动态分配的结构,它需要一些内核的特殊处理来进行初始化;驱动程序不能自己动态分配该结构,而是必须调用
在头文件<include/genhd.h>
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 */
unsigned short events; /* supported events */
unsigned short event_flags; /* flags related to event processing */
struct xarray part_tbl;
struct block_device *part0;
const struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
int flags;
unsigned long state;
struct gendisk *alloc_disk(int minors)
{
return alloc_disk_node(minors,NUMA_NO_NODE);
}
参数是该磁盘使用的次设备号数目。当不再需要一个磁盘时,调用下面的函数即卸载磁盘。
void del_gendisk(struct gendisk *disk);
分配一个gendisk结构体并不能使磁盘对文件系统可用。为达这个目的,并调用add_disk.Gendisk中包含了一个指针struct block_device_operations *fops;指向对应的块设备操作函数,接下来看一个block_device_operations都有哪些函数需要驱动程序来实现。
const struct block_device_operations *fops;
在上面的哪个结构体里面已经写了这段代码
3、块设备操作
struct request_queue;
4、请求队列
块设备的读写请求放置在一个队列上,称之为请求队列。gendisk结构包括了一个指针,指向这个特定于设备的队列,由以下数据类型表示。
queue_ head是该数据结构的主要成员,是一个表头,用于构建一个I/O请求的双链表。 链表每个元素的数据类型都是request,代表向块设备读取数据的一个请求。
<include/linux/blkdev.h>
我们需要为gendisk创建并初始化对应的请求队列,函数如下:
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
{
return blk_init_queue_node(rfn,lock,NUMA_NO_NODE);
}
该参数的参数是一个需要驱动实现的函数,用来处理该队列中的request和控制访问队列权限的自旋锁。
4、1请求处理
请求队列创建初始化如下所示:
my_request_queue=blk_init_queue(my_request,&lock);
其中request的处理函数my_request编写如下,主要实现的功能有:
- 使用blk_fetch_request函数获取队列中的request,循环处理队列中的request。
- 获取请求的起始地址与读写扇区数。
- 根据读写的请求不同,分别处理。(因为是内存模拟的设备,使用memcpy函数直接拷贝数据)。
5、代码部分:
#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"
staticintmybdrv_ma_no, diskmb=4, disk_size;
static char *ramdisk;
static struct gendisk *my_gd;
static spinlock_tlock;
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);
while (rq) {
nr_sectors=blk_rq_cur_sectors(rq);
sector=blk_rq_pos(rq);
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);
} 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;
return0;
}
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;
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);
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);
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);
pr_info("device successfully registered, Major No. = %d\n",mybdrv_ma_no);
pr_info("Capacity of ram disk is: %d MB\n", diskmb);
return0;
}
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);
module_exit(my_exit);
因为虚拟机的原因,代码暂时未能成功运行。
5、问题:
1、请求队列调度算法详解:
请求调度算法是一种调度算法,它被哪个请求应被下一个服务。这种调度方式主要应用在磁盘I/O操作中,可以按照优先级、先进先出(FIFO)或其他方式进行排序。
在请求队列调度中,当一个请求到达时,它会被添加到请求队列的末尾。当一个请求完成后,调度器会从请求队列的头部取出下一个请求,并将其分配给可执行的处理器。这样,请求就可以按照先来先服务的顺序依次执行。
2、请求队列算法在哪些场景中适用?
请求队列调度算法主要适用于以下场景:
1、磁盘I/O操作:在处理磁盘I/O请求时,请求队列调度算法可以按照请求的优先级、先进先出(FIFO)或其他方式进行排序,决定哪个请求应被下一个服务。
2、资源分配:在操作系统中,当多个进程同时请求资源时,请求队列调度算法可以用来决定哪个进程将首先获得资源。
3、cpu调度:在多任务操作系统中,CPU调度也是使用请求队列调度算法的一种应用场景。当有新的任务到达时,该算法可以决定哪个任务将首先获得CPU资源。
3、如何实现请求队列调度算法?
实现请求队列调度算法需要以下步骤:
1、创建一个请求队列,用于存储待处理的请求。
2、当有新的请求到达时,将其添加到请求队列的末尾。
3、当一个请求完成后,从请求队列的头部取出下一个请求,并将其分配给可执行的处理器。
4、如果存在多个优先级,则可以根据优先级对请求进行排序。例如,高优先级的请求可以被排在队列的前面,而低优先级的请求则排在后面。
5、可以使用各种调度算法来决定请求的执行顺序,例如先进先出(FIFO)、最短作业优先(SJF)、轮转法(RR)等。
6、在实现过程中,需要考虑并发控制和同步机制,以确保多个线程或进程同时访问请求队列时的数据安全性。
7、最后,可以根据实际需求对算法进行优化,例如使用缓存、预取等技术来提高性能。