QNX之编写资源管理器(二)
QNX相关历史文章:
Fleshing Out the Skeleton
在讲完大体框架后,这篇文章将涉及到更多细节:消息类型;资源管理器属性;添加功能;安全事项等。
1. Message types
在前文中也提到过,资源管理器需要处理两类消息:
- connect message,连接消息
- I/O message, IO消息
1.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
;
1.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
一样,对应到客户端的不同请求,及消息类型。
1.3 Default message handling
由于资源管理器接收的大量消息处理的是一组公共的属性,因此QNX提供了一个iofunc_*()
共享库,实现了一些默认的消息处理函数。目前实现的默认函数可用于处理客户端的以下请求:
chmod()
chown()
close()
devctl()
fpathconf()
fseek()
fstat()
lockf()
lseek()
mmap()
open()
pathconf()
stat()
utime()
2. 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
上。
3. Ways of adding functionality to the resource manager
3.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处理程序中。
3.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()
,释放锁;
3.3 Writing the entire function yourself
有时候默认的处理函数对特定的资源管理器来说没有用处,可以自己实现处理函数,在这些实现的处理函数中,可以去调用帮助函数,比如iofunc_read_verify()
。
4. Security
资源管理器通常是一个特权进程,因此需要小心,防止客户端迫使它耗尽资源或损耗系统。在设计资源管理器时,应该考虑以下几点:
- 管理路径名空间中资源管理器条目的权限,可以将权限指定为
iofunc_attr_init()
的参数; - 资源管理器通常需要运行在root权限,以便能与路径名空间绑定,但是更好的方式是运行在非root权限,而使用
procmgr_ability()
去获取特权的能力。 - 如果资源管理器不是一个没有资源约束阈值的关键进程,它可以简单的运行在约束模式下,而如果是一个关键进程,应该保持
PROCMGR_AID_RCONSTRAINT
能力,需要确保受约束的客户端不使用它来分配超过当前阈值的资源。 - 对客户端的能力进行检查,通常可以调用
ConnectClientInfoAble()
或iofunc_client_info_able()
来检查。