路径名解析(Pathname Resolve)
正因为路径名非常重要,路径名解析几乎存在于QNX的每一个角落。正确地理解QNX路径名解析,对于理解QNX上系统的实现,有很深刻的意义。
比如有个服务器程序,通过“name_attach()”或是更常用的"resmgr_attach()",将自己的频道(nd0,pid0,chid0),与一个路径名 “/dev/myservice” 关联起来了,具体,客户端是怎样与这个服务器进行连接的呢?
客户端程序用我们熟悉的 open("/dev/myservice", O_RDWR), 这个调用,最后会进入到一个_connect_request() 的递归函数里,对路径名“/dev/myservice"进行解析。这个函数,看到路径名的第一个字符是’/’,就会先连接procnto里的”路径名管理器“,路径名管理器的频道是默认的,nd=ND_LOCAL_NODE, pid=PATHMGR_PID, chid=PATHMGR_CHID。连接成功后,解析程序会把”尚未解析“的路径(dev/myservice)通过_IO_CONNECT消息,发给路径名管理器。路径名管理器查表后知道,这个路径与(nd0, pid0, chid0)关联,所以路径名管理器会返回一个”请转去连接 (nd0, pid0, chid0)“的消息。客户端程序据此,会用 _connect_request() 再与 nd0, pid0, chid0, 进行连接,连接后发送_IO_CONNECT请求,如果成功返回,则连接号(Connection ID)返回,最终作为 open() 的返回值,返回给用户程序。
下面这个,是稍微复杂一的例子。如果open("/net/remote/dev/ser1", O_RDWR),具体的解析是怎么进行的呢?
客户端
发送" net/remote/dev/ser1" 给路径管理器
服务器端
路径管理器发现最长符合(longest match)的服务器是 "net/",所以返回 "请连接 (0, io-pkt pid, qnet chid)"
客户端
发送 "remote/dev/ser1" 给 (0, io-pkt pid, qnet chid)
服务器端
QNET收到这个请求,查表找到 "remote" 的机器号nd,并返回 "请连接(ndRemote, PATHMGR_PID, PATHMGR_CHID)"
客户端
发送 "dev/ser1" 给(ndRemote, PATHMGR_PID, PATHMGR_CHID),其实这也就是机器remote上的路径名管理器
服务器端
remote上的路径名管理器,发现 "dev/ser1" 是本地的 devc-ser8250这个服务器注册的,所以返回:"请连接 (ndRemote, devc-8250 pid, devc-8250 chid)"
客户端
发送"" 给(ndRemote, devc-8250 pid, devc-8250 chid),请求连接
服务器端
devc-8250收到这请求,验证客户端有足够的权限后,返回 EOK
客户端将连接号通过open()的返回值返回给程序,至此,一个与远程的串口驱动的连接就完成了。
在一个QNX系统下,多个资源管理器,如果注册了相关重叠的路径名,会发生什么事情呢?比如,devb-eide(0,eide-pid,eide- chid)注册了 “/”, (在通常的UNIX系统里,这个叫文件系统的mount,在QNX里,这个跟用resmgr_attach("/", …)没什么两样);而 devb-ram(0, ram-pid, ram-chid)注册了 “/fs”,而一个USB驱动(0, usb-pid, usb-chid)注册了 “/fs/usb”;这时候,怎么保证客户端能正确读写各服务器里的文件呢?
服务器程序 注册的路径名 拥有的文件
devb-eide / /home, /home/user, /fs/usb/diskfile
devb-ram /fs /fs/ram/ramfile
usb /fs/usb /fs/usb/usbfile
请注意,硬盘上有一个叫fs的子目录,里面有个myfilesys的文件夹,下面有个file0文件。这个fs子目录与devb-ram注册的 /fs 是重叠的。如果客户端open("/fs/usb/usbfile", O_RDWR)时,会发生什么事呢?
客户端
发送 "fs/usb/usbfile" 到路径名管理器
服务器
路径名管理器发现有3个服务器都有可能,这时它会把所有3个服务器的nd/pid/chid都返回给客户端。同时还保证"最长附合"(longest match)的服务器排在最上面,在这个例子里,usb的注册是 /fs/usb,相对被查询的 "fs/usb/usbfile"来说是3个服务器里最长附合的,其次是devb-ram, 然后再是 devb-eide。这个返回是这样的:
请连接: (0, usb-pid, usb-chid)
(0, ram-pid, ram-chid)
(0, eide-pid, eide-chid)
客户端
发送 "usbfile" 到 (0, usb-pid, usb-chid)
服务器
usb服务器检查发现自己有这个文件,如果客户端权限没有问题的话,返回 EOK
客户端
成功返回
如果客户端的请求是 open("/fs/ram/ramfile", O_RDWR),那么路径名管理器返回的列表里,就只有devb-ram, devb-eide两个服务器了。因为usb服务器注册的是 /fs/usb,所以 “/fs/ram/ramfile” 决不会在它上面。
当然,如果客户端的请求是 open("/fs/usb/diskfile", O_RDWR) 时,会怎么样呢?
客户端
发送 "fs/usb/diskfile" 到路径名管理器
服务器端
路径名管理器发现有3个服务器都有可能,这时它会把所有3个服务器的nd/pid/chid都返回给客户端。同时还保证 "最长附合"(longest match)的服务器排在最上面,在这个例子里,usb的注册是 /fs/usb,相对被查询的 "fs/usb/usbfile"来说是3个服务器里最长附合的,其次是devb-ram, 然后再是 devb-eide。这个返回是这样的:
请连接: (0, usb-pid, usb-chid)
(0, ram-pid, ram-chid)
(0, eide-pid, eide-chid)
客户端
发送 "diskfile" 到 (0, usb-pid, usb-chid)
服务器端
usb服务器检查发现自己并没有这个文件,这时它返回一个标准的出错信息 ENOENT
客户端
客户端收到ENOENT,意识到列表里的第一个服务器里没有指定的路径名;它会接下去试第二个服务器
发送 "usb/diskfile" 到 (0, ram-pid, ram-chid)
服务器端
deb-ram发现自己根本就没有 "usb" 这样的文件夹,也返回一个标准出错信息 ENOENT
客户端
客户端收到ENOENT,意识到第二个服务器里也没有指定的路径名;再试第三个服务器
发送 "fs/usb/diskfile" 到 (0, eide-pid, eide-chid)
服务器端
devb-eide收到这个请求,检查后返回成功。
客户端
成功返回
如果客户端请求 open("/fs/usb/nobody", O_RDWR),那么在同三个服务器都交换信息后,最后 open() 会收到一个 ENOENT 的出错返回,表示找不到这个文件。
还有要注意的是,只有特定的出错信息号(像这例子里的ENOENT),才会收客户端接着尝试下一个服务器。不是所有的出错都会使客户端转向下一个服务器。比如客户端请求 open("/fs/usb/usbfile", O_RDWR),但usb服务器发现客户没有权利WRITE,这时它会返回一个 EPERM。客户端在收到 EPERM 后,会直接出错返回,而不是去尝试下一个服务器。
换言之,如果客户端明明是 open("/fs/usb/diskfile", O_RDWR); 但第一个接到这个请求的usb服务器,因为文件系统有问题或是别的原因,返回一个比如 EIO;这时,虽然正确的文件是在devb-eide上,但客户端也不会去尝试devb-ram和devb-eide,而是将收到的 EIO 直接出错返回的。
最后,还有一种情形是几个服务器注册完全一样的路径名;在QNX里这是允许的。路径名解析的过程是一样的,路径名管理器会把所有汪册同一路径名的服务器一齐返回,让客户端去连接。唯一的区别是谁在最上面。默认的方法是先注册的服务器在最上面,但 resmgr_attach()有一个标志位可以改变这个顺序。有兴趣的可以去查一查。
几个服务器注册同一路径名,一种情形是为了冗余,万一某个服务器 crash了,客户端的open()也不会受影响。只是路径名管理器返回的列表里少了一项而已。还有一种情形是动态均衡服务器的负荷,在最上面的服务器接了太多请求时,可以故意返回 ENOENT,让下一个服务器去处理。
从上面可以看到,一个open()的开销是相对比较大的。虽然“最长附合”原则提供了一定程度的帮助,(尽量保证第一个服务器就能找对)但总的来说,开销还是很大,特别是企图连接一个不存在的文件时。所以一般在设计系统时,可以在初始化阶段预先打开的文件要预先open()好,这样在系统真正运行时不需要搜索不同的服务器了。
总而言之,路径名解析在QNX里有很重要的作用。要正确理解。另外,服务器的注册,可以是 /dev/ser1 这样的一个”文件“,也可以是 “/dev/socket” 这样一个目录。当”文件“与”目录“重叠”时,具体路径管理器返回的列表是怎样的,在resmgr_attach()的文档里也有很详细的说明,有用到的时候可以仔细查一下。