• linux中的文件IO==Linux应用编程1


    一、文件操作的主要API

    1、什么是一些由Linux系统提供支持的哈桑农户,由应用程序来使用

    • (1)API 是一些由 Linux 系统提供支持的函数,由应用程序来使用
    • (2)应用程序通过调用 API 来调用操作系统中的各种功能。
    • (3)学习一个操作系统,就是学习使用这个操作系统的 API。

    2、Linux 常用的文件 API

    • (1)open、close、write、read、lseek。

    3、文件操作的一般步骤

    • (1)open 打开一个文件,得到一个文件描述符,然后对文件进行读写操作(或其他操作), 最后 close 关闭文件即可。
    • (2)文件平时是存放在文件系统中的块设备的,我们把这种文件叫静态文件。当我们去 open 打开一个文件时,Linux 的内核操作包括:在进程中建立一个打开文件的数据结构,记录下打开的这个文件;然后申请一段内存,将静态文件的内容从块设备读取到内存中的 特定地址管理存放,称为动态文件。
    • (3)文件打开后,针对这份文件的读写操作,都是针对这份动态文件的。当我们 close 关 闭动态文件时,内核就将内存中的动态文件更新到块设备中的静态文件。
    • (4)常见现象:打开一个大文件时比较慢;写了一半的文件如果没保存直接关机,重启后 文件内容丢失。
    • (5)为什么要这么设计?因为块设备本身有读写限制,对块设备的操作非常不灵活。而内存可以按字节为单位操作,而且可以随机操作,很灵活。

    4、重要概念:文件描述符

    • 文件描述符实际是一个数字,在进程中表示一个特定的含义,当我们 open 打开一个 文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应 用程序一个数字作为文件描述符,即该进程中该文件的标识。
    • (2)注意文件描述符的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了。

    二、一个简单的文件读写示例

    1、文件打开与文件关闭

    • (1)Linux 中的文件描述符 fd 的合法范围是 0 或者一个正整数,不可能是一个负数。
    • (2)open 返回的 fd 必须记录好,对文件的所有操作都离不开 fd。

    2、实时查询 man 手册

    • (1)man 1 xxx 查询 Linux shell 命令,man 2 xxx 查询 API,man 3 xxx 查询库函数。

    3、读取文件内容

    • (1)ssize_t read(int fd, void *buf, size_t count); ssize_t 是 Linux 内核用 typedef 重定义的一 个类型,其实就是 int,返回值表示成功读取的字节数;fd 表示文件描述符;buf 是应 用程序自己提供的一段缓冲区,用来存储读出的内容;count 是要读取的字节数

    4、向文件中写入内容

    • (1)ssize_t write(int fd, const void *buf, size_t count);

    5、示例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(int agc,char* argv[])
    {
    	int fd=-1;
    	char buf[20]={0},writebuf[5]="Linux";
    	fd=open("a.txt",O_WRDR);
    	if(-1=fd)
    		printf("file opened failed\n");
    	else
    		printf("file opened successful,fd=%d.\n",fd);
    	
    	/*ret=read(fd,buf,5);
    	if(ret<0)
    		printf("file read failed\n");
    	else{
    		printf("read %d byte(s).\n", ret);
    		printf("the content is:[%s]\n",buf);
    	}*/
    	
    	ret=write(fd,writebuf,sizeof(writebuf));
    	if(ret<0)
    		printf("write failed!\n");
    	else
    		printf("write %d byte(s).\n", ret);
    	close(fd);return 0;	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    四、文件读写的一些细节

    1、errno 和 perror

    • (1)errno 就是 error number,即错误号码
    • Linux 对各种常见的错误做了个编号,当函数 执行出现错误时,函数会返回一个特定的 errno 来告诉系统这个函数到底哪里错了。
    • (3)errno 实质是一个 int 型的数字,每个数字对应一种错误。
    • (4)perror 就是 print error,即打印错误,perror 内部会读取 error 并将这个数字直接转成 相应的错误信息字符串,然后打印出来。
      fd=open("a.txt",O_WDWR|O_CREAT|O_EXCL);
      if(-1==fd){
      	perror(file opened failed!\n);
      	_Exit(-1);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    2、read和write的count

    • (1)count 表示我们想要读或写的字节数,返回值表示实际完成的字节数。
    • (2)count 再和阻塞、非阻塞结合起来,就会更加复杂。如果一个函数是阻塞式的,我们想要读取 30 个字节,而实际只完成了 20 个字节,就会导致阻塞。
    • 我们不可能把 count 设置为 210241024,而是把 count 设置为一个合适的数字(比如 说 2048),然后通过多次读出或写入来完成任务。缓冲的方式

    3、文件 IO 和标准缓冲 IO 的效率

    • (1)文件 IO 就是指 open、read、write、close 等 API 函数构成的一套用来读写文件的体系, 它能完成文件读写,但效率并不高。
    • (2)应用层 C 语言库函数提供了一套用来读写文件的函数列表,叫标准 IO。标准 IO 由一 系列的 C 库函数(fopen、fread、fwrite、fclose)构成,这些标准 IO 其实是由文件 IO 封装而来的,其实就是在应用层加了一个缓冲机制,这样我们通过 fwrite 写入的内容 不是直接进入内核中的 buf,而是进入了应用层标准 IO 库自己维护的一个 buf 中,然后标准 IO 库根据操作系统单次 write 的最佳 count 来选择合适的时候将内容写入内核 的 buf 中。

    五、Linux 如何管理文件

    1、硬盘中的静态文件和 inode(i 节点)

    • 硬盘(块设备)-》block-》扇区-》字节
    • 一块硬盘可以分为两个区域:硬盘内容管理表与真正存储内容的区域。操作系统访问硬盘时先去读取硬盘的管理表,从中找到对应文件的扇区级别信息,然后再通过这个信息去查询真正存储内容的区域。
    • 管理表中每一个文件对应一个inode,每个inode有个数字编号,对应一个结构体,结构体中记录该文件各种信息。
    • 格式化U盘时,一般有两种方法,一种是删除内容管理表,一种删除所有内容。

    2、内存中的动态文件和 vnode(v 节点)

    • (1)一个程序的运行就是一个进程,进程中打开的文件就属于该进程。每个进程都有一个 进程信息表记录了这个进程的所有信息,进程信息表中有一个指针会指向一个文件管理表,文件管理表记录了当前进程打开的所有文件的信息,通过文件信息中的文件描述符 fd 就 可以找到特定文件的 vnode。
    • (2)一个vnode记录了一个被打开的文件的各种信息。

    3、文件与流的概念

    • (1)文件操作中,一个文件中很多个字符的数据被挨个读出或写入时,就构成了一个字符流。
    • (2)流的概念是动态的。
    • (3)编程中提到流这个概念,一般都是 IO 相关的,所以经常叫 IO 流,文件操作时就构成 了一个 IO 流。

    六、lseek 详解

    1、lseek 函数介绍

    • (1)文件指针:动态文件在内存中是文件流的形式,文件流很长,有很多个字节,文件指针指出了当前正在操作的位置,就如 GUI 下的光标一样。
    • (2)lseek 函数:文件指针是 vnode 的一个元素,不能被直接访问,需要用 lseek 函数来访问这个指针。
    • (3)当我们打开一个文件时,文件指针默认指向文件流的开始,我们也可以通过 lseek 函 数来移动文件指针的位置。
      lseek(fd,10.SEEK_SET);
    • (4)如果先对一个文件写入若干字节的内容后立即去读取的话,是读不到刚写入的内容的, 因为此时文件指针已经被 write 移动到了该内容后面。因为读写读写会移动文件指针的位置。

    2、lseek计算文件长度

    • (1)Linux 中并没有一个函数可以直接返回一个文件长度,但是我们可以用 lseek 写一个函数来实现。
    #include  
    #include  
    #include  
    #include  
    #include  
    #include 
    int cal_len(const char* pathname)//const *   不能改变变量值
    {
    	int fd=-1,ret=-1;
    	fd=open(pathname,O_RDONLY);
    	if(-1=fd) return -1;
    	ret=lseek(fd,0,SEEK_END);//成功的话,返回的是新的偏移量
    	return ret;
    }
    int mian(int argc,char* argv[])
    {
    	int fd=-1,ret=-1;
    	if(argc!=2)	_exit(-1);
    	printf("The file has %d byte(s)\n",cal_len(argv[1]));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3、用lseek构建空洞文件

    • (1)空洞文件就是这个文件中有一段是空的,而普通文件是不能有空的,因为我们 write 时文件指针是依次从前往后移动的。
    • (2)打开一个文件后,用 lseek 往后移动一段距离,再 write 写入,就会构成一个空洞文件。
    • (3)空洞文件对多线程操作文件是极其有用的,有时候我们需要创建一个很大的文件,如 果从头开始构建的时间很长,就可以用多个线程来同时对文件进行写入。
    • 4个人修一条路,每人修一段。其每一个的起点就用lseek来移动。
      /* 将文件读写位置移动到偏移文件头10个字节处*/ 
      ret=lseek(fd,10,SEEK_SET);
      printf("lseek,ret=%d.\n",ret);
      ret=write(fd,writebuf,strlen(writebuf));//sizeof带个结束字符‘\0‘
      
      • 1
      • 2
      • 3
      • 4

    七、多次打开同一个文件与O_APPEND

    1、重复打开同一文件读取

    • (1)一个进程中两次打开同一文件,然后分别读取,结果是:fd1 和 fd2 分别读,两个文件指针相互独立。
    • (2)文件指针是包含在动态文件的文件管理表中的,可以看出 Linux 系统的进程中不同 fd 对应的是不同的独立的文件管理表。

    2、重复打开同一文件写入

    • (1)一个进程中两次打开同一文件,然后分别写入,结果是:fd1 和 fd2 分别写入,两个文件指针相互独立。
    • (2)若希望接续写而不是分别写,在open时加入O_APPEND就可以了,O_TRUNC | O_APPEND 也可生效。

    3、O_APPEND 的实现原理和其原子操作性说明

    • (1)O_APPEND 可以让 write 函数多做一件事情,就是移动自己的文件指针的同时也移动别人的文件指针。
    • (2)O_APPEND 对文件指针的影响是原子的。
    • (3)原子操作的含义是:这个操作一旦开始就不会被打断,必须等待其操作结束其他代码才能运行。

    八、文件共享的实现方式

    在这里插入图片描述
    1、什么是文件共享

    • (1)文件共享就是同一个文件(同一个 inode,同一个 pathname)被多个独立的读写体(可 理解为文件描述符)去同时(一个打开之后尚未关闭,此时另一个去操作)操作。
    • (2)文件共享的意义:可以通过文件共享来实现多线程同时操作一个大文件,以减少文件读写时间,提升效率。空洞文件

    2、文件共享的 3 种情况

    • (1)文件共享的核心是如何实现多个文件描述符指向同一文件。
    • (2)3 种情况:
      • ①同一个进程多次使用(同时)open 打开同一个文件;
      • ②在不同的进程中 分别使用(同时)open 打开同一个文件;
      • ③使用 Linux 提供的 dup 和 dup2 两个 API 来让进程复制文件描述符。
    • (3)分析文件共享的核心在于:分别写/读还是接续写/读

    3、再论文件描述符
    dnimg.cn/b7dc2c58bd84441ba830de05c74b0876.png)

    • (1)文件描述符的本质是一个数字,这个数字是进程表中的文件描述符表(数组)的一个 表项,通过该表项可查找得到文件管理表指针,进而可以访问这个文件对应的文件管理表。
    • (2)操作系统规定:fd 从 0 依次开始分配,内核会从文件描述符表中挑选一个未经使用的 最小的 fd 返回。
    • (3)fd 中的 0、1、2 已经默认被系统占用了,分别对应三个文件 stdin、stdout、stderr。因此用户进程得到的最小 fd 是 3。
    • (4)标准输入对应的一般是键盘、标准输出一般是 LCD 显示器。
    • (5)printf 函数就是默认输出到 stdout 上,stdio 中还有一个函数叫 fprintf,这个函数可以 指定输出到哪个文件描述符上。

    九、文件描述符的复制——dup 和 dup2

    1、使用 dup 进行文件描述符复制

    • (1)dup 系统调用对文件描述符 oldfd 进行复制,会返回一个新的文件描述符。
    • (2)两个文件描述符指向同一个动态文件,构成文件共享。
    • (3)利用两个文件描述符对文件进行读/写,是接续读/写而不是分别读/写。
    • (4)练习:我们可以通过 close(1),关闭标准输出,再使用 dup 复制 oldfd 得到 1 这个文件 描述符,这样就将 oldfd 对应的文件跟标准输出通道绑定起来了,printf 输出的信息将 输出到这个文件里。
      close(1);
      fd2=dup(fd1);//由dup返回的新文件符一定是当前可用文件描述符中的最小数值。如果是dup2可以指定fd的值
      printf("File opened sucessfully! The fd1 = %d, fd2 = %d\n", fd1, fd2);
      
      • 1
      • 2
      • 3

    2、使用 dup 的缺陷

    • (1)不能指定新的文件描述符的数字,而是由系统根据规则自动分配的

    3、使用 dup2 进行文件描述符复制

    • (1)dup2 支持指定新的文件描述符的数字,使用方法为 fd2 = dup2(fd1, *),*为数字。
    • (2)利用 dup2 复制的文件描述符跟原来的文件描述符进行交叉读/写时,结果是接续读/ 写而不是分别读/写。

    4、命令行中重定位命令 > important

    • (1)Linux 中的 shell 命令执行后,打印结果都是默认进入 stdout 的(本质上是由于 ls、pwd 等都是调用 printf 进行打印的),所以我们可以在 shell 中直接看到命令执行结果。
    • (2)Linux 中的“>”命令可以将打印输出重定位到一个自定义文件中。ls > a.txt

    十、多功能fd管理大师fcntl

    1、fcntl 的原型和作用

    • (1)fcntl 是一个多功能文件管理工具箱,接收 2 个参数+1 个变参。第一个参数是所要操作的文件的 fd,第二个参数是 cmd,表示要进行哪种操作,第三个 arg 变参是用来配合 cmd 使用的。
    • (2)cmd 的形式类似于 F_XXX,需要使用的时候可查询 man 手册。

    2、fcntl 常用的 cmd

    • (1)F_DUPFD 这个 cmd 的作用是复制文件描述符,从可用的 fd 数字列表中找出一个和 arg 一样大或者比 arg 大的数字作为 oldfd 的一个复制 fd,即返回>=arg 的最小可用数字。

    3、使用 fcntl 模拟 dup2

    fd1=open("a.txt",O_RDWR);
    close(fd);
    fd2=fcntl(fd1,F_DUPFD,0);
    
    • 1
    • 2
    • 3

    十一、标准IO缓冲库介绍

    1、与文件 IO 的区别

    • (1)标准 IO 是 C 库函数,文件 IO 是 API。
    • (2)C 库函数是由 API 封装而来的,比 API 好用。
    • (3)C 库函数具有可移植性而 API 不具有可移植性。
    • (4)性能和易用性上,C 库函数要好一些,譬如 IO,文件 IO 是不带缓存的,而标准 IO 是 带缓存的,因此标准 IO 比文件 IO 性能更高。

    2、常用标准 IO 函数介绍

    • (1)fopen、fread、fwrite、fclose、fseek。

    3、简单的标准 IO 读写示例

    #include 
    #include 
    int main()
    {
    	FILE* fp=NULL;
    	char writebuf[5]="abcde",readbuf[10]={0};
    	fp=fopen("1.txt","r+");
    	if(fp==NULL)
    		exit(-1);
    	printf("File opened sucessfully!\n");
    	//fwrite(writebuf, 1, 5, fp);
    	fread(readbuf, 1, 10, fp);//缓冲区,每个数据项的字节大小,数据项个数,FILE文件指针
    	printf("readbuf is [%s]\n", readbuf);
    	fclose(fp);return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    Maven最新版本安装及配置
    Arduino开发板使用I2C SSD1306 OLED显示屏的方法
    链表单向链表跳跃链表
    第四章 串【24王道数据结构笔记】
    Python入门、环境搭建、变量、数据类型
    获取商品历史价格返回值说明
    LeetCode
    阿里巴巴API接口解析,实现按关键字搜索商品
    ElementUI之CUD+表单验证
    Solidity 小白教程:12. 事件
  • 原文地址:https://blog.csdn.net/weixin_47397155/article/details/126099729