文件的I/O操作,隐藏在"FILE*"背后的文件描述符

一 . FILE指针

当我们在程序中进行文件的读写操作,经常会和下列函数打交道

//打开文件流
FILE *fopen(const char *path,const char* mode)
//读操作
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)
//关闭文件流
int fclose(FILEE *fp)

它们都用到了一个FILE* 的指针, 那么FILE* 指针指向的内容是什么呢?

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

其中我们比较关心的是一个定义为整型的变量 fileno

 int _fileno;//文件描述符

我们使用的fopen和返回的file结构体,实际上是对系统调用的一层封装,

在fopen底层上是使用系统调用open来实现的,

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

而open返回的是一个文件描述符,就是file结构体里封装的那个int _fileno

二 文件描述符

进程的状态信息 task_struct 里会保存一个files*的指针 指向 files_struct结构体,

这个结构体就就是维护该进程打开的文件表,如图所示

这里写图片描述
files_struct结构体实际上就是一个指针数组,

文件描述符就是其下标

我们可以通过数组的下标找到数组的内容,也就是具体的文件对象了.

我们可以发现,每个进程都默认打开了 3个文件,分别是”std in” “std out” “std error”

由于在Linux里”一切皆文件”

形象的说默认打开的三个文件功能分别是其实就是 0 键盘输入 1屏幕显示 2输出错误信息

由于默认打开了这三个文件.所以默认我们是用open函数打开新文件.返回的是 3

三. 系统调用open() read() write() close()

open()

open()函数返回的是一个整型(文件描述符),

#include<fcntl.h>
int open(constchar*pathname,int  flags);
int open(constchar*pathname,int  flags,mode_tmode);

返回值:成功则返回文件描述符,否则返回-1
对于open函数来说,第三个参数仅当创建新文件时(即 使用了O_CREAT 时)才使用,用于指定文件的访问权限位(access permission bits)。pathname 是待打开/创建文件的POSIX路径名(如/home/user/a.cpp);flags 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于fcntl.h)通过逻辑位或逻辑构成。

O_RDONLY只读模式
O_WRONLY只写模式
O_RDWR读写模式

read()

read()函数read()会把参数fd所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。

返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或无可读取的数据。错误返回-1,并将根据不同的错误原因适当的设置错误码。


#include<unistd.h>
ssize_t read (int fd, void *buf, size_t count);

我们可以注意到 read函数返回值是ssize_t ,因为它出错时要返回-1,所以ssize_t成了一个有符号的数,当然有符号整型能表示的最大长度就比size_t足足小了一半

write()

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

write 向文件描述符 fd 所引用的文件中写入 从 buf 开始的缓冲区中 count 字节的数据.

返回值 成功时返回所写入的字节数(若为零则表示没有写入数据).错误时返回-1,并置errno为相应值.

## close()

       #include <unistd.h>

       int close(int fd);

close 关闭一个文件描述符,使它不在指向任何文件

以上参考自Linux 的manual手册 需深入了解还需仔细阅读man手册相关函数的介绍

一个小例子

我们将一个srcfile文件文件里的内容 输出到屏幕上

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
        //打开一个文本文件,权限设置为只读,目前fd肯定是 3
    int fd = open("srcfile",O_RDONLY);
    if (fd == -1)
        return 2;
    char buf[1024];
    ssize_t n;
    while((n = read(fd,buf,sizeof(buf))))
    {
            //fd == 1是屏幕文件
            write(1,buf,n);
    }
    close(fd);
    return 0;
}
结果如下: 

这里写图片描述

输入输出的重定向

当我们了解什么是文件描述符后,重定向就很容易理解了

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
    //关闭屏幕文件
    close(1);
    // fd总是从最小未被占用的无符号整型开始分配 
    int fd = open("srcfile",O_WRONLY);
    printf("talking to the moon");
    fflush(stdout);
    close(fd);
    return 0;
}

我们发现printf() 函数结果不会显示到屏幕上了,而打开srcfile发现,printf()会把内容输入到该文件里

printf()的缓冲区

注意到我们在关闭文件描述符之前,使用 fflush(stdout) 刷型了标准输出的缓冲区

因为printf()是自带缓冲区的,

如果在关闭文件之前不把缓冲区内容刷新出来,那么printf就不会把内容输入到srcfile中

下面这个例子很好的说明了缓冲区的概念

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
    const char* info1 = "Hi,write";
    const char* info2 = "hi,printf";
    const char* info3 = "hi,im fwrite";
    write(1,info1,strlen(info1));
    printf("%s",info2);
    fwrite(info3,strlen(info3),1,stdout);
    close(1);
    return 0;
}

我们分别使用了三种方式输出字符串到屏幕上, 但是结果呢?

这里写图片描述

发现只有 write 打印出来了结果

原因是: write是系统调用,没有缓冲区,而fwrite和printf是c库里封装的函数为了提升性能就加上了自己的缓冲区

缓存类型:
全缓存:当填满I/O缓存后才进行实际I/O操作(或者执行fflush、flose、exit、return),4K大小
行缓存:当填满I/O缓存后才进行实际I/O操作或者遇到新航服‘\n’(或者执行fflush、floce、exit、return),1K大小
无缓存:标准错误输出strerr

还应该注意的一点是 c库中的函数如果是写入到屏幕,缓冲区是行缓冲,如果写入到文件中去,就变成了全缓冲

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POSIX API(Portable Operating System Interface)是一组操作系统API标准,用于确保可移植性。这些API包括文件 I/O 操作方法,可以在不同的操作系统上使用相同的语法进行文件操作。下面是一些文件 I/O 操作方法: 1. 打开文件: ``` int open(const char *path, int flags); // 返回文件描述符 ``` `open()` 函数用于打开文件,并返回文件描述符file descriptor),这个文件描述符被用来在后续的文件操作中标识文件。参数 `path` 是文件路径,参数 `flags` 是打开文件的模式,比如 `O_RDONLY` 表示只读,`O_WRONLY` 表示只写,`O_RDWR` 表示读写等。 2. 写文件: ``` ssize_t write(int fd, const void *buf, size_t count); // 返回成功写入的字节数 ``` `write()` 函数用于将数据写入文件。参数 `fd` 是文件描述符,参数 `buf` 是要写入的数据缓冲区,参数 `count` 是要写入的字节数。函数返回成功写入的字节数。 3. 读文件: ``` ssize_t read(int fd, void *buf, size_t count); // 返回成功读取的字节数 ``` `read()` 函数用于从文件中读取数据。参数 `fd` 是文件描述符,参数 `buf` 是存储读取数据的缓冲区,参数 `count` 是要读取的字节数。函数返回成功读取的字节数。 4. 关闭文件: ``` int close(int fd); // 返回0表示成功 ``` `close()` 函数用于关闭文件。参数 `fd` 是文件描述符。函数返回0表示成功关闭文件。 以上是 POSIX API 中文件 I/O 操作的几个基本方法,还有一些其他的方法,如 `lseek()` 用于改变文件读写位置, `fstat()` 用于获取文件状态等。在实际的文件操作中,需要根据实际需求选择合适的方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值