二.文件IO/系统调用IO

1.文件描述符实现原理

参考文章:每天进步一点点——Linux中的文件描述符与打开文件之间的关系
文件描述符(fd)是在文件IO中贯穿始终的类型,它实质是个整型数(数组下标)
文件描述符优先使用当前可用范围内最小的一个
文件IO操作:open,close,read,write,lseek
文件IO与标准IO的区别
IO的效率问题
文件共享
原子操作
程序中的重定向dup,dup2
同步:sync,fsync,fdatasync
fcntl()
ioctl()
/dev/fd目录

在这里插入图片描述

2.open和close

open

在这里插入图片描述

  1. 成功open返回文件描述符,失败返回-1
  2. 参数 flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR
  3. cache加速读,buffer加速写
  4. mode参数作用
    fopen和open权限映射关系:

r => O_RD_ONLY
r+ => O_RDWR
w => O_WRONLY|O_CREAT|O_TRUNC 只读,无则创建,有则删除
w+ => O_RDWR|O_TRUNC|O_CREAT 读写,有则删除,无则创建

close

在这里插入图片描述
close() closes a file descriptor, so that it no longer refers to any file and may be reused.
close() returns zero on success. On error, -1 is returned, and errno is set appropriately.

3. read和write

read

在这里插入图片描述
返回值:On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.

write

在这里插入图片描述
返回值: On success, the number of bytes written is returned (zero indicates nothing was written). On error, -1 is returned, and errno is set appropriately.

lseek(标准IO中fseek和ftell的结合)

在这里插入图片描述
whence:
在这里插入图片描述
返回值:
Upon successful completion, lseek() returns the resulting offset location as measured in bytes from the beginning of the file. On error, the value (off_t) -1 is returned and errno is set to indicate the error.

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


#define BUFSIZE 1024
/*
	实现copy文件

 */
int main(int argc, char** argv) {
	int sfd, dfd;
	char * buf[BUFSIZE];
	int len, ret;
	int pos = 0;
	if (argc != 3) {
		fprintf(stderr, "Usage:....\n");
		exit(1);
	}
	sfd = open(argv[1],O_RDONLY);
	
	if (sfd < 0) {
		perror("open()");
		exit(1);
	}
	dfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0600);//权限600
	if (dfd < 0) {
		close(sfd);
		perror("open()");
		exit(1);
	}
	while (1) {
		len = read(sfd, buf, BUFSIZE);//真正读取的字节数
		if (len < 0) {		//返回-1代表读取出错
			perror("read()");
			break;
		}
		else if(len == 0) {//读完了
			break;
		}
		pos = 0;
		while (len > 0) {//防止真正写进去的数少于读进来的数
			ret = write(dfd, buf + pos, len);//写入失败返回-1
			if (ret < 0) {
				perror("write()");
				exit(1);
			}
			pos += ret;
			len -= ret;
		}
		
	}
	close(dfd);
	close(sfd);
	exit(0);
}



假如执行time ./main main.cpp tt.cpp 看看用时
在这里插入图片描述

sys代表系统调用层面(kernel)层面的用时,user代表user层面用时。
real = sys+user+调度等待时间

4.文件IO和标准IO的区别

举例:传达室老大爷跑邮局,一封一封跑(文件IO,系统调用),累计到20封再跑(标准IO),如果有加急(比如说6封就要去跑),相当于fflush.
区别:响应速度(文件IO)vs 吞吐量(标准IO)
面试:如何使一个程序变快?增大吞吐量(用标准IO)
转换: fileno函数: 标准IO转文件IO返回的是文件描述符
在这里插入图片描述
fdopen函数:文件IO转标准IO,传入文件描述符返回FILE结构体
在这里插入图片描述
提醒:标准IO与文件IO不可混用。FILE结构体里的指示文件位置的变量pos和文件IO中文件表的指示位置成员pos的值不一样。(标准IO的存到缓冲区里的,FILE中pos加一文件表中的pos不一定加一)

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


int main(int argc, char** argv) {
	
	putchar('a');//标准IO
	write(1, "b", 1);//文件IO  往标准输出(1)输出“b”,一个字节

	putchar('a');//标准IO
	write(1, "b", 1);//文件IO  往标准输出(1)输出“b”,一个字节

	putchar('a');//标准IO
	write(1, "b", 1);//文件IO  往标准输出(1)输出“b”,一个字节

	exit(0);
	//输出:bbbaaa
}


为何如此输出?
命令行输入strace ./main
在这里插入图片描述
可以看到标准IO是一次输出3个

5.文件共享

多个任务共同操作一个文件或者协同完成任务
面试:写程序删除一个文件的第10行
补充函数:truncate/ftruncate

6.dup,dup2和原子操作

原子操作:不可分割的最小单位
原子操作的作用:解决竞争和冲突,函数tmpnam不原子,tmpfile好

dup

自己实现dup

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

#define FNAME "/tmp/out"

int main(int argc, char** argv) {
	int fd;
	close(1);//关闭1号(stdout)
	fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);//会占用可用文件描述符里最小的,那fd是1号
	if (fd < 0) {
		perror("open()");
		exit(1);
	}

	puts("hello,world");
	exit(0);

}
/*
	终端输入 cat /tmp/out
	输出 : hello,world

*/

在这里插入图片描述
描述:The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor
返回值:On success, these system calls return the new file descriptor. On error, -1 is returned, and errno is set appropriately.

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

#define FNAME "/tmp/out"

int main(int argc, char** argv) {
	int fd;
	
	fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);//会占用可用文件描述符里最小的,那fd是1号
	if (fd < 0) {
		perror("open()");
		exit(1);
	}
	printf("%d\n", fd);  // 3, 在终端stdout输出
	close(1);//关闭1号(stdout)
	int ret = dup(fd); 
	printf("%d\n", ret);//1, 此时输出在/tmp/out
	close(fd);//1号和3号同时关联/tmp/out,关掉一个也没事(引用计数)

	puts("hello,world");//输出在/tmp/out
	exit(0);

}

以上程序存在的问题:

  1. 万进程没有默认1号文件描述符,(假设只有0,2),那fd一创建被顶到1号去了(根据创建原则,可用描述符最小值),后面close(1)直接把文件相关结构体析构了
  2. 假设是并发,close(1)后正好兄弟线程创建了个文件,直接顶到1号上去了,那本线程的dup函数创建的新fd就不是1了,意味着可能输出到其他文件上去了。原因就在于close(1);和dup(fd);不原子,应该close(1)后立马执行dup, 不给其他线程机会。引出dup2

dup2

dup2其实就是close+dup的原子操作版本
If the file descriptor newfd was previously open, it is silently closed before being reused.会先去关闭newfd再执行dup。但是如果oldfd==newfd它就什么也不会做(直接关闭会导致文件直接没了)

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

#define FNAME "/tmp/out"

int main(int argc, char** argv) {
	int fd;
	
	fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0600);//会占用可用文件描述符里最小的,那fd是1号
	if (fd < 0) {
		perror("open()");
		exit(1);
	}
	printf("%d\n", fd);  // 3, 在终端stdout输出
	
	int ret = dup2(fd, 1); //先去关闭1号再去dup
	printf("%d\n", ret);//1, 此时输出在/tmp/out
	if(fd != 1)//防止原来的fd本来就等于1的情况(oldfd == newfd),直接close会析构相关结构体
		close(fd);

	puts("hello,world");//输出在/tmp/out
	exit(0);

}

7.fcntl和ioctl

fcntl:文件描述符所变得魔术几乎都来源于此函数

fcntl的详细使用

ioctl:设备相关的内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值