傲渊山岳 发表于 2024-12-8 20:40:39

【Linux】文件描述符fd

1.前置预备

   
[*]文件 = 内容 + 属性
[*]访问文件之前,都必须先打开他
#include<stdio.h>   
   
int main()   
{   
FILE* fp=fopen("log.txt","w");   
if(fp==NULL)   
{   
    perror("fopen");   
    return 1;   
}   
fclose(fp);   
                                                                                                                                                            
return 0;   
}       把源代码编成可执行程序,并没有打开文件,当程序运行执行到fopen时,才被打开,fopen和malloc一样,都是运行时操作,
当访问一个文件时,是进程在举行访问,但是进程是在内存中被CPU调度,文件是在磁盘中的,
根据冯诺依曼,CPU不能直接访问到磁盘,所以,文件也必须被加载到内存中。
    所以打开文件fopen,是在做什么?
把文件加载到内存中
    进程 = 属性 + 内容,同样 ,文件 = 内容 + 属性,OS必要对进程举行管理,需不必要对加载到内存的文件举行管理呢?
必须要!!!
    如果管理文件呢?
先描述,再构造,
在内核中,文件 = 文件的内核数据布局 + 文件的内容,
磁盘中,文件 = 文件属性 + 内容
    结论:我们研究打开的文件,本质是在研究进程和文件的关系。
没有被打开的文件呢?在磁盘上
文件:
1.被打开的文件------加载到内存,
2.没有被打开的文件------磁盘 
2.以“w”方式打开文件

先看用C语言打开一个文件,用"w"的方式打开一个文件log.txt,如果这个文件不存在,就新建,如果这个文件存在,则清空内容后打开, 
https://i-blog.csdnimg.cn/direct/975bbbd644dc414ea54e898ad5abdf0e.png
在下令行上,可以通过 > 文件 。对一个文件举行操作,> 意思跟"w"一样,文件不存在则创建,存在则对内容举行清空, > 也叫输出重定向。
https://i-blog.csdnimg.cn/direct/8f238f7eaa4a431988c650814f02db77.png
下面一段代码内容是文件不存在,新建然后举行数据插入:
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
      perror("fopen");
      return 1;
    }
    const char *message = "hello file\n";
    int i = 0;
    while(i<10)
    {
      fputs(message,fp);
      i++;
    }
    fclose(fp);
    return 0;
} https://i-blog.csdnimg.cn/direct/ae4c231dd21b486d93fd02197a5e9fa0.png
下面一段代码是打开文件,然后关闭文件,不举行任何操作,会对文件内容举行清空:
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
      perror("fopen");
      return 1;
    }
    // const char *message = "hello bit\n";
    // int i = 0;
    // while(i<10)
    // {
    //   fputs(message,fp);
    //   i++;
    // }
    fclose(fp);
    return 0;
} https://i-blog.csdnimg.cn/direct/320c8fe3ac9445c083734e35f1b6851d.png
3.以“a”方式打开文件

https://i-blog.csdnimg.cn/direct/aa1659821112403784f9bff037c192b9.png
 在下令行上,通过 >> 文件。对文件举行操作,>> 和“a”意思一样,对内容举行追加
https://i-blog.csdnimg.cn/direct/b8eb854348a0426a9e5c5da1023fb504.png
先用“w”方式打开文件,每次执行该程序,都会先举行清空,无法举行追加:
#include <stdio.h>

int main()
{
    //'w'文件不存在新建
    FILE *fp = fopen("./log.txt","w");
    if(fp == NULL)
    {
      perror("fopen");
      return 1;
    }

    char buffer;
    const char *message = "hello bit";
    int i = 0;
    while(i<10)
    {
      snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
      fputs(buffer,fp);
      i++;
    }
    fclose(fp);
    return 0;
}  https://i-blog.csdnimg.cn/direct/780bff44426248fa863512403fad2074.png
这时可以用“a”方法举行打开文件,每次执行该程序,都会追加式的像文件举行插入:
#include <stdio.h>

int main()
{
    //'a'追加内容
    FILE *fp = fopen("./log.txt","a");//append
    if(fp == NULL)
    {
      perror("fopen");
      return 1;
    }

    char buffer;
    const char *message = "hello bit";
    int i = 0;
    while(i<10)
    {
      snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
      fputs(buffer,fp);
      i++;
    }
    fclose(fp);
    return 0;
} https://i-blog.csdnimg.cn/direct/20861391617c4fb28f0db2ec19af6df2.png
4.程序默认打开三个输入输出流

