笔记003——文件IO

1 文件IO简介

① 文件IO就是posix(可移植操作系统接口)定义的一组函数,不同系统的文件IO不同。

② 文件IO无缓冲,单次读写更快,但多次读写时会频繁调用外设,降低速度。

③ 文件IO的核心概念是文件描述符fd(file description)。

④ 文件IO只有二进制文件读写。

⑤ 标准IO依赖于文件IO。

2 文件描述符

① 文件IO依赖于文件描述符来操作文件,标准IO依赖于显示的文件结构体来操作文件,结构体中也有fd。

② 文件描述符是一个整型数字,每一个进程都有一个独立的存放文件描述符的整型数组。

③ 文件描述符指向了一个隐式的结构体,该结构体包含了文件的位置信息和打开次数。

④ 不同进程打开的文件描述符不同,同一进程多次打开的文件描述符也不同。

⑤ 多次打开同一文件会记录打开次数,只有最后一个才会释放结构体资源。

⑥ 文件描述符范围为0~1023,一般0、1、2继承父进程为标准流,也可以改配置。

3 open 函数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*打开成功返回文件描述符,打开失败返回-1,设置errno*/
int open(const char *pathname, int flags); /*用来打开已有的文件*/
int open(const char *pathname, int flags, mode_t mode); /*创建新的文件*/
/*flags用来表示读写权限,mode为文件权限,664,为0666&~*umask/
/*可以打开设备文件,但是不能创建设备文件,设备文件的创建使用mknode*/


/*
	备注:文件创建使用3参数,多参的实现是可变参数,传入多个参数也不会报错
*/

4 读写权限的组合

  • 文件创建符和文件状态符。

  • 其中O_RDONLY、O_WRONLY、O_RDWR必须有一个。

flags描述
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_CREAT不存在,创建
O_EXCLO_CREAT时,文件存在报错
O_NOCTTY文件为终端,则终端不能作为open函数的进程的控制端
O_TRUNC文件已存在,则覆盖
O_APPEND文件已存在,追加
其他描述
O_DIRECT最小化cache影响,cache读取到缓冲区,buffer写入到缓冲区
O_DIRECTORY非目录打开失败
O_LARGEFILE大文件时使用
O_NOATIME提升性能时间,不修改系统时间
O_NOFOLLOW不打开连接文件
O_NONBLOCK非阻塞,打不开就不打开,默认阻塞,打不开就等
O_SYNC同步
读写权限组合
rO_RDONLY
r+O_RDWR
wO_WRONLY | O_CREAT| O_TRUNC,0664
w+O_RDWR | O_CREAT| O_TRUNC,0664
aO_WRONLY | O_CREAT | O_APPEND,0664
a+O_RDWR |  O_CREAT | O_APPEND,0664

5 close函数

#include <unistd.h>
/*关闭fd,关闭成功返回0,关闭失败返回-1,并设置errno*/
/*文件只能释放一次,且程序结束自动关闭所有*/
int close(int fd);
/*
常见错误
EBADF fd 不是有效的打开文件描述符。

EINTR 关闭() 调用被信号中断;参见信号(7)。

EIO 发生 I/O 错误。

ENOSPC, EDQUOT
在 NFS 上,这些错误通常不会针对超出可用存储空间的第一次写入报告,而是针对后续的写入、fsync 或 close报告。
EEXIST 文件不存在
 */

6 打开关闭模版

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv){
	int fd = 0;
	if(argc < 2){
		fprintf(stderr,"argc is not enough\n");
	}
	/*打开已存在的文件*/
	fd = open(argv[1],O_RDONLY);
	if(fd < 0){
		if(errno == EEXIST){
			perror("文件不存在");
		}else{
			perror("其他错误");
		}
		exit(1);
	}
	puts("open is success");
	
	/*关闭文件描述符*/
	if(close(fd) != 0){
		fprintf(stderr,"close is wrong: %s\n",strerror(errno));
	}
	puts("close is success");
	return 0;
}

7 write函数

#include <unistd.h>
/*
	从buf中写入count个字节到fd指向的流,count不超过buf的大小
	写入成功返回写入的字节数,写入失败返回-1,如果返回值小于count可能是磁盘不足或者中断导致的问题
*/
ssize_t write(int fd, const void *buf, size_t count);

8 read函数

#include <unistd.h>
/*
	从fd指向的文件中读取count个字节存到缓冲区中,缓冲区起始地址为buf
	读取成功返回读取到的字节数,0表示读到了末尾,-1表示发生了错误并设置errno
	接近文件末尾,或者从管道或终端读取或者中断等都可能导致读取到的小于count
*/
ssize_t read(int fd, void *buf, size_t count);

