Chinaunix首页 | 论坛 | 博客
  • 博客访问: 387951
  • 博文数量: 62
  • 博客积分: 5015
  • 博客等级: 大校
  • 技术积分: 915
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-08 02:00
文章分类

全部博文(62)

文章存档

2009年(45)

2008年(17)

我的朋友

分类: LINUX

2009-04-27 23:56:48

最近发现SUSE10在重启后,Lun的设备文件名会出现交错的情况。比如,主机上有两个qlogic的HBA,分别连到阵列。在重启后,/dev下lun对应的设备文件可能出现如下情况:
HBA0: /dev/sda  /dev/sdd
HBA1:/dev/sdb  /dev/sdc
设备文件名的生成,是在sd_probe中进行(参见“linux scsi sd设备文件名的生成”),而设备文件,如/dev/sda,一般则是udev根据/sys/block下的块设备,也就是sd_probe中生成的设备文件名,生成的。因此,产生上述的设备文件名在不同host controller间乱序的现象,是因为其HBA驱动采用了异步扫描机制。为了更加深入理解这个问题,找到以上上现象的根源,需要分析Linux scsi的异步(或者说是并行)扫描机制。
其实在2.6.16中,scsi驱动并不支持这种异步扫描机制。Scsi_scan_host直接调用了scsi_scan_host_selected来对host上的所有lun进行扫描。它一次调用scsi_scan_channel,scsi_scan_target来扫描host上的每个channel和每个channel上的所有target。其中,在scsi_scan_target中,先调用scsi_probe_and_add_lun来扫描Lun0,然后尝试调用report lun的方式来扫描所有的lun,如果不成功,则调用顺序扫描的方式。其中前者需要scsi的report lun命令的支持。
但是,不论何种情况,整个扫描过程都是串行的。比如scsi_report_lun_scan,它首先构造report lun命令,并封装到request。然后调用scsi_execute_req执行该request。scsi_execute_req是个同步阻塞函数。它最终会调用blk_execute_rq来执行request。在blk_execute_rq中,先调用blk_execute_rq_nowait来提交request,然后阻塞在request的waiting变量中,等待request完成:
rq->waiting = &wait;
blk_execute_rq_nowait(q, bd_disk, rq, at_head, blk_end_sync_rq);
wait_for_completion(&wait);
当report lun的request返回后,遍获得了target中总共lun的数目。下一步就是调用scsi_probe_and_add_lun对每一个lun惊醒扫描。scsi_probe_and_add_lun主要调用两个函数:scsi_probe_lun和scsi_add_lun。前者构造scsi inquery命令,并调用scsi_execute_req来执行。而后者,则是对代表lun的scsi_dev进行初始化,并注册到内核:
if (scsi_sysfs_add_sdev(sdev) != 0)
return SCSI_SCAN_NO_RESPONSE;
scsi_sysfs_add_sdev把扫描的scsi_dev注册到内核,并为该设备生成相应的sysfs目录。其中,注册设备到内核,主要调用了device_add函数。该函数会把该scsi_dev挂到scsi_bus_type,然后调用scsi bus上的驱动,如sd驱动,来探测该设备。由于我们这儿是磁盘设备,因此它会调用sd的sd_probe来探测该设备。其中,在sd_probe中,会为该设备生成gendisk和其磁盘名称,如sda,也就是说scsi驱动没扫描到一个lun,就会为其生成相应的设备名。
从scsi_scan_host到最后调用sd_probe生成磁盘名称的整个过程是一个同步串行的过程。那么是否存在多个线程同时调用scsi_scan_host来对多个host controller进程扫描呢?对于一般的direct access的驱动来说,答案是否定的。对于一般的scsi host controller驱动,它会在其probe方法中调用scsi_scan_host方法来对某一host进行扫描。而该probe方法是通过controller驱动加载时,调用driver_register(如pci_register_driver)方法来触发的。在driver_register方法中,会对总线上每个设备,调用probe方法来对其探测。这个过程是串行的,只会在一个线程中执行。因此,对多个host controller的扫描也是一个串行的过程。
那么为什么在2.6.16的qlogic驱动中,在扫描lun的时候,会出现设备名交错的现象呢?之前我们分析的仅是一般设备。对于像FC HBA这种设备,它的扫描的实现方法又不同了。因为他并会在其probe方法中调用scsi_scan_host直接去扫描host上所有的lun,而是通过transport layer调用scsi_host的work queue,去并行地扫描lun。为了说明这个问题,我们拿qlogic的FC HBA作为例子。
在qla的probe方法qla2x00_probe_one中,并没有调用scsi_scan_host来直接对lun进行扫描。而是通过调用qla2x00_reg_remote_port来间接扫描lun。
qla2x00_reg_remote_port会调用scsi fc transport驱动的fc_remote_port_add来注册fc port。其中会调用fc_rport_create来创建一个fc port。对lun的扫描,真正发生在该函数中:
struct fc_rport *
fc_rport_create(struct Scsi_Host *shost, int channel,
 struct fc_rport_identifiers  *ids)
{

INIT_WORK(&rport->scan_work, fc_scsi_scan_rport, rport);

scsi_queue_work(shost, &rport->scan_work);
}
在该函数中,会初始化rport的work_struct can_work为fc_scsi_scan_rport。然后把该work调度到scsi host的work queue:work_q,也就是线程scsi_wq_x。该work queue是在分配scsi_host时初始化的。这样,每个scsi host,都会有一个线程对其进程扫描。而每个线程主要是调用scsi_scan_target(通过fc_scsi_scan_rport调用)函数来扫描该target下的所有lun。前面分析过,该函数是串行的,并且在扫描到一个lun后,会马上为其生成设备名。这样问题就出现了。在第一个scsi host的scan_work还没有执行完时,该worker thread可能被切换出去。如果这个时候,第二个scsi  host的scan work切换进来,进行扫描。由于sd设备名称是sd驱动全局的,所以这个时候如果第二个scsi host扫描到了lun,会接着第一个host分配的设备名继续分配。如果像这样,两个扫描线程交替切换,就出现了两个HBA下lun的设备名称交错的情况。并且,由于这种切换取决于kernel的调度,所以每次扫描所得到的lun的名字可能不一样。第一次启动时叫sda的lun,可能在第二次启动后变成sdb。
通过以上分析,说明了造成sd设备名称乱序的原因。其实归根到底就是因为2.6.16内核中只支持串行扫描,在对sd设备生成设备名时,采取的是先来先得的机制。但在qla等驱动中采用了并行扫描的方式,而采用这种方式时,又没有做全局的协调。导致在sd设备名称生成时,产生了竞态。
在较新的内核版本中考虑到了这个问题。比如在2.6.27中,scsi_scan_host的默认实现就是异步扫描:
void scsi_scan_host(struct Scsi_Host *shost)
{
 struct task_struct *p;
 struct async_scan_data *data;
 if (strncmp(scsi_scan_type, "none", 4) == 0)
  return;
 data = scsi_prep_async_scan(shost);
 if (!data) {
  do_scsi_scan_host(shost);
  return;
 }
 p = kthread_run(do_scan_async, data, "scsi_scan_%d", shost->host_no);
 if (IS_ERR(p))
  do_scan_async(data);
}
该版本的scsi_scan_host会在每次调用时,都创建一个线程scsi_scan_x来调用do_scan_async来进程异步扫描。do_scan_async首先调用do_scsi_scan_host来发起扫描,然后调用scsi_finish_async_scan来通知系统,当前扫描完成。其异步扫描机制是这样实现的。首先在do_scsi_scan_host中调用scsi_scan_host_selected来串行扫描,但是在扫描到一个lun之后,并不马上为其分配名字:
if (!async && scsi_sysfs_add_sdev(sdev) != 0)
  return SCSI_SCAN_NO_RESPONSE;
