目录
一. 文件的基本操作(打开、定位、读写、关闭)
1.1 文件操作的基本顺序
打开 open
创建 create
定位 lseek
读 read
写 write
关闭 close
1.2 open函数
-
用于打开或者创建一个文件
-
函数原型
int open( const char* pathname, int oflag, ... )
-
参数
pathname: 要打开或者创建的文件描述符;
oflag: 用于指定文件打开模式标识信息等;- 文件打开模式标志( 必须且唯一 )
O_RDONLY : 只读打开
O_WRONLY: 只写打开
“只写”并不意味着完全只能写入而不能被读取,而只是根据用户组的权限设置,“只写”所对应的用户组没有对该文件的读取权限,而其它用户组(例如超级用户root)可能具有读取该文件的权限。
O_RDWR: 读写打开 - 其他文件标志( 可选 )
O_APPEND: 每次写的数据都添加到文件尾
O_TRUNC: 若此文件存在,并以读写或只写打开,则清空文件全部内容(即将其长度截短为0)
O_CREAT: 若文件不存在,则创建该文件。此时,open函数需要第
三个参数,用于指定该文件的访问权限位
O_EXCL: 若同时指定了O_CREAT标志,而文件已经存在,则会出
错。可用于测试文件是否存在
… :可变参数,mode_t mode
参考: linux open函数的 mode_t涵义 - 文件打开模式标志( 必须且唯一 )
-
返回值
-
整型数据:
- 成功时,返回文件文件描述符
- 出错时,返回-1
1.3 文件描述符
-
进程打开文件的内核数据结构
task_struct: 进程控制块( Process Control Block, PCB )
files_struct: 包含了进程已打开的文件表, open函数返回的文件描述是符已打开文件表的索引
file: 文件对象, 代表一个已打开的文件
f_dentry: struct dentry *f_dentry; 文件相关目录项
f_pos: 文件偏移量
d_inode: struct inode *d_inode; -
索引节点
1)文件系统索引节点的信息,存储在磁盘上;
2)当需要时,调入内存,填写VFS的索引节点(即inode结构)
3)每个文件都对应了一个索引节点
4)通过索引节点号,可以唯一的标识文件系统中的指定文件
struct inode{
…
unsigned long i_no;//索引节点号
umode_t i_mode;//文件类型访问权限
uid_t i_uid;//文件拥有者ID
gid_t i_gid;//文件拥有者所在组ID
off_t i_size;//文件大小
time_t i_atime;//最后访问时间
time_t i_mtime;//最后修改时间
}; -
文件描述符
1)文件描述符是已打开文件的索引, 通过该值可以在fd_array表中检索相应的文件对象
2)文件描述符是一个非负的整数
3)文件描述符0、1、2分别对应于标准输入、标准输出、标准出错,在进程创建时, 已经打开。
1.3 creat函数
(不是create)
-
用于创建一个新文件
-
函数原型
int creat(const char *pathname, mode_t mode)
-
参数
pathname: 要创建的文件名(包括路径信息)
mode: 同open的第三个参数, 访问权限位 -
返回值
成功返回只写打开的文件描述符
出错返回-1
creat函数的功能可以用open函数实现:
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
//指定O_TRUNC标志是因为当文件存在时,调用creat函数,会将文件的大小变为0.
1.4 lseek函数
-
lseek函数用于修改当前文件偏移量
当前文件偏移量的作用当前文件偏移量的作用: 规定了从文件什么地方开始进行读、写操作
通常,读、写操作结束时,会使文件偏移量增加读写的字节数
当打开一个文件时,除非指定了O_APPEND标志,否则偏移量被设置为0 -
函数原型
off_t lseek(int filedes, off_t offset, int whence)
-
参数
filedes: open/creat函数返回的文件描述符;
offset:
相对偏移量:需结合whence才能计算出真正的偏移量
类型off_t:通常情况下是32位数据类型
whence: 该参数取值是三个常量之一
SEEK_SET: 文件偏移量为距文件开始处的offset个字节
SEEK_CUR: 文件偏移量为当前文件偏移量+offset(可正可负)
SEEK_END: 文件偏移量为当前文件长度+offset(可正可负) -
返回值
若成功,返回新的文件偏移量
若出错,返回-1 -
获得当前偏移量
off_t CurrentPosition; CurrentPosition = lseek(fd, 0, SEEK_CUR); //fd: open或creat返回的文件描述符 //0: 相对当前偏移量为0的位置
lseek操作并不引起任何I/O操作,只是修改内核中的记录。
- 空洞文件
空洞文件特点就是offset大于实际大小,也就是说一个文件的两头有数据而中间为空,以‘\0‘填充。
使用lseek修改文件偏移量后,当前文件偏移量有可能大于文件的长度在这种情况下,对该文件的下一次写操作,将加长该文件这样文件中形成了一个空洞。对空洞区域进行读,均返回0.
1.5 read函数
-
用于从文件中读出数据
-
函数原型
ssize_t read(int fd, void *buff, size_t nbytes)
-
参数
fd: open或creat返回的文件描述符
buff: 指向缓冲区,用于存放从文件读出的数据
void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据.
void真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。
nbytes:unsigned int;需要从文件中读出的字节数
缓冲区的大小>=nbytes -
返回值
类型: ssize_t, 即int
出错:返回-1
成功:返回从文件中实际读到的字节数
当读到文件结尾时,则返回0
很多情况下,read实际读出的字节数都小于要求读出的字节数
1.6 write函数
-
用于向文件写入数据
-
函数原型
ssize_t write( int fd, const void *buff, size_t nbytes );
-
参数
fd:文件描述符
buff:指向缓冲区,存放了需要写入文件的数据
nbytes:需要写入文件的字节数 -
返回值
返回值类型:ssize_t,即int
出错:返回-1
成功:返回实际写入文件的字节数
write出错的原因:
磁盘满
没有访问权限
超过了给定进程的文件长度限制
1.7 close函数
- 用于关闭一个已打开的文件
- 函数原型
- 参数
- 返回值
二. I/O效率
ssize_t read(int fd, void *buff, size_t nbytes)
nbytes的取值是影响I/O效率的关键。
-
原因
- ◼ Linux文件系统采用了某种预读技术
- ◼ 当检测到正在进行顺序读取时,系统就试图读入比应用程序所要求 的更多数据
- ◼ 并假设应用程序很快就会读这些数据
- ◼ 当BUFFSIZE增加到一定程度后,预读就停止了
三. 文件共享
3.1 进程之间的文件共享
-
两个独立的进程各自打开同一个文件
两个独立的进程打开文件时,操作系统内核中都会创建一个不同的文件表项(struct file结构体),但是这些不同的struct file结构体最终都会指向同一个struct inode结构体,因为一个文件只能对应唯一的一个struct inode结构体。由于每次open都会创建不同的struct file结构体,也就是说不同的文件描述符对应着不同的文件表项(struct file结构体)。所以每个进程有各自独立的当前文件偏移量,互不影响。
举个例子,两个独立的进程打开了同一文件,进程A在fd[3]上打开了文件,而进程B是在fd[4]上打开该文件, 接着向 fd[3] 中写入了一部分数据,在完成每个write后,当前文件偏移量即增加所写的字节数,fd[3] 的当前文件偏移量被更新。然后再向fd[4] 中写入数据时,由于此时fd[4] 的当前文件偏移量仍处在文件开头,所以写入的数据会覆盖了先前向 fd[3] 中写入的数据。
如果用O_APPEND标志打开了一个文件,则该标志存储在file结构体中。每次执行写操作时,当前偏移量首先被设置为文件长度。
-
不同进程共享文件对象
进程间共享文件还有一种形式是共享文件对象。即共享file结构体。
这附图就描述了不同进程共享文件对象的情况。
在这幅图中,进程AB有各自的PCB和文件表。进程A的fd_array[3]和fd_array[4]指向了同一个文件对象,当然也就共享了索引节点。这种情况通常是调用fork函数之后,父子进程之间对文件的共享。
由于文件偏移量存储在file对象的f_pos字段,所以两个进程共享。若a调用了lseek设置为50,进程b就要从50处开始读写。
3.2 进程内的文件共享
-
多次使用open函数打开相同文件
-
使用dup/dup2函数或者fcntl函数
dup函数
-
作用
用于复制一个已存在的文件描述符,使新老文件描述符都只想同一个文件对象。
-
函数原型
int dup( int filedes );
-
返回值
成功返回新的(未占用的最小的)文件描述符
出错返回-1 -
参数
filedes:文件描述符
dup2函数
-
作用
用于复制一个已经存在的文件描述符 -
函数原型
int dup2(int filedes, int filedes2);
-
dup和dup2的区别
dup返回的新文件描述符一定是当前可用描述符中的最小值。
dup2则将文件描述符复制到指定位置,即将filedes复制到filedes2。
如果filedes2已经打开,dup2则先将其关闭;若filedes2等于filedes,则直接返回filedes,而不关闭。
四. 其他重要I/O函数
4.1 sync/fsync/fdatasync函数
通常Linux实现在内核中设有缓冲区高速缓存或页面高速缓存。
大多数的磁盘I/O都通过缓冲区进行。
当将数据写入文件时,内核通常先将数据复制到某一个缓冲区中。
如果该缓冲区满或者内核需要重用该缓冲区,则将该缓冲排入到输出队列。
等到其达到队首时,才进行实际的磁盘读写操作。
延迟写。
延迟写是指把要写的电脑数据先都放到内存里,等积累多了再一次性写到硬盘,降低对硬盘的读写损耗。
-
延迟写优点
- 减少了磁盘读写次数 延迟写缺点
- 降低了文件内容的更新速度
- 当系统发生故障,高速缓冲区中的内容可能丢失 解决办法
-
对缓冲区进行清理,希望将数据写入到磁盘
sync、fsync、fdatasync起到了刷缓存的作用
-
sync函数
作用:将所有修改过的缓冲区排入写队列,然后就返回,并不等待实际的写磁盘操作结束。
sync函数针对的是所有修改过的缓冲区,并不仅仅针对某个被修改过的文件
通常称为update的系统守护进程会周期性地调用sync函数,即保证定期冲洗内核缓冲区
原型:
void sycn();
-
fsync函数
作用:
fsync函数只对文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束后才返回。
原型:int fsync(int filedes);
参数:
filedes:文件描述符返回值:
成功返回0,出错返回-1 -
fdatasync函数
作用:
fdatasync和fsync类似,但它只影响文件的数据部分;而fync不仅影响文件的数据,还同步更新文件的属性。
函数原型:int fdatasync(int filedes);
参数:
filedes:文件描述符返回值:
成功返回0,出错返回-1。 -
对比
4.2 fcntl函数
-
作用:
用于改变已经打开文件的性质用于改变已经打开文件的性质 -
函数原型
int fcntl(int filedes, int cmd, .../* int arg,可变参数 */);
-
返回值
成功时,返回值依赖于第二个参数cmd
出错时,返回-1 -
参数
第三个参数依赖于第二个参数。
-
第一个参数filedes
-
已打开文件的文件描述符
第二个参数cmd的五种取值方式:
-
复制一个现存的描述符(cmd=F_DUPFD),fcntl返回新文件描述符。新描述符是尚未打开的各描述符中,大于或等于第三个参数值的最小值。
-
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD),将文件描述符filedes对应的标志,作为返回值返回。
-
获得/设置文件状态标志(cmd=F_GETFL或F_SETFL),设置文件描述符filedes对应的标志。新标志按照第三个参数设置。文件状态标志包括:O_RDONLY、 O_WRONLY 、O_RDWR 、O_APPEND 、
O_NONBLOCK(非阻塞方式)、 O_SYNC(等待写方式)、O_ASYNC(异步方式,仅4.3+BSD)。 -
获得/设置异步I/O信号接收进程(cmd=F_GETOWN或F_SETOWN),将fcntl函数的第三个参数,设置为文件状态标志。可以更改的标志包括:O_APPEND、O_NONBLOCK、O_SYNC、O_ASYNC
-
获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW),获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。
4.3 ioctl函数
- I/O操作的杂物箱
- 其实现的功能往往和具体的设备有关系
- 设备可以自定义自己的ioctl命令
- 操作系统提供了通用的ioctl命令
- ioctl类似于windows的DeviceIoControl函数