跳到主要内容

2. 文件编程

2.1 什么是文件I/O

I/O是指输入/输出(Input/Output)的缩写,在Linux系统中,所有的输入和输出都是通过文件进行的,这也被称为文件I/O。 Linux文件I/O指的是在Linux操作系统中主存和外部设备(比如硬盘、U盘)进行文件输入和输出的过程,其中数据从设备到内存的过程称为输入,数据从内存到设备的过程叫输出。

2.2 Linux文件I/O编程的基本方式

  • 标准I/O(stdio):标准I/O函数库提供了一系列高级的文件I/O函数,如fopen、fclose、fread、fwrite等,它们可以帮助程序员更方便地进行文件操作。标准I/O函数库还提供了缓冲区机制,可以提高文件I/O的效率。
  • 文件I/O(syscall):系统调用I/O是直接使用系统调用进行文件操作,例如open、read、write、close等函数。与标准I/O函数库不同,系统调用I/O函数没有缓冲区,文件I/O的效率更高。
  • 原始I/O(raw I/O):原始I/O是一种低级别的文件I/O,使用read和write函数进行数据读写,可以更加细粒度地控制文件的I/O操作。原始I/O一般用于对特殊设备进行操作,例如磁盘分区、网络设备等等。 这三种方式在Linux系统中都可以使用,选择哪一种方式主要取决于具体的应用场景和需要。标准I/O适用于大多数文件I/O操作,可以提高程序的开发效率;系统调用I/O适用于对文件进行更底层的操作,可以提高文件I/O的效率;原始I/O则适用于对特殊设备进行操作。

2.3 文件描述符

  • 对于文件I/O来说,一切都是通过文件描述符进行的。在Linux系统中,当打开或者创建一个文件的时候,内核向进程返回一个对应的文件描述符(非负整数)。同时还规定一个进程启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4......

  • POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h

2.4 打开或创建文件

  • Linux提供open函数来打开或者创建一个文件。该函数声明如下:

    #include <sys/types.h>
    #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表示文件称,可以包含(绝对相对)路径;
    • flags 表示文件打开方式;
    • mode用来规定对该文件的所有者、文件的用户组及系统中其他用户的访问权限。
    • 如果函数行成功,就返回文件描述符,如果函数执行失败,就返回-1。
  • 文件打开的方式flags可以使用下列宏 (当有多个选项时,采用“|”连接):

    • O_RDONLY:打开一个只供读取的文件。
    • O_WRONLY: 打开一个只供写入的文件。
    • O_RDWR:打开一个可供读写的文件。
    • O_APPEND:写入的所有数据将被追加到文件的末尾。
    • O_CREAT:打开文件,如果文件不存在就建立文件。
    • O_EXCL:如果已经置O_CREAT且文件存在,就强制open失败。
    • O_TRUNC:在打开文件时,将文件的内容清空。
  • mode只有创建文件时才使用此参数,指定文件的访问权限。模式有:

    • S_IRUSR:文件所有者的读权限位。
    • S_IWUSR:文件所有者的写权限位。
    • S_IXUSR: 文件所有者的执行权限位。
    • S_IRWXU: S IRUSRS IWUSRS IXUSR。
    • S_IRGRP:文件用户组的读权限位。
    • S_IWGRP.文件用户组的写权限位。
    • S_IXGRP:文件用户组的执行权限位。
    • S_IRWXG: S_IRGRP|S_IWGRPI|S_IXGRP。
    • S_IROTH:文件其他用户的读权限位。
    • S_IWOTH:文件其他用户的写权限位。
    • S_IXOTH: 文件其他用户的执行权限位。
    • S_IRWXO:S_IROTH|S_IWOTH|IXOTH。
  • 文件访问权限的计算是根据umask&~mode得出来的。

    • 当我们登录 Linux 系统之后创建文件总有一个默认权限,设置用户创建文件的默认权限就是umask所做的工作。umask 设置文件权限通过指定建立文件时预设的权限掩码来实现,不能默认创建可执行文件,必须手工赋予执行权限,所以创建文件时默认为最大权限 666,而目录默认最大权限为777。

    • 计算方法:

      • 例如umask值为022:

        linaro@linaro-alip:~$ umask
        0022
      • 则默认文件的权限为:110 110 110 & (~000 010 010) = 110 110 110 & 111 101 101 = 110 100 100

      • 即默认权限为:644

  • 打开当前工作目录 luckfox 下的文件test.txt:

    int fd = open("./luckfox/test.txt", O_RDWR)
  • 如果要以只读方式打开当前目录上级目录下的某个文件:

    int fd = open("../luckfox .txt", O_RDWR)
  • 打开绝对路径下的文件(不存在就创建,否则以读写方式打开):

    int fd = open("./luckfox/test.txt", O_CREAT| O_RDWR)
  • 创建文件:新建一个open.cpp文件,在open.cpp中输入代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, char *argv[])
    {
    int fd;
    fd = open("luckfox.txt", O_CREAT | O_RDWR, 0666);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }
    printf("fd is %d\n", fd);
    return 0;
    }
    • 如果函数执行成功,返回的文件描述符为3;否则返回-1。

