前言

本章讨论普通文件的读写、读写效率、简单介绍文件描述符、IO效率、文件共享和原子操作、dup、文件映射、临时文件。

文件描述符

在Linux系统中,打开的文件是用一个整数来表示的,表示打开文件的整数,称之为文件描述符。当需要往写数据/读数据时,读写函数都需要文件描述符作为参数,以便系统知道用户操作的时哪个文件。

文件基本操作

open/creat

mode选项 解释
O_RDONLY 读方式打开
O_WRONLY 写方式打开
O_RDWR 读写方式打开
O_CREAT 创建文件,如果文件存在,会被截断
O_TRUNC 截断
O_APPEND 追加
O_EXCL 和O_CREAT一起用,如果文件存在则失败
int open(const char* path, int flag, ...);

open函数的flag值得是mode选项(注意互斥问题)。第三个参数指示新建的文件的属性。文件真实的权限,受umask的影响。影响方法

真实mode = 指定的mode & ~umask
 

close

关闭文件。

在dup时,有更多讨论。

read/write

读写文件,会导致文件指针移动。

文件指针和lseek

文件指针是一个整数,描述当前读写位置,可以使用lseek移动文件指针。

文件读写效率

当读写文件时,缓冲区设置为1024到4096是一个比较合适的尺寸。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h> int main1()
{
int fdr = open("a.out", O_RDONLY);
if(fdr < 0)
{
perror("open read");
return -1;
} int fdw = open("b.out", O_WRONLY|O_CREAT, 0777);
if(fdw < 0)
{
perror("open write");
return -1;
} // 1. 如果文件很大怎么办
int filelen = lseek(fdr, 0, SEEK_END);//读取文件的长度
lseek(fdr, 0, SEEK_SET);//将文件读的指针。制回开始位置 char* buf = malloc(filelen);
read(fdr, buf, filelen);
write(fdw, buf, filelen); close(fdr);
close(fdw);
} int main2()
{
int fdr = open("a.out", O_RDONLY);
if(fdr < 0)
{
perror("open read");
return -1;
} int fdw = open("b.out", O_WRONLY|O_CREAT, 0777);
if(fdw < 0)
{
perror("open write");
return -1;
} // 一个一个字节拷贝,效率很低下
char buf;
while(1)
{
if(read(fdr, &buf, 1) <= 0)
{
break;
}
write(fdw, &buf, 1);
} close(fdr);
close(fdw); } int main()
{
int fdr = open("a.out", O_RDONLY);
if(fdr < 0)
{
perror("open read");
return -1;
} int fdw = open("b.out", O_WRONLY|O_CREAT, 0777);
if(fdw < 0)
{
perror("open write");
return -1;
} // buf尺寸多少最合适?
char buf[1024];
int ret;
while(1)
{
ret = read(fdr, &buf, sizeof(buf));
if(ret <= 0)
{
break;
}
write(fdw, &buf, ret);//避免最后一次读取数据,没有1024个字节
} close(fdr);
close(fdw); }

文件共享

两个进程可以打开同一个文件进行操作,实现数据的共享。但是当两个进程打开同一个文件进行写操作时,会相互覆盖。当文件被打开两次时,两个文件描述符有各自的文件指针。

内核保存一个全局的文件描述结构体,而一个文件打开两次之后,两个结构体各自有各自的文件指针。

dup

dup函数可以复制文件描述符,让两个文件描述符指向同一个文件结构,通过dup复制文件描述符和两次打开文件描述符不同,所以两个文件描述符共享一个文件指针。

当一个文件描述符被关闭时,关闭的是内核的文件描述结构,但是如果文件描述结构体中,引用计数器不为1,那么close函数就只是减少了引用计数器而已。

 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h> // 0, 1, 2
// 0 标准输入
// 1 标准输出
// 2 标准错误 int main()
{
printf("this is output to terminate\n"); // 保存1号文件描述符
int fd_save1 = dup(1); // 打开了一个新文件
int fd_file = open("new_output.txt", O_CREAT|O_RDWR, 0777); // 将新文件拷贝到1的位置
dup2(fd_file, 1); // 打印调试信息到文件
printf("this is output to file\n"); // 将保存的1号文件恢复到1号位置上
dup2(fd_save1, 1); // 此时再输出,则又输出到终端
printf("output to terminate again\n"); return 0;
}

文件原子操作

原子操作是指一个操作一旦启动,则无法被能破坏它的其它操作打断。

  • 写文件
    无论是两次打开还是dup,同时操作一个文件都可能引起混乱,解决这个问题的方法是,可以通过O_APPEND解决这个问题。O_APPEND选项可以使得当一个写操作正在进行时,另外一个对该文件的写操作会阻塞等待。这意味着有O_APPEND选项的文件描述符,写操作无法被打断。

应用场景,多进程写Log文件

  • 创建文件
    除了写操作有原子性问题,创建文件也有,如果两个进程同时调用creat或者带O_CREAT的open,创建同一个文件时,可能会出现这种情况,第一个操作创建成功之后,写入数据,而第二个操作的O_CREAT把数据抹去了。
    但是如果在O_CREAT之后,加上O_EXCL,那么可以避免这种情况。

