标准IO依赖于系统IO进行实现;
文件描述符的概念
文件IO操作:open,close,read,write,lseek
将文件IO与标准IO的区别
IO的效率问题
文件共享
原子操作
程序中的重定向实现:dup, dup2
同步: sync, fsync, fdatasync
fcntl();
ioctl();
/dev/fd/目录
1.文件描述符的概念
整型数,为指针在数组中的下标,open()返回的文件描述符会优先选择当前可用范围内最小的使用,存在一个进程中;
每个进程都存在一个文件描述符数组;
解释:使用系统调用函数open()打开文件(inode),会内部产生一个类似FILE*的结构体,包含文件位置指针,打开次数计数器等信息。产生的结构体会对用户屏蔽,结构体首地址指针会保存到一个数组当中。交给用户的为当前存放指针的数组下标,即为文件描述符。默认0, 1, 2描述符分别对应stdin,stdout,stderr。数组大小查询:ulimiti -a。默认大小为1024。
特殊情况:
1.两个进程打开同一个文件,会产生两个结构体;
2.同一个文件在一个进程中打开两次,执行两次open函数,产生两个结构体,两个文件描述符,关联同一个文件。如果没有一定协议,对文件内容操作会产生竞争;关闭一个文件描述符,不影响另一个;
3.两个文件描述符指向同一个结构体(参见dup),关闭一个文件描述符,不会关闭结构体。原因在于结构体有计数器,反映当前结构体有几个指针引用。计数器为0时,才free结构体。
**寻找过程:**文件描述符->数组下标->指针->结构体->操作文件的信息
程序设计角度解释buf和cache: buf理解为写的缓冲区,cache理解为读的缓冲区;
**阻塞IO和非阻塞IO实现上的区别:**函数为同一套,在于有没有加参数O_NONBLOCK。
打印机打印东西,如果有文件过来,则打印;没有文件过来,则一直等待,阻塞IO; 有文件过来,则打印;没有文件过来,忙其他事情,非阻塞IO;
2.文件IO操作:open,close,read,write,lseek
r -> O_RDONLY
r+ -> O_RDWR
w -> O_WRONLY|O_CREAT|O_TRUNC(方式只写,文件不存在则创建,存在则截断)
w+ -> O_RWDR|O_TRUNC|O_CREAT(方式读写,有则清空,无则创建)
open函数:
open函数有两种形式,是通过可变参数实现的。
如何判别函数是重载函数还是可变参数函数?
多输入几个参数,看错误提示;可变参数一般警告,重载为报错;
gcc -wall
IO/sys/mycpy.c
while(1)
{
ret = read(sfd, buf, BUFFSIZE);
printf("%d\n", ret);
if(ret < 0)
{
perror("read src_file");
break;
}
if(ret == 0)
{
break;
}
//wet = write(dfd, buf, BUFFSIZE);
//检测如果没有一次写完,就继续写,直至把这次读到的数据全部写完
//坚持写够字节.原因考虑信号的影响,信号会打断阻塞的系统调用,很可能在写不够
//len字节的情况下就被打断
pos = 0;
while(ret > 0)
{
wet = write(dfd, buf+pos, ret);
if(wet < 0)
{
perror("write des_file");
//break;
exit(1);
}
pos += wet;
ret -= wet;
}
}
3.将文件IO与标准IO的区别
系统调用IO :每调用一次,就实打实的从用户态到内核态执行一次,实时性高;
标准IO:操作只有在内存满/换行/fflush才会真正执行一次,合并系统调用
区别:响应速度:系统调用IO > 标准IO
吞吐量:系统调用IO < 标准IO
面试:如何使一个程序变快?
一分为二看问题,响应速度快,多用系统调用IO;吞吐量大,多用标准IO;
从用户体验看,变快一般指吞吐量变大;
提醒:系统调用IO和标准IO不可混用;
原因:标准IO对文件的改变并没有真正改变文件的内容,而是写到了缓冲区中;只有真正刷新的时候
才会真正改变文件内容。故标准IO对文件的操作不能与系统调用IO一一对应起来;
参见IO/sys/ab.c
IO/sys/ab.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
putchar('a');
write(1, "b", 1);
putchar('a');
write(1, "b", 1);
putchar('a');
write(1, "b", 1);
exit(0);
}
输出结果为:bbbaaa
strace ./ab : 查看可执行文件ab的系统调用如何发生:
文件描述符与文件类型指针转换函数:
int fileno(FILE* stream); //从文件指针转换到对应的文件描述符
FILE* fdopen(int fd, const char* mode);//把一个已经打开的文件描述符转换成文件指针
4.IO的效率问题
习题:将IO/sys/mcpy.c程序进行更改,将BUFFSIZE的值成倍放大
1.观察进程所消耗的时间;
2.注意性能最佳拐点出现时的BUFFSIZE值;
3.何时程序会出问题;
//计算程序执行的时间
time ./mycpy <src_file> <des_file>
5.文件共享
多个任务共同操作一个文件或者协同完成任务
面试:写程序删除一个文件的第10行
思路1:
//定位到11行行首,读取内容。定位到10行行首,进行覆盖写;
//4次系统调用
//最后需要把文件截短
while()
{
lseek11 + read + lseek10 + write
}
思路2:
当前进程空间把文件打开两次
1 -> open r ->fd1 -> lseek 11
2 -> open r+ ->fd2 -> lseek 10 //采用r+ 是为了检查文件是否存在
while()
{
1->fd1-> read
2->fd2-> write
}
思路3
process1 -> open -> r
process -> open -> r+
process1 -> read -> process2 -> write
思路2和3都是为了减少系统调用,打开文件两次;
补充函数:
//未打开的文件截断到多长
int truncate(const char* path, off_t length);
//已打开的文件截断到多长
int ftruncate(int fd, off_t length);
6.原子操作
原子操作:不可分割的操作
原子操作的作用:解决竞争和冲突
如解决tmpnam()的非原子操作问题
7.程序中的重定向实现:dup, dup2
#include <unistd.h>
//拷贝一份oldfd的副本,放到当前可用范围内最小位置上去
//和oldfd指向同一个文件结构
int dup(int oldfd);
//newfd作为oldfd的副本,如果newfd被占用,则会先释放newfd
//如果newfd和oldfd是同样值的文件描述符,则不做任何操作
int dup2(int oldfd, int newfd);
IO/sys/dup.c
//将puts("hello!")从标准输出重定向输出到tmp/out中
int flag;
//思路1
/*
flag = close(1);
if(flag < 0)
{
perror("close()");
exit(1);
}
int fd;
fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
if(fd < 0)
{
perror("open");
exit(1);
}
*/
//思路2
//缺陷:
//1.当前进程文件描述符默认只有0,没有1,则open后,fd就为1,在close(1)出会出现问题;
//2.有另一个任务在跑,close(1)语句后,没有执行dup之前,另一个任务打开一个新的文件,
//则描述符1被分配走
//产生缺陷原因:close(1) + dup(fd) 操作非原子
/*
int fd;
fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
if(fd < 0)
{
perror("open");
exit(1);
}
close(1);
dup(fd);
close(fd);
*/
//思路3
//还是有写缺陷,不应该从main的角度来写
//应该从模块的角度来写,更改完文件描述符1后,需要在puts操作结束后,复原现场
//1.不要有内存泄露 2.不要产生越界 3.永远当作在写小模块,不是在写main函数
int fd;
fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600);
if(fd < 0)
{
perror("open");
exit(1);
}
dup2(fd, 1);
if(fd != 1)
close(fd);
//重新打开dev的标准输出
/***********************************************/
puts("hello!");