本文是基础IO的第一个部分,基础IO部分将主要讲解以下内容:文件fd 文件系统 软硬链接 操作系统的内存管理 以及 动静态库。本节重点讲解文件fd,其余内容将在后面的博客更新。
文件 = 内容 + 属性
文件分为打开了的文件和没打开的文件。
打开的文件:谁打开?进程 (本质是研究进程和文件的关系)
没打开的文件:磁盘里,很多,如何存储?------>快速增删查改。
本节重点讨论打开的文件!
文件被打开,必须先加载到内存,且是其属性先加载到内存,一个进程可以打开多个文件。
操作系统内,一定存在着大量被打开的文件,要对他们进行管理:先描述,再组织!
此时对文件的管理,就转换成对链表的增删查改。
目录
一、共识
二、C文件接口
三、过渡到系统,认识文件系统调用
四、访问文件的本质
五、重定向和缓冲区
重定向
缓冲区
FILE *fp = fopen ("log.txt","w");
如果没有指定路径,默认在当前路径创建(当前路径指进程的当前路径,即cwd,如果更改了cwd,就把文件创建到新的路径了)。
child("/home/JY");//更改当前进程的工作路径。
size_t fwrite ( const void *ptr,size_t size,size_t nmemb,FILE * stream );
size:一个基本单位的大小
nmemb:几个基本单位
const char *message = "hello Linux"; fwrite(message,strlen(message),1,fp);
"w" 写入前,会先对文件进程清空。
(echo "hello Linux" > log.txt 输出重定向本质:打开log.txt 文件,清空,再写入)。
"a" 追加,往文件结尾处写。
文件在磁盘上,访问文件,就是访问硬件。用户不能直接访问硬件,必须通过系统调用接口。
打开文件的系统调用接口:
int open ( const char *pathname,int flags,mode_t mode)
pathname:文件路径
mode:文件权限,如果没传,文件的权限可能为乱码,手动传了之后,注意还有umask(更改当前进程的umask:umask(0));
flags:打开文件的方式(比特位级别的传参方式)
可传:O_RDONLY(只读) O_WRONLY(只写) O_RDWR(读写)
O_CREAT(创建) O_APPEND(追加)O_TRUNC (清空)等等。
头文件:
补充:比特位级别的传参
C语言里的fopen函数就是对open系统调用接口的封装!
如:FILE *fp = fopen("log.txt","w");
其内部一定封装了:int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
以“w”方式打开,如果文件不存在,会自动创建一个,所以要带O_CREAT;打开文件后,会先清空文件内容,所以要带O_TRUNC。
ssize_t write ( int fd,const void * buf,size_t count );
ssize_read ( int fd,void * buf,size_t count );
open的返回值fd本质就是array数组的下标,FILE是C库自己定义的struct,其中必定也封装了fd。 printf("stdin->fd:%d\n",stdin->fileno);
补充:系统默认打开3个文件,键盘文件、显示器文件、显示器文件
在C语言里就是stdin,stdout,stderr
关闭文件:struct file里有一个引用计数count,在调用close(1), 会将1号文件的count--;判断count是否为0,在将struct file* 置空,为0就回收该struct file。
文件描述符的分配规则是什么?
从0小标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符。
重定向
close(1)之后,1号位置指向空,open log.txt时,从小下标开始找,找到1,就让1指向log.txt,所以后面在向1号文件write时,就是往log.txt里写入了。这就是重定向的原理。
其实不用先关闭再打开,只需要有一个系统接口能将3号指针拷贝给1号指针,就能完成重定向。
对应的系统调用接口:
追加重定向:把选项O_TRUNC改为O_APPEND
C缓冲区刷新:
a、无缓冲(直接刷新) b、行缓冲 c、全缓冲(文件)
先看一个现象,下面程序运行时,为什么没有结果输出到显示器?
每个打开的文件都有一个语言级别的缓冲区,C语言里的printf/fprintf/fwrite等,会先将数据写到C语言提供的缓冲区里(区别于内核级缓冲区),它会在某些时机刷新到内核级的缓冲区。在本例中,fwrite写到了C语言级的缓冲区,然后就将1号显示器文件关闭了,并没有写入内核级的缓冲区,因此,没有成功将数据写入1号显示器文件。
现象二:上面的代码是能成功打印hello Linux的。
write是系统接口,会直接将数据写入内核级缓冲区,所以即使没有‘\n’,也会显示结果。
现象三:
由显示器重定向到文件时,刷新方案由行缓冲变成了全缓冲,write是直接写到系统缓冲区的,而printf等C接口本来是行缓冲,但变为了全缓冲,就先不刷新了,fork创建子进程后,发生写时拷贝,因此数据多了一份,进程结束后,就刷新了缓冲区。
用户刷新的本质:将数据通过write写到内核中,平时用的flush一定封装了write。
进程退出时,也会刷新缓冲区,那为什么还需要语言级的缓冲区呢?
1、解决效率问题
2、配合格式化