Linux——基础IO
一、概念引入
在开始之前我们需要知道如下几个概念:
1. Linux下一切皆文件
2. 文件 = 内容 + 属性
3. 所有对文件的操作可以总结为两点:a.对内容操作 b.对属性操作
4. 内容是数据,属性其实也是数据,都是存储文件,必须既存储内容,又存储属性数据——默认就是在磁盘中的文件
5. 我们要访问一个文件的时候,都是要先把这个文件打开
这里前四点应该都很好理解,我们重点谈谈第五点
当“ 我们 ”要访问一个文件的的时候,这里的“ 我们 ”其实是进程,因为在我们之前C语言的学习中,如何打开文件,对文件进行读写操作,都是写在代码中,只有当将代码编译汇编形成可执行程序,被我们执行起来的时候,文件才会真正被创建,被读写了,而执行可执行程序的过程,本质上其实是之前学的创建进程的过程——所以访问一个文件,本质上是进程在访问
对于文件,默认指的是放在磁盘中的文件,打开这个文件,是为了让文件被访问,而上面我们说到,文件是进程被访问,进程在哪? 进程在内存中,所以如果文件要被访问,也是要被加载到内存的!
加载磁盘上的文件到内存,一定会涉及到访问磁盘设备,谁来做这个工作?
操作系统来做,一个进程通过操作系统,打开文件,所以操作系统一定要给我们提供系统调用接口
一个进程可以打开多个文件吗? 多个进程可以打开多个文件吗?
进程 : 打开的文件 = 1 : n
加载到内存中,被打开的文件,可能会存在多个
操作系统运行中,可能会打开很多文件,操作系统要不要管理被打开的文件呢? 答案肯定是要,但如何管理?
先描述,再组织
一个文件要被打开,一定要先在内核中形成被打开的文件对象
文件按照是否被打开分为:
被打开的文件(在内存中)
没有被打开的文件(在磁盘中)
综上,文件的管理工作分为两部分:
1. 对打开的文件进行管理——研究进程和被打开文件的关系
2. 没有被打开的文件也要在磁盘中进行管理
二、文件系统调用接口
2.1 C语言中文件接口
C语言中给我们提供了一套打开和关闭文件的接口
我们可以简单使用下
w方式打开:文件如果不存在,就创建文件,如果存在,会清空文件内容
等价于在命令行中:
a方式打开:文件如果不存在,就创建文件,从文件结尾处开始写入,不清空文件
等价于在命令行中:
2.2 系统文件接口
我们学习的C语言打开文件的接口,底层一定封装了系统调用接口
open()
close()
return value
2.2.1 参数介绍
const char* pathname
为文件名与文件路径,这个很好理解,与上文C语言提供的fopen()
需要传路径一样
int flags
是一个标志位,代表着文件以怎样的形式打开,以下是部分选项(主要):
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
上面三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
O_TRUNC: 覆盖写
注:这些都是宏,使用每个比特位作为标志位
mode_t mode
当文件不存在,并且需要创建文件的时候,需要告诉OS创建文件的八进制权限码(前面权限中的知识),而这个参数传入的就是创建文件的权限值
return value
返回值是一个int类型的值,这个值叫做fd,关于这个返回值fd,下文中会重点详谈
2.2.2 简易调用系统接口
注意:这里有个文件传参的细节
Linux中,当我们想向一个文件中写入字符串的时候,不需要strlen()+1
,\0
是C语言的规定,不是文件的规定
也就是传参的时候,不需要把C语言中的末尾结束标志也传入文件中
2.3 文件读写接口
上文中介绍了C语言和系统中,打开与关闭文件的接口,并没有提读写的接口
C语言中:
const char *msg = "hello linux \n";
size_t s = fwrite(msg, strlen(msg), 1,fp);
//msg:缓冲区首地址,size: 本次读取,期望写入多少个字节的数据,nmemb:一次写入几个字节,stream:打开文件时返回的文件指针,返回值:实际写了多少字节数据
char buf[1024];
size_t s = fread(buf,1,strlen( msg), fp);
//buf:缓冲区首地址,size: 本次读取,期望读入多少个字节的数据,nmemb:一次读入几个字节,stream:打开文件时返回的文件指针,返回值:实际读入了多少字节数据
系统中:
const char *msg = "hello linux \n";
ssize_t s = write(fd, msg, len);
//fd:下文中解释,msg:缓冲区首地址,count: 本次读取,期望写入多少个字节的数据,返回值:实际写了多少字节数据
char buf[1024];
ssize_t s = read(fd, buf, strlen(msg));
//fd:下文中解释,buf:缓冲区首地址,count: 本次读取,期望读入多少个字节的数据,返回值:实际读入了多少字节数据
对比C语言和系统中的接口,我们可以发现函数的区别并不大,因为C语言中的接口是封装库函数中的接口实现的
但两者之间还是有些区别,C语言中调用时传入的是打开文件时调用fopen()
返回的FILE* fp
类型的指针,而系统接口中传入的却是调用open()
返回的int fd
,fd
是什么呢?与C语言中的FILE* fp
有什么关联呢?
回答这些问题需要打通语言和系统关于文件部分的理解
三、文件描述符——fd
在引入中我们谈到,文件本质是进程在打开,而进程需要读写文件,文件就必须被加载到内存中,如何加载?
先描述,再组织——为其创建对应的PCB
3.1 进程与文件的联系
而进程又是如何与文件联系起来的呢?
看下图:
现在我们就能回答上文中的问题了
返回值fd是什么?
在进程的pcb中有个
struct files_struct* files
的指针
这个指针指向一个struct files_struct
的结构体
在这个结构体中,有一个数组:file* fd_array[]
这个数组每个变量都是file*
类型的指针,指向从磁盘加载的struct file
当进程打开一个文件的时候,OS就会为其创建struct file
,并把它的地址放到file* fd_array[]
的数组中
并向进程返回放入数组位置的下标
fd是file* fd_array[]
数组中的下标
FILE究竟是什么东西呢?
是一个C语言提供的结构体类型
FILE中必定封装了文件描述符fd,怎么证明?
FILE结构体中也有文件描述符:_fileno
3.2 三个默认打开的文件
我们可以看到,我们打开的文件的描述符fd = 3,那么0,1,2去哪里了呢?
OS/C语言为什么默认要把0,1,2、stdin,stdout,stderr打开呢?
为了让程序员默认进行输入输出代码编写
stdin,stdout我们很好理解,标准输入输出文件,键盘和显示器都需要频繁使用,但stderr是什么,为什么默认输出也是显示器?
重定向部分回答
3.3 VFS虚拟文件系统
根据冯诺依曼体系结构,计算机中的外设不会与cpu直接交互,而是与内存进行数据的交换
我们所知的主要外设有键盘,显示器,磁盘,网卡等等
外设是硬件