<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; 块大小</title>
	<atom:link href="https://zohead.com/archives/tag/block-size/feed/" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>NFS读写块大小问题分析</title>
		<link>https://zohead.com/archives/nfs-rwsize/</link>
		<comments>https://zohead.com/archives/nfs-rwsize/#comments</comments>
		<pubDate>Sat, 05 Jul 2014 14:44:32 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[NFS]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[UDP]]></category>
		<category><![CDATA[块大小]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=723</guid>
		<description><![CDATA[本文同步自（最佳显示效果请点击）：https://zohead.com/archives/nfs-rwsize/ Linux NFS 客户端在挂载服务器的 NFS 共享时可以使用 rsize 和 wsize 参数指定 NFS 读写的块大小，但实际使用时发现并不完全凑效，下面简单分析一下。 我先在一台 RHEL6 客户端上挂载另一台 RHEL6 服务器上的 NFS 共享： 从上面可以看到不指定 rsize 和 wsize 参数时，默认的读写块大小都是 256KB（rsize=262144），而且使用的是 TCP 协议（proto=tcp）。 下面使用 UDP 协议挂载 NFS 共享： 从结果可以 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>本文同步自（最佳显示效果请点击）：<a href="https://zohead.com/archives/nfs-rwsize/" target="_blank">https://zohead.com/archives/nfs-rwsize/</a></p>
<p>Linux NFS 客户端在挂载服务器的 NFS 共享时可以使用 rsize 和 wsize 参数指定 NFS 读写的块大小，但实际使用时发现并不完全凑效，下面简单分析一下。</p>
<p>我先在一台 RHEL6 客户端上挂载另一台 RHEL6 服务器上的 NFS 共享：</p>
<p><pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# mount -t nfs 192.168.1.122:/nfs/share /mnt/nfs
[root@localhost ~]# grep /mnt/nfs /proc/mounts
192.168.1.122:/nfs/share /mnt/nfs nfs rw,relatime,vers=3,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.122,mountvers=3,mountport=892,mountproto=udp,local_lock=none,addr=192.168.1.122 0 0
</pre>
</p>
<p>从上面可以看到不指定 rsize 和 wsize 参数时，默认的读写块大小都是 256KB（rsize=262144），而且使用的是 TCP 协议（proto=tcp）。</p>
<p>下面使用 UDP 协议挂载 NFS 共享：</p>
<p><pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# mount -t nfs -o udp 192.168.1.122:/nfs/share /mnt/nfs
[root@localhost ~]# grep /mnt/nfs /proc/mounts
192.168.1.122:/nfs/share /mnt/nfs nfs rw,relatime,vers=3,rsize=32768,wsize=32768,namlen=255,hard,proto=udp,timeo=11,retrans=3,sec=sys,mountaddr=192.168.1.122,mountvers=3,mountport=892,mountproto=udp,local_lock=none,addr=192.168.1.122 0 0
</pre>
</p>
<p>从结果可以看出，使用 UDP 协议时块大小就只有 32KB 了。</p>
<p>准备在客户端这边修改 mount 参数将 NFS TCP 方式的读写块大小增加到 1MB：</p>
<p><pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# mount -t nfs -o rsize=1048576,wsize=1048576 192.168.1.122:/nfs/share /mnt/nfs
[root@localhost ~]# grep /mnt/nfs /proc/mounts
192.168.1.122:/nfs/share /mnt/nfs nfs rw,relatime,vers=3,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.122,mountvers=3,mountport=892,mountproto=udp,local_lock=none,addr=192.168.1.122 0 0
</pre>
</p>
<p>但从上面的结果来看，实际使用的块大小还是 256KB。</p>
<p>在客户端这边修改 mount 参数将 NFS UDP 方式的读写块大小增加到 256KB：</p>
<p><pre class="brush: bash; title: ; notranslate">
[root@localhost ~]# mount -t nfs -o udp,rsize=262144,wsize=262144 192.168.1.122:/nfs/share /mnt/nfs
[root@localhost ~]# grep /mnt/nfs /proc/mounts
192.168.1.122:/nfs/share /mnt/nfs nfs rw,relatime,vers=3,rsize=32768,wsize=32768,namlen=255,hard,proto=udp,timeo=11,retrans=3,sec=sys,mountaddr=192.168.1.122,mountvers=3,mountport=892,mountproto=udp,local_lock=none,addr=192.168.1.122 0 0
</pre>
</p>
<p>UDP 模式下读写的块大小也无法修改，客户端似乎已经使用了最大的读写块大小。</p>
<p>没办法，下面来看看 Linux kernel 源代码，找出真正的原因，先在 include/linux/nfs_xdr.h 文件中找到了 NFS I/O 块大小的定义：</p>
<p><pre class="brush: cpp; title: include/linux/nfs_xdr.h; notranslate">
/*
 * To change the maximum rsize and wsize supported by the NFS client, adjust
 * NFS_MAX_FILE_IO_SIZE.  64KB is a typical maximum, but some servers can
 * support a megabyte or more.  The default is left at 4096 bytes, which is
 * reasonable for NFS over UDP.
 */
#define NFS_MAX_FILE_IO_SIZE    (1048576U)
#define NFS_DEF_FILE_IO_SIZE    (4096U)
#define NFS_MIN_FILE_IO_SIZE    (1024U)
</pre>
</p>
<p>这里可以看到 NFS 默认使用 4KB 块大小，客户端实际挂载时会做调整，最小 1KB，最大 1MB。</p>
<p>NFS 客户端在挂载时会与 NFS 服务器协商适合的读写块大小值，我们来看看 fs/nfs/client.c 文件中协商设置 NFS 文件系统信息的代码：</p>
<p><pre class="brush: cpp; highlight: [0,11,12,13,14]; title: fs/nfs/client.c; notranslate">
static void nfs_server_set_fsinfo(struct nfs_server *server, struct nfs_fsinfo *fsinfo)
{
	unsigned long max_rpc_payload;

	/* Work out a lot of parameters */
	if (server-&gt;rsize == 0)
		server-&gt;rsize = nfs_block_size(fsinfo-&gt;rtpref, NULL);
	if (server-&gt;wsize == 0)
		server-&gt;wsize = nfs_block_size(fsinfo-&gt;wtpref, NULL);

	if (fsinfo-&gt;rtmax &gt;= 512 &amp;&amp; server-&gt;rsize &gt; fsinfo-&gt;rtmax)
		server-&gt;rsize = nfs_block_size(fsinfo-&gt;rtmax, NULL);
	if (fsinfo-&gt;wtmax &gt;= 512 &amp;&amp; server-&gt;wsize &gt; fsinfo-&gt;wtmax)
		server-&gt;wsize = nfs_block_size(fsinfo-&gt;wtmax, NULL);

	max_rpc_payload = nfs_block_size(rpc_max_payload(server-&gt;client), NULL);
	if (server-&gt;rsize &gt; max_rpc_payload)
		server-&gt;rsize = max_rpc_payload;
	if (server-&gt;rsize &gt; NFS_MAX_FILE_IO_SIZE)
		server-&gt;rsize = NFS_MAX_FILE_IO_SIZE;
	server-&gt;rpages = (server-&gt;rsize + PAGE_CACHE_SIZE - 1) &gt;&gt; PAGE_CACHE_SHIFT;

	server-&gt;backing_dev_info.name = &quot;nfs&quot;;
	server-&gt;backing_dev_info.ra_pages = server-&gt;rpages * NFS_MAX_READAHEAD;

	if (server-&gt;wsize &gt; max_rpc_payload)
		server-&gt;wsize = max_rpc_payload;
	if (server-&gt;wsize &gt; NFS_MAX_FILE_IO_SIZE)
		server-&gt;wsize = NFS_MAX_FILE_IO_SIZE;
	/*......*/
}
</pre>
</p>
<p>从上面 nfs_server_set_fsinfo 函数的代码可以看到 NFS 客户端实际参考了服务器返回的 rtmax 和 wtmax 值，而这个值可以在挂载 NFS 文件系统时用抓包工具看到（NFS 使用的 RPC 协议）。</p>
<p>下面的图片中显示的就是 NFS 客户端中指定 rsize 和 wsize 参数为 1MB 时 Wireshark 上抓到的 NFS FSINFO 请求的实际数据；</p>
<div style="width: 513px" class="wp-caption alignnone"><a href="http://zohead.com/wp-content/uploads/nfs-mount-cap.jpg" target="_blank"><img alt="挂载NFS的网络抓包" src="http://zohead.com/wp-content/uploads/nfs-mount-cap.jpg" width="503" height="231" /></a><p class="wp-caption-text">挂载NFS的网络抓包</p></div>
<p>上面图片里小椭圆圈表示的是 NFS FSINFO 请求，大椭圆圈里就是服务器传过来的 rtmax 和 wtmax 值了，我们可以看到值就是 256KB。这样也就能解释了为什么客户端增大 NFS 读写块大小也不起作用了。</p>
<p>我们后台登陆到 NFS 服务器上，可以从 /proc/fs/nfsd/max_block_size 文件中看到当前 NFS 服务器的最大块大小，然后尝试修改它：</p>
<p><pre class="brush: bash; title: ; notranslate">
~ # cat /proc/fs/nfsd/max_block_size
262144
~ # echo 524288 &gt; /proc/fs/nfsd/max_block_size
~ # cat /proc/fs/nfsd/max_block_size
262144
</pre>
</p>
<p>可以看到当前 NFS 服务器的最大读写块大小确实是 256KB，但是我们想修改它的值的时候，却似乎又修改不了。这样只能再看看修改 max_block_size 的 kernel 源代码了，对应的代码在 nfsd/nfsctl.c 文件中：</p>
<p><pre class="brush: cpp; highlight: [14,15,18]; title: nfsd/nfsctl.c; notranslate">
static ssize_t write_maxblksize(struct file *file, char *buf, size_t size)
{
	char *mesg = buf;
	if (size &gt; 0) {
		int bsize;
		int rv = get_int(&amp;mesg, &amp;bsize);
		if (rv)
			return rv;
		/* force bsize into allowed range and
		 * required alignment.
		 */
		if (bsize &lt; 1024)
			bsize = 1024;
		if (bsize &gt; NFSSVC_MAXBLKSIZE)
			bsize = NFSSVC_MAXBLKSIZE;
		bsize &amp;= ~(1024-1);
		mutex_lock(&amp;nfsd_mutex);
		if (nfsd_serv &amp;&amp; nfsd_serv-&gt;sv_nrthreads) {
			mutex_unlock(&amp;nfsd_mutex);
			return -EBUSY;
		}
		nfsd_max_blksize = bsize;
		mutex_unlock(&amp;nfsd_mutex);
	}

	return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, &quot;%d\n&quot;,
							nfsd_max_blksize);
}
</pre>
</p>
<p>write_maxblksize 函数中判断了传入的参数，如果写入的值超过 NFSSVC_MAXBLKSIZE 值则固定为 NFSSVC_MAXBLKSIZE 值，那我们来看看 NFSSVC_MAXBLKSIZE 的定义：</p>
<p><pre class="brush: cpp; title: linux/nfsd/const.h; notranslate">
/*
 * Maximum blocksizes supported by daemon under various circumstances.
 */
#define NFSSVC_MAXBLKSIZE   RPCSVC_MAXPAYLOAD
/* NFSv2 is limited by the protocol specification, see RFC 1094 */
#define NFSSVC_MAXBLKSIZE_V2    (8*1024)
</pre>
</p>
<p>linux/nfsd/const.h 中 NFSSVC_MAXBLKSIZE 定义为了 RPCSVC_MAXPAYLOAD 的值，那看看 linux/sunrpc/svc.h 中 RPCSVC_MAXPAYLOAD 的实际值：</p>
<p><pre class="brush: cpp; title: linux/sunrpc/svc.h; notranslate">
/*
 * Maximum payload size supported by a kernel RPC server.
 * This is use to determine the max number of pages nfsd is
 * willing to return in a single READ operation.
 *
 * These happen to all be powers of 2, which is not strictly
 * necessary but helps enforce the real limitation, which is
 * that they should be multiples of PAGE_CACHE_SIZE.
 *
 * For UDP transports, a block plus NFS,RPC, and UDP headers
 * has to fit into the IP datagram limit of 64K.  The largest
 * feasible number for all known page sizes is probably 48K,
 * but we choose 32K here.  This is the same as the historical
 * Linux limit; someone who cares more about NFS/UDP performance
 * can test a larger number.
 *
 * For TCP transports we have more freedom.  A size of 1MB is
 * chosen to match the client limit.  Other OSes are known to
 * have larger limits, but those numbers are probably beyond
 * the point of diminishing returns.
 */
#define RPCSVC_MAXPAYLOAD   (1*1024*1024u)
#define RPCSVC_MAXPAYLOAD_TCP   RPCSVC_MAXPAYLOAD
#define RPCSVC_MAXPAYLOAD_UDP   (32*1024u)
</pre>
</p>
<p>从 linux/sunrpc/svc.h 中可以看到 NFS 读写块大小必须为 2 的幂，这样也大概知道读写块大小限制的原因了：</p>
<p>对于 UDP 来说，由于一个 UDP 包最大才 64KB，因此使用 UDP 协议的 NFS 读写块大小最大不超过 48KB，而 kernel 中则直接限制为 32KB 了；而使用 TCP 协议的 NFS 由于没有这个限制允许更大的读写块大小，但 Linux kernel 还是将其限制为 1MB 了。</p>
<p>至于 max_block_size 值不能直接修改的现象也找到原因了，在 nfsd/nfsctl.c 文件中高亮显示的第 18 行代码里判断了 NFS 服务器是否在启动运行，如果在运行则不允许修改。</p>
<p>下面就好办了，先卸载 NFS 共享的挂载，停止服务器的 NFS 服务，修改 max_block_size 值，然后重新启动 NFS 服务，</p>
<p><pre class="brush: bash; title: ; notranslate">
~ # service nfs stop
Shutting down NFS mountd:                                  [  OK  ]
Shutting down NFS daemon:                                  [  OK  ]
Shutting down NFS services:                                [  OK  ]
~ # echo 1048576 &gt; /proc/fs/nfsd/max_block_size
~ # cat /proc/fs/nfsd/max_block_size
1048576
~ # service nfs start
</pre>
</p>
<p>可以看到现在 NFS 的最大块大小可以修改了，接着在客户端中指定读写块大小并重新挂载 NFS 共享，这个时候客户端也能正确使用更大的块大小了：</p>
<p><pre class="brush: bash; title: ; notranslate">
[root@localhost fs]# mount -t nfs -o rsize=1048576,wsize=1048576 192.168.1.122:/nfs/share /mnt/nfs
[root@localhost fs]# grep /mnt/nfs /proc/mounts
192.168.1.122:/nfs/share /mnt/nfs nfs rw,relatime,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.122,mountvers=3,mountport=892,mountproto=udp,local_lock=none,addr=192.168.1.122 0 0
</pre>
</p>
<p>如果要深究一下 NFS 服务器初始的最大块大小只有 256KB 的原因，可以看看 kernel 中 nfsd/nfssvc.c 文件中的代码：</p>
<p><pre class="brush: cpp; highlight: [13,20,22,23,24]; title: nfsd/nfssvc.c; notranslate">
int nfsd_create_serv(void)
{
	int err = 0;

	WARN_ON(!mutex_is_locked(&amp;nfsd_mutex));
	if (nfsd_serv) {
		svc_get(nfsd_serv);
		return 0;
	}
	if (nfsd_max_blksize == 0) {
		/* choose a suitable default */
		struct sysinfo i;
		si_meminfo(&amp;i);
		/* Aim for 1/4096 of memory per thread
		 * This gives 1MB on 4Gig machines
		 * But only uses 32K on 128M machines.
		 * Bottom out at 8K on 32M and smaller.
		 * Of course, this is only a default.
		 */
		nfsd_max_blksize = NFSSVC_MAXBLKSIZE;
		i.totalram &lt;&lt;= PAGE_SHIFT - 12;
		while (nfsd_max_blksize &gt; i.totalram &amp;&amp;
		       nfsd_max_blksize &gt;= 8*1024*2)
			nfsd_max_blksize /= 2;
	}

	nfsd_serv = svc_create_pooled(&amp;nfsd_program, nfsd_max_blksize,
				      nfsd_last_thread, nfsd, THIS_MODULE);
	if (nfsd_serv == NULL)
		err = -ENOMEM;
	else
		set_max_drc();

	do_gettimeofday(&amp;nfssvc_boot);		/* record boot time */
	return err;
}
</pre>
</p>
<p>从上面的可以看到，NFS 服务器在决定默认的最大读写块大小时考虑到内存占用情况，每个 NFS 内核线程最多只使用 1/4096 的物理内存大小，对于物理内存超过 4GB 的机器才使用最大的 1MB 读写块大小。</p>
<p>来看看我们使用的 NFS 服务器的内存情况，可以看到服务器只使用了 2GB 的内存：</p>
<p><pre class="brush: bash; title: ; notranslate">
~ # free
             total       used       free     shared    buffers     cached
Mem:       2040272     335476    1704796          0       2360      70076
-/+ buffers/cache:     263040    1777232
Swap:            0          0          0
</pre>
</p>
<p>按照 nfsd/nfssvc.c 文件中的代码，i.totalram 实际值为所有可用的物理内存的页数量，我们这里就是 2040272 / 4KB（默认的 PAGE_SIZE 页大小），按照高亮的第 22 - 24 行代码计算出来的默认最大块大小值就是 262144 了。</p>
<p>本文为个人经过测试及分析 Linux kernel 源代码得出的分析结果，如果其中存在错误还请提出指正哦~~~ ^_^</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/nfs-rwsize/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
