?接下来我将介绍Linux当中与IO有关的知识,下面是本章节的思维导图,接下来的几篇博客我将按照思维导图的划分进行讲解。
思维导图获取
提取码: m9cz
?
IO操作
语言级IO操作
??在介绍系统级别的IO操作之前,我们先来回顾一下语言级别的IO操作。这里就以C语言中的IO操作为例了。 ??在C语言中,我们首先需要打开一个文件,使用的函数就是fopen,fopen可以指定以何种方式去打开文件,如: 以只读的方式、以只写的方式、以追加的方式等。 ??在打开文件之后,我们就可以写入或读取这个文件了。C语言中,文件的写入操作有fputs、fwrite、fprintf等。文件的读取操作有fgets、fread、fscanf等。下面给出一个C语言中常用的文件操作函数的接口及其功能的解释。
C语言文件操作函数接口 | 功能 |
---|
fopen | 打开文件 可以指定打开的方式 | fputc | 向文件中写入一个字符 | fputs | 向文件中写入一个字符串 | fwrite | 向文件中写入指定数量的全部字符 | fprintf | 以格式化的方式输入到文件中 | fgetc | 从文件中读取一个字符 | fgets | 从文件中读取n个字符(会读取\n) | fread | 从文件中读取指定数量的全部字符 | fscanf | 以格式化的方式读取文件中的数据 | fseek | 将文件指针移动到 特定的偏移量的位置 | ftell | 返回文件指针相对于起始的偏移量 | rewind | 让文件指针回到起始的位置 | fclose | 关闭文件 |
? 问题:为什么我们可以直接 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);
参数: ????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);
close
int close(int 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:传入你要读取的字符个数 ?
函数使用演示
#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;
}
?
#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);
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
cout << "fd:" << fd << endl;
const char* buffer = "Put words to log.txt\n";
write(1, buffer, strlen(buffer));
close(fd);
return 0;
}
|