**
(一)open
**
进程是通过调用open函数来打开一个已存在的文件或者创建一个新文件的。
int open(char *filename,int flags,mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
1.flags参数指明了进程打算如何访问这个文件:
O_RDONLY | 只读 |
---|---|
O_WRONLY | 只写 |
O_RDWR | 可读可写 |
下面的代码说明如何以读的方式打开一个已存在的文件:
fd=open("foo.txt",O_RDONLY,0);
flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的指示:
O_CREAT | 如果文件不存在,就创建它的一个截断的(空)文件 |
---|---|
O_TRUNC | 如果文件已存在,就截断它 |
O_APPEND | 在每次写操作前,设置文件位置到文件的结尾处 |
下面的代码说明的是如何以读的方式打开一个已存在文件,并在后面添加一些数据:
fd=open("foo.txt",O_WRONLY|O_APPEND,0);
mode参数指定了新文件的访问权限位。
掩码 | 描述 |
---|---|
S_IRUSR | 使用者(拥有者)能够读这个文件 |
S_IWUSR | 使用者(拥有者)能够写这个文件 |
S_IXUSR | 使用者(拥有者)能够执行这个文件 |
S_IRGRP | 拥有者所在组的成员能够读这个文件 |
S_IWGRP | 拥有者所在的组成员能够写这个文件 |
S_IXGRP | 拥有者所在组的成员能够执行这个文件 |
S_IROTH | 其他人(任何人)能够读这个文件 |
S_IWOTH | 其他人(任何人)能够写这个文件 |
S_IXOTH | 其他人(任何人)能够执行这个文件 |
作为上下文的一部分,每个进程都有一个umask,它是通过调用umask函数来设置的。当进程通过带某个参数的open函数用来创建一个新文件时,文件的访问权限位被设置为mode&~umask。例如,假设我们给定下面的mode和umask默认值:
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_TROTH|S_IWOTH
#define DEF_UMASK S_IWGRP|S_IWOTH
接下来,下面的代码片段一个新文件,文件的拥有者有读写权限,而所有其他的用户都有读权限:
umask(DEF_UMASK);
fd=open(“foo.txt”,O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);
int main()
{
int fd1,fd2;
fd1=open("foo.txt",O_RDONLY,0);
close(fd1);
fd2=open("baz.txt",O_RDONLY,0);
printf("fd2=%d\n",fd2);
exit(0);
}
结论:fd2=3
在对fd1进行open时,fd1=3,进行close时,3被释放了
所以在对fd2进行open时,fd2=3了。
(二)read和write
读文件:ssize_t read(int fd, void *buf, size_t n);
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的实际传送的字节数量。
写文件:ssize_t write ( int fd, const void *buf , size_t n );
write函数从内存位置buf复制最多n个字节到描述符fd的当前文件位置。
(size_t被定义为unsigned long,而ssize_t(有符号的大小)被定义long)
(共享文件)
可以用许多不同的方式来共享Linux文件。内核用三个相关的数据结构来表示打开的文件:
描述符表 | 每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表指向文件表中的一个表项 |
---|---|
文件表 | 打开文件的集合是由一张文件表来表示,所有的进程共享这张表 |
v-node表 | 同文件表一样,所有的进程共享这张v-node表,每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员 |
下图展示了一个示例,其中描述符1和4通过不同的打开文件表表项来引用两个不同的文件。这是一种典型的情况,没有共享文件,并且每个描述符对应一个不同的文件。
图10-12
下图所示,多个描述符也可以通过不同的文件表表项来引用同一个文件。例如,如果以同一个filename调用open函数两次,就会发生这种情况。关键思想是每个描述符都有它自己的文件位置,所以对不同描述符的读操作可以从文件的不同位置获取数据。
图10-13
我们也能理解父子进程是如何共享文件的。假设在调用fork之前,父进程有如图10-12所示的打开文件。然后,图10-14展示了调用fork后的情况。子进程有一个父进程描述表的副本。父子进程共享相同的打开文件表集合,因此共享相同的文件位置。一个很重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了它们的描述符。
关于dup2:
int dup2(int oldfd,int newfd);
dup2函数复制描述表表项oldfd到描述表项newfd,覆盖描述表表项newfd以前的内容。如果newfd已经打开,dup2会在复制oldfd之前关闭newfd.
argv[1]=abcde
例子一
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl,h>
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
#include<stdio.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_RDNOLY,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;
}
对fd1来说使用open得到3,对fd2来说使用open得到4,对fd3来使用open得到5,然后使用dup2(fd2,fd3),实际上对fd3的操作就是读对fd2的操作。这时使用read,fd1读到了a,fd2读到了a,因为此时对fd3的操作相当于对fd2的操作,所以按照顺序fd3读到了b。
例子二
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl,h>
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
#include<stdio.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()){
sleep(s);
read(fd1,&c2,1);
printf("Parent:c1=%c,c2=%c\n",c1,c2);
}else{
sleep(1-s);
read(fd1,&c2,1);
printf("Child:c1=%c,c2=%c\n",c1,c2);
}
return 0;
}
因为没有将s打印出来,所以通过分析及视觉,子进程打印结束后,大概等待一秒,父进程才打印出来。,read在fork之前进行了第一次读,c1没有争议的读到了a,此时光标在‘a’后。子进程因为没有sleep,从而先比父进程先读,读到了b,此时光标在‘b’后。父进程sleep结束后,读取c2,也就读到了c。
例子三
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl,h>
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,const void *buf,size_t n);
#include<stdio.h>
int main(int argc,char *argv[])
{
int fd1,fd2,fd3;
char *fname=argv[1];
//创建一个名为fname的新文件,可读可写,在里面写入pqrs
fd1=open(fname,O_CREAT|O_TRUNC|O_RDWR,S_IRUSR|S_IWUSR);
write(fd1,"pqrs",4);
//以追加写的方式打开刚刚创建号的fname的文件,在里面写入jklmn
fd3=open(fname,O_APPEND|O_WRONLY,0);
write(fd3,"jklmn",5);
//dup,使之后对fd2的操作都变为对fd1的操作
fd2=dup(fd1);
write(fd2,"wxyz",4);
write(fd3,"ef",2);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
所以最后打印出来pqrswxyznef