9 lseek函数

#include <sys/types.h>
#include <unistd.h>
/*成功时返回从起始位置到当前的偏移相当于位置,失败返回-1并设置errno*/
off_t lseek(int fd, off_t offset, int whence);
/*第10位,则距离SEEK_SET偏移9位*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void){
	int fd = open("/home/test_src",O_RDONLY);
	char buf[1024];
	
	/*读取5个字节*/
	read(fd,buf,5);
	write(1,buf,5);
	write(1,"\n",1);  /*打印到标准输出流*/
	
	/*重新定位*/
	lseek(fd,-4,SEEK_CUR);
	
	/*再读取一个字节*/
	read(fd,buf,1);
	write(1,buf,1);
	write(1,"\n",1);
	
	return 0;
}

10 代码实现

/* 注意:当返回值小于count可能是因为出现了一些阻塞打断了IO*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char **argv){
	int fd_src = 0;	/*读取文件的描述符*/
	int fd_dest = 0; /*写入文件的描述符*/
	int len = 0;	/*读到的字节长度*/
	int ret = 0;  /*返回写入的字节*/
	int pos = 0;  /*当前写入的位置*/
	char buf[1024]; /*缓冲区*/
	if(argc < 3){
		fprintf(stderr,"argc is not enough\n");
	}
	/*打开已存在的文件*/
	fd_src = open(argv[1],O_RDONLY);
	if(fd_src < 0){
		if(errno == EEXIST){
			perror("文件不存在");
		}else{
			perror("其他错误");
		}
		goto END;
	}
	fd_dest = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0664);
	if(fd_dest < 0){
		close(fd_src);
		if(errno == EEXIST){
			perror("文件不存在");
		}else{
			perror("其他错误");
		}
		goto END;
	}
	
	/*代码实现:将argv[1]的内容复制到argv[2]中*/
	while(1){
		len = read(fd_src,buf,1024);
		if(len < 0){
			perror("read is wrong!");
			goto CLOSE;
		}else if(len == 0){
			perror("Read To EOF");
			goto CLOSE;
		}
		pos = 0;
		while(len > 0){
			ret = write(fd_dest,buf+ pos,len);
			if(ret < 0){
				perror("write is wrong!");
				goto CLOSE;
			}
			/*写入可能被阻塞,应当从当前地址继续写*/
			pos += ret; /*pos修改到读取到的位置*/
			len -= pos; /*剩余的读取字节减少*/
			/*正常写入就不管*/
		}
	}
CLOSE:
	/*关闭文件描述符*/
	if(close(fd_dest) != 0){
		fprintf(stderr,"close is wrong: %s\n",strerror(errno));
	}
	if(close(fd_src) != 0){
		fprintf(stderr,"close is wrong: %s\n",strerror(errno));
	}
END:
	return 0;
}

11 文件IO与标准IO比较

  • 文件IO与标准IO的区别:

​ 文件IO没有buf和cache,响应速度更快。

​ 标准IO有buf和cache、fflush,吞吐量更大,

  • 如何提高文件的读写速度?

​ 如果是文件的吞吐量大(用户体验号),则使用标准IO;如果要求响应快,则用文件IO。

  • 注意:标准IO与文件IO不能混用
/*
	文件IO没有缓冲区,fd中的文件位置就是实际的文件位置。
	标准IO有缓冲区,FILE中的文件位置只是缓冲区位置,可能文件位置并没变。
*/

/*
	FILE*转化为fd:
	#include <stdio.h>
	int fileno(FILE *stream);
*/

/*
	fd转化为FILE*:
	#include <stdio.h>
	FILE *fdopen(int fd, const char *mode);
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

	/*
	打印结果:abc123
	文件IO每次都直接输出到标准流
	标准IO等到刷新或者缓冲区满足条件才一次性打印到标准流
	*/

int main(void){
	fputc('1',stdout);
	write(1,"a",1);
	fputc('2',stdout);
	write(1,"b",1);
	fputc('3',stdout);
	write(1,"c",1);
	fputc('\n',stdout);
	return 0;
}

12 IO效率问题

文件IO打开一个文件,bufferzise从2两倍递增,比较速度的拐点。

time ./程序 测试程序的运行时间

real:用户在意的真实时间,usr + sys + 一些其他的。

usr:应用层面的时间。

sys:系统层面的时间,与操作系统有关,系统调用的时间。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

