《Linux Device Drivers》第十六章 块设备驱动程序——note

  • 简介

    • 一个块设备驱动程序主要通过传输固定大小的随机数据来访问设备
    • Linux内核视块设备为与字符设备相异的基本设备类型
    • Linux块设备驱动程序接口使得块设备可以发挥其最大的功效,但是其复杂程序又是编程者必须面对的一个问题
    • 一个数据块指的是固定大小的数据,而大小的值由内核确定
    • 数据块的大小通常是4096个字节,但是可以根据体系结构和所使用的文件系统进行改变
    • 与数据块对应的是扇区,它是由底层硬件决定大小的一个块,内核所处理的设备扇区大小是512字节
    • 如果要使用不同的硬件扇区大小,用户必须对内核的扇区数做相应的修改
  • 注册
    • 注册块设备驱动程序

      • <linux/fs.h>
      • int register_blkdev(unsigned int major, const char *name);
        • 如果需要的话分配一个动态的主设备号
        • 在/proc/devices中创建一个入口项
      • int unregister_blkdev(unsigned int major, const char *name);
    • 注册磁盘
      • struct block_device_operations

        • int (*open) (struct inode *inode, struct file *filp);
        • int (*release) (struct inode *inode, struct file *filp);
        • int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
        • int (*media_changed) (struct gendisk *gd);
        • int (*revalidate_disk) (struct gendisk *gd);
        • struct module *owner;
      • gendisk结构
        • <linux/genhd.h>
        • struct gendisk
          • int major;
          • int first_minor;
          • int minors;
            • 常取16
          • char disk_name[32]
            • 显示在/proc/partitions和sysfs中
          • struct block_device_operations *fops;
          • struct request_queue *queue;
          • int flags;
          • sector_t capacity;
          • void *private_data;
        • struct gendisk *alloc_disk(int minors);
        • void del_gendisk(struct gendisk *gd);
        • void add_disk(struct gendisk *gd);
  • 块设备操作

    • open和release函数

      • 对于那些操作实际硬件设备的驱动程序,open和release函数可以设置驱动程序和硬件的状态。这些操作包括使磁盘开始或者停止旋转,锁住可移动介质的仓门以及分配DMA缓存等
      • 有一些操作能够让块设备在用户空间内被直接打开,这些操作包括给磁盘分区,或者在分区上创建文件系统,或者运行文件系统检查程序
    • 对可移动介质的支持
      • 调用media_changed函数以检查介质是否被改变
      • 在介质改变后将调用revalideate函数
    • ioctl函数
      • 高层的块设备子系统在驱动程序获得ioctl命令前,已经截取了大量的命令
      • 实际上在一个现代驱动程序中,许多ioctl命令根本就不用实现
  • 请求处理
    • 每个块设备驱动程序的核心是它的请求函数
    • 驱动程序所需要知道的任何关于请求的信息,都包含在通过请求队列传递给我们的结构中
    • request函数介绍
      • void request(request_queue_t *queue);

        • 当内核需要驱动程序处理读取、写入以及其他对设备的操作时,就会调用该函数
      • 每个设备都有一个请求队列
        • dev->queue = blk_init_queue(test_request, &dev->lock);
      • 对request函数的调用是与用户空间进程中的动作完全异步的
    • 一个简单的request函数
      • struct request * elv_next_request(request_queue_t queue);
      • void end_request(struct request *req, int succeeded);
      • struct request
        • sector_t secotr;
        • unsigned long nr_sectors;
        • char *buffer
        • rq_data_dir(struct request *req);
    • 请求队列
      • 一个块设备请求队列可以这样描述:包含块设备I/O请求的序列
      • 请求队列跟踪未完成的块设备的I/O请求
      • 请求队列还实现了插件接口
      • I/O调度器还负责合并邻近的请求
      • 请求队列拥有request_queue或request_queue_t结构类型
      • <linux/blkdev.h>
      • 队列的创建与删除
        • request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
        • void blk_cleanup_queue(request_queue_t *queue);
      • 队列函数
        • struct request *elv_next_request(request_queue_t *queue);
        • void blkdev_dequeue_request(struct request *req);
        • void elv_requeue_request(request_queue_t *queue, struct request *req);
      • 队列控制函数
        • void blk_stop_queue(request_queue_t *queue);
        • void blk_start_queue(request_queue_t *queue);
        • void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
        • void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_segment_size(request_queue_t *queue, unsigned short max);
        • void blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
        • void blk_queue_dma_alignment(request_queue_t *queue, int mask);
        • void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
      • 请求过程剖析
        • 从本质上讲,一个request结构是作为一个bio结构的链表实现的
        • bio结构
          • bio结构包含了驱动程序执行请求的全部信息,而不必与初始化这个请求的用户空间的进程相关联
          • <linux/bio.h>
          • struct bio
            • sector_t bi_sector;
            • unsigned int bi_size;
              • 以字节为单位所需要传输的数据大小
            • unsigned long bi_flags;
            • unsigned short bio_phys_segments;
            • unsigned short bio_hw_segments;
            • struct bio_vec *bi_io_vec
          • struct bio_vec
            • struct page *vb_page;
            • unsigned int bv_len;
            • unsigned int bv_offset;
          • example
            • int segno;
            • struct bio_vec *bvec;
            • bio_for_each_segment(bvec, bio, segno)
            • {
              • /* 使用该段进行一定的操作 */
            • }
          • char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);
          • void __bio_kunmap_atomic(char *buffer, enum km_type type):
          • struct page *bio_page(struct bio *bio);
          • int bio_offset(struct bio *bio);
          • int bio_cur_sectors(struct bio *bio);
          • char *bio_data(struct bio *bio);
          • char *bio_kmap_irq(struct bio *bio, unsigned long *flags);
          • void bio_kunmap_irq(char *buffer, unsigned long *flags);
        • request结构成员
          • struct request

            • sector_t hard_sector;
            • unsigned long hard_nr_sectors;
            • unsigned int hard_cur_sectors;
            • struct bio *bio;
            • char *buffer;
            • unsigned short nr_phys_segments;
            • struct list_head queuelist;
        • 屏障请求
          • 在驱动程序接收到请求前,块设备层重新组合了请求以提高I/O性能
          • 出于同样的目的,驱动程序也可以重新组合请求
          • 但在无限制重新组合请求时面临了一个问题:一些应用程序的某些操作,要在另外一些操作开始前完成
          • 2.6版本的块设备层使用屏障(barrier)请求来解决这个问题
          • 如果一个请求被设置了REQ_HARDBARRER标志,那么在其他后续请求被初始化前,它必须被写入驱动器
          • void blk_queue_ordered(request_queue_t *queue, int flag);
          • int blk_barrier_rq(sruct request *req);
            • 如果返回一个非零值,该请求是一个屏障请求
        • 不可重试请求
          • int blk_noretry_request(struct request *req);
      • 请求完成函数
        • int end_that_request_first(struct request *req, int success, int count);
        • void end_that_request_last(struct request *req);
        • example
          • void end_request(struct request *req, int uptodate)
          • {
            • if (!end_that_request(req, uptodate, req->hard_cur_sectors)
            • {
              • add_disk_randomness(req->rq_disk);
              • blkdev_dequeue_request(req);
              • end_that_request_last(req);
            • }
          • }
        • 使用bio
          • example

            • struct request *req
            • struct bio *bio;
            • rq_for_each_bio(bio, req)
            • {
              • /* 使用该bio结构进行一定的操作 */
            • }
        • 块设备请求和DMA
          • int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);
          • clear_bit(QUEUE_FLAG_CLEAR, &queue->queue_flags);
        • 不使用请求队列
          • typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
          • void bio_endio(struct bio *bio, unsigned int bytes, int error);
          • request_queue_t *blk_alloc_queue(int flags);
            • 并未真正地建立一个保存请求的队列
          • void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);
          • drivers/block/ll_rw_block.c
  • 其他一些细节
    • 命令预处理

      • typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req);

        • 该函数要能返回下面的值之一

          • BLKPREP_OK
          • BLKPREP_KILL
          • BLKPREP_DEFER
      • void blk_queue_prep_rq(request_queue_t *queue, prep_rq_fn *func);
    • 标记命令队列
      • 同时拥有多个活动请求的硬件通常支持某种形式的标记命令队列(Tagged Command Queueing, TCQ)
      • TCQ只是为每个请求添加一个整数(标记)的技术,这样当驱动器完成它们中的一个请求后,它就可以告诉驱动程序完成的是哪个
      • int blk_queue_int_tags(request_queue_t *queue, int depth, struct blk_queue_tag *tags);
      • int blk_queue_resize_tags(request_queue_t *queue, int new_depth);
      • int blk_queue_start_tag(request_queue_t *queue, struct request *req);
      • void blk_queue_end_tag(request_queue_t *queue, struct request *req);
      • struct request *blk_queue_find_tag(request_queue_t *queue, int tag);
      • void blk_queue_invalidate_tags(request_queue_t *queue);
