Linux操作系统-系统级IO
Posted TangguTae
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux操作系统-系统级IO相关的知识,希望对你有一定的参考价值。
目录
基本概念
I/O:输入输出,是在主存和外部设备(例如磁盘驱动器、终端和网络)之间复制数据的过程。输入操作是从I/O设备复制数据到主存,而输出操作时从主存复制数据到I/O设备。
所有的I/O设备在Linux系统下被模型化为文件,所以可以这样说,Linux下一切皆文件。Linux内核引出一个简单、低级的应用接口(一系列的系统函数)来操作文件,称为Unix I/O。
在C语言中,C标准库中提供的像printf、scanf、fopen、fwrite等等I/O函数,在Linux系统中则是open、write、read等系统级I/O函数。高级别的I/O函数其实就是通过这些系统级的函数来实现的。
每个Linux文件都有自己的类型,例如普通文件(包含任意数据)、目录(包含一组链接的文件)、套接字(socket网络通信的文件)、管道(进程间通信)等等
open函数
用法
open函数将filename转换为一个文件描述符,如果打开不成功返回-1,打开成功返回文件描述符
open第一个参数是带路径或者不带路径的文件名称(默认当前路径)
第二个参数flags表示打开的方式
比如flags可以设置成O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读可写)的方式打开,改参数可以设置多个模式,比如O_RDONLY | O_CREAT表示以只读的方式打开,如果没有该文件,就创建一个同名的文件。
最后一个参数是新建一个文件默认给它的权限是什么(注意,关于全限这里还需异或umask)。
例子:
#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);//以只读的方式打开log.txt
if(fd < 0)//打开失败
cerr<<"open error"<<endl;
exit(1);
char buf[128]='\\0';
ssize_t s = read(fd,buf,127);//从打开的文件中读取内容
if(s > 0)
buf[s] = '\\0';
cout<<buf<<endl;
cout<<fd<<endl;
//需要close文件描述符
return 0;
先建好文件,将hello world重定向到log.txt文件当中,运行的结果如上图所示。
成功打开文件,文件描述符为3。
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;
int main()
int fd1 = open("log1.txt",O_RDONLY|O_CREAT);
int fd2 = open("log2.txt",O_RDONLY|O_CREAT);
int fd3 = open("log3.txt",O_RDONLY|O_CREAT);
int fd4 = open("log4.txt",O_RDONLY);
int fd5 = open("log5.txt",O_RDONLY);
cout<<fd1<<endl;
cout<<fd2<<endl;
cout<<fd3<<endl;
cout<<fd4<<endl;
cout<<fd5<<endl;
//需要close掉文件描述
return 0;
期初没有log1-5这5个文件,前三个open打开时增加O_CREAT选项,默认没有则创建,后面则不然。
打开成功返回文件描述符,负责返回-1,然后创建对应的文件。
文件描述符是什么?
这些数字本身是数组下标
上述例子发现新打开一个文件描述符默认是从3开始的,那么既然是数组下标,那么前面的0,1,2代表什么?
描述符0:标准输入(stdin)
描述符1:标准输出(stdout)
描述符2:标准错误(stderr)
在一个进程里面可能会打开多个文件,所以这些文件描述符需要被管理起来(先描述,后组织)
底层原理:
1、每个文件对应一个结构体 struct file(和进程类似,task_struct)(描述)。
2、将文件连接起来(链表的方式),这样就可以通过一个文件找到其他文件的位置。
3、将进程的PCB与文件关联起来(1:n的关系),linux内核采用数组的方式关联。
这个就是文件描述符的数组fd_array[],struct file* fd_array[],用一个指针数组来存储文件对应的结构体指针。
在task_struct结构体里有一个struct files_struct*成员变量
在这个结构体里面又有fd_array这个struct file*的成员变量
再往里面走一走,看看这个指针数组指向的内容到底是什么:
在fs.h这个头文件里定义了struct file结构体,struct file这个结构体比较复杂,大概包含文件的属性信息、文件的缓冲、位置、inode信息和一些方法。
struct file_operations 定义了很多方法
可以发现里面都是函数指针的形式,原因是对于不同的设备(硬件:硬盘、网卡、键盘等)他的读写的方法不一样,所以对应的操作系统打开这些设备,对这些设备操作的时候方法(读、写)也要有区别,所以用函数指针的方式指向他们各自的方法(驱动)。
所以整体的逻辑就是进程task_struct中有指向files_struct的结构体,files_struct中有指针数组fd_array存放file类型的指针,file类型的结构体中又有一个file_operations的成员变量,在file_operations结构体中最终的系统函数read、write等函数指针。
read和write函数
read函数
read函数从描述符fd的当前文件位置读取(复制)最多count个字节到内存的buf位置。 一般选择少读一个字符,然后人工加入'\\0',因为read是不关心'\\0'。
返回值是一个ssize_t类型,返回值为-1的时候表示读出错,如果返回值为大于0表示实际读到了多少字节,如果返回值等于0表示读到了文件的结尾。
write函数
write函数是从内存位置buf复制至多count个字节到文件描述符所对应的文件 。
如果写入成功,返回写入的字节数,如果不成果返回-1;
文件重定向
对于linux下要把命令行的参数重定向到文件中可以用 > 或者 >> 来进行重定向
例子:
原理
对于进程来说,默认的文件描述从3开始,默认从最小的没有被分配的fd开始分配
默认情况下三个file分别为0,1,2,对应的0:硬件为键盘(stdin):1:显示器(stdout)、2:显示器(stderr)。
如果我们1给关掉,即把标准输出的文件描述符给close掉,close(1); 此时如果在打开一个文件,默认分给这个文件的文件描述符为1。
上述过程是在内核里完成的,应用层的命令行并不知道放fd = 1已经不是stdout了,所以这就叫重定向。
例子:
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;
int main()
close(1);//关掉标准输入
int fd = open("log.txt",O_WRONLY|O_CREAT);//新打开一个文件
cout<<"hello world"<<endl;//输出
cout<<fd<<endl;
close(fd);
return 0;
追加重定向
O_APPEND
打开文件时多加一个O_APPEND选项,就可以实现追加重定向
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;
int main()
close(1);//关掉标准输入
int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);//新打开一个文件
cout<<"hello world"<<endl;//输出
cout<<fd<<endl;
close(fd);
return 0;
运行两次test,发现连续往同一个文件里写入了两次相同的数据,实现了追加重定向。
小细节:
这些程序运行后的进程为什么打开了0,1,2这三个文件描述符,而不需要程序员进行open?
因为最开始的那个进程打开了输入输出,fd前三个元素在最开始的进程中已经指向了对应的struct file,所以在创建子进程时会按照父进程的模板创建,所以也同样的默认的打开了输入输出文件。注意:fd还是数据,一旦修改会发生写时拷贝
dup2函数
dup2函数复制描述符表项oldfd到newfd之中,覆盖newfd之前的内容。如果newfd已经打开了,dup2会在复制之前关闭newfd。注意,这个拷贝是对内容的拷贝而不是对数字的拷贝。
#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_WRONLY|O_CREAT|O_APPEND,0644);//新打开一个文件
dup2(fd,1);//用dup2来重定向
cout<<"hello world"<<endl;//输出
cout<<fd<<endl;
close(fd);
return 0;
注意:
文件描述符也是系统资源,打开了记得关闭,不然会造成系统泄漏。
文件系统
Linux文件系统(ext列),Windows文件系统(NTFS)
文件 = 属性 + 内容
通常情况下,文件会将属性与内容分开存放,文件的属性存放在inode里面,文件的内容存放在block里面。
文件分区
磁盘是一个非常大的存储介质,是典型的的块设备需要将物理内存映射到逻辑内存,然后将其管理起来。一个磁盘有多个分区,一个文件分区:
一个文件分区有多个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 节点表(inode Table):存放文件属性 如 文件大小,所有者,最近修改时间等。
- 数据区(Data blocs):存放文件内容。
inode
inode的内容主要记录文件的属性以及该文件实际数据的位置。
inode Table中可以存放多个inode,但是inode的个数有限制,所以会存在有空间而没办法新建文件的可能。
Data blocks存放数据,里面有很多block,一个block的大小是由格式化的时候确定的(可以设定1k,2k,4k字节)。
一个文件对应一个inode,而一个文件可以有多个block(当文件太大),所以他们之间的对应关系为 文件:inode:block =1:1:n。
所以说我们要找到一个对应的文件,需要先找到inode,而inode有一个id,在Linux下通过 ls -li 查看每个文件的所对应的inode的id。
最左边就是文件的inode对应的id,其实目录本身也是一个文件,里面只存文件名和对应的inode的映射关系。 inode id是操作系统所关心的,而我们用户层面只关心的文件名等一些信息。
删除一个文件:
只需要将inode Bitmap中对应的位置0,对应的block Bitmap中的位也置为0。
其中的数据并没有被抹去,所以可以恢复数据
硬链接与软链接
硬链接:和指向的文件共享同一个inode,不是一个独立的文件。
ln 创建链接(默认硬链接)
可以看到具有相同的inode。这两个文件是共享的,删除其中一个不影响另外一个。
中间的一串数字代表硬链接文件的个数 ,包括自己。
一个文件目录默认是2,因为目录下面默认有两个隐藏文件
一个是当前目录. 和上一级目录.. 。
软链接:具有独立的inode ,是一个独立的文件
ln -s可以软链接
属性为l代表链接文件,他的inode与log.txt的inode不同,所以是一个独立的文件。
总结
这些东西有点绕,需要总结一下
当我新建一个文件的时候,文件(struct file) = 属性 + 数据,属性对应着inode,每个inode对应一个id,linux内部通过inode id来识别文件,所以说系统并不记录文件名(文件名存放在目录当中,目录建立文件名与inode id的映射关系),只认识文件的inode id,而inode里面是包含很多属性信息的,还包含block在dataBlocks中的映射关系,这种映射可以找到数据在磁盘的哪个地方。当进程打开文件的时候,会返回一个文件标识符fd来记录此时打开的是哪一个文件,接下来的read,write等操作,是直接操作文件标识符fd来实现的,而管理文件标识符的是file* fd_array这个指针数组,fd就是指针数组的下标。fd_array又在files_struct这个结构体当中,在创建一个进程的时候,存在一个files_struct* 的指针指向这个结构体,最终由进程所管理。
以上是关于Linux操作系统-系统级IO的主要内容,如果未能解决你的问题,请参考以下文章