`

read系统调用,mmap系统调用

阅读更多
【转】read系统调用,mmap系统调用 

2012-07-23 09:54:28|  分类: Linux |  标签:linux  文件系统  虚拟内存  存储系统   |字号 订阅

一般情况下,操作文件既可以使用标准I/O,也可直接使用系统调用。两者有何区别呢?

在输入输出中,直接使用底层的系统调用效率是非常低的,为什么?

(1)  系统调用会影响系统性能。执行系统调用时,Linux必须从用户态代码

     切换到内核态,然后再返回用户代码。

(2)  硬件会对底层系统调用一次所能读写的数据块做出一定的限制。



带缓存的文件操作是标准C 库的实现,第一次调用带缓存的文件操作函数时标准库会自动分配内存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对硬盘上的文件直接进行的,而是针对内存中的缓存的。何时从硬盘中读取文件或者向硬盘中写入文件有标准库的机制控制。不带缓存的文件操作通常都是系统提供的系统调用,更加低级,直接从硬盘中读取和写入文件,由于IO瓶颈的原因,速度并不如意,而且原子操作需要程序员自己保证,但使用得当的话效率并不差。另外标准库中的带缓存文件IO 是调用系统提供的不带缓存IO实现的。

“术语不带缓冲指的是每个read和write都调用嗯内核中的一个系统调用。所有的磁盘I/O都要经过内核的块缓冲(也称内核的缓冲区高速缓存),唯一例外的是对原始磁盘设备的I/O。既然read或write的数据都要被内核缓冲,那么术语“不带缓冲的I/O“指的是在用户的进程中对这两个函数不会自动缓冲,每次read或write就要进行一次系统调用。“--------摘自<unix环境编程>

库函数与系统调用的层次关系

open、read、write、close等系统函数称为无缓冲I/O(Unbuffered I/O )函数,因为它
们位于C 标准库的I/O 缓冲区的底层 。用户程序在读写文件时既可以调用C 标准I/O 库函
数,也可以直接调用底层的Unbuffered I/O 函数,那么用哪一组函数好呢?

     用Unbuffered I/O 函数每次读写都要进内核,调一个系统调用比调一个用户空间的函
      数要慢很多,所以在用户空间开辟I/O 缓冲区还是必要的,用C 标准I/O 库函数就比
      较方便,省去了自己管理I/O 缓冲区的麻烦。

     用C 标准I/O 库函数要时刻注意I/O 缓冲区和实际文件有可能不一致,在必要时需调
      用 fflush(3)。

     我 们知道UNIX 的传统是Everything is a file,I/O 函数不仅用于读写常规文件,也用
      于读写设备,比如终端或网络设备。在读写设备时通常是不希望  缓冲的,例如向代
      表网络设备的文件写数据就 是希望数据通过网络设备发送出去,而不希望只写到缓
      冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,
      所以网络编程通常直接调 用Unbuffered I/O 函数。

C 标准库函数是C 标准的一部分,而Unbuffered I/O 函数是UNIX 标准的一部分,在所 支
持C 语言的平台上应该都可以用C 标准库函数(除了  些平台的C 编译器没有完全符合C
标准之外),而只  在 UNIX 平台上才能使用Unbuffered I/O 函数,所以C 标准I/O 库函数
在头文件 stdio.h中声明,而read、write等函数在头文件unistd.h中声明。在支持
C 语言的非UNIX 操作系统上,标准I/O 库的底层可能由另外一组系统函数支持,例如
Windows 系统的底层是Win32 API,其中读写文件的系统函数是ReadFile、WriteFile。



先看两段代码:

//使用fopen库函数,每次读取1个字节

// 标准库函数是带缓冲的
fread.c

[cpp] view plaincopy

    #include <stdio.h> 
    #include <stdlib.h> 
    void main() 
    { 
        FILE *pf = fopen("test.file", "r"); 
        char buf[2] = {0}; 
        int ret = 0; 
        do { 
            ret = fread(buf, 1, 1, pf); 
        }while(ret); 
    }  



read.c

[cpp] view plaincopy

    #include <stdio.h> 
    #include <stdlib.h> 
    void main() 
    { 
        int fd = open("test.file", 0); 
        char buf[2] = {0}; 
        int ret = 0; 
        do { 
            ret = read(fd, buf, 1); 
        }while(ret); 
    } 



将它们编译后得到的可执行程序fread和read分别在同一台PC(linux系统)上执行,得到的如果如下:

[c-sharp] view plaincopy

    [xiangy@compiling-server test_read]$ time ./fread 
    real    0m0.603s 
    user    0m0.597s 
    sys     0m0.006s 
    [xiangy@compiling-server test_read]$ time ./read 
    real    0m15.240s 
    user    0m3.847s 
    sys     0m11.392s 
    [xiangy@compiling-server test_read]$ ll test.file 
    -rw-r--r-- 1 xiangy svx8004 23955531 Sep 24 17:17 test.file  



发现没有?fread与read的效率差有数十倍之多!可见啊~ read一个字节这种写法是相当不可取的!

但是,事情为什么会是这样的呢?让我们用strace来看看:

[c-sharp] view plaincopy

    [xiangy@compiling-server test_read]$ strace ./fread 
    execve("./fread", ["./fread"], [/* 34 vars */]) = 0 
    …… 
    read(3, "BZh91AY&SY/20v/322/25/4/320/240/177/377/377/377/377/376"..., 4096) = 4096 
    …… 
    [xiangy@compiling-server test_read]$ strace ./read 
    execve("./read", ["./read"], [/* 34 vars */]) = 0 
    …… 
    read(3, "B", 1)                         = 1 
    …… 



看到了吧~fread库函数在内部做了缓存,每次读取4096个字节;而read就老老实实一个字节一个字节地读……

那么再想想,我们读的是什么?是磁盘。难道上面提到的差异,就是因为这4096倍的读磁盘次数差而引起的吗?并不是这样。
磁 盘是块设备,每次读取的最小单位是块。而当我们通过系统调用读一个字节时,linux会怎么做呢?它会是读取一个块、然后返回一个字节、再把其余字节都丢 掉吗?当然不会,这样的操作系统也太拙劣了……换个角度想一想,如果真是每read一个字节就操作一次磁盘去读一个块,那么上面的test.file有 24M之大,近两千四百万次的磁盘读操作也不大可能会在15秒钟完成吧~
实际上linux的文件系统层(fs层)不但不是这样拙劣,反而很高明。不仅会将每次读的一整块数据缓存下来,还有预读机制(一次预读多个块,以减少磁盘寻道时间)。

那么,fread与read执行的效率差来自于哪里呢?实际上就是来自于4096倍的系统调用次数差!fread库函数中缓存的作用并不是减少读磁盘的次数,而是减少系统调用的次数。

由此可见,系统调用比起普通函数调用有很大的开销,编写代码时应当注意尽量减少系统调用的使用。



为了进一步减少系统调用的次数,关于读文件的这个问题,我们还可以这样做:

mmap.c

[cpp] view plaincopy

    #include <stdio.h> 
    #include <stdlib.h> 
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <unistd.h> 
    #include <sys/mman.h> 
    void main() 
    { 
        int fd = open("test.file", 0); 
        struct stat statbuf; 
        char *start; 
        char buf[2] = {0}; 
        int ret = 0; 
        fstat(fd, &statbuf); 
        start = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 
        do { 
            *buf = start[ret++]; 
        }while(ret < statbuf.st_size); 
    } 



同样是遍历整个文件,但是读文件的过程中不需要使用系统调用。(原理是:mmap的执行,仅仅是在内核中建立了文件与虚拟内存空间的对应关系。用户访问这些虚拟内存空间时,页面表里面是没有这些空间的表项的,于是产生缺页异常。内核捕捉这些异常,逐渐将文件读入。)



将其编译后得到的可执行程序mmap和之前的fread、read分别在同一台PC上执行,得到的如果如下:

[c-sharp] view plaincopy

    [xiangy@compiling-server test_read]$ time ./fread 
    real    0m0.901s 
    user    0m0.892s 
    sys     0m0.010s 
    [xiangy@compiling-server test_read]$ time ./mmap 
    real    0m0.112s 
    user    0m0.106s 
    sys     0m0.006s 
    [xiangy@compiling-server test_read]$ time ./read 
    real    0m15.549s 
    user    0m3.933s 
    sys     0m11.566s 
    [xiangy@compiling-server test_read]$ ll test.file 
    -rw-r--r-- 1 xiangy svx8004 23955531 Sep 24 17:17 test.file  



mmap方式与fread方式相比,效率还要高出几倍。
分享到:
评论

相关推荐

    linux mmap文件内存映射机制

    mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而 Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享...

    windows 操作系统课程设计

    任务 I/O系统调用开销比较任务目的: 本任务主要目的在于了解I/O系统调用的特点并通过性能测试对此有直观的认识。任务要求:在LINUX平台用C编程逆序一个文本文件,注意显示逆序结果的必须是原文件名。如文件原内容为...

    Python3 mmap内存映射文件示例解析

    内存映射通常可以提供I/O性能,因为使用内存映射是,不需要对每个访问都建立一个单独的系统调用,也不需要在缓冲区之间复制数据;实际上,内核和用户应用都能直接访问内存。 内存映射文件可以看作是可修改的字符串或...

    mini2440 lcd驱动测试程序

    介绍:mmap 在所调用的进程中的一个虚拟地址空间 创建了一个新的映射 这块新的空间的起始地址就是addr 而这块空间的长度则由length来确定"&gt;Mmap 0 screensize PROT READ | PROT WRITE MAP SHARED fbfd 0 ...

    soso:一个类似Unix的简单操作系统

    Libc(Musl带有基本调用,例如open,read等)。 用户空间程序作为ELF文件 mmap支持 帧缓冲图形(用户空间可以使用mmap访问) 共享内存 串行端口 PS / 2鼠标 Soso具有Libc,因此仅依赖一小部分Libc的现有应用程序...

    Linux程序设计 第4版.haozip01

    3.4.2 read系统调用 83 3.4.3 open系统调用 84 3.4.4 访问权限的初始值 85 3.4.5 其他与文件管理有关的系统调用 88 3.5 标准i/o库 91 3.5.1 fopen函数 91 3.5.2 fread函数 92 3.5.3 fwrite函数 92 3.5.4 ...

    Linux程序设计 第4版.haozip02

    3.4.2 read系统调用 83 3.4.3 open系统调用 84 3.4.4 访问权限的初始值 85 3.4.5 其他与文件管理有关的系统调用 88 3.5 标准i/o库 91 3.5.1 fopen函数 91 3.5.2 fread函数 92 3.5.3 fwrite函数 92 3.5.4 ...

    idontgiveashell:从网络加载.so并在seccomp沙箱中执行

    seccomp-bpf允许以下系统调用: rt_sigreturn , exit exit_group , read , write mmap , mprotect getcwd 。 memdlopen( )用于从内存加载动态库。 尽管是概念证明,但这个想法很棒,而且效果很好。 ...

    Linux C 一站式学习

    2.2. 调用系统函数向进程发信号 2.3. 由软件条件产生信号 3. 阻塞信号 3.1. 信号在内核中的表示 3.2. 信号集操作函数 3.3. sigprocmask 3.4. sigpending 4. 捕捉信号 4.1. 内核如何实现信号的捕捉 4.2. sigaction ...

    获取USB摄像头的1080p的JPEG格式的图片20180608_1806.7z

    获取USB摄像头的1080p的JPEG格式的图片20180608_1806.7z 电脑上的系统:ubuntu14.04 // http://www.linuxidc.com/Linux/2011-03/33020.htm // V4L2摄像头获取单幅图片测试程序(MMAP模式) // [日期:2011-03-06] ...

    宋劲彬的嵌入式C语言一站式编程

    2.2. 调用系统函数向进程发信号 2.3. 由软件条件产生信号 3. 阻塞信号 3.1. 信号在内核中的表示 3.2. 信号集操作函数 3.3. sigprocmask 3.4. sigpending 4. 捕捉信号 4.1. 内核如何实现信号的捕捉 4.2. sigaction ...

    AT&T Assembly Language

    其中包括一些如何使用汇编链接C语言库,汇编调用系统调用,汇编执行浮点运算,C语言内嵌汇编等。 Chapter 1: What Is Assembly Language? 1 Processor Instructions 1 Instruction code handling 2 Instruction code...

    linux_c API函数大全

    mmap(建立内存映射) 28 3.6 30 munmap(解除内存映射) 30 4.日期时间篇 31 4.1 31 asctime(将时间和日期以字符串格式表示) 31 4.2 31 ctime(将时间和日期以字符串格式表示) 31 4.3 32 gettimeofday(取得目前...

Global site tag (gtag.js) - Google Analytics