换行回车探讨
- \r : 回车,定位到本行开头
- \n : 换行,光标移到下一行
- \r\n : 将光标移动到下一行开头
- windows 下,每行结尾 \r\n
- 类unix,每行结尾 \n
- Mac系统,每行结尾\r
\r\n,windows下好像改善了,使得Linux下文件即便只有\n在windows下也能正确显示
结论:统一用 \n 就可以
printf行缓冲
- printf末尾不加\n就无法及时将信息输出到屏幕,是因为行缓冲的存在【windows上无,类unix上才有】
- ANSI C中将 \n 认为是行刷新标记,printf函数直至行满或遇到\n才会自动刷新输出流。
- 要么在printf函数后加上\n,要么使用下面两种方式
fflush ( stdout ); // 刷新标准输出缓冲区
setvbuf ( stdout, nullptr, _IONBF, 0 ); // 禁止printf缓冲区【标准输出缓冲区】,使得printf直接输出
write()系统调用思考
多个进程同时调用write()往日志写数据:是否会造成数据混乱
-
多个进程同时调用write()往日志写数据是否会产生数据混乱?通过让父子进程同时在死循环内执行日志写测试进行测试。
-
多个进程同时写一个文件,可能会出现数据覆盖、混乱等情况,保证数据不混乱不覆盖需要以下几点:
- a)
ngx_log.fd = open ( ( const char * ) plogname, O_WRONLY | O_APPEND | O_CREAT, 0644 );
- O_APPEND 这个标记能保证多个进程操作同一个文件时不会相互覆盖
- b) 内核write()是系统调用,是原子操作,线程安全
- c) 父进程fork()产生的子进程,父子进程是亲缘关系,共享文件表项
- a)
-
资料:《Unix环境高级编程 第三版》 第三章:文件IO 3.10-3.12,涉及到了文件共享、原子操作以及函数dup、dup2的讲解
- 第八章:进程控制 8.3 涉及到了fork函数
write()写的安全问题:数据是否被成功写入磁盘
- write和物理磁盘间,存在着程序缓冲区和内核缓冲区,write先将数据从程序缓冲区写入内核缓冲区,然后返回,内核会在一定时间后刷新一次内核缓冲,将内核缓冲数据实际写入到物理磁盘中,即数据在内核缓冲区没有写到磁盘之前,即便write函数返回,也不表示数据一定写到磁盘了
-
为了确保内核缓冲区数据能及时写入磁盘,内核设置了写入时间上限,当到达这个时间上限后,内核会将内核缓冲区的脏数据全部写入磁盘,这些跟脏数据、写入时间有关的文件 在目录
/proc/sys/vm/dirty*
-
目前代码并没有强制写磁盘,后续会讲,所以还是存在突然断电数据没有写入磁盘的可能性
掉电导致write数据丢失破解法
O_DERICT、O_SYNC、缓存同步技术
- O_DERICT: open参数,绕过内核缓冲区,直接访问物理磁盘,但是可想而知效率肯定低
- O_SYNC: open参数,同步选项,只对write有效,是每次write操作等待物理IO完成,将掉电等问题造成的损失减到最小
不管用哪个,都要注意:每次写磁盘数据,务必要大块大块写,一般512-4k 4k写;【一个扇区是512B】,不要每次写几个字节,否则会被抽死******
前两个方法都不推荐,因为实时写入磁盘的方式都很慢,且违背 了操作系统设置 内核缓冲区的初衷
-
缓存同步:尽量保证缓存数据和写到磁盘上的数据一致
- sync(void): 将所有修改过的块缓冲区排入写队列,然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘没有保证
- fsync(int fd): 将fd对应的文件的块缓冲区立即写入磁盘,并等待实际写磁盘操作结束返回**********
- fdatasync(int fd): 类似于fsync,但只影响文件的数据部分。而fsync除数据外,还会同步更新文件属性【文件属性和文件数据是分开存放在两个文件的,所以fdatasync要比fsync快,不关心文件属性时可以使用fdatasync】
-
fsync( int fd ) 的正确使用姿势
假设整个文件4M,先执行1000次write(4k) 再执行1次fsync ( fd )
对于我们写的小的日志系统,没必要动用缓存同步技术,即便断电丢失几条日志也不是很重要
所以日志中只用write就足够了,而且随着win系统的发展,基本上write后去查看磁盘的时候已经写入了
应对掉电问题的方法,在大型数据库文件中需要特别注意,此时就需要用到缓存同步技术
标准IO库
// fopen, flose
// fread, fwrite
// fflush
// fseek
// fgetc, getc, getchar
// fputc, put, putchar
// fgets, gets
// printf, fprintf, sprintf
// scanf, fscanf, sscanf
fwrite 与 write 区别:
- fwrite 是标准IO库函数,一般在stdio.h
- write 是系统调用
fwrite等标准IO库函数又加了一层Clib缓冲,非线程安全,因此不建议使用,用的不好会导致数据写入混乱
而write等系统调用由于其原子性操作,是线程安全的,结合O_APPEND标志就可以正常写入数据
【注意是父子进程,如果是不相干进程依旧有可能乱序,具体建议看书参考学习:《Linux/Unix系统编程手册上》 第13章对也有对IO缓存比较清晰的描述 】
标准IO库函数加入的Clib缓冲如图示
标准IO库与系统调用、缓冲等如图示
IO缓冲小结
书籍总结
- 资料:《Unix环境高级编程 第三版》 第三章:文件IO 3.10-3.12,涉及到了文件共享、原子操作以及函数dup、dup2的讲解
- 第八章:进程控制 8.3 涉及到了fork函数
- 《Linux/Unix系统编程手册上》 第13章 IO缓存