系统文件IO操作与文件描述符

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统文件IO操作与文件描述符相关的知识,希望对你有一定的参考价值。

系统文件IO操作与文件描述符


IO操作

语言级IO操作

在C语言中,我们首先需要打开一个文件,使用的函数就是fopen,fopen可以指定以何种方式去打开文件,如: 以只读的方式、以只写的方式、以追加的方式等。
  
在打开文件之后,我们就可以写入或读取这个文件了。C语言中,文件的写入操作有fputs、fwrite、fprintf等。文件的读取操作有fgets、fread、fscanf等。
   
问题:为什么我们可以直接 printf 和 scanf?

任何进程在运行的时候,都会默认打开的3个输入/输出流

  • stdin ( 键盘 )     :标准输入
  • stdout ( 显示器 )  :标准输出
  • stderror ( 显示器 ) :标准错误

他们三个流的类型都是FILE*类型的文件指针,就和我们打开文件一样,我们往往使用一个FILE *的指针去接收fopen函数的返回值。


系统级IO操作

为什么我要在前面讲C语言的文件操作接口呢?因为接下来的系统级的IO接口和C语言中的接口非常相似,相似到只要你知道函数名你几乎它是干什么的了。

open

int open(const char* pathname, int flags);            
int open(const char* pathname, int flags, mode_t mode);
//包含于:<sys/types.h>    <sys/stat.h>    <fcntl.h>

参数:

  • pathname:要打开或创建的 目标文件  //支持相对路径和绝对路径
  • flag:传参标志位
flag参数有以下几种,可以传1个或多个。使用多个时,用“|”或运算符连结。
        O_RDONLY:只读打开
        O_WRONLY:只写打开
        O_RDWR :读、写 打开
        O_CREAT :若文件不存在,则创建它。使用该选项时,必须要用mode选项指定新文件的权限
        O_APPEND :追加 写
mode:当创建新文件时,对权限的设置。如:0666 (注意,该权限还需要与umask进行运算)

返回值:

  • 成功:返回新打开文件的文件描述符 (我下面会详细讲,类似C语言中的文件指针,但是它是个int)
  • 失败:-1

演示:

umask(0004);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
//创建一个文件并以只写的方式打开,文件的权限设置为0666 (还需要与umask值进行运算)

close

int close(int fd);	//关闭指定的文件描述符fd

write

ssize_t write(int fd, const void* buf, size_t count);

参数:

  • fd:文件描述符
  • buf:要写入的字符串
  • count:要写入的字符个数

read

ssize_t read(int fd, void* buf, size_t count);

参数:

  • fd:文件描述符
  • buf:从文件中读取的字符所存放的空间
  • count:传入你要读取的字符个数

函数使用演示

//测试write
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;

int main()

	umask(002);
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666); 
  	char buff[64] = "write some buffer to log.txt\\n";
	write(fd, buff, sizeof(buff));
                                                                                                 

 	close(fd);
 	return 0;


//测试read
#include <iostream>
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
using namespace std;    

int main()

  int fd = open("log.txt", O_RDONLY);
  char buffer[64];
  read(fd, buffer, sizeof(buffer));

  cout << buffer; //这里没有加换行哦!
  close(fd);
  return 0;    
 

文件描述符

从刚才的open函数的返回值开始就一直在提到文件描述符了,文件描述符到底是个什么东西呢?

我们可以从open的返回值入手,在C语言中,我们使用fopen函数得到的返回值是一个FILE *的指针,我们每次在进行读写函数调用的时候都要再传入这个指针去指定是哪个目标文件。而这里的文件描述符其实和FILE *是很类似的!它也是用来标识我们打开的是哪个文件的,实际上在底层,FILE *指针中也必然是包含了文件描述符的,FILE *指针是语言级别的,它是对系统调用的一个封装。

说了这么多,我们该透彻的分析一下了。文件描述符的本质是:数组下标,它是什么数组的下标呢?看下图!

结合这个图,我们可以从左上角的进程task_struct结构体出发,在task_struct中存在一个指向files_struct结构体的指针。而files_struct结构体中存有一个数组struct file *fd_array,这个数组也被叫做“文件描述符表”,因为它是个数组,所以我们可以通过数组下标去访问数组中的元素,而这里的数组下标就被称为“文件描述符”。数组中的每个元素都是一个指向file结构体的指针,而这里的file结构体则对应一个被打开的文件,它用来描述文件的相关信息。而在file结构体中存有一个指针file_operations,它指向了一个结构体,结构体中存着一堆函数指针,每次我们要对文件进行读写操作的时候,本质都是去使用了这里的函数指针从而进行函数调用的!


文件描述符的分配规则

结论:

  • 在files_struct结构体中的fd_array[]指针数组中,会优先找到当前没有被使用的最小下标,将其作为新的文件描述符。

举🌰应用:

  • 比如:当我们close(0)后,也就是把 输入(键盘) 文件关闭了,那么你再新创建一个文件时,该文件的fd文件描述符就会优先使用0 这个位置!
#include <iostream>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>  
#include <string.h> 
using namespace std;    
    
int main()    
    
  close(1);		//默认情况下,1号文件描述符对应显示器,这里把显示器关闭了
  umask(0);    
  int fd = open("log.txt", O_WRONLY | O_CREAT, 0666); //我们新打开的文件就会使用1号文件描述符

  //我们正常向显示器输出的内容也会被打印到文件中(类似重定向)
  cout << "fd:" << fd << endl;	

  const char* buffer = "Put words to log.txt\\n";
  //向1号文件描述符所对应的文件中写入字符串
  write(1, buffer, strlen(buffer));	
    
  close(fd);    
  return 0;                                                                                         
  

以上是关于系统文件IO操作与文件描述符的主要内容,如果未能解决你的问题,请参考以下文章

IO系统-文件与目录操作

zz``文件系统磁盘布局与I/O映射

操作系统-IO零拷贝

操作系统-IO零拷贝

操作系统-IO零拷贝

Linux文件与文件描述符的介绍