文件描述符 文件句柄 重定向

 

一文件相关的系统调用

C语言标准库函数中有对文件的操作

fopen(),fwrite(),fread(),fclose();等

 

FILE *fd=fopen("./test.txt","w");                                                                                                                      
      if(fd==NULL)
      {   
          perror("fopen");
          return 1;
      }   
      char * str=(char *)"hello world";
      fwrite(str,strlen(str),1,fd);
      fclose(fd);

操作系统中对文件的操作有系统调用

open(),write(),read(),cloes()等

 

事实上C语言中的文件操作函数是通过系统调用来实现的。

当我们打开一个文件时,系统会为该文件分配一个文件描述符,这个文件描述符是一个比较小得整数

当操作系统创建一个进程时,会先对这个进程先进行描述,在进行组织

对进程进行描述是用一个结构体,称为进程控制块(PCB),对进程进行组织是用一个链表将这些进程控制块链在一起

 

一个进程中可以打开多个文件,同样,操作系统对文件也是先进行描述,再进行组织

文件描述也是用一个结构体来,对其进行组织也是用一个链表将其链上去

 

下面是一个简单的例子

int fd=open("test.txt",O_WRONLY | O_CREAT,0644);                                                                                                         
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    char * str=(char *)"hello world";
    printf("fd:%d\n",fd);
    write(fd,str,strlen(str));
    close(fd);

 

这里的文件描述符打印出来是3

这是因为操作系统会为进程默认打开三个文件,分配三个文件描述符0,1,2,分别是标准输入,标准输出,标准错误。

文件描述符默认从空闲的文件描述符中最小的开始分配

close(0);
    close(2);
    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    int fd_2=open("test2.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0||fd_2<0)
    {   
        perror("open");exit(1);
    }   
    //这里默认从文件描述符中找最小的                                                                                                                         
    printf("fd_1:%d\n",fd_1);
    printf("fd_2:%d\n",fd_2);

操作系统中关于文件的相关系统调用

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

int open(const char *pathname, int flags, mode_t mode);

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);
 

这里注意read()函数

返回值sszie_t 是一个有符号长整型,返回值大于0时,表示本次实际读的字符个数,小于0时表示读取失败,等于0时表示读到文件的结束标志EOF(在Linux下输入时按下Ctrl+D可以获取,windows下按下Ctrl+Z可以获取)

说了这么多次文件描述符,那么文件描述符到底是什么呢

files_struct结构体里面的内容:

files_struct结构保存了进程打开的所有文件表数据,描述一个正被打开的文件。Linux中一个进程最多只能同时打开NR_OPEN_DEFAULT个文件,而且前三项分别设为标准输入、标准输出和出错信息输出文件,定义如下:

struct files_struct {  
    atomic_t        count;              //自动增量  
    struct fdtable  *fdt;  
    struct fdtable  fdtab;  
    fd_set      close_on_exec_init;     //执行exec时 需要关闭的文件描述符初值集合  
    fd_set      open_fds_init;          //当前打开文件 的文件描述符屏蔽字  
    struct file         * fd_array[NR_OPEN_DEFAULT];  
    spinlock_t      file_lock;  /* Protects concurrent writers.  Nests inside tsk->alloc_lock */  
}; 

file结构体:

文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。在内核创建和驱动源码中,struct file的指针通常被命名为file或filp

 

二、重定向

看下面代码:

    close(1);                                                                                                                                                
    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0)
    {
        perror("open");
        exit(1);
    }
    //这里默认从文件描述符中找最小的,所以这里分配的文件描述符为1
    printf("fd_1:%d\n",fd_1);
    //因为printf()函数底层实现还是调用了系统调用fprintf();
    //fprintf(stdout,"%d\n",fd_1)
    //所以这里其实是将内容输出重定向到文件描述符为1的文件中、
    //那么这里就不会看到标准输出上与内容输出
    //而是在我们打开的文件中

    int fd_1=open("test1.txt",O_WRONLY|O_CREAT,0644);
    if(fd_1<0)
    {
        perror("open");exit(1);
    }
    //这里默认从文件描述符中找最小的,所以这里分配的文件描述符为1
    printf("fd_1:%d\n",fd_1);
    //因为printf()函数底层实现还是调用了系统调用fprintf();
    //fprintf(stdout,"%d\n",fd_1)
    //所以这里其实是将内容输出重定向到文件描述符为1的文件中、
    //那么这里就不会看到标准输出上与内容输出
    //而是在我们打开的文件中

 

 

    //修改文件句柄,完成重定向                                                                                                                                 
    //dup2(int oldfd,int newfd);这个函数的作用是用newfd是覆盖了oldfd
    //对newfd文件的操作其实就是oldfd的操作。
    int fd=open("test.txt",O_CREAT | O_WRONLY,0644);
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    dup2(fd,1);
    printf("fd : %d\n",fd);//就是相当于对文件描述符为1中的问价写其实就是对文件描述符为fd的文件进行操作
    const char * str="nihao shijie\n";
    write(1,str,strlen(str));
    close(fd);
//修改文件句柄,完成重定向                                                                                                                                 
    //dup2(int oldfd,int newfd);这个函数的作用是用newfd是拷贝覆盖了oldfd
    //相当于对newfd文件的操作其实就是oldfd的操作。
    int fd=open("test.txt",O_CREAT | O_WRONLY,0644);
    if(fd<0)
    {   
        perror("open");exit(1);
    }   
    dup2(fd,1);
    printf("fd : %d\n",fd);//就是相当于对文件描述符为1中的问价写其实就是对文件描述符为fd的文件进行操作
    const char * str="nihao shijie\n";
    write(1,str,strlen(str));
    close(fd);

 

//三种输出“hello world”

//多种打印hello world                                                                                                                                       
   const char * str="hello world\n";
   printf("%s",str);
   write(1,str,strlen(str));
   fprintf(stdout,"%s",str);

 

当我们在函数结束的时候创建一个子进程呢?

    const char * str_printf="str_printf\n";
    const char * str_fprintf="str_fprintf\n";                                                                                                                
    const char * str_write="str_write\n";
                 
    printf("%s",str_printf);
    fprintf(stdout,"%s",str_fprintf);
    write(1,str_write,strlen(str_write));

    fork(); 

 

缓冲区刷新格式:

1.无缓冲(系统调用,write)

2.行缓冲  (显示器)按行刷新换缓冲区

3.全缓冲(文件)缓冲区满了才会进行刷新

当然每一个进程结束会刷新缓冲区

当我们在标准输出上进行输出时是按行刷新自己的缓冲区的,其实是没有什么不同的,因为这里的字符串后面都加了'\n',会自己刷新缓冲区,创建一个子进程后,子进程的缓冲区中为空

因为输出到一个文件中时是一种全缓冲(等到缓冲区满了才进行刷新)

所以当将其输出重定向到一个文件中的话,是一个全缓冲,父进程将字符串输出后,并没有进行刷新,字符串仍然在父进程的缓冲区中,创建一个子进程的话,子进程会将父进程发数据进行拷贝,那么子进程的缓冲区中就会有和父进程同样的字符串,结束时刷新缓冲区,字符串就会被刷新到目标文件,所以会出现两遍printf()和fprintf()的内容打印了两遍。write()是属于系统调用,没有缓冲区,直接输出,所以是一遍。

上面两个printf()函数和fprintf()函数底层都是调用系统调用write(),但是系统调用在用户态是没有缓冲区的,所以上面说的缓冲区缓冲区是C库提供的

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值