【文件描述符|重定向|缓冲区】(一)

简介: 【文件描述符|重定向|缓冲区】(一)

1 C语言文件操作的回顾

这块博主在讲解C语言时就已经做了很详细的讲解,这里就不详细讲了,直接给出代码。

写操作:

#include<stdio.h>    
#include<stdlib.h>    
#include<errno.h>    
#define LOG "log.txt"    
int main()    
{    
  FILE* fw=fopen("LOG","w");    
  if(fw==NULL)    
  {    
    perror("fopen:");    
    exit(1);                                                                                                                                
  }    
  const char* str="hello file\n";    
  fputs(str,fw);    
  fputs(str,fw);    
  fputs(str,fw);    
  fclose(fw);    
  return 0;    
}    

读操作:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<errno.h>
  4 
  5 #define LOG "log.txt"
  6 int main()
  7 {
  8   FILE* fr=fopen("LOG","r");
  9   if(fr==NULL)                                                                                                                          
 10   {
 11     perror("fopen:");
 12     exit(1);
 13   }
 14 
 15   char buffer[256];
 16   fgets(buffer,sizeof(buffer),fr);
 17   printf("%s\n",buffer);
 18   fclose(fr);
 19   return 0;
 20 }

除了用上面的方法外C语言我们还可以用fprintf/fscanf;fwrite/fread等等。输出到显示器有哪些方法呢?除了用printf外,我们还可以用fprintf参数给stdout,这是由于Linux下一切皆文件的准则,至于为啥我们在后面会给出解释。

在C语言我们知道C会默认打开三个流:标准输入(stdin),标准输出(stdout),标准错误(stderr),而这三个流的返回指针都是FILE*类型。

打开文件的方式:

r Open text file for reading.

The stream is positioned at the beginning of the file.

r+ Open for reading and writing.

The stream is positioned at the beginning of the file.

w Truncate(缩短) file to zero length or create text file for writing.

The stream is positioned at the beginning of the file.

w+ Open for reading and writing.

The file is created if it does not exist, otherwise it is truncated.

The stream is positioned at the beginning of the file.

a Open for appending (writing at end of file).

The file is created if it does not exist.

The stream is positioned at the end of the file.

a+ Open for reading and appending (writing at end of file).

The file is created if it does not exist. The initial file position

for reading is at the beginning of the file,

but output is always appended to the end of the file

如上,是我们之前学的文件相关操作。还有 fseek ftell rewind 的函数,有兴趣的老哥可以取移步博主讲解文件操作那里。


2 系统文件I/O

上面我们介绍的是语言层面的文件操作,但是系统层面的该如何操作呢?我们一个一个来介绍。

2.1 open

我们可以通过man手册查询:

e30a91d17549456aa8dedc9ccca5bdf7.png

我们来看看第一个参数:就是要打开文件的名字;第二个参数:这个要重点讲解。

我们翻到手册后面:

46b840a88eb74a4dbf0131e5c0008c77.png

这里面出现了一堆宏,究竟是什么鬼呢?其实open的第二个参数是一个位图结构,里面每一个比特位对应这一个宏,如何通过比特位将宏联系起来可以参考这种方式:

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10
// 0000 0000 0000 0000 0000 0000 0000 0000
void Print(int flags)
{
    if(flags & ONE) printf("hello 1\n"); //充当不同的行为
    if(flags & TWO) printf("hello 2\n");
    if(flags & THREE) printf("hello 3\n");
    if(flags & FOUR) printf("hello 4\n");
    if(flags & FIVE) printf("hello 5\n");
}
int main()
{
    printf("--------------------------\n");
    Print(ONE);
    printf("--------------------------\n");
    Print(TWO);
    printf("--------------------------\n");
    Print(FOUR);
    printf("--------------------------\n");
    Print(ONE|TWO);
    printf("--------------------------\n");
    Print(ONE|TWO|THREE);
    printf("--------------------------\n");
    Print(ONE|TWO|THREE|FOUR|FIVE);
    printf("--------------------------\n");
    return 0;
}

open接口的详细介绍:

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

若要使用mode选项,这里文件的权限还要受umask的影响,如果想要自己在参数中设置的就是文件权限,可以直接用umask(0)来设置文件的默认掩码。

2.2 write

6cced82aa8e646c4be2abc2e9042ee56.png

我们可以来试试:

    //C库
    fprintf(stdout, "hello fprintf\n");
    //系统调用
    const char *msg = "hello write\n";
    write(1, msg, strlen(msg)); //+1?

这里问一下大家,这里strlen(msg)是否要+1?我们想想如果是要拷贝\0的话那的确是要加,但是别忘了现在我们是系统调用接口,我们并不想要\0进入到文件中,换一种说法就是在给文件中根本就不认识什么\0,因为\0是由C语言给我们提供的,系统根本就不认识,那上面还有个\n呢?\n文件系统是可以识别到的能够自动换行。所以这里就不要加上\0了。

2.3 read


5d717aa039544bd896434acc601a6118.png

我们可以来试试:

