您的位置:首页 > 运维架构 > Linux

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

2018-03-27 10:46 363 查看

一 . 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足足
4000
小了一半

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库中的函数如果是写入到屏幕,缓冲区是行缓冲,如果写入到文件中去,就变成了全缓冲
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