2.5 读取文件的数据

  • Linux提供read函数从打开的文件中读取数据,该函数声明如下:

    #include<unistd.h>
    ssize_t read(int fd, void * buf ,size_t count)
  • 参数含义:

    • fd为文件描述符;
    • buf表示读出数据缓冲区地址;
    • count表示读出的字节数。
  • 函数含义:把参数fd所指的文件传送count个字节到buf指针所指的内存中。

  • 返回值:若读取成功,则返回实际读到的字节数。若失败,返回-1。若已达到文件尾或没有可读取数据,则返回0。注意:因此读到的字节数可能小于count的值。

  • 从文件中读取数据:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, char *argv[])
    {
    int fd;
    char buf[32] = {0};
    ssize_t ret;
    fd = open("luckfox.txt", O_RDWR);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }
    printf("fd is %d\n", fd);
    ret = read(fd,buf,32);
    if (ret == -1)
    {
    printf("read is error\n");
    return -2;
    }
    printf("buf is %s", buf);
    printf("ret is %ld\n", ret);
    close(fd);
    return 0;
    }

2.6 向文件写入数据

  • Linux提供write函数将数据写入已打开的文件内,该函数声明如下:

    #include<unistd.h>
    ssize_t write(int fd, void * buf ,size_t count)
  • 参数含义:

    • fd为文件描述符;
    • buf表示读出数据缓冲区地址;
    • count表示读出的字节数。
  • 函数含义:把参数buf所指的缓冲区中的count个字节数数据写入fd所指的文件内。

  • 向文件中写入数据:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <cstring>

    int main(int argc, char *argv[])
    {
    int fd;
    ssize_t ret;
    char buf[] = "boys and girls\n hi,children!";
    char filename[] = "luckfox.txt";

    fd = open(filename,O_RDWR|O_APPEND);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }
    printf("fd is %d\n", fd);
    ret = write(fd,buf,strlen(buf));
    printf("write %d bytes to file %s\n",ret,filename);
    close(fd);
    return 0;
    }

2.7 文件偏移量

  • 在实际应用中,有的时候需要从文件中某个位置开始读写,此时需要让文件读写位置移动到新的位置。在Linux系统中可使用函数 lseek 来修改文件偏移量 ( 读写位置 ),该函数声明如下:

    #include <sys/types.h>
    #include<unistd.h>

    off_t lsweek(int fd, off_t offset, int whence)
  • 参数含义:

    • fd为文件描述符;
    • offset:偏移量,以字节为单位,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移。
    • whence:与参数offset偏移量搭配使用,该参数为当前位置的基点,可以使用一下三种值:
      • SEEK_SET:相对于文件开头;
      • SEEK_CUR:相对于当前的文件读写指针位置;
      • SEEK_END:相对于文件末尾。
  • 把文件位置指针设置为100:

    lseek(fd,100,SEEK SET);
  • 把文件位置设置成文件末尾:

    lseek(fd,0,SEEK END);
  • 确定当前的文件位置:

    lseek(fd,0,SEEK CUR);
  • 第二次重新开始读取文件:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, char *argv[])
    {
    int fd;
    char buf[32] = {0};
    ssize_t ret;
    fd = open("luckfox.txt", O_CREAT | O_RDWR, 0666);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }
    // printf("fd is %d\n", fd);

    ret = read(fd, buf, 32);
    if (ret < 0)
    {
    printf("read is error\n");
    return -2;
    }
    printf("buf is %s\n", buf);
    printf("ret is %ld\n", ret);

    lseek(fd,0,SEEK_SET);
    ret = read(fd, buf, 32);

    printf("ret is %ld\n", ret);
    close(fd);
    return 0;
    }
  • 确定文件的当前位置:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, char *argv[])
    {
    int fd;
    char buf[32] = {0};
    ssize_t ret;
    fd = open("luckfox.txt", O_CREAT | O_RDWR, 0666);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }

    ret = read(fd, buf, 8);
    if (ret < 0)
    {
    printf("read is error\n");
    return -2;
    }
    printf("buf is %s\n", buf);
    printf("ret is %ld\n", ret);

    ret=lseek(fd,0,SEEK_CUR);

    printf("ret is %ld\n", ret);
    close(fd);
    return 0;
    }
  • 查看文件大小(字节数):

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, char *argv[])
    {
    int fd;
    char buf[32] = {0};
    ssize_t ret;
    fd = open("luckfox.txt", O_CREAT | O_RDWR, 0666);
    if (fd == -1)
    {
    printf("open is error\n");
    return -1;
    }


    ret = read(fd, buf, 2);
    if (ret < 0)
    {
    printf("read is error\n");
    return -2;
    }
    printf("buf is %s\n", buf);
    printf("ret is %ld\n", ret);

    ret=lseek(fd,0,SEEK_END);


    printf("ret is %ld\n", ret);
    close(fd);
    return 0;
    }