Magenta - 文件系统概述
文件系统最初用于管理存储设备上的文件,后期随着虚拟文件系统的出现,也使用文件系统的方式来管理设备、提供kernel配置接口等等。
文件访问流程
Magneta是以微内核的方式实现,所以其文件系统是以client/server的方式实现的。server利用Port监听Channel,以获取用户的请求事件;用户发起的文件操作请求并不是由kernel实现的,请求经过Channel传递给文件系统的server,server执行完请求后,将结果再通过Channel返回给用户。
Magenta是使用Channel和Port实现Client和server的交互的。Magenta的Channel类似于Linux的pipe或socket pair;Port则类似于select或event poll。
在上图中,线程”foo”创建file Channel后,将操作请求和此file Channel的句柄经root Channel发送到Port;Port监听到root Channel的写动作后,触发线程”mxio-dispatcher”从root Channel中读取具体的请求事件;处理完请求事件后,经file Channel返回处理结果,并利用Port监听此file Channel,以便响应后续的读写请求。可见file Channel代表新打开的文件。
至于Channel的句柄是怎么从进程”bin/devmgr”传递给进程”foo”的,在后面会有说明。
Port和Channel的说明
下图是Port和Channel的数据结构示意图,包含2个Port对象和2个Channel对象。其中memfs:memfs_global_dispatcher监听2个Channel。
Port可以同时监听多个handle的状态变化。在用户层面,用户创建port并调用mx_port_wait监听Port;如果有handle的状态改变,则port会将消息通知到user,user调用回掉函数处理消息事件。
我们来看看Port是怎么实现监听功能的。在user层面,创建了memfs::memfs_global_dispatcher,因为需要实现对memfs::global_vfs_root的监听,所以创建了一个handler,此handler包括需要监控的handle以及相应的回掉函数。这里实现的是对1个Channel对象的监听,因为”mxio-dispatcher” server是从channel读取用户的请求的。
在kernel层面,PortDispatcher在ChannelDispacher对象中创建1个PortObserver,此PortObserver包含了user层面所提供的回掉函数以及参数等(其成员key_其实是handler_t对象的地址)。当ChannelDispacher对象状态有变化时,则通知到PortDispatcher,PortDispatcher会将PortObserver的packet_赋值后链入packets_并发送给user。user可根据packet中的key得到handler,从而得到回掉函数和参数。
在本例中,由于监听的是根目录,所以参数中包含根目录对应的Vnode,即global_vfs_root。回掉函数从而可以从根目录开始搜索用户所请求的文件目录。
由图可见,真正的文件操作是由在线程”mxio-dispatcher”中的回掉函数vfs_handler执行的。此函数的实现以后章节再讲。
关于Port和handle的关系,可见1个Port可以监控多个handle;而1个handle可以被多个Port监控。
文件系统的结构
我把文件系统分为前端和后端。前端是呈现给用户的目录结构,后端则指各种文件类型的不同具体操作。
Magenta的前端类似于Linux的inode/dentry结构,也有VnodeMemfs/Dnode结构。Dnode用于管理目录树,而VnodeMemfs则指向文件的操作。
devmgr在初始化阶段,创建了如下目录结构:
<root>
|--dev
|--boot
|--tmp
|--data
|--volume
即在根目录下创建了5个目录。我们来看看这些目录是怎么创建和组织的。先来看看涉及到的数据结构。
正如前面所述,每个目录对应着一对VnodeDir/Dnode,而每个文件则对应着一对VnodeFile/Dnode。VnodeDir和VnodeFile继承自VnodeMemfs和fs::Vnode。VnodeMemfs和Dnode分别通过VnodeMemfs.dnode_和Dnode.vnode_互指。目录名保存在Dnode的成员name_中,目录树是由Dnode的成员parnet_/children_来组织的。对文件/目录的具体操作由VnodeFile/VnodeDir决定。
创建完以上的目录后,这些目录还只存在于ram中,用户还不可见,还需要为这些目录添加操作接口,以便用户可以遍历访问。如下的2个成员是VnodeDir所特有,可见远程映射和监控只可在目录层面做。
fs::RemoteContainer remoter_;
fs::WatcherContainer watcher_;
< root >对应的VnodeDir是memfs::global_vfs_root,它的句柄remote_.remote指向一个channel对象的一端;而另一端则由mxio_root_handle管理,供用户打开和读写等。
Client端的实现
在Magenta的进程用户空间,类似于Linux,使用描述符管理已打开的文件,目前支持最多可打开256个文件。
用户如果请求打开的文件路径是绝对路径,则使用mxio_root_handle来执行具体的打开动作。mxio_root_handle是struct mxrio类型。
typedef struct mxio {
mxio_ops_t* ops; // = mx_remote_ops
uint32_t magic;
atomic_int_fast32_t refcount;
int32_t dupcount;
int32_t flags;
} mxio_t;
struct mxrio {
// base mxio io object
mxio_t io;
// channel handle for rpc
mx_handle_t h;
// event handle for device state signals, or socket handle
mx_handle_t h2;
// transaction id used for synchronous remoteio calls
_Atomic mx_txid_t txid;
};
每个进程都有自己的私有mxio_root_handle。进程”bin/devmgr”的mxio_root_handle是自己创建的,而其他进程的mxio_root_handle一般是由父进程传递过来的。由于进程”bin/devmgr”是init进程,所以实际上其他进程的进程mxio_root_handle都是根据”bin/devmgr”的mxio_root_handle而创建的,即一般而言,所有进程的mxio_root_handle的成员“mx_handle_t h”实际指向同一个Channel对象。至于handle是怎么在进程间专递的,后期再讲。
进程”bin/devmgr”创建的mxio_root_handle,其“mx_handle_t h”指向的channel监听的是根目录memfs::global_vfs_root,所以可以打开绝对路径的文件。
mx_remote_ops在执行open动作时,先创建一个新的channel,将channel的一端句柄和文件路径通过remote channel handle发送给server;server接收到新channel和路径后,打开文件,并将打开的结果通过新的channel发送给client,并监听此新channel;client会通过新channel读取到打开结果,创建一个新的mxrio_t,其成员“mx_handle_t h”就指向此新的channel,从而此新channel代表一个新打开的文件。然后为此新的mxrio_t分配一个文件描述符,提供给用户操作。