【Linux】文件描述符 (上篇)

这篇具有很好参考价值的文章主要介绍了【Linux】文件描述符 (上篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

📖 前言

本章开始,我们将进入Linux文件相关的学习与操作,从复习回顾C语言的文件操作接口,再从操作系统角度出发,学习系统调用接口。再了解虚拟文件系统,内核管理文件的数据结构。最后通过学习的操作文件的系统接口来模拟实现C语言的文件操作接口,最后对之前实现的shell进程改进。目标已经确定,接下来就要搬好小板凳,准备开讲了…🙆🙆🙆🙆


1. 文件的预备知识

  • 文件 = 文件内容 + 文件属性
  • 文件属性也是数据,即便你创建一个空文件,也要占据磁盘空间。
  • 文件操作 = 文件内容的操作 + 文件属性的操作
  • 有可能,在操作文件的过程中,既改变内容,又改变属性。
  • 属性可能随着内容的变化可能在变化。
  • 所谓的 “打开” 文件,究竟在干什么?
  • 将文件的属性或内容加载到内存中!
  • —— 这是由冯·诺依曼体系决定的!CPU只能在内存中对文件进行读写操作。
  • 打开文件不是目的,访问文件才是目的。
    • 例如:将文件内容中小写字母改成大写字母,是先将文件内容读到内存里,再把buff里面所有的内容改成大写,再写回到文件中。
  • 程序被加载到内存中后磁盘中还有吗?
  • 是的程序被加载到内存中后,磁盘中仍然存在程序文件。
  • 程序文件是存储在磁盘上的二进制文件,它包含了程序的代码、数据和资源等信息。
  • 当程序被加载到内存中时,操作系统会将程序文件中的代码和数据复制到内存中,程序在内存中执行。
  • 但是,如果需要重新启动程序或者重新加载程序,操作系统仍然需要从磁盘中读取程序文件。
  • 因此,程序文件仍然存在于磁盘中,直到被删除或者替换为止。
  • 如果程序压根没运行,这个程序就在磁盛上单独的一份。
  • 进程对应着磁盘上的一个程序,程序被加载到内存里的时候,在内存里有一份,在磁盘里也有一份。
  • 是不是所有的文件,都会处于被打开的状态?
  • 绝对不是!没有被打开的文件,在哪里?
    • 只在磁盘上静静的存储着!
  • 打开的文件(内存文件)和磁盘文件
  • 下面讲的所有内容都是打开文件。
  • 软硬链接,inode的时候再讲磁盘文件。
  • 通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?
  • fopen,fclose, fread, fwrite …
  • 当我们的文件程序运行起来的时候,才会执行对应的代码。
  • 然后才是真正的对文件进行相关的操作。
  • 真正的是进程对文件进行操作!
  • 目前学习文件就变成了:进程和打并文件的关系
  • 进程在内存,打开文件也在内存,所以是内存级的关系。
  • 文件的本质是进程和打开文件之间的关系。
  • 小结:
  • 对文件的操作:
    • 只有将程序编成可执行程序之后,加载到内存里的时候。
    • 变成了一个进程并且被CPU调度,开始执行自己编写的代码,这个时候才开始进行文件操作。
    • 所以当文件程序运行起来的时候,才会执行对应的代码,然后才是对文件的操作。
    • 所以通常说对文件的操作这句话,应该准确的说是:程序对应的进程对文件的操作。
  • 把文件打开在做什么:
    • 打开一个文件最终的目标是通过CPU执行用户代码,来完成对应文件操作。
    • 所以如果要是对数据进行操作,尤其是通过CPU来对数据进行操作(通过代码的方式)的话,就必须要求数据也要在内存当中。
    • 这个数据指的是文件的属性或者内容,一定要加载到内存里,这是由体系结构决定的(冯.诺依曼)。
  • 访问一个文件就得先打开,打开就得在内存里。
  • 程序要打开文件,必须先把程序变成进程。
  • 所有文件操作的本质都是在研究进程和打开文件的关系。

2. 复习C语言的文件操作

  • 什么是当前路径
  • 在之前我们讲进程概念的时候我们讲过,【对当前路径的理解复习-传送门】。
  • 每个进程都有个工作路径,调用chdir可以更改工作路径。
  • 更改当前路径用chdir就可以更改,哪个进程调用这个函数就更改哪个进程的当前路径。
    • 一个进程在运行之前是会把自己所在的路径保存在自己的PCB当中。
    • 所以该进程当前在哪个路径,它自己是知道的。
    • 所以当前路径最准确的说法是 进程的当前所处的工作路径。

在源代码路径下是不对的,更准确的说法是在当前进程所对应的路径下。只不过默认的一个进程的工作路径是在,当前自己所处的路径,不过路径是可以改的。所以cd、pwd查看的时候,这个进程路径变化或者不变,,说白了就是在更改自己进程的当前路径。

【Linux】文件描述符 (上篇),Linux,linux

在C语言中,fopen 函数用于打开文件,并返回一个指向文件的指针。下面是常见的几个选项:

1. "r":以只读方式打开文件。文件必须已经存在才能成功打开。
2. "w":以写入方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则清空文件内容。
3. "a":以追加方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则在文件末尾追加内容。
4. "rb":以二进制只读方式打开文件。类似于 "r",但以二进制模式读取文件。
5. "wb":以二进制写入方式打开文件。类似于 "w",但以二进制模式写入文件。
6. "ab":以二进制追加方式打开文件。类似于 "a",但以二进制模式追加内容。

这些选项可以根据需要选择,用于读取、写入或追加文件的内容。此外,还可以使用其他选项和模式来进行更高级的文件操作,例如对文件进行读写结合(“r+”、“w+”、“a+”)或者以二进制方式进行读写操作(“rb+”、“wb+”、“ab+”)。请注意,打开文件后应该使用 fclose 函数关闭文件指针,以释放相关资源。

通过fopen打开文件,r选项,再通过fgets按行读取文件:

#include <stdio.h>
#include <unistd.h>

int main()
{
    //1. 默认这个文件会在哪里形成呢?
    //2. r, w, r+, w+, a, a+
    //(r+ 和 w+ 都叫做既读又写,只不过w+多了一个功能,就是文件不存在会自动创建)
    //3. 关注一下文件清空的问题
    
    FILE* fp = fopen("log.txt", "r"); 
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    char buffer[64] = { 0 };
    while(fgets(buffer, sizeof(buffer), fp) != NULL)
    {
        printf("echo : %s\n", buffer);
    }

    fclose(fp);

    return 0;
}
  • a:追加写入,不断的往文件中新增内容 -> 追加重定向

【Linux】文件描述符 (上篇),Linux,linux
代码演示:

#include <stdio.h>
#include <unistd.h>

int main()
{    
    FILE* fp = fopen("log.txt", "a"); //写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    const char* msg = "Hello World";
    int cnt = 1;
    while(cnt <= 5)
    {
        fprintf(fp, "%s: %d\n", msg, cnt++);
    }
    fclose(fp);

    return 0;
}

【Linux】文件描述符 (上篇),Linux,linux

  • 当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了
    【Linux】文件描述符 (上篇),Linux,linux

以w打开的时候文件就被清空了,如果不存在就创建。

  • 回归理论:
  • 当我们向文件写入的时候,最终是不是向磁盘写入?是的!
  • 磁盘是硬件吗?就是硬件!
  • 只有谁有资格向硬件写入呢?操作系统!
  • 能绕开操作系统吗?不能!
    • 因为操作系统是软硬件资源的管理者。
    • 所有的上层访问文件的操作,都必须贯穿操作系统。
    • 只有操作系统可以间接的向硬件当中写入,必须得经过操作系统,并且不能绕过操作系统。
    • 因为操作系统本身就是硬件的管理者。
  • 操作系统是如何被上层使用的?
    • 必须使用操作系统提供的相关系统调用!

所有的语言都对系统接口做了封装,封装了系统接口。

  • 为什么要封装?
  1. 原生系统接口,使用成本比较高!
  2. 语言不具备跨平台性!
  • 封装是如何解决跨平台问题的呢?
  • C语言的解决办法:穷举所有的底层接口 + 条件编译!
  • 其他语言用的可能就是多态解决。
  • 不同的语言用不同的方式对系统调用进行封装。
  • 所以就导致了不同语言对文件操作的接口都不同。

所有的跨平台的语言,必须通过自己的方案,对所有的系统接口做相关封装,设计好自己对应语言当中的IO接口。

如果直接使用OS接口:
【Linux】文件描述符 (上篇),Linux,linux
具有上下级的关系:

任何一个语言,只要在同一个平台下跑,底层接口是不变的(Linux, Window…)。

我们为什么要学习系统级接口的原因:

  • 文件接口更接近于操作系统,这样理解语言层面的接口会很简单。
  • 在一个平台当中,这些接口是不变的。
  • 只要将不变的接口学了, 在学习其他语言的时候会更容易理解。

3. Linux系统级文件接口

3.1 open、 close、 read、 write 接口:

open、 close、 read、 write四个系统调用接口。

open函数的介绍:

【Linux】文件描述符 (上篇),Linux,linux
两个同名函数,这是同名函数,难道是函数重载吗?我们来看看GPT的回答:

【Linux】文件描述符 (上篇),Linux,linux
第二个参数标记位,通过宏来实现的:

  • O_RONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT…
  • 系统传递标记位,是用位图结构来进行传递的!【位图复习-传送门】
  • 每一个宏标记,一般只需要有一个比特位是1,并且和其他宏对应的值,不能重叠。

演示一下:

#include <stdio.h>

#define PRINT_A 0x1 //0000 0001
#define PRINT_B 0x2 //0000 0010
#define PRINT_C 0x4 //0000 0100
#define PRINT_D 0x8 //0000 1000
#define PRINT_DFL 0x0

//等同于系统级open
void Show(int flags)
{
    if(flags & PRINT_A) printf("hello A\n");
    if(flags & PRINT_B) printf("hello B\n");
    if(flags & PRINT_C) printf("hello C\n");
    if(flags & PRINT_D) printf("hello D\n");

    if(flags == PRINT_DFL) printf("hello Default\n");
}

int main()
{
    printf("PRINT_DFL:\n");
    Show(PRINT_DFL);

    printf("PRINT_A\n");
    Show(PRINT_A);

    printf("PRINT_B\n");
    Show(PRINT_B);

    printf("PRINT_A 和 PRINT_B\n");
    Show(PRINT_A | PRINT_B);

    printf("PRINT_C 和 PRINT_D\n");
    Show(PRINT_C | PRINT_D);

    printf("PRINT all:\n");
    Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);

    return 0;
}

