11-S3C2440驱动学习(八)嵌入式linux-块设备驱动程序

来源:互联网 发布:ios 跳转到淘宝客户端 编辑:程序博客网 时间:2024/06/05 09:23

一、回顾字符设备驱动

主要分为简单字符设备驱动程序,和复杂字符设备驱动

1、简单字符设备驱动

对于简单的字符设备驱动,不需要采用分离分层的思想,主要包括以下几个部分。


2、复杂字符设备驱动

对于复杂的字符设备驱动,采用分离分层的思想,内核中已经实现好了核心层部分,我们只需要实现与硬件相关的部分就可以,最后形成一个总体。这样就是一个通用的字符驱动框架。如LCD驱动、V4L2驱动,当然有时候我们可以选择不采用分离分层的思想,按简单驱动程序的框架来实现一个驱动。


3、字符设备驱动常用技巧

(1)    查询方式

(2)    休眠唤醒,APP-read  drv_read 

(3)    poll机制

(4)    异步通知发信号

(5)    通用驱动程序:输入子系统、融入内核代码

二、块设备驱动

这里一般特指,NAND flash这类设备。

1、一些概念

(1)硬盘结构

硬盘里面有很多磁头,每个磁头有很多环,类似跑道。称为柱面:0柱面 1柱面;跑道里面有很多块称为扇区:扇区0  扇区1。硬盘操作的耗时主要在磁盘转换的时候,如果我们一会读1磁头,一会写2磁头,然后又读1磁头,如果来回这么跳转,会导致效率十分低。

.

(2)Flash

Flash有很多块,每个块里面有很多扇区。

Flash以块为单位进行操作。操作扇区时也需要读取整个块到内存中。如果读1块中的1扇区,又去写2块的1扇区,然后又读1块的2扇区,如果不优化效率将很低。


因此,基于以上硬件的特点,及效率问题,内核采用了电梯调度算法进行命令的优化。也就是收到一个命令时不会立刻执行,而是放入队列,当存在多个命令时,经过电梯调度算法来进行优化后执行,这样就提升了读写的效率,这个是块设备驱动程序要考虑的问题。

2、块设备驱动程序的框架


应用程序文件读写—》扇区读写,文件系统来做转换之后调用ll_rw_block函数操作块设备。

那么扇区读写函数是?

ll_rw_block:low_level  read write block

ll_rw_block作用:把读写放入队列并优化,之后调用队列的处理函数。即优化后执行。


分析ll_rw_block        for (i = 0; i < nr; i++) {            struct buffer_head *bh = bhs[i];            submit_bh(rw, bh);                struct bio *bio; // 使用bh来构造bio (block input/output)                submit_bio(rw, bio);                    // 通用的构造请求: 使用bio来构造请求(request)                    generic_make_request(bio);                        __generic_make_request(bio);                            request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列                                                          // 调用队列的"构造请求函数"                            ret = q->make_request_fn(q, bio);                                    // 默认的函数是__make_request                                    __make_request                                        // 先尝试合并                                        elv_merge(q, &req, bio);                                                                                // 如果合并不成,使用bio构造请求                                        init_request_from_bio(req, bio);                                                                                // 把请求放入队列                                        add_request(q, req);                                                                                // 执行队列                                        __generic_unplug_device(q);                                                // 调用队列的"处理函数"                                                q->request_fn(q);

int submit_bh(int rw, struct buffer_head *bh)

函数作用:用buffer_head来构造bio,最后提交bio--》submit_bio

void submit_bio(int rw, struct bio *bio)

      --generic_make_request

函数作用:使用bio来构造请求,把请求放入队列。

elv_merge(q, &req, bio); 电梯调度算法,来尝试把bio合并到队列里面去。如果合并不成,使用bio构造请求init_request_from_bio(req, bio); 把请求放入队列add_request(q,req); 执行队列__generic_unplug_device(q); 调用队列的"处理函数" q->request_fn(q);

这里强调:块设备的读写并不会立刻执行,而是先放入队列进行优化。

3、怎么写块设备驱动程序呢?

常规思路,分配结构体,设置结构体,注册到某个地方。

(1)分配gendisk:alloc_disk

(2)设置

分配/设置队列: request_queue_t  // 它提供读写能力

           blk_init_queue                 //设备队列

设置gendisk其他信息          // 它提供属性: 比如容量

(3) 注册: add_disk

4、内存模拟块设备

参考:
drivers\block\xd.c
drivers\block\z2ram.c
(1)拷贝头文件,搭出框架


(2)明确需要做的任务

       /*1. 分配一个gendisk结构体 */

       /*2. 设置 */

       /*2.1 分配/设置队列: 提供读写能力 */

       /*2.2 设置其他属性: 比如容量 */

       /*3. 注册 */

(3)实现

ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */

ramblock_buf= kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);//分配内存模拟

while ((req = elv_next_request(q)) != NULL)//电梯调度算法

所有代码:

/* 参考: * drivers\block\xd.c * drivers\block\z2ram.c */#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;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;//printk("do_ramblock_request %d\n", ++cnt);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");

(4)实验




没有立刻写,等一会才出现write


要么都是读要么都是写 不会一读一写。效率高 

分区试试


柱面数不知道


static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo){/* 容量=heads*cylinders*sectors*512 */geo->heads     = 2;  //  多少磁头 多少面 2面geo->cylinders = 32;   //   多少个柱面 多少环  32环 geo->sectors   = RAMBLOCK_SIZE/2/32/512;//一环里面多少扇区return 0;}

测试5th:

1. insmod ramblock.ko

2. ls /dev/ramblock*

3. fdisk /dev/ramblock


分别操作分区。




阅读全文
2 0