[Linux]基础IO

●个人主页:你帅你先说.
●欢迎点赞关注收藏
●既选择了远方,便只顾风雨兼程。
●欢迎大家有问题随时私信我!
●版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。

为您导航

  • 1.回顾C语言文件操作
  • 2.语言I/O
  • 3.系统I/O
  • 3.1open
    • 3.2文件描述符fd
    • 3.3文件描述符的分配规则
    • 3.4输出重定向
    • 3.5dup2系统调用
    • 3.6缓冲区
  • 4.文件系统
    • 4.1inode
    • 4.2软硬链接
    • 4.3ACM

1.回顾C语言文件操作

#include 
#include 
int main()
{
	 FILE *fp = fopen("myfile", "w");
	 if(!fp)
	 {
	 	printf("fopen error!\n");
	 }
	 const char *msg = "hello bit!\n";
	 int count = 5;
	 while(count--)
	 {
	 	fwrite(msg, strlen(msg), 1, fp);
	 }
	 fclose(fp);
	 return 0;
}

2.语言I/O

每次我们运行C程序的时候,C程序默认会打开三个输入输出流,stdin,stdout,stderr。这三个流的类型都是FILE*, fopen返回值类型,文件指针。
前面我们也讲过
stdin:键盘
stdout:显示器
stderr:显示器

[Linux]基础IO_第1张图片
在这里插入图片描述
刚刚我们说了stdout、stderr都是显示器,那是不是用stderr也可以输出显示呢?确实是这样的。那这两个有什么区别吗?
stdout下的重定向
在这里插入图片描述
stderr下的重定向
在这里插入图片描述
所以这里重定向的本质是把stdout的内容重定向到文件中,而对于stderr则不会重定向到文件中。
总结一下:
不管你要输入还要输出,最终都是访问硬件:显示器、键盘、文件(磁盘)。而硬件是由操作系统管理的,所有语言上对文件的操作,都必须贯穿操作系统,访问操作系统,需要通过系统调用接口,所以几乎所有语言的fopen、fclose、fread等底层一定需要使用OS提供的系统调用。

3.系统I/O

3.1open

#include 
#include 
#include 

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
	 O_RDONLY: 只读打开
	 O_WRONLY: 只写打开
	 O_RDWR : 读,写打开
	 这三个常量,必须指定一个且只能指定一个
	 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
	 O_APPEND: 追加写
返回值:
	 成功:新打开的文件描述符
	 失败:-1
//注意,在对文件进行写操作时,不需要写入"\0",因为"\0"作为字符串的结束,只是C的规定。

举个例子

int main()
{
	//C语言接口
	fopen("./log.txt","w");
	//系统接口
	int fd = open("./log.txt",O_WRONLY | O_CREAT,0664/*设置初始权限*/);
	//系统接口其实就是C语言接口的底层接口
}

这里面的O_RDONLY、O_WRONLY等都是只有一个比特位是1的数据,且不重复。它们之间任意两个之间按位或构成flags代表一个标志。

3.2文件描述符fd

我们再来看看它的返回值fd
[Linux]基础IO_第2张图片

在这里插入图片描述
大家可能会疑惑,为什么没有0、1、2,直接从3开始,因为刚刚我们说了执行C语言程序时会默认打开三个输入输出流,而三个输入输出流是文件,所以0、1、2是stdin、stdout、stderr的描述符。
其实从这个例子我们也可以得到一个结论:一个进程可以打开多个文件
三个标准输入输出流的返回值是0、1、2,你能联想到什么?相信大家能猜到,数组下标,你没猜错,就是这样的。
那操作系统是怎么通过这个数组来管理这些文件的呢?一张图给你答案。
[Linux]基础IO_第3张图片
当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件
描述符,就可以找到对应的文件。文件的信息保存一个个struct file的结构体里也说明了我们前面说的操作系统管理的方式是先描述,再组织。
现在我们知道了进程和文件是如何建立联系的,不知道大家好不好奇这些文件是怎么和硬件进行联系的。同样,一张图带你了解。
[Linux]基础IO_第4张图片
所以,文件和硬件之间建立联系就是通过函数指针。所以站在struct file的上层来看,所有的文件,读和写只需要调用对应的接口就行,不用关心到底是什么文件,所以一切皆文件

