NFS 协议
Tags: nfs
Published: 2023-03-31 02:00:00

# NFS

## NFS协议简介

目前总共有4个大版本，v1至v4，从v2开始开放。

以v3为例，其中主要分为3个部分

- 文件系统挂载
- 文件系统访问
- 文件锁

### v4的组合协议 COMPOUND

两种RPC procedures：

- NULL：空流程、什么也不做
- COMPOUND：组合流程，包含一系列NFS操作

对于一次NFS协议请求，它会包含多个原子操作，即COMPOUND。其结构如下

```
+-----+--------------+--------+-----------+-----------+-----------+----
| tag | minorversion | numops | op + args | op + args | op + args | ...
+-----+--------------+--------+-----------+-----------+-----------+----
```

对应的reply信息格式如下：

```
+------------+-----+--------+-----------------------+----
|last status | tag | numres | op + retVal + results | ...
+------------+-----+--------+-----------------------+----
```

具体可以通过wireshark抓包查看。

对于一个COMPUND请求，server会按照请求内的OPcode一个一个执行操作，如果执行到一个操作返回了一个非OK的status code，即non-zero status code，那么整个流程会停止，并直接返回。

一个COMPUND请求无法保证想事务一样拥有原子性，所以需要尽量避免构造过于复杂的COMPUND请求。

所有的COMPUND请求都是同步的，一个操作做完才会做下一个。

## client-side caching

为了提高访问速度，NFS会在客户端设置缓存，但是需要做额外的事情来确保缓存和远端数据的一致性。

原有的一致性机制比较落后：客户端不定期地检查文件的本地缓存是否还有效，如果一个文件只被一个客户端访问时，这种检查时很多余的。

### Data Caching

由于NFS提供了文件共享和文件锁的机制，客户端的缓存不能对这些机制产生影响，所以，NFS客户端不能对上层的应用提供缓存数据的相关接口，换句话说，上层的应用不应感知客户端缓存的存在。

### Delegation 机制

为了减少多余的缓存一致性检查，NFSv4引入了delegation机制，这种机制的基本原理是客户端A打开文件时服务器给客户端A颁发一个凭证，只要客户端A持有这个凭证就可以认为没有与其他客户端发生冲突，读写操作中就不需要再去check cache validity。当客户端B试图访问同一个文件时，这两个客户端就发生了冲突。服务器先暂时不响应客户端B的请求，而是向客户端A发送RECALL请求。客户端A接收到RECALL请求后将缓存中的数据刷新到服务器中，然后将凭证交还给服务器。这时服务器再响应客户端B的请求，以后两个客户端就按照之前的老方法检查自己的本地缓存是否有冲突。

由于在出现多客户端访问的情况下还是用的原始的一致性检查方法，所以delegation机制适用只读场景和客户端对数据修改不频繁的场景。

#### 正向通道和反向通道

为了实现delegation机制，NFS客户端和服务器之间会建立两条RPC通道，一条为正向通道，即客户端发送RPC请求，服务器进行处理；另一条则是delegation需要使用的反向通道，即服务器检测到文件冲突后，向客户端发送RPC请求，客户端进行处理（将缓存刷新到客户端，并返还delegation凭证）。

#### Open Delegation

总共涉及两个操作：

- OPEN_DELEGATE_READ
- OPEN_DELEGATE_WRITE（Linux不支持）

当 client 持有 OPEN_DELEGATE_READ 时，可以保证对应的文件没有被其他 client 通过 OPEN4_SHARE_ACCESS_WRITE/OPEN4_SHARE_ACCESS_BOTH 打开。

当 client 持有 OPEN_DELEGATE_READ 时，自己仍然可以对文件进行加锁操作，但是需要将对应的操作发送到server，不能本地执行。

Delegation的颁发完全取决于server，一些必要的条件如下：

- 与client的反向通道有效
- 与现有的OPEN、delegation、锁没有冲突

```C
/*
 * Attempt to hand out a delegation.
 *
 * Note we don't support write delegations, and won't until the vfs has
 * proper support for them.
 */
static void
nfs4_open_delegation(struct svc_fh *fh, struct nfsd4_open *open,
			struct nfs4_ol_stateid *stp)
{
	struct nfs4_delegation *dp;
	struct nfs4_openowner *oo = openowner(stp->st_stateowner);
	struct nfs4_client *clp = stp->st_stid.sc_client;
	int cb_up;
	int status = 0;

	cb_up = nfsd4_cb_channel_good(oo->oo_owner.so_client);
	open->op_recall = 0;
	switch (open->op_claim_type) {
		case NFS4_OPEN_CLAIM_PREVIOUS:
			if (!cb_up)
				open->op_recall = 1;
			if (open->op_delegate_type != NFS4_OPEN_DELEGATE_READ)
				goto out_no_deleg;
			break;
		case NFS4_OPEN_CLAIM_NULL:
		case NFS4_OPEN_CLAIM_FH:
			/*
			 * Let's not give out any delegations till everyone's
			 * had the chance to reclaim theirs, *and* until
			 * NLM locks have all been reclaimed:
			 */
			if (locks_in_grace(clp->net))
				goto out_no_deleg;
			if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))
				goto out_no_deleg;
			break;
		default:
			goto out_no_deleg;
	}
	dp = nfs4_set_delegation(clp, fh, stp->st_stid.sc_file, stp->st_clnt_odstate);
	if (IS_ERR(dp))
		goto out_no_deleg;

	memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));

	trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
	open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
	nfs4_put_stid(&dp->dl_stid);
	return;
out_no_deleg:
	open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE;
	if (open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS &&
	    open->op_delegate_type != NFS4_OPEN_DELEGATE_NONE) {
		dprintk("NFSD: WARNING: refusing delegation reclaim\n");
		open->op_recall = 1;
	}

	/* 4.1 client asking for a delegation? */
	if (open->op_deleg_want)
		nfsd4_open_deleg_none_ext(open, status);
	return;
}
```