https://i-blog.csdnimg.cn/direct/8c257b40595041f2a84d9cf958a0162c.png
   一个程序默认启动,会打开三个输入输出流,尺度输入,尺度输出,尺度错误,在C语言中,底层硬件所对应的文件键盘与显示器,把他们包装成文件的样子,末了访问键盘显示器,就可以以文件FILE*指针的情势举行访问
https://i-blog.csdnimg.cn/direct/5f6eea236e864954a262e184aa92ea0a.png
   总结:是进程会默认打开三个输入输出流
 看看下面代码,把内容打印到显示器方法:可以通过stdout举行操作
#include <stdio.h>

int main()
{
    printf("hello word\n");
    fputs("bit\n",stdout);
    fwrite("aaaaa\n",1,4,stdout);
    fprintf(stdout,"bbbb\n");
    return 0;
} 2.文件管理

往显示器上显示,往磁盘文件打开或写入文件,读写键盘,本质上是访问硬件,我们用户在访问硬件,不可能直接通过语言举行直接访问硬件的,必须要通过操作系统,
我们使用的C接口,看起来是直接访问硬件,其实是通过操作系统提供的系统调用接口,才能访问到硬件,所以我们使用的C接口,底层肯定要封装对应的文件类的系统调用!!
   fopen fclose fread fwrite 都是C尺度库当中的函数,我们称之为库函数 (libc)。
⽽ open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝ 
https://i-blog.csdnimg.cn/direct/94fe12e582c94c4d9c97f69b00cc5952.png
1.open 

现在我们来认识一下文件系统调用接口:我们操作文件,首先是要打开文件,系统调用open。
https://i-blog.csdnimg.cn/direct/982a24424d0b430db702668227141a0a.png
   第一个参数是文件名,
第二个参数,实际上是一个32bit位,也就是一个位图
第三的参数,如果文件已经创建,就不必要带,如果文件没有被创建,就必要对文件举行权限赋值
 我们通过下面一段代码来认识,位图参数的通报:
#include <stdio.h>

#define ONE (1<<0)//1   000001
#define TWO (1<<1)//2   000010
#define THREE (1<<2)//4 000100
#define FOUR (1<<3)//16 001000
#define FIVE (1<<4)//32 010000

//code 1
void PrintTest(int flags)
{
    //都为1才为1,只要有一个不为1,就为0
    if(flags & ONE)
    {
      printf("one\n");
    }
    if(flags & TWO)
    {
      printf("two\n");
    }
    if(flags & THREE)
    {
      printf("three\n");
    }
    if(flags & FOUR)
    {
      printf("four\n");
    }
    if(flags & FIVE)
    {
      printf("five\n");
    }
}

int main()
{
    printf("=====================\n");
    PrintTest(ONE);
    printf("=====================\n");
    PrintTest(TWO);
    printf("=====================\n");
    PrintTest(THREE);
    printf("=====================\n");
    //只要两个操作数对应的位中有一个为1,那么结果位就为1。
    PrintTest(ONE | THREE);
    printf("=====================\n");
    PrintTest(ONE | TWO | THREE);
    printf("=====================\n");
    PrintTest(ONE | TWO | THREE | FOUR);
    printf("=====================\n");
    return 0;
} https://i-blog.csdnimg.cn/direct/f5a8b5993a524ae088fbe5b249609afe.png
所以我们可以通过 | 的方式举行通报参数,通俗点意思就是说,| 两边条件都满意。
   open函数第二个参数:


[*]O_RDONLY:以只读方式打开文件。
[*]O_WRONLY:以只写方式打开文件。
[*]O_RDWR:以读写方式打开文件。
[*]O_CREAT : 若⽂件不存在,则创建它。必要使⽤mode选项,来指明新⽂件的访问 权限
[*]O_TRUNC:如果文件已存在且成功打开,则将其长度截断为 0。
[*]O_APPEND:写入时将数据追加到文件末了。
    返回值: 成功:新打开的⽂件描述符 失败:-1 
现在我们来使用一下open函数,第二个参数通报 O_WRONLY | O_CREAT,以只写方式打开,如果文件不存在则创建:
#include <stdio.h>
#include <fcntl.h>