3.3文件描述符的分配规则

刚刚我们说了,0、1、2分别代表三个标准输入输出流,那我们是不是可以直接往显示器输出,答案是可以的。
向显示器输出
[Linux]基础IO_第5张图片
[Linux]基础IO_第6张图片
从显示器读入
[Linux]基础IO_第7张图片在这里插入图片描述
这三个输入输出流是默认打开的,那如果我们一开始关掉其中一个会怎么样?
[Linux]基础IO_第8张图片
在这里插入图片描述
你会发现原来0的位置,被新的文件给继承了,所以文件描述符的分配规则是给新文件分配的fd,是从fd_array中找一个最小的,没有被使用的,作为新的fd。

3.4输出重定向

[Linux]基础IO_第9张图片
[Linux]基础IO_第10张图片
这段代码的结果很让人意外,为什么我们printf的内容没有显示在文件内,而是被写入了文件中,联想刚刚所讲的知识,不知道大家能不能想到原因。
刚刚我们说了0、1、2分别执行标准输入、标准输出、标准错误,当你把标准输出关闭,然后又创建了新文件,新文件会继承1的位置,而我们printf的内容出现到了文件中,说明printf的本质是向标准输出打印
我们在使用C语言定义文件类型的时候,使用的是FILE*,相信大家也能猜到,这肯定是个结构体,在语言层面,我们有printf、fprintf、cin等输入输出函数,在系统层面,我们有open、write,这些函数会返回fd,语言层面使用这些函数一定是调用系统的这些接口,所以FILE*这个结构体里一定包含一个整数,是对应在系统层面这个文件打开对应的fd。再讲细一点,printf函数会向stdout打印,stdout是FILE*类型的,想要通过fd对应到显示器文件,就必须有一个整数来对应系统层面。
stdout->fd(1)->fd_array[1]->显示器文件。

echo "hello world" > log.txt

所以这个命令的原理你应该懂了,就是echo的标准输出关掉,然后创建文件,所以内容会写入到文件中。
所以刚刚我们讲了这么多,实际上是在讲输出重定向的原理。

3.5dup2系统调用

[Linux]基础IO_第11张图片
它的参数写的很直观,一个是你旧的fd,一个是你新的fd,新的fd是旧的fd的拷贝。相当于你把新的fd的指针指向旧的fd指针所指向的位置。
[Linux]基础IO_第12张图片
在这里插入图片描述
这里有一个问题,指向程序替换的时候,会不会影响我们曾经打开的所有的文件?
答案是肯定不会,因为我们文件的这些操作都是内核所对应的数据结构,而程序替换只会替换代码和数据,并不影响文件。
fork->child->dup2(fd,1)->exec*(“echo”,“echo”…)

3.6缓冲区

[Linux]基础IO_第13张图片