### 文件属性缓存

一个文件的所有属性都是作为一个整体来缓存，不是只缓存部分；client维护一个缓存保鲜时间，超过时间需要向server确认有没有改变发生；client向server发送改变属性的请求，必须再附带一个GETATTR操作，用于更新本地的缓存。

文件属性的缓存基本都属write through的，除了以下三个：

- size: The size of the object in bytes
- time_modify: The time of last modification to the object.
- change: A value created by the server that the client can use to determine if file data, directory contents, or attributes of the object have been modified. 可以简单把它设成文件元数据的最近修改时间，即time_metadata

以上三个属性与文件内容的修改相关，如果把它们的缓存也设为write through，意味着文件data缓存也必须是write through。

**client如何校验自己缓存的文件属性？**

client 向服务器索取 change 和 time_access 两个属性，通过与自己缓存的 change 属性确定当前的属性缓存是否有效。获取 time_access 是为了更新自己本地缓存的该属性。

client 自己访问本地缓存的对象时，不能更新 time_access 属性，因为它是直写的，并会影响 server 上的 change 属性。

### 文件名缓存

客户端可以缓存LOOKUP和READDIR的结果，对其设置一个过期时间。如果client对目录进行了修改，则需要通过change属性来决定被修改的目录是否被其他人修改过（有没有冲突）



### 遗留问题

1. filehandle啥时候与fsid多对一?
2. fsid是如何保证全局唯一的？
3. 有冲突，本客户端咋处理?其它客户端咋处理？
4. 获取锁为啥要检查缓存有效性?
5. 啥时候反向通多会无效?
6. 如果确定file system的刷新频率和time_metadata的resolution？ 如果比time_metadata快咋处理?
7. nfs的cache和vfs的cache有关系吗?
8. cache缓存的粒度是啥，文件？？？
9. 梳理下代码里面，cache提供的接口
10. 除了delegation场景外，其它场景如何保证数据的一致性?



#### 获取锁为啥要检查缓存有效性?

对一段数据加锁意味着这段数据不会被别人修改，client可以把它当成是自己的，可以无顾虑的使用自己的本地缓存。如果自己本地的缓存与服务器上的不一致，那么。

但是，文档里也指出：客户端不禁用cache的话，文件范围锁提供的一致性可能不会生效。

#### filehandle啥时候多对一?

文件句柄是解析路径的时候生成的，所以存在两个文件句柄指向同一个文件系统对象情况，也就是存在同一个客户端内，同一个文件可能有两份缓存，同一个客户端内部甚至都能发生缓存不一致。

#### 如果确定file system的刷新频率和time_metadata的resolution？ 如果比time_metadata快咋处理?

Linux的NFS代码里对change的种类有以下几种定义：

```C
enum nfs4_change_attr_type {
	NFS4_CHANGE_TYPE_IS_MONOTONIC_INCR = 0,
	NFS4_CHANGE_TYPE_IS_VERSION_COUNTER = 1,
	NFS4_CHANGE_TYPE_IS_VERSION_COUNTER_NOPNFS = 2,
	NFS4_CHANGE_TYPE_IS_TIME_METADATA = 3,
	NFS4_CHANGE_TYPE_IS_UNDEFINED = 4,
};
```

编号为3的就是使用time_metadata

前三种类似于使用一个递增的counter

最后一种是未定义，相当于是一个默认值？

前4种都可以通过比较大小来决定数据的新旧，越大越新，而最后一种只能比较是否相等

所以，如果file system的刷新频率比time_metadata的单位更快，那么就可以用前面的递增计数器来实现change属性。

#### nfs的cache和vfs的cache有关系吗？NFS cache缓存的粒度是啥？

VFS的cache就是NFS的客户端cache。缓存的粒度是page，一个page归属于一个inode。客户端在进行read操作时，会先检查缓存的数据是否有效，如果无效则删除与缓存数据的mapping，然后调用vfs的读接口更新缓存数据。

#### 除了delegation场景外，其它场景如何保证数据的一致性?

仅通过现有的client side caching机制无法完全保证数据一致性，在多个客户端访问同一个文件时，需要使用文件锁来实现一致性控制。

[delegation机制_ycnian的博客-CSDN博客_nfs delegation](https://blog.csdn.net/ycnian/article/details/8557841)

[分发delegation_ycnian的博客-CSDN博客](https://blog.csdn.net/ycnian/article/details/8624209)

[返还delegation_ycnian的博客-CSDN博客](https://blog.csdn.net/ycnian/article/details/8636126)