从"read"看系统调用的耗时
2013-11-26 09:58
162 查看
从"read"看系统调用的耗时
![](http://cnc.qzs.qq.com/ac/b.gif)
1、fread和read有何不同?
先看两段代码:
fread.c
![](http://b270.photo.store.qq.com/psb?/V100ACR70ifMWc/NGdqkKqhC4EfNBMnHYLaQJNY1x.0Xx6FMfrgkPPdxzc!/b/dDH*.qDVEAAA&bo=7wCRAAAAAAABAFk!&su=0264132801&rf=2-9)
read.c
![](http://b274.photo.store.qq.com/psb?/V100ACR70ifMWc/RX.WaqZ0M700ZGSbbT9AlYHBNMCkzLviEY542CO*GrE!/b/dOq8VaONDQAA&bo=2ACSAAAAAAABAG0!&su=067925601&rf=2-9)
两个文件的功能完全一样,打开同一个名为test.file的文件,并逐字节地读取整个文件。
将它们编译后得到的可执行程序fread和read分别在同一台PC(linux系统)上执行,得到的如果如下:
![](http://b272.photo.store.qq.com/psb?/V100ACR70ifMWc/y*WQPa221KYzebEecotXVwHdgW25YMTI1R1wWM9wITE!/b/dNSOJKIhDgAA&bo=vgG3AAAAAAABAC8!&su=0176553649&rf=2-9)
发现没有?fread与read的耗时相差数十倍之多!可见啊~ read一个字节这种写法是相当不可取的!
2、是什么引起的差异?
但是,事情为什么会是这样的呢?让我们用strace来看看:
![](http://b270.photo.store.qq.com/psb?/V100ACR70ifMWc/4jGS7z.*xU1hrb8E0hC4MsfISbrJNpPJZvpJC30T0yk!/b/dD*49KAqDwAA&bo=XQK1AAAAAAABAM0!&su=067723217&rf=2-9)
看到了吧~fread库函数在内部做了缓存,每次读取4096个字节;而read就老老实实一个字节一个字节地读……
那么再想想,我们读的是什么?是磁盘。难道上面提到的差异,就是因为这4096倍的读磁盘次数差而引起的吗?并不是这样。
磁盘是块设备,每次读取的最小单位是块。而当我们通过系统调用读一个字节时,linux会怎么做呢?它会是读取一个块、然后返回一个字节、再把其余字节都丢掉吗?当然不会,这样的操作系统也太拙劣了……
实际上linux的文件系统层(fs层)不仅会将每次读的一整块数据缓存下来,还有预读机制(一次预读多个块,以减少磁盘寻道时间),并且缓存的内容是放在文件对应的inode里面,是可以在进程间共享的。(省略细节若干……)
那么,fread与read执行的耗时差别来自于哪里呢?从代码看,它们都做了相同次数的函数调用;从内核看,它们都造成了基本上相同的磁盘IO……但是注意到,第一段代码中一共进行了N(N=约24M)次fread函数调用,产生约N/4096次系统调用;第二段代码中一共进行了N次read函数调用,产生N次系统调用。实际上这里的耗时差就来自于4096倍的系统调用次数差!fread()库函数中缓存的作用并不是减少读磁盘的次数,而是减少系统调用的次数。
由此可见,系统调用比起普通函数调用有很大的开销,编写代码时应当注意避免滥用系统调用。
3、进一步提高效率?
为了进一步减少系统调用的次数,关于读文件的这个问题,我们还可以这样做:
mmap.c
![](http://b274.photo.store.qq.com/psb?/V100ACR70ifMWc/jfEwbOCYWOBmWoz8XENN3OXueI8yXYRU81zjLN9Uv.s!/b/dHPGW6PdDgAA&bo=5AH5AAAAAAABADs!&su=0180730561&rf=2-9)
同样是遍历整个文件,但是读文件的过程中不需要使用系统调用,直接把文件当成内存buffer来读就行了。其原理是:mmap的执行,仅仅是在内核中建立了文件与虚拟内存空间的映射关系。用户访问这些虚拟内存空间时,页表里面并没有这些空间的表项,于是CPU产生缺页异常。内核捕捉这些异常,逐渐将文件读入内存,并建立相关的页表项。(省略细节若干……)
将其编译后得到的可执行程序mmap和之前的fread、read分别在同一台PC上执行,得到的如果如下:
![](http://b272.photo.store.qq.com/psb?/V100ACR70ifMWc/84YVnIvAbDWfVlI*SfAViw*U0eRqkSEyQRmpwATb2n8!/b/dNSOJKIiDgAA&bo=wgH6AAAAAAABAB4!&su=044279313&rf=2-9)
mmap方式与fread方式相比,耗时又减少了好几倍。
4、为什么?
看到这里,我们不禁要问,系统调用为什么就这么耗时呢?系统调用与普通函数调用到底有什么不同?
1、两者都是在调用处进行跳转,转到被调用的代码中去执行;
系统调用使用的"跳转"指令相对复杂。因为跳转到内核空间去执行时,CPU特权级别需要改变(否则没有权限访问到内核空间)。于是,CPU必须封装一条指令,既实现跳转、又实现特权级别的改变,并且还要保证跳转到的地方就是内核代码(否则用户程序用这个指令假跳一下,自己就拥有特权了)。而软中断指令恰好能满足这三点要求,所以,X86下实现系统调用的经典方法就是"INT 0x80"(现在好像换sysenter了吧~ 但是指令要做的事情应该不会变);
2、两者都是执行到返回点,然后跳转回到原先的调用点;
系统调用的返回过程还伴随着很多的工作,比如检查是否需要调度、是否有异步信号需要处理、等等。然后,既然来的时候改变了CPU特权级别,返回的时候还得改回去;
3、两种调用中,调用前后的代码都在相同的虚拟地址空间中(内核空间也属于用户进程所能看到的虚拟地址空间范围内,尽管进程一般情况下没有权限去访问),地址空间并没有切换;
运行内核代码时使用的栈是内核栈,系统调用时需要进行栈的切换;
4、两者的参数传递看似相同;
普通函数调用是通过栈来传递参数的;而系统调用是通过寄存器来传递参数,寄存器不够用时才逼不得已使用栈。因为栈要切换,参数传递起来不那么简单;(但是在这一点上,系统调用与普通函数调用的耗时并无太大差异。)
5、CPU执行内核代码和执行用户程序代码没什么区别;
但是注意到,内核代码对用户参数是充分的不信任。以read/fread的buffer参数为例,fread库函数一般不会检查buffer参数是否合法。就算想要检查,也没这个能力。他不知道buffer是不是个野指针,不知道buffer的大小是否与len不符,不知道buffer指向的这块内存是否可写……他唯一能做的检查只是buffer是否为NULL,可惜这没什么意义。但是通过系统调用进入内核以后,情况就不同了。前面说到的那些检查,统统都要做,并且每次调用都要不厌其烦地做;
以上几点区别,仅是我目前能够想到的。但是管中窥豹,可见一斑。进入内核以后,要做的事情的确是很多很多。
![](http://cnc.qzs.qq.com/ac/b.gif)
1、fread和read有何不同?
先看两段代码:
fread.c
read.c
两个文件的功能完全一样,打开同一个名为test.file的文件,并逐字节地读取整个文件。
将它们编译后得到的可执行程序fread和read分别在同一台PC(linux系统)上执行,得到的如果如下:
发现没有?fread与read的耗时相差数十倍之多!可见啊~ read一个字节这种写法是相当不可取的!
2、是什么引起的差异?
但是,事情为什么会是这样的呢?让我们用strace来看看:
看到了吧~fread库函数在内部做了缓存,每次读取4096个字节;而read就老老实实一个字节一个字节地读……
那么再想想,我们读的是什么?是磁盘。难道上面提到的差异,就是因为这4096倍的读磁盘次数差而引起的吗?并不是这样。
磁盘是块设备,每次读取的最小单位是块。而当我们通过系统调用读一个字节时,linux会怎么做呢?它会是读取一个块、然后返回一个字节、再把其余字节都丢掉吗?当然不会,这样的操作系统也太拙劣了……
实际上linux的文件系统层(fs层)不仅会将每次读的一整块数据缓存下来,还有预读机制(一次预读多个块,以减少磁盘寻道时间),并且缓存的内容是放在文件对应的inode里面,是可以在进程间共享的。(省略细节若干……)
那么,fread与read执行的耗时差别来自于哪里呢?从代码看,它们都做了相同次数的函数调用;从内核看,它们都造成了基本上相同的磁盘IO……但是注意到,第一段代码中一共进行了N(N=约24M)次fread函数调用,产生约N/4096次系统调用;第二段代码中一共进行了N次read函数调用,产生N次系统调用。实际上这里的耗时差就来自于4096倍的系统调用次数差!fread()库函数中缓存的作用并不是减少读磁盘的次数,而是减少系统调用的次数。
由此可见,系统调用比起普通函数调用有很大的开销,编写代码时应当注意避免滥用系统调用。
3、进一步提高效率?
为了进一步减少系统调用的次数,关于读文件的这个问题,我们还可以这样做:
mmap.c
同样是遍历整个文件,但是读文件的过程中不需要使用系统调用,直接把文件当成内存buffer来读就行了。其原理是:mmap的执行,仅仅是在内核中建立了文件与虚拟内存空间的映射关系。用户访问这些虚拟内存空间时,页表里面并没有这些空间的表项,于是CPU产生缺页异常。内核捕捉这些异常,逐渐将文件读入内存,并建立相关的页表项。(省略细节若干……)
将其编译后得到的可执行程序mmap和之前的fread、read分别在同一台PC上执行,得到的如果如下:
mmap方式与fread方式相比,耗时又减少了好几倍。
4、为什么?
看到这里,我们不禁要问,系统调用为什么就这么耗时呢?系统调用与普通函数调用到底有什么不同?
1、两者都是在调用处进行跳转,转到被调用的代码中去执行;
系统调用使用的"跳转"指令相对复杂。因为跳转到内核空间去执行时,CPU特权级别需要改变(否则没有权限访问到内核空间)。于是,CPU必须封装一条指令,既实现跳转、又实现特权级别的改变,并且还要保证跳转到的地方就是内核代码(否则用户程序用这个指令假跳一下,自己就拥有特权了)。而软中断指令恰好能满足这三点要求,所以,X86下实现系统调用的经典方法就是"INT 0x80"(现在好像换sysenter了吧~ 但是指令要做的事情应该不会变);
2、两者都是执行到返回点,然后跳转回到原先的调用点;
系统调用的返回过程还伴随着很多的工作,比如检查是否需要调度、是否有异步信号需要处理、等等。然后,既然来的时候改变了CPU特权级别,返回的时候还得改回去;
3、两种调用中,调用前后的代码都在相同的虚拟地址空间中(内核空间也属于用户进程所能看到的虚拟地址空间范围内,尽管进程一般情况下没有权限去访问),地址空间并没有切换;
运行内核代码时使用的栈是内核栈,系统调用时需要进行栈的切换;
4、两者的参数传递看似相同;
普通函数调用是通过栈来传递参数的;而系统调用是通过寄存器来传递参数,寄存器不够用时才逼不得已使用栈。因为栈要切换,参数传递起来不那么简单;(但是在这一点上,系统调用与普通函数调用的耗时并无太大差异。)
5、CPU执行内核代码和执行用户程序代码没什么区别;
但是注意到,内核代码对用户参数是充分的不信任。以read/fread的buffer参数为例,fread库函数一般不会检查buffer参数是否合法。就算想要检查,也没这个能力。他不知道buffer是不是个野指针,不知道buffer的大小是否与len不符,不知道buffer指向的这块内存是否可写……他唯一能做的检查只是buffer是否为NULL,可惜这没什么意义。但是通过系统调用进入内核以后,情况就不同了。前面说到的那些检查,统统都要做,并且每次调用都要不厌其烦地做;
以上几点区别,仅是我目前能够想到的。但是管中窥豹,可见一斑。进入内核以后,要做的事情的确是很多很多。
相关文章推荐
- 从"read"看系统调用的耗时
- 从"read"看系统调用的耗时
- 从"read"看系统调用的耗时
- 系统出现内存不能为"Read"或"written"的原因
- NHibernate3.3.0GA+ExtJS4.1.1+ASP.NET MVC3.0权限管理系统(4)--关于(3)报错"Cannot read property 'addCls' of null"
- Linux&Unix--open/close/write/read系统调用】
- 【Linux&Unix--open/close/write/read系统调用】
- VB调用系统的"打印机设置"和"页面设置".
- 【Linux&Unix--open/close/write/read系统调用】
- 在与sap系统集成时遇到的问题.sap系统发布一webservice,java(xfire)作为客户端调用,调用时抛出如下异常: Wrong Content-Type and empty HTTP-Body received: ("HTTP Code 20
- ls -l |grep "^-"|wc -l 程序问题的定位—strace -p pid(跟踪系统调用)gdb -p pid(调试)lsof -p pid(查看当前进程打开的文件描述符) ps -e
- 【Linux&Unix--open/close/write/read系统调用】
- 系统出现内存不能为"Read"或"written"的原因
- Android调用支付宝快捷支付时提示"系统繁忙,请稍后再试(ALI64)"错误
- 系统调用 4--read
- jion方法(合并某个线程) jion()调用优先级别,把"谢霆锋"调到前面输出
- "Pure Virtual Function Called" 纯虚函数调用错误分析(翻译)
- 出现0x0000002c内存不能为"read"的问题
- 系统调用read,write和标准库函数IO
- Win7+Ubuntu11.10(EasyBCD硬盘安装) ——win7和ubuntu双系统中win7启动不了GRUB_TIMEOUT="10"