假定外部存储设备为磁盘,文件如果没有被使用,那么它静静躺在磁盘上,如果它被使用,则文件将被加载进内存中。故此,可以将文件分为内存文件和磁盘文件。
// 1.读时的open写法
int fd = open("log.txt", O_RDONLY);
// 2.清空写时的open写法
int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC);
//3.追加写时的open写法
int fd2 = open("log.txt", O_WRONLY | O_CREAT | O_APPEND);
当一个进程要打开文件时,操作系统将文件的属性加载到内存中,根据这些属性创建一个struct file
结构体,并且将该结构体与进程控制块task_struct
建立联系。
操作系统将文件加载进内存,创建对应的struct file 结构体,并且与struct task_struct
建立联系。在Linux中,task_struct 内部有一个 struct files_struct
字段,这个结构体里面有struct file* fd_array[]
数组,file descriptor就是这个数组的下标。如图:
文件描述符的本质就是struct file*fd_array 数组中的下标,当操作系统创建文件结构体struct file
时,将该结构体地址填入数组中,至此进程与文件建立了联系。
操作系统从开始扫描数组,遇到空位置,则填入地址,给调用进程返回数组下标,即文件描述符。
观察上面代码,发现文件描述符是从3开始的,这是因为c语言中默认打开三个文件,标准输入-stdin,标准输出-stdout,标准错误-stderr,它们对应数组的 0 1 2,stdout和stderr默认都指向显示器。
struct file是内核级别的结构体,而struct FILE是c语言库中定义的结构体,这两者之间没有任何关系。
struct FILE内部有一个文件描述符int fd;
字段。当进程读写文件时,os会根据文件描述符找到对应的struct file
结构体,然后进行读写操作。
重定向有三种:输出重定向,追加重定向,输入重定向
ls . > text
,这样原本打印在显示器上的消息就打印到文本text中了,这叫做输出重定向。cat < text
,原本是从键盘读取字符,然后重定向到从text中读取字符 ,这就是输入重定向。为什么ls . > text
可以将打印到显示器上的数据打印到text中的呢?在Linux中,命令ls
是一个程序,它也是用c语言编写的,所以是用printf来进行打印的,而printf默认会打印到stdout对应的文件中,stdout中的fd为1,如果我们将数组下标为1的元素内容从显示器改变为目标文件,那么这样就做到了将打印到显示器上的数据打印到text中。输入重定向也是同理,改变文件描述符的指向即可。即:重定向的原理就是改变数组元素的指向。这种改变是上层无法获知的,所以stdout依旧认为它对应的文件是显示器。
./test > text 2>&1
: 将test的执行结果输出到text中,并且将1号文件描述符的内容靠别到2号文件描述符中,即,stderr也会输出到text中./test 1>log 2>err
:将标准输出改变为log,标准错误改变为err
close(1);
int fd = open("log.txt", O_WRONLY|O_CREAT|_TRUNC); // 1
c语言库中struct FILE
结构体中有一个缓冲区,用来暂存数据,这个缓冲区叫做用户级缓冲区。内核结构体struct file
中有一个缓冲区,用来暂存数据,这个缓冲区叫做内核级缓冲区。缓冲区的目的是为了提高IO效率。
用户级缓冲区的刷新策略:‘
内核级缓冲区的刷新策略由os系统决定,当然我们可以调用fsync()
强制刷新。
当程序调用printf函数时,先将字符串拷贝到用户缓冲区,然后结合相关刷新策略,调用write函数将字符串从用户缓冲区拷贝到内核缓冲区,最后os结合刷新策略将数据刷新到外设中,这就是数据的流动方式。共经历三次拷贝。
#include
#include
#include
#include
#include
#include
int main()
{
// 情况1
printf("hello world!\n");
// 情况2 printf("hello world!");
const char* msg = "tieite\n";
write(1,msg, strlen(msg)),
fork();
return 0;
}
为什么会有这样奇怪的结果呢?这与缓冲区的刷新策略有关?因为printf默认输出到显示器,而显示器的刷新策略是行刷新,所以情况1正确执行。而情况2中,没有换行符,所以数据留在用户缓冲区中,没有刷新到内核缓冲区,等调用fork的时候,创建子进程,子进程又继承了父进程的代码和数据,故此就有了两份数据“hello world!”当父子进程退出时,都会刷新用户缓冲区,于是内核缓冲区中就有两份hello world。
磁盘是计算机主要的存储介质,可以存储大量的二进制数据,并且断电后也能保持数据不丢失。早期计算机使用的磁盘是软磁盘(Floppy Disk,简称软盘),如今常用的磁盘是硬磁盘([Hard disk](https://baike.baidu.com/item/Hard disk/2806058?fromModule=lemma_inlink),简称硬盘)。现在的pc大部分都用的是SSD固态硬盘(电)。
确定一个扇区的方式:先定位在哪个盘面上(磁头head),然后在定义哪个柱面cylinder(磁道),最后根据扇区(sector)编号即可定位某一个扇区。这种寻址方法也叫做CHS定位法。
操作系统的寻址方式和硬件的寻址方式需要解耦合,所以操作系统需要有一套新的地址。os将盘面逻辑抽象成线性的(类似以前的那种磁带全部展开后)。
将这样的结构看作一个数组,每一个扇区占一个数组下标,这样就可以轻易的获取每个扇区的地址。但是操作系统一次IO的基本单位是块(4KB),所以将8个扇区看作一个块,然后重新抽象得到下图。下面这样的地址叫做LBA-逻辑块地址。磁盘也叫做块设备,以块为单位进行IO。
我们电脑上只带有一块盘,但是为了更好的管理,所以将盘分为若干区,为了更好的管理一个区(区等同于c盘,d盘),又将区分为若干组。每个组内有许多字段。
Linux是将文件内容和属性分开存储的。
struct Inode{ int inode_num ; int block[NUM];///....};
os查找文件:
ls -li
:查看当前目录下所有文件的inode编号
- 先找到文件对应的Inode编号
- 在Inode Table中找到Inode节点
- 根据Inode节点获得其内容块的地址、
struct inode
{
int inode_number; //inode 编号
int ref_count; // 硬链接数
int modes; //权限
size_t uid;
size_t size;
//....
int databloacks[NUM]; //内容
};
touch
命令的时候,是os先遍历访问InodeBitmap寻找空闲的Inode Table,创建Inode,然后在当前目录下用Inode编号和文件名建立一个映射关系。目录也是一个文件,目录里面存储的是文件名和Inode编号的映射关系ln -s myfile myfile-soft
ln myfile myfile-hard
功能:
Linux下,每一个目录下都有两个特殊目录.
和..
,一个点代表当前路径,两个点代表上级路径,为什么呢?因为一个点是当前路径的硬链接,两个点是上级路径的硬链接。这两个特殊路径使得路径切换更加容易。
.
和..
是操作系统可用识别的特殊路径。