上述代码,模拟了用宏做标记为的实现。

返回值:

【Linux】文件描述符 (上篇),Linux,linux

  • 返回的是个整数,文件描述符
  • -1是发生错误,出错之后error会设置

【Linux】文件描述符 (上篇),Linux,linux

  • O_TRUNCopen函数中的一个标志位,表示截断(truncate)文件。
  • C语言在w方式打开文件的时候,会清空的!
  • 同样的道理, O_APPEND是C语言中a方式打开文件,追加!

【Linux】文件描述符 (上篇),Linux,linux

O_TRUNC标志的含义是,如果指定的文件已经存在,那么在打开该文件的同时会将其内容清空(即截断文件)。如果指定的文件不存在,则会创建一个新的空文件。

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

int main()
{
    //有点像就近原则
    umask(0);

    //fopen("log.txt", "w"); //底层的调用的是open, O_WRONLY | O_CREAT | O_TRUN和这些选项,还要设置属性
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open");

        return 1;
    }

    printf("fd : %d\n", fd);

    int cnt = 0;
    //const char* str = "Hello World\n";
    const char* str = "aaa";
    while(cnt < 2)
    {
        //不能带\0,这是C语言的用法, 文件不认识\0
        write(fd, str, strlen(str));
        cnt++;
    }

    return 0;
}