时间: 2024-08-24 12:32:14

《Linux Device Drivers》第十六章 块设备驱动程序——note的相关文章

《Linux Device Drivers》第三章 字符设备驱动程序——note

主设备号和次设备号 那些名称被称为特殊文件.设备文件,或者简单称之为文件系统树的节点,它们通常位于/dev目录 通常而言,主设备号标识设备对应的驱动程序 一个主设备号对应一个驱动程序 设备编号的内部表达 dev_t(<linux/types.h>) dev_t是一个32位的数,12位表示主设备号,其余20位表示次设备号 <linux/kdev_t.h> MAJOR(dev_t dev); MINOR(dev_t dev); MKDEV(int major, int minor);

《Linux Device Drivers》第六章 高级字符驱动程序操作——note

ioctl 支持的操作,例如 简单数据传输 控制动作,例如用户空间发起弹出介质动作 反馈硬件的状态,例如报告错误信息 参数配置,例如改变波特率 执行自破坏 用户空间的ioctl方法原型:int ioctl(int fd, unsigned long cmd, -);每个ioctl命令就是一个独立的系统调用,而且是非公开的 驱动程序的ioctl方法原型:int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, u

《Linux Device Drivers》第十一章 内核的数据类型——note

简介 由于Linux的多平台特性,任何一个重要的驱动程序都应该是可移植的 与内核代码相关的核心问题是应该能够同时访问已知长度的数据项,并充分利用不同处理器的能力 内核使用的数据类型主要被分成三类 类似int这样的标准C语言类型 类似u32这样的有确定大小的类型 像pid_t这样的用于特定内核对象的类型 本章将讨论在什么情况下使用这三种类型以及如何使用 使用标准C语言类型 当我们需要"两个字节的填充符"或者"用四个字节字符串表示的某个东西"时,我们不能使用标准类型,因