/*
	bufsize从2字节两倍递增到8G
	曲线:
                2~1024*4,时间递减
                1024后不再减少
                8G段错误,核心已转存
*/

#define BufSize  2
int main(int argc, char **argv){
	int fd_src = 0;	/*读取文件的描述符*/
	int fd_dest = 0; /*写入文件的描述符*/
	int len = 0;	/*读到的字节长度*/
	int ret = 0;  /*返回写入的字节*/
	int pos = 0;  /*当前写入的位置*/
	char buf[BufSize]; /*缓冲区*/
	/*打开已存在的文件*/
	fd_src = open("/snap/gnome-42-2204/111/usr/lib/x86_64-linux-gnu/dri/crocus_dri.so",O_RDONLY);
	if(fd_src < 0){
		if(errno == EEXIST){
			perror("文件不存在");
		}else{
			perror("其他错误1");
		}
		goto END;
	}
	fd_dest = open("/home/test_dest",O_WRONLY|O_CREAT|O_TRUNC,0664);
	if(fd_dest < 0){
		close(fd_src);
		if(errno == EEXIST){
			perror("文件不存在");
		}else{
			perror("其他错误2");
		}
		goto END;
	}
	
	/*代码实现:将argv[1]的内容复制到argv[2]中*/
	while(1){
		len = read(fd_src,buf,BufSize);
		if(len < 0){
			perror("read is wrong!");
			goto CLOSE;
		}else if(len == 0){
			perror("Read To EOF");
			goto CLOSE;
		}
		pos = 0;
		while(len > 0){
			ret = write(fd_dest,buf,len + pos);
			if(ret < 0){
				perror("write is wrong!");
				goto CLOSE;
			}
			/*写入可能被阻塞,应当从当前地址继续写*/
			pos += ret; /*pos修改到读取到的位置*/
			len -= pos; /*剩余的读取字节减少*/
			/*正常写入就不管*/
		}
	}
	
CLOSE:
	/*关闭文件描述符*/
	write(1,"success\n",8);
	if(close(fd_dest) != 0){
		fprintf(stderr,"close is wrong: %s\n",strerror(errno));
	}
	if(close(fd_src) != 0){
		fprintf(stderr,"close is wrong: %s\n",strerror(errno));
	}
END:
	return 0;
}

13 文件共享

/*
	问题:删除文件的第十行。
	思路1:同一进程打开一个fd_r,一个fd_r+,
	思路2:不同进程,一个进程打开fd_r,一个进程打开fd_r+,借助进程间通信实现
*/

/*********删除test文件的第11位*******/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


#define BufSize 1024

int main(void){
	
	int fd_src = 0;
	int fd_dest = 0;
	char buf[BufSize];
	int pos_src = 11;
	int pos_dest = 10;
	int len = 0;
	int ret = 0;
	int pos = 0;
	fd_src = open("/home/test",O_RDONLY);
	if(fd_src < 0){
		perror("fd_src is failed!");
		exit(1);
	}
	fd_dest = open("/home/test",O_RDWR);
	if(fd_dest < 0){
		perror("fd_dest is failed!");
		close(fd_src);
		exit(1);
	}
	
	lseek(fd_src,pos_src,SEEK_SET);  /*读取定位到第11位*/
	lseek(fd_dest,pos_dest,SEEK_SET); /*写入定位到第10位*/
	
	while(1){
		len = read(fd_src,buf,BufSize);
		if(len < 0){
			perror("READ ERROR:");
			goto END;
		}
		if(len == 0){
			write(1,"MODIFY SUCCESS\n",15);
			break;
		}
		pos = 0;
		while(len > 0){
			ret = write(fd_dest,buf,len);
			if(ret < 0){
				perror("READ ERROR: ");
				goto END;
			}
			pos += ret;
			len -= ret;
		}
	}
	
END:
	close(fd_dest);
	close(fd_src);
	return 0;
}

14 文件截断

#include <unistd.h>
#include <sys/types.h>
/*截断文件,成功返回0,否则返回-1并设置errno*/
int ftruncate(const char *path, off_t length);  /*文件必须打开且可写*/
int truncate(int fd, off_t length); /*文件不用打开*/
/*
	注意:
	如果长度比length小,则会用空字符填充,比length大则会截断
	尺寸改变时间为上次状态更改时间和上次修改时间
	用户ID和组ID也许会清除。
*/



