一、概述

  Linux内核中SCSI子系统由SCSI上层,中间层,底层驱动模块三部分组成,负责管理SCSI资源和处理其他子系统,如文件系统,提交到SCSI子系统中的IO请求。

因此,理解SCSI子系统的IO处理机制对理解整个SCSI子系统至关重要,同时也有助于理解整个Linux内核的IO处理机制。

二、SCSI

  SCSI设备访问请求的提交分为两个步骤:

  • 用户空间提交请求到通用块层
  • 通用块层提交块请求到SCSI子系统

  用户空间提交请求到同样块层:

    在Linux用户空间,有三种方式提交对SCSI设备请求到通用块层:

  1. 通过文件系统(fs)提供的文件访问接口进行访问。  对建立在 SCSI 设备上的 Linux 文件系统中的文件读写操作,就属于这种访问方式; 
  2. RAW设备访问方式。  常见的应用就是 dd 命令, RAW设备访问方式 和 通过文件系统提供的文件访问接口进行访问的最大区别在于前者对SCSI设备直接进行线性地址访问, 不需要由文件系统进行地址映射;
  3. SCSI PASSTHROUGH 方式。 通过 LINUX 提供的 SG 进行访问,就属于这种方式,用户可以直接发 CDB[2] 命令给 SCSI 设备。所以,通过该接口,用户可以做一些 SCSI 管理操作,如 SES 管理等。

 LINUX 内核处理三种访问请求的方式

  经由文件系统或RAW设备方式提交的请求,会通过底层块设备访问层(ll_rw_block()),生成块IO请求(bio),并提交给通用块层 [3] ;而通过 SG 接口提交的访问请求,会调用 SCSI 中间层提供的接口,将请求直接交由通用块层进行处理

通用块层提交块访问请求到 SCSI 子系统

  为什么要通过通用块层呢?这是因为首先通用块层会根据磁盘访问的特性对请求进行优化操作;其次,通用块层提供了调度功能,能够对请求进行调度;再次,通用块层可扩展的结构,使各种设备的块驱动都能比较容易的和其集成。

当请求提交到通用块层后,通用块层需要完成准备,调度并交付块访问请求给 SCSI 中间层的操作。块访问请求可以理解为描述了块访问区域,访问方式和关联的 BIO 的请求,在内核中用 'struct request'结构表示。块设备会有对应的块访问请求设备队列,用于记录需要该设备处理的访问请求,新生成的块访问请求会被加入到对应设备的块访问请求队列中。 SCSI 子系统对 IO 的处理,实际上是处理块访问请求队列上的块访问请求。

  通用块层提供了两种方式调度处理块访问请求队列:

  • 直接调度
  • 通过 LINUX 内核工作队列机制调度执行。

  两种方式,最后都会调用块访问请求队列处理函数进行处理,而 SCSI 设备在初始化时会向通用块层注册 SCSI 子系统定义的块访问请求队列处理函数。清单 1[4] 显示了这个过程。这样当通用块层处理 SCSI 设备的块访问请求队列时,调用的就是 SCSI 中间层定义的这些处理函数。通过这种方式,通用块层就将块访问请求的处理交给了 SCSI 子系统。

 1 struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
 2  {   ……
 3     q = blk_init_queue(scsi_request_fn, NULL);
 4      //request generate block layer allocate a request queue
 5     ……
 6     blk_queue_prep_rq(q, scsi_prep_fn); //Prepare a scsi request
 7     blk_queue_max_hw_segments(q, shost->sg_tablesize);
 8     //define sg table size
 9     ……
10     blk_queue_softirq_done(q, scsi_softirq_done);
11  }

SCSI 子系统处理块访问请求

   当 SCSI 子系统的请求队列处理函数被通用块层调用后,SCSI 中间层会根据块访问请求的内容,生成、初始并提交 SCSI 命令 (struct scsi_cmd) 到 SCSI TARGET 端

