【Linux】文件描述符fd

打印 上一主题 下一主题

主题 1788|帖子 1788|积分 5364

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.前置预备

   

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

先看用C语言打开一个文件,用"w"的方式打开一个文件log.txt,如果这个文件不存在,就新建,如果这个文件存在,则清空内容后打开, 

在下令行上,可以通过 > 文件 。对一个文件举行操作,> 意思跟"w"一样,文件不存在则创建,存在则对内容举行清空, > 也叫输出重定向

下面一段代码内容是文件不存在,新建然后举行数据插入:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     //'w'文件不存在新建
  5.     FILE *fp = fopen("./log.txt","w");
  6.     if(fp == NULL)
  7.     {
  8.         perror("fopen");
  9.         return 1;
  10.     }
  11.     const char *message = "hello file\n";
  12.     int i = 0;
  13.     while(i<10)
  14.     {
  15.         fputs(message,fp);
  16.         i++;
  17.     }
  18.     fclose(fp);
  19.     return 0;
  20. }
复制代码

下面一段代码是打开文件,然后关闭文件,不举行任何操作,会对文件内容举行清空:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     //'w'文件不存在新建
  5.     FILE *fp = fopen("./log.txt","w");
  6.     if(fp == NULL)
  7.     {
  8.         perror("fopen");
  9.         return 1;
  10.     }
  11.     // const char *message = "hello bit\n";
  12.     // int i = 0;
  13.     // while(i<10)
  14.     // {
  15.     //     fputs(message,fp);
  16.     //     i++;
  17.     // }
  18.     fclose(fp);
  19.     return 0;
  20. }
复制代码

3.以“a”方式打开文件


 在下令行上,通过 >> 文件。对文件举行操作,>> 和“a”意思一样,对内容举行追加

先用“w”方式打开文件,每次执行该程序,都会先举行清空,无法举行追加:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     //'w'文件不存在新建
  5.     FILE *fp = fopen("./log.txt","w");
  6.     if(fp == NULL)
  7.     {
  8.         perror("fopen");
  9.         return 1;
  10.     }
  11.     char buffer[1024];
  12.     const char *message = "hello bit";
  13.     int i = 0;
  14.     while(i<10)
  15.     {
  16.         snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
  17.         fputs(buffer,fp);
  18.         i++;
  19.     }
  20.     fclose(fp);
  21.     return 0;
  22. }
复制代码
 

这时可以用“a”方法举行打开文件,每次执行该程序,都会追加式的像文件举行插入:
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     //'a'追加内容
  5.     FILE *fp = fopen("./log.txt","a");//append
  6.     if(fp == NULL)
  7.     {
  8.         perror("fopen");
  9.         return 1;
  10.     }
  11.     char buffer[1024];
  12.     const char *message = "hello bit";
  13.     int i = 0;
  14.     while(i<10)
  15.     {
  16.         snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);
  17.         fputs(buffer,fp);
  18.         i++;
  19.     }
  20.     fclose(fp);
  21.     return 0;
  22. }
复制代码

4.程序默认打开三个输入输出流


   一个程序默认启动,会打开三个输入输出流,尺度输入,尺度输出,尺度错误,在C语言中,底层硬件所对应的文件键盘与显示器,把他们包装成文件的样子,末了访问键盘显示器,就可以以文件FILE*指针的情势举行访问
  

   总结:是进程会默认打开三个输入输出流
   看看下面代码,把内容打印到显示器方法:可以通过stdout举行操作
  1. #include <stdio.h>
  2. int main()
  3. {
  4.     printf("hello word\n");
  5.     fputs("bit\n",stdout);
  6.     fwrite("aaaaa\n",1,4,stdout);
  7.     fprintf(stdout,"bbbb\n");
  8.     return 0;
  9. }
复制代码
2.文件管理

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

1.open 

