一,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
2.1 不同的IO提交方式: 设置不同的完成回调函数
submit_bh()// --->bio->bi_end_io =end_bio_bh_io_syncswap_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_iobreak;
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设备离线 */
}