SCSI命令初始化和提交:

  SCSI 命令记录了命令描述块 (CDB),感测数据缓存 (SENSE BUFFER),IO 超时时间等 SCSI 相关的信息和 SCSI 子系统处理命令需要的一些其他信息,如回调函数等。清单 2 显示了这个命令的主要结构。

 1 struct scsi_cmnd {
 2     ……
 3     void (*done) (struct scsi_cmnd *);      /* Mid-level done function */
 4     ……
 5     int retries;                /*retried time*/
 6     int timeout_per_command;   /*timeout define*/
 7     ……
 8     enum dma_data_direction sc_data_direction;  /*data transfer direction*/
 9     ……
10     unsigned char cmnd[MAX_COMMAND_SIZE];   /*cdb*/
11     void *request_buffer;          /* Actual requested buffer */
12     struct request *request;      /* The command we are working on */
13     ……
14     unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];
15                                /* obtained by REQUEST SENSE when
16                                * CHECK CONDITION is received on original
17                                * command (auto-sense) */
18     /* Low-level done function - can be used by */
19     /*low-level driver to point  to completion function. */
20     void (*scsi_done) (struct scsi_cmnd *);
21     ……
22  };

  初始化的过程首先按照电梯调度算法,从块设备的请求队列上取出一个块访问请求,根据块访问请求的信息,定义 SCSI 命令中数据传输的方向,长度和地址。其次,定义 CDB,SCSI 中间层的回调函数等。

  在完成初始化后,SCSI 中间层通过调用scsi_host_template[5]结构中定义queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动部分。queuecommand函数,是一个 SCSI 命令队列处理函数,在 SCSI 底层驱动中,定义了queuecommand函数的具体实现。因此,SCSI

中间层,调用queuecommand函数实际上就是调用了底层驱动定义的queuecommand函数的处理实体,将 SCSI 命令提交给了各个厂家定义的 SCSI 底层驱动进行处理。这个过程和通用块设备层调用 SCSI 中间层的处理函数进行块请求处理的机制很相似,这也体现了 LINUX

内核代码具有很好的扩展性。底层驱动接受到请求后,就要开始处理 SCSI 命令了,这一层和硬件关系紧密,所以这块代码一般都是由各个厂家自己实现。基本流程可概括为:从底层驱动维护的队列中,取出一个 SCSI 命令,封装成厂家自定义的请求格式,然后采用

DMA 或者其他方式,将请求提交给 SCSI TARGET 端,由 SCSI TARGET 端对请求处理,并返回执行结果给 SCSI 底层驱动层。

SCSI 命令执行结果的处理

  当 SCSI 底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,SCSI 子系统主要通过两次回调过程完成对命令执行结果的处理。 SCSI 底层驱动在接受到 SCSI TARGET 端返回的命令执行结果后,会调用 SCSI 中间层定义的回调函数,将处理结果交付给 SCSI 中间层进行处理,这是第一次回调过程。 SCSI 中间层处理完成后,将调用 SCSI 上层定义的回调函数,结束 IO 在整个 SCSI 子系统中的处理,这为第二次回调过程。

第一次回调:

  SCSI 中间层在调用queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动的同时,也将回调函数指针传给了 SCSI 底层驱动。底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,会调用该回调函数,产生一个中断号为 BLOCK_SOFTIRQ 的软中断进行第一次回调处理。在这次回调处理过程中,SCSI 中间层首先会根据 SCSI 底层驱动处理的结果判断请求处理是否成功。处理成功,并不意味着处理没有错误,而是返回的信息,能够让 SCSI 中间层很明确的知道,对于这个命令,中间层已经没有必要继续进行处理了。所以,对于处理成功的 SCSI 命令,SCSI 中间层会调用第二次回调函数进入到第二次回调过程。清单 3 显示了 SCSI 中间层定义的该软中断的处理函数。

 1 static void scsi_softirq_done(struct request *rq)
 2  {
 3     ……
 4     disposition = scsi_decide_disposition(cmd);
 5     ……
 6     switch (disposition) {
 7       case SUCCESS:
 8         scsi_finish_command(cmd);
 9         //enter to second callback process
10         break;
11       case NEEDS_RETRY:
12         scsi_retry_command(cmd);
13         break;
14       case ADD_TO_MLQUEUE:
15         scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY);
16          break;
17        default:
18          if (!scsi_eh_scmd_add(cmd, 0))
19             scsi_finish_command(cmd);
20     }
21  }

第二次回调:

  不同的 SCSI 上层模块会定义自己不同的第二次回调函数,如 SD 模块,会在sd_init_command函数中,定义自己的第二次回调函数sd_rw_intr,这个回调函数会根据 SD 模块的需要,对 SCSI 命令执行的结果做进一步的处理。清单 4 显示了 SD 模块注册第二次回调的代码。虽然各个 SCSI 上层模块可以定义自己的第二次回调函数,但是这些回调函数最终都会结束 SCSI 子系统对这个块访问请求的处理。

1 static int sd_init_command(struct scsi_cmnd * SCpnt)
2  {
3     ……
4     SCpnt->done = sd_rw_intr;
5     return 1;
6  }

