文章目录

  • 基础IO
    • 1. 系统文件I/O
      • 1.1 接口介绍
      • 1.2 open函数返回值
      • 1.3 文件描述符
      • 1.4 0 & 1 & 2
      • 1.5 文件描述符的分配规则
      • 1.6 重定向
    • 2. 使用dup2系统调用
    • 3. FILE

基础IO

1. 系统文件I/O

操作文件,除了C接口外,我们还可以采用系统接口来进行文件访问,我们来看看如下代码:

读文件

1.1 接口介绍

open man open

#include #include #include int open(const char* pathname, int flags);int open(const char* pathname, int flag, mode_t mode);pathname: 要打开或者创建的目标文件flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个进行“或”运算,构成flags参数:O_RDONLY:只读打开O_WRONLY:只写打开O_RDWR:读,写打开这三个常量必须指定,并且只能指定一个O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限O_APPEND:追加写返回值:成功:新打开的文件描述符失败:-1

open函数具体使用那个,和具体的应用场景有关,如果目标文件不存在,需open创建,第三个参数表示创建文件的默认权限。

1.2 open函数返回值

认识一下两个概念:系统调用和库函数

  • fopen fclose fread fwrite都是C语言标准库中的函数,我们称之为库函数
  • open close read write lseek 都属于系统提供的接口,称为系统调用接口

之前的博客中用到的一张图:

系统调用接口和库函数的关系一目了然

1.3 文件描述符

我们知道了文件描述符就是一个小小的整数

在计算机操作系统中,文件描述符是用于访问文件和I/O设备的一个抽象概念。它是一个非负整数,用来标识一个已经打开的文件或I/O设备。在Unix、Linux和其他类Unix系统中,文件描述符是一种非常常见的概念。

文件描述符通常被用于在程序中访问打开的文件或设备,如磁盘文件、标准输入、标准输出等。它们是一种轻量级的机制,能够提供高效、灵活的I/O操作,因为它们可以被用来表示任何类型的I/O流,包括文件、管道、套接字等。

文件描述符通常是通过调用系统调用(如open、close、read、write等)返回的,操作系统会维护一个表格来跟踪打开的文件和设备以及相应的文件描述符。在使用文件描述符时,程序需要保证文件描述符的唯一性,因为文件描述符是在系统范围内唯一的。

一般来说,标准输入、标准输出和标准错误输出的文件描述符分别是0、1、2。其他文件描述符的值通常是由操作系统分配的,通常是一个递增的整数。例如,打开一个文件会返回一个新的、当前未使用的文件描述符。关闭文件时,该文件描述符会被释放,并可以被其他打开的文件或设备使用。

1.4 0 & 1 & 2

  • Linux默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

所以输入和输出还可以采用如下方式:

现在我们知道,文件描述符就是从0开始的整数。当我们打开文件的时候,操作系统要创建相应的数据结构来描述目标文件,于是就有了file结构体。表示已经打开的文件对象。而执行open系统调用,所以必须要让进程和文件关联起来。每个进程都有一个*files指针,指向一张表files_struct,这个表最重要的部分就是包含了一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是这个数组的下标。所以,只要拿着文件描述符就可以找到对应的文件

1.5 文件描述符的分配规则

我们可以查看如下代码:

输出查看我们发现是:3

关闭0或者2重新查看:

我们可以发现这个规律:文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

1.6 重定向

如果我们关闭1会发生怎么的事情呢?我们看如下代码:


此时我们发现本该在显示器上显示的内容,输出到了文件当中。这种现象叫做重定向

2. 使用dup2系统调用

函数原型是这样:

#include int dup2(int oldfd, int newfd);

printf是C库当中的一个IO函数,一般往stdout中输出,但是stdout访问底层文件的时候还是找的是fd : 1,但是fd1已经变成了myfile的地址,不在是显示器文件的地址,所以输出的任何消息都是往文件中写入,进而完成输出重定向。

3. FILE

  • 因为IO相关的函数与系统调用接口对应,并且库函数封装系统调用,所以本质上访问文件都是通过fd访问的
  • C库的FILE内部,必定封装了fd

以上的代码运行结果是:

但是我们如果对进程实现输出重定向会怎么样?.test > myfile,我们发现结果是这样:

我们发现printffwrite都输出了两次,但是write只输出了一次(系统调用)这是为什么?

  • 一般C库函数写入文件是全缓冲,但是写入显示器是行缓冲
  • printffwrite自带缓冲区,当发生重定向到普通文件的时候,数据的缓冲方式变成了全缓冲
  • 而我们放在缓冲区的数据就不会立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入到文件当中
  • fork的时候父子进程数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也有了同样的数据,随机产生两份数据
  • write没有所谓的缓冲

所以我们可以发现:printffwrite库函数自带缓冲区,而write系统调用没有带缓冲区,另外这里的缓冲区都是用户级缓冲区