在上一篇章中我们主要学习了一个独立的程序是如何在系统上运行的,可是我们在实际编写程序的时候,会发现程序有时还要与IO设备或者别的程序交互和通信。这一篇章主要来学习程序与IO设备交互的知识。
程序与IO设备的交互是经常可以见到的,比如与磁盘、键盘、网络的交互。UNIX系统设计中一切皆文件的思想,因此所有的IO设备都被模型化为文件。这种思想使得我们在读写文件的时候可以忽略文件的底层机制,只用read、write函数即可对所有不同类型的文件来进行读取。在UNIX系统级要想读写文件中的内容,那么open、close、read、write这四个函数大家肯定要知道了。
首先我先来介绍一下我们经常在读文件时碰到的EOF,内核对每个打开的文件都保持着一个文件位置。对于read函数,读文件就是从文件拷贝n个字节到存储器,如果当前的文件位置到文件的结尾不足n个字节时,返回实际读到的字节数,如果再次读的话,会触发一个称为end-of-file的条件,应用程序可以检测到这个条件。在文件的结尾处并没有EOF符号。
其次我们来介绍一下共享文件。大家对于如下这个程序不知有何看法?假设hh.txt中的值为abc。
#include <stdio.h>
#include <sys/type.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd1, int fd2;
char c;
fd1 = open("hh.txt", O_RDONLY, 0);
fd2 = open("hh.txt", O_RDONLY, 0);
read(fd1, &c, 1);
read(fd2, &c, 1);
printf("%c", c);
close(fd1);
close(fd2);
return 0;
}
那我来告诉大家结果,打印出来的值为a。在内核中用三种数据结构来表示打开的文件。第一种是int型的描述符,即上面程序中的fd1、fd2。每个进程都有一张描述符表。第二种是打开文件表,所有进程共享,它里面主要保存着文件的位置,以及打开文件的文件位置即读文件时如果读了一个字符,那么这个文件位置就不再是0而是1了。第三种是v-node表,所有进程共享。主要保存着文件的属性。
对于上面的程序,打开文件表就有两个,每个文件表中都保存着打开文件的位置,他们是相互独立的,因此在最后一次read的时候,读的还是a。
当然我们在写程序时大部分的时候是不使用这几个系统级函数的,已经有人为我们开发出了一套标准io函数以供我们使用,比如printf,fopen,fread。
对于我们程序与别的程序的通信与交互(典型的例子就是客户端与服务器)主要是使用套接字来完成的,套接字是对网络抽象的一种文件类型,它也是用文件描述符来引用的,这样我们前面讲解的东西对套接字也完全适用。
对于多线程编程的情况,即不同线程间程序与程序的交互与通信。这里要考虑的问题还是比较多的,并且模型也与前面的模型不太一样,为了不影响这篇文章的主题,这里就不再累赘了,下次有空的时候为大家讲解。