Linux(12)—— 文件与文件描述符

本文主要讨论文件和文件描述符。

文件包括内容和属性,文件也包括被打开的和没有打开的文件;打开文件本质上还是进程去操作;一个进程可以打开多个文件,而操作系统也需要对这些文件进行管理。(先描述再组织)

如果一个文件被打开,那么它会被加载到内存中;而操作系统中移动存在大量被打开的文件。在内核中,一个被打开的文件都必须有自己的文件打开对象,其中包含很多的属性。和进程类似,这个对象就和PCB结构一样,内部存储着文件的信息;而这些对象通过链表等数据结构串在一起,转化为链表的增删改查。

C语言接口

在c语言中提供了打开文件的接口fopen:

我们在程序中使用,尝试创建一个名为 log.txt 的文件,如果创建成功就立即关闭它:

cpp 复制代码
#include <stdio.h>

int main()
{
    FILE *fp = fopen("log.txt","w");
    if(fp == NULL)//open file failed
    {
        perror("fopen failed");
        return 1;//给一个错误码
    }
    fclose(fp);
    return 0;
}

fopen有两个参数,第一个表示操作的文件名,第二个w表示打开模式;它的作用为:

  • 文件存在,清空其所有内容重新开始;
  • 文件不存在,创建一个新文件。

最后使用fclose关闭文件,释放系统资源并确保数据写入磁盘。

在创建log.txt文件时,会自动在进程的当前路径下创建;即当前进程的cwd(工作路径)那么如果对当前进程的工作路径进行更改,是否就可以将文件新建到其他目录呢?

修改进程的工作路径我们需要使用chdir函数:

参数就是想要修改到的路径。

打开文件的方式除了w外还有其他的类型,总结如下:

|----|---|---|--------------------------------------------------------------|
| 模式 | 读 | 写 | 作用 |
| r | √ | × | 以只读方式打开文本文件。如果文件不存在,则会打开失败。 |
| w | × | √ | 以只写方式打开文件;如果文件已存在,其长度会被截断为零(即清空原内容);如果文件不存在,则会创建一个新文件。 |
| a | × | √ | 以追加(写入)方式打开文件;其文件流位于文件末尾,写入的新内容会接在原文件的后面而不会覆盖。且如果文件不存在,则创建它。 |
| r+ | √ | √ | 以读写方式打开文件。作用同r。 |
| w+ | √ | √ | 以读写方式打开文件,作用同w。 |
| a+ | √ | √ | 以读取和追加的方式打开文件。作用同a。 |

所以,w方法在写入之前都会将文件清空。

而w和a方法都是写入,w会清空文件,a值在原有的内容上追加。


c程序默认在启动的时候,会默认打开三个标准输入输出流:stdin、stdout、stderr;因为在Linux中一切皆文件,这三个输入输出流分别对应了不同的对象:

  • stdin:键盘文件
  • stdout:显示器文件
  • stderr:显示器文件

我们的printf、scanf等函数就是对这些流文件进行读和写来实现的。

系统文件I/O

文件是存储在磁盘上的,而磁盘是外部设备,访问文件实际上就是访问硬件。

几乎所有的库,只要是访问文件或硬件设备,必须要被封装。如printf,scanf等。,我们上面使用的函数全是封装好的可以直接使用的函数。不只是c语言,其他语言都会有这样的输入输出函数;而它们的底层实现都是基于下面这写函数来实现的:

和进程类似,文件的各种信息会存储于一个专门的结构体,叫做struct file;该结构体包含了文件的所有属性,而各个文件通过链表的形式链接在一起,通过操作系统来进行管理。

不过,进程和文件是 1:n 的关系,即一个进程可能会打开多个文件,所以文件的结构体中还需要一个信息来标注该文件的位置,这样才能知道哪个文件是哪个进程打开的。

在struct file中,会存在一个struct file* next指针,该指针会指向一个包含有数组的大结构体,这个数组就为文件描述符表。这个数组是一个指针数组,存储了文件的地址,并存放于一个数组下标对应的空间中。

所以本质上来说,open的返回值就是该数组的下标,从而能够找到对应的文件。

键盘、显示器默认打开,所以任何语言中都会自动打开自己封装好的流,就是对应的键盘显示器。

open

open函数的返回值:

成功:新打开的文件描述符

失败:-1

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

文件fd

文件描述符就是一个小整数,即open函数的返回值。

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2

0,1,2对应的物理设备一般是:键盘,显示器,显示器。

文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体来表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files,;指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针。

所以,也就是上面提到的,在本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
int main()
{
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
 
    close(fd);
    return 0;
}

运行可以看到fd = 3。这是因为fd会取第一个没有使用的下标作为返回值。如果我们关闭了3,那么3就会被fd获取到。

同样的,如果我们关闭0或者2:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
int main()
{
     close(0);
     //close(2);
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
 
    close(fd);
    return 0;
}

输出后发现结果为fd = 0或fd = 2。原理和上面相同。

不过,如果我们关闭了1,却发现显示器上不会显示任何结果。

这是因为我们使用的是printf函数,该函数的底层逻辑使用的就是1号描述符,所以我们关掉了1号之后当然就不会打印出结果了。

相关推荐
前端玖耀里2 小时前
Linux C/C++ 中系统调用与库函数调用的区别
linux·c语言·c++
艾莉丝努力练剑2 小时前
【Linux:文件】基础IO:文件操作的系统调用和库函数各个接口汇总及代码演示
linux·运维·服务器·c++·人工智能·centos·io
松涛和鸣2 小时前
70、IMX6ULL LED驱动实战
linux·数据库·驱动开发·postgresql·sqlserver
使者大牙2 小时前
【单点知识】CANopen实用协议介绍
服务器·网络·tcp/ip
狂野小青年2 小时前
Jenkins如何添加全局凭证
运维·jenkins
m0_694845572 小时前
music-website 是什么?前后端分离音乐网站部署实战
linux·运维·服务器·云计算·github
you-_ling3 小时前
Linux软件编程:Shell命令
java·linux·服务器
FairGuard手游加固3 小时前
面具外挂检测方案
linux·运维·服务器
鲨辣椒100863 小时前
Linux软件编程基石——基础指令使用
linux·windows·microsoft