fcntl和ioctl

fcntl可以用来设置文件描述符属性、文件状态属性、文件描述符复制、设置文件锁、设置文件通知等功能,这里只表示学习通过fcntl修改文件描述符属性。

如果一个文件描述符没有O_APPEND属性,但是后来又需要这个属性,那么可以通过fcntl来设置。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h> // uint32_t status; // 位操作 int main()
{
int fd = open("t11.txt", O_CREAT|O_RDWR, 0777);
if(fd < 0)
{
perror("open");
return -1;
} // 通过fcntl修改文件属性,增加O_APPEND属性
int flags = fcntl(fd, F_GETFL);
flags |= O_APPEND;
fcntl(fd, F_SETFL, flags); lseek(fd, 0, SEEK_SET);
write(fd, "hello", 5); lseek(fd, 0, SEEK_SET);
write(fd, "world", 5); close(fd);
return 0;
}

ioctl是一个杂项函数,一般用于文件底层属性设置。

文件映射

文件映射能将硬盘映射到进程的地址,这样可以像操作内存一样操作文件,而且效率很高,但是有一定限制:

  • 文件长度必须大于等于映射长度

  • 映射的offset必须是页的整数倍

页的尺寸获取方式:
命令行getconf -a | grep PAGE_SIZE
函数sysconf(_SC_PAGE_SIZE)

//运用mmap实现文件的映射
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h> int main()
{
int fd = open("a.map", O_RDWR);
if(fd < 0)
{
perror("open");
return -1;
} void* ptr = mmap(NULL, /* 指定映射的地址,如果为空,那么内核自动选择一个地址 */
4096, /* 映射长度 */
PROT_READ|PROT_WRITE, /* 访问方式,要和打开文件使的flag一致 */
MAP_SHARED, /* 修改映射地址的数据,反应到硬盘,如果是MAP_PRIVITED,那么修改数据,不会刷新到硬盘 */
fd, /* 文件描述符 */
0 /*从什么地方开始映射*/); if(ptr == MAP_FAILED)
{
perror("mmap");
return -1;
} // 像访问内存一样的访问硬盘,虚拟内存
// 通过这种方式访问大文件效率更高
// 进程之间共享数据
strcpy((char*)ptr, "hello"); munmap(ptr, 4096);
close(fd); return 0;
}

临时文件

可以通过mktemp(3)来获取一个临时文件路径,但是该文件不一定在/tmp目录下,在哪个目录下需要程序员指定。

Linux还有更多的创建临时文件的函数,学有余力的同学可以通过man 3 mktemp,查看相关函数。

#include <stdio.h>
#include <stdlib.h>
int main()
{
char buf[] = "./hello-XXXXXX"; char* p = mktemp(buf);
printf("p=%s\n", p);
printf("buf=%s\n", buf);
}

缓存

为了提高IO效率,系统为应用程序提供了缓存服务。当应用程序写数据到硬盘时,内核只是将数据写入内核缓存,然后返回成功。

缓存的存在隐藏风险,如果缓存数据未写入硬盘时,发生断电故障,那么会导致数据的不完整性。

关键数据的不完整,可能会导致系统崩溃。

使用O_SYNC选项打开文件时,那么写入操作将保证数据写入到硬盘再返回,当然这个选项导致IO效率降低。

也可以使用syncfsyncfsyncdata之类的函数,将数据写入硬盘。

fwrite和write都有缓存,不过fwrite在用户空间和内核空间都有缓存,而write只有在内核空间有缓存。

补充

标准输入/输出/错误

每一个进程都默认打开三个文件,三个文件描述符分别是0,1,2。printf其实是调用write(1)实现的。

一般不直接使用0,1,2来表示三个文件,而是用宏STDIN_FILENO,STDOUT_FILENO, STDERR_FILENO来表示输入、输出、错误。

open返回值

返回可用的最小的文件描述符。

小于0,表示对文件操作失败

文件描述符

进程范围内唯一。

dup2

也是用来赋值文件描述符,第二个参数指示复制的位置。

dup2(int oldfd, int newfd);

使用的命令和函数总结

函数

open/creat:打开文件/创建文件
read:读文件
write:写文件
close:关闭文件
lseek:定位文件读写位置
fcntl:修改文件属性
sysconf:读取系统配置
dup/dup2:复制文件描述符
sync/fsync/fsyncdata:同步文件数据
mmap/munmap:文件映射
mkstemp:得到临时文件路径

命令

touch:修改文件的访问时间,创建文件
cat:访问文件内容
vim:编辑
ulimit:显示一些限制信息(文件描述符最大值、栈的空间尺寸)
umask:文件创建的权限掩码
getconf:对应sysconf