[Linux]基础IO_第14张图片
有了刚刚上面的讲解,相信这个结果是在大家的预期内的。
如果我们要把标准输出和标准错误一起写到文件里要怎么操作?
在这里插入图片描述
这个是命令行的一种写法,就是让它们都指向这个文件。
我们来看一段代码
[Linux]基础IO_第15张图片
这段代码执行完后你去查看log.txt你会发现里面什么内容也没有。什么原因?
[Linux]基础IO_第16张图片
首先,向stdout输出的数据会先保存到C语言的缓冲区里,当时机合适的时候会刷新到文件内核缓冲区,要将数据写入文件中,肯定需要fd,但最后你把fd给关掉了,所以数据也就不会刷新到文件内核缓冲区。我们之前写的进度条程序里fflush其实就是帮你把数据刷新到文件内核缓冲区,然后它的底层还会调用函数把文件内核缓冲区里的数据刷新到磁盘里。除了我们所熟知的当进程退出时缓冲区会刷新以外,还有三种刷新策略:
1.立即刷新(不缓冲)。
2.行缓冲(遇到\n刷新),例如显示器。
3.全缓冲,缓冲区满了才刷新,例如往磁盘文件里写数据。
[Linux]基础IO_第17张图片
在这里插入图片描述
这个结果现在你也应该理解为什么是输出4个,因为都是往显示器上打印,显示器是行刷新,遇到\n就会刷新,我输出完后你再关闭1不影响。
在这里插入图片描述
这个结果大家就会有疑惑了,为什么log.txt里面不是三条信息而是一条信息。刚刚我们说过了,往磁盘里面写数据是全缓冲,也就是重定向之后,缓冲方式变化了。fprintf来的数据还来不及刷新就已经被关掉了,所以我们看不到另外两条数据被写入磁盘中。那为什么用write写的数据可以被写入了?因为write是系统调用接口,不经过C语言缓冲区处理,直接把数据写入文件内核缓冲区。
[Linux]基础IO_第18张图片
[Linux]基础IO_第19张图片
我们发现系统调用接口并不受影响,只打印一份。而C语言的接口则打印了两份。我们来分析一下原因,首先,我们重定向之后,刷新策略变了,变成了全缓冲,当你fork()了一个进程后,父进程退出缓冲区刷新,本质是一种写入,所以就发生了写时拷贝,那么子进程和父进程就各自保留一份代码和数据。所以要解决问题,只需要在fork()之前加上fflush()刷新缓冲区,就不会发生写时拷贝了。现在回过头来想为什么write没有两份?我们刚刚说了,write是系统调用接口,它没有缓冲区,它直接写入到文件的内核缓冲区。

4.文件系统

[Linux]基础IO_第20张图片
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。

4.1inode

Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下
块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
数据区:存放文件内容

位图:
从右向左
0000 1010
比特位的位置含义:inode编号
比特位的内容含义:特定inode“是否”被占用
知道了位图的概念,你能推断出操作系统删除文件做了什么操作吗?
实际上就是把1置0,并没有真正删除文件,置0以后有新的文件就会覆盖这片区域。
Linux中文件名在系统层面没有意义,是给用户使用的,而操作系统在识别不同文件时,是通过inode编号,一个文件只有一个inode来唯一标识。

4.2软硬链接

[Linux]基础IO_第21张图片
删除软链接

unlink log_s(软链接名)

软链接有什么用?
[Linux]基础IO_第22张图片
比如你有一个程序在一个较深的目录下
[Linux]基础IO_第23张图片
通过软链接后
在这里插入图片描述
就可以直接在当前目录执行了。这种方式有点像我们Windows里的快捷方式。
[Linux]基础IO_第24张图片
我们发现,软链接是有自己独立的inode,它是一个独立文件,有自己的inode属性,也有自己的数据块。
既然有软链接,相应的就有硬链接。
在这里插入图片描述
[Linux]基础IO_第25张图片
我们发现,硬链接本质不是一个独立的文件,而是一个文件名和inode编号的映射关系,因为自己没有独立的inode。
[Linux]基础IO_第26张图片
这一列的数字代表的就是硬链接数,也就是有几个文件指向它。

4.3ACM

  • Access 最后访问时间
  • Change 属性最后修改时间
  • Modify 文件内容最后修改时间

在较新的Linux内核中,Access时间不会立即更新,而是有一定的时间间隔,OS才会自动进行更新时间。
我们之前将Makefile的时候说过,make只能执行一次,只有文件改动才能再次执行,本质其实是Makefile与gcc会通过Modify来判定源文件和可执行文件程序谁更新,从而指导系统哪些源文件需要被重新编译。

喜欢这篇文章的可以给个一键三连 点赞关注收藏

你可能感兴趣的:(Linux,运维,后端开发)