?
日期:2019/3/24
内容:Linux文件描述符。
?
一、基本概念
一个非负整数。应用程序利用文件描述符来访问文件。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
二、文件读写
sys/stat.h |
status |
fcntl.h |
file control |
?
必须取值:O_RDONLY、O_WRONLY、O_RDWR。
可选取值(两种取值通过 | 组合)
O_APPEND |
每次写操作都写入文件的末尾 |
O_CREAT |
如果指定文件不存在,则创建这个文件 |
O_EXCL |
如果要创建的文件已存在,则返回-1,并且修改errno的值 |
O_TRUNC |
如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)(truncate,截断) |
O_NOCTTY |
如果路径名指向终端设备,不要把这个设备用作控制终端。 |
O_NONBLOCK |
如果路径名指向FIFO/块文件/字符文件,则把文件的打开和后继I/O |
?
参????数 |
说????明 |
参????数 |
说????明 |
S_IRUSR |
所有者拥有读权限 |
S_IXGRP |
群组拥有执行权限 |
S_IWUSR |
所有者拥有写权限 |
S_IROTH |
其他用户拥有读权限 |
S_IXUSR |
所有者拥有执行权限 |
S_IWOTH |
其他用户拥有写权限 |
S_IRGRP |
群组拥有读权限 |
S_IXOTH |
其他用户拥有执行权限 |
S_IWGRP |
群组拥有写权限 |
? |
? |
(数字表示法,例如chmod -r dir 777,777表示最高权限)
int open(char *path,int flags,mode_t mode); |
·mode是文件权限标志
·返回值:成功则返回文件描述符,否则返回-1
|
int openat(int dirfd,const char *pathname,mode_t mode); |
·用到时再补充 |
int read(int fd,void *buf,size_t size); |
·返回值成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。
·注意判断返回值与参数size的大小关系
|
int write(int fd,size_t size); |
·返回实际写入的字节数,当有错误发生时则返回-1。 |
?
三、内核与文件管理
-
索引节点(Index Node,inode)
>>索引节点保存了文件的元信息数据,包括:文件大小,磁盘位置,创建和修改时间,所有者,访问权限。
-
file结构体
>>一个file结构体表示一个打开的文件。
>>其包含的信息:对应的inode,文件当前的访问位置(即读写位置),打开模式,etc
-
文件描述符表
>>实质是一个数组,数组的元素类型是指针,指针指向一个file结构体。上文所述fd是数组下标。
>>用于保存被打开的文件。
>>内核打开文件时,分配一个file结构体表示被打开的文件,将该file结构体指针保存在文件描述符表中。
- 打开文件的过程
- 找到文件对应的索引节点inode
- 分配一个file结构体
- file结构体的inode字段指向第1步的inode
- file结构体的文件访问位置字段初始化为0
- 从文件描述符表中找一个空闲项,指向第2步的file结构体,返回该空闲项在数组中的的下标,即fd
|
?

|
?
-
进程控制块(PCB)
>>进程控制块是操作系统表示进程状态的数据结构。
>>存放用于描述进程情况及控制进程运行所需的全部信息。包括:进程标识信息(PID)、处理机状态、进程调度信息、打开文件列表(即上文所述的文件描述符表,记录进程打开的文件)
>>私有的文件描述符表
>>>>文件描述符表对进程来说是私有的: 每个进程都有一个私有的文件描述符表; 操作系统有N个进程,则对应有N张文件描述符表。
>>>>两个进程打开不同的文件,文件描述符可能是相同的。进程A打开文件a.txt,open返回值是3;进程B打开文件b.txt,open返回值也可能是3。
>>实例代码
aopen.c |
bopen.c |
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
????int fd = open("aopen.c",O_RDONLY);
????printf("a fd = %dn",fd);
????close(fd);
????return 0;
}
|
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
????int fd = open("bopen.c",O_RDONLY);
????printf("b fd = %dn",fd);
????close(fd);
????return 0;
}
|

