SCSI系列三:linux SCSI 子系统五十-sg(2)
SG(SCSI Generic)驱动是Linux内核中用于与SCSI设备通信的驱动程序。它允许用户空间应用程序通过文件系统接口来进行与SCSI设备的通信,支持传统的ioctl调用,同时还提供了异步的read()和write()接口。SG驱动提供了一种通用的接口,允许用户空间应用程序与SCSI设备进行通信。它通过一系列的数据结构和函数,实现了SCSI命令的传输、异步I/O的控制以及请求的管理等功能,
common define
#ifdef CONFIG_SCSI_PROC_FS
#include <linux/proc_fs.h>
static char *sg_version_date = "20140603";
static int sg_proc_init(void);
#endif
#define SG_ALLOW_DIO_DEF 0
#define SG_MAX_DEVS 32768
/* SG_MAX_CDB_SIZE should be 260 (spc4r37 section 3.1.30) however the type
* of sg_io_hdr::cmd_len can only represent 255. All SCSI commands greater
* than 16 bytes are "variable length" whose length is a multiple of 4
*/
#define SG_MAX_CDB_SIZE 252
#define SG_DEFAULT_TIMEOUT mult_frac(SG_DEFAULT_TIMEOUT_USER, HZ, USER_HZ)
static int sg_big_buff = SG_DEF_RESERVED_SIZE;
/* N.B. This variable is readable and writeable via
/proc/scsi/sg/def_reserved_size . Each time sg_open() is called a buffer
of this size (or less if there is not enough memory) will be reserved
for use by this file descriptor. [Deprecated usage: this variable is also
readable via /proc/sys/kernel/sg-big-buff if the sg driver is built into
the kernel (i.e. it is not a module).] */
static int def_reserved_size = -1; /* picks up init parameter */
static int sg_allow_dio = SG_ALLOW_DIO_DEF;
static int scatter_elem_sz = SG_SCATTER_SZ;
static int scatter_elem_sz_prev = SG_SCATTER_SZ;
#define SG_SECTOR_SZ 512
这是一些 sg.c 文件的片段,其中包含了一些宏定义和变量初始化。以下是每个片段的简要说明:
-
#ifdef CONFIG_SCSI_PROC_FS:这是一个条件编译指令,用于检查是否启用了 SCSI proc 文件系统支持。如果启用了,将包含linux/proc_fs.h头文件,并定义一个名为sg_version_date的字符串变量。 -
#define SG_ALLOW_DIO_DEF 0:定义了一个宏,用于设置默认的SG_ALLOW_DIO的值,这在后面的代码中可能会使用。 -
#define SG_MAX_DEVS 32768:定义了一个宏,表示最大的设备数量。 -
#define SG_MAX_CDB_SIZE 252:定义了一个宏,表示 SCSI 命令的最大长度(以字节为单位)。 -
#define SG_DEFAULT_TIMEOUT mult_frac(SG_DEFAULT_TIMEOUT_USER, HZ, USER_HZ):定义了一个宏,用于设置默认的超时时间。 -
static int sg_big_buff = SG_DEF_RESERVED_SIZE;:定义了一个静态整数变量sg_big_buff,并将其初始化为默认的保留缓冲区大小。 -
static int def_reserved_size = -1;:定义了一个静态整数变量def_reserved_size,并将其初始化为 -1。 -
static int sg_allow_dio = SG_ALLOW_DIO_DEF;:定义了一个静态整数变量sg_allow_dio,并将其初始化为默认的SG_ALLOW_DIO值。 -
static int scatter_elem_sz = SG_SCATTER_SZ;:定义了一个静态整数变量scatter_elem_sz,并将其初始化为默认的 scatter-gather 元素大小。 -
static int scatter_elem_sz_prev = SG_SCATTER_SZ;:定义了一个静态整数变量scatter_elem_sz_prev,并将其初始化为默认的 scatter-gather 元素大小。 -
#define SG_SECTOR_SZ 512:定义了一个宏,表示扇区的大小。
sg_interface
static int sg_add_device(struct device *);
static void sg_remove_device(struct device *);
static DEFINE_IDR(sg_index_idr);
static DEFINE_RWLOCK(sg_index_lock); /* Also used to lock
file descriptor list for device */
static struct class_interface sg_interface = {
.add_dev = sg_add_device,
.remove_dev = sg_remove_device,
};
这段代码是针对 sg 驱动的一些设备管理和接口定义的片段,以下是对其中的各部分的简要解释:
-
static int sg_add_device(struct device *);:这是一个函数声明,用于添加设备到sg驱动中。 -
static void sg_remove_device(struct device *);:这是一个函数声明,用于从sg驱动中移除设备。 -
static DEFINE_IDR(sg_index_idr);:这是一个宏,用于定义一个 IDR(ID Register),用于管理设备的索引。 -
static DEFINE_RWLOCK(sg_index_lock);:这是一个宏,用于定义一个读写自旋锁,用于保护设备的索引和文件描述符列表。 -
static struct class_interface sg_interface = { ... };:这是一个结构体定义,用于定义一个sg驱动的类接口。其中包含了两个函数指针,分别是在添加设备和移除设备时要调用的函数。
Sg_scatter_hold
typedef struct sg_scatter_hold { /* holding area for scsi scatter gather info */
unsigned short k_use_sg; /* Count of kernel scatter-gather pieces */
unsigned sglist_len; /* size of malloc'd scatter-gather list ++ */
unsigned bufflen; /* Size of (aggregate) data buffer */
struct page **pages;
int page_order;
char dio_in_use; /* 0->indirect IO (or mmap), 1->dio */
unsigned char cmd_opcode; /* first byte of command */
} Sg_scatter_hold;
这段代码定义了一个结构体 Sg_scatter_hold,用于在 SCSI 命令执行过程中保存散列-收集(scatter-gather)信息的临时缓冲区。以下是各字段的简要解释:
-
unsigned short k_use_sg:用于计算在内核中的散列-收集分段的数量。 -
unsigned sglist_len:malloc 分配的散列-收集列表的大小。 -
unsigned bufflen:数据缓冲区的总大小(聚合的数据大小)。 -
struct page **pages:指向内存页面数组的指针,这些页面用于存储数据块。 -
int page_order:内存页面的阶数,用于计算分配的页面大小。 -
char dio_in_use:指示是否正在使用直接 I/O(Direct I/O)。 -
unsigned char cmd_opcode:命令的第一个字节,用于确定执行的 SCSI 命令。
这个结构体主要用于在进行 SCSI 命令执行时管理数据传输、内存页面和相关信息。它提供了一种在执行期间存储和跟踪数据传输细节的方式。
Sg_request
struct sg_device; /* forward declarations */
struct sg_fd;
typedef struct sg_request { /* SG_MAX_QUEUE requests outstanding per file */
struct list_head entry; /* list entry */
struct sg_fd *parentfp; /* NULL -> not in use */
Sg_scatter_hold data; /* hold buffer, perhaps scatter list */
sg_io_hdr_t header; /* scsi command+info, see <scsi/sg.h> */
unsigned char sense_b[SCSI_SENSE_BUFFERSIZE];
char res_used; /* 1 -> using reserve buffer, 0 -> not ... */
char orphan; /* 1 -> drop on sight, 0 -> normal */
char sg_io_owned; /* 1 -> packet belongs to SG_IO */
/* done protected by rq_list_lock */
char done; /* 0->before bh, 1->before read, 2->read */
struct request *rq;
struct bio *bio;
struct execute_work ew;
} Sg_request;
这段代码定义了一个名为 Sg_request 的结构体,用于管理每个文件的 SCSI 请求(SG 请求)。以下是各字段的简要解释:
-
struct list_head entry:链表条目,用于将请求链接到请求队列中。 -
struct sg_fd *parentfp:指向相关联文件描述符的指针。 -
Sg_scatter_hold data:用于保持缓冲区和散列-收集信息的结构体,这将在请求执行期间用于数据传输。 -
sg_io_hdr_t header:SG 请求中的 SCSI 命令和信息,定义在<scsi/sg.h>中。 -
unsigned char sense_b[SCSI_SENSE_BUFFERSIZE]:用于存储 SCSI 设备感知缓冲区的数组。 -
char res_used:表示是否使用了保留缓冲区(Reserve Buffer)。 -
char orphan:标志,指示是否丢弃此请求。 -
char sg_io_owned:标志,表示该请求是否属于 SG_IO。 -
char done:表示请求的状态,0 表示在提交请求之前,1 表示在提交读取请求之前,2 表示读取完成。 -
struct request *rq:与请求相关联的 Linux 内核请求结构。 -
struct bio *bio:与请求相关联的 Linux 内核生物(BIO)结构,用于块设备 I/O。 -
struct execute_work ew:用于处理请求执行的工作。
这个结构体用于在 SCSI 请求的整个生命周期中跟踪请求状态、数据传输和相关信息。
Sg_fd
typedef struct sg_fd { /* holds the state of a file descriptor */
struct list_head sfd_siblings; /* protected by device's sfd_lock */
struct sg_device *parentdp; /* owning device */
wait_queue_head_t read_wait; /* queue read until command done */
rwlock_t rq_list_lock; /* protect access to list in req_arr */
struct mutex f_mutex; /* protect against changes in this fd */
int timeout; /* defaults to SG_DEFAULT_TIMEOUT */
int timeout_user; /* defaults to SG_DEFAULT_TIMEOUT_USER */
Sg_scatter_hold reserve; /* buffer held for this file descriptor */
struct list_head rq_list; /* head of request list */
struct fasync_struct *async_qp; /* used by asynchronous notification */
Sg_request req_arr[SG_MAX_QUEUE]; /* used as singly-linked list */
char force_packid; /* 1 -> pack_id input to read(), 0 -> ignored */
char cmd_q; /* 1 -> allow command queuing, 0 -> don't */
unsigned char next_cmd_len; /* 0: automatic, >0: use on next write() */
char keep_orphan; /* 0 -> drop orphan (def), 1 -> keep for read() */
char mmap_called; /* 0 -> mmap() never called on this fd */
char res_in_use; /* 1 -> 'reserve' array in use */
struct kref f_ref;
struct execute_work ew;
} Sg_fd;
这段代码定义了一个名为 Sg_fd 的结构体,用于保存文件描述符的状态。以下是各字段的简要解释:
-
struct list_head sfd_siblings:链表条目,用于将文件描述符链接到同一设备的文件描述符列表中,受设备的sfd_lock保护。 -
struct sg_device *parentdp:指向拥有该文件描述符的设备的指针。 -
wait_queue_head_t read_wait:等待队列头,用于将读取操作推迟直到命令执行完毕。 -
rwlock_t rq_list_lock:读写锁,保护对rq_list请求列表的访问。 -
struct mutex f_mutex:互斥锁,保护对该文件描述符的更改。 -
int timeout:超时时间,单位为 jiffies,默认为SG_DEFAULT_TIMEOUT。 -
int timeout_user:用户级超时时间,单位为 jiffies,默认为SG_DEFAULT_TIMEOUT_USER。 -
Sg_scatter_hold reserve:为该文件描述符保留的缓冲区。 -
struct list_head rq_list:请求列表的头,用于跟踪提交给设备的请求。 -
struct fasync_struct *async_qp:用于异步通知的 fasync 结构。 -
Sg_request req_arr[SG_MAX_QUEUE]:SG 请求的数组,最多支持SG_MAX_QUEUE个请求。 -
char force_packid:标志,表示是否将pack_id输入到read()函数。 -
char cmd_q:标志,表示是否允许命令排队。 -
unsigned char next_cmd_len:表示下一次写入操作时要使用的命令长度。 -
char keep_orphan:标志,表示是否保留孤立(orphan)请求以供读取。 -
char mmap_called:标志,表示是否在该文件描述符上调用了mmap()。 -
char res_in_use:标志,表示是否正在使用保留(reserve)数组。 -
struct kref f_ref:内核引用计数结构,用于管理该文件描述符的引用计数。 -
struct execute_work ew:用于处理文件描述符
Sg_device
typedef struct sg_device { /* holds the state of each scsi generic device */
struct scsi_device *device;
wait_queue_head_t open_wait; /* queue open() when O_EXCL present */
struct mutex open_rel_lock; /* held when in open() or release() */
int sg_tablesize; /* adapter's max scatter-gather table size */
u32 index; /* device index number */
struct list_head sfds;
rwlock_t sfd_lock; /* protect access to sfd list */
atomic_t detaching; /* 0->device usable, 1->device detaching */
bool exclude; /* 1->open(O_EXCL) succeeded and is active */
int open_cnt; /* count of opens (perhaps < num(sfds) ) */
char sgdebug; /* 0->off, 1->sense, 9->dump dev, 10-> all devs */
char name[DISK_NAME_LEN];
struct cdev * cdev; /* char_dev [sysfs: /sys/cdev/major/sg<n>] */
struct kref d_ref;
} Sg_device;
这段代码定义了一个名为 Sg_device 的结构体,用于保存每个 SCSI generic 设备的状态。以下是各字段的简要解释:
-
struct scsi_device *device:指向底层 SCSI 设备的指针。 -
wait_queue_head_t open_wait:等待队列头,用于在使用O_EXCL打开设备时排队等待。 -
struct mutex open_rel_lock:互斥锁,用于保护在打开(open())或释放(release())过程中的访问。 -
int sg_tablesize:适配器的最大散射-聚集表大小。 -
u32 index:设备的索引号。 -
struct list_head sfds:文件描述符列表的头。 -
rwlock_t sfd_lock:读写锁,用于保护对文件描述符列表的访问。 -
atomic_t detaching:原子整数,用于标识设备是否正在被移除。 -
bool exclude:布尔值,表示是否使用O_EXCL打开设备。 -
int open_cnt:设备被打开的次数。 -
char sgdebug:调试标志,用于控制调试模式。 -
char name[DISK_NAME_LEN]:设备的名称。 -
struct cdev * cdev:字符设备结构的指针,用于在/sys/cdev/major/sg<n>中表示设备。 -
struct kref d_ref:内核引用计数结构,用于管理该设备的引用计数。
在上述代码片段中,有三个主要的数据结构:Sg_device、Sg_fd、Sg_request。它们之间的关系可以通过如下方式解释:
-
Sg_device(设备结构):
Sg_device结构用于表示每个 SCSI generic 设备的状态。- 它包含了设备的基本信息,如底层 SCSI 设备指针、打开等待队列、设备索引号等。
- 在每个设备结构中,还有一个
sfds字段,它是一个链表的头,用于管理打开的文件描述符(Sg_fd结构)。
-
Sg_fd(文件描述符结构):
Sg_fd结构用于表示每个打开的文件描述符的状态。- 每个打开的文件描述符与一个特定的
Sg_device相关联,通过parentdp字段来指向它。 - 在
Sg_fd结构中,有一个req_arr字段,它是一个数组,用于存储正在进行的请求(Sg_request结构)。
-
Sg_request(请求结构):
Sg_request结构用于表示每个正在处理的请求。- 每个请求与一个特定的文件描述符相关联,通过
parentfp字段来指向它。 - 请求可能涉及的信息包括 SCSI 命令、散射-聚集列表、数据缓冲、状态等。
综上所述,关系如下:
- 每个
Sg_device结构可以有多个Sg_fd结构(文件描述符),每个Sg_fd结构代表一个打开的文件描述符。 - 每个
Sg_fd结构可以有多个Sg_request结构(请求),每个Sg_request结构代表一个正在处理的请求。
tasklet or soft irq callback
/* tasklet or soft irq callback */
static enum rq_end_io_ret sg_rq_end_io(struct request *rq, blk_status_t status);
static int sg_start_req(Sg_request *srp, unsigned char *cmd);
static int sg_finish_rem_req(Sg_request * srp);
static int sg_build_indirect(Sg_scatter_hold * schp, Sg_fd * sfp, int buff_size);
static ssize_t sg_new_read(Sg_fd * sfp, char __user *buf, size_t count,
Sg_request * srp);
static ssize_t sg_new_write(Sg_fd *sfp, struct file *file,
const char __user *buf, size_t count, int blocking,
int read_only, int sg_io_owned, Sg_request **o_srp);
static int sg_common_write(Sg_fd * sfp, Sg_request * srp,
unsigned char *cmnd, int timeout, int blocking);
static int sg_read_oxfer(Sg_request * srp, char __user *outp, int num_read_xfer);
static void sg_remove_scat(Sg_fd * sfp, Sg_scatter_hold * schp);
static void sg_build_reserve(Sg_fd * sfp, int req_size);
static void sg_link_reserve(Sg_fd * sfp, Sg_request * srp, int size);
static void sg_unlink_reserve(Sg_fd * sfp, Sg_request * srp);
static Sg_fd *sg_add_sfp(Sg_device * sdp);
static void sg_remove_sfp(struct kref *);
static Sg_request *sg_get_rq_mark(Sg_fd * sfp, int pack_id, bool *busy);
static Sg_request *sg_add_request(Sg_fd * sfp);
static int sg_remove_request(Sg_fd * sfp, Sg_request * srp);
static Sg_device *sg_get_dev(int dev);
static void sg_device_destroy(struct kref *kref);
#define SZ_SG_HEADER sizeof(struct sg_header)
#define SZ_SG_IO_HDR sizeof(sg_io_hdr_t)
#define SZ_SG_IOVEC sizeof(sg_iovec_t)
#define SZ_SG_REQ_INFO sizeof(sg_req_info_t)
#define sg_printk(prefix, sdp, fmt, a...) \
sdev_prefix_printk(prefix, (sdp)->device, (sdp)->name, fmt, ##a)
这些函数的名称和注释提供了关于它们的一些信息,下面是对这些函数的功能和用途的简要说明:
sg_rq_end_io:任务完成时的回调,处理请求完成的情况。sg_start_req:开始处理请求,构建并发送 SCSI 命令。sg_finish_rem_req:结束处理请求,清理剩余的请求数据。sg_build_indirect:构建间接 I/O,用于处理散射-聚集列表。sg_new_read:执行新的读取请求。sg_new_write:执行新的写入请求。sg_common_write:通用的写入请求函数。sg_read_oxfer:读取已传输的数据。sg_remove_scat:移除散射-聚集列表。sg_build_reserve:构建保留区域,为请求保留缓冲区。sg_link_reserve:链接保留区域到请求。sg_unlink_reserve:从请求中取消链接保留区域。sg_add_sfp:添加一个文件描述符到设备。sg_remove_sfp:从设备移除文件描述符。sg_get_rq_mark:获取标记的请求。sg_add_request:添加一个请求到文件描述符。sg_remove_request:从文件描述符中移除请求。sg_get_dev:获取设备的状态。sg_device_destroy:销毁设备。
这些函数在 SCSI generic 驱动程序中用于处理不同的请求、IO 操作和设备状态。它们相互协作以实现 SCSI 命令的发送和接收、请求的处理、保留区域的管理等。
sg_check_file_access & sg_allow_access
/*
* The SCSI interfaces that use read() and write() as an asynchronous variant of
* ioctl(..., SG_IO, ...) are fundamentally unsafe, since there are lots of ways
* to trigger read() and write() calls from various contexts with elevated
* privileges. This can lead to kernel memory corruption (e.g. if these
* interfaces are called through splice()) and privilege escalation inside
* userspace (e.g. if a process with access to such a device passes a file
* descriptor to a SUID binary as stdin/stdout/stderr).
*
* This function provides protection for the legacy API by restricting the
* calling context.
*/
static int sg_check_file_access(struct file *filp, const char *caller)
{
if (filp->f_cred != current_real_cred()) {
pr_err_once("%s: process %d (%s) changed security contexts after opening file descriptor, this is not allowed.\n",
caller, task_tgid_vnr(current), current->comm);
return -EPERM;
}
return 0;
}
static int sg_allow_access(struct file *filp, unsigned char *cmd)
{
struct sg_fd *sfp = filp->private_data;
if (sfp->parentdp->device->type == TYPE_SCANNER)
return 0;
if (!scsi_cmd_allowed(cmd, filp->f_mode))
return -EPERM;
return 0;
}
这段代码涉及到对使用 read() 和 write() 的异步 I/O 接口进行保护的操作,以防止潜在的内核内存损坏和特权升级的问题。
-
sg_check_file_access:此函数用于检查调用者的权限,以确保不会出现在不受控制的情况下调用异步 I/O 接口的情况。它比较文件描述符的安全上下文,如果安全上下文发生变化,则拒绝访问并返回-EPERM,表示权限不足。这有助于防止潜在的特权升级攻击。 -
sg_allow_access:该函数用于检查是否允许访问文件,基于指定的 SCSI 命令和文件模式。如果设备类型为扫描仪(TYPE_SCANNER),则允许访问。否则,通过scsi_cmd_allowed函数检查给定的 SCSI 命令和文件模式是否允许访问,如果不允许,则返回-EPERM。
这两个函数一起实现了对异步 I/O 接口的安全性检查,以确保不会发生潜在的内核损坏或特权升级的情况。
总结
SG(SCSI Generic)驱动是Linux内核中用于与SCSI设备通信的驱动程序。它允许用户空间应用程序通过文件系统接口来进行与SCSI设备的通信,支持传统的 ioctl 调用,同时还提供了异步的 read() 和 write() 接口。以下是与SG驱动相关的主要数据结构和函数的概述:
数据结构:
-
sg_io_hdr_t:代表一个SCSI命令及其相关信息,如数据传输方向、传输长度等。 -
sg_request:用于跟踪SG命令的请求,包含了SG命令的各种信息以及处理的状态。 -
sg_fd:表示文件描述符的状态,包括命令队列、超时设置、保留区域等。 -
sg_device:表示每个SCSI通用设备的状态,包括所属SCSI设备、打开等待队列、索引号等。
函数:
-
sg_check_file_access:检查文件描述符的安全上下文,以确保异步I/O接口的调用来自于合法的权限上下文,防止潜在的内核内存损坏和特权升级。 -
sg_allow_access:检查是否允许访问文件,基于给定的SCSI命令和文件模式。 -
sg_rq_end_io:SCSI请求完成的回调函数,用于处理请求完成后的后续操作。 -
sg_start_req:开始一个SG请求,将SCSI命令添加到请求队列中。 -
sg_finish_rem_req:完成一个SG请求,释放相关资源。 -
sg_build_indirect:构建间接I/O的scatter-gather列表。 -
sg_new_read和sg_new_write:新的异步读写函数,用于进行读取和写入操作。 -
sg_common_write:通用的写入函数,执行SG命令的写入操作。 -
sg_read_oxfer:从请求的数据缓冲区读取数据。 -
sg_build_reserve:构建保留区域,用于存储文件描述符相关的缓冲区。 -
sg_link_reserve和sg_unlink_reserve:链接和解除链接保留区域。 -
sg_add_sfp和sg_remove_sfp:添加和移除文件描述符对象。 -
sg_get_rq_mark:根据pack_id获取请求对象。 -
sg_add_request和sg_remove_request:添加和移除请求对象。 -
sg_get_dev和sg_device_destroy:获取设备对象和销毁设备对象。
总结:
SG驱动提供了一种通用的接口,允许用户空间应用程序与SCSI设备进行通信。它通过一系列的数据结构和函数,实现了SCSI命令的传输、异步I/O的控制以及请求的管理等功能,同时也提供了对安全性的保护,防止潜在的内核损坏和特权升级问题。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)