现在我们来认识一下文件系统调用接口:我们操作文件,首先是要打开文件,系统调用open

   第一个参数是文件名,
  第二个参数,实际上是一个32bit位,也就是一个位图
  第三的参数,如果文件已经创建,就不必要带,如果文件没有被创建,就必要对文件举行权限赋值
   我们通过下面一段代码来认识,位图参数的通报:
  1. #include <stdio.h>
  2. #define ONE (1<<0)//1   000001
  3. #define TWO (1<<1)//2   000010
  4. #define THREE (1<<2)//4 000100
  5. #define FOUR (1<<3)//16 001000
  6. #define FIVE (1<<4)//32 010000
  7. //code 1
  8. void PrintTest(int flags)
  9. {
  10.     //都为1才为1,只要有一个不为1,就为0
  11.     if(flags & ONE)
  12.     {
  13.         printf("one\n");
  14.     }
  15.     if(flags & TWO)
  16.     {
  17.         printf("two\n");
  18.     }
  19.     if(flags & THREE)
  20.     {
  21.         printf("three\n");
  22.     }
  23.     if(flags & FOUR)
  24.     {
  25.         printf("four\n");
  26.     }
  27.     if(flags & FIVE)
  28.     {
  29.         printf("five\n");
  30.     }
  31. }
  32. int main()
  33. {
  34.     printf("=====================\n");
  35.     PrintTest(ONE);
  36.     printf("=====================\n");
  37.     PrintTest(TWO);
  38.     printf("=====================\n");
  39.     PrintTest(THREE);
  40.     printf("=====================\n");
  41.     //只要两个操作数对应的位中有一个为1,那么结果位就为1。
  42.     PrintTest(ONE | THREE);
  43.     printf("=====================\n");
  44.     PrintTest(ONE | TWO | THREE);
  45.     printf("=====================\n");
  46.     PrintTest(ONE | TWO | THREE | FOUR);
  47.     printf("=====================\n");
  48.     return 0;
  49. }
复制代码

所以我们可以通过 | 的方式举行通报参数,通俗点意思就是说,| 两边条件都满意。
   open函数第二个参数:
  

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以读写方式打开文件。
  • O_CREAT : 若⽂件不存在,则创建它。必要使⽤mode选项,来指明新⽂件的访问 权限
  • O_TRUNC:如果文件已存在且成功打开,则将其长度截断为 0。
  • O_APPEND:写入时将数据追加到文件末了。
    返回值: 成功:新打开的⽂件描述符 失败:-1 
  现在我们来使用一下open函数,第二个参数通报 O_WRONLY | O_CREAT,以只写方式打开,如果文件不存在则创建:
  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. int main()
  4. {
  5.     //以只写方式打开,如果文件不存在则创建
  6.     open("log.txt",O_WRONLY | O_CREAT);
  7.     return 0;
  8. }
复制代码

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

此时我们就可以发现权限没有错乱,但同时又有一个题目第三方的全是为什么只要一个r?
由于系统里有个umask,他会默认屏蔽掉一些权限,系统的umask = 0002,联合666,就会编成664,所以文件的最终权限会联合umask值来举行最终确认。


我们在编写代码的时候,也可以举行设置umask值,如下代码所示:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. int main()
  6. {
  7.     //不让系统屏蔽某些权限
  8.     umask(0);
  9.     //以只写方式打开,如果文件不存在则创建
  10.     open("log.txt",O_WRONLY | O_CREAT,0666);
  11.     return 0;
  12. }
复制代码

对umask清0,最终文件权限就是是第三个参数通报的权限,对在代码中umask清0,并不会影响到系统的umask值。
   1.每一个进程有一个umask值,表示创建文件umask权限,进程umask权限默认从系统中得到,但本技艺umask权限,采取就近原则来直接使用用户的umask值
  2.touch创建文件其实权限都是666,然后受umask影响编程664.
  下面代码模拟实现touch:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. //模仿touch
  6. int main(int argc,char *argv[])
  7. {
  8.     //不让系统屏蔽某些权限
  9.     //umask(0);
  10.     //以只写方式打开,如果文件不存在则创建
  11.     open(argv[1],O_WRONLY | O_CREAT,0666);
  12.     return 0;
  13. }
复制代码

2.write

