-
Notifications
You must be signed in to change notification settings - Fork 0
/
Linux文件读写机制及优化方式.html
282 lines (256 loc) · 20 KB
/
Linux文件读写机制及优化方式.html
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="littlewhite" />
<meta name="copyright" content="littlewhite" />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary">
<meta name="keywords" content="linux, Skill, " />
<meta property="og:title" content="Linux文件读写机制及优化方式 "/>
<meta property="og:url" content="https://chukeer.github.io/Linux文件读写机制及优化方式.html" />
<meta property="og:description" content="本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只是做了不同封装。以下所有测试均使用open, read, write这一套系统api 缓存¶ 缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用read,write都去直接操作磁盘,一方面速度会被限制,一方面也会降低磁盘使用寿命,因此不管是对磁盘的读操作还是写操作,操作系统都会将数据缓存起来 Page Cache¶ 页缓存(Page Cache)是位于内存和文件之间的缓冲区,它实际上也是一块内存区域,所有的文件IO(包括网络文件)都是直接和页缓存交互,操作系统通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别,这些具体数据结构及之间的关系我们暂且不讨论,只需知道页缓存的存在以及它在文件IO中扮演着重要角色,很大一部分程度上,文件读写的优化就是对页缓存使用的优化 Dirty Page¶ 页缓存对应文件中的一块区域,如果页缓存和对应的文件区域内容不一致,则该页缓存叫做脏页(Dirty Page)。对页缓存进行修改或者新建页缓存,只要没有刷磁盘,都会产生脏页 ..." />
<meta property="og:site_name" content="楚客" />
<meta property="og:article:author" content="littlewhite" />
<meta property="og:article:published_time" content="2016-09-19T00:00:00+08:00" />
<meta property="" content="2016-09-19T00:00:00+08:00" />
<meta name="twitter:title" content="Linux文件读写机制及优化方式 ">
<meta name="twitter:description" content="本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只是做了不同封装。以下所有测试均使用open, read, write这一套系统api 缓存¶ 缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用read,write都去直接操作磁盘,一方面速度会被限制,一方面也会降低磁盘使用寿命,因此不管是对磁盘的读操作还是写操作,操作系统都会将数据缓存起来 Page Cache¶ 页缓存(Page Cache)是位于内存和文件之间的缓冲区,它实际上也是一块内存区域,所有的文件IO(包括网络文件)都是直接和页缓存交互,操作系统通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别,这些具体数据结构及之间的关系我们暂且不讨论,只需知道页缓存的存在以及它在文件IO中扮演着重要角色,很大一部分程度上,文件读写的优化就是对页缓存使用的优化 Dirty Page¶ 页缓存对应文件中的一块区域,如果页缓存和对应的文件区域内容不一致,则该页缓存叫做脏页(Dirty Page)。对页缓存进行修改或者新建页缓存,只要没有刷磁盘,都会产生脏页 ...">
<title>Linux文件读写机制及优化方式 · 楚客
</title>
<!--
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.1/css/font-awesome.css" rel="stylesheet">
--!>
<link href="https://chukeer.github.io/theme/css/bootstrap-combined.min.css" rel="stylesheet">
<link href="https://chukeer.github.io/theme/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://chukeer.github.io/theme/css/pygments.css" media="screen">
<link rel="stylesheet" type="text/css" href="https://chukeer.github.io/theme/tipuesearch/tipuesearch.css" media="screen">
<link rel="stylesheet" type="text/css" href="https://chukeer.github.io/theme/css/elegant.css" media="screen">
<link rel="stylesheet" type="text/css" href="https://chukeer.github.io/theme/css/custom.css" media="screen">
</head>
<body>
<div id="content-sans-footer">
<div class="navbar navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="https://chukeer.github.io/"><span class=site-name>楚客</span></a>
<div class="nav-collapse collapse">
<ul class="nav pull-right top-menu">
<li ><a href="https://chukeer.github.io">Home</a></li>
<li ><a href="https://chukeer.github.io/categories.html">Categories</a></li>
<li ><a href="https://chukeer.github.io/tags.html">Tags</a></li>
<li ><a href="https://chukeer.github.io/archives.html">Archives</a></li>
<li><form class="navbar-search" action="https://chukeer.github.io/search.html" onsubmit="return validateForm(this.elements['q'].value);"> <input type="text" class="search-query" placeholder="Search" name="q" id="tipue_search_input"></form></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span1"></div>
<div class="span10">
<article>
<div class="row-fluid">
<header class="page-header span10 offset2">
<h1><a href="https://chukeer.github.io/Linux文件读写机制及优化方式.html"> Linux文件读写机制及优化方式 </a></h1>
</header>
</div>
<div class="row-fluid">
<div class="span8 offset2 article-content">
<p>本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只是做了不同封装。以下所有测试均使用open, read, write这一套系统api</p>
<h2 id="_1">缓存<a class="headerlink" href="#_1" title="Permanent link">¶</a></h2>
<p>缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机内存和磁盘,内存操作速度远远大于磁盘,如果每次调用read,write都去直接操作磁盘,一方面速度会被限制,一方面也会降低磁盘使用寿命,因此不管是对磁盘的读操作还是写操作,操作系统都会将数据缓存起来</p>
<h3 id="page-cache">Page Cache<a class="headerlink" href="#page-cache" title="Permanent link">¶</a></h3>
<p>页缓存(Page Cache)是位于内存和文件之间的缓冲区,它实际上也是一块内存区域,所有的文件IO(包括网络文件)都是直接和页缓存交互,操作系统通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别,这些具体数据结构及之间的关系我们暂且不讨论,只需知道页缓存的存在以及它在文件IO中扮演着重要角色,很大一部分程度上,文件读写的优化就是对页缓存使用的优化</p>
<h3 id="dirty-page">Dirty Page<a class="headerlink" href="#dirty-page" title="Permanent link">¶</a></h3>
<p>页缓存对应文件中的一块区域,如果页缓存和对应的文件区域内容不一致,则该页缓存叫做脏页(Dirty Page)。对页缓存进行修改或者新建页缓存,只要没有刷磁盘,都会产生脏页</p>
<h3 id="_2">查看页缓存大小<a class="headerlink" href="#_2" title="Permanent link">¶</a></h3>
<p>linux上有两种方式查看页缓存大小,一种是free命令</p>
<div class="highlight"><pre><span></span> $ free
total used free shared buffers cached
Mem: 20470840 1973416 18497424 164 270208 1202864
-/+ buffers/cache: 500344 19970496
Swap: 0 0 0
</pre></div>
<p>cached那一列就是页缓存大小,单位Byte</p>
<p>另一种是直接查看/proc/meminfo,这里我们只关注两个字段</p>
<div class="highlight"><pre><span></span><span class="n">Cached</span><span class="o">:</span> <span class="mi">1202872</span> <span class="n">kB</span>
<span class="n">Dirty</span><span class="o">:</span> <span class="mi">52</span> <span class="n">kB</span>
</pre></div>
<p>Cached是页缓存大小,Dirty是脏页大小</p>
<h3 id="_3">脏页回写参数<a class="headerlink" href="#_3" title="Permanent link">¶</a></h3>
<p>Linux有一些参数可以改变操作系统对脏页的回写行为</p>
<div class="highlight"><pre><span></span> $ sysctl -a 2>/dev/null | grep dirty
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 0
vm.dirty_ratio = 20
vm.dirty_bytes = 0
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 3000
</pre></div>
<p><strong>vm.dirty_background_ratio</strong>是内存可以填充脏页的百分比,当脏页总大小达到这个比例后,系统后台进程就会开始将脏页刷磁盘(vm.dirty_background_bytes类似,只不过是通过字节数来设置)</p>
<p><strong>vm.dirty_ratio</strong>是绝对的脏数据限制,内存里的脏数据百分比不能超过这个值。如果脏数据超过这个数量,新的IO请求将会被阻挡,直到脏数据被写进磁盘</p>
<p><strong>vm.dirty_writeback_centisecs</strong>指定多长时间做一次脏数据写回操作,单位为百分之一秒</p>
<p><strong>vm.dirty_expire_centisecs</strong>指定脏数据能存活的时间,单位为百分之一秒,比如这里设置为30秒,在操作系统进行写回操作时,如果脏数据在内存中超过30秒时,就会被写回磁盘</p>
<p>这些参数可以通过<code>sudo sysctl -w vm.dirty_background_ratio=5</code>这样的命令来修改,需要root权限,也可以在root用户下执行<code>echo 5 > /proc/sys/vm/dirty_background_ratio</code>来修改</p>
<h2 id="_4">文件读写流程<a class="headerlink" href="#_4" title="Permanent link">¶</a></h2>
<p>在有了页缓存和脏页的概念后,我们再来看文件的读写流程</p>
<h3 id="_5">读文件<a class="headerlink" href="#_5" title="Permanent link">¶</a></h3>
<ul>
<li>用户发起read操作</li>
<li>操作系统查找页缓存<ul>
<li>若未命中,则产生缺页异常,然后创建页缓存,并从磁盘读取相应页填充页缓存</li>
<li>若命中,则直接从页缓存返回要读取的内容</li>
</ul>
</li>
<li>用户read调用完成</li>
</ul>
<h3 id="_6">写文件<a class="headerlink" href="#_6" title="Permanent link">¶</a></h3>
<ul>
<li>用户发起write操作</li>
<li>操作系统查找页缓存<ul>
<li>若未命中,则产生缺页异常,然后创建页缓存,将用户传入的内容写入页缓存</li>
<li>若命中,则直接将用户传入的内容写入页缓存</li>
</ul>
</li>
<li>用户write调用完成</li>
<li>页被修改后成为脏页,操作系统有两种机制将脏页写回磁盘<ul>
<li>用户手动调用fsync()</li>
<li>由pdflush进程定时将脏页写回磁盘</li>
</ul>
</li>
</ul>
<p>页缓存和磁盘文件是有对应关系的,这种关系由操作系统维护,对页缓存的读写操作是在内核态完成,对用户来说是透明的</p>
<h2 id="_7">文件读写的优化思路<a class="headerlink" href="#_7" title="Permanent link">¶</a></h2>
<p>不同的优化方案适应于不同的使用场景,比如文件大小,读写频次等,这里我们不考虑修改系统参数的方案,修改系统参数总是有得有失,需要选择一个平衡点,这和业务相关度太高,比如是否要求数据的强一致性,是否容忍数据丢失等等。优化的思路有以下两个考虑点</p>
<ol>
<li>最大化利用页缓存</li>
<li>减少系统api调用次数</li>
</ol>
<p>第一点很容易理解,尽量让每次IO操作都命中页缓存,这比操作磁盘会快很多,第二点提到的系统api主要是read和write,由于系统调用会从用户态进入内核态,并且有些还伴随这内存数据的拷贝,因此在有些场景下减少系统调用也会提高性能</p>
<h3 id="readahead">readahead<a class="headerlink" href="#readahead" title="Permanent link">¶</a></h3>
<p>readahead是一种非阻塞的系统调用,它会触发操作系统将文件内容预读到页缓存中,并且立马返回,函数原型如下</p>
<div class="highlight"><pre><span></span>ssize_t readahead(int fd, off64_t offset, size_t count);
</pre></div>
<p>在通常情况下,调用readahead后立马调用read并不会提高读取速度,我们通常在批量读取或在读取之前一段时间调用readahead,假设如下场景,我们需要连续读取1000个1M的文件,有如下两个方案,伪代码如下</p>
<p><strong>直接调用read函数</strong></p>
<div class="highlight"><pre><span></span>char* buf = (char*)malloc(10*1024*1024);
for (int i = 0; i < 1000; ++i)
{
int fd = open_file();
int size = stat_file_size();
read(fd, buf, size);
// do something with buf
close(fd);
}
</pre></div>
<p><strong>先批量调用readahead再调用read</strong></p>
<div class="highlight"><pre><span></span>int* fds = (int*)malloc(sizeof(int)*1000);
int* fd_size = (int*)malloc(sizeof(int)*1000);
for (int i = 0; i < 1000; ++i)
{
int fd = open_file();
int size = stat_file_size();
readahead(fd, 0, size);
fds[i] = fd;
fd_size[i] = size;
}
char* buf = (char*)malloc(10*1024*1024);
for (int i = 0; i < 1000; ++i)
{
read(fds[i], buf, fd_size[i]);
// do something with buf
close(fds[i]);
}
</pre></div>
<p>感兴趣的可以写代码实际测试一下,需要注意的是在测试前必须先回写脏页和清空页缓存,执行如下命令</p>
<div class="highlight"><pre><span></span>sync && sudo sysctl -w vm.drop_caches=3
</pre></div>
<p>可通过查看/proc/meminfo中的Cached及Dirty项确认是否生效</p>
<p>通过测试发现,第二种方法比第一种读取速度大约提高10%-20%,这种场景下是批量执行readahead后立马执行read,优化空间有限,如果有一种场景可以在read之前一段时间调用readahead,那将大大提高read本身的读取速度</p>
<p>这种方案实际上是利用了操作系统的页缓存,即提前触发操作系统将文件读取到页缓存,并且操作系统对缺页处理、缓存命中、缓存淘汰都由一套完善的机制,虽然用户也可以针对自己的数据做缓存管理,但和直接使用页缓存比并没有多大差别,而且会增加维护代价</p>
<h3 id="mmap">mmap<a class="headerlink" href="#mmap" title="Permanent link">¶</a></h3>
<p>mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系,函数原型如下</p>
<div class="highlight"><pre><span></span>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
</pre></div>
<p>实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。如下图所示</p>
<p><img alt="" src="http://littlewhite.us/pic/mmap.jpg" /></p>
<p>mmap除了可以减少read,write等系统调用以外,还可以减少内存的拷贝次数,比如在read调用时,一个完整的流程是操作系统读磁盘文件到页缓存,再从页缓存将数据拷贝到read传递的buffer里,而如果使用mmap之后,操作系统只需要将磁盘读到页缓存,然后用户就可以直接通过指针的方式操作mmap映射的内存,减少了从内核态到用户态的数据拷贝</p>
<p>mmap适合于对同一块区域频繁读写的情况,比如一个64M的文件存储了一些索引信息,我们需要频繁修改并持久化到磁盘,这样可以将文件通过mmap映射到用户虚拟内存,然后通过指针的方式修改内存区域,由操作系统自动将修改的部分刷回磁盘,也可以自己调用msync手动刷磁盘</p>
<hr/>
<aside>
<nav>
<ul class="articles-timeline">
<li class="previous-article">« <a href="https://chukeer.github.io/http缓存服务器淘汰策略.html" title="Previous: http缓存服务器淘汰策略">http缓存服务器淘汰策略</a></li>
<li class="next-article"><a href="https://chukeer.github.io/Mac Alfred快速复制剪贴板和指定文本.html" title="Next: Mac Alfred快速复制剪贴板和指定文本">Mac Alfred快速复制剪贴板和指定文本</a> »</li>
</ul>
</nav>
</aside>
</div>
<section>
<div class="span2" style="float:right;font-size:0.9em;">
<table class="table">
<!-- <time pubdate="pubdate" datetime="2016-09-19T00:00:00+08:00"> 9 19, 2016</time> -->
<tr>
<td>Published</td>
<td><time pubdate="pubdate" datetime="2016-09-19T00:00:00+08:00">2016- 9-19</time></td>
</tr>
<tr>
<td>Category</td>
<td><a class="category-link" href="https://chukeer.github.io/categories.html#skill-ref">Skill</a></td>
</tr>
<tr>
<td>Tags</td>
<td>
<ul class="list-of-tags tags-in-article">
<li><a href="https://chukeer.github.io/tags.html#linux-ref">linux
<span>7</span>
</a></li>
</ul>
</td>
</tr>
</table>
</div>
</section>
</div>
</article>
</div>
<div class="span1"></div>
</div>
</div>
<div id="push"></div>
</div>
<footer>
<div id="footer">
<ul class="footer-content">
<li class="elegant-power">Powered by <a href="http://getpelican.com/" title="Pelican Home Page">Pelican</a>. Theme: <a href="http://oncrashreboot.com/pelican-elegant" title="Theme Elegant Home Page">Elegant</a> by <a href="http://oncrashreboot.com" title="Talha Mansoor Home Page">Talha Mansoor</a></li>
</ul>
</div>
</footer> <!--
<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
-->
<script src="https://chukeer.github.io/theme/js/jquery.min.js"></script>
<script src="https://chukeer.github.io/theme/js/bootstrap.min.js"></script>
<script>
function validateForm(query)
{
return (query.length > 0);
}
</script>
<script>
$("div.article-content table").addClass("table table-hover");
</script>
</body>
<!-- Theme: Elegant built for Pelican
License : http://oncrashreboot.com/pelican-elegant -->
</html>