一、如何用C插口进行文件操作
i.基础函数
打开文件:fopen
关掉文件:fclose
读取文件数据:fread
写入数据:fwrite
FILE *fopen(const char *path, const char *mode);//打开函数
int fclose(FILE *fp);//关闭函数
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream);//读取函数
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);//写入函数
ii.细节补充
关于C插口进行文件操作的操作和用法已有好多资料参考,这儿不再赘言,在这儿我补充关于当前路径的理解。
举个反例:假定你写了个程序,其功能是在当前目录创建一个新文件。当你在这个程序所处目录运行这个程序,这么该目录就生成了一个新文件,这没啥问题。那如果你在返回到根目录运行该程序,请问生成的文件是在刚才那种目录下还是根目录下呢?
答案是在根目录下,由此得出的推论是:当前路径并不是可执行程序所处的路径,而是可执行程序启动的路径,如在根目录下启动该程序linux版qq,则当前路径为根目录。所以我们要深刻意识到:当前路径是和进程有关的
二、如何用系统插口进行文件操作
i.打开文件函数open
int open(const char *pathname, int flags, mode_t mode);
头文件与函数原型
返回值
若果成功返回filedescriptor:文件描述符,是一个数字;失败返回-1,并设置errno
参数剖析
pathname:要打开的文件所处的路径,可使用相对路径
mode:可选项,用于设置新建一个文件时的默认权限,如777则为可读可写可执行,同时注意要更改umask
flags:设置以何种方式打开文件,即类似于C插口的可读/可写/可读写打开
Theargumentflagsmustincludeoneofthefollowingaccessmodes:O_RDONLY,O_WRONLY,orO_RDWR.Theserequestopeningthefileread-only,write-only,orread/write,respectively.
常见的选项有:O_RDONLY,O_WRONLY,orO_RDWR,O_CREAT(若文件不存在则创建)
怎样在传参标志位传多个选项,就像时传O_WRONLY和O_CREAT?
事实上flags参数设计的很巧妙,通过按位或来实现传递多个参数,首先它是个int型数,即有32位,操作系统设计的时侯把某个位置位1,其他位置为0作为一种状态,这么它可以表示32种状态,这儿我只用16个bit位来举例,假定0x0001表示写权限记作O_WRONLY,0x0004表示可创建文件记作O_CREAT,我们调用open函数打开某文件想给它写权限和若该文件没有则创建,则将O_WRONLY|O_CREAT传递给flags,将O_WRONLY和O_CREAT按位与得到的int数在bit[0]和bit[2]都为1,达到只用一个参数传入两种权限的疗效
ii.向文件读取函数read
头文件与函数原型
#include
ssize_t read(int fd, void *buf, size_t count);
参数剖析
fd:要读取的文件的文件描述符,每位文件都有一个文件描述符,open函数打开一个文件返回值也是文件描述符
buf:读取到的数据储存之处
count:buf的大小,byte为单位
返回值
若读取成功,返回读取到数据的大小(byte为单位)
若读取失败(如读取过程中被讯号中断)返回-1,并设置errno
iii.向文件写入函数write
头文件与函数原型
#include
ssize_t write(int fd, const void *buf, size_t count);
参数剖析
fd:要写入的文件的文件描述符
buf:要写入的数据储存之处,表针
count:要写入内容buf的大小,byte为单位
返回值
若写入成功,返回写入了的数据的大小(byte为单位)
若写入失败返回-1,并设置errno
相关视频推荐
3个linux内核的秘密,让你彻底搞清文件系统
linux显存管理-繁杂的显存问题,怎么理出自己的思路下来
学习地址:C/C++Linux服务器开发/后台构架师【零声教育】-学习视频教程-腾讯课堂
须要C/C++Linux服务器构架师学习资料加群812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,解释器,DPDK,ffmpeg等),免费分享
三、深入理解文件描述符
i.何为文件描述符
当我们打开文件时,该文件的相关信息会被加载到显存,如创建时间,所属者,操作权限等等,OS对这种信息则需用数据结构维护上去,因而有了structfile(用于描述显存中的文件),多个打开的文件就有多个struct,它们管理上去产生单向数组,结构体里储存文件的属性信息。每位进程进程都拥有task_struct进程控制块,task_struct上面有个表针files,用于指向一张表files_struct,该表包含了一个表针字段,而这字段里每位表针指向一个显存中的文件structfile,进而实现了进程与文件的联接,而文件描述符就是该链表的下标,只要晓得文件描述符就可以找到显存中的文件
FILE结构体部分成员:
新视角诠释fopen做了哪些
1.给调用的用户申请FILE结构体并返回地址(FILE)
2.底层调用系统级函数open得到fd,将fd填充到FILE变量中的fileno
ii.文件流表针和文件描述符
文件流表针是标准库操作句柄:FILE*,文件描述符是系统调用插口句柄:intfd,文件描述符是每位文件的惟一标示,文件流表针底层封装了文件描述符,在文件流表针结构体中可以见到_fileno保存了fd
iii.文件描述符的数字规律
看一个文件打开的程序,观察filedescriptor
我们发觉这个文件的文件描述符fd=3
再看一个打开多个文件的程序,观察filedescriptor
我们发觉打开多个文件,它们的文件描述符fd从3依次递增,我们晓得open函数出现错误返回-1,这么请问0,1,2去哪了呢???
虽然当任何进程在运行的时侯,默认打开三个输入输出流stdin,stdout,stderr,而它们对应的的filedescriptor就是0,1,2linux site:infoq.cn,所以再打开文件时分配的fd只能从3递增了
stdin,stdout,stderr分别对应按键,显示器,显示器,OS打开它们挺好地彰显了LInux中一切皆文件的思想
总结:文件描述符的分配遵守最小未占用原则:从下标0开始逐位分配,若已被使用则不分配
四、重定向
i.原理
本质是更改files_struct表中的表针字段中表针所指向的内容
ii.通过关掉文件描述符实现重定向
#include
#include
#include
#include
#include
int main()
{
//演示重定向基本原理
umask(0);
close(1);//关闭显示器
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);//fd=1
if(fd<0){ return 1;} //打开失败
write(1,"hello world!n",13);//写到1号文件
write(1,"hello world!n",13);
write(1,"hello world!n",13);
write(1,"hello world!n",13);
write(1,"hello world!n",13);
close(fd);
return 0;
}
运行结果:本该讲到标准输出的内容被重定向到log.txt
原理剖析:
在打开新的文件前,我们先close(1);关掉显示器,即上图打红叉的线被取消,所以当新的文件被打开它的文件描述符为1,即上图红色的线被连上,所以当往1号文件写入会被写入到log.txt而不会在显示器显示
iii.使用dup2系统调用实现重定向
a.函数原型
#include
int dup2(int oldfd, int newfd);
参数理解:
newfd为oldfd的拷贝,这儿的copy是拷贝fd_array链表对应oldfd下标的内容,理解为对前者的操作变为对后者的操作
b.用法示例
dup(fd,1)//fd为某个新打开文件的描述符
这儿newfd是1,oldfd是fd,new为old的拷贝,即new也指向分段,所以本要输出到1的内容重定向到fd去
五、剖析缓冲区
i.三种缓冲机制
行缓冲->见于显示器刷新数据的策略全缓冲->见于对文件(理解为c盘文件)写入数据的策略无缓冲->如stderr
ii.哪些情况下缓冲区会被刷新
调用exit()结束进程时会刷新缓冲区当缓冲区满了也会被刷新下来可通过fflush刷下来流被关掉时也会被刷下来行缓冲遇到’n’会被刷新下来
行缓冲遇见上述五种情形会刷新缓冲区,全缓冲遇见除最后一种情形会刷新缓冲区
一份代码验证缓冲区:
理论上应当是先复印“helloworld”再sleep,但观察到的现象是先sleep再复印。这是由于字符串被保留在缓冲区,不满足上述五种刷新条件所以没有被立刻刷新下来,当进程结束后“helloworld”才出现
iii.关于缓冲区的理解
显存和硬碟中进行数据交互(写入/读取)是须要代价的linux fopen a,所以凑足一定量才交互,这个攒的量就置于缓冲区。对于文件,可以攒好多才交互,这样交互效率最高;而对于显示器采用行缓冲,因为人是想收到及时反馈的,所以行缓冲可理解为数据交互效率和用户体验感的平衡
iv.提出三个问题
这个缓冲区在哪?这个缓冲区是谁提供的?OS也有缓冲,与文件缓冲的对比
这三个问题将在下边剖析一个奇怪现象中给出答案
v.一个奇怪的现象
1.代码:
#include
#include
#include
int main()
{
//C函数接口
printf("hello printfn");
fprintf(stdout,"hello fprintfn");
//system call
const char*msg="hello writen";
write(1,msg,strlen(msg));
fork();//加了fork重定向C接口内容打印2次,系统接口内1次
return 0;
}
观察重定向和不重定向的运行结果
2.总结出现的现象:
重定向和不重定向改变了进程的缓冲方法C插口复印了两次,OS插口复印了一次
3.剖析:
为何C插口的显示了两次?
代码次序执行,printf和fprintf的内容储存到缓冲区,由于不是往显示器打,所以不是行刷新,此时缓冲区存在两句话。当fork时创建了子进程,母女进程代码数据共享,缓冲区也是显存上的一块。若OS先调用父进程,则父进程先刷新缓冲区,复印出两句话,到调用子进程时,由于进程具有独立性互不干扰linux fopen a,子进程也要刷新缓冲区,则先写时拷贝再刷新,则又复印出两句话
为何OS插口的没有显示两次?
系统调用函数如write没有缓冲区,所以不会发生写时拷贝造成缓冲区内容被复印了两次
解释第二个问题
对比可得到推论缓冲区语言自带的,不是OS级别的
无论是printf还是fprint都是打到stdout,而stdout是一个FILE结构体,上面除了封装了fd,还维护了缓冲区
解释第一个和第三个问题
内核区和用户区都有缓冲区,我们这儿所说的缓冲区,都是用户级缓冲区,但要刷新的时侯用户级缓冲区的数据不能直接加载到c盘或显示器,得经过操作系统步入到内核区的缓冲区(有自己的机制),之后数据才被加载出去
文章评论