int main()
{
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT);
    return 0;
} https://i-blog.csdnimg.cn/direct/64b7ac882ef3446a96efac75016cc516.png
这里我们发现新创建的文件,他的权限是错乱的,那是由于我们调用系统接口时,新创建文件,必要给文件举行权限设置,而我们语言级接口fopen不必要,是由于底层对其举行了封装。
所以用系统调用接口时,新建文件,还要告诉新建文件默认的起始权限是多少!!!!也就是第三个参数通报,通报权限。
下面代码我们给文件举行权限赋值666,意思就是文件权限为rw-rw-rw-,r = 4  w = 2 x = 1.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT,0666);
    return 0;
}

https://i-blog.csdnimg.cn/direct/1a780fdb79bd41c2bd5465551111d1f5.png
此时我们就可以发现权限没有错乱,但同时又有一个题目第三方的全是为什么只要一个r?
由于系统里有个umask,他会默认屏蔽掉一些权限,系统的umask = 0002,联合666,就会编成664,所以文件的最终权限会联合umask值来举行最终确认。
https://i-blog.csdnimg.cn/direct/6ca1d833b386425e8aec9361bbe8de9e.png
https://i-blog.csdnimg.cn/direct/c2a08369844e475dbbb4cc2c28a1b8d6.png
我们在编写代码的时候,也可以举行设置umask值,如下代码所示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
    //不让系统屏蔽某些权限
    umask(0);
    //以只写方式打开,如果文件不存在则创建
    open("log.txt",O_WRONLY | O_CREAT,0666);
    return 0;
}



https://i-blog.csdnimg.cn/direct/d186a0c30b5b4115badc5a8a2be43372.png
对umask清0,最终文件权限就是是第三个参数通报的权限,对在代码中umask清0,并不会影响到系统的umask值。
   1.每一个进程有一个umask值,表示创建文件umask权限,进程umask权限默认从系统中得到,但本技艺umask权限,采取就近原则来直接使用用户的umask值
2.touch创建文件其实权限都是666,然后受umask影响编程664.
下面代码模拟实现touch:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//模仿touch
int main(int argc,char *argv[])
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    open(argv,O_WRONLY | O_CREAT,0666);
    return 0;
}

https://i-blog.csdnimg.cn/direct/56c4bf910645410ea35b1db9cb007405.png
2.write

第一个参数传文件表示符,第二个参数传一个指向要写入的数据的缓冲区的指针,第三个参数表示写入的大小
https://i-blog.csdnimg.cn/direct/87eb29dee8dd4fb4a48e26a740264f58.png
3.read

第一个参数是文件标识符,第二个参数是一个指针,指向用于存储读取数据的缓冲区,第三个参数是该区域大小。
https://i-blog.csdnimg.cn/direct/bbfd415223984145a32e5b6ca7b42849.png
4.close

https://i-blog.csdnimg.cn/direct/a875aa07966a49368d8b6fde3875980a.png
5.文件描述符fd

https://i-blog.csdnimg.cn/direct/de973aef242d4861a605dbc89680e6c1.png
   open返回值,成功时返回一个文件描述符,失败返回-1
现在我们看一下文件的返回值是多少,有什么用? 
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/8cd4a481be994000b67db978a55c11c2.png
此时文件标识符为3,为什么文件打开从3开始呢?
由于进程启动,默认打开了三个尺度的输入输出流stdin,stdout,stderr,由于Linux下统统皆文件,这三个尺度的输入输出流被当成文件打开了。
现在我们使用系统调用接口来举行文件写入,如下代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "hello word\n";
    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/32c9f4a503064452952ce1e0d6345ddc.png
紧接着,我们只修改一个message指向的内容,原本文件内容稳定:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建
    int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/ed588046824d42bf8bb2720cd12cb0be.png
由于在做操作时只告诉了写入,并没有告诉要清空,只覆盖在原来基础上举行覆盖式的写入!!!
所以我们再加个选项O_TRUNC,如果文件存在则举行先清空:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/6cbda854dc88421482954320f5a5af29.png
如果对该文件只举行了打开然后关闭不做写入:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    //write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/ba94e5dec401419a8dc07407c54e7d25.png
   内容被清空了,这样O_WRONLY | O_CREAT | O_TRUNC传参,最终作用就是和用fopen打开使用“w”方法一样,fopen使用“w”方法底层就是封装了这!!!
