9pfs相关结构以及源码分析

9pfs

image-20221030142903019

1. 基础数据结构

1.1 9pdev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 9PDEV
* A structure used to interact with a 9P device.
* 一个专门用来与9p设备进行通信的结构体
*/
struct uk_9pdev {
/* Underlying transport operations. */
const struct uk_9pdev_trans_ops *ops;
/* Allocator used by this device. */
struct uk_alloc *a; /* See uk_9pdev_connect(). */
/* Transport state. */
enum uk_9pdev_trans_state state;
/* Maximum size of a message. */
uint32_t msize;
/* Maximum size of a message for the transport. */
uint32_t max_msize;
/* Transport-allocated data. */
void *priv;
/* @internal Fid management. */
struct uk_9pdev_fid_mgmt _fid_mgmt;
/* @internal Request management. */
struct uk_9pdev_req_mgmt _req_mgmt;
  • ops:设备基础的一些传输操作,包括connect、disconnect、request
  • a: 用于connect的内存分配器
  • state:设备的状态,包括已连接(默认)和disconnect后的状态,在后者任何的请求都是非法的,当所有的资源都释放后9pdev也会释放
  • msize:一个消息最大的大小,这个值可以自己指定
  • max_msize:一个消息对于传输的最大大小,这个值是这个dev支持的最大大小
  • priv:传输的数据
  • _fid_mgmt:内部fid管理,用于描述fid的可用性,里面有自旋锁、下一个可用的fid、和可用的fid的链表、在使用中的fid的链表
  • _req_mgmt:内部的request管理,包括自旋锁、可用的tag的位图,已经被分配但是还没有被销毁的request链表,空闲的request链表。

1.2 9pdev_trans

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 用来描述传输的结构
* A structure used to describe a transport. */
struct uk_9pdev_trans {
/*
* Transport name (e.g. "virtio", "xen"). This field is reserved for
* future use, when multiple transport options are available on the
* same platform, such as RDMA or TCP, in addition to the platform
* specific transport.
*/
const char *name;
/* Supported operations. */
const struct uk_9pdev_trans_ops *ops;
/* Allocator used for devices which use this transport layer. */
struct uk_alloc *a;
/* @internal Entry in the list of available transports. */
struct uk_list_head _list;
};

