`

从"read"看系统调用的耗时

 
阅读更多
【转】从"read"看系统调用的耗时

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()函数在linux系统中调用过程。从用户层到文件系统层到通用块层到page cache层只到驱动层设备层的过程。

    read系统调用流程图.doc

    read系统调用流程图.doc

    文件读写的系统调用源码

    使用C++,利用open ,read ,write系统调用实现文件数据的读写

    windows_系统调用函数表

    windows_系统调用函数表明细NTSTATUS WINAPI NtAcceptConnectPort(PHANDLE,ULONG,PLPC_MESSAGE,BOOLEAN,PLPC_SECTION_WRITE,PLPC_SECTION_READ)

    read,open,write系统调用C语言内嵌汇编

    read,open,write系统调用C语言内嵌汇编

    Linux 2.6.11内核文件IO系统调用详解

    本文主要讲述的是文件I/O操作的2.6.11内核版本实现,包括了主要的数据结构、宏定义和函数流程。分别讲述open,create,close,read,write,lseek系统调用。

    linux read函数的系统调用流程

    这里较详细的介绍了一下在android中的系统函数read()在sd card中读取数据的过程。

    Nachos系统调用实习报告.doc

    本 次lab一共要求完成10个系统调用,包括两大部分,文件系统相关——Create,Open,Clo se,Read,Write;用户程序相关——Exec,Fork,Yield,Join,Exit。需要在阅读和理 解源码的基础上,知道系统调用的执行流程...

    232个windows 系统调用函数表

    232个windows 系统调用函数表 NTSTATUS WINAPI NtAcceptConnectPort(PHANDLE,ULONG,PLPC_MESSAGE,BOOLEAN,PLPC_SECTION_WRITE,PLPC_SECTION_READ); NTSTATUS WINAPI NtAccessCheck(PSECURITY_DESCRIPTOR,HANDLE,...

    操作系统文件系统的用户界面

    分别使用文件的系统调用read(fd, buf, nbytes), write(fd, buf, nbytes)和文件的库函数fread(buf, size, nitems, fp), fwrite(buf, size, nitems, fp),编写一个文件的复制程序(文件大小>1M )。 main(int argc, ...

    操作系统实验用C语言编程实现复制文件

    1.实验内容与目的 熟悉有关文件的系统调用,学习文件系统的系统调用命令,提高对文件系统实现功能的理解和掌握。使用creat open read write 等系统调用用C语言编程实现复制文件。

    linux 编程实验系统调用报告

    linux编程中的系统调用,包括文件读写,只用open,read,write,close 进行文件复制,管道的读写,未命名管道的建立以及命名管道的建立

    Linux 2.6.17.9内核文件系统调用详解

    本部分主要讲述的是文件I/O操作的2.6.17.9内核版本实现,包括了主要的数据结构、宏定义和函数流程。以下分别讲述open,create,close,read,write,lseek系统调用。

    阅读器关闭时 Read 的尝试无效。

    在C#中连接数据库时,错误提示:阅读器关闭时 Read 的尝试无效。

    操作系统报告(管道通信)

    编制一个程序:实现进程的管道...把buf中的长度为size字符的消息送入管道入口fd[1],接收进程则使用read(fd[0],buf,size)从管道出口fd[0]读出size字符的消息置入buf中。这里,管道按fifo方式传送消息,且只能单向传递。

    C#调用海康威视相机

    用C#调用海康威视工业相机sdk,可以实现打开相机,采集图片,保存图片,配置参数等功能。适合新手学习C#调用海康相机

    管道机制实现进程间的通信

    操作系统课程作业(本人亲自研究写出) 学习利用管道机制实现进程间的通信,加深对通信机制的理解。 实验内容: ...c) 父进程调用函数read()通过管道读出子进程发来的消息,将消息输出屏幕,然后终止

    mtd_read()函数的执行流程分析

    先注册一个字符设备的驱动程序, 让系统记住: 主设备号为MTD_CHAR_MAJOR的字符设备文件对应的文件操作集为mtd_fops。字符设备文件将在系统检测到flash设备时创建。

    Linux被中断的系统如何调用详解

    慢系统调用,指的是可能永远无法返回,从而使进程永远阻塞的系统调用,比如无客户连接时的accept、无输入时的read都属于慢速系统调用。 在Linux中,当阻塞于某个慢系统调用的进程捕获一个信号,则该系统调用就会被...

    Unix操作系统设计

    5.2 系统调用read 5.3 系统调用write 5.4 文件和记录的上锁 5.5 文件的输入/输出位置的调整lseek 5.6 系统调用close 5.7 文件的建立 5.8 特殊文件的建立 5.9 改变目录及根 5.10 改变所有者及许可权方式 5.11 ...

Global site tag (gtag.js) - Google Analytics