在了解文件描述符之前,先来认识几个系统接口,即系统调用函数
open(),close()
read() ,write()
进程是通过调用open函数来打开一个已经存在的文件或创建一个不存在的文件:
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
返回值:成功返回文件描述符,失败返回-1
open函数将pathname转换成一个文件描述符,并且返回描述符数字。描述符数字总是进程中当前没有打开的最小描述符。
flags指明进程准备如何访问这个文件
- O_RDONLY :只读
- O_WRONLY :只写
- O_RDWR :可读可写
例如下面语句,以读的方式打开text.c文件
int fd=open("text.c",O_RDONLY,0);
flags也可以是一个或多个位掩码的或,为写提供一些额外的提示
例如下面语句,以写的方式打开text.c文件,如果不存在,则创建该文件
int fd=open("text.c",O_WRONLY|O_CREAT,0);
mode参数指定新文件的访问方式,但是会受到umask()函数的限制。每个进程都有一个umask,它通过调用umask函数来设置的。当进程通过带某个mode参数的open函数创建新文件,文件的权限就被设置为:mode& ~umask.,
最后,进程是通过调用close函数来关闭一个已经打开的文件
#include
int close(fd);
返回值:成功返回0.失败返回-1
注意:关闭一个已经关闭的描述符会出错
关于open,close的简单运用。注意fd2的值,后面会讲解
#include
#include
#include
#include
#include
int main( )
{
int fd1=open(" mytext.c",O_RONLY);
close(fd1);
int fd2=open("mytext.c",O_RONLY);
printf("fd2=%d\",fd2);
close(fd2);
return 0;
}
下面是运行结果
[zyc@localhost file_io]$ ./a.out
fd2=3
读和写文件
#include
ssize_t read(int fd, const void *buf, size_t count);
返回值:成功返回读的字节数,若EOF则为0,失败返回-1
ssize_t write(int fd, const void *buf, size_t count);
返回值:成功返回写的字节数,失败返回-1
参数解释:
fd:文件描述符,一般是open函数的返回值结果
buf: 内存缓冲区
size_t count :需要写/读的内容大小
read函数从文件描述符fd的当前位置复制最多n个字节到内存位置buf,返回值-1表示读取失败,而返回值0表示遇到EOF,否则返回实际传送的字节数。
write函数从内存位置buf复制n个字节到描述符fd的当前位置。
下面程序演示read和write函数运用
hello.c写文件
#include
#include
#include
#include
#include
#include
int main ()
{
umask(0);
int fd=open("myfile",O_WRONLY|O_CREAT,0644); //打开文件
if(fd<0)
{
perror("open");
return 1;
}
int count=5;
const char* msg="hello world\n";
int len=strlen(msg);
while(count--)
{
write(fd,msg,len); //调用系统接口往文件里写内容
}
close(fd);
从图中看出,我们已经将内容写进myfile文件中了,下面我们在调用系统接口read,读出myfile文件中的内容
hello.c读文件
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 int main ()
8 {
9 int fd=open("myfile",O_RDONLY); //打开文件
10 if(fd<0)
11 {
12 perror("open");
13 return 1;
14 }
15
16 const char* msg="hello world\n";
17 char buf[1024]; //定义 一个缓冲区
18 while(1)
19 {
20 ssize_t s=read(fd,buf,strlen(msg));//read读文件
21 if(s>0)
22 {
23 printf("%s",buf);
24 }
25 else
26 break;
27 }
28
29 close(fd);
30 return 0;
31 }
32
从运行结果可以看出,执行该程序,我们就会看到刚刚写进myfile文件中的内容。
以上就是系统调用接口函数的简单运用。
下面我们来分析一下与这个几个函数息息相关的”fd”,
我们把” fd ”叫做文件描述符,通过上面的介绍,可以知道了文件描述符就是一个小整数。它的功能类似于C库中文件接口中的FILE *
来看下面这个图
在操作系统里,每创建一个文件,操作系统就会为这个文件创建数据结构来描述目标文件即图中file,于是有了file结构体表示已经打开文件对象,当执行一个进程,调用open函数,打开文件,就必须让进程和文件关联起来。而在操作系统中可不止一个进程,一个文件,是存在很多进程和文件,为了方便管理,操作系统又创建了一个字符指针数组,作为进程和文件沟通的桥梁,也就是图中的file_struct,该数组的每个元素都是一个字符指针,通过该指针,就可以找到对应的文件。比如myfile文件。
中间桥梁已经构架好了,现在就需要将进程与桥梁连接起来。
于是操作系统又在进程的task_struct中设置了一个*files指针,指向file_struct。*file指向字符指针数组组的那个下标,就会找到该下标对应字符指针所指向的文件。而这里的下标,也就是我们的文件描述符。
文件描述符分配规则:在 file_struct数组中,找到当前没有被使用的最小下标,作为一个新的文件描述符。
下面来举例证明:
#include
#include
#include
#include
int main()
{
//close(0);
//close(1);
int fd=open("myfile",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
~
可以看出,当未关闭0或者1时,fd是3,当关闭0时,fd就变成了1。验证了fd的分配规则。(过程如下图所示)
在这里应该注意:一般情况下,file_struct数组下标的 0,1,2 分别被标准输入(键盘),标准输出(显示器),标准错误(显示器)占用,只有当调用close()函数,关闭掉某个下标 ,fd才会是0或1,或2.
其次,由于数组下标没有负数,所以fd也不会是负数。
所以,通过上面的介绍,可以知道,fd的本质其实就是一个表示数组下标的小整数。
文件描述符fd是在系统调用时标示文件的
FILE*是在库函数调用时标示文件的
库函数是对系统函数的封装。一般情况下,库函数基本能满足用户需求,但是系统调用比库函数权利更大。
下图是关于系统调用和库函数的层级关系图,二者关系一目了然。
来看下面代码(注意不要和前面代码混在一起)
```
#include
#include
#include
#include
int main()
{
close(1);//关闭标准输出
int fd=open("myfile1",O_WRONLY|O_CREAT,0644);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
本该输出到显示器的内容,却输出到了myfil中。这种现象就叫做输出重定向,当然还存在输入重定向。常见的重定向有
> 会删除原来的内容,写当下输出的内容
>> 直接在原文后面追加新内容
< 输入重定向
上面结果解释:
printf函数是C库当中的IO函数,本该往stdout输出,在执行了close(1),关闭了标准输出,并且它的打开文件表和v_node文件表也被关闭。当open在底层访问文件时,找到的还是fd:1.但此时fd:1所表示的内容,已经变成了myfile的地址,不再是显示器文件的地址,凡是应该写进标准输出文件的内容,全部被改写到myfile_1文件中
如下图所示
#include
int dup2(int oldfd,int newfd)
返回值:成功返回非负的描述符,失败返回-1
mytext.c文件内容:asd
下面是该函数运用举例
#include
#include
#include
#include
#include
int main( )
{
int fd1,fd2;
char c;
fd1=open("mytext.c",O_RDONLY,0);
fd2=open("mytext.c",O_RDONLY,0);
read(fd2,&c,1);
dup2(fd2,fd1);
read(fd1,&c,1);
printf( "c=%c\n",c );
return 0;
}
输出结果:
[zyc@localhost file_io]$ ./a.out
c=s
过程如图所示,与上图同理,不做赘述
当然,还可以通过命令,来实现重定向
echo 源文件 > 目标文件