第一个参数传文件表示符,第二个参数传一个指向要写入的数据的缓冲区的指针,第三个参数表示写入的大小

3.read

第一个参数是文件标识符,第二个参数是一个指针,指向用于存储读取数据的缓冲区,第三个参数是该区域大小。

4.close


5.文件描述符fd


   open返回值,成功时返回一个文件描述符,失败返回-1
  现在我们看一下文件的返回值是多少,有什么用? 
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. int main()
  7. {
  8.     //不让系统屏蔽某些权限
  9.     //umask(0);
  10.     //以只写方式打开,如果文件不存在则创建
  11.     int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
  12.     if(fd1<0)
  13.     {
  14.         perror("open");
  15.         return 0;
  16.     }
  17.     printf("fd1: %d\n",fd1);
  18.     return 0;
  19. }
复制代码

此时文件标识符为3,为什么文件打开从3开始呢?
由于进程启动,默认打开了三个尺度的输入输出流stdin,stdout,stderr,由于Linux下统统皆文件,这三个尺度的输入输出流被当成文件打开了。
现在我们使用系统调用接口来举行文件写入,如下代码:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建
  12.     int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
  13.     if(fd1<0)
  14.     {
  15.         perror("open");
  16.         return 0;
  17.     }
  18.     printf("fd1: %d\n",fd1);
  19.     const char *message = "hello word\n";
  20.     write(fd1,message,strlen(message));
  21.     close(fd1);
  22.     return 0;
  23. }
复制代码

紧接着,我们只修改一个message指向的内容,原本文件内容稳定:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建
  12.     int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);
  13.     if(fd1<0)
  14.     {
  15.         perror("open");
  16.         return 0;
  17.     }
  18.     printf("fd1: %d\n",fd1);
  19.     const char *message = "aaaaaa";
  20.     write(fd1,message,strlen(message));
  21.     close(fd1);
  22.     return 0;
  23. }
复制代码

由于在做操作时只告诉了写入,并没有告诉要清空,只覆盖在原来基础上举行覆盖式的写入!!!
所以我们再加个选项O_TRUNC,如果文件存在则举行先清空:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建,如果存在则清空
  12.     int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  13.     if(fd1<0)
  14.     {
  15.         perror("open");
  16.         return 0;
  17.     }
  18.     printf("fd1: %d\n",fd1);
  19.     const char *message = "aaaaaa\n";
  20.     write(fd1,message,strlen(message));
  21.     close(fd1);
  22.     return 0;
  23. }
复制代码

如果对该文件只举行了打开然后关闭不做写入:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建,如果存在则清空
  12.     int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  13.     if(fd1<0)
  14.     {
  15.         perror("open");
  16.         return 0;
  17.     }
  18.     printf("fd1: %d\n",fd1);
  19.     const char *message = "aaaaaa\n";
  20.     //write(fd1,message,strlen(message));
  21.     close(fd1);
  22.     return 0;
  23. }
复制代码

   内容被清空了,这样O_WRONLY | O_CREAT | O_TRUNC传参,最终作用就是和用fopen打开使用“w”方法一样,fopen使用“w”方法底层就是封装了这!!!
  现在我们使用追加的情势举行写入:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建,如果存在则清空
  12.     //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  13.     int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  14.     if(fd1<0)
  15.     {
  16.         perror("open");
  17.         return 0;
  18.     }
  19.     printf("fd1: %d\n",fd1);
  20.     const char *message = "aaaaaa\n";
  21.     write(fd1,message,strlen(message));
  22.     close(fd1);
  23.     return 0;
  24. }
复制代码
 在上一个log.txt基础上举行内容的追加。
   这样O_WRONLY | O_CREAT | O_APPEND传参,最终作用就是和用fopen打开使用“a”方法一样,fopen使用“a”方法底层就是封装了这!!!
  


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