char buffer[1024];
    // 这里我们无法做到按行读取,我们是整体读取的。
    ssize_t n = read(fd, buffer, sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意\0问题
    if(n > 0)
    {
        buffer[n] = '\0';
        printf("%s\n", buffer);
    }
这里还是有一样的问题,我们将文件中的内容读到字符串中由于文件中是没有\0的,所以需要我们自己手动增加\0.(当然你调用C语言的接口是不会出现这些问题的)

2.4 close

这个很简单,大家自己看看文档就懂了。

f38443c389d04bfb80e6830106bf4ce1.png

之前我们讲了C语言会默认打开3个文件流(stdin,stdout,stderr),那么我们是不是就可以合理猜测我们继续打开文件会与我们默认打开的文件之间有什么关系?

我们上手来验证验证:

  int fd1=open(LOG,O_WRONLY | O_CREAT ,0666);
 27   int fd2=open(LOG,O_WRONLY | O_CREAT ,0666);
 28   int fd3=open(LOG,O_WRONLY | O_CREAT ,0666);
 29   int fd4=open(LOG,O_WRONLY | O_CREAT ,0666);
 30   int fd5=open(LOG,O_WRONLY | O_CREAT ,0666);
 31 
 32   printf("%d %d %d %d %d\n",fd1,fd2,fd3,fd4,fd5);                                                                                       
 33   close(fd1);
 34   close(fd2);
 35   close(fd3);
 36   close(fd4);
 37   close(fd5);

运行结果:

2caea43c9fe04f78a69d5a7f73c394e5.png

我们发现打印的文件描述符是从3开始打印的,并没有从下标0开始打印,这也正好解释了系统默认给我们打开了三个文件,这三个文件的描述符恰好是0 1 2。

那如果在打开新的文件之前我们关闭标准输入和标准错误会发生什么呢?😝😝

    fclose(stdin);//close(0)
    fclose(stderr);//close(2)    
    int fd1=open(LOG,O_WRONLY | O_CREAT ,0666);
    int fd2=open(LOG,O_WRONLY | O_CREAT ,0666);                                                                                          
    int fd3=open(LOG,O_WRONLY | O_CREAT ,0666);
    int fd4=open(LOG,O_WRONLY | O_CREAT ,0666);                                                                                             
    int fd5=open(LOG,O_WRONLY | O_CREAT ,0666);
    printf("%d %d %d %d %d\n",fd1,fd2,fd3,fd4,fd5);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    close(fd5);

运行结果:

3edaa61c8df848bf8e8c162f357b1e10.png

不难发现此时新打开的文件描述符已经从0开始了。

文件描述符的分配规则是:在文件描述符的表中最小的没有被使用的数组下标分配给新文件。

2.5文件描述符fd

通过对open函数的学习,我们知道了文件描述符就是一个小整数.

0 & 1 & 2

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

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

😝😝😝😝谈谈你对文件描述符的理解?

在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件

3 各种问题的引入

1 如何理解C语言文件操作和系统调用?

通过上面对C语言文件操作的回顾以及对系统文件调用方法的学习,我们不难知道其实C语言的文件操作必定是封装了系统调用的,还记得在给大家讲解进程时给大家看的这样一张图吗?

c0f81cd61e4f4f64b06c403bdda3d067.png

我们调用的库函数其实是对系统调用的封装,是为了方便用户使用而进行的二次开发。不仅仅是C语言包括C++/java/python等语言只要想在Linux平台下跑,进行文件操作时必定封装了系统调用。

2 文件操作时第一步是打开文件,为什么要打开文件呢?文件没有被操作时在什么位置?被操作时又在什么位置?文件load到内存load的是文件内容还是属性?

打开文件的目的是将文件load到内存,文件没有被操作时应该在磁盘,操作时应该在内存中,文件load到内存操作系统为了高效会将文件属性load到内存中。

3 是谁打开文件的?

很显然是OS打开文件的(OS是系统的管理者),那么问题来了是谁让OS打开文件的呢?

是不是因为我们创建了一个进程,让进程请求OS帮助我们打开文件,而进程打开文件很显然不只是打开一个文件,那么如何将这些文件管理起来呢?答案是先描述再组织。

操作系统管理所有文件时是通过file结构体来进行管理的,而进程为了管理文件是在进程的task_struck中又增加了一个files_struct来管理本进程中的文件,具体关系如下图所示:

30f98ca631ad4967908b2b5e9b25cce5.png

其中files_struct结构体又指向了一个指针数组,指针数组中存放着进程所管理文件的地址,通过下标索引就能够轻易的找到文件,对文件进行操作。

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来

描述目标文件。于是就有了file结构体表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进

程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数

组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件

描述符,就可以找到对应的文件。

所以此时我们就能够更加清晰的解释为啥在上面我们关闭标准输入和标准错误后文件描述符发生的改变,本质就是0下标映射的标准输入文件和2下标映射的标准错误文件指向发生了改变。

这样通过映射关系建立连接还有一个好处就是将进程管理和文件管理进行了解耦合,将进程管理与文件管理分别管理了起来。


目录
相关文章
|
7月前
|
缓存 API C语言
文件的缓冲区
文件的缓冲区
95 1
|
7月前
|
存储 C语言
文件缓冲区
文件缓冲区
68 0
|
3月前
|
API Python
实现缓冲区协议
实现缓冲区协议
31 0
|
3月前
|
存储 算法 API
解密缓冲区协议
解密缓冲区协议
38 0
|
7月前
|
存储 缓存 小程序
详细讲解缓冲区
详细讲解缓冲区
修改udp的缓冲区大小
修改udp的缓冲区大小
198 0
修改udp的缓冲区大小
|
存储 网络协议 Linux
网络缓冲区
网络缓冲区
79 0
|
C语言
【文件描述符|重定向|缓冲区】(二)
【文件描述符|重定向|缓冲区】(二)
83 0
|
C语言
理解缓冲区
理解缓冲区
112 0
|
Java Linux Shell
系统文件IO/文件描述符/重定向/FILE/缓冲区的理解
本文较详细地分析了系统文件IO、文件描述符、重定向、FILE和缓冲区的问题,是系统学习操作系统文件IO的学习成果之一。
系统文件IO/文件描述符/重定向/FILE/缓冲区的理解