计算机基础第十章学习日志:关于系统级I/O

计算机基础第十章主要讲解三个函数:
一、
1.open(char *filename, int flags, mode_t mode);

open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件:

O_RDONLY:只读。
O_WRONLY:只写。
O_RDWR:可读可写。
flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示:

O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
O_TRUNC:如果文件已经存在,就截断它。如果文件存在,并且是一个普通文件,而且打开方式是O_WRONLY、O_RDWR,则O_TRUNC会清空文件的内容。O_TRUNC|O_RDONLY的话O_TRUNC不会起作用!
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。
O_EXCL:该标志一般和O_CREAT配合使用,用来测试文件是否存在,如果指定O_CREAT|O_EXCL,如果文件存在,则open会失败。
O_NONBLOCK:非阻塞方式打开文件。
非阻塞:如果文件没有内容,read直接报错,如果文件没有空间,write直接报错。
阻塞:如果文件没有内容,read会阻塞(等待直到有数据)。如果文件没有空间,write会阻塞(等待直到有空间)。
mode参数指定了新文件的访问权限位。在应用时可让测试文件的模式位mode与以下掩码做与运算&即可判断权限。

2.read(int fd, void *buf, size_t n);

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。 返回值-1表示一个错误,同时errno被设置。而返回值0表示EOF,即读到文件尾。否则,返回值表示的是实际传送的字节数量。

3.write(int fd, const void *buf, size_t n);

write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。 若成功则返回写的字节数,若出错则为-1。

二、
接下来让我们用三道例题来熟悉它们的用法:
首先我们要知道共享文件是什么。
1.共享文件
可以用许多不同的方式来共享Linux文件。而内核用三个相关的数据结构来表示打开的文件,如图:
计算机基础第十章学习日志:关于系统级I/O

描述符表:每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。

文件表:打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成包括当前的文件位置、引用计数refcnt(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。

v-node表:同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。
2.例题
ffiles1.c ,代码如下:
计算机基础第十章学习日志:关于系统级I/O
首先,我们需要将文件进行编译链接,如图:
计算机基础第十章学习日志:关于系统级I/O
之后我们执行文件,如图( abcde.txt 中的内容为 abcde ):
计算机基础第十章学习日志:关于系统级I/O
解析:当我们执行这个文件后,结果输出为 c1 = a, c2 = a, c3 = b。
首先我们可以看到三个文件描述符:fd1, fd2, fd3 分别打开相同的文件 abcde.txt ,三个文件描述符都有自己的文件位置且互不干扰。
接下来我们来介绍一下函数 dup2(int oldfd, int newfd),这个函数表示复制 oldfd 覆盖 newfd ,即 newfd 的文件位置指向 oldfd 的文件位置,在之后使用 newfd 的地方相当于使用 oldfd 。
上文代码用 fd2 覆盖了 fd3 ,之后的第一个Read函数读出 abcde.txt 的第一个字符,所以 c1 = a;因为 fd1 和 fd2 指向不同的文件位置,所以第二个Read函数依旧读出abcde.txt 的第一个字符, 所以 c2 = a ;而因为 fd2 和 fd3 指向相同的文件位置,所以所以第三个Read函数读出 abcde.txt 光标后的字符,所以 c3 = b 。

2.ffiles2.c, 如图:
计算机基础第十章学习日志:关于系统级I/O
首先,我们依旧需要将文件进行编译链接,如图:
计算机基础第十章学习日志:关于系统级I/O
之后我们执行文件,如图( abcde.txt 中的内容为 abcde ):
计算机基础第十章学习日志:关于系统级I/O
解析:首先,我们需要知道语句 int s = getpid() & 0x1 是什么意思,getpid()即为获取进程的 pid 然后与十六进制的 0x1 进行异或,也就是说 s 可能为 1 也可能为 0 ,第一个Read函数读出 abcde.txt 的第一个字符,所以 c1 = a;之后 执行 fork() 函数,父进程与子进程共享文件表, 若 s 为 0 ,则子进程等待,父进程先执行 Read()函数,此时 输出 Parent: c2 = b ,Child: c2 = c ; 反之输出Parent: c2 = c ,Child: c2 = b 。

3.ffiles3.c,如图:
计算机基础第十章学习日志:关于系统级I/O
首先,我们依旧需要将文件进行编译链接,如图:
计算机基础第十章学习日志:关于系统级I/O
之后我们执行文件,如图( abcde.txt 中的内容为 abcde ):
计算机基础第十章学习日志:关于系统级I/O
打开 abcde.txt ,此时结果如下:
计算机基础第十章学习日志:关于系统级I/O
解析:首先, fd1 打开 abcde.txt ,清空 abcde.txt 文件,并执行 write()函数,向其中写下 pqrs ,接着 fd3 打开 abcde.txt ,因为有 O_APPEND ,所以执行下一个 write()函数,在光标后面写下 jklmn ,此时 abcde.txt 中的内容为 pqrsjklmn ,随后执行语句 fd2 = dup2( fd1 ),用 fd2 覆盖 fd1 ,下一条 write()函数相当于在 fd1 中写,fd1 的光标在 s 的后面,此时覆盖 s 后面的四个字符,因此,此时abcde.txt 中的内容为 pqrswxyzn ,之后执行的write()函数为在 fd3 的光标后写下 ef ,所以,最后 abcde.txt 中的内容为 pqrswxyznef 。