文件的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库中的函数如果是写入到屏幕,缓冲区是行缓冲,如果写入到文件中去,就变成了全缓冲
相关文章推荐
- 上传文件 隐藏input type="file",用其它标签实现
- RandomAccessFile(随即读取)操作文件有4种模式:"r"、"rw"、"rws" 或 "rwd"
- 用系统命令加载磁盘 (隐藏文件) "学习资料"放的再深也不怕
- "ORA-20100: 为 FND_FILE 创建文件 o0003167.tmp 失败"
- 将html中的<input type="file" />(选择文件) 元素隐藏,并通过其它方式触发点击。file input 美化
- 上传文件 隐藏input type="file",用text显示
- RandomAccessFile(随即读取)操作文件有4种模式:"r"、"rw"、"rws" 或 "rwd"
- FileSystem.DeleteDirectory遇到"无法删除 文件:无法读取源文件或磁盘"
- eclipse 启动失败,出现hs_err_pid972.log类文件,文件中含JavaThread "Bundle File Closer" daemon类内容
- Upload文件时出现"Cannot access a closed file"错误
- 使用"类型文件"(typed File),创建自己的"数据库"
- MySQL导入.sql文件时出现" failed to open file"错误
- 操作properties文件,注意抹掉最前面的"file:"
- 上传文件,用js回调函数实现隐藏input type="file"
- String filePath = request.getSession().getServletContext().getRealPath("/");这句话返回的路径是什么,解释下getRealPath("/")函数中的"/"表示什么意思
- 【安卓学习之常见问题】 apk崩溃,找不到so文件(dex file "couldn't find "libSDL.so)
- cordova 企业应用打包Archive的时候报 "#import <Cordova file not found"
- SpringMVC上传文件需要注意的地方:@RequestParam(value = "file", required = false) MultipartFile file
- html input="file" 浏览时只显示指定文件类型 xls、xlsx、csv
- IE input type=file 隐藏时不能上传文件