• Linux下的文件描述符与文件指针及其区别


    文件描述符

    在Linux系统中一切皆文件。如果要对某个设备进行操作,就不得不打开此设备文件,只要你打开文件就会获得该文件的文件描述符fd(file discriptor),这个文件描述符就是一个整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。 如下图所示。
    这里写图片描述
    图中,文件描述符即为文件描述符数组的下标。
    文件描述符的分配规律:从当前未使用的最小的整数处开始分配 。比如说如果你打开一个文件系统会自动为它打开三个文件,分别是stdin,stdout,stderr,就是标准输入,标准输出,标准输出。他们的文件描述符分别是 0,1,2,也就是说当你的文件打开时它的文件描述符就从3开始分配了,如果你把那三个文件关闭一个,例如,关掉标准输入,则打开的文件的文件描述符将会填上0,让后再往后分配。

    文件描述符的优点:
    兼容POSIX标准,许多Linux和UNIX系统调用都依赖于它。

    文件描述符的缺点:
    不能移植到UNIX以外的系统上去,也不直观。

    概括:
    每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针。已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
    fd为打开文件的文件描述符,而每个进程都有一张文件描述表,fd文件描述符就是这张表的索引,同样这张表中有一表项,该表项又是指向前面提到打开文件的file结构体,file结构体才是内核中用于描述文件属性的结构体。

    file结构体中的成员:
    缓冲区基址,缓冲区当前指针,缓冲区大小,缓冲区剩余字节个数,文件读写模式等。

    struct   FILE   { 
    char   *_ptr;//文件输入的下一个位置 
    int    _cnt; //当前缓冲区的相对位置 
    char   *_base;//指基础位置(应该是文件的其始位置) 
    int    _flag; //文件标志 
    int    _file; //文件的有效性验证 
    int    _charbuf;//检查缓冲区状况,如果无缓冲区则不读取 
    int    _bufsiz; //文件的大小 
    char   *_tmpfname;//临时文件名 
    }; 
    

    对文件操作的一些函数

    打开文件:FILE *fopen(const char *path,const char* mode)
    关闭文件:int fclose(FILEE *fp)
    读文件:size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
    写入文件:size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE* stream)
    //这些函数都是通过流的形式来进行的,也就是缓存区,下面会详细说到缓冲机制。
    

    fopen来说,函数的返回值是FILE*,这是一个文件指针类型, 第一个参数是文件的路径,第二个参数是打开的方式。

    “r”:打开只读文件,若不存在,则会报错。
    “w”:打开只写文件,如果文件内有内容则会被直接清空,若不存在,则会自动创建。
    “a”:以追加方式打开只写文件,若文件不存在,则会自动创建,如果文件存在,则在内容的最后进行写入。
    诸如"a+","w+","r+"则都是以可读写的方式,不在赘述
    “b”:以二进制的方式。
    例:“ab+” 读写打开一个二进制文件,允许读或在文件末追加数据

    缓冲区

    缓冲机制有三种
    1.行缓冲:遇到 换行即从缓冲区刷新
    2.全缓冲:当缓冲区数据填满时刷新
    3.无缓冲:直接将数据输出,不经过缓冲区
    缓冲机制是可以更改的,你可以调用setbuf()和setvbuf()函数来进行。下面用代码实现一下缓冲机制

    #include<stdio.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    int main()
    {
        char* msg = "I am fwrite
    ";
        char* msg1 = " I am write
    ";
    
        printf("I am Printf
    ");
        fwrite(msg,1,strlen(msg),stdout);
        write(1,msg1,strlen(masg1));
    
        pid_t id = fork();
        if(id < 0)//fork error;
        {
            printf("fork error!");
            return;
        }
    
        else(id == 0)//child
        {
            printf("I am child,pid:%d
    ",getpid()); 
        }
    
        else//father
        {
            printf("I am father,pid:%d
    ",getpid());
            sleep(3);
        }
        return 0;
    }

    运行结果如下图
    这里写图片描述

    和我们想的一样,输出五句话,等待两秒钟后,程序退出。
    那么如果把运行结果写入到文件里呢?以下是运行结果。
    这里写图片描述

    可以看到多出了两句话,那么为什么会多出来呢? 那我们就要了解文件的操作。文件的操作分为两种:

    1.流式文件操作:

    fopen() 打开流
    fclose() 关闭流
    fputc() 写一个字符到流中
    fgetc() 从流中读一个字符
    fseek() 在流中定位到指定的字符
    fputs() 写字符串到流
    fgets() 从流中读一行或指定个字符
    fprintf() 按格式输出到流
    fscanf() 从流中按格式读取
    feof() 到达文件尾时返回真值
    ferror() 发生错误时返回其值
    rewind() 复位文件定位器到文件开始处
    remove() 删除文件
    fread() 从流中读指定个数的字符
    fwrite() 向流中写指定个数的字符
    tmpfile() 生成一个临时文件流
    tmpnam() 生成一个唯一的文件名

    直接I/O文件操作

    open() 打开一个文件并返回它的句柄
    close() 关闭一个句柄
    lseek() 定位到文件的指定位置
    read() 块读文件
    write() 块写文件
    eof() 测试文件是否结束
    filelength() 取得文件长度
    rename() 重命名文件
    chsize() 改变文件长度
    直接的I/O是不经过缓冲区的。还有就是当数据直接往显示器输出时,采用行缓冲,而一旦将数据写入文件,则由行缓冲变为全缓冲。

    下面来分析一下上面的那个程序中为什么把运行结果写到文件中它就多出了两句话。
    1.write不经过缓冲区,所以首先被打印出来(虽然程序中先定义printf和fwrite)
    2.由于printf和fwrite是全缓冲,所以此时这两个输出结果在缓冲区中
    3.fork()之后,产生子进程,代码共享,数据各自持有一份
    4.fork()结束,父子进程各自刷新,都会执行一次缓冲区中的数据
    所以子父进程都打了一遍,所有就多了两句话。

    文件指针

    文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。

    通常的,任何程序运行起来之后都会默认打开三个标准输入输出流:标准输入流(键盘)、标准输出 流(显示器),标准错误流(显示器)。
    相应C语言的文件指针:stdin,stdout,stderror

    文件描述符与文件值针的区别

    fd只是一个整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。
    open:文件描述符的操作(如: open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用。
    fopen:流(如: fopen)返回的是一个文件指针(即指向FILE结构体的指针), FILE结构是包含有文件描述符的,fopen可以看作是对open(fd直接操作的系统调用)的封装, 它的优点是带有I/O缓存。

    C语言中文件指针与文件描述符的相互转换

    可通过fdopen和fileno两个函数实现。它们都包含在头文件stdio.h中。

    //fdoppen 函数的原型
    FILE * fdopen(int filedes,const char *opentype);

    第一个参数filedes是一个打开的文件描述符,opentype是表示打开方式的字符串,和fopen函数具有相同的取值,比如”w”或”w+”等。但是你必须保证该字符串的描述和文件实际的打开方式是匹配的。函数fopen()就是返回打开文件的指针;如果操作失败,返回空指针null。

    把文件流指针转换成文件描述符用fileno函数

    //fileno 函数的原型
    int fileno (FILE *stream);

    它返回和stream文件流对应的文件描述符。如果失败,返回-1。

  • 相关阅读:
    oracle中的事务
    delect 删除
    update更新修改数据
    insert插入数据
    复制表、复制表结构、复制数据
    子查询
    分组函数
    C++实现红外Fir谱图文件转BMP图片文件
    windows下安装mysql-5.7.20-winx64
    数据库设计——数值类型
  • 原文地址:https://www.cnblogs.com/chan0311/p/9427351.html
Copyright © 2020-2023  润新知