1、文件描述块儿

  我们从代码的角度看,文件描述块儿就是FILE结构体,当一个进程打开一个文件系统上的文件时,就是生成一个文件描述块儿信息,该信息除了要描述被访问的文件的基本信息(文件名称,路径,权限等等),更重要的是描述当前进程对文件的读写情况。

模型关系如下:


文件描述块内容在/usr/include/libio.h头文件里

其文件描述块内容如下:

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
  #define _IO_file_flags _flags
 
    /* The following pointers correspond to the C++ streambuf protocol. */
    /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
    char* _IO_read_ptr;   /* Current read pointer */
    char* _IO_read_end;   /* End of get area. */
    char* _IO_read_base;  /* Start of putback+get area. */
    char* _IO_write_base; /* Start of put area. */
    char* _IO_write_ptr;  /* Current put pointer. */
    char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

2、文件描述符

 一个进程可以打开多个文件,此时该进程中就会有多个文件描述块儿;文件描述符就是文件描述块儿在该进程中的唯一一个编号。通过该唯一编号就能代表文件描述块儿。所以,在Linux下提供了一套通过文件描述符操作文件读取的方法。

  文件描述符作为一个编号,就只是一个int类型的数据,其编号从0开始,依次向后累加,差值为1。

  每打开一个文件就会产生一个新的文件描述块儿,就会为其分配一个文件描述符;

  文件描述符的分配原则 : 要尽最大努力保持文件描述符的编号的连续性,之所以如此原则,是为了节省编号资源。

  当要关闭一个已经打开的文件时,对应的文件描述符就会被回收,打开新文件时,会寻找最小的可用的文件描述符(包括已经回收的文件描述符)。

  文件描述符与文件描述块儿是一对一的关系,在Linux下,对文件的读写操作都施加在文件描述符上。对文件描述符的操作,其实就等同于对文件描述块儿的操作(例如:使用文件描述符就可以实现对文件的读写操作)。

3、Linux下的I-O

  input和output,在Linux下所有的I/O设备都被抽象为设备文件(块儿设备文件和字符设备文件),通过对这些文件的读写操作,即就是对相关的设备的I/O操作。

  所以read表示输入,write表示输出。

4、标准输入、标准输出、标准错误输出

  任何一个进程启动时,在默认的情况下都会拥有已经打开的三个文件描述块儿,他们就是:标准输入、标准输出、标准错误输出

/* Standard streams.  */
extern struct _IO_FILE *stdin;      /* Standard input stream.  */
extern struct _IO_FILE *stdout;     /* Standard output stream.  */
extern struct _IO_FILE *stderr;     /* Standard error output stream.  */

  也同时对应三个不同的文件描述符,分别是0,1,2。

  标准输入、标准输出、标准错误输出都对应了相关的设备文件,这三个文件描述符儿都是打开的对应的设备文件。事实上在当前的Linux的网络环境中,这三个文件描述符打开的是同一个设备文件。这个设备文件在/dev/pts目录下,对应当前进程的shell环境提供的终端文件。

  文件关闭的本质:断开进程与文件系统中文件的关联关系,即就是将文件描述块儿回收,当然回收时会向操作系统内核报告,以便操作系统内核清除该进程对相关文件的访问标识。

  进程中可以关闭对应的设备文件的文件描述块儿,也就是说可以关闭标准输入、标准输出、标准错误输出。

#include

int main(void){
        printf("This is before;\n");
        close(1); //关闭标准输出
        printf("This is after;\n");
        return 0;
}

运行结果

由于标准输出被关闭,所以其后的输出将不会打印到屏幕上,输出到哪里去了,不知道。

5、文件描述符的复制

  文件描述符的复制指的是,将被复制的文件描述符对应的文件描述块儿复制一份(原模原样,即就是包括被复制文件描述块儿当前的状态),然后返回该文件描述块儿的文件描述符。

  dup()的文件描述符的复制

#include
#include

int main(void){
        char buffer[80] = "Welcome \n";
        int fd = dup(1);

        printf("fd = %d\n", fd);
        write(fd, buffer, strlen(buffer));
        return 0;
}

  dup2()的文件描述符的复制

dup2()就是将第一个参数的文件描述符对应的文件描述块儿复制一份,挂载给第二个参数,如果第二个参数之前有对应的文件描述块儿,则会先进行关闭。

  即就是dup2()就完成了重定向功能!

#include
#include
#include
#include

int main(void){
        char buffer[80] = "Wello to xian\n";
        int fd = open("./data.txt", O_CREAT | O_RDWR, 0755);
        if(fd < 0){
                perror("");
                return -1;
        }

        dup2(fd, 1);
        write(fd, buffer, strlen(buffer));
        return 0;
}

运行结果

6、文件I/O的API

  

方法声明 解释
int close(int fd); 返回值为0表示成功,非0表示失败。
int open(char *path, int flag, int mode);

open("./data.txt", O_CREAT | O_RDWR, 0755);

返回值大于0表示成功,且是一个可用的文件描述符;失败,返回比0小。

int write(int fd, const void *buffer, int size); 返回值大于0表示成功,且表示输出的数据的字节个数。失败,返回小于0;
int read(int fd, void *buffer, int size); 返回值大于0表示成功,且表示读入的数据的字节个数。失败,返回小于0;

void *是一种特殊的指针类型,表示无类型指针,即就是可以指向任意类型的指针。

下面写一个文件读写的例子:

#include
#include
#include
#include

int main(void){
        int fd;
        char buf[20] = "Red hat Linux";

        fd = open("./tmp.txt", O_CREAT | O_EXCL | O_RDWR, 0755);
        if(fd < 0){
                perror("");
                return -1;
        }
        int len = write(fd, buf, strlen(buf));
        printf("写入了 : %d个字节\n", len);
        //0 1 2三个设备文件其实是同一个文件
        write(1, buf, strlen(buf)); //标准输出到屏幕
        printf("\n");  
        read(1, buf, sizeof(buf)); //从标准输出(屏幕)读入
        write(0, buf, strlen(buf)); //因为同一个文件,输出到屏幕
        return 0;
        // write()都是输出到屏幕的,read()都是从屏幕读入的。
}

运行结果


关于write()和read()函数:

  (1)、0 1 2都是同一文件,关键看,write()函数功能,写入,read()函数功能,读出;

  (2)、在对三种0 1 2 标准文件的操作时,write()是显示到屏幕的功能,read()是从屏幕读入到指定buf中的内容。