文件操作的系统调用
open
O_RDONLY: 只读打开
O_WRONLY: 只写打开 O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
open的返回值
返回最小没有使用的文件描述符fd(一般是3),012默认打开。在内核中,OS为每个打开的文件创建struct file结构体,并用链表组织管理起来。struct file中包含前后指针和文件属性(从磁盘中载入)
一个进程对应一个task_struct,一个进程可以打开多个文件,每个文件对应一个file结构体,fd在内核中本质是一个指针数组下标,打开一个文件把file内容的地址填充到array数组中,随后把数组下标即fd返回给用户。
看下面这段代码
int main()
{
close(1);
int fd=open("log.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return -1;
}
printf("fd:%d\n",fd);//stdout->FILE(fd=1)->log.txt
printf("hello world\n");
fprintf(stdout,"hello,fprintf\n");
const char* s="hello,fwrite\n";
fwrite(s,strlen(s),1,stdout);
fflush(stdout); //刷新缓冲区
close(fd);
return 0;
}
Linux下一切皆文件,一个进程默认打开三个文件:标准输入、标准输出、标准错误,并给它们分配fd,分别是0、1、2.
关闭了fd=1,即stdout,打开的log.txt的fd便占据了fd=1的数组位置,数组中该位置的内容映射到了struct file对象中log.txt的位置,所以printf等输出到stdout里的内容就输入到log.txt里去了。这种操作叫做重定向,重定向的本质是在OS内部,更改fd内容的指向。
重定向的系统调用
dup2
缓冲区
缓冲区本质就是一段内存,其意义是节省进程进行数据 IO 的时间
缓冲区的刷新策略
立即刷新 — 无缓冲
行刷新 — 行缓冲(显示器)
缓冲区满 — 全缓冲(磁盘文件)
为什么显示器采取的是行缓冲呢?因为显示器是给用户看的,如果采取全缓冲的刷新策略,用户体验会比较差。为了用户体验不会太差且 IO 效率也不至于太低,所以显示器就采取了行缓冲的刷新策略。
两种特殊情况:一是用户强制刷新缓冲区;二是进程退出时,一般都要进行缓冲区刷新。
一个例子
int main()
{
printf("hello,printf\n");
fprintf(stdout,"hello,fprintf\n");
const char* s="hello,fputs\n";
fputs(s,stdout);
const char* ss="hello,write\n";
write(1,ss,strlen(ss));
fork();
return 0;
}
上述代码向显示器上打印,对应的刷新策略是行刷新,那么最后执行fork时c语言的缓冲区已经刷新完成无数据了。
对比
然而我们重定向到文件中,输出结果就如上。向磁盘文件中打印,刷新策略就变成了全缓冲。fork()的时候函数执行完了,但数据还没有刷新。仍在当前进程c标准库中的缓冲区中,子进程拷贝父进程的数据和缓冲区,最后子进程结束,刷新子进程缓冲区(父子进程共享数据发生修改会发生写时拷贝),父进程结束刷新父进程缓冲区。write是系统调用,不会进入语言层的缓冲区,所以只会打印一次。
磁盘寻址方式
分治管理磁盘文件
磁盘的基本单位是扇区(512bytes),但是操作系统(文件系统)和磁盘IO的基本单位是:4kb(8*512bytes)
为何?
1.太小了可能会导致多次IO,进而导致效率降低
2.如果操作系统和磁盘一样大,如果磁盘IO基本大小变了,OS的源代码也要变。因此硬件和软件进行解耦
super block:表示整体的控制信息,例如硬盘分区中每个block的大小、硬盘分区上一共有多少个block group、以及每个block group中有多少个inode。每个分区有备份
Data blocks:多个4kb大小的集合,(linux在磁盘上存储文件的时候,将内容和属性是分开存储的),保存的是文件的内容。
inode table:inode是一个大小为128bytes的空间,保存的是对应的文件属性,该块组内,所有文件的inode集合需要标识唯一性,每一个inode块都要有一个inode编号。一个文件,一个inode,一个inode编号。
bitmap:有多少个blocks,就有多少个比特位,比特位和特定的block一一对应,其中比特位为1,代表该block被占用。inode bitmap对应inode节点是否被占用,block bitmap 对应特定的block。
group descriptor table:块组描述符,这个块组多大,已经使用了多少,有多少个inode,已经占用了多少等等。
一个文件对应一个inode属性节点,一个文件不一定只有一个block,inode结构体保存block块的编号,遇到较大的文件,有些data block可以存其他块的块号。
如何查看文件
一个目录下,可以保存很多文件,但是这些文件没有重复的文件名。
目录是文件,有自己的inode,有自己的data block。
而我们查看文件使用的是文件名,并没有使用 inode 来查看文件。其实文件名和 inode 存在一一映射的关系,目录的数据块放的就是当前目录文件名和 inode 的映射关系。所以同一个目录下,不可以存在同名的文件。因为文件名就是一个 key 值,通过该 key 值找到对应的 inode 编号,进而找到该文件!!!所以在一个目录里创建文件,必须要有该目录的写入权限。因为要将文件名和 inode 的映射关系写入到目录的数据块中。
软链接和硬链接
ln -s 现有文件 目标文件 #将目标文件与现有文件进行软链接
ln 现有文件 目标文件 #将目标文件与现有文件进行硬链接
unlink 文件名 #删除软链接文件
本质区别:有无独立的inode,软链接有独立的inode(软链接是一个独立的文件),硬链接没有独立的inode。
软链接相当于windows下的快捷方式
硬链接相当于建立文件名与inode的关联,可以有多个文件名指向同一个inode,修改inode结构体中引用计数,当删除一个文件名时引用计数–,当引用计数减为0,才真正删除了文件。
默认创建一个目录,引用计数(硬链接)是2:自己目录名(.)和上级目录名(…)inode的映射关系。