也就是说,假如现在有两个host,host controller 驱动分别为其调用scsi_scan_host来扫描lun。这个时候会有两个扫描线程,它们并行地扫描lun。这时的情况就像2.6.16中qla利用host的work queue来并行扫描一样。但唯一不同点就是,此时,在扫描到lun后并不马上生成设备名。设备名的生成,是scsi_finish_async_scan中执行的:
static void scsi_finish_async_scan(struct async_scan_data *data)
{
wait_for_completion(&data->prev_finished);
scsi_sysfs_add_devices(shost);
if (!list_empty(&scanning_hosts)) {
  struct async_scan_data *next = list_entry(scanning_hosts.next,
    struct async_scan_data, list);
  complete(&next->prev_finished);
}
}
在每开始一个异步扫描时,都会把代表当前host扫描的async_scan_data加入队列,其中的prev_finished完成变量表示前一个host扫描完成。在为host的所有lun生成设备名之前(调用scsi_sysfs_add_devices之前),需要等待前一个host扫描并生成文件名结束(wait_for_completion(&data->prev_finished);
)。而前一个host扫描并生成完所有lun的设备名后,会通知后一个host:complete(&next->prev_finished);。这样,就保证了每个host中lun名称的顺序。在此基础上,如果能保证host的扫描顺序,也就是host的async_scan_dada加入完成队列的顺序,那么整个系统的lun的名字顺序就能保证了。
这种把扫描和设备名生成分开的设计是合理的。Lun的扫描属于i/o操作,为了提高其效率,需要尽可能地并发执行。扫描的直接结果是为lun生成scsi_dev结构,该结构是lun在系统中的逻辑表示。它生成的先后顺序,和其设备名称其实没有关系。因此可以可以并行地生成scsi_dev,然后串行地分配设备名。相对于前者,后者要快的多。
此外,之前提到的“首先在do_scsi_scan_host中调用scsi_scan_host_selected来串行扫描”其实并不准确。2.6.27的scsi扫描中,显式地支持了驱动自身的异步扫描。也就是之前提到的,在2.6.16中,qla驱动通过scsi_host的work queue进行扫描的方式:
static void do_scsi_scan_host(struct Scsi_Host *shost)
{
 if (shost->hostt->scan_finished) {
  unsigned long start = jiffies;
  if (shost->hostt->scan_start)
   shost->hostt->scan_start(shost);
  while (!shost->hostt->scan_finished(shost, jiffies - start))
   msleep(10);
 } else {
  scsi_scan_host_selected(shost, SCAN_WILD_CARD, SCAN_WILD_CARD,
    SCAN_WILD_CARD, 0);
 }
}
如果驱动支持自身的异步扫描,则他需要实现scan_finished方法。如果驱动实现了scan_finished方法,则scsi驱动会调用scan_start来触发驱动自身的扫描,比如qla中的scan_start实现为:
qla2xxx_scan_start(struct Scsi_Host *shost)
{
 scsi_qla_host_t *ha = shost_priv(shost);
 set_bit(LOOP_RESYNC_NEEDED, &ha->dpc_flags);
 set_bit(LOCAL_LOOP_UPDATE, &ha->dpc_flags);
 set_bit(RSCN_UPDATE, &ha->dpc_flags);
}
这些标志的实现,最终会导致scsi fc transport 层的fc_scsi_scan_rport 函数被调度到host得work queue中执行。这种情况和直接调用scsi_scan_host_selected进行扫描的区别在于,这个时候其实存在两个相关线程在为一个host的扫描服务。Scsi_wq_x线程执行fc_scsi_scan_rport函数,进行具体的扫描工作,而scsi_scan_x线程,等待前者完成。
scsi异步扫描大致如上所述。从整个分析可以看出,sd的设备名顺序并不能得到完全保证。即便是在2.6.27中,把生成设备名的操作串行化,并推迟到最后同意处理。但它任然依赖于bios对host的排序。它仅能保证单个host内部的所有lun的设备名是有序的。Sd名字顺序问题,其实还是其名字空间的平面化造成的。Sd设备名不能反映出设备的层次关系,为设备名的生成造成了巨大障碍。虽然我们可以通过第三方软件,如udev来映射固定的设备名。但是对于那些不依赖/dev下设备名,而只查看sys下的设备目录的软件,像udev这样的工具就没有办法了。
阅读(2317) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~