现在我们使用追加的情势举行写入:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}
https://i-blog.csdnimg.cn/direct/c008c28543f34a01b64bf4e94cb47e8f.png 在上一个log.txt基础上举行内容的追加。
   这样O_WRONLY | O_CREAT | O_APPEND传参,最终作用就是和用fopen打开使用“a”方法一样,fopen使用“a”方法底层就是封装了这!!!
https://i-blog.csdnimg.cn/direct/2d59c94f00324e2e8f19ba97ea7ce14d.png

   fopen fclose fread fwrite 都是C尺度库当中的函数,我们称之为库函数 (libc)。
open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝ 
fopen fclose fread fwrite底层就是对open close read write举行了封装。
 关于fd的题目:

我们一连打开几个文件,看看文件描述符是多少?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //不让系统屏蔽某些权限
    //umask(0);
    //以只写方式打开,如果文件不存在则创建,如果存在则清空
    //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd2 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd3 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    int fd4 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
    if(fd1<0)
    {
      perror("open");
      return 0;
    }
    printf("fd1: %d\n",fd1);
    printf("fd2: %d\n",fd2);
    printf("fd3: %d\n",fd3);
    printf("fd4: %d\n",fd4);

    const char *message = "aaaaaa\n";

    write(fd1,message,strlen(message));
    close(fd1);
    return 0;
}

https://i-blog.csdnimg.cn/direct/9fee366517c343c28da5f17d9d7e9c43.png
观察到文件描述符从3开始依次创建!!!
而前面我们说到,0,1,2被键盘,显示器,显示器占用,又说过,这些硬件在底层被包装成文件的情势,同样,我们能不能通过0,1,2举行对键盘文件,显示器文件举行写与读呢?
看下面代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    const char *message = "hello write\n";
    write(1,message,strlen(message));

    return 0;
}

https://i-blog.csdnimg.cn/direct/453e4e352d4c437191e68ad1ff5192aa.png
我们通过write,直接向文件描述符1,举行写入message指向的内容,效果的确打印在屏幕上
我们再来看看下面调用read举行读:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    //abcd
    char buffer;
    ssize_t s = read(0,buffer,sizeof(buffer));
    if(s > 0)
    {
      //把回车改为0
      buffer = 0;
      printf("%s\n",buffer);
    }

    return 0;
}
https://i-blog.csdnimg.cn/direct/3e1a645c2e164e81941ad2661931a5a3.png
我们键盘输入abcd,然后打印,发现的确从标识符0举行读取。
内核角度:

我们来从内核角度举行观察,当我们磁盘中有个文件log.txt包含内容+属性,当我们把程序举行加载,就酿成了进程,OS为了对其举行管理,就有了PCB,CPU调度该进程,执行到open函数,此时打开文件,也就是把磁盘中文件加载到内存,加载到内存中,那么OS也要对其举行管理,先描述再构造,OS要为我们文件创建一个数据布局struct file,内里包含文件的属性,一个文件就对应一个struct内核对象,而进程启动时,会默认打开三个输入输出的文件,当我们打开了好几个文件,创建这些文件的struct file,然后为了方便管理,struct file内里包含一个struct file*的指针,通过这个指针就可以把这些文件以链表的情势举行管理起来,文件链表的情势。每创建一个文件,就链入该链表中举行管理,所以这样把对文件的管理,转换成对链表的增删查改,
而Linux内核以软件的一向特性,进程管理和文件管理,这是两个单元的,他们两个不能是强耦合,必须是松耦合的!!!
所以我们现在就是要把进程和文件举行关联起来,我们进程是一个布局体对象,文件也是一个布局体对象,这就可以当作布局体与布局体直接的联系,一般情况下一个进程可以打开多个文件,1:n的情势
所以接下来,在我们task_struct中存在一个struct file_struct *files的布局,所指向的struct file_strct布局体对象内里存在一个很紧张的成员变量struct file* fd_array[]。包含了一个指针数组。天然就具备下标0 1 2 3.....。然后把键盘文件,显示器文件,显示器文件填入进来,0 1 2就被他们三个给占用了厥后我们调用接口open,打开文件,然后我们系统在查的时候在这个数组内里查0 1 2都被占用,3没有被占,然后加载打开这个文件,在内核中把这个struct file创建出来,把该布局体对象的地点填入到3号下标位置。
   所以writr(3,message,strlen(message)),就是进程拿着3号描述符找到本身进程内部的
