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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
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、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);
    }
    

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 <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <string.h>
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;
}

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‘
    
七、多次打开同一个文件与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);
    

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);
十一、标准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 <stdio.h>
#include <stdlib.h>
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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值