《Linux Device Drivers》 第十七章 网络驱动程序——note

简介 网络接口是第三类标准Linux设备,本章将描述网络接口是如何与内核其余的部分交互的 网络接口必须使用特定的内核数据结构注册自身,以备与外界进行数据线包交换时调用 对网络接口的常用文件操作是没有意义的,因此在它们身上无法体现Unix的"一切都是文件"的思想 网络驱动程序异步自外部世界的数据包 网络设备向内核请求把外部获得的数据包发送给内核 Linux内核中的网络子系统被设计成完全与协议无关 在网络世界中使用术语"octet"指一组8个的数据位,它是能为网络设备和

《Linux Device Drivers》第四章 调试技术——note

1.本章知识点 内核中和调试相关的选项 CONFIG_DEBUG_KERNEL CONFIG_DEBUG_SLAB CONFIG_DEBUG_PAGEALLOC CONFIG_DEBUG_SPINLOCK CONFIG_DEBUG_SPINLOCK_SLEEP CONFIG_INIT_DEBUG CONFIG_DEBUG_INFO CONFIG_MAGIC_SYSRQ CONFIG_DEBUG_STACKOVERFLOW CONFIG_DEBUG_STACK_USAGE CONFIG_KALLS

《Linux Device Drivers》第十一章 核心数据类型——note

基本介绍 因为Linux多平台特性,不管是哪一个重要驱动力应该是便携 与内核代码相关的核心问题应该是访问的同时是数据项的已知长度.能力和利用不同的处理器 内核使用的数据类型主要分为三类 类似int这种标准C语言类型 类似u32这种有确定大小的类型 像pid_t这种用于特定内核对象的类型 本章将讨论在什么情况下使用这三种类型以及怎样使用 使用标准C语言类型 当我们须要"两个字节的填充符"或者"用四个字节字符串表示的某个东西"时.我们不能使用标准类型,由于在不同的体系架

《Linux Device Drivers》第五章 并发和竞态——note

并发及其管理 竞态通常作为对资源的共享访问结果而产生 当两个执行线程需要访问相同的数据结构(或硬件资源)时,并发的可能性就永远存在 只要可能就应该避免资源的共享,但共享通常是必须的,硬件本质上就是共享的 访问管理的常见技术称为"锁定"或者"互斥" 信号量和互斥体 建立临界区:在任意给定的时刻,代码只能被一个线程执行 可以使用一种锁定机制,当进程在等待对临界区的访问时,此机制可让进程进入休眠状态 一个信号量本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为P

《Linux Device Drivers》 第七章 时间、延时及延缓操作——note

度量时间差 内核通过定时器中断来跟踪时间流 时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ的值设定,在常见的x86 PC平台上,默认定义为1000 <linux/param.h> <linux/timex.h> jiffies_64 unsigned long jiffies 使用jiffies计数器 <linux/jiffies.h> int time_after(unsigned long a, unsigned long b); int time

鸟哥的Linux私房菜——第十六章:学习Shell Scripts

视频链接: 1. 什么是 Shell Script       (shell写的脚本)1.1 干嘛学习 shell scripts? ()1.2 第一支 script 的撰写与执行1.3 撰写 shell script 的良好习惯建立 2. 简单的 shell script 练习: (read -p  date)3. 善用判断式:3.1 利用 test 指令的测试功能3.2 利用判断符号 [ ] 3.3 Shell script 的预设变数($0, $1...)4. 条件判断式:4.1 利用 i