创建一个文件的话,这个文件要受到Linux权限的约束的。

  • 所以要打开一个之前并不存在的文件的话,不能用两个参数的open,我们要用三个参数的open。
  • 最后的mode代表所创建文件的权限。

不然权限是乱的,如图所示:

【Linux】文件描述符 (上篇),Linux,linux
加了权限参数之后,发现是664,为什么不直接是0666呢?

【Linux】文件描述符 (上篇),Linux,linux
这个和umask有关,我们要在一开始将umask设置成0。

【Linux】文件描述符 (上篇),Linux,linux

不然不同的地方使用这段代码,umask可能都不同,需要我们认为控制,若不这样,很可能造成不同地方跑同样的代码,创造出来的文件权限不同。

为什么我们write往文件里写的时候,写的长度为什么不带上最后的‘\0’呢?

因为这是刻意为之,因为文件是不认是C语言的‘\0’。

  • 我们先来看一下write接口:

【Linux】文件描述符 (上篇),Linux,linux
在Linux中,有几个默认打开的文件是常见的:

  • 标准输入(stdin):文件描述符为0,通常与用户交互,通过键盘输入数据。
  • 标准输出(stdout):文件描述符为1,通常将程序输出的内容显示在终端上。
  • 标准错误(stderr):文件描述符为2,用于输出程序的错误消息或诊断信息。