我们一连打开几个文件,看看文件描述符是多少?
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //不让系统屏蔽某些权限
  10.     //umask(0);
  11.     //以只写方式打开,如果文件不存在则创建,如果存在则清空
  12.     //int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
  13.     int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  14.     int fd2 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  15.     int fd3 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  16.     int fd4 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);
  17.     if(fd1<0)
  18.     {
  19.         perror("open");
  20.         return 0;
  21.     }
  22.     printf("fd1: %d\n",fd1);
  23.     printf("fd2: %d\n",fd2);
  24.     printf("fd3: %d\n",fd3);
  25.     printf("fd4: %d\n",fd4);
  26.     const char *message = "aaaaaa\n";
  27.     write(fd1,message,strlen(message));
  28.     close(fd1);
  29.     return 0;
  30. }
复制代码

观察到文件描述符从3开始依次创建!!!
而前面我们说到,0,1,2被键盘,显示器,显示器占用,又说过,这些硬件在底层被包装成文件的情势,同样,我们能不能通过0,1,2举行对键盘文件,显示器文件举行写与读呢?
看下面代码:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     const char *message = "hello write\n";
  10.     write(1,message,strlen(message));
  11.     return 0;
  12. }
复制代码

我们通过write,直接向文件描述符1,举行写入message指向的内容,效果的确打印在屏幕上
我们再来看看下面调用read举行读:
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/stat.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. int main()
  8. {
  9.     //abcd
  10.     char buffer[128];
  11.     ssize_t s = read(0,buffer,sizeof(buffer));
  12.     if(s > 0)
  13.     {
  14.         //把回车改为0
  15.         buffer[s-1] = 0;
  16.         printf("%s\n",buffer);
  17.     }
  18.     return 0;
  19. }
复制代码

我们键盘输入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号下标对应的文件举行写入。
    这个表叫做文件描述符表,这个表是构建了进程和文件之间的关系的一张表,每一个进程都有这一张表,两边通过指针来完成各个模块的关联息争耦!!!!
  

我们来打开内核源代码来看看到底是不是这回事

   所以fd到底是什么?-----数组下标!!!
  在系统内里 ,fd文件描述符是访问文件的唯一方式!!!
  但是我们在C语言里用的全是文件流FILE *,而不是fd文件描述符?
  所以什么是FILE?什么是FILE*?
  这个是C语言给我们提供访问文件的一个东西,由于只能通过文件描述符举行访问,这个FILE是在C语言上封装的一个struct FILE,布局体对象,这个布局体里必定要有许多的属性,但这个属性内里肯定有一个对文件描述符举行封装的一个成员变量!!!
  我们来看一下下面代码来举行验证:由于stdin,stdout,stderr是FILE*类型,所以用他举行指向一个成员变量_fileno,就可以看到文件描述符:
  1. int main()
  2. {
  3.     printf("stdin:%d\n",stdin->_fileno);
  4.     printf("stdout:%d\n",stdout->_fileno);
  5.     printf("stderr:%d\n",stderr->_fileno);
  6.     return 0;
  7. }
复制代码

再来fopen打开一个文件:
  1. int main()
  2. {
  3.     printf("stdin:%d\n",stdin->_fileno);
  4.     printf("stdout:%d\n",stdout->_fileno);
  5.     printf("stderr:%d\n",stderr->_fileno);
  6.     FILE *fp = fopen("log.txt","w");
  7.     printf("fp:%d\n",fp->_fileno);
  8.     return 0;
  9. }
复制代码

这样就验证上述说法!!!
   所以任你文件怎么办,OS只认文件描述符!!!
  在C语言上,用到的函数都是对系统调用的封装!!!
  不但做了接口上的封装,还做了类型上的封装就是struct_file!!!
  重新理解统统皆文件:

如果理解硬件为文件呢?
OS被称为软硬件的管理者,我们OS系统不光对软件举行管理,也要对硬件举行管理,先描述再构造,所以我们硬件也肯定要是一个布局体。