/********截断文件*******/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(void){
	int fd = open("/home/test",O_RDWR);
	if(fd < 0){
		perror("open is failed");
		exit(1);
	}
	/*截断/home/test*/
	if(truncate("/home/test",2)<0){
		perror("truncate is failed");
		exit(1);
	}
	
	/*填充*/
	if(ftruncate(fd,20) < 0){
		perror("ftruncate is failed");
		exit(1);
	}
	write(1,"success!\n",9);
	close(fd);
	return 0;
}

15 原子操作

原子操作:
  • 不可再分割的操作,解决多线程下的竞争操作。
重定向问题:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(void){
	
	
	
/***********************题目*************************/
/*不修改后面的程序,将hello重定向到/home/test文件中*/
	puts("hello");
	return 0}
解题思路:
  • ① 关闭stdout的fd。

  • ② 打开/home/test的fd,会自动占用没有被占用的最小fd。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(void){
	int fd = 0;
	close(1);
	fd = open("/home/test",O_RDWR|O_CREAT|O_TRUNC,0x664);
	if(fd < 0){
		perror("open is failed:");
		exit(1);
	}
	
/***********************题目*************************/
/*不修改后面的程序,将hello重定向到/home/test文件中*/
	puts("hello");
	return 0;
}
存在问题:
  • ① 可能并没有标准输出,所以1就是自己。

  • ② 有可能还没有顶上去就被别人顶上去了。

dup和dup2函数:
#include <unistd.h>
/*复制描述符,成功返回新的fd,失败返回-1,并设置errno*/
int dup(int oldfd); /*将oldfd赋值给最小的可用的fd*/
int dup2(int oldfd, int newfd); /*原子操作*/
/*
dup2将oldfd拷贝到newfd,如果newfd打开会将其关闭
如果newfd不是有效文件,则调用失败
如果newfd等于oldfd,则不执行仍何操作,并返回newfd
*/
dup实现重定向
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/*同样存在上述问题*/
int main(void){
	int fd = 0;
	fd = open("/home/test",O_RDWR|O_CREAT|O_TRUNC,0x664);
	if(fd < 0){
		perror("open is failed:");
		exit(1);
	}
	close(1);
	dup(fd); /*fd的拷贝文件描述符自动填充最小位置*/
	
/***********************题目*************************/
/*不修改后面的程序,将hello重定向到/home/test文件中*/
	puts("hello");
	return 0;
}
dup2实现重定向
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(void){
	int fd = 0;
	int fd_stdout = dup(fileno(stdout));  /*复制一个stdout的fd*/
	
	fd = open("/home/test",O_RDWR|O_CREAT|O_TRUNC,0x664);
	if(fd < 0){
		perror("open is failed:");
		exit(1);
	}
	
	dup2(fd,1); /*fd的拷贝文件描述符自动填充最小位置*/
	close(fd);
	
/***********************题目*************************/
/*不修改后面的程序,将hello重定向到/home/test文件中*/
	puts("hello");
	
	/*
		补充:要有多任务并发编程的思想,自己修改了则需要改回来
		
	*/
	fflush(stdout); /*刷新缓冲区,否则还没输出就关闭了*/
	dup2(fd_stdout,1);
	write(1,"OK\n",3);
	close(fd_stdout);
	
	return 0;
}

16 同步

sync命令
将内核层面的buffer、cache缓存的写入同步到持久存储。
解除设备挂载或者关机时需要将缓存写入设备。
-d, --data
              仅同步文件数据,不同步不需要的元数据
-f, --文件系统
              同步包含文件的文件系统
fsync和fdatasync函数
#include <unistd.h>
/*同步数据到设备或文件,成功返回0,失败返回-1并设置errno*/
int fsync(int fd); /*将fd中所有的文件数据以及缓冲区全部刷到外设中,但目录可能不一定导入,需要对目录也fsync*/
int fdatasync(int fd);  /*只刷数据,不刷元数据(时间、文件属性等信息),目的是减少空间的占用*/
fcntl函数
#include <unistd.h>
#include <fcntl.h>
/*
文件描述符的管理工具,文件描述符的行为都来自该函数
该函数通过可变参数实现
cmd指令不同后面的参数以及前面的返回值都不同
dup和dup2都是该函数实现
*/
int fcntl(int fd, int cmd, ... /* arg */ );
ioctl函数
#include <sys/ioctl.h>
/*
设备相关内容管理函数
函数的man手册很少,但ioctl_list命令可以看到
多一个功能就多一大段定义,难
*/
int ioctl(int fd, unsigned long request, ...);
虚目录
ls -la /dev/fd/
虚目录:显示当前进程的文件描述符信息。
当前则为ls的信息。
需要文件打开该目录才能查看。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值