💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 16.4. 一些其他的细节 本节涵盖块层的几个其他的方面, 对于高级读者可能有兴趣. 对于编写一个正确的驱动下面的内容都不需要, 但是它们在某些情况下可能是有用的. ### 16.4.1. 命令预准备 块层为驱动提供一个进制来检查和预处理请求, 在它们被从 elv_next_request 返回前. 这个机制允许驱动提前设立真正的驱动器命令, 决定是否这个请求可被完全处理, 或者进行其他的维护工作. 如果你想使用这个特性, 创建一个命令准备函数, 它要适应这个原型: ~~~ typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req); ~~~ 请求结构包含一个成员 cmd, 它是一个 BLK_MAX_CDB 字节的数组; 这个数组可被这个准备函数用来存储实际的硬件命令(或者任何其他的有用信息). 这个函数应当返回一个下列的值: BLKPREP_OK 命令准备正常进行, 并且这个请求可被传递给你的驱动的请求函数. BLKPREP_KILL 这个请求不能完成; 它带有一个错误码而失败. BLKPREP_DEFER 这个请求这次无法完成. 它位于队列的前面, 但是不能传递给请求函数. 准备函数被 elv_next_request 在请求返回到你的驱动之前立刻调用. 如果这个函数返回 BLKPREP_DEFER, 从 elv_next_request 返回给你的驱动的返回值是 NULL. 这个操作描述可能是有用的, 如果, 例如你的设备已达到它能够等候的请求的最大数目. 为使块层调用你的准备函数, 传递它到: ~~~ void blk_queue_prep_rq(request_queue_t *queue, prep_rq_fn *func); ~~~ 缺省地, 请求队列没有准备函数. ### 16.4.2. 被标识的命令排队 可同时有多个请求被激活的硬件, 常常支持某种被标识的命令排队(TCQ). TCQ 简单地说是关联一个整数 "tag" 到每个请求的技术, 注意当驱动器完成每个请求时, 他可告知驱动是哪一个. 在以前的内核版本, 实现 TCQ 的块驱动不得不自己做所有的工作; 在2.6, 一个 TCQ 支持框架已经被添加到块层, 以给所有的驱动来使用. 如果你的驱动器进行标记命令排队, 你应当在初始化时通知内核这个事实, 使用: ~~~ int blk_queue_init_tags(request_queue_t *queue, int depth, struct blk_queue_tag *tags); ~~~ 这里, queue 是你的请求队列, 而 depth 是你的设备能够在任何时间拥有的等待的标记请求的数目. tags 是一个可选的指针指向一个 struct blk_queue_tag 结构数组; 必须有 depth 个. 正常地, tags 可用 NULL, 并且 blk_queue_init_tags 分配这个 数组. 如果, 但是, 你需要和多个设备分享通用的 tags, 你可传递这个标记数组指针(存储在 queue_tags 成员)从另一个请求队列. 你应当从不真正自己分配这个标记数组; 块层需要初始化这个数组并且不输出这个初始化函数给模块. 因为 blk_queue_init_tags 分配内存, 它可能失败. 在那个情况下它返回一个负的错误码给调用者. 如果你的设备可处理的标记的数目改变了, 你可通知内核, 使用: ~~~ int blk_queue_resize_tags(request_queue_t *queue, int new_depth); ~~~ 这个队列锁必须在这个调用期间被持有. 这个调用可能失败, 返回一个负错误码. 一个标记和一个请求结构的关联被 blk_queue_start_tag 来完成, 它必须在成员队列锁被持有时调用: ~~~ int blk_queue_start_tag(request_queue_t *queue, struct request *req); ~~~ 如果一个 tag 可用, 这个函数分配它给这个请求, 存储这个标识号在 req->tag, 并且返回 0. 它还从队列中解除这个请求, 并且连接它到它自己的标识跟踪结构, 因此你的驱动应当小心不从队列中解除这个请求, 如果在使用标识. 如果没有标识可用, blk_queue_start_tag 将这个请求留在队列并且返回一个非零值. 当一个给定的请求的所有的传送都已完成, 你的驱动应当返回标识, 使用: ~~~ void blk_queue_end_tag(request_queue_t *queue, struct request *req); ~~~ 再一次, 你必须持有队列锁, 在调用这个函数之前. 这个调用应当在 end_that_request_first 返回 0 之后进行(意味着这个请求完成), 但要在调用 end_that_request_last 之前. 记住这个请求已经从队列中解除, 因此它对于你的驱动在此点这样做可能是一个错误. 如果你需要找到关联到一个给定标识上的请求(当驱动器报告完成, 例如), 使用 blk_queue_find_tag: ~~~ struct request *blk_queue_find_tag(request_queue_t *qeue, int tag); ~~~ 返回值是关联的请求结构, 除非有些事情已经真的出错了. 如果事情真地出错了, 你的请求可能发现它自己不得不复位或者对其中一个它的设备进行一些其他的大动作. 在这种情况下, 任何等待中的标识命令将不会完成. 块层提供一个函数可用帮助在这种情况下恢复: ~~~ void blk_queue_invalidate_tags(request_queue_t *queue); ~~~ 这个函数返回所有的等待的标识给这个池, 并且将关联的请求放回请求队列. 你调用这个函数时必须持有队列锁.