SCSI和相关的命令集
所有SCSI设备都应响应INQUIRY命令,其响应的一部分是所谓的外围设备类型。linux内核使用它来决定哪个上层驱动程序控制设备。还有一些设备属于使用SCSI命令集的其他(即不被视为SCSI)传输,其主要示例是(S-)ATAPI CD和DVD驱动器。并非所有外围设备类型都映射到上层驱动程序,并且这些类型的设备通常通过SCSI generic(sg)驱动程序访问
SG_IO ioctl概述
赋予SG_IO ioctl的第三个参数是指向sg_io_hdr结构实例的指针,该结构在<scsi / sg.h>头文件中定义。SG_IO ioctl的执行可视为分三个阶段:
- 对sg_io_hdr实例中的元数据进行健全性检查; 读取输入字段和其中一些字段指向的数据; 构建SCSI命令并将其发送到设备
- 等待来自设备的响应,超时命令或用户终止调用SG_IO ioctl的进程(或线程)
- 写入输出字段,并在某些情况下将数据写入某些字段指向的位置,然后返回
只有阶段1返回ioctl错误(即返回值-1和在errno中设置的值)。在阶段2中,应谨慎使用命令超时,因为设备(以及同一互连上的其他一些设备)可能最终被重置。如果用户终止调用SG_IO ioctl的进程或线程,那么显然阶段3从未发生但命令执行运行完成(或超时)并且内核“抛弃”结果。如果该命令产生CW状态为CHECK CONDITION(在字段“status”中),则在阶段3中写出感测数据。
现在我们假设SCSI命令涉及将用户数据传输到设备或从设备传输。SCSI子系统不支持向设备进行真正的双向数据传输。所有数据DMA传输(假设硬件支持DMA)都发生在阶段2.但是,如果正在使用间接IO(即既不是直接IO也不是mmap-ed传输),则:
- 数据从阶段1中的用户空间读入内核缓冲区,并在阶段2或者DMA中编辑到设备
- 在阶段2中将数据从设备读入内核缓冲区并在阶段3中写入用户空间
当使用直接IO或mmap-ed传输时,所有用户数据都在阶段2中移动。如果在此类数据传输期间终止进程,则内核会正常处理此问题(通过固定关联的内存页直到传输完成)。
sg_io_hdr结构有22个字段(成员),但通常只需要设置少量字段。以下代码片段显示了一个简单的TEST UNIT READY SCSI命令的设置,它没有相关的数据传输:
unsigned char sense_b [32];
unsigned char turCmbBlk [] = {TUR_CMD,0,0,0,0,0};
struct sg_io_hdr io_hdr;
memset(&io_hdr,0,sizeof(struct sg_io_hdr));
io_hdr.interface_id ='S';
io_hdr.cmd_len = sizeof(turCmbBlk);
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = SG_DXFER_NONE;
io_hdr.cmdp = turCmbBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = DEF_TIMEOUT;
if(ioctl(fd,SG_IO,&io_hdr)<0){
memset()调用非常重要,将未使用的输入字段设置为安全值。将超时字段设置为零不是一个好主意; 对于大多数SCSI命令,30,000(30秒)是合理的默认值。与往常一样,良好的错误处理会消耗更多代码。对于出现问题时产生“感知数据”的SCSI命令尤其如此。例如,如果在磁盘读取期间出现中等错误,则检测数据将包含故障的逻辑块地址(lba)。另一个错误处理示例是设备认为是“非法请求”的SCSI命令,感测数据可以显示它反对的命令块(通常称为“cdb”)中字段的字节和位位置。有关错误处理的示例,请参阅sg3_utils包,其“
下面是一组重要的sg_io_hdr结构字段和简短的摘要:
命令块(以前称为“cdb”):
- cmdp - 指向cdb的指针(SCSI命令块)
- cmd_len - cdb的长度(以字节为单位)
数据传输:
- dxferp - 指向用户数据的指针,用于开始读取或开始写入
- dxfer_len - 要传输的字节数
- dxfer_direction - 是从设备读取(到用户存储器)还是写入设备(从用户存储器)或不传输数据:分别为DXFER_FROM_DEV,DXFER_TO_DEV或DXFER_NONE
- resid - 要传输的请求字节数(即dxfer_len)减去传输的实际数量
错误指示:
- status - 从设备返回的SCSI状态
- host_status - 来自主机总线适配器的错误,包括启动器(端口)
- driver_status - 驱动程序(中级或低级驱动程序)错误和建议掩码
感知数据(仅在'status'为CHECK CONDITION或(driver_status&DRIVER_SENSE)为真时使用):
- sbp - 开始将感知数据写入的指针
- mx_sb_len - 要写入sbp的最大字节数
- sb_len_wr - 写入sbp的实际字节数
sg_io_hdr结构中的字段在SCSI-Generic-HOWTO 文档中有更详细的定义 。
SG_IO ioctl在sg驱动程序中
Linux内核2.4.0是第一个生成内核,SG_IO ioctl出现在SCSI通用(sg)驱动程序中。sg驱动程序本身自1993年以来一直在linux中.sg驱动程序中sg_io_hdr结构的一个实例可以是:
- 由SG_IO ioctl的第三个参数指出
- UNIX write()或read()系统调用的第二个参数指向的,它们将sg设备节点的文件描述符作为其第一个参数
在SCSI-通用-HOWTO 文档描述LK 2.4系列,包括其使用的SG_IO IOCTL的sg驱动程序。在lk 2.4系列之前,sg驱动程序只有sg_header结构。它被用作异步命令接口,其中命令,元数据和可选的用户数据是通过Unix write()系统调用发送的。通过Unix read()系统调用接收包括错误信息(例如,感测数据)或可选的用户数据的相应响应。在lk 2.4系列的开头,对sg驱动程序做了两个主要的补充:
- 新的元数据结构(sg_io_hdr)作为原始混合元数据和数据结构的替代(sg_header)
- 使用新元数据结构并且是同步的SG_IO ioctl:它发送了一个SCSI命令并等待其回复
sg_io_hdr仅包含元数据,因为它包含指向数据来源位置(命令或数据输入)或转到(检测数据或数据输出)的位置的指针。这些指针在混合的32/64位环境中引起了问题,尤其是当用户应用程序(例如cdrecord)构建为32位且内核为64位时。lk 2.6系列有一个兼容层,可以通过专用于SG_IO ioctl的代码来处理这个问题。不幸的是,在设计sg_io_hdr结构时没有预见到这个问题。
sg驱动程序中SG_IO ioctl的一个重要特性是它是用户可中断的。这意味着在发出命令(例如,像磁盘格式的长持续时间命令)和其响应到达之间,用户可以在相关应用程序上命中控制-C。内核将保持稳定,资源将在适当的时间清除。sg驱动程序不会尝试中止这样一个“在飞行中”的命令,它只是抛弃响应并清理。当然,用户没有直接的方法来查明中断的命令是否成功,可能存在间接方式。
这里也可以按顺序发出警告:诸如格式之类的长持续时间命令通常会被赋予长超时值。如果用户中断了发送format命令的应用程序,则设备可能仍然忙于执行格式化(特别是如果未设置IMMED位)。因此,如果用户随后发送了诸如TEST UNIT READY或REQUEST SENSE之类的短持续时间命令来查看设备正在执行的操作,则这些命令可能会超时。这将调用SCSI子系统错误处理程序,该处理程序最有可能发送设备重置,从而中止格式,以引起设备的注意。这可能不是用户想到的!
SG_IO ioctl的差异
在下表中,sg_io_hdr结构字段按它们在该结构中出现的顺序列出。基本上,“in”字段出现在结构的顶部并在阶段1中读取。后面的字段被称为“out”并且由阶段3中的SG_IO实现写入。
sg_io_hdr字段 | 进出 | 类型 | 不同 | 简要描述包括实现之间的差异 |
interface_id | 在 | INT | 守卫场。当前实现仅接受“(int)'S'”。如果未设置,则sg驱动程序将errno设置为ENOSYS,而块层将其设置为EINVAL | |
dxfer_direction | 在 | (-ve)int | 次要 | 数据传输方向。SG_DXFER_NONE和朋友被定义为负整数,因此sg驱动程序可以区分sg_io_hdr实例和sg_header实例。这种细微差别与SG_IO的非sg驱动程序使用无关。见下文。 |
cmd_len | 在 | unsigned char | 将命令长度限制为255个字节。没有SCSI命令(即使OSD中的可变长度命令)这么长(还) | |
max_sb_len | 在 | unsigned char | 驱动程序可以通过sbp指针输出的最大感测数据字节数 | |
iovec_count | 在 | 未签约的短片 | 是 | 如果不是sg驱动程序且大于零则则SG_IO ioctl失败,并且errno设置为EOPNOTSUPP; 当此字段大于零时,sg驱动程序将dxferp视为指向数组struct sg_iovec的指针 |
dxfer_len | 在 | unsigned int | 次要 | 要传输到设备或从设备传输的数据的字节数。与/ sys / block / <device> / queue / max_sectors_kb相关的块设备的上限 |
dxferp | in [*in or *out] | 无效* | 次要 | 指向(用户空间)数据的指针,用于传输(如果从设备读取)或传输(如果写入设备)。当iovec_count大于0时,sg驱动程序中的间接进一步级别。 |
cmdp | 无符号的字符 * | 指向SCSI命令的指针。如果cmdp为NULL,则sg驱动器中的SG_IO ioctl将失败,并且errno设置为EMSGSIZE;如果cmdp为无效,则为EFAULT; 在两种情况下,块层都将errno设置为EFAULT。 | ||
sbp | 进出] | 无符号的字符 * | 指向用户数据区的指针,如果SCSI状态为CHECK CONDITION,则不会写入来自设备的max_sb_len字节的感测数据。 | |
timeout | 在 | unsigned int | 是 (如果= 0) | SCSI中级等待响应的时间(以毫秒为单位)。如果该计时器在命令完成之前到期,则可以中止该命令,可以根据错误处理程序设置重置设备(以及可能在同一互连上的其他设备)。危险的东西,SG_IO ioctl无法控制(通过这个界面)究竟会发生什么。在sg驱动程序中,超时值0表示0毫秒,在块层(当前)中表示60秒。 |
flags | 在 | unsigned int | 是 | 块层SG_IO ioctl忽略该字段; sg驱动程序使用它来请求直接IO或mmap-ed传输等特殊服务。这有点面具。 |
pack_id | in - > out | INT | unused(用于用户空间程序标记) | |
usr_ptr | in - > out | 无效* | unused(用于用户空间指针标记) | |
status | out | 无符号的字符 | SCSI命令状态,零表示良好 | |
masked_status | out | 无符号的字符 | 逻辑上:masked_status ==((状态&0x3e)>> 1)。旧的Linux SCSI子系统使用情况,已弃用。 | |
msg_status | out | 无符号的字符 | SCSI并行接口(SPI)消息状态(非常旧,已弃用) | |
sb_len_wr | 出 | unsigned short | 通过sbp指针输出的感测数据的实际长度(以字节为单位)。 | |
HOST_STATUS | 出 | unsigned short | 启动器(端口)报告的错误。这些是scsi.h中的“DID_ *”错误代码 | |
DRIVER_STATUS | 出 | unsigned short | 位掩码:低级驱动程序(LLD)报告的错误和建议。这些是scsi.h中的“DRIVER_ *”错误代码 | |
resid | 出 | INT | (dxfer_len - number_of_bytes_actually_transferred)。通常仅在设备缩短DMA传输时设置。不一定是错误。较旧的LLD总是产生零。 | |
duration | 出 | unsigned int | 从命令注入SCSI中间级别到调用相应的“完成”回调之间经过的毫秒数。大致是SCSI命令的持续时间(以毫秒为单位)。 | |
info | 出 | unsigned int | 次要 | 位掩码指示已完成(或未完成)以及是否检测到任何错误。如果检测到错误,则块层SG_IO ioctl仅设置SG_INFO_CHECK |
open()考虑因素
打开设备节点时,各种驱动程序具有不同的特征。ioctl系统调用的一个问题是用户只需要读取权限来执行它,但是可以使用像SG_IO这样的ioctl写入设备(例如格式化它)。命令(操作代码)嗅探逻辑用于克服此安全问题。此外,SG_IO ioctl的用户在与sd,st或cdrom驱动程序“共享”设备时需要知道这些驱动程序中的状态机可能被欺骗。这可能是不可避免的,但SG_IO ioctl的用户应该采取适当的谨慎措施。
在标志为零的linux中打开文件意味着O_RDONLY标志,因此只读访问。所有open()系统调用都可以产生ENOENT(没有这样的文件或目录); ENODEV(没有此类设备)如果文件存在但没有连接的设备和EACCES(权限被拒绝),如果用户没有适当的权限。
具有CAP_SYS_RAWIO功能的用户(通常与“root”用户关联)绕过所有命令嗅探和其他访问控制,否则将导致EACCES或EPERM错误。使用sg驱动程序,这样的用户可能仍然需要使用O_RDWR(而不是O_RDONLY)打开()设备节点以使用所有SCSI命令。
open()标志 | sg 说明 | sd 笔记 | st 笔记 | cdrom 笔记 | 评论 |
<无>或 O_RDONLY | 1,2 | 3,4 | 3,5 | 3,6 | 最好添加O_NONBLOCK。对于具有可移动介质(例如磁带驱动器)的设备,取决于是否正在访问驱动器或其介质。 |
O_RDONLY | O_NONBLOCK | 1,7 | 3 | 3,13 | 3 | 建议将SCSI命令识别为从设备读取信息 |
O_RDWR | 2 | 4,8,9 | 5,8,9 | 6,8,9 | 再次,可以更好地添加O_NONBLOCK |
O_RDWR | O_NONBLOCK | 7 | 8,9 | 8,9,13 | 8,9 | 建议在发送任意(包括特定于供应商的)SCSI命令时 |
<< interaction with O_EXCL>> | 10 | 11 | 12 | 11 | 仅在确定没有其他应用程序可能要访问设备(或分区)时使用。令人惊讶的应用程序确实“捅”了一些设备。 |
<< interaction with O_DIRECT>> | - | - > | - | - > | 要求数据传输的扇区对齐(由sg和st忽略) |
备注:
- 在后续的SG_IO ioctl调用中,sg驱动程序将仅允许其allow_ops数组中的SCSI命令,其他则导致errno中的EPERM(不允许操作)。见下文 。
- 如果此sg设备节点的先前open()仍保留O_EXCL,则此open()等待直到它清除。
- 在后续的SG_IO ioctl调用中,块层将仅允许在drivers / block / scsi_ioctl.c文件的verify_command()函数中列为“safe_for_read”的SCSI命令; 其他导致errno中的EPERM(不允许操作)。见下文。
- 如果可移动介质并且不存在则产生ENOMEDIUM(未找到介质)
- 如果磁带不在驱动器中,则产生EIO(输入/输出错误),如果磁带“正在使用”,则产生EBUSY(资源忙)。每个st设备节点一次只允许一个打开的文件描述符(尽管可以使用dup())。
- 如果托盘关闭且介质不存在则产生ENOMEDIUM(未找到介质); 如果托盘打开然后尝试关闭它,如果没有介质存在则产生ENOMEDIUM
- 如果此sg设备节点的先前open()仍保留O_EXCL,则产生EBUSY(资源忙)。
- 在后续的SG_IO ioctl调用中,块层将允许列为“safe_for_read”或“safe_for_write”的SCSI命令。对于其他SCSI命令,用户需要CAP_SYS_RAWIO功能(通常与“root”用户关联); 如果没有收益EPERM(不允许操作)。自启动以来的其他SCSI命令的第一个实例,向日志发送恼人的“scsi:unknown opcode”消息。
- 如果媒体或驱动器被标记为不可写,则产生EROFS(只读文件系统)。
- 如果sg设备节点已经有独占锁定,那么后续的打开尝试(O_EXCL)将等待,除非给出O_NONBLOCK,在这种情况下它会产生EBUSY(资源忙)
- 在块设备级别(它知道设备内的分区)实现。如果先前打开(O_EXCL)处于活动状态,则后续打开(O_EXCL)将产生EBUSY(资源繁忙)。挂载的文件系统通常使用O_EXCL打开设备/分区; 只要使用SG_IO ioctl的应用程序也不尝试使用O_EXCL标志,那么它将被允许访问该设备。
- st驱动程序不支持(即忽略)O_EXCL标志。但是,它只允许每个磁带设备有一个活动的open()这一事实具有类似的功能。
- 如果磁带“正在使用”,则产生EBUSY(资源忙)。每个st设备节点一次只允许一个打开的文件描述符。
O_EXCL标志在sg驱动程序和块层中具有不同的效果。在sg驱动程序中,一旦O_EXCL保留在设备上,所有后续的open()尝试都将等待或产生EBUSY(无论它们是否尝试使用O_EXCL标志)。一旦在块层中成功打开分区/设备(使用sd或cdrom驱动程序),仅拒绝后续使用O_EXCL标志的open()尝试(使用EBUSY)。块层中的设备上保持的O_EXCL锁定对通过sg驱动程序访问同一设备没有影响(反之亦然)。
在具有可移动介质的sd或cdrom设备节点上首次成功打开将向设备发送PREVENT ALLOW MEDIUM REMOVAL(阻止)SCSI命令。如果成功,这将禁止随后的START STOP UNIT(弹出)SCSI命令并取消激活驱动器上的弹出按钮。在紧急情况下,可以使用SG_IO ioctl来阻止此操作,例如sdparm 实用程序,特别是“sdparm --command = unlock”。
open()标志O_NDELAY具有与O_NONBLOCK相同的值和含义。其他标志(如O_DIRECT,O_TRUNC和O_APPEND)对SG_IO ioctl没有影响。
SCSI命令权限
在linux中,用户只需要对文件描述符具有读取权限即可执行ioctl()系统命令。在SG_IO ioctl的情况下,可以发送显然改变设备状态的SCSI命令(例如,写入磁盘)。因此,SG_IO ioctl的两个实现都需要多于某些命令的读取权限,特别是那些已知可以更改设备状态或具有某些未知操作的命令(例如供应商特定命令)。
这是一个SCSI命令表,不需要用户具有写权限(或者在某些情况下,CAP_SYS_RAWIO功能通常等同于“root”用户):
SCSI命令 | (草案)标准 | sg驱动程序需要 | 块层SG_IO 需要(除了st) | 评论 |
BLANK(空白) | MMC-4 | O_RDWR | O_RDWR | |
CLOSE TRACK/SESSION (关闭跟踪/会话) | MMC-4 | O_RDWR | O_RDWR | |
ERASE(擦除) | MMC-4 | O_RDWR | O_RDWR | |
FLUSH CACHE(检查FLUSH) | SBC-3,MMC-4 | O_RDWR | O_RDWR | 真的是SYNCHRONIZE CACHE命令 |
FORMAT UNIT (格式单位) | SBC-3,MMC-4 | O_RDWR | O_RDWR | 默认命令超时可能不够长 |
GET CONFIGURATION (获取配置) | MMC-4 | O_RDWR | O_RDONLY | 读取CD / DVD元数据 |
GET EVENT STATUS NOTIFICATION (获取事件状态通知) | MMC-4 | O_RDWR | O_RDONLY | |
GET PERFORMANCE (获得效能) | MMC-4 | O_RDWR | O_RDONLY | |
INQUIRY (查询) | SPC-4 | O_RDONLY | O_RDONLY | 所有SCSI设备都应响应此命令 |
LOAD UNLOAD MEDIUM (加载卸载介质) | MMC-4 | O_RDWR | O_RDWR | MEDIUM可能被CD,DVD或其他任何东西取代 |
LOG SELECT (日志选择) | SPC-4 | O_RDWR | O_RDWR | 用于更改日志记录或清除记录的数据 |
LOG SENSE (LOG 检测) | SPC-4 | O_RDONLY | O_RDONLY | 用于获取记录的数据 |
MAINTENANCE COMMAND IN (维护指挥) | SPC-4 | O_RDONLY | CAP_SYS_RAWIO | 各种“REPORT ...”命令,例如REPORT SUPPORTED OPERATION CODES |
MODE SELECT (6+10) 模式选择(6 + 10) | SPC-4 | O_RDWR | O_RDWR | 用于更改SCSI设备元数据 |
MODE SENSE (6+10) 模式感应(6 + 10) | SPC-4 | O_RDONLY | O_RDONLY | 用于读取SCSI设备元数据 |
PAUSE RESUME 暂停恢复 | MMC-4 | O_RDWR | O_RDONLY | |
PLAY AUDIO (10) 播放音频(10) | MMC-4 | O_RDWR | O_RDONLY | |
PLAY AUDIO MSF 播放音频 MSF | MMC-4 | O_RDWR | O_RDONLY | |
PLAY AUDIO TI 播放音频TI | ?? | O_RDWR | O_RDONLY | 操作码0x48,未分配给SPC-4中的任何规范 |
PLAY CD 播放CD | MMC-2 | O_RDWR | O_RDONLY | 旧的,现在备用SPC-4 |
PREVENT ALLOW MEDIUM REMOVAL 防止允许中度去除 | SPC-4,MMC-4 | O_RDWR | O_RDWR | sd,st和cdrom驱动程序在内部使用它 |
READ (6+10+12+16) 读(6 + 10 + 12 + 16) | SBC-3 | O_RDONLY | O_RDONLY | READ(16)在lk2.6.11之前需要带有sg驱动程序的O_RDWR |
READ BUFFER 读BUFFER | SPC-4 | O_RDONLY | O_RDONLY | |
READ BUFFER CAPACITY 读缓冲容量 | MMC-4 | O_RDWR | O_RDONLY | |
READ CAPACITY(10) 读能力(10) | SBC-3,MMC-4 | O_RDONLY | O_RDONLY | |
READ CAPACITY(16) 读能力(16) | SBC-3, MMC-4 | O_RDONLY | CAP_SYS_RAWIO | 在SERVICE ACTION IN命令中。需要大于2 TB的RAID |
READ CD 读CD | MMC-4 | O_RDWR | O_RDONLY | |
READ CD MSF 读CD MSF | MMC-4 | O_RDWR | O_RDONLY | |
READ CDVD CAPACITY 读CDVD容量 | SBC-3,MMC-4 | O_RDONLY | O_RDONLY | 来自cdrom.h的奇怪(旧?)名称。实际上是READ CAPACITY。 |
READ DEFECT (10) 读缺陷(10) | SBC-3 | O_RDWR | O_RDONLY | |
READ DISC INFO 读DISC信息 | MMC-4 | O_RDWR | O_RDONLY | |
READ DVD STRUCTURE 读DVD结构 | MMC-4 | O_RDWR | O_RDONLY | |
READ FORMAT CAPACITIES 读格式容量 | MMC-4 | O_RDWR | O_RDONLY | |
READ HEADER 读标题 | MMC-2 | O_RDWR | O_RDONLY | |
READ LONG (10) | SBC-3 | O_RDONLY | O_RDONLY | 但不读长(16) |
READ SUB-CHANNEL 读子通道 | MMC-4 | O_RDWR | O_RDONLY | |
READ TOC/PMA/ATIP 读TOC / PMA / ATIP | MMC-4 | O_RDWR | O_RDONLY | |
READ TRACK(RZONE)INFO | MMC-4 | O_RDWR | O_RDONLY | 在MMC-4中称为READ TRACK INFO |
RECEIVE DIAGNOSTIC 接受诊断 | SPC-4 | O_RDONLY | CAP_SYS_RAWIO | SES命令集大量使用此命令。只能通过sg设备节点访问SES设备 |
REPAIR (RZONE) TRACK 修理(RZONE)轨道 | MMC-4 | O_RDWR | O_RDWR | |
REPORT KEY 报告键 | MMC-4 | O_RDWR | O_RDONLY | |
REPORT LUNS 报告LUN | SPC-4 | O_RDONLY | CAP_SYS_RAWIO | 自SPC-3起必须遵守 |
REQUEST SENSE 请求扫描 | SPC-4 | O_RDONLY | O_RDONLY | 除了那些因自动感应而流离失所的人以外的用途 |
RESERVE(RZONE)TRACK | MMC-4 | O_RDWR | O_RDWR | |
SCAN | MMC-4 | O_RDWR | O_RDONLY | |
SEEK | MMC-4 | O_RDWR | O_RDONLY | |
SEND CUE SHEET | MMC-4 | O_RDWR | O_RDWR | |
SEND DVD STRUCTURE | MMC-4 | O_RDWR | O_RDWR | |
[SEND EVENT] | MMC-2 | O_RDWR | cdrom.h关联操作码0xa2但MMC-2使用操作码0x5d ?? | |
SEND KEY | MMC-4 | O_RDWR | O_RDWR | |
SEND OPC INFORMATION | MMC-4 | O_RDWR | O_RDWR | |
SERVICE ACTION IN | SPC-4,SBC-3 | O_RDONLY | CAP_SYS_RAWIO | 在此处阅读容量(16)服务操作 |
SET CD SPEED | MMC-4 | O_RDWR | O_RDWR | cdrom.h调用此SET SPEED |
SET STREAMING | MMC-4 | O_RDWR | O_RDWR | |
START STOP UNIT | SBC-3,MMC-4 | O_RDWR | O_RDONLY | 嗯 |
STOP PLAY/SCAN | MMC-4 | O_RDWR | O_RDONLY | |
SYNCHRONIZE CACHE | SBC-3,MMC-4 | O_RDWR | O_RDWR | cdrom.h调用此FLUSH CACHE |
TEST UNIT READY | SPC-4 | O_RDONLY | O_RDONLY | 所有SCSI设备都应响应此命令 |
VERIFY (10+16) | SBC-3,MMC-4 | O_RDWR | O_RDONLY | |
WRITE (6+10+12+16) 写(6 + 10 + 12 + 16) | SBC-3 | O_RDWR | O_RDWR | |
WRITE LONG (10+16) | SBC-3 | O_RDWR | O_RDWR | |
WRITE VERIFY (10+16) | SBC-3,MMC-4 | O_RDWR | O_RDWR | 只有WRITE VERIFY(10)在MMC-4中 |
没有为sg驱动程序提及的任何其他SCSI命令(操作码)需要O_RDWR。对于块层SG_IO ioctl未提及的任何其他SCSI命令(操作码)需要具有CAP_SYS_RAWIO能力的用户。在st设备节点上的所有“块”SG_IO ioctl调用都需要具有CAP_SYS_RAWIO能力的用户。如果用户没有足够的权限通过SG_IO ioctl执行SCSI命令,则系统调用失败(即没有发送SCSI命令),并且errno设置为EPERM(不允许操作)。
sg驱动程序和块层SG_IO代码都使用内部表来强制执行上表中显示的权限(allow_ops和cmd_type分别为[safe_for_read和safe_for_write])。该技术不能很好地扩展,因为更高级的命令集(例如OSD)使用服务动作(和一个操作码:在OSD的情况下为0x7f)。在命令集之间的操作码使用中也可能存在重叠,例如在SBC,MMC和SSC之间。
来自用户进程的CAP_SYS_RAWIO
虽然根进程通常具有CAP_SYS_RAWIO,但在用户ID(即非root)下运行的进程通常不会。因此,非root进程可能无法使用SG_IO发送需要CAP_SYS_RAWIO的SCSI命令。即使设备节点文件的权限位允许读取或写入访问,也可能发生这种情况,用户进程在使用SG_IO时将收到EPERM。
默认情况下,将功能分配给其他进程(CAP_SETPCAP)的功能仅限于极少数进程,例如某些内核线程。更改此默认值需要更改并重新编译内核。
由根进程分叉并稍后调用setuid的进程将失去父根进程(以及setuid之前的子进程)具有的CAP_SYS_RAWIO功能。但是,子进程可以在允许的集合中保留根进程的功能,并
在fork()之后调用setuid:/ * ...之后将其提升,仍然以root身份运行... * /
prctl(PR_SET_KEEPCAPS, 1,0,0,0);
setuid的(...);
cap_set_proc(cap_from_text( “CAP_SYS_RAWIO + EP”));
这样,具有父根进程的用户进程可以“返回”所需的功能,以通过SG_IO直接将SCSI命令发送到设备。
上述技术可能用于以root权限(大多数是)启动的守护进程,然后在fork()之后更改为另一个用户。对于作者而言,在某些或所有SCSI命令(例如与sd和st驱动程序相关联的节点)上需要CAP_SYS_RAWIO的设备节点上使用SG_IO ioctl的实用程序如何使用上述技术并不明显。
SG_IO和st驱动程序
为了实现其用户空间API,st驱动程序必须维护有关读磁头相对于磁带结构元素的位置的信息(文件标记,磁带的开头,数据的结尾)。由于流设备SCSI命令没有地址,因此st驱动程序必须知道已发送了哪些命令。在读取时,当读取失败并且提取感测数据时,会注意到文件标记。如果SG_IO与磁带命令混合,则st驱动程序可能会丢失信息(它不会查看SG_IO命令和结果)。因此,st驱动程序可能无法实现用户期望的语义。如果用户接受此信息或知道何时使用SG_IO不会导致信息丢失,那么使用SG_IO就可以了。
因此,不建议将st驱动程序读取,写入和ioctl命令与通过SG_IO发送的SCSI命令混合,以改变磁带的状态。无论是通过st或sg设备节点发送SG_IO SCSI命令,这都适用。
每个命令的最大传输大小
单个SCSI命令可以传输的最大数据量通常是一个问题。各种SCSI命令集(例如,用于磁盘READ和WRITE的SBC-3,用于磁带读取和写入的SSC-3以及用于READ + WRITE BUFFER的SPC-4)允许非常大的数据传输大小,但Linux并不是那么容易。主机总线适配器(HBA)可能具有传输大小限制,传输和SCSI设备本身也是如此。在后一种情况下,SBC-3定义了“块限制”重要产品数据(VPD),而SSC具有READ BLOCK LIMITS SCSI命令。SBC-3的可选块限制VPD页面包含最大和最佳计数。在作者看来,后一种区别非常重要:块susbsystem应该尝试使用最佳大小,而通过用户时应该只限制最大大小。此外,如果传递用户超过SCSI设备强加的最大传输大小,则设备可以报告错误。有一个潜在的假设,即使用传递接口的应用程序知道它们正在做什么,或者至少比各种内核系统知道更多。另一方面,内核有责任分配关键的共享资源,如内存。
在过去,Linux使用单个“足够大”的内存块作为大数据传输的源或目标。然后,分散 - 收集列表添加到中断的位置转换为较小的(通常为“页面”大小(i386体系结构上为4 KB))块,这使得内核更容易进行内存管理。现在,在lk 2.6系列中,单块内存选项正在逐步淘汰。
Linux SCSI子系统通过其SCSI_MAX_PHYS_SEGMENTS定义对分散收集列表施加128个元素限制。linux SCSI子系统分配各种内存池的方式,SCSI_MAX_PHYS_SEGMENTS可以增加到256.与每种类型的HBA相关联,通常有一个低级驱动程序(LLD)。每个LLD都可以使用scsi_host_template :: sg_tablesize字段进一步限制元素的最大数量。在lk 2.6.16之前,sg和st驱动程序仅使用.sg_tablesize字段,因为lk 2.6.16这些驱动程序也受SCSI_MAX_PHYS_SEGMENTS约束。这导致最大转移尺寸可能减半。许多LLD将.sg_tablesize字段设置为SG_ALL(为255),但除非HBA硬件具有约束,否则它们也可以将该字段设置为256。
可以将用户空间存储器分配为来自HBA的DMA传输的源和/或目的地(即,直接IO)。即使用户空间使用单个malloc()分配了大量内存,HBA DMA元素通常也具有不同的内存视图。该视图可能包含许多“页面”大小不连续的部分。这具有消耗或可能耗尽散射 - 聚集元素的效果。
sg驱动程序尝试使用每个元素构建分散集合列表,其中SG_SCATTER_SZ字节大。这个定义可以在include / scsi / sg.h中找到,并且已经设置为32 KB多年。这是i386架构上页面大小(4 KB)的8倍。一些需要非常大的传输的用户会增加这个定义(并且最好保持2的幂)。但是,由于lk 2.6.16的另一个限制发挥作用:MAX_SEGMENT_SIZE定义设置为64 KB。MAX_SEGMENT_SIZE是默认值,可以由LLD调用blk_queue_max_segment_size()覆盖。
在lk 2.6.16中,即使使用sg(和st)驱动程序,另外两个LLD参数也会起作用。这些是scsi_host_template :: max_sectors和scsi_host_template :: use_clustering。
LLD中的.max_sectors设置是单个SCSI命令的分散收集列表(用于数据传输)中允许的最大512字节扇区数。是的,当尝试发送SCSI WRITE BUFFER命令来上传固件时,这是一个奇怪的限制。Sysfs使得LLD的.max_sectors设置在/ sys / block / sd <x> / queue / max_hw_sectors_kb中可见(转换为千字节)。LLD的.max_sector中的最大允许值似乎是65535(十六进制为0xffff)。假设已经克服了其他限制,这将最大传输大小限制为(32 * 1024 * 1024 - 512)字节。[65535扇区限制是因为Scsi_Host :: max_sectors的类型为“unsigned short”。希望将来这种类型扩展为“int”(或删除)。]
.use_clustering字段应设置为ENABLE_CLUSTERING。如果不是,则块子系统重建它从具有页面大小(例如4KB)元素的sg驱动程序获得的分散收集列表。[实际上是这样做,但是当设置了ENABLE_CLUSTERING时,它会再次合并它们!]
结论
在某些情况下,通过SG_IO ioctl发送命令可能会干扰更高级别驱动程序对设备的使用。SG_IO ioctl的用户应该知道他们正在使用强大但低级别的工具,并相应地编写代码。这方面的一个例子是在磁盘上执行自检的实用程序:如果计算机有可能当时在该磁盘上使用文件系统,则“背景”自检应优先于“前台”自检。即使是短前景自检也可能需要长达两分钟才能锁定文件系统。