Linux---文件描述符

关于文件的一些概念

  1. 文件包含文件内容和文件属性
  2. 对文件的操作包括对文件内容的操作和对文件属性的操作,也有可能同时进行对文件内容和文件属性的操作
  3. 打开文件这个动作是指的将文件的属性和内容加载到内存中
  4. 磁盘上并不是所有的文件都会被加载到内存中
  5. 对文件的理解可以分为内存文件和磁盘文件
  6. 通常当我们在对文件进行操作(打开文件,访问文件....)时,实质上是程序加载到内存中变成的进程对文件进行操作
  7. 这里讨论的就是进程和“打开文件”的关系

C语言中的文件操作函数实际上是对系统接口的封装,在向文件写入数据时是对磁盘内进行写入,只有操作系统有资格向硬件磁盘写入。上层(用户)如何调用操作系统呢?要通过相应的系统接口!平时我们没有用到系统接口是因为C语言的函数都经过了封装。

为什么要进行封装?因为如果在程序中直接进行系统调用(linux环境),将代码放入到windows环境下就不会兼容。不具有跨平台特性。

为了更好地了解底层原理,所以我们有必要学一学文件级别的系统接口

1.系统接口

 open

返回值小于0,打开失败。 

flags代表打开方式,就像C语言中的r,w等等。不过这里要特殊一些。

O_RDONLY(只读), O_WRONLY(只写), O_RDWR(读写),O_APPEND(读写),O_CREAT(没有文件创建文件),O_TRUNC(如果原来文件中有内容,将内容全部清空)等,他们实际上都是宏,是系统传递表计位,是用位图结构进行传递的,每一个宏标记,一般只需要一个比特位是1,并且和其他的宏对应的标记不能相同如下例。所以他们也可以是或的形式。

mode代表文件初始权限。如果被打开的文件是第一次出现,那么要给上初始权限。否则文件的显示权限部分会出现乱码(不等于最终权限)

#include<stdio.h>
#define O_A 0b0001
#define O_B 0b0010
#define O_C 0b0100
#define O_D 0b1000

int main()
{
    int a = 1;
    if(a==O_A)
        printf("O_A");
    else if(a==O_B)
        printf("O_B");
    else if(a==O_C)
        printf("O_C");
    else
        printf("O_D");
}
int fd = open("text.txt",O_RDONLY)//只读
int fd = open("text.txt",O_WRONLY)//只写
int fd = open("text.txt",O_RDWR)//读写
int fd = open("text.txt",O_RDONLY|O_CREAT)//只读,如果没有文件创建文件
int fd = open("text.txt",O_RDONLY|O_WRONLY|O_CREAT)//读写,如果没有文件创建文件

int fd = open("text.txt",O_RDONLY|O_CREAT,0777);//初始权限给的是0777

write 

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
   int fd = open("log.txt",O_RDWR|O_CREAT,0666);
   if(fd<0)
   {
    perror("open");
    return 1;
   }

   const char* str = "hello world\n";
   write(fd,str,strlen(str));                                                                                                                                                         
   printf("%d\n",fd);

   close(fd);
   return 0;
}
//这里可以把字符串中的'\0'作为结束标志写入到文件中吗?答案是否定的,因为只有在C语言中
//'\0'是字符串结束的标志,在其他的打开方式下,'\0'可能会被识别成其他的字符!

read

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd = open("log.txt",O_RDWR|O_CREAT,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    char str[128];
    ssize_t s= read(fd,str,127);
    if(s>0)
    {
        str[s] = '\0';
        printf("%s",str);                                                                                                                                                                
    }
    close(fd);
    return 0;
}

 2.文件描述符

 open函数的返回值fd就是文件描述符。每次打开函数后,fd都是从3开始的,那么0,1,2呢?这里可以直接说明:0是标准输入(键盘),1是标准输出(显示器),2是标准错误(显示器),每次都是默认打开的。不禁联想到C语言中的FILE* stdin,FILE* stdout,FILE* stderr。

 验证:

 linux下一切皆文件

