- Introduction
QNX Neutrino允许用户编写进程充当资源管理器,并且可以动态的启动和停止。这样的好处是可以降低系统在运行时的内存需求,并且可以灵活的应对定制化嵌入式系统中的各种设备。
资源管理器通常负责向各类设备提供接口,这些设备可能涉及管理实际的硬件设备(比如串口、并口、网卡或磁盘驱动器)或虚拟设备(比如/dev/null、网络文件系统、伪ttys等)
在其他操作系统中,这个功能通常与设备驱动程序相关联,与设备驱动程序不同的是资源管理器不需要与内核进行耦合,看起来更像是用户级程序。 - What is a resource manager?
资源管理器是用户级的服务器程序,它接收来自其他程序的请求服务,并可选的与硬件进行通信。QNX Neutrino强大和灵活的本地IPC机制,可以让资源管理器与内核解耦合。
由于QNX Neutrino是一个分布式微内核操作系统,几乎所有非内核功能都由用户可安装的程序提供,因此客户端程序与资源管理器之间需要一个定义清晰与良好的接口,并对资源管理器的功能进行文档化。
在路径名空间映射中,进程管理器会将路径名和资源管理器进行映射绑定,比如串口可以由资源管理器devc-ser*管理,但是实际的路径名空间中名称为dev/ser1,可以通过打开dev/ser1来获取串口服务。
2.1 Why write a resource manager?
编写一个资源管理器有以下几个原因:
- 客户端使用POSIX接口与资源管理器通信;
- 可以减少接口类型的数量,当有很多服务器进程时,将服务器进程都编写成资源管理器,可以最大化减少客户端需要使用的接口数量;
- 可以使用命令行工具与资源管理器通信,比如cat /proc/my_status,也可以使用命令去测试驱动程序;
2.2 The types of resource managers
一般可以将资源管理器分为两类: - 设备资源管理器
- 文件系统资源管理器
- 设备资源管理器
设备资源管理器只在文件系统中创建单个文件条目,每个条目都向进程管理器注册,每个名称通常代表一个设备。 - 文件系统资源管理器
文件系统资源管理器会向进程管理器注册一个挂载点,挂载点是注册到进程管理器中的路径的一部分。路径的其他部分由文件系统资源管理器管理。比如挂载点是/mount,路径是/mount/home/thomasf,由进程管理器来标识/mount,而由文件系统管理器来标识home/thomasf。
2.3 Communication via native IPC
当一个资源管理器绑定好对应的路径名后,它便可以收到客户端的请求信息了,比如io_open、io_read等。
客户端程序与资源管理器之间的所有通信都是通过本机IPC消息传递完成的,它有许多独特的功能:
- 定义良好的应用程序接口,客户端与资源管理器分工明确;
- 资源管理器的接口简单,与OS交互也是通过本地IPC,不需要担心其他模块的影响;
- 网络传递,底层原生IPC机制本质是网络分布式的,程序可以无缝的访问网络中其他节点的资源;
所有QNX Neutrino的设备驱动程序和文件系统都是作为资源管理器实现的,这意味着“原生”QNX Neutrino设备驱动程序或文件系统能做的一切,用户编写的资源管理器也能做到
- Resource manager architecture
资源管理器的核心如下:
initialize the dispatch interface
register the pathname with the process manager
DO forever
receive a message
SWITCH on the type of message
CASE io_open:
perform io_open processing
ENDCASE
CASE io_read:
perform io_read processing
ENDCASE
CASE io_write:
perform io_write processing
ENDCASE
. // etc. handle all other messages
. // that may occur, performing
. // processing as appropriate
ENDSWITCH
ENDDO
资源管理器架构包括三部分: - 创建一个通道,以便客户端程序可以连接到资源管理器上,并发送消息;
- 资源管理器需要向进程管理器注册路径名,以便能在对该特定的路径名请求打开时,能解析到该资源管理器;
- 接收并处理消息;
每个资源管理器都需要消息处理功能(上文代码中的switch/case部分),QNX Neutrino提供了一组库函数来方便的处理这个功能,当然也能处理其他关键功能。
3.1 Message types
资源管理器会收到两种类型的消息:
- connect messages,客户端在操作路径名时(比如io_open)时会发送连接消息,这可能会涉及到权限检查(客户端是否有打开设备的权限),并为该请求设置上下文;
- I/O messages,基于上下文(客户端和资源管理器之间创建的)来发送I/O消息,比如io_read;
3.2 The resource manager shared library
QNX Neutrino提供了一下共享库,可以让资源管理器编写变得相对简单一点。
- 自动默认消息处理
当资源管理器不想处理某些消息时,可以使用默认的动作,目前有两个级别的默认动作:
- 给客户端返回ENOSYS,表明不支持特定功能;
- iofunc_*()共享库,允许资源管理器自动处理多种功能;
由于资源管理器接收的大量消息都处理一组公共属性,iofunc_*共享库允许资源管理器自动处理stat()、chmod()、chown()、lseek()等函数,无需再编写额外代码。有三个主要的结构需要考虑: - context,保存每次打开时使用的数据,例如文件中的当前位置(lseek()偏移量);
- attributes structure,保存每个设备的数据,例如设备所有者的用户和组ID、最后修改时间等;
- mount structure,对于文件系统管理器来说,需要mount structure,包含对整个挂载设备都可全局访问的数据项;
[图片]
- A resource manager is responsible for three data structures
当有多个客户端程序在特定资源上打开多种设备时,数据结构如下图:
[图片]
Multiple clients opening various devices
iofunc_*()默认函数的运行假设是:使用了context块和attribute结构的默认定义,这是一个可靠的假设,原因有二:
- 默认的context和attribute结构为大多数应用程序提供了足够的信息;
- 如果默认结构没有包含足够的信息,它们可以封装在自定义的结构中;
在自定义数据结构时,需要把attribute的结构放在前边,以便iofunc_attr_t *()函数能使用,如下图:
[图片]
- 资源管理器共享库提供了跟踪open()/dup()/close()消息的默认处理函数,并且只对最后一个close执行操作;
- 多线程处理
QNX Neutrino提供多线程,可以基于这个来构造资源管理器,多个线程等待消息并同时处理它们。资源管理器共享库不仅可以跟踪创建的线程数量和等待线程的数量,还负责维护最佳的线程数量。 - dispatch功能
操作系统提供一套`dispatch*函数集,可用于:
- 给需要多种消息类型的资源管理器和客户端提供一个公共阻塞点;
- 给没有绑定到资源管理器的消息类型提供灵活的接口;
- 在线程中将阻塞和处理代码解耦合;
-
Combine messages
QNX支持将IO消息或connect消息组合成一个消息,从而进行一些类似原子性的操作。比如通过将io_lseek和io_read消息组合成一个消息,资源管理器在收到这个消息时,readblock()函数就会允许线程去原子性的执行lseek()和read()操作。 -
Message types详细介绍
在前文中也提到过,资源管理器需要处理两类消息:
-
connect message,连接消息
-
I/O message, IO消息
4.1 connect message
客户端发出connect message来执行基于路径名的操作。当调用resmgr_attach()函数时,会将一个指针传递给resmgr_connect_funcs_t结构,该结构定义了一系列连接函数:
typedef struct _resmgr_connect_funcs {unsigned nfuncs;
int (*open) (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void *extra);int (*unlink) (resmgr_context_t *ctp, io_unlink_t *msg,
RESMGR_HANDLE_T *handle, void *reserved);int (*rename) (resmgr_context_t *ctp, io_rename_t *msg,
RESMGR_HANDLE_T *handle,
io_rename_extra_t *extra);int (*mknod) (resmgr_context_t *ctp, io_mknod_t *msg,
RESMGR_HANDLE_T *handle, void *reserved);int (*readlink) (resmgr_context_t *ctp, io_readlink_t *msg,
RESMGR_HANDLE_T *handle, void *reserved);int (*link) (resmgr_context_t *ctp, io_link_t *msg,
RESMGR_HANDLE_T *handle,
io_link_extra_t *extra);int (*unblock) (resmgr_context_t *ctp, io_pulse_t *msg,
RESMGR_HANDLE_T *handle, void *reserved);int (*mount) (resmgr_context_t *ctp, io_mount_t *msg,
RESMGR_HANDLE_T *handle,
io_mount_extra_t *extra);
} resmgr_connect_funcs_t;
可以调用iofunc_func_init()接口来用默认的处理程序指针来初始化这个结构。自己也可以重写某些接口,进行覆盖即可。
需要注意的是,resmgr_attach()接口只是将函数指针拷贝到resmgr_connect_func_t和resmgr_io_funcs_t结构中,而不是拷贝整个结构。需要分配这些结构,将它们声明为静态的或者全局变量。如果资源管理器用于具有不同处理程序的多个设备,则应该分开定义独立的结构。
这些连接消息都有一个_IO_CONNECT类型,此外还有子类型来进行分类,各个字段介绍如下: -
nfuncs,结构中函数的个数,可用于扩展;
-
open,处理客户端的open()/fopen()/sopen()等请求,消息子类型包括_IO_CONNECT_COMBINE, _IO_CONNECT_COMBINE_CLOSE, _IO_CONNECT_OPEN等;
-
unlink,处理客户端unlink()请求,消息子类型为_IO_CONNECT_UNLINK;
-
rename,处理客户端rename()请求,消息子类型为_IO_CONNECT_RENAME;
-
mknod,处理客户端mkdir()/mkfifo()/mknod()请求,消息子类型为_IO_CONNECT_MKNOD;
-
readlink,处理客户端readlink()请求,消息子类型为_IO_CONNECT_READLINK;
-
link,处理客户端link()请求,消息子类型为_IO_CONNECT_LINK;
-
unlock,处理来自内核的请求,以便在连接消息阶段接触对客户端的阻塞;
-
mount,处理客户端mount()请求,消息子类型为_IO_CONNECT_MOUNT;
4.2 I/O messages
I/O消息依赖于客户端和资源管理器之间已有的绑定关系,比如当客户端调用read()函数发送_IO_READ消息时,需要先通过open()函数来与资源管理器建立绑定关系,进而获取到文件描述符。
regmgr_io_funcs_t结构体定义了I/O消息处理函数:
typedef struct resmgr_io_funcs {
unsigned nfuncs;
int (*read) (resmgr_context_t *ctp, io_read_t *msg,
RESMGR_OCB_T *ocb);
int (*write) (resmgr_context_t *ctp, io_write_t *msg,
RESMGR_OCB_T *ocb);
int (*close_ocb) (resmgr_context_t *ctp, void *reserved,
RESMGR_OCB_T *ocb);
int (*stat) (resmgr_context_t *ctp, io_stat_t *msg,
RESMGR_OCB_T *ocb);
int (*notify) (resmgr_context_t *ctp, io_notify_t *msg,
RESMGR_OCB_T *ocb);
int (*devctl) (resmgr_context_t *ctp, io_devctl_t *msg,
RESMGR_OCB_T *ocb);
int (*unblock) (resmgr_context_t *ctp, io_pulse_t *msg,
RESMGR_OCB_T *ocb);
int (*pathconf) (resmgr_context_t *ctp, io_pathconf_t *msg,
RESMGR_OCB_T *ocb);
int (*lseek) (resmgr_context_t *ctp, io_lseek_t *msg,
RESMGR_OCB_T *ocb);
int (*chmod) (resmgr_context_t *ctp, io_chmod_t *msg,
RESMGR_OCB_T *ocb);
int (*chown) (resmgr_context_t *ctp, io_chown_t *msg,
RESMGR_OCB_T *ocb);
int (*utime) (resmgr_context_t *ctp, io_utime_t *msg,
RESMGR_OCB_T *ocb);
int (*openfd) (resmgr_context_t *ctp, io_openfd_t *msg,
RESMGR_OCB_T *ocb);
int (*fdinfo) (resmgr_context_t *ctp, io_fdinfo_t *msg,
RESMGR_OCB_T *ocb);
int (*lock) (resmgr_context_t *ctp, io_lock_t *msg,
RESMGR_OCB_T *ocb);
int (*space) (resmgr_context_t *ctp, io_space_t *msg,
RESMGR_OCB_T *ocb);
int (*shutdown) (resmgr_context_t *ctp, io_shutdown_t *msg,
RESMGR_OCB_T *ocb);
int (*mmap) (resmgr_context_t *ctp, io_mmap_t *msg,
RESMGR_OCB_T *ocb);
int (*msg) (resmgr_context_t *ctp, io_msg_t *msg,
RESMGR_OCB_T *ocb);
int (*reserved) (resmgr_context_t *ctp, void *msg,
RESMGR_OCB_T *ocb);
int (*dup) (resmgr_context_t *ctp, io_dup_t *msg,
RESMGR_OCB_T *ocb);
int (*close_dup) (resmgr_context_t *ctp, io_close_t *msg,
RESMGR_OCB_T *ocb);
int (*lock_ocb) (resmgr_context_t *ctp, void *reserved,
RESMGR_OCB_T *ocb);
int (*unlock_ocb) (resmgr_context_t *ctp, void *reserved,
RESMGR_OCB_T *ocb);
int (*sync) (resmgr_context_t *ctp, io_sync_t *msg,
RESMGR_OCB_T *ocb);
int (*power) (resmgr_context_t *ctp, io_power_t *msg,
RESMGR_OCB_T *ocb);
} resmgr_io_funcs_t;
这个结构的使用与resmgr_connect_funcs_t一样,对应到客户端的不同请求,及消息类型。
4.3 Default message handling
由于资源管理器接收的大量消息处理的是一组公共的属性,因此QNX提供了一个iofunc*()共享库,实现了一些默认的消息处理函数。目前实现的默认函数可用于处理客户端的以下请求: -
chmod()
-
chown()
-
close()
-
devctl()
-
fpathconf()
-
fseek()
-
fstat()
-
lockf()
-
lseek()
-
mmap()
-
open()
-
pathconf()
-
stat()
-
utime()
- Setting resource manager attribute
除了定义connect和I/O函数结构体之外,resmgr_attach()函数还需要用到resmgr_attr_t来指定资源管理器的属性。定义如下:
typedef struct _resmgr_attr {
unsigned flags;
unsigned nparts_max;
unsigned msg_max_size;
int (*other_func)(resmgr_context_t *,
void *msg);
unsigned reserved[4];
} resmgr_attr_t;
各个成员介绍如下: - flags
可用于修改资源管理器接口的行为,可以将其设置为0,或者是以下不同状态的组合:
- RESMGR_FLAG_ATTACH_LOCAL,设置资源管理器,不向procnto注册路径,可以向资源管理器通道发送消息;
- RESMGR_FLAG_ATTACH_OTHERFUNC,该结构中的other_func成员指向一个用于未处理I/O消息的函数;
- RESMGR_FLAG_CROSS_ENDIAN,服务器支持跨端处理,可以在服务器端做必要的转换,客户端不需要做任何事情;
- RESMGR_FLAG_NO_DEFAULT,未实现;
- RESMGR_FLAG_RCM,在处理请求时自动采取客户端的资源约束模式;
-
nparts_max
分配给IOV数组的组件数量。 -
msg_max_size
消息缓冲的大小。
这些成员在实现自己的处理函数时很重要。 -
other_func
other_func可用于指定一个例程,在资源管理器接收到不能理解的I/O消息时调用。要使用这个成员,需要在flag字段里置上RESMGR_FLAG_ATTACH_OTHERFUNC。如果other_func成员设置成NULL的话,资源管理器在收到不能理解的消息时,便会返回ENOSYS错误给客户端。
对于非I/O消息类型时,需要使用message_attach()接口来将消息绑定到dispatch handle上。 -
Ways of adding functionality to the resource manager
6.1 Using the default functions
下边是一个使用自己的io_open处理程序的示例:
main (int argc, char **argv)
{
…/* install all of the default functions /
iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);/ take over the open function */
connect_funcs.open = io_open;
…
}
int
io_open (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void extra)
{
return (iofunc_open_default (ctp, msg, handle, extra));
}
上述的代码只是一个增量步骤,可以允许在调用默认处理函数之前或者之后执行某些操作,比如可以实现如下代码:
/ example of doing something before */
extern int accepting_opens_now;
int
io_open (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void extra)
{
if (!accepting_opens_now) {
return (EBUSY);
}
/
* at this point, we're okay to let the open happen,
* so let the default function do the "work".
*/
return (iofunc_open_default (ctp, msg, handle, extra));
}
或者:
/* example of doing something after */
int
io_open (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void extra)
{
int sts;
/
* have the default function do the checking
* and the work for us
/
sts = iofunc_open_default (ctp, msg, handle, extra);
/
* if the default function says it's okay to let the open
* happen, we want to log the request
*/
if (sts == EOK) {
log_open_request (ctp, msg);
}
return (sts);
}
这种方法的优势是,只需要很少的工作就可以添加到标准的默认POSIX处理程序中。
6.2 Using the helper functions
在很多默认处理函数中,都调用到了帮助函数,比如下边的iofunc_chmod_default()和iofunc_stat_default():
int
iofunc_chmod_default (resmgr_context_t *ctp, io_chmod_t *msg,
iofunc_ocb_t *ocb)
{
return (iofunc_chmod (ctp, msg, ocb, ocb -> attr));
}
int
iofunc_stat_default (resmgr_context_t *ctp, io_stat_t *msg,
iofunc_ocb_t *ocb)
{
iofunc_time_update (ocb -> attr);
iofunc_stat (ocb -> attr, &msg -> o);
return (_RESMGR_PTR (ctp, &msg -> o,
sizeof (msg -> o)));
}
在上边的代码中分别都调用到了iofunc_chmod()、iofunc_time_update()、iofunc_stat()等帮助函数。
更复杂的case如下:
int
iofunc_open_default (resmgr_context_t *ctp, io_open_t *msg,
iofunc_attr_t *attr, void *extra)
{
int status;
iofunc_attr_lock (attr);
if ((status = iofunc_open (ctp, msg, attr, 0, 0)) != EOK) {
iofunc_attr_unlock (attr);
return (status);
}
if ((status = iofunc_ocb_attach (ctp, msg, 0, attr, 0))
!= EOK) {
iofunc_attr_unlock (attr);
return (status);
}
iofunc_attr_unlock (attr);
return (EOK);
}
调用了以下帮助函数:
- iofunc_attr_lock()接口,获取锁,用于互斥访问属性结构;
- iofunc_open(),进行权限验证;
- iofunc_ocb_attach(),绑定OCB结构;
- iofunc_attr_unlock(),释放锁;
6.3 Writing the entire function yourself
有时候默认的处理函数对特定的资源管理器来说没有用处,可以自己实现处理函数,在这些实现的处理函数中,可以去调用帮助函数,比如iofunc_read_verify()。
- Security
资源管理器通常是一个特权进程,因此需要小心,防止客户端迫使它耗尽资源或损耗系统。在设计资源管理器时,应该考虑以下几点:
- 管理路径名空间中资源管理器条目的权限,可以将权限指定为iofunc_attr_init()的参数;
- 资源管理器通常需要运行在root权限,以便能与路径名空间绑定,但是更好的方式是运行在非root权限,而使用procmgr_ability()去获取特权的能力。
- 如果资源管理器不是一个没有资源约束阈值的关键进程,它可以简单的运行在约束模式下,而如果是一个关键进程,应该保持PROCMGR_AID_RCONSTRAINT能力,需要确保受约束的客户端不使用它来分配超过当前阈值的资源。
- 对客户端的能力进行检查,通常可以调用ConnectClientInfoAble()或iofunc_client_info_able()来检查。
- POSIX-Layer Data Structures
这篇文章主要讲述和POSIX-Layer例程支持相关的几个关键数据结构。
8.1 Introduction
资源管理器定义了三个与POSIX-Layer相关的数据结构:
- iofunc_ocb_t,包含了每次打开的数据,比如打开文件的偏移等;
- iofunc_attr_t,资源管理器有时可能得管理多个设备,比如devc-ser*对应到/dev/ser1,/dev/ser2等,这个结构包含一个名字对应的数据;
- iofunc_mount_t,主要用于文件系统,设备通常不需要这个结构;
三者之间的关系如下:
[图片] - A resource manager is responsible for three data structures
[图片]
Multiple clients with multiple OCBs, all linked to one mount structure
8.2 iofunc_ocb_t结构
OCB(Open Control Block)结构,用于维护客户端和资源管理器之间特定会话的状态信息,在open()时创建,在close()时退出。这个数据结构用于iofunc layer中的帮助函数。该结构至少包含以下内容:
typedef struct _iofunc_ocb {
IOFUNC_ATTR_T *attr;
int32_t ioflag;
off_t offset;
uint16_t sflag;
uint16_t flags;
} iofunc_ocb_t; - attr,指向属性结构体;
- ioflag,包含了资源打开模式:O_RDONLY/O_RDWR/O_WRONLY,对应的ioflag值为_IO_FLAG_RD/_IO_FLAG_RD | _IO_FLAG_WR/_IO_FLAG_WR,这些信息从io_connect_t结构体中继承得来;
- offset,读写资源的偏移,资源管理器可以修改这个成员;
- sflag,定义共享模式,从io_connect_t结构中继承得来;
- flags,当设置IOFUNC_OCB_PRIVILEGED位时,特权进程来执行open()操作,此外也可以使用IOFUNC_OCB_FLAGS_PRIVATE范围之内的flag。资源管理器可以修改这个成员;
8.3 iofunc_attr_t结构
这个结构为资源管理器提供了设备的特征,与OCB结构结合使用。
typedef struct _iofunc_attr {
IOFUNC_MOUNT_T *mount;
uint32_t flags;
int32_t lock_tid;
uint16_t lock_count;
uint16_t count;
uint16_t rcount;
uint16_t wcount;
uint16_t rlocks;
uint16_t wlocks;
struct _iofunc_mmap_list *mmap_list;
struct _iofunc_lock_list *lock_list;
void *list;
uint32_t list_size;
off_t nbytes;
ino_t inode;
uid_t uid;
gid_t gid;
time_t mtime;
time_t atime;
time_t ctime;
mode_t mode;
nlink_t nlink;
dev_t rdev;
} iofunc_attr_t; - mount,指向挂载结构体;
- flags,可以是不同比特位的组合,包括:IOFUNC_ATTR_ATIME/IOFUNC_ATTR_CTIME/IOFUNC_ATTR_DIRTY_NLINK/IOFUNC_ATTR_DIRTY_MODE/IOFUNC_ATTR_DIRTY_OWNER/IOFUNC_ATTR_DIRTY_RDEV/IOFUNC_ATTR_DIRTY_SIZE/IOFUNC_ATTR_DIRTY_TIME/IOFUNC_ATTR_MTIME,用于标识修改记录。
- lock_tid和lock_count,用于多线程的加锁和统计;
- count, rcount, wcount, rlocks and wlocks,计数值及锁;
- mmap_list and lock_list,mmap_list用于iofunc_mmap()和iofunc_mmap_default()函数,lock_list用于iofunc_lock_default()函数,通常用户不需要修改会检查这两个成员;
- list and list_size,保留字段;
- nbytes,资源的字节数,比如对于文件来说,放置的就是文件的大小;
- inode,特定挂载点的inode,每个挂载点都必须是唯一的;
- uid and gid,资源的用户ID和组ID,通常由chown()等函数来更新;
- mtime, atime, and ctime,修改时间、访问时间,以及状态改变时间,是三个POSIX时间成员;
- mode,资源的模式,定义在<sys/stat.h>中,以S_*开头;
- nlink,链接数量;
- rdev,包含字符特殊设备的设备号,以及指定专用设备的rdev号;
8.4 iofunc_mount_t结构(可选)
这个结构中的成员,尤其是conf和flags,可以修改某些iofunc层函数的行为。这个结构中至少包含以下内容:
typedef struct _iofunc_mount {
uint32_t flags;
uint32_t conf;
dev_t dev;
int32_t blocksize;
iofunc_funcs_t *funcs;
} iofunc_mount_t; - flags,包含一个相关的位用于标识资源管理器使用的偏移量是32-bit的(与扩展的64-bit偏移相反);
- conf,包含以下位:IOFUNC_PC_CHOWN_RESTRICTED/IOFUNC_PC_NO_TRUNC/IOFUNC_PC_SYNC_IO/IOFUNC_PC_LINK_DIR/IOFUNC_PC_ACL,这些选项是由iofunc层_IO_PATHCONF默认处理程序返回的;
- dev,包含文件系统对应的设备号,当客户端调用stat()函数时,会将该值填充到struct stat st_dev成员中;
- blocksize,包含了设备的块大小;
- funcs,这是一个struct _iofunc_funcs结构,用于扩展OCB;