Linux-基础IO之文件描述符和重定向

系统调用函数

在了解文件描述符之前,先来认识几个系统接口,即系统调用函数
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也可以是一个或多个位掩码的或,为提供一些额外的提示

  • O_ CREAT :如果文件不存在,则创建一个新的空文件
  • O_TRUNC :如果文件存在,就截断它
  • O_APPEND :在每次写操作之前,设置文件位置到文件结尾处。

例如下面语句,以写的方式打开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);

Linux-基础IO之文件描述符和重定向_第1张图片
从图中看出,我们已经将内容写进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 

Linux-基础IO之文件描述符和重定向_第2张图片
从运行结果可以看出,执行该程序,我们就会看到刚刚写进myfile文件中的内容。
以上就是系统调用接口函数的简单运用。

下面我们来分析一下与这个几个函数息息相关的”fd”,

我们把” fd ”叫做文件描述符,通过上面的介绍,可以知道了文件描述符就是一个小整数。它的功能类似于C库中文件接口中的FILE *
来看下面这个图
Linux-基础IO之文件描述符和重定向_第3张图片

在操作系统里,每创建一个文件,操作系统就会为这个文件创建数据结构来描述目标文件即图中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;
}
~             

Linux-基础IO之文件描述符和重定向_第4张图片

可以看出,当未关闭0或者1时,fd是3,当关闭0时,fd就变成了1。验证了fd的分配规则。(过程如下图所示)
Linux-基础IO之文件描述符和重定向_第5张图片

在这里应该注意:一般情况下,file_struct数组下标的 0,1,2 分别被标准输入(键盘),标准输出(显示器),标准错误(显示器)占用,只有当调用close()函数,关闭掉某个下标 ,fd才会是0或1,或2.

其次,由于数组下标没有负数,所以fd也不会是负数。

所以,通过上面的介绍,可以知道,fd的本质其实就是一个表示数组下标的小整数。

文件描述符fd和FILE*区别和联系

文件描述符fd是在系统调用时标示文件的
FILE*是在库函数调用时标示文件的
库函数是对系统函数的封装。一般情况下,库函数基本能满足用户需求,但是系统调用比库函数权利更大。
下图是关于系统调用和库函数的层级关系图,二者关系一目了然。
Linux-基础IO之文件描述符和重定向_第6张图片

重定向

来看下面代码(注意不要和前面代码混在一起)

```

#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;
}

Linux-基础IO之文件描述符和重定向_第7张图片
本该输出到显示器的内容,却输出到了myfil中。这种现象就叫做输出重定向,当然还存在输入重定向。常见的重定向有

>   会删除原来的内容,写当下输出的内容
>>   直接在原文后面追加新内容
<      输入重定向

上面结果解释:
printf函数是C库当中的IO函数,本该往stdout输出,在执行了close(1),关闭了标准输出,并且它的打开文件表和v_node文件表也被关闭。当open在底层访问文件时,找到的还是fd:1.但此时fd:1所表示的内容,已经变成了myfile的地址,不再是显示器文件的地址,凡是应该写进标准输出文件的内容,全部被改写到myfile_1文件中
如下图所示

Linux-基础IO之文件描述符和重定向_第8张图片
我们还可以用dup2函数来实现重定向

#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

过程如图所示,与上图同理,不做赘述

Linux-基础IO之文件描述符和重定向_第9张图片

当然,还可以通过命令,来实现重定向

echo 源文件 > 目标文件

你可能感兴趣的:(操作系统,系统调用,文件描述符,重定向)