文件io_文件夹io - CSDN
  • Linux系统下操作文件有两类基本API,一类由系统调用提供,被称为系统IO。一类由标准C库提供,被称为标准IO。   拓展: 它们都是Linux下操作文件的标准接口,那又有什么区别?我们该如何选择使用呢? 第...

    Linux系统下操作文件有两类基本API,一类由系统调用提供,被称为系统IO。一类由标准C库提供,被称为标准IO。

     

    拓展:

    它们都是Linux下操作文件的标准接口,那又有什么区别?我们该如何选择使用呢?

    第一:系统IO是更底层的接口,因此任何设备和文件最终都是可以通过系统IO来操作。系统IO不提供缓冲区,意味着每次读写都必须进入内核,对于大数据量的读写操作会影响效率。

    第二:标准IO是由标准库提供的接口,因此功能更加丰富,而且标准IO提供缓冲区,增加数据处理的吞吐量。标准IO还对读写操作提供更加丰富的操作方式,例如按字节、按行、按块、按数据格式读写。但是有些特殊文件无法使用标准IO,比如socket套接口,比如LCD显示屏。

    第三:在能使用标准IO的场合,我们尽量使用它,毕竟它功能丰富效率高,但在无法使用标准IO的场合,我们还是只能用系统IO。

    第四:所谓的系统IO,就是指这样的一堆函数:

    open()

    read()

    wirte()

    lseek()

    ioctl()

    close()

    等等

    所谓的标准IO,指的是这一堆函数:

    fopen()

    fgets()

    fread()

    scanf()

    getchar()

    fputs()

    fwrite()

    printf()

    fseek()

    fclose()

    等等。

    展开全文
  • 文件IO基础知识

    2016-10-29 21:05:35
    学习文件IO之前,先了解一下什么是库函数和系统调用。库函数:顾名思义是把函数放到库里。是把一些常用到的函数编完放到一个库文件里,供别人用,别人用的时候把它所在的文件名#include加到里面就可以了。可分为两类...

    学习文件IO之前,先了解一下什么是库函数和系统调用。

    库函数:顾名思义是把函数放到库里。是把一些常用到的函数编完放到一个库文件里,供别人用,别人用的时候把它所在的文件名#include<>加到里面就可以了。可分为两类,一类是c语言标准规定的库函数,一类是编译器特定的库函数。

    系统调用:是操作系统为用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口函数;
    文件描述符
    对于内核而言,所有打开的文件、设备、网络socket都是通过文件描述符引用,文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传给read或write。

    注意:应用程序运行的时候,系统将会为该进程默认打开三个文件描述符:
    1.标准输入:STDIN_FILENO (0)
    2.标准输出 : STDOUT_FILENO (1)
    3.标准出错: STDERR_FILENO (2)

    文件描述符的变化范围是: 0~OPEN_MAX-1

    下面依次介绍linux系统调用函数:
    1.open()
    int open(const char path, int oflag, … /*mode_t mode/);
    返回值 int fd 文件描述符(file description), open系统调用返回的文件描述符一定是最小的、未使用的文件描述符数值

    参数
    path: 要打开的文件、设备的路径名
    oflag: 由多个选项进行“或”运算构造oflag参数
    必选: O_RDONLY (只读)、 O_WRONLY(只写),O_RDWR(读写)
    可选: O_APPEND 每次写时都追加到文件的尾端。O_CREAT 文件不存在则创建它,使用该选项需要第三个mode
    O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。
    O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0;
    O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY….
    mode: oflag带O_CREAT选项时,必须带该参数用来指定打开文件的权限模式,如066。
    例: int fd; fd = open(“text.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666);
    fd = open(“text.txt”, O_WRONLY|O_APPEND);

    总结
    (1).在windows下编程,系统不提供open(),write()函数,打开文件用fopen()库函数时,所有操作系统都可以调用。
    (2).open()函数里面的参数如果有O_CREAT这个参数时,则一定要有三个参数

    2.creat()
    int creat(const char *path, mode_t mode);
    此函数用来创建一个新文件并返回其fd
    **它等价于**open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
    例: int fd; fd=creat(“text.txt”, 0644);

    3.close()
    该函数用来关闭一个打开的文件,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭它所有打开的文件。
    int close(int fd);

    ps:编程一旦用了open()函数,一定要close()关闭,否则文件描述符一直会被占用。

    4.lseek()
    每打开一个文件时都有一个与其关联的“当前文件偏移量”。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常读写操作都是从当前文件偏移量处开始,并使用偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0.

    off_t lseek(int fd, off_t offset, int whence);

    注意:whence:
    SEEK_SET, 则将该文件偏移量设置为距文件开始处offset个字节;
    SEEK_CUR,则该文件的偏移量设置为当前值加offset,offset可为正或负;
    SEEK_END,则将该文件偏移量设置为长度加offset,offset可正可负;
    off_t pos;
    pos = lseek(fd, 0, SEEK_CUR);

    lseek()函数运用举例,如下图:
    这里写图片描述
    该程序的目的是创建文件text.txt,并向文件里面写内容,并将内容输出在屏幕上。

    5.read()/write()
    (1)read()函数用来从打开的文件中读取数据:
    ssize_t read(int fd, void *buf, size_t nbytes);
    如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。
    (2)write()函数用来往打开的文件中写入数据:
    ssize_t write(int fd, const void *buf, size_t nbytes);
    如write成功,则返回实际写入的字节数,失败返回-1。

    注意:系统调用出错处理
    (1)大部分的Linux系统调用返回值都是0表示成功,-1表示失败。在库函数中有个整形类型的errno变量,每个errno值对应着以字符串表示的错误类型。如果系统调用出错,则该函数将重新设置errno的值,通过该信息我们可以查看系统调用出错的具体原因
    (2)perror() 用来将上一个函数发生错误的原因输出到标准出错(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串
    (3)strerror通过标准错误的标号,获得错误的描述字符串 ,将单纯的错误标号转为转为字符串描述,方便用户查找错误。

    例:
    int main(int argc, char *argv)
    {
    int fd = -1;
    char buf[1024];
    //char *buf;
    fd = open(“file.txt”, O_RDWR|O_CREAT|O_TRUNC);
    if(fd < 0)
    {
    perror(“Open failure”);
    printf(“Open %s failure:[%d] %s\n”, “file.txt”, errno, strerror(errno));
    return ;
    }
    write(fd, “Hello”, 5);
    memset(buf, 0, sizeof(buf));
    read(fd, buf, sizeof(buf));
    printf(“file content: %s\n”, buf);
    close(fd);
    }

    6.dup() 和 dup2()
    以下两个函数都可以用来复制一个现有的文件描述符。
    int dup(int fd);
    int dup2(int fd, int fd2);
    注意:由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。
    dup2可以用fd2参数指定新描述符的值。如果fd2已经打开,则先关闭。如fd等于fd2, 则
    dup2返回fd2, 而不关闭它。
    使用这两个函数可以用来实现标注输入、标准输出、标准出错重定向:
    int fd = -1;
    fd = open(“std.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    printf(“Hello Word!\n”);

    例:
    int main(int argc, char *argv)
    {
    int fd = -1;
    fd = open(“file.txt”, O_RDWR|O_CREAT|O_TRUNC);
    if(fd < 0)
    {
    printf(“Open %s failure:[%d] %s\n”, “file.txt”, errno, strerror(errno));
    return ;
    }
    dup2(fd, 1);
    printf(“fd=%d”, fd);
    close(fd);
    }

    8.ioctl()
    ioctl()函数一直是I/O操作的杂物箱,不能用本章中其他函数表示的I/O操作通常都能用ioctl()表示。终端I/O、设备I/O是使用ioctl()最多的地方。
    int ioctl(int fd, int cmd, …);
    fd: 文件描述符
    cmd: 命令字,这个参数需要与设备驱动中的cmd保持一致。
    第三个参数可选
    注意:写LED驱动时,将会在驱动中实现该函数,在应用程序空间再调用该函数
    控制LED的亮和灭。
    int fd = -1;
    fd = open(“/dev/led”, O_RDWR);
    ioctl(fd, TURN_OFF, 3);
    close(fd);

    9.stat系列函数
    stat系列函数用来返回文件或目录的相关信息
    int stat(const char * restrict path, struct stat *restrict buf);
    int fstat(int fd, struct stat *buf);
    struct stat
    {
    mode_t st_mode; struct timespec st_atime;
    ino_t st_ino; struct timespec st_mtime;
    dev_t st_dev; struct timespec st_ctime;
    dev_t st_rdev; blksize_t st_blksize;
    nlink_t st_nlink; blkcnt_t st_blocks;
    uid_t st_uid; }
    gid_t st_gid;
    off_t st_size;
    文件类型: 普通文件(-)、目录文件(d)、块设备(b)、字符设备(c)、FIFO(p)、套接字
    socket(s)、符号链接(l)

    int main (int argc, char **argv)
    {
    struct stat stbuf;
    stat(“file.txt”, &stbuf);
    printf(“File size: %d bytes UID:%d GID:%d\n”,
    stbuf.st_size, stbuf.st_uid, stbuf.st_gid);
    return 0;
    }

    10.access()
    access可以用来测试文件是否存在或测试其权限位:
    int access(const char *path, int mode);
    mode: F_OK、R_OK、W_OK、X_OK
    (1)测试文件是否存在
    if( !(access(“test.txt”, F_OK)) ) // 返回0表示存在;返回-1表示不存在。
    {
    printf(“File exist\n”);
    }
    (2)测试文件是否可读可写
    if( !(access(”test.txt”, R_OK|W_OK)) ) // 返回0表示存在;返回-1表示不存在。
    {
    printf(“File mode is read/write \n”);
    }

    11.unlink()/rename
    int unlink(const char *path);
    调用该函数将path指定的文件的链接数减1,如果对该文件还有其他链接存在,则仍可以通过其他链接访问该文件的数据。
    只有当链接记数达到0时,该文件的内容才可被删除。如果有进程打开了该文件,其内容也不能被删除。关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个记数达到0,内核再去检查它的链接记数,如果记数也是0,那么就删除该文件内容。

    int rename(const char *oldname, const char *newname);
    ps:调用该函数可以实现文件的重命名

    展开全文
  • 课程内容:什么是API,OS API与库API,open函数,read/write/close函数,lseek函数,进程表和文件描述符表,对文件进行共享操作,dup/dup2函数,fcntl函数
  • 本期课程主要介绍跟文件、目录相关的文件IO函数的使用,通过编程实战形式,实现了常用的shell命令:ls\cat\rmdir\wc等。以及实现音频播放器V2.0版本:支持循环列表播放。通过10来个Linux环境下shell命令的编程实现,...
  • 文件IO与标准IO

    2018-10-24 09:07:00
    文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。 ...

    一、先来了解下什么是文件I/O和标准I/O:

    文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。

    标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存。

    (1)全缓存:当填满标准I/O缓存后才进行实际的I/O操作。 
    (2)行缓存:当输入或输出中遇到新行符时,标准I/O库执行I/O操作。 
    (3)不带缓存:stderr就是了。

    二、二者的区别

          文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。

          通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。

          文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件。

    三、相关API

    系统IO
              
     1、open()
                    #include <sys/stat.h>
                    #include <fcntl.h>
                    int open(const char *pathname, int flags);
                    int open(const char *pathname, int flags, mode_t mode);
                        功能:打开或新建一个文件,并指定打开的方式或新建文件的权限
                        参数:
                            pathname------包含路径的文件名,例如“./a.txt”
                            flags---------文件标志
                                O_RDONLY----只读
                                O_WRONLY----只写
                                O_RDWR------可读可写
                                O_CREAT-----如果文件不存在,新建文件,此时mode才有用
                                O_EXCL------与O_CREAT配合使用,可以用来通过返回值判断文件是否新建成功
                                O_TRUNC-----在文件存在&&是普通文件&&能写入的情况下,文件内容清空
                                O_APPEND----打开文件时,把文件读写指针定位在文件末尾,可追加写入内容
                            mode----------如果文件为新建,指定权限为mode,如:0777
                        返回值:成功    返回文件描述符
                                失败     返回  -1
                2、read()
                    #include <unistd.h>
                    ssize_t read(int fildes, void *buf, size_t nbyte);
                        功能:读取描述符为fildes的文件的内容,放入buf指向的内存中,nbyte是期望读取的字节数
                        返回值:成功,返回实际读取的字节数(实际读取的字节数小于或等于nbyte)
                                失败,返回-1
                3、write()
                    #include <unistd.h>
                    ssize_t write(int fildes, const void *buf, size_t nbyte);
                        功能:把buf指向的内存中的内容,写入描述符为fildes的文件,nbyte是期望写入的字节数
                        返回值:成功,返回实际写入的字节数(实际写入的字节数小于或等于nbyte)
                                失败,返回-1
                4、close()
                    #include <unistd.h>
                    int close(int fildes);
                        功能:关闭描述符为fildes的文件
                        返回值:成功返回0
                                失败返回-1
                5、lseek()
                    #include <unistd.h>
                    off_t lseek(int fildes, off_t offset, int whence);
                        功能:定位描述符为fd的文件的读写指针
                        参数:    fd---文件描述符
                                offset---基于基点的偏移量,正数向后偏移,负数向前偏移
                                whence---基点
                                    SEEK_SET----文件开头
                                    SEEK_END----文件末尾
                                    SEEK_CUR----当前位置
                        返回值:成功 返回从文件开头到定位位置的字节数
                                失败 返回-1
                6、ioctl()
                    #include <stropts.h>
                    int ioctl(int fildes, int request, ... /* arg */);
                        功能:对描述符位fildes文件,进行request操作(一般是驱动中给出命令字(读写之外的其它操作),用户程序通过用的命令字,操控硬件设备)
                        request---->SET_BAUD      //设置波特率
                                    REDA_BAUD    //读取波特率
                                    FBIOGET_VSCREENINFO //显示屏命令
                        #define 查询屏幕大小的命令 (有固定格式要求的32位整数)
                        fd = open("/dev/fb0",.....)
                        struct struct fb_var_screeninfo var_info;
                        ioctl(fd,FBIOGET_VSCREENINFO,&var_info)
                        struct fb_var_screeninfo {
                                                    __u32 xres;                //x方向的像素点个数
                                                    __u32 yres;                //y方向的像素点个数
                                                    ...
                                                    __u32 bits_per_pixel;}    //每个像素点用多少位表示颜色值
                7、fcntl()
                    #include <fcntl.h>
                    int fcntl(int fildes, int cmd, ...);
                        功能:根据文件描述词来操作文件的特性
                        参数:cmd------>操作命令
                                    F_DUPFD :复制文件描述词 。 
                                    FD_CLOEXEC :设置close-on-exec标志。如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。 
                                    F_GETFD :读取文件描述词标志。 
                                    F_SETFD :设置文件描述词标志。 
                                    F_GETFL :读取文件状态标志。 
                                    F_SETFL :设置文件状态标志。
                                            其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT,  O_EXCL, O_NOCTTY 和 O_TRUNC不受影响, 可以更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。 
                            F_SETLK:在指定的字节范围获取锁(F_RDLCK,F_WRLCK)或者释放锁(F_UNLCK)。如果与另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。
                        F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会立即返回并将errno置为EINTR。 
                                    F_GETLK:获取文件锁信息。 
                                    F_UNLCK:释放文件锁。
                        返回值:成功 根据不同命令字而不同
                                失败 -1
                8、mmap()
                    #include <sys/mman.h>
                    void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
                        功能:把描述符为fd的文件,映射到一片内存空间
                        参数:    addr-----指定地址映射,一般设置NULL,表示让系统决定映射的位置
                                length----映射的内存的大小,以字节为单位
                                port----对内能够做何种操作
                                       PROT_EXEC  Pages may be executed.可执行
                                       PROT_READ  Pages may be read.可读
                                       PROT_WRITE Pages may be written.可写
                                       PROT_NONE  Pages may not be accessed.不可访问
                                flags----映射标志
                                    MAP_SHARED--共享
                                    MAP_PRIVATE---私有
                                offset---一般设置为0
                        返回值     成功---返回内存首地址
                                失败---返回(void *)-1  MAP_FAILED
                9、munmap()
                    #include <sys/mman.h>
                    int munmap(void *addr, size_t length);
                        功能:取消映射
                        参数:    addr----要取消映射的内存的基地址
                                length---取消映射内存的大小,以字节为单位
                10、dup()\dup2()
                    #include <unistd.h>
                    int dup(int fildes);
                    int dup2(int fildes, int fildes2);
                        功能:复制文件描述符
                        参数:    fildes--->要复制的文件描述符
                                fildes2-->指定的新的文件描述符
                        返回值:成功 返回新的文件描述符
                                失败 -1
                11、显示屏\触摸屏("/dev/fb0","/dev/input/event0")
                    相关结构体:
                                struct fb_var_screeninfo {                  //显示屏
                                                    __u32 xres;                //x方向的像素点个数
                                                    __u32 yres;                //y方向的像素点个数
                                                    ...
                                                    __u32 bits_per_pixel;}    //每个像素点用多少位表示颜色值
                                struct input_event {
                                                    struct timeval time;    -----发生时间
                                                    __u16 type;                ----触摸屏事件类型EV_ABS
                                                    __u16 code;                ---ABS_X    ABS_Y
                                                    __s32 value;}            ----坐标值,触摸屏有触摸动作时,打出x y方向的坐标值    
     标准C库IO
                1、fopen()
                    #include <stdio.h>
                    FILE *fopen(const char *path, const char *mode);
                        功能:打开或创建文件(获取指定文件的文件指针)
                        参数:path---->指向包含路径的文件名
                              mode---->文件模式标志
                                r          以只读方式打开文件,要求文件必须存在
                                r+         以读/写方式打开文件,要求文件必须存在
                                w          以只写方式打开文件,如果文件不存在会创建新文件,如果存在会将其内容清空
                                w+         以读/写方式打开文件,如果文件不存在会创建新文件,如果存在会将其内容清空
                                a          以只写方式打开文件,文件如果不存在会创建新文件
                                        且文件位置偏移量被自动定位到文件末尾(即以追加方式写数据)
                                a+         以读/写方式打开文件,文件如果不存在会创建新文件
                                        且文件位置偏移量被自动定位到文件末尾(即以追加方式写数据)
                        返回值:成功 返回FILE*
                                失败 返回NULL
                2、fclose()
                    #include <stdio.h>
                    int fclose(FILE *stream);
                        功能:关闭文件
                        参数:stream ---->要关闭的文件的指针
                        返回值:成功 返回0
                                失败 返回EOF
                3、fread()
                    #include <sys/ioctl.h>
                    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
                        功能:从stream对应的文件中读取 size *nmemb个字节,存入ptr指向的内存
                        参数:    ptr------自定义缓冲区指针
                                size-----读取的块的大小
                                nmemb----期望读取到的块数
                                stream---即将被读取数据的文件指针
                        返回值:成功 返回的块数等于期望读到的块数
                                失败 返回的块数小于期望的块数,表示出错或到达文件末尾
                4、fwrite()
                    #include <sys/ioctl.h>
                    size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
                        功能:把ptr指向的内存中的size *nmemb个字节写入stream对应的文件
                        参数:    ptr------自定义缓冲区指针
                                size-----写入的块的大小
                                nmemb----期望写入的数据块个数
                                stream---即将被写入数据的文件指针
                        返回值:成功 返回的块数等于期望写入的块数
                                失败 返回的块数小于期望的块数
                5、O操作相关函数
                    #include <sys/ioctl.h>
                    #include <stdio.h>
                    int fputc(int c, FILE *stream);---把c(字符)输出到stream对应的文件
                    int fputs(const char *s, FILE *stream);----把字符串s输出到stream对应的文件
                    int putc(int c, FILE *stream);-----把c输出到stream对应的文件
                    int putchar(int c);------只能通过stdout 把c输出到屏幕
                    int puts(const char *s);----只能向stdout对应的屏幕输出字符串s
                6、I操作相关函数
                    #include <sys/ioctl.h>
                    #include <stdio.h>
                    int fgetc(FILE *stream);---从stream对应的文件中读取一个字符
                    char *fgets(char *s, int size, FILE *stream);----从stream对应的文件中最多读取大小为size-1个字符,存入s指向的位置  注意:读取过程中遇到‘\n’或者EOF停止
                    int getc(FILE *stream);---从stream对应的文件中读取一个字符
                    char *gets(char *s);-----从自定义缓冲区s中读取数据,由于没有指定s的大小,容易造成溢出导致程序出现段错误
                    int getchar(void);------只能通过stdin对应标准输入设备获取字符
                7、feof()/ferror()
                    #include <sys/ioctl.h>
                    int feof(FILE *stream);
                        功能:测试stream对应的文件读写操作是否到末尾
                        返回值:已到末尾返回非零值(逻辑真值)
                                未到末尾返回零值(逻辑假值)
                    int ferror(FILE *stream)    
                        功能:测试stream对应的文件读写操作是否出错
                        返回值:出错返回非零值(逻辑真值)
                                未出错返回零值(逻辑假值)
                8、fseek()/ftell()
                    #include <sys/ioctl.h>
                    int fseek(FILE *stream,long offset,int whence);----设定指定文件的当前位置偏移量
                        参数:    stream---需要设置位置偏移量的文件指针
                                offset---新位置偏移量相对基准点的偏移
                                whence---基准点(SEEK_SET:文件开头处;SEEK_CUR:当前位置;SEEK_END:文件末尾处)
                        返回值:成功    返回0
                                失败    返回-1
                    long ftell(FILE *stream);
                        功能:返回值是stream对应的文件的读写指针位置
                9、缓冲相关
                    #include <stdio.h>
                    int fflush(FILE *stream);   //刷缓冲满
                    int setvbuf(FILE *stream, char *buf, int mode, size_t size);
                        功能:设置stream对应文件的缓冲位置、类型、大小
                        参数:    buf------指向缓冲区
                                mode-----缓冲类型
                                    _IONBF unbuffered 不缓冲
                                    _IOLBF line buffered  行缓冲
                                    _IOFBF fully buffered  满缓冲
                                size-----缓冲区的大小
                        注意:标准输出默认使用行缓冲,普通文件默认使用全缓冲,标准出错不缓冲。
                10、stat()/fstat()/lstat()
                    #include <sys/stat.h>
                    int stat(const char *restrict path, struct stat *restrict buf);
                    int fstat(int fildes, struct stat *buf);0
                    int lstat(const char *restrict path, struct stat *restrict buf);
                        功能:获取文件的元数据(类型、权限、大小等)
                        参数:    path--文件路径
                                fildes--文件描述符
                                buf--属性结构体(保存获取到的数据)
                        返回值:成功 0
                                失败 NULL
                        struct stat {
                                       dev_t     st_dev;         /* ID of device containing file 普通文件所在存储器的设备号*/
                                       ino_t     st_ino;         /* inode number 文件索引号*/
                                       mode_t    st_mode;        /* protection 文件类型、文件权限*/
                                       nlink_t   st_nlink;       /* number of hard links */
                                       uid_t     st_uid;         /* user ID of owner 文件所有者的UID*/
                                       gid_t     st_gid;         /* group ID of owner 文件所属组的GID*/
                                       dev_t     st_rdev;        /* device ID (if special file) 特殊文件设备号*/
                                       off_t     st_size;        /* total size, in bytes 文件大小*/
                                       blksize_t st_blksize;     /* blocksize for filesystem I/O */
                                       blkcnt_t  st_blocks;      /* number of 512B blocks allocated */
                                        ....
                                    }
            目录检索
                1、mkdir
                #include <sys/stat.h>
                #include <sys/types.h>
                int mkdir(const char *pathname, mode_t mode);
                    功能:新建一目录文件,并指定权限
                    参数:    pathname---包含路径的文件名
                            mode-----权限
                    返回值:成功 0
                            失败 -1
                2、opendir
                #include <sys/types.h>
                #include <dirent.h>
                DIR *opendir(const char *name);
                    功能:打开一个目录文件
                    返回值:成功返回DIR *
                            失败返回NULL
                3、readdir
                #include <dirent.h>
                struct dirent *readdir(DIR *dirp);
                    功能:读取目录文件的内容(目录项)
                    struct dirent    {
                                    ino_t          d_ino;       /* inode number */
                                    off_t          d_off;       /* not an offset; see NOTES */
                                    unsigned short d_reclen;    /* length of this record */
                                    unsigned char  d_type;      /* type of file; not supported
                                                                      by all filesystem types */
                                    char           d_name[256]; /* filename */
                                    };
                                    其中d_type(文件类型)成员取值如下:
                                    DT_BLK      This is a block device.
                                    DT_CHR      This is a character device.
                                    DT_DIR      This is a directory.
                                    DT_FIFO     This is a named pipe (FIFO).
                                    DT_LNK      This is a symbolic link.
                                    DT_REG      This is a regular file.
                                    DT_SOCK     This is a UNIX domain socket.
                                    DT_UNKNOWN  The file type is unknown.
                    返回值:成功返回struct dirent *
                            出错或到了目录末尾返回NULL
                4、chdir()
                #include <dirent.h>
                int chdir(const char *path);
                    功能:切换到path目录下
                    返回值:切换成功返回0
                            出错返回-1

    展开全文
  • 文件IO

    2018-09-03 01:12:16
    1.文件描述符 在 SYSIO 中贯穿了一个整形数,它就是文件描述符。对内核而言,所有打开的文件都是通过文件描述符引用的。它的作用与 STDIO 中的 FILE 结构体类似,但是它们的工作原理是完全不同的。它实际上是一个由...

    1.文件描述符

    在 SYSIO 中贯穿了一个整形数,它就是文件描述符。对内核而言,所有打开的文件都是通过文件描述符引用的。它的作用与 STDIO 中的 FILE 结构体类似,但是它们的工作原理是完全不同的。它实际上是一个由内核保存的数组下标,所以不会是负数,下面我会用一张图来表示它的作用。

    图1 SYSIO 文件描述符

    我解释下图上画的东西。

    图片一共分为标准 IO 和系统 IO 两部分,系统 IO 部分有一个数组,这个数组中的指针指向了内核中具体描述文件信息的结构体,而通过这些结构体再引用具体的文件(inode)。而操作系统提供给我们的文件描述符就是这个数组的下标。这个数组的长度是 1024,也就表示一个进程最多可以打开 1024 个文件,当然这个上限可以通过 ulimt(1) 命令修改,具体用法这里不再赘述。

    当产生一个文件描述符时,优先采用当前最小的可用数值。假设当前已经占用的文件描述符为1、2、3、5,那么再次产生的文件描述符就是 4。

    还要注意一点,上面这个文件描述符数组在每个进程中都会持有一份,所以理论上是每个进程最多可以打开 1024 个文件,而不是系统中所有的进程一共只能打开 1024 个文件。

     

    2.fileno(3)

     

    1 #include <stdio.h>
    2 
    3 int fileno(FILE *stream);
    4 
    5    Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
    6 
    7        fileno(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

     

     这个函数的作用是从 STDIO 的 FILE 结构体指针中获得 SYSIO 的文件描述符。

     

    3.fdopen(3)

     

    1 #include <stdio.h>
    2 
    3 FILE *fdopen(int fd, const char *mode);
    4 
    5   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
    6 
    7      fdopen(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

     

     这个函数和上面的 flieno(3) 函数的功能是反过来的,作用是把 SYSIO 的文件描述符转换为 STDIO 的 FILE 结构体指针。mode 参数的作用与 fopen(3) 中的 mode 参数相同,这里不再赘述。

    虽然这两个函数可以在 STDIO 与 SYSIO 之间互相转换,但是并不推荐对同一个文件同时采用两种方式操作。因为 STDIO 和 SYSIO 之间它们处理文件的私有数据是不同步的,如果同时使用两种方式操作同一个文件则可能带来不可预知的后果,具体可以参考上一篇博文中提到的那个合并系统调用的例子。

     

    4.open(2)

     

    1 open - open and possibly create a file or device
    2 
    3 #include <sys/types.h>
    4 #include <sys/stat.h>
    5 #include <fcntl.h>
    6 
    7 int open(const char *pathname, int flags);
    8 int open(const char *pathname, int flags, mode_t mode);

     

     想要使用 SYSIO 操作文件或设备,要先通过 open(2) 函数获得一个文件描述符。注意博文中在函数上方标识出来的头文件大家在使用这个函数的时候一定要一个不少的全部包含到源代码中

    参数列表:

      pathname:要打开的文件路径。

      flags:指定文件的操作方式,多个选项之间用按位或( | )运算符链接。

      可选项:可选项有很多,这里只介绍常用的,想要查看完全的可选项,可以查阅 man 手册。

    选项 说明
    O_APPEND 追加到文件尾部。
    O_CREAT 创建新文件。
    O_DIRECT 最小化缓冲。buffer 时写的加速机制,cache 是读的加速机制。
    O_DIRECTORY 强调一定要打开一个目录,如果 pathname 不是目录则会打开失败。
    O_LARGEFILE 打开大文件的时候要加这个,会将 off_t 定义为 64 bit,当然也可以在编译的时候使用上一篇博文提到的宏定义来指定 off_t 的长度。
    O_NOFOLLOW 如果 pathname 是符号链接则不展开,也就是说打开的是符号链接文件本身,而不是符号链接指向的文件。
    O_NONBLOCK 非阻塞形式。阻塞是读取不到数据时死等,非阻塞是尝试读取,无论能否读取到数据都返回。

     

      mode:8 进制文件权限。当 flags 包含 O_CREAT 选项时必须传这个参数,否则可以不用传这个参数。当然系统在创建文件的时候不会直接这个参数,而是通过如下的公式计算得到最终的文件权限:

    mode & ~(umask)

      具体的 umask 的值可以通过 umask(1) 命令获得。通过这样的公式进行计算可以避免程序中创建出权限过高的文件。

    不知道小伙伴们注意到没有,这个函数有一个有趣的地方。C 语言中是没有函数重载这个概念的,那么为什么这两个 open(2) 函数很像重载的函数呢?实际上它们是用可变长参数列表来实现的。

    顿时让我想起来一道面试题:如何确定一个函数是用重载实现的还是用变长参数实现的?答案是给它多传几个参数嘛,如果报错了那一定是函数重载,否则就是变长参数实现的呗。

     

    5.close(2)

    1 close - close a file descriptor
    2 
    3 #include <unistd.h>
    4 
    5 int close(int fd);

     关闭文件描述符。

    参数是要关闭的文件描述符。注意当一个文件描述符被关闭之后就不能再使用了,虽然 fd 这个变量的值没有变,但是内核已经将相关的资源释放了,这个 fd 相当于一个野指针了。

    返回值:

      成功为0,失败为-1。但很少对它的返回值做校验,一般都认为不会失败。

     

    6.read(2)

    1 read - read from a file descriptor
    2 
    3 #include <unistd.h>
    4 
    5 ssize_t read(int fd, void *buf, size_t count);

    这是 SYSIO 读取文件的函数,作用是从文件描述符 fd 中读取 count 个字节的数据到 buf 所指向的空间。

    返回值:返回成功读取到的字节数;0 表示读取到了文件末尾;-1 表示出现错误并设置 errno。

      注意 read(2) 函数与 STDIO 中的 fread(3) 函数的返回值是有区别的,fread(3) 返回的是成功读取到了多少个对象,而 read(2) 函数返回的是成功读取到的字节数量。

     

    7.write(2)

    1 write - write to a file descriptor
    2 
    3 #include <unistd.h>
    4 
    5 ssize_t write(int fd, const void *buf, size_t count);

     write(2) 是 SYSIO 向文件中写入数据的函数,作用是将 buf 中 count 字节的数据写入到文件描述符 fd 所对应的文件中。

    返回值:返回成功写入的字节数;0 并不表示写入失败,仅仅表示什么都没有写入;-1 才表示出现错误并设置 errno。

      注意 write(2) 函数与 STDIO 中的 fwrite(3) 函数的返回值是有区别的,fwrite(3) 返回的是成功写入了多少个对象,而 write(2) 函数返回的是成功写入的字节数量。

    大家想一想,为什么会出现写入的值是 0 的情况呢?其实原因有很多,其中一个原因是当写入的时候发生了阻塞,而阻塞中的 write(2) 系统调用恰巧被一个信号打断了,那么 write(2) 可能没有写入任何数据就返回了,所以返回值会是0。至于什么是阻塞,什么是信号,LZ 会在后面的博文中讲解。

     

    8.lseek(2)

    1 lseek - reposition read/write file offset
    2 
    3 #include <sys/types.h>
    4 #include <unistd.h>
    5 
    6 off_t lseek(int fd, off_t offset, int whence);

    通过上一篇博文大家知道了文件位置指针这个概念,它是系统为了方便我们读写文件而设定的一个标记,随着我们通过函数对文件的读写,它会自动相应的向文件尾部偏移。

    那么是不是说当我们读取过文件的一段内容之后,就没办法回去再次读取同一段内容了呢?

    其实不是的,通过 lseek(2) 函数就可以让我们随心所欲的控制文件位置指针了。

    参数列表:

      fd:要操作的文件描述符;

      offset:想对于 whence 的偏移量;

      whence:相对位置;三选一:SEEK_SET、SEEK_CUR、SEEK_END

        SEEK_SET 表示文件的起始位置;

        SEEK_CUR 表示文件位置指针当前所在位置;

        SEEK_END 表示文件末尾;

     返回值:

      成功时返回文件首相对于移动结束之后的文件位置指针所在位置的偏移量;失败时返回 -1 并设置 errno;

    这个函数的 offset 参数和返回值都对基本数据类型进行了封装,这一点要比标准库的 fseek(3) 更先进。

    写一段伪代码来说明这个函数的使用方法。

    1 lseek(fd, -1024, SEEK_CUR); // 从文件位置指针当前位置向前偏移 1024 个字节
    2 lseek(fd, 1024, SEEK_SET); // 从文件起始位置向后偏移 1kb
    3 lseek(fd, 1024UL*1024UL*1024UL*5UL, SEEK_SET); // 产生一个 5GB 大小的空洞文件

      

     9.time(1)

    之前讨论过 STDIO 与 SYSIO 的效率问题,所以在这里聊一聊 time(1) 命令。

    这个命令可不是用来查看系统当前时间的,想要查看系统时间得使用 date(1) 命令,这个不是我们今天要讨论的内容,所以就不说了。

    time(1) 命令的作用是监视一个程序的用户时间,从而可以粗略的帮助我们分析这个程序的执行效率。

     

     1 while ((readlen = read(srcfd, buf, BUFSIZE)) > 0) {
     2         pos = 0;
     3         while (readlen > 0) {
     4                 writelen = write(destfd, buf+pos, readlen);
     5                 if (writelen < 0) {
     6                         err = errno;
     7                         goto e_write;
     8                 }
     9                 pos += writelen;
    10                 readlen -= writelen;
    11         }
    12 }
    

     

     这是一个模仿 cp(1) 命令的程序的核心部分代码,其中的 buf 是一个 char 数组,用来作为数据读写的缓存。当 buf 的容量不同时文件拷贝的效率也是不同的,因为过于频繁的执行系统调用和使用过大的缓存都会使效率下降。如果通过不停的修改 buf 的容量的方式测试 buf 为多大的时候拷贝效率最高的话,就可以使用 time(1) 命令监视程序的执行时间。

    >$ gcc -Wall mycp_sysio.c -o mycp_sysio
    >$ time ./mycp_sysio rhel-server-6.4-x86_64-dvd.iso tmp.iso
    real 1m30.014s
    user 0m0.003s
    sys 1m29.003s

    sys 是程序在内核态消耗的时间,也就是执行系统调用所消耗的时间。 

    user 是程序在用户态消耗的时间,也就是程序本身的代码所消耗的时间。

    real 是用户等待的总时间,是 sys + user + CPU 调度时间,所以 real 时间会稍微比 sys + user 时间长一点。一个程序从提高响应素的的方式提高用户体验,一般指的就是提高 real 时间。

     

     10.文件共享

     文件共享就是指多个进程共同处理同一个文件,就是 图1 中第二个文件表项和第三个文件表项共同指向同一个 inode 的图示,不过这两个文件表项来自于不同的进程表项时才叫做文件共享。

     

     11.原子操作

     通俗来讲,原子操作就是将多个动作一气呵成的做完,中间不会被打断,要么执行完所有的步骤,要么一步也不会执行。这里用创建临时文件来举个栗子。

    1 tmpnam, tmpnam_r - create a name for a temporary file
    2 
    3 #include <stdio.h>
    4 
    5 char *tmpnam(char *s);

     

    如果我们需要创建一个临时文件,那么首先需要又操作系统提供一个文件名,然后再创建这个文件。

    tmpnam(3) 函数就是用来获得临时文件的文件名的。为什么要通过这个函数由操作系统来为我们生成文件名呢?就是因为系统中进程比较多,临时文件也比较多,怕文件重名嘛。

    但是这个函数只负责生成一个目前系统中不存在的临时文件名,并不负责创建一个文件,所以创建文件的任务要由我们自己使用 fopen(3) 或 open(2) 等手段创建。

    假设在我们拿到这个文件名的时候,临时文件还没有在磁盘上真正创建,另一个进程拿到了一个与我们相同的文件名,那么这个时候再创建文件就是有问题的了。

    这就是因为获得文件名与创建文件这个动作不原子造成的,如果获得唯一的文件名和创建文件这个动作一气呵成中间不会被打断,则这个问题就不会发生,我们创建好文件之后另一个进程就再也拿不到相同的文件名了。

    1 tmpfile - create a temporary file
    2 
    3 #include <stdio.h>
    4 
    5 FILE *tmpfile(void);

     

    既然使用 tmpnam(3) 函数无法原子的创建临时文件,那么就没有原子的方式来避免上面描述的问题了吗?当然有办法,那就是使用 tmpfile(3) 函数来创建临时文件。

    tmpfile(3) 函数是获得文件名和创建临时文件的动作一气呵成的,它直接会返回一个创建好的 FILE 结构体指针给我们;

    当然系统中有许多地方需要原子操作,不仅仅是创建临时文件,所以系统还有其它函数提供了原子操作,遇到的时候我们再讲解,这里不再详述。

     

    12.dup(2)、dup2(2)

    1 dup, dup2 - duplicate a file descriptor
    2 
    3 #include <unistd.h>
    4 
    5 int dup(int oldfd);
    6 int dup2(int oldfd, int newfd);

     

    这两个函数是用来复制文件描述符的,就是 图1 中 文件描述符 3 和 6 指向了同一个文件表项的情况。

    举个栗子,用 dup(2) 实现输出的重定向。

     

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 
     4 int main (void)
     5 {                                                                               
     6         /* 要求在不改变下面的内容的情况下,使输出的内容到文件中  */
     7 
     8         puts("dup test.");
     9 
    10         return 0;
    11 }

     

    puts(3) 函数是将参数字符串写入到标准输出 stdout(文件描述符是 1) 中,而标准输出默认目标是我们的 shell。如果想要让 puts(3) 的参数输出到一个文件中,实现思路是:首先打开一个文件获得一个新的文件描述符,然后关闭标准输出文件描述符(1),然后使用 dup(2) 函数族复制产生一个新的文件描述符,此时的 1 号文件描述符就不是标准输出的文件描述符了,而是我们自己创建的文件的描述符了。还记得我们之前提到过吗,文件描述符优先使用可用范围内最小的。进程中当前打开的文件描述符有标准输入(0)、标准输出(1)、标准错误(2)和我们自己打开的文件(3),当我们关闭了 1 号文件描述符后,当前可用的最小文件描述符是 1,所以新复制的文件描述符就是 1。而标准库函数 puts(3) 在调用系统调用 write(2) 函数向 1 号文件描述符打印时,正好是打印到了我们所指定的文件中。

     

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <fcntl.h>
     4 
     5 #include <sys/types.h>
     6 #include <sys/stat.h>
     7 
     8 int main (void)
     9 {
    10     int fd = -1;
    11 
    12     fd = open("tmp", O_WRONLY | O_CREAT | O_TRUNC, 0664);
    13     /* if error */
    14 
    15     #if 0
    16     close(1); // 关闭标准输出
    17     dup(fd);
    18     #endif
    19     dup2(fd, 1);
    20     close(fd);
    21 
    22     /* 要求在不改变下面的内容的情况下,使输出的内容到文件中  */
    23 
    24     puts("dup test.");
    25 
    26     return 0;
    27 }

     

     

    由于题目的要求是 puts(3) 上面注释以下的内容都不能修改,原则上 1 号文件描述符在这里使用完毕也需要 close(2),所以这里造成了一个内存泄漏,但并不影响对 dum(2) 函数族的解释和测试。

    上面的代码用 close(2) + dup(2) 的方式或者 dup2(2) 的方式都可以实现。

    dup(2) 和 dup2(2) 的作用是相同的,区别是 dum2(2) 函数可以用第二个参数指定新的文件描述符的编号。

    如果新的文件描述符已经被打开则先关闭它再重新打开。

    如果两个参数相同,则 dup2(2) 函数会返回原来的文件描述符,而不会关闭它。

    另外一点比较重要,close(2) + dup(2) 的方式不原子,而 dup2(2) 这两步动作是原子的,在并发的情况下可能会出现问题。后面的博文我们会通过信号和多线程来讨论并发。

     

    13.sync(2)

    1 sync, syncfs - commit buffer cache to disk
    2 
    3 #include <unistd.h>
    4 
    5 void sync(void);

     

    sync(2) 函数族的函数作用是全局催促,将 buffer 和 cache 刷新和同步到 disk,一般在设备即将卸载的时候使用。这个函数族的函数不是很常用,所以用到的时候再具体讨论,这里不再详述。

     

    14.fcntl(2)

    1 fcntl - manipulate file descriptor
    2 
    3 #include <unistd.h>
    4 #include <fcntl.h>
    5 
    6 int fcntl(int fd, int cmd, ... /* arg */ );

    这是一个管家级别的函数,根据不同的 cmd 和 arg 读取或修改对已经打开的文件的操作方式。具体的命令和参数请查阅 man 手册,这里不再花费大量篇幅赘述。

     

    15.ioctl(2)

    1 ioctl - control device
    2 
    3 #include <sys/ioctl.h>
    4 
    5 int ioctl(int d, int request, ...);

    Linux 的一切皆文件的设计原理将所有的设备都抽象为一个文件,当一个设备的某些操作不能被抽象成打开、关闭、读写、跳过等动作时,其它的动作都通过 ioctl(2) 函数控制。

    例如将声卡设备抽象为一个文件,录制音频和播放音频的动作则可以被抽象为对声卡文件的读、写操作。但是像配置频率、音色等功能无法被抽象为对文件的操作形式,那么就需要通过 ioctl(2) 函数对声卡设备进行控制,具体的控制命令则由驱动程序提供。

    16. /dev/fd

    /dev/fd 是一个虚拟目录,它里面是当前进程所使用的文件描述符信息。如果用 ls(1) 查看,则里面显示的是 ls(1) 这个进程所使用的文件描述符信息。而打开里面的文件则相当于复制文件描述符。

    展开全文
  • Linux文件IO

    2018-09-18 19:15:28
    1 基本文件操作 1.1函数说明  open()函数:用于打开或创建文件, 在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。  close()函数:用于关闭一个被打开的文件。 当一个进程终止时, 所有被它...

    1 基本文件操作

    1.1函数说明

    open()函数:用于打开或创建文件, 在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。


    close()函数:用于关闭一个被打开的文件。 当一个进程终止时, 所有被它打开的文件都由内核自动关闭, 很多程序都使用这一功能而不显式地关闭一个文件。


    read()函数:用于将从指定的文件描述符中读出的数据放到缓存区中, 并返回实际读入的字节数。 若返回 0, 则表示没有数据可读, 即已达到文件尾。 读操作从文件的当前指针位置开始。 当从终端设备文件中读出数据时, 通常一次最多读一行。


    write()函数:用于向打开的文件写数据, 写操作从文件的当前指针位置开始, 对磁盘文件进行写操作, 若磁盘已满或超出该文件的长度, 则 write()函数返回失败。


    lseek()函数:用于在指定的文件描述符中将文件指针定位到相应的位置。 它只能用在可定位(可随机访问) 文件操作中。 管道、 套接字和大部分字符设备文件不可定位的, 所以在这些文件的操作中无法使用 lseek()调用。


    2.2 函数格式

    open()函数的语法要点。

    flag 参数可通过“|” 组合构成, 但前 3 个标志常量(O_RDONLY、O_WRONLY 及 O_RDWR) 不能相互组合。

    perms 是文件的存取权限, 既可以用宏定义表示法, 也可以用八进制表示法,一般在加上 O_CREAT 后才使用这个参数。


    close()函数的语法要点。

    read()函数的语法要点。

    在读普通文件时, 若读到要求的字节数前已到达文件的尾部, 则返回的字节数会小于希望读出的字节数。


    write()函数的语法要点。

    在写普通文件时, 写操作从文件的当前指针位置开始。


    lseek()函数的语法要点。

    3.3 函数使用实例

          下面实例中的 open()函数带有 3 个 flag 参数: O_CREAT、 O_TRUNC 和 O_WRONLY,这样就可以对不同的情况指定相应的处理方法。 另外, 这里对该文件的权限设置为 0600。
        下面列出文件基本操作的实例, 基本功能是从一个文件(源文件) 中读取最后 10KB 数据并复制到另一个文件(目标文件)。 在实例中源文件是以只读方式打开的, 目标文件是以只写方式打开(可以是读/写方式) 的。 若目标文件不存在, 可以创建并设置权限的初始值
    为 644, 即文件所有者可读可写, 文件所属组和其他用户只能读。读者需要留意的地方是改变每次读/写的缓存大小(实例中为 1KB) 会怎样影响运行效率。

    其源码如下所示:

    /* copy_file.c */
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <stdio.h>
    #define BUFFER_SIZE 1 /* 每次读/写缓存大小, 影响运行效率 */
    #define SRC_FILE_NAME "src_file" /* 源文件名 */
    #define DEST_FILE_NAME "dest_file" /* 目标文件名 */
    #define OFFSET 10 /* 复制的数据大小 */
    int main()
    {
    	int src_file, dest_file;
    	unsigned char buff[BUFFER_SIZE];
    	int real_read_len;
    	/* 以只读方式打开源文件 */
    	src_file = open(SRC_FILE_NAME, O_RDONLY);
    	/* 以只写方式打开目标文件, 若此文件不存在则创建该文件, 访问权限值为 644 */
    	dest_file = open(DEST_FILE_NAME, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    	if (src_file < 0 || dest_file < 0)
    	{
    		printf("Open file error\n");
    		exit(1);
    	} 
    	/* 将源文件的读/写指针移到最后 10字节 的起始位置 */
    	lseek(src_file, -OFFSET, SEEK_END);
    	/* 读取源文件的最后 10字节 数据并写到目标文件中, 每次读写 1KB */
    	while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0)
    	{
    		write(dest_file, buff, real_read_len);
    	} 
    	close(dest_file);
    	close(src_file);
    	return 0;
    }

    2 文件锁

    2.1 fcntl()函数说明

           前面讲述的 5 个基本函数实现了文件的打开、 读/写等基本操作, 本节将讨论在文件已经共享的情况下如何操作, 也就是当多个用户共同使用、 操作一个文件的情况。 这时, Linux通常采用的方法是给文件上锁, 来避免共享的资源产生竞争的状态。
          文件锁包括建议性锁强制性锁

    建议性锁要求每个上锁文件的进程都要检查是否有锁存在, 若已有锁,可以选择继续写,也可以选择不写。 在一般情况下, 内核和系统都不使用建议性锁。

    强制性锁是由内核执行的锁, 当一个文件被上锁进行写入操作时, 内核将阻止其他任何文件对其进行读写操作。 采用强制性锁对性能的影响很大, 每次读写操作都必须检查是否有锁存在。
          在 Linux 中, 实现文件上锁的函数有 lockf()和 fcntl()

    lockf()用于对文件施加建议性锁 

    fcntl()不仅可以施加建议性锁, 还可以施加强制性锁。 同时, fcntl()还能对文件的某一记录上锁, 也就是记录锁。
          记录锁又可分为读取锁和写入锁

    读取锁又称为共享锁, 它能够使多个进程都能在文件的同一部分建立读取锁。

    写入锁又称为排斥锁, 在任何时刻只能有一个进程在文件的某个部分建立写入锁。 当然, 在文件的同一部分不能同时建立读取锁和写入锁。
          fcntl()函数具有很丰富的功能, 它可以对已打开的文件描述符进行各种操作, 不仅包括管理文件锁,还包括获得设置文件描述符和文件描述符标志、文件描述符的复制等很多功能。


    本文主要介绍 fcntl()函数建立记录锁的方法, 关于它的其他操作, 感兴趣的读者可以参看fcntl 手册。


    2.2 fcntl()函数格式

    用于建立记录锁的 fcntl()函数语法。

    这里, lock 的结构如下所示:
     

    struct flock {
                 short l_type; /* 锁类型: F_RDLCK, F_WRLCK, F_UNLCK */
                 short l_whence; /*取值为SEEK_SET(文件头), SEEK_CUR(文件当前位置), SEEK_END(文件尾) */
                 off_t l_start; /* 相对于l_whence字段的偏移量 */
                 off_t l_len; /* 需要锁定的长度 */
                 pid_t l_pid; /* 当前获得文件锁的进程标识(F_GETLK) */
    };

    lock 结构中每个变量的取值含义。

    为加锁整个文件, 通常的方法是将 l_start 设置为 0, l_whence 设置为 SEEK_SET, l_len设置为 0。


    2.3 fcntl()使用实例

          下面首先给出了使用 fcntl()函数的文件记录锁功能的代码实现。 在该代码中, 首先给flock 结构体的对应位赋予相应的值。
          接着调用两次 fcntl()函数。 用 F_GETLK 命令判断是否可以进行 flock 结构所描述的锁操作: 若可以进行, 则 flock 结构的 l_type 会被设置为 F_UNLCK, 其他域不变; 若不可进行, 则 l_pid 被设置为拥有文件锁的进程号, 其他域不变。

          用 F_SETLK 和 F_SETLKW 命令设置 flock 结构所描述的锁操作,后者是前者的阻塞版。
           当第一次调用 fcntl()时, 使用 F_GETLK 命令获得当前文件被上锁的情况, 由此可以判断能不能进行上锁操作; 当第二次调用 fcntl()时, 使用 F_SETLKW 命令对指定文件进行上锁/解锁操作。 因为 F_SETLKW 命令是阻塞式操作, 所以, 当不能把上锁/解锁操作进行下
    去时, 运行会被阻塞, 直到能够进行操作为止。

    文件记录锁的功能代码具体如下所示:
     

    /* lock_set.c */
    int lock_set(int fd, int type)
    {
    	struct flock old_lock, lock;
    	lock.l_whence = SEEK_SET;
    	lock.l_start = 0;
    	lock.l_len = 0;
    	lock.l_type = type;
    	lock.l_pid = -1;
    	/* 判断文件是否可以上锁 */
    	fcntl(fd, F_GETLK, &lock);
    	if (lock.l_type != F_UNLCK)
    	{
    		/* 判断文件不能上锁的原因 */
    		if (lock.l_type == F_RDLCK) /* 该文件已有读取锁 */
    		{
    			printf("Read lock already set by %d\n", lock.l_pid);
    		}
    		else if (lock.l_type == F_WRLCK) /* 该文件已有写入锁 */
    		{
    			printf("Write lock already set by %d\n", lock.l_pid);
    		}
    		} 
    		/* l_type 可能已被 F_GETLK 修改过 */
    		lock.l_type = type;
    		/* 根据不同的 type 值进行阻塞式上锁或解锁 */
    		if ((fcntl(fd, F_SETLKW, &lock)) < 0)
    		{
    			printf("Lock failed:type = %d\n", lock.l_type);
    			return 1;
    		} 
    		switch(lock.l_type)
    		{
    			case F_RDLCK:
    			{
    				printf("Read lock set by %d\n", getpid());
    			}break;
    			case F_WRLCK:
    			{
    				printf("Write lock set by %d\n", getpid());
    			}break;
    			case F_UNLCK:
    			{
    				printf("Release lock by %d\n", getpid());
    				return 1;
    			}break;
    			default:	break;
    		}/* end of switch */
    	return 0;
    }

    下面的实例是文件写入锁的测试用例, 这里首先创建了一个 hello 文件, 之后对其上写入锁, 最后释放写入锁。 代码如下所示:
     

    /* write_lock.c */
    #include <unistd.h>
    #include <sys/file.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include "lock_set.c"
    int main(void)
    {
    	int fd;
    	/* 首先打开文件 */
    	fd = open("hello",O_RDWR | O_CREAT, 0644);
    	if(fd < 0)
    	{
    		printf("Open file error\n");
    		exit(1);
    	} 
    	/* 给文件上写入锁 */
    	lock_set(fd, F_WRLCK);
    	getchar();
    	/* 给文件解锁 */
    	lock_set(fd, F_UNLCK);
    	getchar();
    	close(fd);
    	exit(0);
    }

         为了能够使用多个终端, 更好地显示写入锁的作用, 本实例主要在 PC 上测试, 读者可将其交叉编译, 下载到目标板上运行。 下面是在 PC 上的运行结果。 为了使程序有较大的灵活性, 笔者采用文件上锁后由用户输入任意键使程序继续运行。 建议读者开启两个终端, 并且在两个终端上同时运行该程序, 以达到多个进程操作一个文件的效果。 在这里, 笔者首先运行终端一, 请读者注意终端二中的第一句。
    终端一:
    $ ./write_lock
    write lock set by 4994
    release lock by 4994
    终端二:
    $ ./write_lock
    write lock already set by 4994
    write lock set by 4997
    release lock by 4997
    由此可见, 写入锁为互斥锁, 同一时刻只能有一个写入锁存在。
    接下来的程序是文件读取锁的测试用例, 原理与上面的程序一样。
     

    /* fcntl_read.c */
    #include <unistd.h>
    #include <sys/file.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include "lock_set.c"
    
    /* lock_set.c */
    int lock_set(int fd, int type)
    {
    	struct flock old_lock, lock;
    	lock.l_whence = SEEK_SET;
    	lock.l_start = 0;
    	lock.l_len = 0;
    	lock.l_type = type;
    	lock.l_pid = -1;
    	/* 判断文件是否可以上锁 */
    	fcntl(fd, F_GETLK, &lock);
    	if (lock.l_type != F_UNLCK)
    	{
    		/* 判断文件不能上锁的原因 */
    		if (lock.l_type == F_RDLCK) /* 该文件已有读取锁 */
    		{
    			printf("Read lock already set by %d\n", lock.l_pid);
    		}
    		else if (lock.l_type == F_WRLCK) /* 该文件已有写入锁 */
    		{
    			printf("Write lock already set by %d\n", lock.l_pid);
    		}
    		} 
    		/* l_type 可能已被 F_GETLK 修改过 */
    		lock.l_type = type;
    		/* 根据不同的 type 值进行阻塞式上锁或解锁 */
    		if ((fcntl(fd, F_SETLKW, &lock)) < 0)
    		{
    			printf("Lock failed:type = %d\n", lock.l_type);
    			return 1;
    		} 
    		switch(lock.l_type)
    		{
    			case F_RDLCK:
    			{
    				printf("Read lock set by %d\n", getpid());
    			}break;
    			case F_WRLCK:
    			{
    				printf("Write lock set by %d\n", getpid());
    			}break;
    			case F_UNLCK:
    			{
    				printf("Release lock by %d\n", getpid());
    				return 1;
    			}break;
    			default:	break;
    		}/* end of switch */
    	return 0;
    }
    
    int main(void)
    {
    	int fd;
    	fd = open("hello",O_RDWR | O_CREAT, 0644);
    	if(fd < 0)
    	{
    		printf("Open file error\n");
    		exit(1);
    	} 
    	/* 给文件上读取锁 */
    	lock_set(fd, F_RDLCK);
    	getchar();
    	/* 给文件解锁 */
    	lock_set(fd, F_UNLCK);
    	getchar();
    	close(fd);
    	exit(0);
    }

    同样开启两个终端, 并首先启动终端一上的程序, 其运行结果如下所示。
    终端一:
    $ ./read_lock
    read lock set by 5009
    release lock by 5009
    终端二:
    $ ./read_lock
    read lock set by 5010
    release lock by 5010
    读者可以将此结果与写入锁的运行结果相比较, 可以看出, 读取锁为共享锁, 当进程
    5009 已设置读取锁后, 进程 5010 仍然可以设置读取锁。


    3 多路复用

    3.1 函数说明
        前面的 fcntl()函数解决了文件的共享问题, 接下来该处理 I/O 复用的情况了。
    总的来说, I/O 处理的模型有 5 种。
    1.阻塞 I/O 模型: 在这种模型下, 若所调用的 I/O 函数没有完成相关的功能, 则会使进程挂起, 直到相关数据到达才会返回。 如常见对管道设备、 终端设备和网络设备进行读写时经常会出现这种情况。


        2.非阻塞 I/O 模型: 在这种模型下, 当请求的 I/O 操作不能完成时, 则不让进程睡眠,而且立即返回。 非阻塞 I/O 使用户可以调用不会阻塞的 I/O 操作, 如 open()、 write()和 read()。 如果该操作不能完成, 则会立即返回出错(如打不开文件) 或者返回 0(如
    在缓冲区中没有数据可以读取或者没空间可以写入数据)。

    如何实现非阻塞IO访问:

    O_NONBLOCK: 只能在打开的时候设置

    fcntl: 可以在文件打开后设置


    3.I/O 多路复用模型: 在这种模型下, 如果请求的 I/O 操作阻塞, 且它不是真正阻塞I/O, 而是让其中的一个函数等待, 在此期间, I/O 还能进行其他操作。 外部表现为阻塞式,内部非阻塞式自动轮询多路阻塞式IO。其实内部就是while加非阻塞(类似),但是它内部不会占用CPU 太多时间。

    优点:不会浪费太多CPU时间,且达到while加非阻塞的效果

    缺点:

    1.select和poll本身是阻塞式的,当里面的条件没满足或者超时,就会一直阻塞,

    2.这两个函数可以同时注册多个阻塞,但是只要有一个阻塞发生就会马上退出函数,而不会等其它阻塞发生,更不会等全部阻塞发生后才退出

     

    4.信号驱动 I/O 模型: 在这种模型下, 进程要定义一个信号处理程序, 系统可以自动捕获特定信号的到来, 从而启动 I/O。 这是由内核通知用户何时可以启动一个 I/O操作决定的。它是非阻塞的。 当有就绪的数据时, 内核就向该进程发送 SIGIO 信号。 无论我们如何处理 SIGIO 信号, 这种模型的好处是当等待数据到达时, 可以不阻塞。 主程序继续执行,只有收到 SIGIO 信号时才去处理数据即可。


    5.异步 I/O 模型: 在这种模型下, 进程先让内核启动 I/O 操作, 并在整个操作完成后通知该进程。 这种模型与信号驱动模型的主要区别在于: 信号驱动 I/O 是由内核通知我们何时可以启动一个 I/O 操作, 而异步 I/O 模型是由内核通知进程 I/O 操作何时完成的。 现在, 并不是所有的系统都支持这种模型。

    涉及的函数:
    (1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)设置文件的异步IO属性
    (2)signal或者sigaction(SIGIO)注册异步IO处理函数
    优点:1.类似中断处理,可以一直检测,
    2.当一个鼠标阻塞发生,然后执行相应处理函数后,还可以继续检测鼠标阻塞,而不用手动又来重新添加一个鼠标阻塞,


            可以看到, select()和 poll()的 I/O 多路转接模型是处理 I/O 复用的一个高效的方法。 它可以具体设置程序中每一个所关心的文件描述符的条件、 希望等待的时间等, 从 select()和poll()函数返回时, 内核会通知用户已准备好的文件描述符的数量、 已准备好的条件(或事件) 等。 通过使用 select()和 poll()函数的返回结果(可能是检测到某个文件描述符的注册事件或是超时, 或是调用出错), 就可以调用相应的 I/O 处理函数了。


    3.2 函数格式

    select()函数的语法要点。

    可以看到, select()函数根据希望进行的文件操作对文件描述符进行了分类处理, 这里对文件描述符的处理主要涉及 4 个宏函数

    一般来说,

    在每次使用 select()函数之, 首先使用 FD_ZERO()和 FD_SET()来初始化文件描述符集(在需要重复调用 select()函数时, 先把一次初始化好的文件描述符集备份下来,每次读取它即可)。

    在 select()函数返回, 可循环使用 FD_ISSET()来检查描述符集, 在执行完对相关文件描述符的操作后, 使用 FD_CLR()来清除描述符集。

    FD_ISSET(int fd, fd_set *set) 这个函数很有意思,当注册完一个文件后,再调用这个函数检查这个文件描述符,得到的值式非零,

    当调用了select()后,再退出后,如果这个文件描述符的阻塞没有发生,则再调用这个函数就会返回0,如果发生了就会返回非0

    可以用于检测阻塞有没有发生

    另外, select()函数中的 timeout 是一个 struct timeval 类型的指针,该结构体如下所示:

    struct timeval
    {
        long tv_sec; /* 秒 */
        long tv_unsec; /* 微秒 */
    }

    可以看到, 这个时间结构体的精确度可以设置到微秒级, 这对于大多数的应用而言已经
    足够了。
    poll()函数的语法要点。

    3.3 使用实例

            当使用 select()函数时, 存在一系列的问题, 例如, 内核必须检查多余的文件描述符,每次调用 select()之后必须重置被监听的文件描述符集, 而且可监听的文件个数受限制(使用 FD_SETSIZE 宏来表示 fd_set 结构能够容纳的文件描述符的最大数目) 等。 实际上,poll机制 比 select 机制相比效率更高, 使用范围更广。

    select()函数的实例。

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/select.h>
    #include <sys/time.h>
    int main(void)
    {
    	// 读取鼠标
    	int fd = -1, ret = -1;
    	char buf[200];
    	fd_set myset;   //创建文件描述符集
    	struct timeval tm;
    	fd = open("/dev/input/mouse0", O_RDONLY);
    	if (fd < 0)
    	{
    		perror("open:");
    		return -1;
    	}
    	// 当前有2个fd,一共是fd一个是0
    	// 处理myset
    	FD_ZERO(&myset);     //初始化文件描述符集
    	FD_SET(fd, &myset);  //添加文件描述
    	FD_SET(0, &myset);  //添加文件描述
    	if (FD_ISSET(0, &myset))
    	{
    		printf("键盘读出的内容是:.\n");
    	}
    	if (FD_ISSET(fd, &myset))
    	{
    		printf("鼠标读出的内容是:.\n");
    	}
    	tm.tv_sec = 5;
    	tm.tv_usec = 0;
    	ret = select(fd+1, &myset, NULL, NULL, &tm);
    	if (ret < 0)
    	{
    		perror("select: ");
    		return -1;
    	}
    	else if (ret == 0)
    	{
    		printf("超时了\n");
    	}
    	else
    	{
    		// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
    		if (FD_ISSET(0, &myset))
    		{
    			// 这里处理键盘
    			memset(buf, 0, sizeof(buf));
    			read(0, buf, 5);
    			printf("键盘读出的内容是:[%s].\n", buf);
    		}
    		if (FD_ISSET(fd, &myset))
    		{
    			// 这里处理鼠标
    			memset(buf, 0, sizeof(buf));
    			read(fd, buf, 50);
    			printf("鼠标读出的内容是:[%s].\n", buf);
    		}
    	}
    	return 0;
    }

    poll()函数的实例。
        

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <poll.h>
    int main(void)
    {
    	// 读取鼠标
    	int fd = -1, ret = -1;
    	char buf[200];
    	struct pollfd myfds[2] = {0};
    	fd = open("/dev/input/mouse0", O_RDONLY);
    	if (fd < 0)
    	{
    		perror("open:");
    		return -1;
    	}
    	// 初始化我们的pollfd
    	myfds[0].fd = 0;			// 键盘
    	myfds[0].events = POLLIN;	// 等待读操作
    	myfds[1].fd = fd;			// 鼠标
    	myfds[1].events = POLLIN;	// 等待读操作
    	ret = poll(myfds, 2, 10000);
    	if (ret < 0)
    	{
    		perror("poll: ");
    		return -1;
    	}
    	else if (ret == 0)
    	{
    		printf("超时了\n");
    	}
    	else
    	{
    		// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
    		if (myfds[0].events == myfds[0].revents)
    		{
    			// 这里处理键盘
    			memset(buf, 0, sizeof(buf));
    			read(0, buf, 5);
    			printf("键盘读出的内容是:[%s].\n", buf);
    		}
    		if (myfds[1].events == myfds[1].revents)
    		{
    			// 这里处理鼠标
    			memset(buf, 0, sizeof(buf));
    			read(fd, buf, 50);
    			printf("鼠标读出的内容是:[%s].\n", buf);
    		}
    	}
    	return 0;
    }
    

     

     

     


     

    展开全文
  • 一、文件文件类型 1、文件定义  定义:文件(File)是一个具有符号名字的一组相关联元素的有序序列。文件可以包含的内容十分广泛,操作系统和用户都可以将具有一定独立功能的一个程序模块、一组数据或一组文字...
  • 文件IO操作大全

    2017-06-10 13:38:39
    一、文件基本知识 “文件是指一组相关数据的有序集合”,这个数据集的名称就是文件名。所有文件都是通过流的形式进行输入输出操作的。流是一个数据字节的序列。流入程序的流称为输入流,流出程序的流称为输出流。 流...
  • Linux文件IO详解

    2018-06-04 19:48:43
    文件IO 不带缓存的IO函数:open, read, write, lseek和close。每个函数对应内核的一个系统调用,这些函数不是C语言标准,但属于POSIX接口。 文件描述符 对Linux而言,所有的打开文件都由文件描述符引用。文件...
  • Linux文件IO_01

    2019-04-08 16:22:43
    Linux文件IO0.综述1.Linux常用文件IO接口(API)3.IO实例3.1打开文件3.2读取文件内容3.3向文件中写入内容 0.综述 IO就是input和output,文件IO就是文件的读写。文件没有打开时是存放在块设备中的文件系统里的,这样...
  • 在谈两者区别之前,先来了解下什么是标准IO以及文件IO。1 定义标准IO: 标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头中的定义,具有一定的可移植性。标准IO库处理很多细节。例如缓存...
  • 文件IO 就指的是我们通常讲的open、close、write、read等API函数构成的一套用来读写文件的体系,这套体系可以很好的完成文件读写,但是效率并不是最高的。 标准IO应用层C语言库函数提供了一些用来做文件读写的函数...
  • 文件IO消耗分析

    2013-02-07 14:57:17
    Linux在操作文件时,将数据放入文件缓存区,直到内存不够或系统要释放内存给用户进程使用,因此在查看Linux内存状况时经常会发现可用(free)的物理内存不多,但是cached用了很多,这是Linux提升文件IO速度的一种...
  • 文件IO与标准IO: 1.文件OI又称为低磁盘IO 遵循POSIX标准,标准IO又称为高磁盘IO,遵循ANSI C相关标准。 Linux中使用的的是GLIBC,它是标准C库的超集,不仅包含ANSI C中定义的函数,也包含POSIX标准中定义的函数。...
  • 一、文件IO 大多数UNIX文件IO只需用到5个函数:open、read、write、lseek以及close。 这些函数经常被称为不带缓存的IO(unbuffered IO),不带缓存是指每个read和write都调用内核中的一个系统调用。 这些不带缓存的IO...
  • 漫谈linux之文件IO

    2014-01-23 14:33:38
    同事的文章,觉得写得很清晰,收藏了。 在Linux 开发中,有几个关系到性能的东西,技术人员非常关注:...阐述之前,要先有个大视角,让我们站在万米高空,鸟瞰我们的文件IO,它们设计是分层的,分层有2个好处,一是
  • 本地普通文件IO一般都是同步阻塞的本地普通文件IO一般关注的是缓存,一般都是同步阻塞的。普通文件的file descriptor是block也是POSIX标准的。这点不同于网络IO,网络IO要考虑传输两边进程处理等,设计之初就提供了...
  • Linux 文件IO理解

    2016-01-05 19:17:20
    linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作。不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据...
  • 漫谈linux文件IO

    2012-08-23 16:42:53
    这篇文章写的比较全面,也浅显易懂,备份下。... 在Linux 开发中,有几个关系到性能的东西,技术人员非常关注:进程,CPU...剖析文件IO的细节。从多个角度探索如何提高IO性能。本文尽量用通俗易懂的视角去阐述。不copy内
1 2 3 4 5 ... 20
收藏数 1,339,122
精华内容 535,648
关键字:

文件io