文件描述符(file descriptors)和文件指针(有的也称文件句柄(file handles)也有称为文件流)的关系
一、 先来看看open和fopen的关系
int open(const char *path, int access,int mode)
path 要打开的文件路径和名称
access 访问模式,宏定义和含义如下:
O_RDONLY 1 只读打开
O_WRONLY 2 只写打开
O_RDWR 4 读写打开
还可选择以下模式与以上3种基本模式相与:
O_CREAT 0x0100 创建一个文件并打开
O_TRUNC 0x0200 打开一个已存在的文件并将文件长度设置为0,其他属性保持
O_EXCL 0x0400 未使用
O_APPEND 0x0800 追加打开文件
O_TEXT 0x4000 打开文本文件翻译CR-LF控制字符
O_BINARY 0x8000 打开二进制字符,不作CR-LF翻译
mode 该参数仅在access=O_CREAT方式下使用,其取值如下:
S_IFMT 0xF000 文件类型掩码
S_IFDIR 0x4000 目录
S_IFIFO 0x1000 FIFO 专用
S_IFCHR 0x2000 字符专用
S_IFBLK 0x3000 块专用
S_IFREG 0x8000 只为0x0000
S_IREAD 0x0100 可读
S_IWRITE 0x0080 可写
S_IEXEC 0x0040 可执行
FILE *fopen(char *filename, char *mode)
filename 文件名称
mode 打开模式:
r 只读方式打开一个文本文件
rb 只读方式打开一个二进制文件
w 只写方式打开一个文本文件
wb 只写方式打开一个二进制文件
a 追加方式打开一个文本文件
ab 追加方式打开一个二进制文件
r+ 可读可写方式打开一个文本文件
rb+ 可读可写方式打开一个二进制文件
w+ 可读可写方式创建一个文本文件
wb+ 可读可写方式生成一个二进制文件
a+ 可读可写追加方式打开一个文本文件
ab+ 可读可写方式追加一个二进制文件
open和fopen的区别:
前者属于低级IO,后者是高级IO。
前者返回一个文件描述符,后者返回一个文件指针。
前者无缓冲,后者有缓冲。
前者与 read, write 等配合使用, 后者与 fread, fwrite等配合使用。
后者是在前者的基础上扩充而来的,在大多数情况下,用后者。
fopen /open区别总结
UNIX环境下的C 对二进制流文件的读写有两套班子:1) fopen,fread,fwrite ; 2) open, read, write这里简单的介绍一下他们的区别。
1. fopen 系列是标准的C库函数;open系列是 POSIX 定义的,是UNIX系统里的system call。也就是说,fopen系列更具有可移植性;而open系列只能用在 POSIX 的操作系统上。
2. 使用fopen 系列函数时要定义一个指代文件的对象,被称为“文件句柄”(file handler),是一个结构体;而open系列使用的是一个被称为“文件描述符” (file descriptor)的int型整数。
3. fopen 系列是级别较高的I/O,读写时使用缓冲;而open系列相对低层,更接近操作系统,读写时没有缓冲。由于能更多地与操作系统打交道,open系列可以访问更改一些fopen系列无法访问的信息,如查看文件的读写权限。这些额外的功能通常因系统而异。
4. 使用fopen系列函数需要"#include ";使用open系列函数需要"#include " ,链接时要之用libc(-lc)
小结:
总的来说,为了使程序获得更好的可移植性,未到非得使用一些fopen系列无法实现的功能的情况下,fopen系列是首选。
read/write和fread/fwrite区别
1,fread是带缓冲的,read不带缓冲.
2,fopen是标准c里定义的,open是POSIX中定义的.
3,fread可以读一个结构.read在linux/unix中读二进制与普通文件没有区别.
4,fopen不能指定要创建文件的权限.open可以指定权限.
5,fopen返回指针,open返回文件描述符(整数).
6,linux/unix中任何设备都是文件,都可以用open,read.
如果文件的大小是8k。你如果用read/write,且只分配了2k的缓存,则要将此文件读出需要做4次系统调用来实际从磁盘上读出。
如果你用fread/fwrite,则系统自动分配缓存,则读出此文件只要一次系统调用从磁盘上读出。
也就是用read/write要读4次磁盘,而用fread/fwrite则只要读1次磁盘。效率比read/write要高4倍。
如果程序对内存有限制,则用read/write比较好。
都用fread 和fwrite,它自动分配缓存,速度会很快,比自己来做要简单。如果要处理一些特殊的描述符,用read和write,如套接口,管道之类的
系统调用write的效率取决于你buf的大小和你要写入的总数量,如果buf太小,你进入内核空间的次数大增,效率就低下。而fwrite会替你做缓存,减少了实际出现的系统调用,所以效率比较高。
二、 文件描述符和文件指针的关系
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话。标准文件描述符图如下:
下图展示了文件描述符、打开的文件句柄以及i-node之间的关系,图中,两个进程拥有诸多打开的文件描述符。
在Linux系统中,每个文件描述符对应一个打开的文件,同时不同的文件描述符也可以指向同一个文件。相同的文件可以被不同的进程打开,每个进程中有对应的文件描述符(可以相同也可不相同);相同文件也可以在同一个进程中多次打开(对应不同的文件描述符)。上面已简单说明,为了高效管理打开的文件,Linux系统为每个进程维护了一个文件描述表,该表的值都是从0开始的,所以在不同的进城中可以看到相同的文件描述符。这种情况下,相同的文件描述符有可能指向同一个文件,也有可能指向不同的文件,具体情况要依据具体场景分析。要想理解具体的概况,需要了解和查看内核维护的3个数据结构:
进程级的文件描述符表
系统级的打开文件描述符表
文件系统的i-node表
进程级的文件描述符表中的每一项记录了该进程中单个文件描述符的相关信息。
控制文件描述符的一组标志(目前,此类标记只定义了一个,即close-on-exec)
对应打开文件句柄的应用(文件指针)
文件指针:C语言中使用文件指针作为I/O句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。其中,FILE结构包括一个缓冲区和一个文件描述符,而改文件描述符就是PCB中文件描述符表中的一个索引。因此,从某种意义上来说,文件指针就是文件句柄的句柄。
Linux内核对所有打开文件的维护有一个系统级的描述符表(open file descriptor table),或称之为打开文件表(open file table)。表中每条记录称为打开文件句柄(open file handler),一个打开文件句柄存储了与一个打开文件相关的全部信息,譬如:当前文件偏移量、文件访问模式、该文件i-node对象的引用、文件类型和访问权限等。
下图展示了文件描述符、打卡文件句柄(文件指针)、打开文件以及i-node之间的关系,其中,三个进程拥有多个打开的文件描述符。
Process 1中,文件描述符3和21都指向了同一个文件a,这可能是通过dup()、dup2()、fcntl()或对同一个文件多次调用open()函数形成的;
Process 1文件描述符21和Process 2中文件描述符35都指向文件a,但是却对应两个不同打开文件句柄(文件指针),二者打开模式不同(O_RDONLY与O_WRDONLY)。这种情形可能是调用fork()后创建子进程,或某个进程通过UNIX套接字将一个打开的文件描述符传递给另一个进程,或两个独立的进程分别open()打开同一个文件。
Process 1和Process 3中文件描述符3分别对应文件a和文件b。
文件句柄也称为文件指针(FILE *):C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)
C语言中FILE结构体的定义:
/* Define outside of namespace so the C++ is happy. */
struct _IO_FILE;
__BEGIN_NAMESPACE_STD
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;
__END_NAMESPACE_STD
#if defined __USE_LARGEFILE64 || defined __USE_SVID || defined __USE_POSIX \
|| defined __USE_BSD || defined __USE_ISOC99 || defined __USE_XOPEN \
|| defined __USE_POSIX2
__USING_NAMESPACE_STD(FILE)
#endif
# define __FILE_defined 1
#endif /* FILE not defined. */
#undef __need_FILE
#if !defined ____FILE_defined && defined __need___FILE
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE __FILE;
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
};
这个_IO_FILE结构体中的“int _fileno”就是fd,即文件描述符。
这个可以通过程序验证:
#include
#include
#include
#include
#include
#include
int main(){
char buf[50] = {"file descriptor demo"};
FILE *myfile;
myfile = fopen("test","w+");
if(!myfile){
printf("error: openfile failed!\n");
}
printf("The openfile's descriptor is %d\n", myfile->_fileno);
if(write(myfile->_fileno,buf,50)<0){
perror("error: write file failed!\n");
exit(1);
}else{
printf("writefile successed!\n");
}
exit(0);
}
三、 文件描述符和文件指针的相互转换
1文件描述符,在linux系统中,设备也是以文件的形式存在,要对该设备进行操作就必须先打开这个文件,打开这个文件就会获得这个文件描述符,它是个很小的正整数,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
文件描述符的优点:兼容POSIX标准,许多Linux和UNIX系统调用都依赖于它。
缺点:不能移植到非UNIX系统上,也不直观。
文件指针,C语言中使用的是文件指针而不是文件描述符作为I/O的句柄,“文件指针(file pointer)”指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符值。而文件描述符值是文件描述符表中的一个索引。从某种意义上来说,文件指针就是句柄的句柄。
2. 文件指针/句柄(FILE*)、文件描述符以及文件路径(filepath)的相互转换
文件路径 到 文件指针:filepath --fopen()-->FILE*;
文件路径 到 文件描述符:filepath--open()--fd;
文件描述符 到 文件指针:fd--fdopen()-->FILE*;
文件指针 到 文件描述符:FILE*--fileno()--->fd;
3.示例代码
int main(int argc, char **argv)
{
FILE *fp;
printf("stdin fileno:%d\n",fileno(stdin));
printf("stdout fileno:%d\n",fileno(stdout));
printf("stderr fileno:%d\n",fileno(stderr));
fp = fopen("aa.c","w+");
printf("aa.c'fileno %d\n",fp->_fileno);
return 0;
}