文件I/O和标准I/O的使用

时间:2022-05-30 14:12:47

这两天写了一个小测试程序,运行在嵌入式系统上,用来测试CPU写SATA硬盘和USB2.0的速度。思路很简单,open()函数打开设备,返回文件描述符。将一块内存的内容通过write()写入文件。利用gettimeofday()可以获取write()的时间,然后就可以算出写的速度了。但是写完了一测,SATA硬盘的写速度达到了300-400MB/s,查阅硬盘手册,理论速度是6Gbps,所以还算正常。可是USB的速度也有300多MB/s!!!这是无论如何也说不通的。但是就是找不出问题在哪。

还是偶然注意到了UNIX环境高级编程的一段话,是讲文件打开的标志,其中有O_SYNC,说是等待物理write完成。不过要注意的是有的Linux系统上该标志无效。这是还有另一个办法就是fsync()函数。使用了fsync()函数之后,USB速度立马降到了十几兆MB有木有。这个速度还是可以理解的,因为我是直接从内存中写,而我们一般往U盘写文件,需要将文件先拷贝到内存,然后再写,所以速度更慢一些。速度降到十几MB的原因是这样的。其实Linux内核中有一些高速缓冲区,write()并没有直接将数据写到USB,而是写到了这个高速缓冲区,然后就返回了,因此时间短,计算的速度就很大。内核在以后的某个时刻将数据写到USB(所以其实之前测的SATA的速度也不是真的)。而fsync()的作用就是等待系统将数据真的写到设备中才返回。

另外,使用标准I/O稍微麻烦一点。因为标准I/O的流操作只是将数据写到用户空间的缓冲区(不是上面说的内核的高速缓冲区),数据全写到缓冲区就返回了。系统根据是行缓冲还是全缓冲还是无缓冲调用write()将数据写入到内核的高速缓冲区。然后内核在以后的某个时刻将数据写到USB,流程更多一点。而在标准I/O里是没有fsync这样的函数的,通过setbuf()可以设置无缓冲,或者在调用fwrite之后使用fflush()函数冲洗流缓冲,但也仅仅是将数据直接写入内核的缓冲区。如果想使用fsync()函数,那么首先fflush()将流冲洗到内核,然后通过fileno()获取流对应的文件描述符,再使用fsync()函数。代码流程更复杂一点。

后来想到一点优化,如果向存储设备中写入固定的数据量,而恰巧设备有问题,比如速度只有几KB/s,那测试时间简直太恐怖了。所以想到用固定时间来测。我malloc()一段很大的内存,向设备中写一小段时间(肯定是写不完的),write()返回写入的数据量,也能计算出速度。但最终没能实现。慢速系统调用可以使用alarm()函数定时,发送信号中断write()并返回写入的数据量,但是磁盘I/O却不属于慢速系统调用。因此这一条路走不通了,暂时还没有其他思路。