ramdisk驱动程序分析-2.6内核--块设备驱动框架(1)

块设备驱动用到的重要结构体与函数如下红色表示部分。

快设备驱动的模块加载函数中通常需要完成如下操作:

1. 分配、初始化请求队列,绑定请求队列request_queue和请求函数;

2. 分配、初始化gendisk,给gendisk的major,fops,queque等成员赋值,最后添加gendisk;

3. 注册快设备驱动。



本代码源自“LINUX设备驱动开发技术及应用 ”一书,经过少许修改,在2.6.28.12内核编译通过,并运行正常。

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>


#include <linux/fs.h>

#include <linux/errno.h>

#include <linux/types.h>

#include <linux/fcntl.h>

#include <linux/vmalloc.h>

#include <linux/hdreg.h>

#include <linux/blkdev.h>

#include <linux/blkpg.h>

#include <asm/uaccess.h>

/*设备名称,段大小,设备大小等信息的定义*/

#define VRD_DEV_NAME "vrd"

#define VRD_DEV_MAJOR 220


#define VRD_MAX_DEVICE 2

#define VRD_SECTOR_SIZE 512

#define VRD_SIZE (4*1024*1024)

#define VRD_SECTOR_TOTAL (VRD_SIZE/VRD_SECTOR_SIZE)

/*2.6内核的设备各自管理请求队列的结构体request_queuegendiskrequest()函数处理该结构题的信息和设备驱动程序自身的信息,因此不使用全局变量,而是使用较为有效的结构体。vrd设备驱动程序定义了vrd_devices。这里使用make_request处理方式,因此不设置LOCK,就能定义管理request_queue结构题的queue域,管理gendisk结构体的gd域,以及RAM Disk的内存管理data*/

typedef struct

{

unsigned char *data;

structrequest_queue *queue;

structgendisk *gd;

}vrd_device;

/*内存disk需要分配内存,分配的内存会放到vdisk*/

static char *vdisk[VRD_MAX_DEVICE];

/*为了支持将两台设备作为指针组,并分配和管理上衣不得vrd_devices应定义device数组*/

static vrd_device device[VRD_MAX_DEVICE];

/*make_request函数的vrd_make_reque处理块设备。内核调用该函数,向块设备驱动发出请求时,使用传递的变量处理请求,在这里不实用request_queue结构题的q变量,实际使用的是bio结构*/

static int vrd_make_request(struct request_queue *q, struct bio *bio)

{

vrd_device *pdevice;

char *pVHDDData;

char *pBuffer;

struct bio_vec *bvec;

int i;

/*首先检查请求的扇区大小和位置是否超出设备的范围。内核传递的信息是以请求的起始扇区和字节为单位的容量。请求的起始扇区号传给bio->bi_sector,请求的大小传给bio->bi_size。该值超出容量时,使用bio_io_error(*,*)来处理错误,bio_io_error2.6内核里的函数原型并不都相同,在有的内核里是两个参数,有的里边是1个参数;bio_endio也是一样,有的是三个参数有的是2个参数*/

if(((bio->bi_sector*VRD_SECTOR_SIZE) + bio-> bi_size) > VRD_SIZE)/*因为RAM Disk没有物理扇区,因此,在vdisk所分配的内存上读取或写入数据。此时的处理位置应该为地址,传送的位置为扇区数,乘以VRD_SECTOR_SIZE大小就可以求出实际地址*/

goto fail;

/*在请求属于正常范围时,vrd就会利用bio->bi_bdev->bd_disk-> private_data获取gendiskprivate_data上分配的vrd_device管理结构体的地址。使用该地址所指结构体域中包含的pdevice->data的内存地址,把对应的bio-> bi_sector*VRD_SECTOR_SIZE 的内存地址写入pVHDDData*/

pdevice = (vrd_device *) bio->bi_bdev->bd_disk-> private_data;

pVHDDData = pdevice->data + (bio-> bi_sector*VRD_SECTOR_SIZE);

/*由于bio变量中含有多个请求处理的bio_vec结构体,为了依次处理各个bio_vec结构体的矢量,使用bio_for_each_segment宏。在宏内部执行for语句时,使用该宏的i变量,因此不能修改*/

bio_for_each_segment(bvec, bio, i)

{

/*请求读/写的扇区信息包含的内核的缓存上,而该缓存以page进行传递。为了获得实际地址,使用kmap函数把bvec->bv_page转换成内存地址,并加上以bvec->bv_offset表示的offset位置,在pBuffer上设置实际内存地址*/

pBuffer = kmap(bvec->bv_page) + bvec-> bv_offset;

switch(bio_data_dir(bio))

{

case READA :

case READ : memcpy(pBuffer, pVHDDData, bvec-> bv_len);

break;

case WRITE : memcpy(pVHDDData, pBuffer, bvec-> bv_len);

break;

default : kunmap(bvec->bv_page);

goto fail;

}

/*每处理一个bvec,把由kmap函数映射的bvec->bv_page,使用kunmap函数取消映射,以bvec->bv_len大小增加pVHDDDData所指的内存位置。*/

kunmap(bvec->bv_page);

pVHDDData += bvec->bv_len;

}

/*结束处理,并终止vrd_make_request函数*/

bio_endio(bio, /*bio->bi_size, */0);

return 0;

fail:

bio_io_error(bio/*, bio->bi_size*/);

return 0;

}

/*安装块设备时,调用open函数,该函数没有特殊的处理内容。对于可清除设备或完整的设备驱动程序可自行处理设备的使用计数*/

int vrd_open(struct inode *inode, struct file *filp)

{

return 0;

}

