SCSI设备IO过程:磁盘上线与IO过程

一,SCSI设备上报过程:硬盘上线过程


SATA盘AHCI控制器初始化过程:
ahci_init()
 ->pci_module_init(&ahci_pci_driver);
static struct pci_driver ahci_pci_driver = {
.name = DRV_NAME,
.id_table = ahci_pci_tbl,
.probe = ahci_init_one,
.remove = ata_pci_remove_one,
};
ahci_init_one(struct pci_dev*pdev,struct pci_device_id*ent)
-->scsi_host_alloc(sht,privsize) //分配一个SCSI控制器 struct scsi_host
//并创建了一个SCSI控制器的错误处理线程:shost->ehandler=kthread_run(scsi_error_handler,"scsi_eh_%d",shost->host_no)
-->scsi_add_host(host,dev); //向系统中添加SCSI 控制器
-->scsi_scan_host(host)   //扫描此SCSI控制器
---->scsi_scan_channel(shost, channel, id, lun, rescan); //扫描所有的总线CHANNEL
------> scsi_scan_target(shost, channel, order_id, lun, rescan);  //扫描所有的目标器
-------->scsi_probe_and_add_lun //扫描目标器下的逻辑设备lun
---------->scsi_alloc_sdev(host, channel, id, lun, hostdata); //分配scsi逻辑设备 struct scsi_device 
//在此指定了设备的总线类型为scsi_bus_type
//并调用了 scsi_alloc_queue(sdev) 为SCSI设备分配了请求队列
// 设置请求队列的unplug超时为3ms, 超时函数blk_unplug_timeout-->generic_unplug_device
---------->scsi_allocate_request(sdev, GFP_ATOMIC);
------------->scsi_probe_lun 
------------->scsi_add_lun(sdev, result, &bflags);
--------------->device_add()  //把设备添加到所属总线的设备列表
------------------>bus_add_device(dev)
--------------------->device_attach(dev) //总线尝试关联设备与驱动
------------------------->driver_probe_device
------------------------->drv->probe(dev);

init_sd() //sd块设备驱动初始化
scsi_register_driver(&sd_template.gendrv)
driver_attach(drv); //把驱动加入总线驱动列表

static struct scsi_driver sd_template = {
.owner = THIS_MODULE,
.gendrv = {
.name = "sd",
.probe = sd_probe,
.remove = sd_remove,
.shutdown = sd_shutdown,
},
.rescan = sd_rescan,
.init_command = sd_init_command,
.issue_flush = sd_issue_flush,
};


sd_probe(dev)  //设置SCSI设备的超时时间,struct scsi_device->timout=30ms
gd=alloc_disk(16)
设备磁盘的超时为30ms(stuct scsi_device.timeout=30*HZ),
add_disk(gd)<----scsi_alloc_queue()
向系统添回块设备。磁盘上线完成

二,磁盘IO过程

scsi_alloc_queue(sdev)
q->make_request_fn=__make_request
q->request_fn = scsi_request_fn
q->prep_rq_fn = scsi_prep_fn

SCSI设备IO过程:磁盘上线与IO过程_第1张图片

2.1 不同的IO提交方式:                  设置不同的完成回调函数

   submit_bh()//   --->bio->bi_end_io =end_bio_bh_io_sync
   swap_readpage()  --->bio->bi_end_io = end_swap_bio_read 
1,submit_bio(int rw,struct bio*bio)//向块层提交BIO的通用接口 
2,-->generic_make_request(bio); //通用块层BIO提交函数
3,---->__generic_make_request(struct bio *bio)
4,------>q->make_request_fn(q,bio)  //提交bio到请求队列

              __make_request(q,bio); 

      //如果队列为空,就分配一个新请求req,plug到请求队列,设置定时器3ms,等待超时unplug

          elv_merge(q,&req,bio)   //IO调度算法,检查bio是否可以合入已有请求,可以向前/向后合并
5.1------>get_request_wait(q,rw,bio)  //不能合并时,获取一个新的请求
----------->get_request(q,rw,bio,GFP_NOIO)
------------->blk_alloc_request(q,rw,gfp_mask) //分配一个新的请求,并初始化
                 mempool_alloc(q->rq.rq_pool, gfp_mask);
                 blk_rq_init(q,rq);
5.2------>init_request_from_bio(req,bio); //用bio初始化一个新请求
5.3------>  __elv_add_request(q, rq, where); //把新请求加入请求队列
    q->elevator->ops->elevator_add_req_fn(q, rq); //IO调度算法,向请求队列中加入新请求
5.4------> __blk_run_queue(q) /或unplug超时 __generic_unplug_device(q); //激活请求队列
------------>q->request_fn(q); //把请求队列提交给SCSI中间 
==============以上为SCSI上层(SCSI设备驱动层)==============
6,----------> scsi_request_fn(q)  //SCSI中间层处理请求

7.1 ----------->req=blk_peek_request(q) 或 rq=elv_next_request(q)  //从请求队列中获取一个请求

