QNX相关历史文章:
Filesystem Resource Managers
这篇文章主要描述文件系统资源管理器。
1. Considerations for filesystem resource managers
由于文件系统资源管理器可能会收到很长的路径名,因此它必须要能够正确地解析和处理路径的每个部分。
比如,一个资源管理器注册了挂载点/mount
,当用户输入:ls -l /mount/home
时, 其中/mount/home
是设备中的一个路径,
那么ls
会做以下事情:
d = opendir("/mount/home");
while (...) {
dirent = readdir(d);
...
}
2. Taking over more than one device
当资源管理器需要处理多个设备时,可以对每个设备名调用resmgr_attach()
接口进行注册,此外每个设备还需要唯一的属性结构,当像chmod()
等函数被调用时,便可对特定设备的属性进行修改。
示例代码如下:
/*
* MOD [1]: allocate multiple attribute structures,
* and fill in a names array (convenience)
*/
#define NumDevices 2
iofunc_attr_t sample_attrs [NumDevices];
char *names [NumDevices] =
{
"/dev/sample1",
"/dev/sample2"
};
main ()
{
...
/*
* MOD [2]: fill in the attribute structure for each device
* and call resmgr_attach for each device
*/
for (i = 0; i < NumDevices; i++) {
iofunc_attr_init (&sample_attrs [i],
S_IFCHR | 0666, NULL, NULL);
pathID = resmgr_attach (dpp, &resmgr_attr, name[i],
_FTYPE_ANY, 0,
&my_connect_funcs,
&my_io_funcs,
&sample_attrs [i]);
}
...
}
在这个代码中,增加了属性结构的数组,并且多次调用了resmgr_attach()
接口,其他地方不需要修改。io_read
或io_write
处理函数不需要改动,iofunc layer
默认处理函数会去处理多种设备的情况。
3. Handling directories
之前的例子路径名都是在/dev
目录下,资源管理器不会限制只能在某个路径下,并可以处理任何数量的路径名,一个实际的限制在于当数量越多的时候,可能面临内存不足以及查找速度慢等问题。
当路径名数量很多时,一个最直接的办法就是使用路径名前缀,比如:
-
CD-ROM
文件系统,可以使用前缀/cdrom
,当对这个路径下的名字操作时,都会去处理CD-ROM
设备; - 一个处理压缩文件的文件系统,可以使用前缀
/uncompressed
; - 网络文件系统可以使用
/mount/flipper
路径名来显示远程机器,当访问这个路径时,就像访问本地机器;
上边这些例子的特点是都实现了文件系统,文件系统资源管理器与设备资源管理器在以下几个关键领域有区别:
-
resmgr_attach()
参数中的_RESMGR_FLAG_DIR
位会通知库,资源管理器会在挂载点路径或挂载点路径下接受匹配; -
_IO_CONNECT
逻辑必须根据数据权限和访问权限来检查路径名的各个部分,它还必须确保在访问特定文件名时绑定了适当的属性; -
_IO_READ
逻辑必须返回路径名指定的“文件”或“路径”数据;
3.1 Matching at or below a mountpoint
在使用resmgr_attach()
函数时,传入的flags
参数为_RESMGR_FLAG_DIR
时,表明允许在指定的挂载点路径或该路径之下进行路径名解析。如果flags
参数为0,表明使用默认值。
3.2 The _IO_OPEN message for filesystems
假设注册了一个挂载点/sample_fsys
,如下:
pathID = resmgr_attach
(dpp,
&resmgr_attr,
"/sample_fsys", /* mountpoint */
_FTYPE_ANY,
_RESMGR_FLAG_DIR, /* it's a directory */
&connect_funcs,
&io_funcs,
&attr);
当客户端调用如下代码:
fopen ("/sample_fsys/spud", "r");
资源管理器会收到_IO_CONNECT
消息,并且调用io_read
处理函数。_IO_CONNECT
消息的数据结构如下:
struct _io_connect {
unsigned short type;
unsigned short subtype; /* _IO_CONNECT_* */
unsigned long file_type; /* _FTYPE_* in sys/ftype.h */
unsigned short reply_max;
unsigned short entry_max;
unsigned long key;
unsigned long handle;
unsigned long ioflag; /* O_* in fcntl.h, _IO_FLAG_* */
unsigned long mode; /* S_IF* in sys/stat.h */
unsigned short sflag; /* SH_* in share.h */
unsigned short access; /* S_I in sys/stat.h */
unsigned short zero;
unsigned short path_len;
unsigned char eflag; /* _IO_CONNECT_EFLAG_* */
unsigned char extra_type; /* _IO_EXTRA_* */
unsigned short extra_len;
unsigned char path[1]; /* path_len, null, extra_len */
};
其中ioflat, mode, sflag, access
表明资源是如何打开的。参数path_len
表明路径名占多少字节,path
放置实际的路径名。注意,出现的路径名是spud
,而不是/sample_fsys/spud
,这是因为消息只包含相对于挂载点的路径名。还要注意的是,路径名中不会有相对路径(.
, ..
)部分,也不会冗余的斜杠(/
),这些都会在消息发送到资源管理器时被解析和删除。
当编写文件系统资源管理器时,在处理路径名时可能会有一些复杂的情况,为了验证访问,我们需要分解传递的路径名,并对每个部分进行检查。可以使用strtok()
等来分解路径名字符串,然后调用iofunc_check_access()
来进行访问验证。验证名称之后发生的绑定要求处理的每个路径都有自己的属性结构,如果将错误的属性绑定到所提供的路径名,将会导致意外的行为。
3.3 Returning directory entries from _IO_READ
当_IO_READ
处理函数被调用后,可能需要返回文件(如果S_ISDIR(ocb->attr->mode)为
false),也可能需要返回目录(如果
S_ISDIR(ocb->attr->mode)为true
)。
对于将目录返回给客户端,存在一些约束,返回的不是一个字节流,返回的是几个struct dirent
的数据结构,dirent
结构必须4字节对齐。数据结构如下:
struct dirent {
#if _FILE_OFFSET_BITS - 0 == 64
ino_t d_ino; /* File serial number. */
off_t d_offset;
#elif !defined(_FILE_OFFSET_BITS) || _FILE_OFFSET_BITS == 32
#if defined(__LITTLEENDIAN__)
ino_t d_ino; /* File serial number. */
ino_t d_ino_hi;
off_t d_offset;
off_t d_offset_hi;
#elif defined(__BIGENDIAN__)
ino_t d_ino_hi;
ino_t d_ino; /* File serial number. */
off_t d_offset_hi;
off_t d_offset;
#else
#error endian not configured for system
#endif
#else
#error _FILE_OFFSET_BITS value is unsupported
#endif
_Int16t d_reclen;
_Int16t d_namelen;
char d_name[1];
};
d_ino
成员包含一个挂载点唯一的文件序列号。这个序列号通常用于各种磁盘检查程序中。在有些文件系统中,d_offset
用于标识目录条目本身,而在其他情况下,它是下一个目录项的偏移量。d_reclen
成员包含此目录项的大小和任何其他相关信息。d_namelen
参数指示d_name
参数的大小,d_name
保存该目录项的实际名称。
dirent
结构中仅包含名称的前四个字节的空间,_IO_READ
处理程序需要返回一个更大的结构,包含名字和dirent
,如下:
struct {
struct dirent ent;
char namebuf[NAME_MAX + 1 + offsetof(struct dirent, d_name) -
sizeof( struct dirent)];
} entry
或者定义成联合体:
union {
struct dirent ent;
char filler[ offsetof( struct dirent, dname ) + NAME_MAX + 1];
} entry;
在我们的io_read
处理程序中,需要生成许多struct dirent
条目,并返回给客户端。如果在资源管理器中维护了目录项缓存,那么构造一组IOVs来指向这些项即可。如果没有缓存的话,则必须手动将目录项组装到缓冲区中,然后返回指向该缓冲区的IOV。
3.3.1 Returning information associated with a directory structure
除了返回_IO_READ
消息中的struct dirent
外,还可以返回struct stat
,尽管这个可以提高效率,但是struct stat
完全是可选的,如果不返回struct stat
的话,客户端就必须通过stat()/lstat()
来获取该信息。
客户端可以通过将消息的xtype
成员设置成_IO_XFLAG_DIR_EXTRA_HINT
,以便向文件系统发送提示以返回额外的信息,但文件系统不保证这样做。如果资源管理器提供信息,则必须将其放入到struct dirent_extra_stat
中,定义如下:
struct dirent_extra_stat {
_Uint16t d_datalen;
_Uint16t d_type;
_Uint32t d_reserved;
struct stat d_stat;
};
资源管理器必须将d_type
设置为_DTYPE_LSTAT
或_DTYPE_STAT
,这取决于它是否解析符号链接。比如:
if(msg->i.xtype & _IO_XFLAG_DIR_EXTRA_HINT) {
struct dirent_extra_stat extra;
extra.d_datalen = sizeof extra.d_stat;
extra.d_type = _DTYPE_LSTAT;
extra.d_reserved = 0;
iofunc_stat(ctp, &attr, &extra.d_stat);
...
}
每个目录项后都有一个dirent_extra_stat
:
dirent
结构必须在4字节边界上对齐,dirent_extra_stat
结构必须在8字节边界上对齐,d_reclen
成员必须包含这两个结构的大小,包含路径名和对齐所需的任何空间。最多不超过7字节的对齐填充。
客户端必须调用_DEXTRA_*()
宏来检查额外的数据,如果检查失败,则需要显示调用lstat()
或stat()
。比如,ls -l
检查额外的_DTYPE_LSTAT
信息,如果不存在,ls
调用lstat()
。ls -L
检查额外的_DTYPE_STAT
信息,如果不存在,ls
调用stat()
。
网友评论