/*取消安装设备时,调用此函数。该函数没有特殊的处理内容。对于可清除设备或完整的设备驱动程序可自行处理设备的使用计数*/

int vrd_release (struct inode *inode, struct file *filp)

{

return 0;

}

/*vrd是非常简单的RAM disk,对于ioctl命令可全部返回-ENOTTY2.6内核的ioctl函数就由内核自行处理*/

int vrd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,

unsigned long arg)

{

return -ENOTTY;

}

/*vbrd块设备并不是可清除设备,因此,block_device_operations结构体的域中支持open,release,ioctl相关函数就行*/

static structblock_device_operations vrd_fops =

{

.owner = THIS_MODULE,

.open = vrd_open,

.release = vrd_release,

.ioctl = vrd_ioctl,

};


int vrd_init(void)

{

int lp;

/*内核插入模块后,使用vmalloc函数来分配ram disk块设备的内存。此处没有考虑分配失败的情况*/

vdisk[0] = vmalloc(VRD_SIZE);

vdisk[1] = vmalloc(VRD_SIZE);

/*注册vrd设备驱动程序*/

register_blkdev(VRD_DEV_MAJOR, VRD_DEV_NAME);

/**/

for(lp = 0; lp < VRD_MAX_DEVICE; lp++)

{


device[lp].data = vdisk[lp];

/*分配gendisk结构题,gendisk结构题是注册会设备的信息结构体*/

device[lp].gd =alloc_disk(1);

device[lp].queue =blk_alloc_queue(GFP_KERNEL);

/*注册rd_make_request函数,该函数是内核和会设备驱动程序的实际输出路径。通过blk_alloc_queue函数分配到块设备的请求队列,并代入到vrd_request_queue*/

blk_queue_make_request(device[lp].queue, &vrd_make_request);

/*设置块设备的想关域gdmajor上分配为主设备号,无分区的从设备好表示各台块设备。在first_minor上设置设备的索引值。将会设备驱动程序的operation结构体带入到gdfops中,并将各台设备的请求队列device[lp].queuedevice[lp].gd->queue上进行设置。为了使vrd_make_request函数引用块设备的信息,在gdprivate_data上代入device[lp]的地址

另外,为了使proc文件系统和sysfs文件系统表示分区及块设备,在device[lp].gd->disk_name上分别代入块名”vrda“和“vrdb”。最后用set_capacity函数设置各台块设备的总扇区数*/

device[lp].gd->major = VRD_DEV_MAJOR;

device[lp].gd->first_minor = lp;

device[lp].gd->fops = &vrd_fops;

device[lp].gd->queue = device[lp].queue;

device[lp].gd->private_data = &device[lp];

sprintf(device[lp].gd->disk_name, "vrd%c" , 'a'+lp);

set_capacity(device[lp].gd,VRD_SECTOR_TOTAL);


/*为了检索分区并注册块设备,调用add_disk()函数,调用该函数后,内核会在内核内部注册各台设备,需要检索分区时,在gendisk结构题内部设置分区处理变量*/

add_disk(device[lp].gd);


}

return 0;


}

/*清除模块未注销块设备驱动程序时,使用del_gendisk函数根据注册的设备数从内核中清除gendisk结构题,并使用put_disk函数清除gendisk结构题的内存。最后,使用unregister_blkdev函数清除块设备,使用vfree函数清除vrd所分配的内存。*/

void vrd_exit(void)

{

int lp;


for(lp = 0; lp < VRD_MAX_DEVICE; lp++)

{

del_gendisk(device[lp].gd);

put_disk(device[lp].gd);

}

unregister_blkdev(VRD_DEV_MAJOR, VRD_DEV_NAME);


vfree(vdisk[0]);

vfree(vdisk[1]);

}


module_init(vrd_init);

module_exit(vrd_exit);


MODULE_LICENSE("Dual BSD/GPL");


上边源代码文件名称为vrd.c
1.编写Makefile 内容如下:obj-m := vrd.o
2.编译:make -C /usr/src/linux-source2.6.28/ M=$(pwd) modules
编译成功后,会出现vrd.ko模块文件
3.insmod vrd.ko,如果没有错误显示
4.cat /proc/devices 里边的block设备中会出现主设备号为220的vrd设备
5.可以使用mknod /dev/vrd0 b 220 0创建一个设备特殊文件于/dev下边的vrd0文件
6.使用mke2fs /dev/vrd0 将其格式化为ext2文件系统,并确认起是否运行正常,正常显示如下信息
mke2fs 1.41.4 (27-Jan-2009)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
1024 inodes, 4096 blocks
204 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=4194304
1 block group
8192 blocks per group, 8192 fragments per group
1024 inodes per group

Writing inode tables: done                           
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 24 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
7.指定了文件格式之后,就可以挂载文件了,创建任意目录并将上述块设备文件挂载在此目录下
mkdir rmdisk
mount dev/vrd0 rmdisk
cd rmdisk
ls -al
显示内容如下:
drwxr-xr-x 3 root     root      1024 2009-06-03 18:18 .
drwxr-xr-x 4 besimple besimple 4096 2009-06-03 11:10 ..
drwx------ 2 root     root     12288 2009-06-03 18:18 lost+found
8.还可以编辑文件保存在此目录下边或者复制文件到此目录下边,然后检查起内容,最后确认其是否能够正常卸载
umount rmdisk
看看rmdisk下边的内容,再挂载一次看看里边的内容和上次一样不一样
9.最后确认模块是否能清除
rmmod vrd
如果没有卸载上述设备文件,就清除模块,则会显示"模块正在使用中"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值