----------------->q->prep_rq_fn(q, rq);//对获取到的请求进行预处理,
---------------------scsi_prep_fn(q,rq)

--------------------->scsi_get_command(sdev, GFP_ATOMIC); 

                          //分配SCSI命令struct scsi_cmnd,并初始化,req->special=cmnd

--------------------->cmd = __scsi_get_command(dev->host, gfp_mask); //分配SCSI命令结构
                          -->kmem_cache_alloc(shost->cmd_pool->slab,gfp_mask | shost->cmd_pool->gfp_mask);
                             scsi_init_io(cmd) //初始化SCSI命令结构中的sg(分散聚合表)
                             drv->init_command(cmd) //驱动初始化SCSI命令
                          -->sd_init_command(cmd) //磁盘驱动初始化SCSI命令
                         scmd->cmnd[]  构建SCSI CDB,
                        设置超时时间cmd->timeout_per_command=scsi设备超时时间30ms
                       设置SCSI命令的回函数cmd->done=sd_rw_intr()  或scsi_done()  
7.2,------------>scsi_dispatch_cmd(cmd); //分发请求,把SCSI命令提交给SCSI控制器   

                     scsi_add_timer(cmd, cmd->timeout_per_command,scsi_times_out); 

                           //设置SCSI命令的超时处理函数30ms

8,----------------->host->hostt->queuecommand(cmd, scsi_done);
=============以上为SCSI中间层(SCSI协议层)=================

本层为SCSI控制器的驱动,由控制器厂商实现驱动,一盘为了扩展
SATA盘AHCI控制器:  ahci_sht->queuecommand=ata_scsi_queuecmd(cmd,scsi_done)
SAS盘SAS控制器:PMC(如pm8001)pm8001_sht->queuecommand=sas_queuecommand(cmd,scsi_done)
LSI(如mpt2sas,mpt3sas) scsih_driver_template->queuecommand=_scsi_qcmd(cmd,scsi_done)


=========以上为SCSI低层(SCSI传输层/SCSI控制器驱动层)==========


2.1,IO返回过程与错误处理

SCSI低层命令返回: 命令返回与错误处理
scsi_done():
scsi_delete_timer(scmd)//删除定时器
__scsi_done(scmd)
30ms超时:scsi_times_out(scmd):
scmd->device->host->hostt->eh_timed_out(scmd)
   如果成功处理,则__scsi_done(scmd)
或(重试) 重新设置定时器,再等30ms

__scsi_done(scmd):
//把scmd添加到scsi_done_q的全局链表
//触发软中断SCSI_SOFTIRQ
--->scsi_softirq()    或者  scsi_softirq_done()
遍历处理scsi_done_q链表中的scmd,
disposition = scsi_decide_disposition(cmd);
switch (disposition) {
case SUCCESS:

    scsi_finish_command(cmd);/* 结束命令 */  

    //调用SCSI命令的回调函数 sd_rw_intr(cmd) 或 scsi_done(cmd)

    //最终都会调用 bio->bi_end_io
    break;
case NEEDS_RETRY:

     scsi_retry_command(cmd);/* 立即重试命令 */ 

    //把SCSI请求重新插入请求队列 scsi_queue_insert(cmd,)

    break;
case ADD_TO_MLQUEUE:
    scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY);/* 延时重试命令 */
    break;
default:
    if (!scsi_eh_scmd_add(cmd, 0))/* 进入SCSI控制器的错误处理流程.唤配shost->ehandler错误处理线程 */
    scsi_finish_command(cmd);/* 不能进行错误处理,强制结束这个SCSI命令 */
}

scsi_error_handler():
if (shost->hostt->eh_strategy_handler) /* 主机适配器定义了错误恢复处理回调 */
rtn = shost->hostt->eh_strategy_handler(shost);
else
scsi_unjam_host(shost);/* 默认的错误恢复函数 */


scsi_unjam_host():

if (!scsi_eh_get_sense(&eh_work_q, &eh_done_q))/* 发送用于错误恢复的SCSI命令 */
if (!scsi_eh_abort_cmds(&eh_work_q, &eh_done_q))/* 放弃故障的命令 */
scsi_eh_ready_devs(shost, &eh_work_q, &eh_done_q);

static void scsi_eh_ready_devs(struct Scsi_Host *shost,
      struct list_head *work_q,
      struct list_head *done_q)
{
if (!scsi_eh_stu(shost, work_q, done_q))/* 发送命令重启设备 */
if (!scsi_eh_bus_device_reset(shost, work_q, done_q))/* 复位逻辑设备 */
if (!scsi_eh_bus_reset(shost, work_q, done_q))/* 复位总线通道 */
if (!scsi_eh_host_reset(work_q, done_q))/* 复位主机适配器 */
scsi_eh_offline_sdevs(work_q, done_q);/* 使SCSI设备离线 */
}

你可能感兴趣的:(LINUX内核学习,存储与编程)