linux中文件IO

时间:2022-09-03 11:23:57

一. 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的更多相关文章

  1. Linux中的IO复用接口简介(文件监视?)

    I/O复用是Linux中的I/O模型之一.所谓I/O复用,指的是进程预先告诉内核,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程进行处理,从而不会在单个I/O上导致阻塞. 在Linux ...

  2. &lbrack;转&rsqb;Linux中文件权限目录权限的意义及权限对文件目录的意义

    转自:http://www.jb51.net/article/77458.htm linux中目录与文件权限的意义 一.文件权限的意义 r:可以读这个文件的具体内容: w:可以编辑这个文件的内容,包括 ...

  3. LSOF查看linux中文件打开情况

    如何查看linux中文件打开情况 前言 我们都知道,在linux下,“一切皆文件”,因此有时候查看文件的打开情况,就显得格外重要,而这里有一个命令能够在这件事上很好的帮助我们-它就是lsof. lin ...

  4. linux中文件压缩介绍

    原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等问题,可查看当前链接:https://app.yinxiang.com/shard/s17/nl/19391737/1c62bb7f-f ...

  5. 深入理解JAVA I&sol;O系列六:Linux中的IO模型

    IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大致描述了数据从外部磁盘向运行中程序的内存中移动的过程. 用户空间.内核空间 现在操作系统都是采用虚拟存储器, ...

  6. linux下 文件IO 相关

    linux下操作文件或设备,需要一个文件描述符 file descriptor,fd 来引用.fd是一个非负整数,实际上是一个索引值,指向文件的记录表,对文件的操作都需要fd.默认的几个:标准输入流 ...

  7. 深入理解JAVA I&sol;O系列六:Linux中的IO模型&lpar;转载的文章非常值得学习&rpar;

    From:http://www.cnblogs.com/dongguacai/p/5770287.html IO模型 linux系统IO分为内核准备数据和将数据从内核拷贝到用户空间两个阶段. 这张图大 ...

  8. linux中文件的三种time(atime&comma;mtime&comma;ctime)

    linux下文件有3个时间的,分别是atime,mtime,ctime.有些博友对这3个时间还是比较迷茫和困惑的,我整理了下,写下来希望对博友们有所帮助. 1 这三个time的含义 简名 全名 中文名 ...

  9. linux中的 IO端口映射和IO内存映射

    参考自:http://blog.csdn.net/zyhorse2010/article/details/6590488 CPU地址空间 (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬 ...

随机推荐

  1. (2)I2c总线SDA&bsol;SCL以及开始终止条件

    I2C只用两条线(SDA和SCL)在连接到总线上的设备之间传送数据.每一个设备都由唯一的地址来识别(不管是微处理器.LCD驱动器.存储器或者键盘接口),并且可以依照设备的功能作为发送器或者接收器使用. ...

  2. tabhost中setup&lpar;&rpar;和setup&lpar;LocalActivityManager activityGroup&rpar;

    如果用系统默认的tabhost时, 直接用getTabhost()初始化,整个类继承tabActivity. 当没有选择系统tabhost默认id时,而是自己定义的id时,必须使用 findViewB ...

  3. Ubuntu安装sar出错Please check if data collecting is enabled in &sol;etc&sol;default&sol;sysstat

    1.安装sysstat apt-get install sysstat 2.安装后无法使用: Cannot open /var/log/sysstat/sa02: No such file or di ...

  4. 怎么删除hao&period;qquu8&period;com绑定

    运行 输入 regedit 编辑 - 查找  hao.qquu8.com 然后修改成 你想绑定的 主页 就好

  5. 2017春 前端自动化&lpar;二&rpar; 页面自动刷新、sass与css转换的使用、pxToRem直观转换

    2017春 前端自动化(二)   页面自动刷新.sass与css转换的使用.pxToRem直观转换 引言:   此文要演示:浏览器页面自动刷新:移动端px与rem的转换,简单直观化:使用sass自动生 ...

  6. H5 26-CSS三大特性之优先级

    26-CSS三大特性之优先级 类>标签>通配符>继承>浏览器默认 --> 0 我是段落 <!DOCTYPE html> <html lang=&quot ...

  7. 实验十一 团队作业7—团队项目设计完善&amp&semi;编码测试

    实验十一 团队作业7—团队项目设计完善&编码测试 实验时间 2018-6-8 Deadline: 2018-6-20 10:00,以团队随笔博文提交至班级博客的时间为准. 评分标准: 按时交 ...

  8. python连接mysql数据库简单例子

    今天用pyhton2连接本地的mysql数据库,总的来说比较简单,但还是遇到一些小问题 代码如下: # -*- coding: utf-8 -*- import os import MySQLdb i ...

  9. bzoj 4408&colon; &lbrack;Fjoi 2016&rsqb;神秘数 数学 可持久化线段树 主席树

    https://www.lydsy.com/JudgeOnline/problem.php?id=4299 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1 ...

  10. &lbrack;转&rsqb;glyphicons-halflings-regular字体 图标

    本文转自:http://www.ijquery.cn/?p=377 一.介绍 采用这种字体,我们可以避免网站制作中采用好多图片,一方面解决了浏览器的兼容性问题.另一方面,这些字体都是矢量字体,我们只要 ...