  • name:传输的名字,例如virtio或者xen
  • ops:传输的操作,即connect、disconnect、request

1.3 9pfs_mount_data

1
2
3
4
5
6
7
8
9
10
11
12
13
// 用来与上层vfs mount绑定的数据,即mount->m_data = uk_9pfs_mount_data
struct uk_9pfs_mount_data {
/* 9P device. */
struct uk_9pdev *dev;
/* Wanted transport. */
struct uk_9pdev_trans *trans;
/* Protocol version used. */
enum uk_9pfs_proto proto;
/* Username to attempt to mount as on the remote server. */
const char *uname;
/* File tree to access when offered multiple exported filesystems. */
const char *aname;
};
  • dev:即用于传输的9p设备
  • trans:传输的具体操作,例如连接、发送等
  • proto:即9p具体的协议名,现在unikraft仅实现了9P2000.u
  • uname:尝试在远程服务器上挂载的用户名
  • aname:当有多个文件系统的时候选择的文件树

1.4 9pfs_file_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 对应一个9P文件的结构体,即vfscore_file->f_data = uk_9pfs_file_data
struct uk_9pfs_file_data {
/* Fid associated with the 9pfs file. */
struct uk_9pfid *fid;
/*
* Buffer for persisting results from a 9P read operation across
* readdir() calls.
*/
char *readdir_buf;
/*
* Offset within the buffer where the stat of the next child can
* be found.
*/
int readdir_off;
/* Total size of the data in the readdir buf. */
int readdir_sz;
};
  • fid:和9p文件系统关联的标识符
  • readdir_buf:通过readdir()操作获取的9p读取操作的持久化缓冲区
  • readdir_off:在缓冲区中的偏移量,在这个偏移量下一个child会被找到
  • readdir_sz:缓冲区中数据的大小

1.5 9pfs_node_data

1
2
3
4
5
6
7
8
9
// 对应上层vfs vnode的结构体 即vnode->v_data = uk_9pfs_node_data
struct uk_9pfs_node_data {
/* Fid associated with the vfs node. */
struct uk_9pfid *fid;
/* Number of files opened from the vfs node. */
int nb_open_files;
/* Is a 9P remove call required when nb_open_files reaches 0? */
bool removed;
};
  • fid:与vnode关联的fid
  • nb_open_files:从这个vnode同时打开的文件数量
  • removed:当打开文件数量为0时应该removed掉

1.6 一些9P协议规定的类型

1.6.1 9p_type

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
31
// 9p message的类型
enum uk_9p_type {
UK_9P_TVERSION = 100, // 建立连接、确认版本信息
UK_9P_RVERSION,
UK_9P_TAUTH = 102, // 验证信息,交换权限
UK_9P_RAUTH,
UK_9P_TATTACH = 104, // 连接服务端,指定特定文件树
UK_9P_RATTACH,
UK_9P_TERROR = 106, // 报错信息,包括原因
UK_9P_RERROR,
UK_9P_TFLUSH = 108, // 终止某个请求
UK_9P_RFLUSH,
UK_9P_TWALK = 110, // 查询文件、目录
UK_9P_RWALK,
UK_9P_TOPEN = 112, // 打开文件
UK_9P_ROPEN,
UK_9P_TCREATE = 114, // 创建文件
UK_9P_RCREATE,
UK_9P_TREAD = 116, // 读取文件
UK_9P_RREAD,
UK_9P_TWRITE = 118, // 写文件
UK_9P_RWRITE,
UK_9P_TCLUNK = 120, // clunk掉已经不需要的fid
UK_9P_RCLUNK,
UK_9P_TREMOVE = 122, // 删除文件
UK_9P_RREMOVE,
UK_9P_TSTAT = 124, // 获取文件基本信息
UK_9P_RSTAT,
UK_9P_TWSTAT = 126, // 修改文件基本信息
UK_9P_RWSTAT,
};

1.6.2 qid type

1
2
3
4
5
6
7
8
9
#define UK_9P_QTDIR               0x80	// 目录
#define UK_9P_QTAPPEND 0x40 // 仅追加文件(AOF)
#define UK_9P_QTEXCL 0x20 // 可执行文件
#define UK_9P_QTMOUNT 0x10 // 挂载
#define UK_9P_QTAUTH 0x08 // 通过auth操作建立的auth权限文件
#define UK_9P_QTTMP 0x04 // 临时文件
#define UK_9P_QTSYMLINK 0x02 // 软链接
#define UK_9P_QTLINK 0x01 // 硬链接
#define UK_9P_QTFILE 0x00 // 普通文件

1.6.3 open mode

1
2
3
4
5
6
7
8
9
#define UK_9P_OREAD               0x00 	// 仅读
#define UK_9P_OWRITE 0x01 // 仅写
#define UK_9P_ORDWR 0x02 // 可读写
#define UK_9P_OEXEC 0x03 // 可执行
#define UK_9P_OTRUNC 0x10 // 截断
#define UK_9P_OREXEC 0x20 //
#define UK_9P_ORCLOSE 0x40 // 关闭
#define UK_9P_OAPPEND 0x80 // 追加
#define UK_9P_OEXCL 0x1000 // 如果文件存在就不再打开

1.6.4 9p_qid

1
2
3
4
5
6
struct uk_9p_qid {
uint8_t type; // 1字节 即1.6.2中的qid类型,代表文件的类型
uint32_t version; // 4字节 代指文件的版本,即每次修改版本都会增加
uint64_t path; // 8字节 层次结构中所有文件中唯一的整数
};

1.6.5 9p_fid

1
2
3
4
5
6
7
8
9
10
// 描述一个被管理的fid
struct uk_9pfid {
uint32_t fid; // fid号码,是唯一的标识
struct uk_9p_qid qid; // 与fid关联的服务端的qid,代表客户端的一个文件
uint32_t iounit; // 保证原子传输的最大尺寸,因为9P的实现可能会限制单个消息的大小,所以一次读或写调用可能会产生一个以上的消息。
bool was_removed; // 代表是否被移除了
__atomic refcount; // 引用计数,即代表有多少在用这个文件
struct uk_9pdev *_dev; // 关联9pdev
struct uk_list_head _list; // 这个fid所在的list,用于9pdev_fid_mgmt来管理fid,即分配fid
};

1.6.6 9p_str

1
2
3
4
struct uk_9p_str {
uint16_t size; // 字符串的字节数,即后面size个字节是这个字符串, 占两位
char *data; // 具体字符串
};

1.6.7 9p_stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct uk_9p_stat {
uint16_t size; // 2字节 整个stat所占用的字节数
uint16_t type; // 4字节 类型
uint32_t dev; // 4字节 设备
struct uk_9p_qid qid; // 13字节 即上文的qid
uint32_t mode; // 4字节 权限与标志
uint32_t atime; // 4字节 上次访问的时间
uint32_t mtime; // 4字节 上次修改(modification)的时间
uint64_t length; // 8字节 文件所占用的字节数
struct uk_9p_str name; // s字节 文件名字,如果是根目录则必须是"/"
struct uk_9p_str uid; // s字节 文件所有者的名字
struct uk_9p_str gid; // s字节 组的名字
struct uk_9p_str muid; // s字节 上一次修改的人的名字
struct uk_9p_str extension; //
uint32_t n_uid;
uint32_t n_gid;
uint32_t n_muid;
};

1.6.8 一些定义

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
/** 
* No tag, used by the Tversion/Rversion pair of request/reply messages.
*/
#define UK_9P_NOTAG UINT16_MAX // 用于version相关的请求 16位最大值65535

/**
* Maximum available tag for use.
* UK_9P_NOTAG (~0) is reserved by the 9P RFC for representing no tag.
*/
#define UK_9P_MAXTAG (UINT16_MAX - 1) // 最大可用的tag数量 65535-1=65534

/**
* Number of possible tags, including NOTAG.
*/
#define UK_9P_NUMTAGS ((uint32_t)(UINT16_MAX) + 1) //所有可能的tag数量65536

/**
* No fid, used to mark a fid field as unused.
*/
#define UK_9P_NOFID UINT32_MAX // No fid,用于标记一个fid字段为未使用。

/**
* No n_uname in TATTACH requests, used to mark the field as unused.
*/
#define UK_9P_NONUNAME UINT32_MAX // 在TATTACH请求中没有n_uname,用于标记字段为未使用。

1.7 9preq_fcall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct uk_9preq_fcall {
/* 请求的数据结构
* Total size of the fcall. Initially, this is the buffer size.
* After ready (on xmit) or reply (on recv), this will be the size of
* the sent/received data.
*/
uint32_t size;
/* Type of the fcall. Should be T* for transmit, R* for receive. */
uint8_t type;
/* Offset while serializing or deserializing. */
uint32_t offset;
/* Buffer pointer. */
void *buf;

/* Zero-copy buffer pointer. */
void *zc_buf;
/* Zero-copy buffer size. */
uint32_t zc_size;
/* Zero-copy buffer offset in the 9P message. */
uint32_t zc_offset;
};
  • size:整个fcall的大小,最初是buffer的大小,在传输和接受就绪后将变成发送/接受的消息的大小。
  • type:fcall的类型,T就是发送,R就是接受
  • offset:序列化或者解序列化的偏移量
  • buf:指向9preq中的xmit_buf(发送)/recv_buf(接受)
  • zc_buf:零拷贝的缓冲区
  • zc_size:零拷贝缓冲区的大小
  • zc_offset:在9preq的接受缓冲中的偏移量,即从偏移量开始读入zc

1.8 9preq

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
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 描述一个9P的请求,通过uk_9pdev_req_create()来创建,当没有被引用的时候释放。uk_9pdev_req_remove()可以强制性的释放。
* 整个请求的大小应该限制在一页(4KB)中
* Describes a 9P request.
*
* This gets allocated via uk_9pdev_req_create(), and freed when it is not
* referenced anymore. A call to uk_9pdev_req_remove() is mandatory to
* correctly free this and remove it from the list of requests managed
* by the 9p device.
*
* Should fit within one page.
*/
struct uk_9preq {
/*
* Fixed-size buffer for transmit, used for most messages.
* Large messages will always zero-copy from the user-provided
* buffer (on Twrite).
*/
uint8_t xmit_buf[UK_9P_BUFSIZE];
/*
* Fixed-size buffer for receive, used for most messages.
* Large messages will always zero-copy into the user-provided
* buffer (on Tread).
*/
uint8_t recv_buf[UK_9P_BUFSIZE];
/* 2 KB offset in the structure here. */
/* Transmit fcall. */
struct uk_9preq_fcall xmit;
/* Receive fcall. */
struct uk_9preq_fcall recv;
/* State of the request. See the state enum for details. */
enum uk_9preq_state state;
/* Tag allocated to this request. */
uint16_t tag;
/* Entry into the list of requests (API-internal). */
struct uk_list_head _list;
/* @internal 9P device this request belongs to. */
struct uk_9pdev *_dev;
/* @internal Allocator used to allocate this request. */
struct uk_alloc *_a;
/* Tracks the number of references to this structure. */
__atomic refcount;
  • xmit_buf:发送的buffer缓冲区,大小为1024B,用于大多数message,大message是通过从用户提供的缓冲区零拷贝来的。
  • recv_buf:接受的buffer缓冲区,同发送。这两个缓冲区占了2KB。
  • xmit:用于传输的数据结构
  • recv:用于接收的数据结构
  • state:代表request的状态,分为五种:NONE(刚刚被创建)、INITIALIZED(初始化了——可以开始接受序列化的数据),READY(准备好了——可以发送了),SENT(已经发送到传输层了)、RECEIVED(从传输层接收到信息并且重要的9P信息例如类型、标签、大小都已经被验证了)
  • tag:这个9p request的tag 占2位
  • refcount:引用计数

2. 与生成9preq有关的基础操作

2.1 9preq_init(9preq.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 初始化一个9preq的操作
void uk_9preq_init(struct uk_9preq *req)
{
// 将req下的xmit和recv的buf指针指向req的xmit_buf和recv_buf,即xmit和recv的buf缓冲区指针有了真正的缓冲区
req->xmit.buf = req->xmit_buf;
req->recv.buf = req->recv_buf;

req->xmit.size = req->recv.size = UK_9P_BUFSIZE; // 传输和接受的初始大小都是1024B即1KB
req->xmit.zc_buf = req->recv.zc_buf = NULL; // zc_buf初始都是空
req->xmit.zc_size = req->recv.zc_size = 0; // zc_size初始都是0
req->xmit.zc_offset = req->recv.zc_offset = 0; // zc_offset初始都是0

req->xmit.offset = UK_9P_HEADER_SIZE; // 发送的初始偏移量是7(size[4]+type[1]+tag[2])
req->recv.offset = 0; // 接受的初始偏移量是0,因为服务端那边写总是从最开始写的

UK_INIT_LIST_HEAD(&req->_list);
uk_refcount_init(&req->refcount, 1);
#if CONFIG_LIBUKSCHED
uk_waitq_init(&req->wq);
#endif
}

2.2 request_create(9p.c)->9pdev_req_create(9pdev.c)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 创建一个与9pdev绑定的req, type是9p消息的类型
struct uk_9preq *uk_9pdev_req_create(struct uk_9pdev *dev, uint8_t type)
{
struct uk_9preq *req;
int rc = 0;
uint16_t tag;
unsigned long flags;

UK_ASSERT(dev);

ukplat_spin_lock_irqsave(&dev->_req_mgmt.spinlock, flags); // 上自旋锁
if (!(req = _req_mgmt_from_freelist_locked(&dev->_req_mgmt))) {
ukplat_spin_unlock_irqrestore(&dev->_req_mgmt.spinlock, flags);
req = uk_calloc(dev->a, 1, sizeof(*req));
if (req == NULL) {
rc = -ENOMEM;
goto out;
}
req->_dev = dev; // 将9preq和设备dev进行绑定
req->_a = dev->a;
ukplat_spin_lock_irqsave(&dev->_req_mgmt.spinlock, flags);
}

uk_9preq_init(req); // 初始化9preq


UK_ASSERT(req->_dev == dev);


req->recv.size = MIN(req->recv.size, dev->msize);// 1024B和设备最大的message大小进行取小
req->xmit.size = MIN(req->xmit.size, dev->msize);

if (type == UK_9P_TVERSION)
tag = UK_9P_NOTAG; // 如果是version的req就将tag赋值为notag
else
tag = _req_mgmt_next_tag_locked(&dev->_req_mgmt); // 从位图中找一个还未使用的tag

req->tag = tag; // 赋值tag
req->xmit.type = type; // 发送的type确定

_req_mgmt_add_req_locked(&dev->_req_mgmt, req); // 更新位图、req链表
ukplat_spin_unlock_irqrestore(&dev->_req_mgmt.spinlock, flags);

req->state = UK_9PREQ_INITIALIZED; // 将req的状态更新为初始化完毕了,即可以写具体的request了

return req;

out:
return ERR2PTR(rc);
}

2.3 9preq_{write/read}[size](9preq.h) -> 9preq_{write/readbuf}

1
2
3
4
5
6
7
8
9
10
11
// 将从buf地址开始的size字节长度的信息写入req的发送缓存中
static inline int uk_9preq_writebuf(struct uk_9preq *req, const void *buf,
uint32_t size)
{
if (req->xmit.offset + size > req->xmit.size) // 如果发送的开始写地址加长度大于发送缓冲区的大小就报错
return -ENOBUFS;

memcpy((char *)req->xmit.buf + req->xmit.offset, buf, size); // 拷贝
req->xmit.offset += size; // 缓冲区的开始写地址加信息长度
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
// 从req中的接受缓存中将信息读size大小到buf中
static inline int uk_9preq_readbuf(struct uk_9preq *req, void *buf,
uint32_t size)
{
if (req->recv.offset + size > req->recv.size)
return -ENOBUFS;

memcpy(buf, (char *)req->recv.buf + req->recv.offset, size);
req->recv.offset += size; // 读缓冲区的地址加上大小,即下次读可以从上次读完的地方开始读
return 0;
}

3. 与9pdev相关的基础操作

3.1 9pdev_trans_register(9pdev_trans.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct uk_9pdev_trans *uk_9pdev_trans_saved_default; // 先定义了一个默认的trans

// 注册函数 在低一层的virtio_9p里有调用这个函数,即将trans注册进默认的trans里
int uk_9pdev_trans_register(struct uk_9pdev_trans *trans)
{
UK_ASSERT(trans);
UK_ASSERT(trans->name);
UK_ASSERT(trans->ops);
UK_ASSERT(trans->ops->connect);
UK_ASSERT(trans->ops->disconnect);
UK_ASSERT(trans->ops->request);
UK_ASSERT(trans->a);

uk_list_add_tail(&trans->_list, &uk_9pdev_trans_list);

if (!uk_9pdev_trans_saved_default)
uk_9pdev_trans_saved_default = trans; // 注册进来

uk_pr_info("Registered transport %s\n", trans->name);

return 0;
}

3.2 9pdev_connect(9pdev.c)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 创建一个9pdev,并且这个dev拥有一些传输相关的操作
struct uk_9pdev *uk_9pdev_connect(const struct uk_9pdev_trans *trans,
const char *device_identifier,
const char *mount_args,
struct uk_alloc *a)
{
struct uk_9pdev *dev;
int rc = 0;

UK_ASSERT(trans);
UK_ASSERT(device_identifier);

if (a == NULL)
a = trans->a; // 如果没有内存分配器就直接用trans的分配器

dev = uk_calloc(a, 1, sizeof(*dev)); // 给具体的dev分配内存
if (dev == NULL) {
rc = -ENOMEM;
goto out;
}

dev->ops = trans->ops; // 给dev的操作赋值,即下层实现在这里注册进dev了
dev->a = a; // 给dev的内存分配器

#if CONFIG_LIBUKSCHED
uk_waitq_init(&dev->xmit_wq);
#endif

_req_mgmt_init(&dev->_req_mgmt); // 初始化9preq管理器,即初始化自旋锁、tag位图、加入req链表
_fid_mgmt_init(&dev->_fid_mgmt); // 初始化fid管理器,因为要保证fid唯一,和上述req管理器一样有自旋锁、fid位图、链表

rc = dev->ops->connect(dev, device_identifier, mount_args); // 底层virtio的connect实现,即连接到virtio_9p_dev设备
if (rc < 0)
goto free_dev;

UK_ASSERT(dev->max_msize != 0);
dev->msize = dev->max_msize; // 设置msize为max_msize
dev->state = UK_9PDEV_CONNECTED; // 设置dev的状态为已连接

return dev;

free_dev:
_fid_mgmt_cleanup(&dev->_fid_mgmt);
_req_mgmt_cleanup(&dev->_req_mgmt);
uk_free(a, dev);
out:
return ERR2PTR(rc);
}

4. 9p整体的操作

4.1 send_and_wait_zc(9p.c) -> 9preq_ready(9preq.c)-> 9pdev_request(9pdev.c)-> 9preq_waitreply(9preq.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 构造Tmessage并且发送、等待Rmessage的最主要函数,一共有三个步骤:构造->发送->等待回复
static inline int send_and_wait_zc(struct uk_9pdev *dev, struct uk_9preq *req,
enum uk_9preq_zcdir zc_dir, void *zc_buf, uint32_t zc_size,
uint32_t zc_offset)
{
int rc;

if ((rc = uk_9preq_ready(req, zc_dir, zc_buf, zc_size, zc_offset))) // 准备9preq
return rc;
uk_9p_trace_ready(req->tag);

if ((rc = uk_9pdev_request(dev, req))) // 发送通过9pdev发送9preq
return rc;
uk_9p_trace_sent(req->tag);

if ((rc = uk_9preq_waitreply(req))) // 等待9pdev收到消息
return rc;
uk_9p_trace_received(req->tag);

return 0;
}

4.1.1 9preq_ready(9preq.c)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 构造message,并且准备好缓存区域 zc(零拷贝,即需要读/写的外部缓存区域) 
// zc_dir是这个外部缓冲区的类型,有三种:NONE(即不需要这个缓冲区)、READ(这个缓冲区用来存储读的信息)、WRITE(要用这个缓冲区里面的信息写入到文件里)
// zc_offset: 表示要读入zc或者从zc写出去的时候应该从9preq内部buf的第几位开始读/写入。
// 如果是read读就是11 (size[4]+Rread[1]+tag[2]+count[4] {data[count]} -> 4+1+2+4=11)
// 如果是write写就是23 (size[4]+Twrite[1]+tag[2]+fid[4]+offset[8]+count[4] {data[count]} -> 4+1+2+4+8+4=23)
int uk_9preq_ready(struct uk_9preq *req, enum uk_9preq_zcdir zc_dir,
void *zc_buf, uint32_t zc_size, uint32_t zc_offset)
{
int rc;
uint32_t total_size;
uint32_t total_size_with_zc;

UK_ASSERT(req);

if (UK_READ_ONCE(req->state) != UK_9PREQ_INITIALIZED) // 确保9preq的状态是初始化完毕才能进行构造
return -EIO;

/* 初始化大小信息 */
total_size = req->xmit.offset; // 在发送缓存里有初始的7个字节的头信息加上根据message类型的信息,即(size[4]+type[1]+tag[2]+x[n]),7+n作为初始的总共大小。如果是写的话7+n就是23,和zc_offset相等。

total_size_with_zc = total_size; // 带着zc的总大小,初始和总大小一样,都是7+n字节
if (zc_dir == UK_9PREQ_ZCDIR_WRITE) // 如果是要把zc缓冲区内的信息写入文件的话
total_size_with_zc += zc_size; // 则缓冲区的总大小加上zc缓冲区的大小

/* 将请求头序列化. */
req->xmit.offset = 0; // 将写的偏移量放到0,即从最开始写
// 写最基础的七个字节的请求头
if ((rc = uk_9preq_write32(req, total_size_with_zc)) < 0 || // 写总共的大小size[4],即7+n+(zc_size)
(rc = uk_9preq_write8(req, req->xmit.type)) < 0 || // 写类型type[1]
(rc = uk_9preq_write16(req, req->tag)) < 0) // 写tag[2],在init阶段就已经初始化好了
return rc;

/* Reset offset and size to sane values. */
req->xmit.offset = 0; // 将写的偏移量置0
req->xmit.size = total_size; // 将message的大小设置为7+n(原来是1024)

/* Update zero copy buffers. */
if (zc_dir == UK_9PREQ_ZCDIR_WRITE) { // 如果是将zc缓存中的写入文件的话
req->xmit.zc_buf = zc_buf; // 将发送缓冲中的zc缓冲区与外部zc缓冲区进行绑定,即指针赋值
req->xmit.zc_size = zc_size; // 发送缓冲区的zc缓冲区大小为外部缓冲区的大小
/* Zero-copy offset for writes must start at the end of buf. */
req->xmit.zc_offset = req->xmit.size; // 发送缓冲区的写的偏移量变为7+n,即在头的后面开始写

} else if (zc_dir == UK_9PREQ_ZCDIR_READ) { // 如果是将req缓冲区的文件读入zc缓冲区的话
req->recv.zc_buf = zc_buf; // 将接受缓冲中的zc缓冲区与外部zc缓冲区进行绑定
req->recv.zc_size = zc_size; // 接受缓冲区的zc缓冲区大小为外部缓冲区的大小
req->recv.zc_offset = zc_offset; // 要读的话从req接受缓冲区的第11位开始读入zc缓冲区
/* The receive buffer must end before the zc buf. */
req->recv.size = zc_offset; // req接受缓冲区必须在zc缓冲区之前结束,即size=11
}

/* 更新一下req的状态为READY即可发送状态 */
UK_WRITE_ONCE(req->state, UK_9PREQ_READY);

return 0;
}

4.1.2 9pdev_request(9pdev.c)

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
31
32
33
34
// 发送READY的请求
int uk_9pdev_request(struct uk_9pdev *dev, struct uk_9preq *req)
{
int rc;

UK_ASSERT(dev);
UK_ASSERT(req);

if (UK_READ_ONCE(req->state) != UK_9PREQ_READY) { // 检查9preq是否已经处于READY状态
rc = -EINVAL;
goto out;
}

if (dev->state != UK_9PDEV_CONNECTED) { // 检查9pdev是否已经处于连接状态
rc = -EIO;
goto out;
}

#if CONFIG_LIBUKSCHED
uk_waitq_wait_event(&dev->xmit_wq, // 如果用了调度库的话就放入调度队列中
(rc = dev->ops->request(dev, req)) != -ENOSPC);
#else
do {
/*
* Retry the request while it has no space available on the
* transport layer.
*/
rc = dev->ops->request(dev, req);// 当传输层已经没有空间发送的时候就一直发送直到有位置了,发送方式实现在virtio层
} while (rc == -ENOSPC);
#endif

out:
return rc;
}

4.1.3 9preq_waitreply(9preq.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 等待下层vritio的回复
int uk_9preq_waitreply(struct uk_9preq *req)
{
int rc;

#if CONFIG_LIBUKSCHED
uk_waitq_wait_event(&req->wq, req->state == UK_9PREQ_RECEIVED);
#else
while (UK_READ_ONCE(req->state) != UK_9PREQ_RECEIVED) // 等到9preq的状态到RECEIVED接受了就说明已经完成了
;
#endif

/* Check for 9P server-side errors. */
rc = uk_9preq_error(req); // 检查req是否存在错误,如果存在错误就获取其错误信息。

return rc;
}

4.2 9p_read(9p.c,一个例子)

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
31
32
33
34
35
36
37
38
//	请求:size[4] Tread tag[2] fid[4] offset[8] count[4]
// 回复:size[4] Rread tag[2] count[4] data[count]
// 就是上层9pfs中uk_9pfs的核心实现
// offset是从文件的哪里开始读,count是读的数量,buf是想要读进去的缓存位置
int64_t uk_9p_read(struct uk_9pdev *dev, struct uk_9pfid *fid,
uint64_t offset, uint32_t count, char *buf)
{
struct uk_9preq *req;
int64_t rc;

if (fid->iounit != 0)
count = MIN(count, fid->iounit); // 读的数量不能大于这个fid文件的一次允许的最大量
count = MIN(count, dev->msize - 11); // 读的数量不能大于设备的通信最大量减去11(回复头的大小)

uk_pr_debug("TREAD fid %u offset %lu count %u\n", fid->fid,
offset, count);

req = request_create(dev, UK_9P_TREAD); // 创建并且初始化请求
if (PTRISERR(req))
return PTR2ERR(req);

if ((rc = uk_9preq_write32(req, fid->fid)) || // 把fid写入请求头中
(rc = uk_9preq_write64(req, offset)) || // 把偏移量写入请求头中
(rc = uk_9preq_write32(req, count)) || // 把要读的数量写入请求头中

// 写入最开始的7个请求头(size[4]+type[1]+tag[2]), 发送请求,并且等待回复
(rc = send_and_wait_zc(dev, req, UK_9PREQ_ZCDIR_READ, buf, count, 11)) ||
(rc = uk_9preq_read32(req, &count))) // 将回复的具体读了多少的数量赋值给count
goto out;

uk_pr_debug("RREAD count %u\n", count);

rc = count; // 返回具体读了多少的count

out:
uk_9pdev_req_remove(dev, req);
return rc;
}

5. 9pfs的操作

5.1 9pfs_mount(9pfs_vfsops.c)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 注册为上层的vfsops.vfs_mount,即挂载9p文件系统
static int uk_9pfs_mount(struct mount *mp, const char *dev,
int flags __unused, const void *data)
{
struct uk_9pfs_mount_data *md;
struct uk_9preq *version_req;
struct uk_9p_str rcvd_version;
struct uk_9pfid *rootfid;
int version_accepted;
int rc;

/* 先把挂载根目录对应的目录项的vnode设置为空 */
mp->m_root->d_vnode->v_data = NULL;

/* Allocate mount data, parse options. */
md = malloc(sizeof(*md));
if (!md)
return ENOMEM;

rc = uk_9pfs_parse_options(md, data); // 给md传递传输操作trans参数
if (rc)
goto out_free_mdata;

mp->m_data = md; // 将挂载的私有数据与md进行绑定

/* 与给定的9P端点建立连接, 即把md的传输操作注册进dev里,并且dev会与下层的vritio_dev进行连接*/
md->dev = uk_9pdev_connect(md->trans, dev, data, NULL);
if (PTRISERR(md->dev)) {
rc = -PTR2ERR(md->dev);
goto out_free_mdata;
}

/* 通过9p_version操作可以与9p服务端建立一个会话(session) */
version_req = uk_9p_version(md->dev, uk_9pfs_proto_str[md->proto],
&rcvd_version);
if (PTRISERR(version_req)) {
rc = -PTR2ERR(version_req);
goto out_disconnect;
}

version_accepted = uk_9p_str_equal(&rcvd_version,
uk_9pfs_proto_str[md->proto]);``//检查version是不是和发送时的协议一样
uk_9pdev_req_remove(md->dev, version_req); // 把请求版本的req删除

if (!version_accepted) {
rc = EIO;
uk_pr_warn("Could not negotiate protocol %s\n",
uk_9pfs_proto_str[md->proto]);
goto out_disconnect;
}

/* Create root fid. */
rootfid = uk_9p_attach(md->dev, UK_9P_NOFID, md->uname, // attach操作连接服务器并且创建一个根目录的fid
md->aname, UK_9P_NONUNAME);
if (PTRISERR(rootfid)) {
rc = -PTR2ERR(rootfid);
goto out_disconnect;
}

rc = uk_9pfs_allocate_vnode_data(mp->m_root->d_vnode, rootfid); // 给根目录分配一个与fid绑定的vnode
if (rc != 0) {
rc = -rc;
goto out_disconnect;
}

return 0;

out_disconnect:
uk_9pdev_disconnect(md->dev);
out_free_mdata:
free(md);
return rc;
}

5.2 9pfs_create(9pfs_vnops.c)->9pfs_create_generic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 是上层vops->vop_create的实现,即vnode的创建
// dvp是要创建的文件vnode所在目录的的vnode
static int uk_9pfs_create_generic(struct vnode *dvp, char *name, mode_t mode)
{
struct uk_9pdev *dev = UK_9PFS_MD(dvp->v_mount)->dev;
struct uk_9pfid *fid;
int rc;

if (strlen(name) > NAME_MAX)
return ENAMETOOLONG;

/* Clone parent fid. */
fid = uk_9p_walk(dev, UK_9PFS_VFID(dvp), NULL); // 根据父目录的fid来创建一个fid

rc = uk_9p_create(dev, fid, name, uk_9pfs_perm_from_posix_mode(mode), // 通过mode来创建一个新文件并且与fid关联
UK_9P_OTRUNC | UK_9P_OWRITE, NULL);

uk_9pfid_put(fid);
return -rc;
}

5.3 9pfs_open(9pfs_vnops.c)

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
31
32
33
34
35
36
37
38
39
40
41
42
// 注册为上层的vnops中vop_open = uk_9pfs_open
// 传进来的vfscore_file的目录项已经绑定
static int uk_9pfs_open(struct vfscore_file *file)
{
struct uk_9pdev *dev = UK_9PFS_MD(file->f_dentry->d_mount)->dev; // 获取9pdev
struct uk_9pfid *openedfid; // 创建打开文件的fid指针
struct uk_9pfs_file_data *fd; // 创建给vfscore_file绑定的9pfs_file_data,主要是为了获取其fid
int rc;

/* 给fd分配内存. */
fd = calloc(1, sizeof(*fd));
if (!fd)
return ENOMEM;

/* Clone fid. */
openedfid = uk_9p_walk(dev, UK_9PFS_VFID(file->f_dentry->d_vnode), // 创建一个新fid,这个fid和老vnode的fid指向的是同一个文件,但是这个fid可以代表这个文件的打开的fid
NULL);
if (PTRISERR(openedfid)) {
rc = PTR2ERR(openedfid);
goto out;
}

/* Open cloned fid. */
rc = uk_9p_open(dev, openedfid,
uk_9pfs_open_mode_from_posix_flags(file->f_flags)); // 打开这个新创建的fid,返回的qid表示文件类型、版本等信息

if (rc)
goto out_err;

fd->fid = openedfid; // 这个打开文件的vfscore_file与fid绑定
file->f_data = fd;
UK_9PFS_ND(file->f_dentry->d_vnode)->nb_open_files++; // vnode打开文件的数量加一

return 0;

out_err:
uk_9pfid_put(openedfid);
out:
free(fd);
return -rc;
}

5.4 9pfs_read(9pfs_vnops.c,一个例子)

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
31
32
33
34
35
36
37
38
39
// 通过打开的文件fp、文件的vnode、要写入的缓存uio来读文件
static int uk_9pfs_read(struct vnode *vp, struct vfscore_file *fp,
struct uio *uio, int ioflag __unused)
{
struct uk_9pdev *dev = UK_9PFS_MD(vp->v_mount)->dev; // 找到挂载的设备
struct uk_9pfid *fid = UK_9PFS_FD(fp)->fid; // 找到要读文件的打开文件fd的fid
struct iovec *iov;
int rc;

if (vp->v_type == VDIR)
return EISDIR;
if (vp->v_type != VREG) // 读的文件类型一定要是普通文件
return EINVAL;
if (uio->uio_offset < 0) // 要读的地方要大于0
return EINVAL;
if (uio->uio_offset >= (off_t) vp->v_size) // 要读的地方要小于文件的大小
return 0;

if (!uio->uio_resid) // 要读入的缓存大小不足了
return 0;

iov = uio->uio_iov; // 真正要读入的缓存
while (!iov->iov_len) { // 寻找下一个可用的缓存区域
uio->uio_iov++; // 下一个缓存区域
uio->uio_iovcnt--; // 可用的缓存数量减一
}

rc = uk_9p_read(dev, fid, uio->uio_offset, // 调用下层的具体read实现,即准备req、发送req、接受req等等,返回rc是具体读到的信息大小,iov_len是要读入的长度,iov_base是要读入缓存的地址
iov->iov_len, iov->iov_base);
if (rc < 0)
return -rc;

iov->iov_base = (char *)iov->iov_base + rc; // 缓存的地址加上具体读了多少字符
iov->iov_len -= rc; // 要读入的长度减去真正读的长度
uio->uio_resid -= rc; // 缓存的剩余大小减去长度
uio->uio_offset += rc; // 文件中的指针加上真实读入的长度

return 0;
}