struct file_struct *files,然后找到struct file *fd_arryay[]然后找到3号下标对应的文件举行写入。
    这个表叫做文件描述符表,这个表是构建了进程和文件之间的关系的一张表,每一个进程都有这一张表,两边通过指针来完成各个模块的关联息争耦!!!!

https://i-blog.csdnimg.cn/direct/62237f7bb7ad4de8b2a6a5e270fda772.png
我们来打开内核源代码来看看到底是不是这回事
https://i-blog.csdnimg.cn/direct/7d85ab0f079247898ee8016b9bac8f21.png
   所以fd到底是什么?-----数组下标!!!
在系统内里 ,fd文件描述符是访问文件的唯一方式!!!
但是我们在C语言里用的全是文件流FILE *,而不是fd文件描述符?
所以什么是FILE?什么是FILE*?
这个是C语言给我们提供访问文件的一个东西,由于只能通过文件描述符举行访问,这个FILE是在C语言上封装的一个struct FILE,布局体对象,这个布局体里必定要有许多的属性,但这个属性内里肯定有一个对文件描述符举行封装的一个成员变量!!!
我们来看一下下面代码来举行验证:由于stdin,stdout,stderr是FILE*类型,所以用他举行指向一个成员变量_fileno,就可以看到文件描述符:
int main()
{

    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    return 0;
}

https://i-blog.csdnimg.cn/direct/7ce8b22179364d628dd364d2013f7797.png
再来fopen打开一个文件:
int main()
{

    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    FILE *fp = fopen("log.txt","w");
    printf("fp:%d\n",fp->_fileno);

    return 0;
}
https://i-blog.csdnimg.cn/direct/094d157e46594f7e97c4a2cbf37caaba.png
这样就验证上述说法!!!
   所以任你文件怎么办,OS只认文件描述符!!!
在C语言上,用到的函数都是对系统调用的封装!!!
不但做了接口上的封装,还做了类型上的封装就是struct_file!!!
重新理解统统皆文件:

如果理解硬件为文件呢?
OS被称为软硬件的管理者,我们OS系统不光对软件举行管理,也要对硬件举行管理,先描述再构造,所以我们硬件也肯定要是一个布局体。
https://i-blog.csdnimg.cn/direct/479de839e1da4a1385b19e45ac83763d.png
在内核中肯定要有描述这些装备,每一个装备都有一个对应的strut device布局体对象,然后OS通过链表对这些布局体举行管理起来,对装备的管理就酿成 对链表的增删查改,对于外设,属性类别一样,但是值可以不一样,对于这些外设最核心的动作就是IO,读写,每一种装备都要有查看内容的放入,最核心的方法就是读和写一个read方法和write方法。
输出一个结论,各个装备的属性可以同一设置成布局体,来体现装备的差异,但方法同样也是不一样的,虽然都叫读写。
对于外设有读写方法,比如键盘读写,显然读写方法没有,就可以把写方法设置为空。
所有的方法在底层实现上肯定是不一样的。
虽然他们方法底层实现不一样,所以Linux设计者,设计,当我们OS启动的时候,创建我们
struct file对象,我们struct file内里有,C语言布局体内里不能包含方法,但是可以包含函数指针:
https://i-blog.csdnimg.cn/direct/8151b91bb6a4462dbc798d3c7d5f8481.png
   我们可以让读指针指向底层的方法,写指针指向底层的方法,我们所对应的装备,在在内核中都创建对应的struct file,然后每个对应装备的struct file内里的函数指针指向对应的方法,站在struct file角度,我们要举行读写,我们压根就不必要关系底层实现方法,只要知道函数指针,就能找到底层方法!!!向上我们要访问任何一个硬件,同一叫做读和写等方法,不消关注底层实现,只必要调这个方法,他主动给我们找到底层实现方法。
    struct file就想当于在软件层面举行了一次封装,在上层看来,统统皆文件,对上层来说只必要提供struct file就可以。只要找到struct file就可以找到底层的方法,
