共享文件
内核使用三个相关的数据结构来表示打开的文件
1.描述符表:每个进程都有独立的描述符表。他的表项是由进程打开的文件描述符索引的,每个打开的文件描述符指向文件表当中的一项。
2.文件表:打开的文件集合是由一张文件表来表示的所有的进程共享这张表。每个文件表的表项的组成:当前文件的位置,引用计数,一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应文件表表项当中的引用计数,直到这个引用计数为0内核才会删除这个表项。
3.v-node表:所有的进程共享这张表。每个表项包含stat结构当中的大多数信息 包括st_size:包含文件的字节数的大小,st_mode编码了文件访问许可和文件类型。
例子:
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
char c1, c2;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
Read(fd1, &c1, 1);
if (fork()) {
/* Parent */
sleep(s);
Read(fd1, &c2, 1);
printf("%d\n",getpid());
printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
Read(fd1, &c2, 1);
printf("%d\n",getpid());
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
ice@ice-virtual-machine:~/chap10/chap10_code$ ./aaa abcde.txt
Child:c1 = a,c2 = b
Parent: c1 = a,c2 = c
分析:父进程中fork返回子进程的pid(非零),子进程中fork返回0.在调用子进程之前,父进程已经从 abcde.txt 读了一个字符 即c1=a。sleep函数可以将一个进程挂起一段指定的时间,若为sleep(1),该进程后执行;sleep(0)不影响进程执行,当父进程ID为偶数时,父进程中是sleep(0),子进程中是sleep(1)。因此父进程先执行,此时读c2,当前光标在a后面,所有读下一个字符 即c2=b;接下来执行子进程,因为这是父子进程共享文件位置,所以读子进程c2时,光标是在b后面的,因此c2=c (如第一次运行结果所示)
I/O重定向
下面来讨论一下I/O重定向的问题。
首先,学习一下dup()和dup2()重定向函数,这两个函数提供了复制文件描述符的功能。
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
这两个函数调用成功之后,都返回一个oldfd文件描述符的副本,失败则返回-1。所不同的是,由dup()函数返回的文件描述符是当前可用文件描述符中的最小值,而dup2()函数则可以利用参数newfd指定要返回的文件描述符。如果参数newfd指定的文件描述符已经打开,系统想将其关闭,然后将oldfd指定的文件描述符复制到该参数。如果newfd等于oldfd,则dup2返回newfd,而不关闭它。
例子1:
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, "pqrs", 4);
fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
Write(fd3, "jklmn", 5);
fd2 = dup(fd1); /* Allocates new descriptor */
Write(fd2, "wxyz", 4);
Write(fd3, "ef", 2);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
ice@ice-virtual-machine:~/chap10/chap10_code$ ./a abcde.txt
abcde.txt文件内容:pqrswxyznef
首先fd1 打开abcde.txt文件,有则打开并清空,无则创建;使用权限为可读可写。调用Write此时文件内容为pqrs。然后fd3打开文件 ,在文件尾追加内容。调用Write此时文件内容为pqrsjklmn。接下来将fd2重定向到fd1位置,fd1的当前光标位置在s后面。调用Write,会覆盖fd3一部分内容,此时文件内容为pqrswyzxn。最后调用Write在文件为追加内容,此时文件内容为pqrswyzxnef。
例子2:
#include "csapp.h"
int main(int argc, char *argv[])//abcde.txt
{
int fd1, fd2, fd3;
char c1, c2, c3;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
fd2 = Open(fname, O_RDONLY, 0);
fd3 = Open(fname, O_RDONLY, 0);
dup2(fd2, fd3);
Read(fd1, &c1, 1);
Read(fd2, &c2, 1);
Read(fd3, &c3, 1);
printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
ice@ice-virtual-machine:~/chap10/chap10_code$ ./aa abcde.txt
c1 = a,c2 = a,c3 = b
fd1、fd2、fd3通过不同的文件表表项来引用同一个文件,每个描述符都有它自己的文件位置,所以对不同的描述符的读操作可以从文件不同的位置获取数据。而dup2(fd2, fd3);使fd2覆盖fd3的内容,此时fd2、fd3是同一个文件表表项。fd1读c1=a;fd2读c2=a,fd3此时与fd2光标位置一样都在a后面,使用fd3读c3=b。