您的位置:首页 > 其它

第三章 文件I/O

2014-12-28 15:39 48 查看
第一、二章

在命令前加 time -p 可以得到程序运行三种时间

系统调用是进入内核的入口点(如 write),通用库函数却不是内核的入口点(如 printf, strcpy)

第三章 文件I/O

open()的falgs中一些陌生的常量:


O_EXCL: 如果指定了 O_CREAT ,而文件已经存在,则出错。可用于测试一个文件是否存在,不存在则创建,使得测试和创建为一个原子操作;存在则出错,错误为EEXIST。

O_SYNC: 对该文件的每一次write都将在write返回前更新文件属性(即若将该属性加入,那么每次write都要等待,直至数据已经写到磁盘上再返回; 这就引出了另一个系统特性,

通常write操作只是将数据排入队列,而实际的写操作则是在以后某时刻执行。 当然,加入此标志后会增加系统时间和执行时间,但却换来了系统异常时的安全性)

使用lseek时,文件偏移量可以大于文件的当前长度,这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,这是允许的。(位于文件中但没有写过的字节被读为0)。

文件中的空洞并不在磁盘上占据存储区,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。

在本章目录下的 file_hole 文件就展示了这个特性。如果创建一个 真真切切的 长度为 310 的文件,那么所占据的磁盘块数就不止4个了。

文件共享

内核使用3种数据结构 表示打开文件,他们之间的关系决定了在文件共享方面的一个进程对另一个进程可能产生的影响。

一、每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,每个描述符占一项:

a、文件描述符标志

b、指向一个文件表项的指针,即二

二、内核为所有打开文件维持一张文件表:

a、文件状态标志(读,写等)

b、当前文件偏移量

c、指向该文件v节点表项的指针

三、每个打开文件(或设备)都有一个v节点(v-node)结构

包含文件类型和对此文件各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点(索引节点)。这些信息是在打开文件时从磁盘读入内存的。

注意:linux没有使用v节点,但整体的概念还是差不多的。 APUE P61

若两个进程各自打开了同一个文件,那么:

我们假设第一个进程在文件描述符3上打开该文件,另一个在4上打开。

打开该文件的各个进程都获得自己的文件表项(是因为这可以使每个进程都有它自己的对该文件的当前偏移量,因此若都使用lseek定位到文件尾端,将发生未知错误,看下面第2、3点)。

各个进程对同一个打开的文件只有一个v节点表项。

各个操作对于这些数据结构的影响:

1、完成每个write后,文件表项中的当前文件偏移量即增加所写入的字节数。(如果导致偏移量超出文件长度,那么将i节点中的文件长度设置为当前文件偏移量)

2、若用 O_APPEND 打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次执行写时,文件表项中的偏移量都被设置为i节点中的文件长度。

3、若用lseek来定位到文件尾端,那么偏移量会被设置为i节点中的文件长度

(与O_APPEND是有所不同的,O_APPEND为原子操作。 因为若多个进程同时打开一个文件,然后调用lseek,并且都执行写操作,那么都将在同一起始位置写而发生覆盖错误。然而使用 O_APPEND时候,使得内核在每次写操作前,都将进程的当前偏移量设置到该文件的尾端处,这就是原子操作的特性)

4、lseek只修改文件表项中的偏移量,不进行任何I/O操作。

原子操作:

要么都做,要么都不做! ---- 因此以上O_APPEND要么检查并写,要么什么都不做

函数 pread 和 pwrite

调用pread 与 在lseek后调用read 类似但也有重要区别:

1、调用pread后,无法中断其定位和读操作

2、不更新当前文件偏移量(!!!)

pwrite同上

函数 dup 和 dup2

使用dup(fd) 相当于使用 fcntl(fd,F_DUPFD,0)

而 dup2(fd,fd2) 相当于使用 close(fd2);fcntl(fd,F_DUPFD,fd2)

注意 : dup2是一个原子操作

