【Linux】文件IO

Linux系统中的大多数文件IO只需用到5个函数:open、read、write、lseek以及close,这些函数是不带缓冲的IO,与标准库中的文件IO(带缓冲)相对应。不带缓冲指的是每个read和write都调用内核中的一个系统调用,它们不是ISO/C标准内的东西,而是POSIX和SUS中的一部分。

文件描述符——

在Linux中,一切都是文件。对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,按照惯例,文件描述符0、1、2固定地分别表示标准输入、标准输出、标准出错,在头文件“unistd.h”中,这三个文件描述符宏定义为STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。文件描述符的变化范围为从0到一个最大值,这个最大值可通过函数sysconf获取,函数的参数为OPEN_MAX(OPEN_MAX - _SC_OPEN_MAX The maximum number of files that a process can have open at any time. Must not be less than _POSIX_OPEN_MAX (20).),也可以使用shell命令getconf取得,这个值可能受到系统配置的存储器总量、整型的字长、资源的软硬限制等影响。

open函数——

#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

调用open函数可以打开或创建一个文件,若成功则返回文件描述符,一定是最小的未使用的描述符数值,若出错则返回-1。pathname是要打开或创建的文件名字,当整个路径名超过PATH_MAX或路径名中任一文件名超过NAME_MAX时,若_POSIX_NO_TRUNC有效,则返回出错状态,并将errno设置为ENAMETOOLONG,否则名字可能被截断。flags参数用来说明如何对文件进行操作,是多个选项按位或的结果,其中“O_RDONLY,”、“O_WRONLY,”、“O_RDWR”三者有且只能有一个,其它的包括“O_CREAT”、“O_TRUNC”等,这些选项在头文件“bits/fcntl.h”中定义。mode参数仅当创建新文件时才使用,用于指定该新文件的访问权限位,与进程的umask值有关,如与user相关的“S_IRWXU”、“S_IRUSR”、“S_IWUSR”、“S_IXUSR”等,这些选项在头文件“fcntl.h”中定义。

create函数——

#include <fcntl.h>
int create(const char *pathname, mode_t mode);

调用create函数也可以创建一个新文件,等效于如下open函数:

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

close函数——

#include <unistd.h>
int close(int fd);

调用close函数关闭一个打开的文件,成功返回0,出错返回-1,一般与open函数配对使用。当一个进程终止时,内核会自动关闭它打开的所有文件,这样便可不用显式地调用close函数关闭打开的文件,另外,文件关闭时还会释放该进程加在它身上的所有记录锁。

lseek函数——

#include <unistd.h>
off_t lseek(int fd, off_t offset, in whence);

调用lseek函数为一个打开的文件设置偏移量,这个偏移量用于read、write函数,成功时返回新的文件偏移量,出错时返回-1。lseek仅将当前的文件偏移量记录在内核中,并不引起任何IO操作,然后该偏移量用于下一个读、写操作。whence参数可以是SEEK_SET、SEEK_CUR、SEEK_END中的一个,分别表示偏移量的基准位置为文件开头、文件当前位置、文件结尾,这三个值在头文件“fcntl.h”中定义。offset可以大于文件的当前长度,在这种情况下,对该文件的下一次写操作将加长该文件,并在文件中产生一个空洞,位于文件中但没有写过的字节都被读为0,然而文件中的空洞并不要求在磁盘上占用存储区,使用命令“ls -ls filename”可查看文件的大小和block数量。

read函数——

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

调用read函数从打开的文件中读取数据,从当前的文件偏移量开始,最多读取count个字节,成功时返回读到的字节数,到达文件末尾时返回0,出错时返回-1,ssize_t为带符号的长整型,size_t为无符号的长整型。

write函数——

#include <unistd.h>
ssize_t write(int fd, cosnt void *buf, size_t count);

调用write函数向打开的文件中写入数据,从当前的文件偏移量开始,最多写入count个字节,成功时返回写入的字节数,出错时返回-1。

IO效率——

APUE一书描述了不同的buffer长度对IO效率的影响,例子中使用read和write函数复制一个文件,文件长度为103316352字节,buffer长度从1B到512K,测试了不同buffer长度下的用户CPU时间、系统CPU时间、时钟时间以及循环次数,结果显示,buffer长度为4096B(4K)时系统CPU时间最短,即一个block的大小。大多数文件系统为改善其性能都采用某种预读技术,当检测到正在进行顺序读取时,系统就试图读入比应用程序所要求的更多数据,并假想应用程序很快就会读这些数据,那么在下次读数据时直接从缓存中获取,提高了读取效率。

文件共享——

Linux系统支持在不同进程间共享打开的文件,内核使用三种数据结构表示打开的文件,分别是进程表(包含一张已打开的文件描述符表,记录了每个文件描述符的close_on_exec标志和指向的文件表)、文件表(记录了文件的读写等状态标志、偏移量和指向的v节点)、v节点(记录了文件状态和对文件进行操作的函数指针以及i节点等),每个进程都维护一个进程表,每个打开的文件都有一个文件表,文件表指向了对应的v节点,但同一个文件共享一个v节点,也就是说,当多个进程打开了同一个文件时,虽然有各自的进程表和文件表,但使用的是同一个v节点,同时对文件进行写操作时必需同步或原子的执行,否则就会引起混乱。例如,调用lseek和read函数,本意是先定位文件偏移量再读取数据,如果这个文件被多个进程共享,两个函数执行之间被别的进程插入了另外的操作就可能带来问题,正确的做法是对这两个操作加锁,或者使用一个等效的替代函数pread,这个函数就好像把lseek和read当作一个原子操作一样,对应的写操作还有一个pwrite函数。对于open函数的O_APPEND就相当于lseek到文件尾并执行write是个原子操作,O_EXCL和O_CREAT先判断文件是否存在再创建文件也是个原子操作。

文件操作——

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup和dup2两个函数都可以用来复制一个现有的文件描述符,dup函数返回当前可用的最小数值,可以用fcntl函数做同样的事,dup2函数则返回指定的newfd文件描述符,newfd已打开时要先关闭它,newfd等于oldfd时则不关闭,可以用close和fcntl函数做同样的事,但dup2函数的这两个操作是原子的。这两个函数获得的新文件描述符与旧文件描述符共享同一个文件表,而close_on_exec标志被清除。

#include <unistd.h>
void sync(void);
int fsync(int fd);
int fdatasync(int fd);

传统Linux实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘IO都通过缓冲进行。当将数据写入文件时,内核通常先将数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其它磁盘数据时,才将该缓冲排入输出队列,然后待其到达队首时,才进行实际的IO操作,这种输出方式被称为延迟写。延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,Linux系统提供了sync、fsync和fdatasync三个函数。sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束,通常称为update的系统守护进程会周期性调用sync函数。fsync和fdatasync对指定的文件描述符起作用,并且等待写磁盘操作结束,而后者只影响文件的数据部分。

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */);

fcntl函数可以改变已打开文件的性质,重点是cmd参数,这些可用的参数在“bits/fcntl-linux.h”头文件中定义,功能包括复制现有的文件描述符,获得和设置文件描述符标记、文件状态标记、异步IO所有权、记录锁。

#include <sys/ioctl.h>
int ioctl(int fd, int request, …);

ioctl函数是IO操作的杂物箱,不能用上面列出的函数表示的IO操作都能用ioctl函数表示,终端IO是ioctl函数的最大使用方面。ioctl是设备驱动程序中对设备的IO通道进行管理的函数,用以控制设备特性,如串口的传输波特率,不同与read、write的数据传输,ioctl传输的主要是控制命令,数据只是辅助的。ioctl的request参数是通过宏定义的,这样理解起来更方便一点,在许多的系统头文件都有定义,一个32bit的数据,包括了设备类型、序列号、方向和数据尺寸,这些数据通过系统调用传递到驱动程序,就需要驱动程序取解析了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值