linux中文件IO
一. linux常用文件IO接口
1.1. 文件描述符
1.1.1. 文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符表的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。
1.1.2. 文件描述符这个数字是open系统调用内部由操作系统自动分配的,操作系统分配这个fd时也不是随意分配,也是遵照一定的规律的:fd从0开始依次增加。fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以当时一个进程最多允许打开20个文件。linux中文件描述符表是个数组(不是链表),所以这个文件描述符表其实就是一个数组,fd是index,文件表指针是value。当我们去open时,内核会从文件描述符表中挑选一个最小的未被使用的数字给我们返回。
1.1.3. fd中0、1、2已经默认被系统占用了,因此用户进程得到的最小的fd就是3了。这三个文件对应的fd就是0、1、2。这三个文件分别叫stdin、stdout、stderr。也就是标准输入、标准输出、标准错误。
1.2. open
1.2.1. 在linux系统中要操作一个文件,一般是先open打开一个文件,得到一个文件描述符,然后对文件进行读写操作(或其他操作),最后close关闭文件即可
1.2.2. 文件平时是存在块设备中的文件系统中的,我们把这种文件叫静态文件。当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中建立了一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内存中特定地址管理存放(叫动态文件)。
1.2.3. 打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件的,而并不是针对静态文件的。当我们对动态文件进行读写后,此时内存中的动态文件和块设备中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。这样做主要由于:块设备本身有读写限制(回忆NnadFlash、SD等块设备的读写特征),本身对块设备进行操作非常不灵活。而内存可以按字节为单位来操作,而且可以随机操作(内存就叫RAM,random),很灵活。所以内核设计文件操作时就这么设计了。
1.3. read
ssize_t read(int fd, void *buf, size_t count);
a. fd表示要读取哪个文件,fd一般由前面的open返回得到
b. buf是应用程序自己提供的一段内存缓冲区,用来存储读出的内容
c. count是我们要读取的字节数
d. 返回值ssize_t类型是linux内核用typedef重定义的一个类型(其实就是int),返回值表示成功读取的字节数。
1.4. write
ssize_t write(int fd, const void *buf, size_t count);
1.4.1. 写入用write系统调用,write的原型和理解方法和read相似
1.5. lseek
off_t lseek(int fd, off_t offset, int whence);
1.5.1. 文件指针:当我们要对一个文件进行读写时,一定需要先打开这个文件,所以我们读写的所有文件都是动态文件。动态文件在内存中的形态就是文件流的形式。
1.5.2. 在动态文件中,我们会通过文件指针来表征这个正在操作的位置。所谓文件指针,就是我们文件管理表这个结构体里面的一个指针。所以文件指针其实是vnode中的一个元素。这个指针表示当前我们正在操作文件流的哪个位置。这个指针不能被直接访问,linux系统用lseek函数来访问这个文件指针。
1.5.3. 当我们打开一个空文件时,默认情况下文件指针指向文件流的开始。所以这时候去write时写入就是从文件开头开始的。write和read函数本身自带移动文件指针的功能,所以当我write了n个字节后,文件指针会自动向后移动n位。如果需要人为的随意更改文件指针,那就只能通过lseek函数了
1.5.4. 用lseek计算文件长度(length = lseek(fd,0,SEEK_END))
1.6. close
int close(int fd);
1.6.1. 关闭打开的文件
PS:实时查man手册
(1)当我们写应用程序时,很多API原型都不可能记得,所以要实时查询,用man手册
(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查库函数
二. open函数的flag详解
2.1. 读写权限
a. O_RDONLY就表示以只读方式打开,
b. O_WRONLY表示以只写方式打开,
c. O_RDWR表示以可读可写方式打开
2.2. 打开存在并有内容的文件时
2.2.1. 当我们打开一个已经存在并且内部有内容的文件时会怎么样?
可能结果1:新内容会替代原来的内容(原来的内容就不见了,丢了)
可能结果2:新内容添加在前面,原来的内容继续在后面
可能结果3:新内容附加在后面,原来的内容还在前面
可能结果4:不读不写的时候,原来的文件中的内容保持不变
2.2.2. O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,则原来的内容会被丢弃。这就对应上面的结果1
2.2.3. O_APPEND属性去打开文件时,如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面,对应结果3
2.2.4. 默认不使用O_APPEND和O_TRUNC属性时就是结果4
2.3. 打开不存在的文件时
2.3.1. 当我们open打开一个文件时如果这个文件名不存在则会打开文件错误。
2.3.2. O_CREAT
a. O_CREAT就表示我们当前打开的文件并不存在,我们是要去创建并且打开它
b. open中加入O_CREAT后,不管原来这个文件存在与否都能打开成功,如果原来这个文件不存在则创建一个空的新文件,如果原来这个文件存在则会重新创建这个文件,原来的内容会被消除掉(有点类似于先删除原来的文件再创建一个新的)
c. open函数在使用O_CREAT标志去创建文件时,可以使用第三个参数mode来指定要创建的文件的权限。mode使用4个数字来指定权限的,其中后面三个很重要,对应我们要创建的这个文件的权限标志。譬如一般创建一个可读可写不可执行的文件就用0666
2.3.3. O_EXCL
a. O_EXCL标志和O_CREAT标志来结合使用。当这连个标志一起的时候,则没有文件时创建文件,有这个文件时会报错提醒我们。
2.4. O_NONBLOCK
2.4.1. 我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。
2.4.2. 只用于设备文件,而不用于普通文件
2.5. O_SYNC
2.5.1. write阻塞等待底层完成写入才返回到应用层。
2.5.2. 无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层(操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。
三. 文件读写的一些细节
3.1. errno
3.1.1. errno就是error number,意思就是错误号码。linux系统中对各种常见错误做了个编号,当函数执行错误时,函数会返回一个特定的errno编号来告诉我们这个函数到底哪里错了。
3.1.2. errno是由OS来维护的一个全局变量,任何OS内部函数都可以通过设置errno来告诉上层调用者究竟刚才发生了一个什么错误。
3.1.3. errno本身实质是一个int类型的数字,每个数字编号对应一种错误。当我们只看errno时只能得到一个错误编号数字(譬如-37),不适应于人看。
3.2. perror
3.2.1. linux系统提供了一个函数perror(意思print error),perror函数内部会读取errno并且将这个不好认的数字直接给转成对应的错误信息字符串,然后print打印出来。
3.3. 文件IO效率和标准IO
3.3.1. 文件IO就指的是我们当前在讲的open、close、write、read等API函数构成的一套用来读写文件的体系,这套体系可以很好的完成文件读写,但是效率并不是最高的。
3.3.2. 应用层C语言库函数提供了一些用来做文件读写的函数列表,叫标准IO。标准IO由一系列的C库函数构成(fopen、fclose、fwrite、fread),这些标准IO函数其实是由文件IO封装而来的(fopen内部其实调用的还是open,fwrite内部还是通过write来完成文件写入的)。标准IO加了封装之后主要是为了在应用层添加一个缓冲机制,这样我们通过fwrite写入的内容不是直接进入内核中的buf,而是先进入应用层标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单次write的最佳count来选择好的时机来完成write到内核中的buf(内核中的buf再根据硬盘的特性来选择好的实际去最终写入硬盘中)。
3.3.3. 库函数比API还有一个优势就是:API在不同的操作系统之间是不能通用的,但是C库函数在不同操作系统中几乎是一样的。所以C库函数具有可移植性而API不具有可移植性。
3.3.4. 性能上和易用性上看,C库函数一般要好一些。譬如IO,文件IO是不带缓存的,而标准IO是带缓存的,因此标准IO比文件IO性能要更高。
四. 退出进程方式
4.1. 在main(main函数由其父进程调用,故返回后进程就over)用return,一般原则是程序正常终止return 0,如果程序异常终止则return -1。
4.2. 正式终止进程(程序)应该使用exit或者_exit或者_Exit之一。
五. 文件共享的实现方式
5.1. 什么是文件共享
5.1.1. 文件共享就是同一个文件(同一个文件指的是同一个inode,同一个pathname)被多个独立的读写体(几乎可以理解为多个文件描述符)去同时(一个打开尚未关闭的同时另一个去操作)操作。
5.1.2. 文件共享的意义有很多:譬如我们可以通过文件共享来实现多线程同时操作同一个大文件,以减少文件读写时间,提升效率。
5.2. 实现共享内存三种方式
5.2.1. 同一个进程中多次使用open打开同一个文件
5.2.1.1. 我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件管理表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件管理表。
5.2.1.2. 两个fd读文件时互相不干扰,都可以读出各种文件管理表指针内容
5.2.1.3. 两个fd写入时独立协议。故存在后写入的覆盖现象
5.2.1.4. open时加O_APPEND标志即可解决覆盖问题。
5.2.1.4.1. O_APPEND的实现原理和其原子操作性说明
a. O_APPEND为什么能够将分别写改为接续写?O_APPEND标志可以让write和read函数内部多做一件事情,就是移动自己的文件指针的同时也去把别人的文件指针同时移动。(也就是说即使加了O_APPEND,fd1和fd2还是各自拥有一个独立的文件指针,但是这两个文件指针关联起来了,一个动了会通知另一个跟着动)
b. O_APPEND对文件指针的影响,对文件的读写是原子的。
c. 原子操作的含义是:整个操作一旦开始是不会被打断的,必须直到操作结束其他代码才能得以调度运行,这就叫原子操作。每种操作系统中都有一些机制来实现原子操作,以保证那些需要原子操作的任务可以运行。
5.2.2. 在不同进程中去分别使用open打开同一个文件(这时候因为两个fd在不同的进程中,所以两个fd的数字可以相同也可以不同)
5.2.2.1. 由于也是独立指针故和5.2.1.相似。
5.2.3. linux系统提供了dup和dup2两个API来让进程复制文件描述符
六. 实例程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> int main(int argc, char *argv[])
{
int fd1 = -, fd2 = -; // fd 就是file descriptor,文件描述符
char buf[] = {};
char writebuf[] = "l love linux";
int ret = -; // 第一步:打开文件
fd1 = open("a.txt", O_RDWR);
fd2 = open("a.txt", O_RDWR); //fd = open("a.txt", O_RDONLY);
if ((- == fd1) || (fd2 == -)) // 有时候也写成: (fd < 0)
{
//printf("\n");
perror("文件打开错误");
// return -1;
_exit(-);
}
else
{
printf("文件打开成功,fd1 = %d. fd2 = %d.\n", fd1, fd2);
} #if 0
// 第二步:读写文件
// 写文件
ret = write(fd, writebuf, strlen(writebuf));
if (ret < )
{
//printf("write失败.\n");
perror("write失败");
_exit(-);
}
else
{
printf("write成功,写入了%d个字符\n", ret);
}
#endif #if 1
while()
{
// 读文件
memset(buf, , sizeof(buf));
ret = read(fd1, buf, );
if (ret < )
{
printf("read失败\n");
_exit(-);
}
else
{
//printf("实际读取了%d字节.\n", ret);
printf("fd1:[%s].\n", buf);
} sleep(); // 读文件
memset(buf, , sizeof(buf));
ret = read(fd2, buf, );
if (ret < )
{
printf("read失败\n");
_exit(-);
}
else
{
//printf("实际读取了%d字节.\n", ret);
printf("fd2:[%s].\n", buf);
} } #endif // 第三步:关闭文件
close(fd1);
close(fd2); _exit();
}
linux中文件IO的更多相关文章
- Linux中的IO复用接口简介(文件监视?)
I/O复用是Linux中的I/O模型之一.所谓I/O复用,指的是进程预先告诉内核,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,从而不会在单个I/O上导致阻塞. 在Linux ...
- [转]Linux中文件权限目录权限的意义及权限对文件目录的意义
转自:http://www.jb51.net/article/77458.htm linux中目录与文件权限的意义 一.文件权限的意义 r:可以读这个文件的具体内容: w:可以编辑这个文件的内容,包括 ...
- LSOF查看linux中文件打开情况
如何查看linux中文件打开情况 前言 我们都知道,在linux下,“一切皆文件”,因此有时候查看文件的打开情况,就显得格外重要,而这里有一个命令能够在这件事上很好的帮助我们-它就是lsof. lin ...
- linux中文件压缩介绍
原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等问题,可查看当前链接:https://app.yinxiang.com/shard/s17/nl/19391737/1c62bb7f-f ...
- linux中文件颜色,蓝色,白色等各自代表的含义
linux中文件颜色,蓝色,白色等各自代表的含义 绿色文件---------- 可执行文件,可执行的程序 红色文件-----------压缩文件或者包文件 蓝色文件----------目录 白色文件- ...
- 深入理解JAVA I/O系列六:Linux中的IO模型
IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大致描述了数据从外部磁盘向运行中程序的内存中移动的过程. 用户空间.内核空间 现在操作系统都是采用虚拟存储器, ...
- linux下 文件IO 相关
linux下操作文件或设备,需要一个文件描述符 file descriptor,fd 来引用.fd是一个非负整数,实际上是一个索引值,指向文件的记录表,对文件的操作都需要fd.默认的几个:标准输入流 ...
- 深入理解JAVA I/O系列六:Linux中的IO模型(转载的文章非常值得学习)
From:http://www.cnblogs.com/dongguacai/p/5770287.html IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大 ...
- linux中文件的三种time(atime,mtime,ctime)
linux下文件有3个时间的,分别是atime,mtime,ctime.有些博友对这3个时间还是比较迷茫和困惑的,我整理了下,写下来希望对博友们有所帮助. 1 这三个time的含义 简名 全名 中文名 ...
随机推荐
- NOIP2016提高A组模拟9.17总结
第一题,典型的隔板问题, 但是我忘记隔板问题怎么打,一开始在花了1小时,还是没想出来,果断弃疗, 最后的40分钟,我打完了第二题,接着又用了20分钟推敲出一种极其猥琐的式子来代替,可惜预处理的阶乘忘记 ...
- 【NOIP2016提高A组模拟9.15】Map
题目 分析 发现,当原图是一棵树的时候,那么新建一条边后,就会变成环套树, 而环内的所有点对都是安全点对,如果环中有k个点,答案就是\(k(k-1)\) 联想到,当把原图做一遍tarjan缩点,每个环 ...
- 织梦dedecms自定义表单导出到excel教程
不写死任何字段,不写死任何东西,修改2个文件,让织梦自定义表单自由导出到Excel表格里. 添加教程 1.\dede\templets\diy_main.htm 找到 前台预览</a> 在 ...
- 启动 jvm 参数小总结
1.启动某项目 nohup java -jar -Xms256m -Xmx512m -Dspring.config.location=/config/application.yml -Dfile.en ...
- cvpr 2019 workshop&oral session
1. Verification and Certification of Neural Networks神经网络的验证与认证 2. Automated Analysis of Marine Video ...
- java8 for循环了改为 流
刚开始用java8的时候,很多都是替代for循环,因为java8推出了强大的流stream,关于流的用法很多,百度一下就可以搜到语法之类,所以这里我只想举一些简单替代for的例子,含义那些就自己去查吧 ...
- Json-lib 的学习笔记
json 按照我的理解来说,就是一个字串表,可以用来表示对象的字符串,也可以用来表示数组.它比 xml 文件节省了很多标签的内容. 关于什么是 json,在这里我们就不过多介绍了. Json-lib ...
- Fatal error: Class 'think\db' not found
在model层写了一个查询语句结果报错 Fatal error: Class 'think\db' not found $list= Db::table('m_my_reserve_assess' ...
- webpack前置知识2(JavaScript项目初始化)
所有的JavaScript项目都是在终端输入npm init -y进行项目初始化,如果要自定义项目规则,去掉 -y 参数. vscode终端快捷键ctrl+` 初始化 运行上述命令后,项目内会新建一个 ...
- 电脑出现了一块tap window adapter v9 网卡 以及虚拟机桥接模式无法通信原因
计算机与外界局域网的连接是通过主机箱内插入一块网络接口板(或者是在笔记本电脑中插入一块PCMCIA卡).网络接口板又称为通信适配器或网络适配器(network adapter)或网络接口卡NIC(Ne ...