【Linux】文件描述符 (上篇),Linux,linux

我们可以通过read接口,读取键盘输入的内容:

int main()
{
    char buffer[1024];
    ssize_t s = read(0, buffer, sizeof(buffer) - 1);
    if(s > 0)
    {
        buffer[s] = '\0';
        printf("echo: %s", buffer);

    }
    return 0;
}

【Linux】文件描述符 (上篇),Linux,linux
验证0,1,2就是标准IO:

//验证0,1,2就是标准IO
int main()
{
    const char* str = "Hello World!\n";

    write(1, str, strlen(str));
    write(2, str, strlen(str));
    return 0;
}

【Linux】文件描述符 (上篇),Linux,linux
验证0, 1, 2 和stdin, stdout, stderr的对应关系:

//0,1,2和stdin, stdout, stderr的对应关系
int main()
{
    printf("stdin: %d\n",stdin->_fileno);
    printf("stdout: %d\n",stdout->_fileno);
    printf("stderr: %d\n",stderr->_fileno);

    return 0;
}

【Linux】文件描述符 (上篇),Linux,linux
close接口,关闭文件:

【Linux】文件描述符 (上篇),Linux,linux
close是真的将文件销毁了吗?

  • 调用 close 函数后,文件仍然存在,并且可以通过重新打开或使用其他文件操作函数来再次访问。
  • close 操作只是将文件与当前程序的连接断开,而不是销毁文件本身。
  • 类似于智能指针中的做法。

调用close()函数会关闭指定的文件描述符,并释放与之相关的资源。这意味着在关闭文件描述符后,该文件描述符将不再有效,不能再进行读写操作。关闭文件描述符只是结束对文件的访问,不会直接影响到与该文件相关的其他数据结构,例如PCB。

3.2 内核当中实现的映射关系:

为什么fd是从3开始的?

  • 因为0,1,2 已经被默认打开了。

【Linux】文件描述符 (上篇),Linux,linux
我们看到stdin,stdout,stderr三个都是FILE的指针,在我们之前学习C语言知道,FILE是个结构体,是描述文件的一个结构体。

  • FILE* 是文件指针,里面封装了多个成员。
  • 通过上面演示的现象说明,FILE结构体内必定封装了fd

0,1, 2, 3, 4, 5…其实是数组下标!凡是用fd返回的,用的都是系统接口,操作系统提供的返回值!
open/ read/write/close - 要么是得到fd要么用到fd。

  • 内核数据结构详解~
  • 一个进程可不可以打开多个文件?当然可以!
  • 所以在内核中,进程:打开的文件 = 1 : n
    • 所以系统在运行中,有可能会存在大量的被打开的文件!
  • OS要不要对这些被打开的文件进行管理呢?
    • 操作系统如何管理这些被打开的文件呢?
    • 一定是先描述,再组织!!

一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构 — 先描述!

  • 进程如何和打开文件建立映射关系呢?

【Linux】文件描述符 (上篇),Linux,linux

  • 内核当中实现的映射关系:

【Linux】文件描述符 (上篇),Linux,linux
由图小结:

  • 其一:
    • 所以打开文件时是先创建一个struct file
    • 并且在当前文件描述符表里面分配一个没有被使用的下标,。
    • 将地址填入表中,并将数组对应的下标返回给用户。
  • 其二:
    • 当用户再次调用read, write等,一定传入了fd。
    • 只需要找到特定进程,找到fd,根据特定的文件描述符再索引到数组。
    • 最后找到文件对象,就可以对它进行相关操作了。
  • 内核当中:对被打开的文件的管理,转化成为了对链表的增删改查!
  • 所以一个进程将来想访问某一个文件,只需要知道该文件在这个映射表当中的数组下标。
  • 进程和文件之间的关系和语言没有关系!!

0,1, 2 -> stdin, stdout, stderr -> 键盘,显示器,显示器(这些都是硬件!)也用上面将的struct file来标识对应的文件吗??是的!!

3.3 如何理解Linux下一切皆文件:

