一 . 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库中的函数如果是写入到屏幕,缓冲区是行缓冲,如果写入到文件中去,就变成了全缓冲