在内核中肯定要有描述这些装备,每一个装备都有一个对应的strut device布局体对象,然后OS通过链表对这些布局体举行管理起来,对装备的管理就酿成 对链表的增删查改,对于外设,属性类别一样,但是值可以不一样,对于这些外设最核心的动作就是IO,读写,每一种装备都要有查看内容的放入,最核心的方法就是读和写一个read方法和write方法。
输出一个结论,各个装备的属性可以同一设置成布局体,来体现装备的差异,但方法同样也是不一样的,虽然都叫读写。
对于外设有读写方法,比如键盘读写,显然读写方法没有,就可以把写方法设置为空。
所有的方法在底层实现上肯定是不一样的。
虽然他们方法底层实现不一样,所以Linux设计者,设计,当我们OS启动的时候,创建我们
struct file对象,我们struct file内里有,C语言布局体内里不能包含方法,但是可以包含函数指针:

   我们可以让读指针指向底层的方法,写指针指向底层的方法,我们所对应的装备,在在内核中都创建对应的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语言实现多态的方法!!!
  

补充:底层实现的这些方法全是在驱动程序内里!!!
 打开内核源代码看看:

文本写入VS二进程写入 

在盘算机里,OS系统层面上只有二进制概念,语言层看起来可以文本写入,也可以二进制写入。
为什么我们语言喜欢做封装?(C/C++)
我们向显示器写12345,我写的是12345这个整数呢,还是’1‘’2‘’3‘’4‘’5‘字符?-----字符!
我们显示器我们给他叫做字符装备,有一个东西叫做ACSLL码表,写12345,实际上是这几个字符ACSLL码值二进制,这个二进程被我们显示器解释成12345字符
看下面两段代码:
  1. int main()
  2. {
  3.     char *message = "hello\n";
  4.     write(1,message,strlen(message));
  5.     return 0;
  6. }
复制代码

  1. int main()
  2. {
  3.     int a = 12345;
  4.     write(1,&a,sizeof(a));
  5.     printf("\n");
  6.     return 0;
  7. }
复制代码

把字符显示到显示器上,直接就是字符,把数字输入到显示器上,显示器是字符装备只认字符,只不过这样的字符转成我们对应的二进程,被显示器转换成字符’9‘’0‘。
所以我们在显示到显示器之前,我们给转换成字符,然后再举行显示,如下代码所示:
  1. int main()
  2. {
  3.     int a = 12345;
  4.     char buffer[1024];
  5.     snprintf(buffer,strlen(buffer),"%d",a);
  6.     write(1,buffer,strlen(buffer));
  7.     printf("\n");
  8.     return 0;
  9. }
复制代码

这样才能把12345显示到显示器上 ,所以用系统调用是没办法直接把一个整数答应到显示器上,必须举行相关的转换,
   所以我们为什么要有printf相关这样的函数呢?不是有write这样的接口呢?、
  由于我们再许多的情况,我们必要把内存级的二进制数据转换成字符风格,通过write打印在显示器上,这个过程叫做格式化的过程。
  如果在系统中只有系统调用,那么这个格式化的过程必须要我们手动本身设置,然后才能显示。
  所以C语言提供一些printf,scanf等一系列接口函数,直接可以举行使用打印,由于在底层对格式化的过程举行了封装!!!
  

    所以为什么我们C语言要给我们许多接口做封装?
  1.方便用户举行操作
  2.提高语言的可移植性
   系统调用的接口类型设计成void*,所以 传过来字符串或者其他类型,你以为你传的是这些类型,在这些接口看来是二进制,由于是void*。
读到的数据,写到的数据,做转化,都是用户举行操作,然后C语言对其举行封装,用户就能直接使用。
   为什么喜欢做封装?如果不是Linux平台,而是win或者macos平台?
  如果是系统调用接口,那么换平台就不能运行的,如果是封装的接口,在Linux调Linux的系统调用接口,在win调win的接口,在什么平台就调什么平台的接口。
  所以语言层,把我们与平台强相关的接口操作做封装,提高语言的可移植性
     在使用这些接口的时候,win和Linux或者其他平台提前给我们安装了一些东西,这个东西叫glibc的库,语言层使用的一些接口,在glibc中举行了封装,把库编成Linux版本的库,win版本的库等等
  语言的可移植性性越高,这个语言就越流通!!!! 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

傲渊山岳

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表