`
mmdev
  • 浏览: 12915257 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Linux设备驱动工程师之路之——块设备驱动

 
阅读更多

Linux设备驱动工程师之路之——块设备驱动

K-Style

转载请注明来自于衡阳师范学院08电2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com

一、重要知识点

1.块设备和字符设备的区别

a.字符设备可访问字节大小数据,块设备只能访问固定大小的整块数据(一般为512字节)。

b.块设备支持随机访问,字符设备只能顺序访问。

2.块设备子系统体系架构

如图


从上到下依次为VFS虚拟文件系统、各种类型的磁盘系统、通用块设备层、I/O调度层(优化访问上层的请求(读写请求))、块设备驱动层、块设备硬件层。

我们编写驱动程序要完成的是调用I/O调度层提供的相关接口对块设备硬件层进行读写及相关操作。

3.块设备驱动程序注册

块设备驱动程序使用

int register_blk_dev(unsigned int major, const char*name)向内核注册。如果major为0,则内核为止分配一个主设备号。在内核2.6中,对register_blk_dev的调用时完全可选的,该接口只做了两件事:一是动态分配主设备号,二是在/proc/devices中创建一个入口项。大多数驱动仍会调用,因为这是一个传统。

4.注册磁盘

虽然register_blk能够获得主设备号,当它并不能让系统使用任何磁盘,因此为了管理独立的磁盘,必须使用另外一个单独的注册接口

void add_disk(struct gendist *gd)

下面我们再来看看参数struct gendisk结构

5.磁盘描述结构struct gendisk

内核使用gendisk结构来表示一个独立的磁盘设备

struct gendisk

{

int major; //主设备号

intfirst_minor; //第一个次设备号

intminors; //最大次设备数,如果不能分区则为1

chardisk_name[32]; //设备名称

structhd_struct **part; //磁盘上的分区信息

structblock_device_operations *fops; //块设备操作结构体

structrequest_queue *queue; //请求队列

void*private_data; //私有数据

sector_tcapacity; //扇区数,512字节为1扇区

…………

}

我们再来看块设备的操作结构体struct block_device_operations

6.块设备操作结构体struct block_device_operations

struct struct block_device_operations

{

int (*open)(struct inode *, struct file*);

int(*release)(struct inode*, struct file *);

int (*ioctl)(struct inode*, struct file *, unsigned, unsigned long);

int (*media_changed)(struct gendisk *)

int (*revalidate_disk)(struct gendisk *)

int (*getgeo)(structblock_device *, struct hd_geometry*);

structmodule *owner;

}

int (*open)(structinode *, struct file*);当系统执行mount、创建分区、在分区上创建文件系统,运行文件系统检查程序等时被调用。

int(*release)(struct inode*, struct file *);当系统执行umount等其他关闭设备操作时被调用。

int (*ioctl)(structinode*, struct file *, unsigned, unsigned long);用来提供一些特殊的操作,比如说查询磁盘物理信息等。

int (*media_changed)(structgendisk *)

int (*revalidate_disk)(structgendisk *)

这两个用来支持可移动介质。上层调用media_change以检查介质是否被改变如改变将返回非0值。

在介质改变后,上层将调用revalidate_disk来重新对新的介质进行一些初始化工作。

int (*getgeo)(struct block_device *, structhd_geometry*);用来填充驱动器信息。

在这里我们就发现块设备和字符设备驱动的区别了,该操作结构体中没有读写函数。因为块设备的读写操作是与I/O调度层的I/O请求绑定在一起的,一旦I/O调度层有I/O请求就会调用块设备的读写操作函数。下面开始介绍块设备如何响应I/O请求。

7.I/O请求

当内核以文件系统,虚拟子系统或者调用形式从块设备输入、输出块数据是,它将使用一个bio结构,用来描述这个操作。该结构会被传递给I/O调度层,I/O调度层会把它合并到一个已经存在的request结构中,或者根据需要再创建一个request结构中。为什么要这样做呢?因为内核为了使提高块设备的读写效率,它会将对相邻的扇区进行操作的多个请求(bio)合并成一个request。同样为了提高块设备的读写效率,I/O调度层又将每个request进行一些排序处理组成一个队列(request_que_t),使驱动以某种顺序去读取request_que_t的每一个request,然后进行块设备的实际读写操作。综上,bio是最基本的请求,然后内核会将对相邻扇区访问的bio组成一个request,接着再把request按照某种调度算法排序组成一个队列request_que_t。我们驱动程序要实现的就是提取每一个quest,然后获取其中的信息进行读写操作。

但是有一个问题,并不是所有块设备都像磁盘设备那样扇区之类的结构,比如说flash,ram盘之类的,对这一类的设备进行上述的I/O调度反而会使效率降低,所有内核又提供了实现I/O请求的另外一种方式,就是绕过请求队列,也就是绕过request和request_que_t直接对bio结构进行处理。

下面我们分别来介绍实现I/O请求响应的两种方式。

8.响应I/O请求实现方式一:request队列方式

request数据结构

struct request

{

struct list_head queuelist; //形成request链表的链表结构

sector_t sector; //要操作的首个扇区

unsigned long nr_sectors; //要操作的扇区数

struct bio *bio; //请求的bio链表头

struct bio *biotail; //请求的bio结构体的链表尾

……

}

操作请求队列的函数

初始化请求队列

struct request_queue *blk_init_queue(request_fn_proc*rfn, spinlock_t *lock)

rfn为请求队列的响应函数,这样就将驱动响应函数和I/O请求绑定到了一起。

lock是访问队列权限的自旋锁。

将该函数的返回值赋给gendisk结构的queue成员,这样就I/O调度层就会把组织好的request形成的队列填充到queue里面,然后调用rfn来响应对该块设备的I/O请求。rfn的原型为

typedef void (request_fn_proc) (request_que_t *q),它只有一个参数就是request_que_t队列。

清除请求队列

void blk_cleanup_queue(request_queue_t *q)

当块设备驱动模块卸载时调用此函数。

返回队列中下一个要处理的的请求(request):

struct request *elv_next_request(request_queue_t *queue)

并删除一个请求

void blkdev_dequeue_request(struct request *req)

9.响应I/O请求实现方式二:直接响应bio方式

bio结构的核心是一个名为bi_io_vec数组,它是由下面的结构组成的:

struct bio_vec {

struct page *bv_page;

unsignedint bv_len;

unsignedint bv_offset;

}

它表示了一个映射的物理页的信息。内核使用bio_for_each_segment(bvec,bio, segno)来遍历每个bio_vec结构。bvec是指当前的dio_vec入口, segno是段号。

驱动是程序使用blk_alloc_queue函数分配一个请求队列来告诉块设备子系统,I/O请求响应的是使用bio方式。

request_queue_t *blk_alloc_queue(int flags)

该函数与blk_init_queue的不同之处在于它并未真正实现一个保存的请求队列。flag是一系列标志用来为队列分配内存。通常是GFP_KERNEL。一旦拥有了队列,将它与make_request将响应函数传递给blk_queue_make_request:

void blk_queue_make_request(request_queue_t *queue,mak_request_fn *func);

请求响应函数的原型为

typedef int (make_request_fn) (request *q, struct bio *bio)

可以看出内核传递了一个bio结构给I/O请求响应函数,func可以读取bio的信息进行块设备的读写操作。

二、驱动代码分析

该驱动将一段内存模拟成一个块设备驱动,并使用bio方式实现I/O请求的响应

#include<linux/module.h>
#include<linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include<linux/kernel.h> /* printk() */
#include <linux/slab.h>            /* kmalloc() */
#include <linux/fs.h>        /* everything... */
#include <linux/errno.h>   /* error codes */
#include <linux/timer.h>
#include <linux/types.h>  /* size_t */
#include <linux/fcntl.h>   /* O_ACCMODE */
#include <linux/hdreg.h>  /* HDIO_GETGEO */
#include<linux/kdev_t.h>
#include<linux/vmalloc.h>
#include <linux/genhd.h>
#include<linux/blkdev.h>
#include<linux/buffer_head.h>     /*invalidate_bdev */
#include <linux/bio.h>
#include<linux/version.h>
 
 
#defineSIMP_BLKDEV_DEVICEMAJOR       COMPAQ_SMART2_MAJOR
#defineSIMP_BLKDEV_DISKNAME       "simp_blkdev"
#define SIMP_BLKDEV_BYTES        (16*1024*1024)
 
static struct request_queue*simp_blkdev_queue;
static struct gendisk*simp_blkdev_disk;
unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];
 
static intsimp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
        struct bio_vec *bvec;
        int i;
        void *dsk_mem;
 
              //判断要访问的数据是否大于块设备最大容量,如果是则调用bio_endio通知内核完成请求。
        if ((bio->bi_sector << 9) +bio->bi_size > SIMP_BLKDEV_BYTES) {
                printk(KERN_ERRSIMP_BLKDEV_DISKNAME
                        ": bad request:block=%llu, count=%u\n",
                        (unsigned longlong)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链表中的每一个bio_vec元素,然后判断是读还是写操作进行数据传输,传输完成后调用bio_endio通知内核完成请求。
        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_ERRSIMP_BLKDEV_DISKNAME
                                ": unknownvalue 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;
}
 
 
struct block_device_operationssimp_blkdev_fops = {
        .owner                = THIS_MODULE,
};
 
static int __initsimp_blkdev_init(void)
{
        int ret;
             
              //分配响应队列
        simp_blkdev_queue =blk_alloc_queue(GFP_KERNEL);
        if (!simp_blkdev_queue) {
                ret = -ENOMEM;
                goto err_alloc_queue;
        }
      
              //将内核响应队列和I/O请求响应函数绑定
       blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
 
              //分配一个gendisk结构
        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }
 
              //初始化gendisk结构
        strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major =SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops =&simp_blkdev_fops;
        simp_blkdev_disk->queue =simp_blkdev_queue;//初始化I/O请求队列
        set_capacity(simp_blkdev_disk,SIMP_BLKDEV_BYTES>>9);
        add_disk(simp_blkdev_disk);//向内核添加一个gendisk对象
 
        return 0;
 
err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
        return ret;
}
 
 
static void __exitsimp_blkdev_exit(void)
{
        del_gendisk(simp_blkdev_disk);//注销gendisk对象
        put_disk(simp_blkdev_disk);//减小gendisk引用计数
        blk_cleanup_queue(simp_blkdev_queue);//清楚请求队列
}
 
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);


分享到:
评论

相关推荐

    linux 设备驱动

    正如Linus Torvalds告知大家:“我们退回到这样一个时代——人人都为自己写设备驱动程序。” 当我不能再写出有创意的编码以后,我就开始为Linux Journal写一些技术性文章了,这也算是为Linux社团的一点贡献吧。后来...

    linux 2.6内核标准教程

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口...本书适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学习操作系统的参考书。

    Linux 2.6内核标准教程(部分)

    Linux内核是Linux操作系统中最核心的部分,用于... 本书适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学习操作系统的参考书。 本资源根据网络试读内容整理。

    linux 2.6内核标准 part3/3

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学习操作系统的参考书。Linux内核是Linux操作系统中最核心的部分,用于实现对硬件...

    LINUX 2_6内核标准 part2/3

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学习操作系统的参考书。Linux内核是Linux操作系统中最核心的部分,用于实现对硬件...

    LINUX 2_6内核标准 part1/3

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学习操作系统的参考书。Linux内核是Linux操作系统中最核心的部分,用于实现对硬件...

    ARM9嵌入式系统设计-基于S3C2410与Linux

    针对在嵌入式市场上颇具竞争力的ARM9处理器——S3C2410和开放源码的Linux操作系统,讲述嵌入式系统的概念、软硬件的开发和调试手段、嵌入式Linux驱动程序和应用程序的开发以及图形用户界面MiniGUI的移植和应用。...

    Linux内核工作原理 word版本 强烈推荐

    设备驱动程序一章将描叙Linux核心是如何控制系统中的物理设备。 文件系统一章描叙了Linux核心是如何维护它所支持的文件系统中的文件。同时还描叙了虚拟文件系统(VFS)及Linux核心的每种文件系统是如何得到支持。 ...

    Linux2.6内核标准教程(共计8-- 第1个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Python灰帽子-黑客与逆向工程师的Python编程之道[简体中文版]

    中文名: Python灰帽子-黑客与逆向工程师的Python编程之道 原名: Gray Hat Python:Python Programming for Hackers and Reverse Engineers 作者:Justin Seitz 资源格式: PDF 版本: 扫描版 出版社: 电子工业出版社 ...

    Linux2.6内核标准教程(共计8--第6个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第8个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第3个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第7个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    中文注释内核源码,适用于嵌入式linux软件工程师,中文注释只是部分,中文注释将保持更新,更新进度取决于我的学习进度

    Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次。如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径。我们都知道,想成为优秀的程序员,需要大量...

    S3C2410完全开发流程

    通过学习这些章节,您可以详细了解到如何在一个裸板上裁减、移植linux,如何构造自己的根文件系统,如何编写适合客户需求的驱动程序——驱动程序这章将结合几个经典的驱动程序进行讲解。您还可以了解到在用在nand ...

    Linux2.6内核标准教程(共计8--第4个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第2个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Linux2.6内核标准教程(共计8--第5个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

    Android系统级深入开发—移植与调试].韩超.梁泉

    本书适合Linux开发人员、移动设备开发人员、Android系统框架层和底层开发人员、有意图深入学习Android的人员、以及从事手机研发的读者阅读。 第1章 Android系统移植开发概述 第2章 Android源代码和开发环境 第3章 ...

Global site tag (gtag.js) - Google Analytics