基础IO详解(一)

简介: 基础IO详解

一、系统文件IO

操作文件除了使用C语言、C++以及其他的语言的接口之外,还可以使用操作系统提供的接口


1.1 open


d778404a5b0849c2b60cd66ef9554962.png

1.1.1 open的第一个参数

open函数的第一个参数是pathname,表示要打开或创建的目标文件


若pathname以路径的方式给出,则需要创建该文件时,在pathname路径下进行创建

若pathname以文件名的方式给出,则需要创建该文件时,默认在当前路径下进行创建

1.1.2 open的第二个参数

open函数的第二个参数是flags,表示打开文件的方式


49e4338e099a4609ba698facd0d2983b.png


上面提供的参数选项仅仅是较为常用的,具体可以man 2 open命令进行查看


打开文件时可以传入多个参数选项,当有多个选项传入时,将这些选项用"按位或"隔开


传参原理:


系统接口open的第二个参数flags为整型类型,在32位平台上有32个bit位。若将一个bit位作为一个标志位,则理论上flags可以传递32种不同的标志位。而实际上flags的参数选项都是宏


352665d3fa8446279074081d1f54fbef.png


在oen函数内部就可以通过使用一系列的位运算来判断是否设置了某一选项。


1.1.3 open的第三个参数

open函数的第三个参数是mode,表示创建文件的默认权限


传入0666参数进行文件创建,按理应得到-rw-rw-rw-权限的文件,但却得到了如下图的文件


6e1f187b8ccd4b6d841212c36659325c.png


这是因为权限掩码的存在(默认为0002),可以在代码中设置权限掩码来避免上述情况发生。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    umask(0000);//设置文件掩码                                                                     
    int fd = open("./log.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    close(fd);
    return 0;
}

7998c4c6faa14baf8892fda1eeeeb1f3.png

注意: 当不需创建文件时,可以不用设置第三个参数


1.1.4 open的返回值

open函数成功调用后返回打开文件的文件描述符,若调用失败则返回-1。文件描述符具体在后面进行讲解。


1.2 close

使用close函数时传入需要关闭文件的文件描述符(即调用open函数的返回值)即可。若关闭文件成功则返回0;若关闭文件失败则返回-1。


9bf52865c1374bedb898e64330cab649.png


1.3 write

Linux系统接口中使用write函数向文件写入信息

fa0541dd5f1841b0b75cb4b9d1c1307a.png



将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。


若数据写入成功,则返回实际写入数据的字节数。

若数据写入失败,则返回-1。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd = open("./log.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    if(fd < 0){
        perror("open:");
        exit(1);
    }
    const char* buf = "hello Linux!\n";
    write(fd, buf, strlen(buf));                                                     
    close(fd);
    return 0;
}


2978d64b25d4432fa2319da986294d07.png

1.4 read

Linux系统接口中使用write函数向文件写入信息


d07790cf7c0a4d7c943b060ea997bd94.png


从文件描述符为fd的文件读取count字节的数据到buf位置当中


若数据读取成功,则返回实际读取数据的字节数

若数据读取失败,则返回-1

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd = open("./log.txt",O_RDONLY);
    if(fd < 0){
        perror("open:");
        exit(1);
    }
    char buf[64] = {0};
    while(read(fd, buf, 63)){
        printf("%s",buf);                                                                                                                                                        
        memset(buf,0x00,64);
    }
    close(fd);
    return 0;
}


f05bd1bc287046e993c17c60051080e4.png


二、文件描述符

2.1 进程与文件描述符

文件被访问的前提是被加载到内存中打开,一个进程可以打开多个文件,而系统当中又存在大量进程。即在系统中任何时刻都可能存在大量已经打开的文件,所以操作系统内部提供了struct file结构体用于描述文件(先描述,再组织),再使用双链表进行管理。

而为了区分已经打开的文件哪些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系。


进程与文件之间的对应关系是如何确定的呢?


task_struct(PCB)中有一个指针,该指针指向一个名为files_struct的结构体。在该结构体中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。


当一个进程打开其第一个文件时,先将该文件从磁盘当中加载到内存,形成对应的struct file变量,将该struct file变量链入文件双链表中,并将该结构体的首地址填入到fd_array数组中下标为3的位置,最后返回该文件的文件描述符给调用进程。

eb35f06da3f245e79d68617ec948327a.png



因此只需要有某一文件的文件描述符,就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作。


内存文件 VS 磁盘文件


当文件存储在磁盘当中时,被称为磁盘文件;当磁盘文件被加载到内存中后,此时的文件被称为内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序加载到内存运行起来后便成了进程,而当磁盘文件加载到内存后便成了内存文件。


磁盘文件由两部分构成,分别是文件内容和文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息。

文件加载到内存时,一般先加载文件的属性信息,当需要对文件内容进行读取、输入或输出等操作时,再延后式的加载文件数据。


2.2 文件描述符的分配规则

Linux进程会默认打开3个文件描述符,分别是标准输入0、标准输出1、标准错误2

分别对应的物理设备一般为:键盘、显示器、显示器


键盘和显示器都属于硬件,且操作系统能够识别。当某一进程创建时,操作系统就会根据键盘、显示器、显示器形成各自的struct file变量,将这3个struct file变量连入文件双链表当中,并将这3个struct file变量的地址分别填入fd_array数组下标为0、1、2的位置,至此就默认打开了标准输入流、标准输出流和标准错误流。

