9p相关基础知识

9p基础知识

intro(9P) - Plan 9 from User Space (9fans.github.io)

9fans/plan9port: Plan 9 from User Space (github.com)

1. 简介

Plan 9 服务器是提供一个或多个分层文件系统(文件树)的代理,Plan 9 进程可以访问这些文件系统。服务器响应客户端层次结构以及创建、删除、读取和写入文件的请求。

客户端传输请求(T-message),然后服务器返回回复(R-messages)。

传输的信息message均由n个字节组成,其中用两字节表示数据长度n,即后面n字节都是数据。字符串用UTF-8编码,并且字符串不用空字符作为结尾,空字符在9p中是非法的。

以下七个字节是每个message中最基础的七个字节:

  • size[4]:每个message开始都是一个4字节的代表整个message的长度,这个长度包括4字节自己。
  • type[1]:接下来一个字节代表这个message的类型,在fcall.h这个头文件中可以看到有哪些类型
  • tag[2]:接下来两个字节代表这个message的标签,用来标识验证,即返回的R-message和发送的T-message的tag应该相同

剩下的字节是不同大小的参数,例如有一个参数parameter[n],则具体表现为n[2]+parameter[n],即两个字节描述参数长度n,后面n个字节是参数本身。字符串string[s]同理是s[2]后面跟上s个字符。9p 名称可以包含除斜杠之外的任何可打印字符(即十六进制 00-1F 和 80-9F 之外的任何字符。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
MESSAGES
size[4] Tversion tag[2] msize[4] version[s]
size[4] Rversion tag[2] msize[4] version[s]
size[4] Tauth tag[2] afid[4] uname[s] aname[s]
size[4] Rauth tag[2] aqid[13]
size[4] Rerror tag[2] ename[s]
size[4] Tflush tag[2] oldtag[2]
size[4] Rflush tag[2]
size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
size[4] Rattach tag[2] qid[13]
size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13])
size[4] Topen tag[2] fid[4] mode[1]
size[4] Ropen tag[2] qid[13] iounit[4]
size[4] Topenfd tag[2] fid[4] mode[1]
size[4] Ropenfd tag[2] qid[13] iounit[4] unixfd[4]
size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
size[4] Rcreate tag[2] qid[13] iounit[4]
size[4] Tread tag[2] fid[4] offset[8] count[4]
size[4] Rread tag[2] count[4] data[count]
size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
size[4] Rwrite tag[2] count[4]
size[4] Tclunk tag[2] fid[4]
size[4] Rclunk tag[2]
size[4] Tremove tag[2] fid[4]
size[4] Rremove tag[2]
size[4] Tstat tag[2] fid[4]
size[4] Rstat tag[2] stat[n]
size[4] Twstat tag[2] fid[4] stat[n]
size[4] Rwstat tag[2]
  • 如果R-message的返回的tag比T-message大1或者是Rerror的话说明请求失败了,如果标签是Rerror后面ename字段还会包含一个描述失败原因的字符串。
  • version的message代表协议版本并且表明了系统最大的可处理的消息大小。它还初始化连接并中止连接上所有未完成的 I/O。版本请求之间的消息集称为会话(session)。
  • 大部分T-message包括fid,一个四字节32位,标志一个在服务器上的当前文件。fid和fd即文件描述符有些类似,但是fid不限于为I/O打开的文件:正在检查的目录、被 stat 调用访问的文件等等——所有被操作系统操作的文件都由 fids 标识。fid由客户端决定,一个连接上的所有请求共享同一个fid空间;当多个客户端共享一个连接时,管理共享的代理必须保证没有两个客户端选择相同的fid。
  • 在attach类型的message中提供的fid会被服务端指向文件树的根。attach操作将用户标识到服务器,并且可以指定由服务器提供的特定文件树(对于拥有多个文件树的服务器)。
  • 在attach中有个afid是用来代表权限的,afid是在auth阶段进行建立的,随后通过读写操作进行交换验证信息(不是由9p明确定义的),一旦验证协议完成了,在attach中使用afid就能获得访问权限。
  • walk类型的message中使服务器将与 fid 关联的当前文件走到旧当前文件或其子目录之一的目录中的文件。Walk 返回一个指向结果文件的新 fid,通常客户端会保持一个根的fid,然后从这个根的fid去walk作为导航。
  • 一个客户端可以同时发送多个T-message,不用等待R-message回复。但是不同的T-message必须要有不同的标签tags。
  • 对auth、attach、walk、open、create请求的回复R-message都会包含一个qid,qid代表在服务端对于一个文件的独一无二的id,当且仅当它们的 qid 相同时,同一服务器层次结构上的两个文件才是相同的。(服务端可以用多个fid去指向服务器上的一个文件)。qid[13]是一个拥有13字节的标志,第一个字节代表这个文件是目录、仅追加文件(AOF)、等等。接下来是两个无符号整数,一共4+8字节,前4个字节代表qid的版本,版本代表这个文件的版本,即修改后文件的版本会增加;后8个字节代表qid的path路径,path路径是层次结构中所有文件中唯一的整数。如果在同一目录中删除并重新创建同名文件,则qids的新旧路径组件应该不同。
  • 在当前目录一个已经存在的文件可以被打开open,或者一个新文件可以被创建create。在打开文件的给定偏移量处(offset)的字节数(count)操作的 I/O 是通过读取(read)和写入(write)完成的。
  • 客户端需要clunk掉任何不需要的fid,remove操作是用来删除文件的。
  • Openfd 是 Unix 实用程序使用的扩展,它允许传统的 Unix 程序将其输入或输出附加到 9P 服务器上的 fid。
  • stat操作获得文件的信息,包括文件名字,访问权限(读、写、执行对于所有者、组、所有人的权限),访问和修改时间,以及所有者和组标识。所有者和组标识是文本名称。wstat操作允许修改文件的某些信息。
  • flush操作是用来终止请求的,当客户端发送了一个Tflush请求时,服务端需要立刻回复一个Rflush,客户端必须等到Rflush回复到达才能重新使用旧的标签old tag。
  • 因为message的大小是可变的,所以会出现message大小过长的情况(比如当文件名太长的时候),在大多数此类情况下,服务器应该生成错误而不是修改数据以适应,例如通过截断文件名。例外情况是,如果需要,应该截断 Rerror 消息中的长错误字符串,因为该字符串只是建议性的,并且在某种意义上是任意的。
  • 大多数程序看不到9p协议,9p是在内核中将访问转为9p message。

目录

目录是通过在权限参数中设置 DMDIR 的 create 创建的。可以使用 read(9P) 找到目录的成员。所有目录都必须支持遍历目录 ..表示父目录,按照惯例,目录不包含 .. 或 . 的显式条目。 服务器树的根目录的父级是它自己。

2. 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 与9p建立连接
→ Tversion <msize> 9P2000
← Rversion <msize> 9P2000

# 把根目录与fid #1 进行绑定
→ Tattach 1 "fs0" "/home/usr0/fs0"
← Rattach <qid>

# 从 fid #1 (/home/usr0/fs0) 到 “notes.txt” (一个文件!)
# 并且将文件关联到 fid #2
→ Twalk 1 2 "notes.txt"
← Rwalk <qid...>

# 通过fid #2 打开文件
→ Topen 2 OWRITE|OREAD
← Ropen <qid> <iounit>

# 读写文件。。。

# 关闭fid #2 文件
→ Tclunk 2
← Rclunk