函数 sync、fsync 和 fdatasync

通常,当我们向文件写入数据时,内核先将数据复制到缓冲区中,然后排入队列,称为 “延迟写”。当内核要重用缓冲区来存其它数据时,即将延迟写数据写入磁盘。

1、sync只是将所有 修改过的 块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束。

2、fsync(int fd) 只对一个文件其作用,且等待写磁盘操作结束才返回。(可用于数据库这种应用)

3、fdatasync(int fd) 只影响数据部分,而fsync则不仅仅影响数据还影响其文件属性。

函数 fcntl

之前在实践教程上学了些,大概是了解了fcntl所改变的是文件的连接属性。(比如非阻塞、添加模式等)

有以下5种功能:

一、复制一个已有的描述符(上面讲过,与dup、dup2一起) cmd = F_DUPFD 或 F_DUPFD_CLOEXEC

FD_DUPFD:

根据之前谈到的关于内核使用的3种数据结构来表示打开的文件:

新的描述符(虽然指向同一个文件)有自己的一套文件描述符标志,其FD_CLOSEXEC被清除(这里解释一下FD_CLOEXEC的作用:

清除该标志表示在使用exec时仍保持有效(默认行为);设置该标志表示在exec时关闭该描述符,即无法再子程序中使用它)

新的描述符与旧的同时指向一个文件表表项

FD_DUPFD_CLOEXEC

复制并设置FD_CLOEXEC标志

二、获取/设置文件描述符标志(这个之前没用过,不过什么是文件描述符标志呢?) cmd = F_GETFD 或 F_SETFD

原来当前也只有一个文件描述符标志,就是 FD_CLOEXEC啊。。。

三、获取/设置文件状态标志(也就是当初所学的皮毛内容) cmd = F_GETFL 或 F_SETFL

四、获取/设置异步 I/O 所有权 (异步I/O接触过,不过没用过这个。。) cmd = F_GETOWN 或 F_SETOWN

F_GETOWN 用于获取当前接收SIGIO 和 SIGURG 信号的进程ID或进程组ID

F_SETOWN 则用于设置接收信号的ID

这里联系到实践教程中的 workspace/System_Programming/Chapter7/bounc_async.c 这个程序,使用到 F_SETOWN来指定接收I/O信号的进程。。

五、获取/设置记录锁(书上说14章有讲。。。)

要注意的是:想要修改状态标志要先使用GET得到原有的标志,之后在其基础上修改自己所需;若直接使用SET,会导致其他状态的丢失

书上说将来讲管道的时候还会讲到这个,也是可以理解的,毕竟被管道搞了段时间,前提是只知道管道有两个口子。。。

函数 ioctl

之前也碰到了,大概是说这个是修改文件本身的属性,而不再是连接属性了

常用于终端 I/O

18、19章再说。。。

有几个地方要注意一下:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
int pid;
int fd;
char buf[BUFSIZ];

fd = open("test",O_RDONLY);

pid = fork();

if(pid == -1)
exit(1);
else if(pid == 0)
{
lseek(fd,10,SEEK_SET);
exit(0);
}
else
{
wait(NULL);
read(fd,buf,BUFSIZ);
printf("%s",buf);
}
return 0;
}


运行结果是输出的内容从文件开头偏移了10个字节

这是 workspace/Test/fork_descriptor.c ,

根据这个程序我们发现父子进程间共用的文件描述符(虽然fd大小相同,但不表示就是同一个,因为关闭其中一个并不影响另一个进程fd的使用,我推测是有个计数器的)是相关的,根据内核使用的打开文件的数据结构,可以发现这两个文件描述符是指向同一个文件表表项的,因此其偏移量相互影响。

dup 产生的两个大小不同的文件描述符也是指向同一个文件,他们也是指向同一个文件表表项的。

然而,无关系的程序使用open打开的文件描述符若是共同指向一个文件,其指向不同的文件表表项,因而偏移量是不同的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: