我们先来回顾几个代码。。。。。
在C语言中往一个文件中写入内容我们这样写(C文件接口)
int main()
{
FILE* fp = fopen("myfile", "r");
//原型:FILE *fopen(const char *path, const char *mode);
if (!fp)
{
printf("fopen error\n");
}
const char* msg = "hello world\n";
int count = 5;
while (count--)
{
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
今天我们来看如何让用系统接口来实现王文件中写入内容,代码实现如下:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
umask(0);
int fd=open("ly_file",O_WRONLY|O_CREAT,0644);
// int open(const char *pathname, int flags);
if(fd<0){
perror("open");
return 0;
}
int count=3;
const char* arr="hello luying\n";
while(count--){
write(fd,arr,strlen(arr));
}
close(fd);
return 0;
}
上面是分别用库函数和系统调用接口实现的对文件的操作,你是不是很纳闷,为什么我们会说这么多题外话,别急,我们慢慢来接近今天的主题
上面的代码可不是我瞎敲的,都是有目的的,嘻嘻嘻。。。。
我们先来熟悉两个概念《系统调用》和《库函数libc》
系统调用:提供的函数有open、close、read、write、ioctl等,包含的头文件是unistd.h
拿其中一个函数来举例说明吧,比如 write: 原型:size_t write(int fd,const void* buf,size_t nbytes);他的操作对象是文件描述符或者文件句柄(file descriptor)要想写一个文件,必须先以可写权限用open系统调用打开一个文件,获得所打开文件的fd即int fd=open("myfile",O_WRONLY|O_CREAT,0644)(仅仅是一个例子,具体参数根据实际应用并参考man手册参数说明进行选择)
库函数:提供的函数有fopen、fclose、fread、fwrite、lseek等,包含的头文件为stdio.h
其实库函数对文件的操作实际上是通过系统调用来实现的,也就是说fwrite()是通过write()实现的;我们也举一个例子,就如fwrite:原型:size_t fwrite(const void* buffer,size_t size,size_t item_num,FILE* pf);其操作对象为文件指针FILE*pf,同样的,要想向一个文件中写入数据,就必须先以可写权限用fopen函数打开一个文件,获得所打开文件的FILE结构指针pf,例如FILE* fp=fopen("ly_text","w")(具体参数参考man手册)
通过open函数的学习,我们知道了文件描述符实际上是一个小整数,我们还都知道在Linux下一切皆文件。在linux下我们打开一个文件就会获得该文件的文件描述符fd( file discriptor), 每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。 下面我们通过图解帮助分析一下两者的关系
【注意】
1.linux进程默认情况下会打开3个缺省打开的文件描述符,分别是标准输入(stdin)、标准输出(stdout)、标准错误(stdout),分别是0/1/2;分别对应的物理设备是键盘、显示器、显示器
2.文件描述符即使从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,所以就有了file结构体,表示一个已经打开的文件对象
3.进程执行open系统调用,那么怎么让进程和文件关联起来呢?每个进程都有一个指针*files,指向一份文件描述符表,(file_struct),如图中所示,文件描述符的本质就是文件指针数组的下标
文件指针(FILE*)
文件指针指向进程用户区中一个被叫做FILE结构的结构数据。FILE结构包括一个缓冲区(另作分析)和一个文件描述符 。
c程序用不同的FILE结构体管理每个文件。程序员可以使用文件,但是不需要知道FILE结构体的细节。实际上,FILE结构体是间接地操作系统的文件控制块(FCB)来实现对文件的操作的。
FILE结构体中的_file ,即就是文件描述符,作为进入打开文件表索引的整数。
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件就是通过fd访问的,也就是说C库当中的FILE结构体内部,必定封装了文件描述符fd
如此,是不是能够清楚一点了呢?
文件描述符的分配原则
我们来实现两个代码,分别观察一下运行结果
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
umask(0);
int fd=open("ly_file",O_WRONLY|O_CREAT,0644);
// int open(const char *pathname, int flags);
if(fd<0){
perror("open");
return 1;
}
printf("fd: %d\n",fd);
close(fd);
return 0;
}
运行结果为
接下来我们再来观察
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
close(0); //我们关闭0 号下标
int fd=open("ly_file",O_WRONLY|O_CREAT,0644);
// int open(const char *pathname, int flags);
if(fd<0){
perror("open");
return 1;
}
printf("fd: %d\n",fd);
close(fd);
return 0;
}
运行结果:
那我们再来验证一组
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
close(2); //关闭2号下标
int fd=open("ly_file",O_WRONLY|O_CREAT,0644);
// int open(const char *pathname, int flags);
if(fd<0){
perror("open");
return 1;
}
printf("fd: %d\n",fd);
close(fd);
return 0;
}
运行结果:
验证可得,文件描述符的分配规则是:在file_struct数组当中,找到当前文件没有被使用的最小的一个下标,作为新文件的文件描述符
至此,我们粗糙的区分了文件描述符fd以及文件指针
【小结】
创建一个进程时,相应的也就创建了文件描述符表,
进程控制块PCB中的*file指针指向文件描述符表,
创建文件时,会为指向该文件的指针FILE*关联一个文件描述符并添加在文件描述符表中。
在文件描述符表中fd相当于数组的下标,FILE*相当于数组的内容,指向一个文件结构体。