文件I/O详谈
缓存区刷新
换行回车进一步示意
\r:回车符,把打印【输出】信息的为止定位到本行开头
\n:换行符,把输出为止移动到下一行
一般把光标移动到下一行的开头,\r\n
a)比如windows下,每行结尾 \r\n
b)类Unix,每行结尾就只有\n
c)Mac苹果系统,每行结尾只有\r
结论:统一用\n就行了
printf()函数不加\n无法及时输出的解释
printf末尾不加\n就无法及时的将信息显示到屏幕 ,这是因为 行缓存[windows上一般没有,类Unix上才有]
需要输出的数据不直接显示到终端,而是首先缓存到某个地方,当遇到行刷新表指或者该缓存已满的情况下,才会把缓存的数据显示到终端设备;
ANSIC中定义\n认为是行刷新标记,所以,printf函数没有带\n是不会自动刷新输出流,直至行缓存被填满才显示到屏幕上;
所以大家用printf的时候,注意末尾要用\n;
或者:fflush(stdout);
或者:setvbuf(stdout,NULL,_IONBF,0); //这个函数. 直接将printf缓冲区禁止, printf就直接输出了。
标准I/O函数,后边还会讲到
write()
多个进程同时去写一个文件,比如5个进程同时往日志文件中写,会不会造成日志文件混乱?
多个进程同时写一个日志文件,我们看到输出结果并不混乱,是有序的;我们的日志代码应对多进程往日志文件中写时没有问题;
wirte()执行的是逻辑操作,真正地写到磁盘是物理操作
- 多个进程写一个文件,可能会出现数据覆盖,混乱等情况
ngx_log.fd = open((const char *)plogname,O_WRONLY|O_APPEND |O_CREAT,0644);
- O_APPEND这个标记能够保证多个进程操作同一个文件时不会相互覆盖;
- 内核wirte()写入时是原子操作
- 父进程fork()子进程是亲缘关系。是会共享文件表项,
- 多进程打开文件使用了O_APPEND,打开的文件在写入内容追加方式,这样数据不会混乱(前提是有亲缘关系的父子进程)
关于write()写的安全问题,是否数据成功被写到磁盘?
- write()调用返回时,内核已经将应用程序缓冲区所提供的数据放到了内核缓冲区(速度极快),但是无法保证数据已经写出到其预定的目的地[磁盘];
- 因为write()调用速度极快,可能没有时间完成该项目的工作[实际写磁盘],所以这个wirte()调用不等价于数据在内核缓冲区和磁盘之间的数据交换
read()也是如此,加入了缓存,如果内核缓冲区里是read想要的数据,那么直接从内核缓冲区里返回,这也被称为**缓存命中 (cache hit)**。
掉电导致 write()的数据丢失破解法
当内核缓冲区的内容积累到一定程度时,内核会将内核缓冲区的数据一次性写入磁盘里面。
但是如果电脑突然断电,在内核缓冲区的数据将会丢弃掉。
方法一 直接I/O:直接访问物理磁盘:
O_DIRECT:绕过内核缓冲区。用posix_memalign
有点违背操作系统设置缓冲区的初衷,不太推荐
方法二:open文件时用O_SYNC选项
同步选项【把数据直接同步到磁盘】,只针对write函数有效,使每次write()操作等待物理I/O操作的完成;
具体说,就是将写入内核缓冲区的数据立即写入磁盘,将掉电等问题造成的损失减到最小;
缺陷:每次写磁盘数据,务必要大块大块写,一般都512-4k 4k的写;不要每次只写几个字节,否则程序性能下降太严重。
方法三:缓存同步:尽量保证缓存数据和写道磁盘上的数据一致;
sync(void):将所有修改过的块缓冲区排入写队列;然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘并没有保证;
fsync(int fd):将fd对应的文件的块缓冲区立即写入磁盘,并等待实际写磁盘操作结束返回;(推荐)
fdatasync(int fd):类似于fsync,但只影响文件的数据部分。而fsync不一样,fsync除数据外,还会同步更新文件属性;
用法:比如每次write(4k),一共调用1000次,一直到把这整个文件write完整[假设整个文件4M]。
1000次write()执行完毕后调用fsync(fd) ,仅仅调用1次fsync
[多次write,每次write建议都4k,然后调用一次fsync(),这才是用fsync()的正确用法]
标准IO库
1 | fopen,fclose |
fwrite和write有啥区别;
fwrite()是标准I/O库一般在stdio.h文件
write():系统调用;
有一句话:所有系统调用(比如write)都是原子性的
fwrite()会先放到CLib缓冲区(stdio.h库里面提供的stdio缓冲区)