文件理论:文件原理和操作,不是语言问题,是系统问题。其他语言的文件操作各不相同,并且底层都是一样的,只不过都被封装上去了操作文件的时侯,第一件事都是打开文件,这么打开文件是做哪些呢,怎样理解呢?文件=内容+属性—>针对文件的操作是对内容的操作、对属性的操作当文件没被操作的时侯,通常都在c盘(外存)当我们对文件操作的时侯,文件在显存:由于冯诺依曼体系对文件操作时,文件须要提早被load到显存,**load的起码有属性,**且显存中存在着大量不同的文件属性
综上所述,打开文件本质就是将须要的文件加载到显存中,OS内部一定同时存在大量的被打开的文件,操作系统对这种文件先描述,再组织!
**先描述:*建立在显存中的文件结构体structfile{c盘的文件,structfilenext}:被打开的文件
再组织:每一个被打开的文件,都要在OS中对应文件对象的struct结构体,可以将所有的structfile结构体用某种数据结构链接上去,在OS内部,对被打开的文件管理就转换成了对数组的增删查改。
**推论:**文件被打开,OS对被打开的文件创建对应的内核数据结构:
文件可以分为两大类:c盘文件,显存文件(被打开的文件)文件的打开,是用户让OS打开的,本质是OS打开文件,用户(进程为代表),我们之前的文件操作都是进程和被打开文件的关系:structtask_struct和structfileC语言中,printf默认是向显示器复印消息,fprintf是向指定文件流复印
OS通常怎样让用户传递标示位呢?
我们(用户)通常都是这样传:
intXXXX(intflag1,intflag2,intflag3);——flag=1,2,3…,假如我们想要同时传递3个以上的标示位呢?总不能一个一个去定义数组吧。
但是系统(OS)是这样做的:
intYYYY(intflag);——flag→int(四个字节),32个比特位,我们传flag时,可以用一个比特位表示一个标志位,一个int最多可以传递32个标志位。这是位图的概念。诸如:
使用二补码按位“与”“或”的特点,来一次传入多个flag值,最多32个,刚好一个int占用的比特位。
我们晓得了大约的系统是怎样传递flag值的,这么才能读懂下边函数的实参是如何传的了:
可以尝试以下代码:
#include
#include
#include
#include
#include
#include
#include
#define LOG "log.txt"
int main(){
// O_CREAT|O_WRONLY: 默认不会对原始文件内容做清空
// int fd = open(LOG, O_WRONLY | O_CREAT, 0666);
// int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);//覆盖写
int fd = open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666); //以读写追加的方式打开,权限与上umask
// int fd = open(LOG, O_RDONLY); //打开 仅读取
if(fd == -1)
{
printf("fd: %d, errno: %d, errstring: %sn", fd, errno, strerror(errno));
}
else printf("fd: %d, errno: %d, errstring: %sn", fd, errno, strerror(errno));
//读取文件内容
char buffer[1024];
// 这里我们无法做到按行读取,我们是整体读取的。
ssize_t n = read(fd, buffer, sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意,问题
if(n > 0)
{
buffer[n] = '';
printf("%sn", buffer);
}
//C语言的写入操作
const char *msg = "bbb";
int cnt = 3;
while(cnt)
{
char line[128];
//把字符串写入到line缓冲区中
snprintf(line, sizeof(line), "%s, %dn", msg, cnt);
//写入到文件fd中
write(fd, line, strlen(line)); //这里的strlen不要+1, 是C语言的规定,不是文件的规定!
cnt--;
}
close(fd);
return 0;
}
O_CREAT:假如没有这个文件就创建
O_WRONLY:打开方法为读写
O_TRUNC:覆盖原文件中内容再写
O_APPEND:追加写
应用层使用操作系统或访问硬件必须通过系统调用!!!OS命令显存和硬碟进行数据交互
打开文件时的返回值
我们可以看见,open函数类型是int,证明它是有返回值的,这么返回的值是哪些呢?返回的就是文件描述符!它的本质就是链表下标!
文件描述符:
任何进程启动的时侯,会打开当前进程的三个文件:标准输入、标准输出、标准错误
尽管标准输出和标准错误就会向显示器复印,但毕竟是不一样的。
文件调用底层示意图:
里面说过,文件被打开时,会默认打开三个设备文件:标准输入,标准输出,标准错误。
当文件被打开时,它才会占用0,1,2三个文件描述符,如上图所示。
重定向:
#define LOG log.txt
int main()
{
close(0);//关闭stdin
int fd=open(LOG,O_RDONLY);
int a,b;
scanf("%d %d",&a,&b);
printf("a=%d , b=%dnfd=%d",a,b,fd);
return 0;
}
log.txt:
123 456
假如我们在代码中,关掉0号文件描述符(close(0)),也就是stdin,这么就难以从鼠标输入了linux 文件描述符,而且输入操作重定向到文件:从log.txt中获取数据123456,输入给a和b。此时我们也可以发觉linux删除文件夹,0号文件描述符fd早已弄成了我们打开的文件,由此可知,当我们打开文件时,和图上描述一样,OS会从上往下遍历找到没有使用的位置给新的文件!
其实我们也可以关掉1号文件描述符,这么就不会在屏幕上输出信息,而会输出到log.txt文件中:
int main()
{
close(1);
int fd=open(LOG,O_WRONLY);
int a=123,b=456;
printf("a=%d , b=%dnfd=%d",a,b,fd);
return 0;
}
怎样理解:一切皆文件
我们使用OS的本质:都是通过进程的形式进行OS的访问的!
C语言的文件操作
FILE*是C语言提供给用户的结构体,和我们前面说的structfile不是一个东西,是上、下层关系。
操作系统层面,我们必须访问fd能够找到文件!
任何语言层或外设文件,必需要经历OS
dup2()函数:
自动关掉不便捷,可以使用dup2函数linux修改文件名,将早已打开文件的fd(oldefd),拷贝到指定fd(newfd)。
int main()
{
int fd=open(LOG,O_RDONLY);
dup2(fd,0);//拷贝fd到0号位置
int a,b;
scanf("%d %d",&a,&b);
printf("a=%d , b=%dnfd=%d",a,b,fd);
return 0;
}
注意:是拷贝过去,不是联通,打开的文件的fd仍然是3!
缓冲区怎样理解缓冲区?先来看出现的问题现象:
对比可以发觉,当没有子进程时,会多复印一行**fprintf,**这是为何呢?
当我们使用文件流stdout,fopen或则标准输入stdin,标准错误stderr,就会导致这些情况。
提出文件缓冲区:
缓冲区就是为了节约调用者的时间。
C库会结合一定的刷新策略:
无缓冲行缓冲:显示器采用的策略全缓冲:普通文件采用的策略
这么C库的缓冲区在哪呢?在进行fopen打开文件的时侯,会得到一个FILE结构体,缓冲区就在这个结构体中!
解释问题:
调用fprintf用的是C库,所以会创建一个FILE结构体,把输出的数据储存在FILE中的缓冲区;
然而write是使用系统调用,不用经过FILE的缓冲区,直接写入到操作系统structfile中的缓冲区,之后再刷新到c盘中。
假如我们不重定向到文件中时,是显示器刷新策略,行缓冲linux 文件描述符,遇见’n’就被刷新走了,再fork()就没有任何意义了,FILE缓冲区是空的。当重定向到文件时,就弄成了文件刷新策略,全缓冲,当调用fork()的时侯,FILE中还有数据,被写实拷贝成了两份,这么都会刷新两次数据,所以才能有两行fprintf,且都在write前面。