Linu基础 文件IO(读写操作)的更多相关文章

  1. C# System.IO和对文件的读写操作

      System.IO命名空间中常用的非抽象类 BinaryReader 从二进制流中读取原始数据 BinaryWriter 从二进制格式中写入原始数据 BufferedStream 字节流的临时存储 ...

  2. 『无为则无心』Python基础 — 41、Python中文件的读写操作(一)

    目录 1.文件操作步骤 2.文件的读写操作 (1)文件的打开 (2)打开文件模式 (3)获取一个文件对象 (4)关于文件路径 1.文件操作步骤 当我们要读取或者写入文件时,我们需要打开文件,在操作完毕 ...

  3. (代码篇)从基础文件IO说起虚拟内存,内存文件映射,零拷贝

    上一篇讲解了基础文件IO的理论发展,这里结合java看看各项理论的具体实现. 传统IO-intsmaze 传统文件IO操作的基础代码如下: FileInputStream in = new FileI ...

  4. android报错及解决2--Sdcard进行文件的读写操作报的异常

    报错描述: 对Sdcard进行文件的读写操作的时候,报java.io.FileNotFoundException: /sdcard/testsd.txt (Permission denied),在往S ...

  5. Hadoop基础-通过IO流操作HDFS

    Hadoop基础-通过IO流操作HDFS 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.上传文件 /* @author :yinzhengjie Blog:http://www ...

  6. C# 运用StreamReader类和StreamWriter类实现文件的读写操作

    对文件的读写操作应该是最重要的文件操作,System.IO命名空间为我们提供了诸多文件读写操作类,在这里我要向大家介绍最常用也是最基本的StreamReader类和StreamWriter类.从这两个 ...

  7. 使用字符流(Writer、Reader)完成对文件的读写操作

    字符流 字符输出流:Writer,对文件的操作使用子类FileWriter 字符输入流:Reader,对文件的操作使用子类FileReader 每次操作的是一个字符 文件字符操作流会自带缓存,默认大小 ...

  8. Java 对不同类型的数据文件的读写操作整合器[JSON,XML,CSV]-[经过设计模式改造](2020年寒假小目标03)

    日期:2020.01.16 博客期:125 星期四 我想说想要构造这样一个通用文件读写器确实不容易,嗯~以后会添加更多的文件类型,先来熟悉一下文件内容样式: <?xml version=&quo ...

  9. INI 文件的读写操作

    在C#中对INI文件进行读写操作,在此要引入using System.Runtime.InteropServices; 命名空间,具体方法如下: #region 变量 private static r ...

随机推荐

  1. 为什么同行业,同个软件,有些 ERP 成功,有的失败了?

    企业的差异性是各类系统部署必须正视的关键问题!同行业,同个软件,有些 ERP 成功,有的失败,基本上是企业差异性没有得到重视的,所以一点也不应该感到奇怪.规模不同.行业不同.发展阶段不同.生产模式不同 ...

  2. aardio + PowerShell 可视化快速开发独立 EXE 桌面程序

    aardio 可以方便地调用 PowerShell ,PowerShell 中也可以自由调用 aardio 对象与函数.不用带上体积很大的System.Management.Automation.dl ...

  3. 「Tubian」Tubian0.41!支持Windows QQ微信!

    Tubian 0.42已发布:https://www.cnblogs.com/tubentubentu/p/16745926.html Sourceforge.net下载:https://source ...

  4. 220726 T3 最优化问题 (树状数组)

    题目描述 在同学们的努力下, 高匀感受到了 alb 的快乐. 高勺意犹未尽,找来了一个长度为 nn 的序列 a_1,a_2,-.,a_na1​,a2​,-.,an​ . 她想要删除这个序列中的 kk  ...

  5. Windows常用快捷键及基本的Dos命令

    Windows 常用快捷键 Ctrl + C: 复制 Ctrl + V: 粘贴 Ctrl + A: 全选 Ctrl + X: 剪贴 Ctrl + Z: 撤销 Ctrl + S: 保存 Alt + F4 ...

  6. instanceof的使用和向下转型

    x instanceof A:检验x是否为类A的对象,返回值为boolean型 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instance ...

  7. 刷完一千道java笔试题的常见题目分析

    java基础刷题遇到的最常见问题 可以先看一下这位博主整理的java面试题(很详细,我看了好几遍了):https://blog.csdn.net/ThinkWon/article/details/10 ...

  8. 齐博x1细节优化,自定义二、三、四维字段支持自定描述

    如下图所示,之前自定义字估中的二.三.四维字段,不支持自定义描述,导致用户输入的时候,不知道该输入什么信息内容.只有站长自己才知道. 现在支持自定义描述,及设置文本或数字.方便引导用户输入相应的信息内 ...

  9. python不确定性计算之模糊动态聚类实验

    模糊动态聚类实验 本实验所采用的模糊聚类分析方法是基于模糊关系上的模糊聚类法,也称为系统聚类分析法,可分为三步: 第一步:数据标准化,建立模糊矩阵 第二步:建立模糊相似矩阵 第三步:聚类 本程序读取E ...

  10. JS中数值类型的本质

    一.JS中的数值类型 众所JS爱好友周知,JS中只有一个总的数值类型--number,它包含了整型.浮点型等数值类型.其中,浮点数的实现思想有点复杂,它把一个数拆成两部分来存储.第一部分是有效位数,也 ...