CSAPP ffiles理解
1. ffliles1.c
#include "csapp.h"
int main(int argc, char *argv[])
{
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;
}
open()
,该函数将 filename 转换为一个文件描述符,并且返回描述符的数字。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>
int open(char *filename, int flags, mode_t mode);
//返回:若成功则为新文件描述符,若出错为-1
flag
参数指明了进程打算如何访问这个文件,可以是一个,也可以是更多位掩码的或,为写提供一些额外的指示。
O_TDONLY:只读。
O_WRONLY:只写。
O_RDWR:可读可写。
O_CREAT:如果文件不存在,就创建它的一个截断的(truncated)(空)文件
O_TRUNC:如果文件已经存在,就截断它。
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。
例如:
fd = Open(“foo.txt”, O_WRONLY | O_APPEND, 0);
3.mode
参数指定了新文件的访问权限
掩码 | 描述 |
---|---|
S_IRUSR | 使用者(拥有者)能够读这个文件 |
S_IWUSR | 使用者(拥有者)能够写这个文件 |
S_IXUSR | 使用者(拥有者)能够执行这个文件 |
S_IRGRP | 拥有者所在组的成员能够读这个文件 |
S_IWGRP | 拥有者所在组的成员能够写这个文件 |
S_IXGRP | 拥有者所在组的成员能够执行这个文件 |
S_IROTH | 其他人(任何人)能够读这个文件 |
S_IWOTH | 其他人(任何人)能够写这个文件 |
S_IXOTH | 其他人(任何人)能够执行这个文件 |
dup2()
dup2()用来复制参数oldfd 所指的文件描述词, 并将它拷贝至参数newfd 后一块返回. 若参数newfd为一已打开的文件描述词, 则newfd 所指的文件会先被关闭。 dup2()所复制的文件描述词, 与原来的文件描述词共享各种文件状态。
所以在此代码中 fd1
,fd2
,fd3
对文件的操作都是只读。同时它们的所获得的文件描述符是不一样的,但最终都指向同一个文件。在它们读的过程中,虽然都是读同一个文件,但是光标所指的位置是未必相同的,这取决于它们自己所读到的位置,即文件的状态不同,相互没有影响。但是当我们使用了dup2(fd2, fd3);
,那么就会把fd2
的文件描述词赋给fd3
,此时它们共享文件状态,所以当fd2
读走一个字符时,fd3
会接着它读走下一个字符,而不是重头开始读取。
运行结果:
$ gcc ffiles1.c csapp.h csapp.c -lpthread -o ffiles1
$ ./ffiles1 abcde.txt
c1 = a, c2 = a, c3 = b
2. ffiles2.c
#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("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
Read(fd1, &c2, 1);
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
int s = getpid() & 0x1;
这一句获取了当前进程的进程号后,与0x1
相与,相当于是获取当前进程的进程号的最后一位赋值给s
,这样得到的结果是随机的,因为进程号的最后一位我们无法确定是什么,0~9都有可能,而这也就导致后面sleep(s);
父进程休眠的时间是不确定的。- 子进程与父进程都是用fd1来读取文本,所以它们是同一个文件状态,一个先读取走了一个字符,那么下一个就会接着读取下一字符。
运行结果如下:
$ gcc ffiles2.c csapp.h csapp.c -lpthread -o ffiles2
$ ./ffiles2 abcde.txt
Parent: c1 = a, c2 = b
Child: c1 = a, c2 = c
在上述的运行结果中,是父进程先运行,所以它就会读取a后面的字符b,而当子进程运行时,就会接着父进程读的位置,读走下一个字符,即字符c。
3. ffliles3.c
#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;
}
O_CREAT|O_TRUNC|O_RDWR
最终表示的是创建一个空白的文件,可读可写。S_IRUSR|S_IWUSR
表示对使用者的权限是可读可写。fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
则表示打开文件后再文件后添加,且只写。
运行结果如下:
/*abcde.txt
pqrswxyznef
*/
这部分代码主要是过程较前面两个会复杂些,那么我们就来一起分析一下吧。
- 文件最开始的内容是
abcde
,但是当fd1
打开该文件后,由于flag为O_CREAT|O_TRUNC|O_RDWR
,那么打开后就会将里面的内容全部截断,即清空,此时得到的文件就是一个空的文本文件。 Write(fd1, "pqrs", 4);
,fd1
在文件中写了四个字符,那么此时的文件中的内容就为 pqrs,并且fd1
的光标指在 s 的后面。Write(fd3, "jklmn", 5);
此时fd3
在文件中写入字符,特别注意,在之前的分析中,fd3
在文件中的写入是设置文件位置到文件的结尾处。所以此时会接着文件的末尾写,所以最后文件的内容即会更改为 pqrsjklmn。fd2 = dup(fd1);
那么此时fd2
将会指向fd1
的文件描述符,两个共享文件状态
dup用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1.
Write(fd2, "wxyz", 4);
所以当执行这一句时,光标的位置会和fd1
的文件位置是一样的,即字符s后面,所以字符也将从该位置开始写入。所以此时文件的内容为 pqrswxyzn。Write(fd3, "ef", 2);
此时fd3
依旧是往文件的末尾添加,最终文件的内容为 pqrswxyznef。