由 fwrite 引发的 能力考量

1,157 阅读4分钟

最近由于 工作需要涉及 读写tf卡 。

问题: TF卡 没有 指定的文件生成,但是日志表明生成。

下面围绕这个问题进行各种尝试,让大家明白程序员其实是个不断试错的过程。

由于 当时是在 对方的系统下面 进行,所以每次烧录 都特别麻烦【嵌入式开发都懂】。但是又因为当时在方案商 要催促着写, 实在 有点 不知所措。 各种百度 与尝试, 其中经历的挫折 也许只有程序员才懂。

经历过之后,花点时间将自己知道的与不知道的进行整理输出吧

第一种解法

写之后在读一遍,我的想法是: 如果我写成功了,那读肯定是读的出来的。

FILE* f = fopen("xx","r");
char buf[1024] ={0};
fread(buf,0,1024,f);// 因为 可以保证存入的大小 小于1024字节
printf("read content:%s",buf);
fclose(f);

事实证明 : 可以正确读取出来。

第二种解法

这种方法 其实是对现象的 假想【存在于系统缓存】。认为是因为缓存的存在

echo 3 >/proc/sys/vm/drop_caches

参考: www.cnblogs.com/wangcp-2014…

这种解法 是修改系统的 配置, 将所有的缓存都禁用掉

echo 0 是不释放缓存

echo 1 是释放页缓存

ehco 2 是释放dentries和inodes缓存

echo 3 是释放 1 和 2 中说道的的所有缓存

事实证明这种方法也没有用。

第三种解法

还是认为由缓存的存在,第二步可能是因为没有获取到root权限导致的。

//Synchronize cached writes to persistent storage
sync();

立即持久化 缓存中的数据

第四种解法

sync 之后判断文件是否存在

if(access("xx",F_OK)==0){
    // 存在
}

这种 出来的结果也还是 存在。

第五种解法

使用 open 的方式进行,这种较 fwrite 是不会往任何缓存上面写的,直接落地到文件。

int filedesc = open("testfile.txt", O_WRONLY | O_APPEND|O_CREAT);
if (filedesc < 0)
{
    write(2,"error open \n",strlen("error open \n"));
    return 1;
}
close(filedesc);

上面 也是成功 执行

到这里其实我就已经卡住了。 后来 对方的 技术主管说,这是由于 挂载前 我们已经将文件写道 未挂载的目录底下,读写是成功的,但是之后 才挂载成功,所以导致的现象就是,日志显示 读写成功,但是 tf卡 不存在 这个文件。

所以这个问题的核心问题是挂载 tf卡之前,就将文件写入了。我们要解的应该是这个问题。

这里测试下

我先自己建立一个文件夹 /temp/hello 然后 往这个文件夹 写入文件, 然后再 mount 看下效果

$ uname -a
Linux debian9-64-Desktop 4.9.0-8-amd64 #1 SMP Debian 4.9.110-3+deb9u6 (2018-10-08) x86_64 GNU/Linux
$ sudo mkdir -p /temp/hello
$ sudo touch a.txt
$ sudo umount -lf /dev/sdc1
$ sudo mount /dev/sdc1 /temp/hello/
$ ls
a.txt

我在我自己的虚拟机上面进行测试发现 挂载前后,文件是存在的。当退出当前目录的时候文件就看不见了。这就是上面问题的现象了。当你把挂载的目录 去掉之后,你就又会发现之前的文件回来了。

最后总结下 ,出现这个问题的原因,还是因为自己对于 挂载 前后 不熟,导致出现了上面的问题。

最后,通过查资料,发现后面可以通过: fsync fdatasync 来进行系统缓存的落地,fcntl 流控,以及 setbuf setvbuf 来解决 缓冲的问题。

fwrite 存在三种 缓冲:

  • 全缓冲 最大大小由<stdio.h> BUFSIZ 控制,根据平台不同,大小不一,4k左右
  • 行缓冲 指向终端设备的流 dev/tty 这个是串口,串口也是存在缓存的所以只有在遇到 换行符 才会执行io操作
  • 不带缓冲

setbuf

 FILE* f = fopen("file","w+");
 setbuf(f,NULL);// 设置 不缓冲
 fclose(f);

这样就可以将缓冲 去掉。

sync

sync 修改过的块 缓冲区排入写队列,然后返回,并不等待时机写磁盘操作结束。

fsync 只对由文件描述符 fd 指定的一个文件起作用,并且顶戴写磁盘操作结束才返回。

fdatasync 只会影响 文件的数据部分,不会对文件的属性进行更新。而 fsync 会对数据和属性同时更新

fcntl

int val = 0;
if ((val = fcntl(atoi(argv[1]), F_GETFL,0) )< 0)
{
    printf("fcntl error for fd %d\n", atoi(argv[1]));
    return -2;
}
val = val|O_SYNC; // 注意这里
int ret = fcntl(1,F_SETFL,val);
printf("fcntl set is %d\n",ret);

如果要设置的话,必须先获取才能 设置。

如果 设置了 O_SYNC 这个属性,则 每次write 都要等待,直至数据写道磁盘上面在返回。

当然也许有人会问 fsyncfcntl 这种方式,两种之间的区别是什么, 不都是 直接落地吗?

这里 就要说了,linux分为用户态缓存和内核态缓存。

fflush setbuf 都是对用户态缓存的操作。

fsync fcntl 都是对 内核态的缓存。

参考连接: www.cnblogs.com/suzhou/p/53…