|
?
-
三个标准文件(stdin、stdout、stderr)
>>一切皆文件
>>stdin:标准输入文件,通常对应终端的键盘,进程的文件描述符表第0项
>>stdout:标准输出文件,通常对应终端屏幕,进程的文件描述符表第1项
>>stderr:标准错误输出文件,通常对应终端屏幕,进程的文件描述符表第2项
>>如果关闭2号文件stderr(即close(2)),再打开一个新文件fd,那么fd=2。
>>实例代码
int main()
{
????char buf[256] = { 0 };
????int len = read(0,buf,sizeof(buf));
????write(1,len);
????return 0;
}
|

|
?
四、重定向
-
系统调用fork
>>创建一个子进程:为子进程创建一个独立的地址空间;为子进程创建一个独立的文件描述符表。
>>子进程复制父进程属性:代码段、数据段的内容;文件描述符表。
-
系统调用dup
>>原型:int dup(int oldfd);
>>功能:通过复制文件描述符oldfd,创建一个新的文件描述符newfd。newfd和oldfd指向相同的文件。
>>返回值:成功newfd,失败-1(如oldfd不是有效的描述符时)
>>成功后,newfd与oldfd不会共享fd_flags(例如the close-on-exec flag,close(newfd)之后,oldfd依然有效)
>>代码与图解
代码 |
dup前 |
dup后 |
int main()
{
????int testfd = 6;
????int newtestfd = dup(testfd);
????printf("newtestfd = %dn",newtestfd);
?
????int oldfd = 2;
????const char *str = "Hello World from str...n";
????int newfd = dup(oldfd);
????write(newfd,str,strlen(str));
????close(newfd);
????write(oldfd,strlen(str));
????return 0;
}
|
?

|
?

|

|
?
-
系统调用dup2
>>原型:int dup2(int old,int new)
>>功能:用new指定新描述符的值,如果new本身已经打开了,则会先将其关闭再将new指向old,最后返回new。如果old等于new,则返回new,并不关闭它。(简记:关new指old,可用于输出的重定向)
>>返回值:成功返回新复制的fd(即newfd),失败-1。如果old无效,返回-1。
>>重定向实例代码(将stderr重定向到文件log)
代码 |
图解 |
int main()
{
????int fd = open("log",O_RDWR | O_CREAT);
????dup2(fd,2);
????char *str = "Hello World...n";
????write(2,strlen(str));
????close(fd);
????return 0;
}

|

|
?
int main()
{
????int fd[2] = {-1,-1};
????pipe(fd);
????const char *str = "Hello world...n";
????char buf[256];
????printf("read = %d,write = %dn",fd[0],fd[1]);
????write(fd[1],strlen(str));
????read(fd[0],255);
????printf("buf = %sn",buf);
????close(fd[0]);
????close(fd[1]);
}

|
?
>>实例2(实现命令:cat pipe.c | wc)
int main()
{
????int fd[2];
????int pid;
????int status = 0;
????char buf[1024 * 2] = "";
????pipe(fd); //must be at the front of fork()
????pid = fork();
?
????if (pid == 0)
????{
????????dup2(fd[1],1); //stdout points to wirte pipe
????????close(fd[0]);
????????close(fd[1]);
????????execlp("cat","cat","pipe2.c",NULL);
????????exit(-1);
????}
?
????wait(&status);
????if(status != 0)
????????perror("execlp error...n");
?
????dup2(fd[0],0); //stdin points to read pipe
????close(fd[0]);
????close(fd[1]);
????//read(0,sizeof(buf) - 1);
????execlp("wc","wc",NULL);
????printf("Are program getting here?n");
????return 0;
}
运行结果

如果把父进程中execlp前面的read加上
 s
分析:
·pipe调用必须在fork之前,否则不会共享pipe
·父子进程中注意关闭不需要的fd[i](因为通常是一个进程读,一个进程写)
·pipe中buf的数据只能读取一次
|
图解:



|
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|