如何知道这些struct file对应的操作方法是不一样的?

  • Linux是C语言写的,虽然不支持结构体中封装函数,但是可以用函数指针。
  • 通过函数指针的方式,让函数指针指向特定的方法。
  • 打开struct file的时候识别底层文件的类型拿到底层文件的读写方法,用自己的函数指针指向就可以了。
  • 上层来使用看的时候就认为是一切皆文件。
  • virtual file system 虚拟文件系统:

【Linux】文件描述符 (上篇),Linux,linux
OS内的内存文件系统以统一的视角看待所有的设备!!

  • 如果要打开文件,就在内核给硬件创建struct file
  • 然后初始化的时候,将函数指针指向具体的设备。
  • 但是在内核中存在的永远都是struct file,然后将struct file关联起来。
  • 所以一个进程看所有的文件都以统一的方式来看待。
  • 当我们访问一个file的时候,具体指向底层哪个对应的设备完全取决于对应的读写方法指向的哪个方法。
  • 对应的设备,对应的读写方法一定是不一样的!

上层使用同一个对象,指针指向不同的对象,最终就能调用不同的方法,可以理解为多态的前身。文章来源地址https://www.toymoban.com/news/detail-520639.html

到了这里,关于【Linux】文件描述符 (上篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux中的文件描述符

    文件描述符的概念:在Linux中, 文件描述符是内核为了高效的管理已经被打开的文件所创建的索引 ,它是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都是通过文件描述符完成的。文件描述符是一个简单的非负整数,用来表明每一个被进程打开的文件。

    2024年02月06日
    浏览(70)
  • 『Linux』文件描述符及重定向——为何说Linux下,一切皆文件?

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 Linux从入门到精通 ,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

    2024年02月12日
    浏览(59)
  • [Linux]文件描述符(万字详解)

    在学习文件描述符前,首先要了解一下Linux系统常用的文件系统接口。 open函数 open函数有两个接口,三个参数的接口是在两个参数的接口的基础上添加了控制创建文件的权限功能,更适合写文件时使用 pathname参数 – 要打开的文件所在的路径 flags参数 – 打开文件的方式 mode参

    2024年02月10日
    浏览(38)
  • Linux文件描述符和文件指针互转

    本文研究的主要是Linux中文件描述符fd与文件指针FILE*互相转换的相关内容,具体介绍如下。 1.文件描述符fd的定义: 文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一

    2024年02月08日
    浏览(41)
  • Linux - fd文件描述符和文件详解

                                                      ​​​​​​​             ​​​​​​​                                                   感谢各位 点赞 收藏 评论 三连支持                                                 本文

    2024年02月08日
    浏览(47)
  • 【看表情包学Linux】文件描述符

       🤣  爆笑 教程  👉 《看表情包学Linux》👈   猛戳订阅     🔥 💭 写在前面: 在上一章中,我们已经把 fd 的基本原理搞清楚了。本章我们将开始探索 fd 的应用特征,探索 文件描述符的分配原则。讲解重定向,上一章是如何使用 fflush 把内容变出来的,介绍 dup2 函数,

    2023年04月15日
    浏览(40)
  • Linux--文件描述符fd的本质

        

    2024年02月16日
    浏览(41)
  • 【Linux】基础 IO(文件描述符)-- 详解

    1、 文件的宏观理解 文件在哪呢? 从广义上理解,键盘、显示器、网卡、声卡、显卡、磁盘等几乎所有的外设都可以称之为文件,因为 “Linux 下,一切皆文件”。 从狭义上的理解, 文件在 磁盘(硬件) 上放着 ,只有操作系统才能真正的去访问磁盘。磁盘是一种永久存储介

    2024年03月24日
    浏览(49)
  • Linux 最大可以打开多少文件描述符?

    在日常开发中,对文件的操作可谓是再寻常不过的一件事情。那么你是否有这样一个疑问,我最多可以打开多少个文件呢? 在Linux系统中,当某个程序 打开文件 时,内核会返回相应的 文件描述符 (fd: file descriptors),也就是所谓的文件句柄,程序为了处理该文件必须引用此描

    2024年02月07日
    浏览(41)
  • Linux文件描述符和打开文件之间的关系

    简介 文件描述符和打开的文件之间似乎呈现出一一对应的关系。然而,实际并非如此。多个文件描述符指向同一打开文件,这既有可能,也属必要。这些文件描述符可在相同或不同的进程中打开。 要理解具体情况如何,需要查看由内核维护的 3 个数据结构。 进程级的文件描

    2024年02月07日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包