Linux IO系统分析(scsi篇)相关推荐

  1. 2020-02-14 转载 开发应该知道的Linux系统分析-网络篇

    开发应该知道的Linux系统分析-网络篇 原文地址:https://cloud.tencent.com/developer/article/1583803 常用网络工具有: 通过ping命令检测网络的 ...

  2. 分享一篇很棒的Linux IO栈讲解

    写在前面 在开始正式的讨论前,我先抛出几个问题: 谈到磁盘时,常说的HDD磁盘和SSD磁盘最大的区别是什么?这些差异会影响我们的系统设计吗? 单线程写文件有点慢,那多开几个线程一起写是不是可以加速呢? ...

  3. 鸟哥的Linux私房菜(基础篇)- 第二十章、启动流程、模块管理与 Loader

    第二十章.启动流程.模块管理与 Loader 最近升级日期:2009/09/14 系统启动其实是一项非常复杂的程序,因为核心得要侦测硬件并加载适当的驱动程序后,接下来则必须要呼叫程序来准备好系统运行的 ...

  4. linux io的cfq代码理解一

    内核版本: 3.10内核. CFQ,即Completely Fair Queueing绝对公平调度器,原理是基于时间片的角度去保证公平,其实如果一台设备既有单队列,又有多队列,既有快速的NVME,又有 ...

  5. 浅析Linux IO

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:0xffffff.org/ 写在前面 在开始正式的讨论前,我 ...

  6. c linux time微秒_学习linux,看这篇1.5w多字的linux命令详解(6小时讲明白Linux)

    用心分享,共同成长 没有什么比每天进步一点点更重要了 本篇文章主要讲解了一些linux常用命令,主要讲解模式是,命令介绍.命令参数格式.命令参数.命令常用参数示例.由于linux命令较多,我还特意选了 ...

  7. linux性能优化--cpu篇

    linux性能优化--cpu篇 前言 负载 CPU使用率 proc perf 一些链接 `perf list` 比较有用的event `perf stat` `perf record` Profili ...

  8. 详解linux io flush

    女主宣言 今天小编为大家分享linux io flush,通过本文你会清楚知道fsync().fdatasync().sync().O_DIRECT.O_SYNC.REQ_PREFLUSH.REQ_F ...

  9. 深入剖析Linux IO原理和几种零拷贝机制的实现

    本文来说下Linux IO原理和几种零拷贝机制的实现 文章目录 概述 物理内存和虚拟内存 物理内存 虚拟内存 内核空间和用户空间 内核空间 用户空间 Linux的内部层级结构 Linux I/O读写方 ...

  10. Linux IO体系、零拷贝和虚拟内存关系的重新思考

    目录 1. 关系梳理 2. 我们先看虚拟内存到底解决什么问题? 3. Linux IO体系重点解决什么问题? 4. 零拷贝重点解决什么问题? 4.1 为什么会谈零拷贝? 4.2 传统IO方式有什么问题 ...

最新文章

  1. android layout组件,Android UI学习 - Linear Layout, RelativeLayout
  2. 分布式消息队列Kafka集群安装
  3. cmakelist 定义变量
  4. Python 相对路径、绝对路径的写法实例演示
  5. Graphviz:利用可视化工具Graphviz将dot数据进行图像可视化或者图像保存(两大方法)之详细攻略
  6. HandlerInterceptorAdapter或HandlerInterceptor的使用
  7. CSS3 列表、表格、滤镜
  8. 系统调用和库函数调用
  9. 一个程序员的成长的六个阶段(转帖)
  10. Tun/Tap接口教材-[翻译:Tun/Tap interface tutorial]
  11. 语言自制教具_学习笔记:蒙特梭利教师必备硬核技能“蒙氏理论+教具制作”...
  12. 在java中生成二维码,并直接输出到jsp页面
  13. 电脑重装系统后如何删除微软商店下载记录
  14. 遗传算法与TSP问题
  15. 为什么经转速环PI之后的输出量是电流(基于MTPA分析,内含代码)
  16. 用计算机算术表白,数学表白密码
  17. A Magic Lamp
  18. 微信图片怎么添加竖排文字_怎样在手机上给微信图片上添加文字?
  19. vs2013下git的使用
  20. Python中的array[:]表示什么意思?记录一个幺蛾子

热门文章

  1. MVC4 Action
  2. SQL*Loader 和 Data Pump
  3. CF1033A. King Escape的题解
  4. Dubbo--002--例子程序
  5. LeetCode131:Palindrome Partitioning
  6. 【转载】快速掌握一个语言最常用的50%
  7. RT thread 设备驱动组件之USART设备
  8. SAE实践——用SVN命令行同步/提交代码
  9. 深入理解HTTP协议(转)
  10. 《授予博士、硕士学位和培养研究生的学科、专业目录》 (1997 颁布 )