这套机制在Linux中被叫做VFS(virtual file system)(假造文件系统)。
这样在上层就看不到各个装备的差异了,所以统统皆文件!!!
所有用户的行为都会被转换成进程,无论启动读写等各种下令,所有行为都是进程,而我们站在进程角度,只必要拿文件描述符,找到对应的struct file,剩下的就不是进程的事情了,只要找到执行file中的方法,就可以完成对这些装备的操作。
所以进程角度,一起皆文件。
所以之前用的系统调用函数open,read,write,访问键盘显示器,都是调用底层装备对应
struct file中的函数指针对应的方法举行访问。
   在上层统统皆文件,底层有各种装备,这种语系在C++当中,这种技术叫做多态!!!这也是C语言实现多态的方法!!!
https://i-blog.csdnimg.cn/direct/6ed70ed85dfd42a9a3aa59778dda0595.png
补充:底层实现的这些方法全是在驱动程序内里!!!
 打开内核源代码看看:
https://i-blog.csdnimg.cn/direct/18dc2b11854542798427b4f60ca53b33.png
文本写入VS二进程写入 

在盘算机里,OS系统层面上只有二进制概念,语言层看起来可以文本写入,也可以二进制写入。
为什么我们语言喜欢做封装?(C/C++)
我们向显示器写12345,我写的是12345这个整数呢,还是’1‘’2‘’3‘’4‘’5‘字符?-----字符!
我们显示器我们给他叫做字符装备,有一个东西叫做ACSLL码表,写12345,实际上是这几个字符ACSLL码值二进制,这个二进程被我们显示器解释成12345字符
看下面两段代码:
int main()
{
    char *message = "hello\n";
    write(1,message,strlen(message));
    return 0;
}
https://i-blog.csdnimg.cn/direct/2697e36c4bcf4f0d9d3524995326ab8b.png
int main()
{
    int a = 12345;
    write(1,&a,sizeof(a));
    printf("\n");
    return 0;
}
https://i-blog.csdnimg.cn/direct/fd76b04aa1ad4875add0fc737a52e8b3.png
把字符显示到显示器上,直接就是字符,把数字输入到显示器上,显示器是字符装备只认字符,只不过这样的字符转成我们对应的二进程,被显示器转换成字符’9‘’0‘。
所以我们在显示到显示器之前,我们给转换成字符,然后再举行显示,如下代码所示:
int main()
{
    int a = 12345;
    char buffer;
    snprintf(buffer,strlen(buffer),"%d",a);
    write(1,buffer,strlen(buffer));
    printf("\n");
    return 0;
}


https://i-blog.csdnimg.cn/direct/7f8151ed0f6745f1a56c4b51d5e9f487.png
这样才能把12345显示到显示器上 ,所以用系统调用是没办法直接把一个整数答应到显示器上,必须举行相关的转换,
   所以我们为什么要有printf相关这样的函数呢?不是有write这样的接口呢?、
由于我们再许多的情况,我们必要把内存级的二进制数据转换成字符风格,通过write打印在显示器上,这个过程叫做格式化的过程。
如果在系统中只有系统调用,那么这个格式化的过程必须要我们手动本身设置,然后才能显示。
所以C语言提供一些printf,scanf等一系列接口函数,直接可以举行使用打印,由于在底层对格式化的过程举行了封装!!!
https://i-blog.csdnimg.cn/direct/2d9ba0936bd1484484fe7bc3bd2fb46b.png
    所以为什么我们C语言要给我们许多接口做封装?
1.方便用户举行操作
2.提高语言的可移植性
 系统调用的接口类型设计成void*,所以 传过来字符串或者其他类型,你以为你传的是这些类型,在这些接口看来是二进制,由于是void*。
读到的数据,写到的数据,做转化,都是用户举行操作,然后C语言对其举行封装,用户就能直接使用。
   为什么喜欢做封装?如果不是Linux平台,而是win或者macos平台?
如果是系统调用接口,那么换平台就不能运行的,如果是封装的接口,在Linux调Linux的系统调用接口,在win调win的接口,在什么平台就调什么平台的接口。
所以语言层,把我们与平台强相关的接口操作做封装,提高语言的可移植性。
     在使用这些接口的时候,win和Linux或者其他平台提前给我们安装了一些东西,这个东西叫glibc的库,语言层使用的一些接口,在glibc中举行了封装,把库编成Linux版本的库,win版本的库等等
语言的可移植性性越高,这个语言就越流通!!!! 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Linux】文件描述符fd