被打开的文件在内存中,进程也在内存中。所以系统在运行过程中,会存在大量被打开的文件。操作系统要对这些文件进行管理。一个进程可以打开多个文件,那么操作系统如何让进程和其打开的文件映射起来呢?

 不要有疑问,0,1,2->stdin,stdout,stderr,键盘,显示屏,虽然都是硬件,但是在linux中也可以作为文件管理。是怎样作为文件使用的呢?

文件描述符的分配规则

遍历files_struct的数组,找到一个最小的且未被使用的下标,分配给新的文件。

重定向

引入

先看一个有趣的代码:

#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
int main()
{
    close(1);

    int fd = open("log.txt",O_RDWR|O_CREAT,0666);
    if(fd < 0)
    {
        perror("open");
        return 0;
    }
    fprintf(stdout,"这是一个进程");
    fflush(stdout);                                                                                                                                                                      
    close(fd);
}

最后它并不会在显示器上打印“这是一个进程”,而是将其写入到log.txt文件中。 那么为什么会出现这种现象?那是因为发生了重定向,本来应该是向某个文件输入,但是输入到另一个文件。

dup函数 

上图中的所有数据都是内核数据结构,只有(系统调用)操作系统才有权限提供接口修改内核数据结构。最常用的就是dup函数。

比较常用的就是dup2函数,oldfd为原来文件的描述符,newfd为重定向输出的文件描述符。

#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
int main()
{

    int fd = open("log.txt",O_RDWR|O_CREAT,0666);
    if(fd < 0)
    {
        perror("open");
        return 0;
    }

    //和上面的代码是等效的
    dup2(fd,1);

    fprintf(stdout,"这是一个进程");
    fflush(stdout);                                                                                                                                                                      
    close(fd);
}

理解缓冲区

缓冲区在哪?缓冲区并不在系统提供的接口中,缓冲区是由C语言提供的!被封装到了FILE中。

 运行后会发现先打印的是hello write,过了5秒才打印了hello printf,因为hello printf首先被加载到了缓冲区,等到进程结束后才打印到了显示器上,而hello write则是直接被打印了出来,说明系统提供的接口write是没有缓冲区的。缓冲区的存在可以集中处理数据刷新,减少IO的次数,可以提高整机的效率。

在缓冲区刷新之前,关闭文件描述符会怎样?都关闭了门口了,肯定是打印不出来了呀!

什么时候刷新?

常规:无缓冲(立即刷新);行缓冲(逐行刷新)显示器文件;全刷新(缓冲区满,刷新)块设备对应的文件,磁盘文件。

特殊:进程退出;用户强制刷新(fflush(*****))

缓冲区在FILE内部,在C语言中,我们每一次打开一个文件,都要有一个FILE*返回,每个文件都有一个fd和属于他自己的语言级别缓冲区!

为什么会出现这种情况呢?要知道刷新的本质是把缓冲区的数据write到OS内部,清空缓冲区!缓冲区是由自己的FILE内部维护的,是属于父进程内部的数据区域!前面的write是OS直接写到stdout的,所以直接打印。而fprint等是语言级别的接口,他们要先将数据写入到属于自己FILE的缓冲区内等待刷新,而最后的fork()产生了子进程,这时他们的数据代码都是共享的,当父进程结束时,要刷新缓冲区,这时父进程缓冲区的数据要被清除,所以子进程发生写时拷贝要将原来共享的数据(缓冲区中的数据)拷贝到新的区域中,当父进程结束后它的缓冲区内容被刷新到显示器上,随后子进程结束也被刷新到显示器上。

标准错误和标准输出的区别

标准错误和标准输出都是向显示器打印,有什么区别呢?标准错误和标准输出是通过不同的文件描述符打印到显示器上的!

  

在发生重定向时,没有说明默认就是重定向标准输出,如果要重定向标准错误则要加上2:

 意义在哪里?可以区分程序中的日常信息和错误信息!方便查找错误。cout和cerr,printf和perror要分开使用。

 如果不想分开这两个内容,想重定向到一个文件中:

 先将1重定向到all.txt中,再把1的地址给2,那么2也指向1所指的文件,2也完成了重定向。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_w_z_j_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值