上一章主要讲了请求队列的一系列问题。下面主要说一下请求函数。首先来说一下硬盘类块设备的请求函数。

请求函数可以在没有完成请求队列的中的所有请求的情况下就返回,也可以在一个请求都不完成的情况下就返回。
下面贴出请求函数的例程:

static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
struct bio_vec *bvec;
int i;
void *dsk_mem; if ((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": bad request: block=%llu, count=%u\n",
(unsigned long long)bio->bi_sector, bio->bi_size);
//这个条件是在判断当前正在运行的内核版本。
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
} dsk_mem = simp_blkdev_data + (bio->bi_sector << 9); //遍历
bio_for_each_segment(bvec, bio, i) {
void *iovec_mem; switch (bio_rw(bio)) {
case READ:
case READA:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(iovec_mem, dsk_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
case WRITE:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(dsk_mem, iovec_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lu\n",
bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem += bvec->bv_len;
} #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, bio->bi_size, 0);
#else
bio_endio(bio, 0);
#endif return 0;
}

首先是一个while大循环的不断检测:

while ((req = elv_next_request(q)) != NULL) 

这个while大循环是在不断的检测,同时elv_next_request这个函数的作用是获得队列中第一个未完成的请求,其实就是在遍历队列中的请求。elv_next_request这个函数用来获得队列中第一个未完成的请求,参数是q,这里的q是请求队列request_queue的函数指针。

还有一个重要的函数就是end_request(req, 0);

在这个函数中,传给end_request这个函数的参数如果是0,则意味着请求失败。如果传的是1则意味着请求处理成功。

在这里end_request这个函数很重要,其原型如下所示:

void end_request(struct request *req, int uptodate)
{ if (!end_that_request_first(req, uptodate, req->hard_cur_sectors))
{
add_disk_randomness (req->rq_disk);
blkdev_dequeue_request (req);
end_that_request_last(req);
}
}

当设备已经完成1个IO请求的部分或者全部扇区传输后,它必须通告块设备层,上述代码中的第4行完成这个工作。
end_that_request_first()函数的原型为:

int end_that_request_first(struct request *req, int success, int count);

这个函数告知块设备层,块设备驱动已经完成count个扇区的传送。
end_that_request_first()的返回值是一个标志,指示是否这个请求中的所有扇区已经被传送。返回值为0表示所有的扇区已经被传送并且这个请求完成,之后,我们必须使用 blkdev_dequeue_request()来从队列中清除这个请求。
最后,将这个请求传递给end_that_request_last()函数:
void end_that_request_last(struct request *req);
end_that_request_last()通知所有正在等待这个请求完成的对象请求已经完成并回收这个请求结构体。
第6行的add_disk_randomness()函数的作用是使用块 I/O 请求的定时来给系统的随机数池贡献熵,它不影响块设备驱动。但是,仅当磁盘的操作时间是真正随机的时候(大部分机械设备如此),才应该调用它。

LDD讲了一个复杂的请求函数:

这个请求函数的代码如下:

static void xxx_full_request(request_queue_t *q)
{
struct request *req;
int sectors_xferred;
struct xxx_dev *dev = q->queuedata;
/* 遍历每个请求 */
while ((req = elv_next_request(q)) != NULL)
{
if (!blk_fs_request(req))
{
printk(KERN_NOTICE "Skip non-fs request\n"); end_request(req, 0);
continue;
}
sectors_xferred = xxx_xfer_request(dev, req);
if (!end_that_request_first(req, 1, sectors_xferred))
{
blkdev_dequeue_request(req);
end_that_request_last(req);
}
}
}
/* 请求处理 */
static int xxx_xfer_request(struct xxx_dev *dev, struct request *req)
{
struct bio *bio;
int nsect = 0;
/* 遍历请求中的每个bio */
rq_for_each_bio(bio, req)
{
xxx_xfer_bio(dev, bio);
nsect += bio->bi_size / KERNEL_SECTOR_SIZE;
}
return nsect;
}
/* bio处理 */
static int xxx_xfer_bio(struct xxx_dev *dev, struct bio *bio)
{
int i;
struct bio_vec *bvec;
sector_t sector = bio->bi_sector; /* 遍历每1段 */
bio_for_each_segment(bvec, bio, i)
{
char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio)
== WRITE);
sector += bio_cur_sectors(bio);
__bio_kunmap_atomic(bio, KM_USER0);
}
return 0;
}

复杂的请求函数结构示意图如下所示:

对于SD卡和U盘这类设备支持无请求队列的的模式。LDD说,为了使用这个模式,驱动必须提供一个制造请求函数,而不是一个请求函数。
第一个参数虽然仍然是请求队列,但是这个请求队列实际上不包含任何request,因为块层没有必要将bio调整为request所以制造请求的主要参数是bio结构体。bio_endio这个函数是通知结束处理函数。
无论处理成功与否,制造请求函数都应该返回0,如果返回一个非0数,那么bio请求将会再一次被提交。

最后制造请求函数的一个重要函数:

void bio_endio(struct bio *bio, int error)
{
if (error)
clear_bit(BIO_UPTODATE, &bio->bi_flags);
else if (!test_bit(BIO_UPTODATE, &bio->bi_flags))
error = -EIO; if (bio->bi_end_io)
bio->bi_end_io(bio, error);
}

linux下的块设备驱动(二)的更多相关文章

  1. linux下的块设备驱动(一)

    块设备的驱动比字符设备的难,这是因为块设备的驱动和内核的联系进一步增大,但是同时块设备的访问的几个基本结构和字符还是有相似之处的. 有一句话必须记住:对于存储设备(硬盘~~带有机械的操作)而言,调整读 ...

  2. Linux块设备驱动(二) _MTD驱动及其用户空间编程

    MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...

  3. linux下获得块设备大小

    运行结果如下 jackie@Ubuntu:~/work/0602$ sudo ./a.out /dev/sda/dev/sda3907029168,2000398934016 //BLKGETSIZE ...

  4. Linux设备驱动--块设备(二)之相关结构体

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

  5. Linux设备驱动--块设备(二)之相关结构体(转)

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

  6. linux 块设备驱动 (三)块设备驱动开发

    一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...

  7. Linux 块设备驱动 (一)

    1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...

  8. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  9. 乾坤合一~Linux设备驱动之块设备驱动

    1. 题外话 在蜕变成蝶的一系列学习当中,我们已经掌握了大部分Linux驱动的知识,在乾坤合一的分享当中,以综合实例为主要讲解,在一个月的蜕茧成蝶的学习探索当中,觉得数据结构,指针,链表等等占据了代码 ...

随机推荐

  1. oracle 表复制

    1. 复制表结构及其数据: create table table_name_new as select * from table_name_old 2. 只复制表结构: ; 或者 create tab ...

  2. css sprites 图片精灵自动生成 插件

    grunt-spritesmith https://www.npmjs.com/package/grunt-spritesmith

  3. Kivy: Crossplatform Framework for NUI

    Kivy: Crossplatform Framework for NUI ivy - Open source Python library for rapid development of appl ...

  4. MySQL主键添加/删除

    2改动数据库和表的字符集alter database maildb default character set utf8;//改动数据库的字符集alter table mailtable defaul ...

  5. Ubuntu14.04更新源

    Ubuntu14.04更新源 http://jingyan.baidu.com/article/7f41ecec1b7a2e593d095ce6.html Ubuntu源 http://wiki.ub ...

  6. c语言,数据类型转换

    在执行算术运算时,计算机比C语言的限制更多.为了让计算机执行算术运算,通常要求操作数有相同的大小(即位的数量相同),并且要求存储的方式也相同.计算机可能可以直接将两个16位整数相加,但是不能直接将16 ...

  7. CCIE路由实验(9) -- IPv6

    1.IPv6地址的各种情况2.配置通过DHCP-PD方式分配前缀信息3.IPv6路由基本配置4.IPv6路由--RIPng5.IPv6路由--EIGRPv66.IPv6路由--OSPFv37.IPv6 ...

  8. 64位win7旗舰版搭建apache+php+mysql开发环境[转]

      我建议把apache.php.mysql都安装在一个文件夹中,比如:web/apache.web/php.web/mysql 1.安装apache2.2.25,请查看win7下安装VC9版本的ap ...

  9. 北京出dell s2740显示器 1900 - V2EX

    水木社区归档站 北京出dell s2740显示器 1900 - V2EX 北京出dell s2740显示器 1900 By kekex · 6 小时 6 分钟前 · 188 次点击 购于今年4月份 镜 ...

  10. 基于jquery-easyui的仓库管理系统

    使用jQuery EasyUI创建的仓库管理系统包括系统管理.数据维护.业务单据管理等,有兴趣可以对其进行修改扩展. 数据库采用MYSQL, 帐号/密码:root/root,演示登录帐号/密码:adm ...