#include <stdio.h>                                                                                                                                                               
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    int fd1 = open("./log1.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd2 = open("./log2.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd3 = open("./log3.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd4 = open("./log4.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd5 = open("./log5.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    printf("fd1: %d\n",fd1);
    printf("fd2: %d\n",fd2);
    printf("fd3: %d\n",fd3);
    printf("fd4: %d\n",fd4);
    printf("fd5: %d\n",fd5);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
    return 0;
}

a291e08c098a4415ac0534f0d18553bf.png



则后续打开的文件的文件描述符,从3开始逐个增加1。


在打开文件之前将文件描述符为0和2的文件关闭又会发生什么呢?


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    close(0);
    close(2);                                                                                                                                                                    
    int fd1 = open("./log1.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd2 = open("./log2.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd3 = open("./log3.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd4 = open("./log4.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    int fd5 = open("./log5.txt",O_RDWR | O_CREAT | O_TRUNC,0666);
    printf("fd1: %d\n",fd1);
    printf("fd2: %d\n",fd2);
    printf("fd3: %d\n",fd3);
    printf("fd4: %d\n",fd4);
    printf("fd5: %d\n",fd5);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);
    return 0;
}


3ada0af6a97344b0b16ad76512ed13ff.png


结论: 文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的


三、重定向

3.1 自实现重定向原理

3.1.1 输出重定向

输出重定向就是,将本应该输出到一个文件的数据重定向输出到另一个文件中


93bbab7a34a54f34902118a5901b6425.png


若想让本应该输出到"显示器文件"的数据输出到log.txt文件当中,可以在打开log.txt文件之前将文件描述符为1的文件关闭(即将“显示器文件”关闭)。当我们后续打开log.txt文件时所分配到的文件描述符就是1。


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
  close(1);
  int fd = open("./log.txt",O_RDWR | O_CREAT | O_TRUNC, 0666);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    printf("hello world\n");
  fflush(stdout);//为什么需要刷新?阅读后续章节《缓冲区》
  close(fd);
  return 0;
}

cd3232fd91f7433d85815daa413ebc61.png

3.1.2 追加重定向

输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据,并无其他区别


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    close(1);
    int fd = open("./log.txt",O_WRONLY | O_APPEND);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    printf("hello world\n");                                                         
    fflush(stdout);
    return 0;
}

5b4570bfd6f843089856cca0eab5485e.png


3.1.3 输入重定向

输入重定向,将本应该从一个文件读取数据,现在重定向为从另一个文件读取数据


2ae2b7427002457f8b3da43aaf645568.png


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    close(0);
    int fd = open("./log.txt",O_RDONLY);
    if(fd < 0){
        perror("opern error:");
        return 1;
    }
    char buf[64] = {0};
    while(scanf("%s",buf) != EOF){
        printf("%s\n",buf);                                                          
    }
    return 0;
}

ef6ac1d8c48542048fefa35ca901b6ec.png



3.2 dup2函数

在Linux环境下还可以使用dup2()系统调用来实现重定向。dup2()本质上是通过fd_array数组中地址元素的拷贝完成重定向的。

f0e8db4ed2c045e98fe886ca3dc3d8a7.png



#include <unistd.h>
int dup2(int oldfd, int newfd);

函数功能: 将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中


函数返回值: 若调用成功则返回newfd,否则返回-1


若oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。

若oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作并直接返回newfd。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
  int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
  if (fd < 0){
    perror("open");
    return 1;
  }
  close(1);
  dup2(fd, 1);
  printf("hello printf\n");
  fprintf(stdout, "hello fprintf\n");
  return 0;
}

b7146e1f3d354965935031bb469b7251.png



目录
相关文章
|
6月前
|
缓存 Linux API
文件IO和标准IO的区别
文件IO和标准IO的区别
79 2
|
4月前
|
存储 Linux 编译器
基础IO(下)
文件系统管理不仅涉及打开的文件,未打开的文件也需要管理,核心是快速定位文件以便通过路径访问。操作系统管理磁盘时,通过选择磁头、磁道和扇区进行寻址。磁盘逻辑上被抽象为数组,通过下标定位。文件系统将大磁盘分割管理,如分区、块组,每个文件对应唯一的inode,存储属性和内容。文件创建和删除涉及inode和数据块的分配与回收。文件查找通过路径确定分区,挂载将文件系统与分区关联。软链接通过路径引用,硬链接共享inode。静态库在编译时链接,动态库在运行时加载,动态链接节省资源。文件系统和库管理涉及内存、磁盘和程序执行的复杂交互。
49 2
基础IO(下)
|
4月前
|
存储 Linux 开发工具
基础IO(上)
本文主要讲述了文件描述符、重定向以及缓冲区的概念和运用。
34 1
基础IO(上)
|
11月前
|
缓存
标准IO和直接IO
标准IO和直接IO
81 0
day26-系统IO(2022.2.23)
day26-系统IO(2022.2.23)
|
缓存 Linux C语言
基础IO+文件(一)
基础IO+文件
94 0
|
存储 Linux 块存储
基础IO+文件(二)
基础IO+文件
79 0
|
编译器 Linux vr&ar
基础IO+文件(三)
基础IO+文件
79 0
|
存储 Linux C语言
基础IO详解(二)
基础IO详解
100 0
|
存储 资源调度 Kubernetes
Garden.io:它是什么以及为什么要使用它?
Garden.io:它是什么以及为什么要使用它?
149 0