基础IO+系统文件IO+文件描述符+文件描述符分配规则
自我名言 :只有努力,才能追逐梦想,只有努力,才不会欺骗自己。
喜欢的点赞,收藏,关注一下把!
1.重新谈论文件
根据前面所学知识,我们也了解一些文件的相关知识:
1.空文件,也要在磁盘占据空间
2.文件=文件内容+文件属性
3.文件操作=对内容+对属性 or 对内容和属性
4.标定一个文件,必须使用:文件路径+文件名【唯一性】
5.如果没有指定对应的文件路径,默认是在当前路径(进程当前的路径),进行访问
6.当我们把fopen,fclose,fwrite,fread接口写完之后,代码编码之后,形成一个二进制可执行程序之后,但是没有运行,文件对应的操作有没有被执行?
其实并没有,对文件的操作,本质:进程对文件的操作!
7.一个文件如果没有被打开,可以直接进行文件访问吗?
不能,一个文件要被访问,必须先被打开。 那么被谁打开呢?
对文件的操作,是在进程中进行的,因此首先是用户进程,其次对文件操作的C接口是封装了系统调用接口,这部分是由OS操作的。
因此是由用户进程+OS共同进行的。
是不是所有文件都被打开呢?
不是,这里文件分成两类
a.被打开的文件
b.没有被打开的文件
上述这么多知识都是为了阐明一点:
文件操作的本质:进程+被打开文件的关系
2.重谈文件操作(C语言)
1.C,C++,Java,python...shell都有对文件操作的接口,但是接口都不一样
根据目前掌握的知识,了解文件在磁盘 里,而磁盘是 一个硬件 ,OS能访问硬件 ,因此所有人想访问磁盘都绕不过OS ,使用的是OS提供的接口 ,想访问的是磁盘上的文件,因此使用的是OS提供文件级别的系统调用接口 。所有语言访问文件接口五花八门,但是操作系统只有一个。
所以上层语言无论如何变化
a.库函数底层都必须调用系统调用接口
b.库函数可以千变万化,但是底层不变
为了降低学习成本------->学习不变的东西
2.1C文件接口
2.文件的操作(C语言)
在C的时候学过文件相关操作知识,这里不再详细说,对这块知识记得不清的可以参考这篇博客超详细C语言文件操作,学完对文件了解更进一步。
fopen打开文件,必须要提供打开的方式。这里总结一下
r(读),w(写),
r+(读写,文件不存在报错),w+(读写,文件不存在创建),
a(append,追加),a+(在文件尾读写,文件不存在创建),
rb(读(二进制)),wb(写(二进制)),
rb+(读写(二进制),报错),wb+(读写(二进制),创建)
ab(二进制文件尾追加),ab+(二进制文件尾读写,创建)
写
c
1 #include<stdio.h>
2
3 int main()
4 {
5 //打开
6 FILE* fp=fopen("log.txt","w");
7 if(fp == NULL)
8 {
9 perror("fopen");
10 return 1;
11 }
12
13 //写
14 int cnt=5;
15 while(cnt)
16 {
17 fprintf(fp,"%s:%d\n","hello linux",cnt--);
18 }
19
20 //关闭
21 fclose(fp);
22 return 0;
23 }
读
c
1 #include<stdio.h>
2 #include<string.h>
3
4 int main()
5 {
6 //打开
7 // FILE* fp=fopen("log.txt","w");
8 FILE* fp=fopen("log.txt","r");
9 if(fp == NULL)
10 {
11 perror("fopen");
12 return 1;
13 }
14
15 // //写
16 // int cnt=5;
17 // while(cnt)
18 // {
19 // fprintf(fp,"%s:%d\n","hello linux",cnt--);
20 // }
21
22 //读
23 char buffer[64];
24 while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
25 {
26 buffer[strlen(buffer)-1]=0;
27 puts(buffer);
28 }
29
30 //关闭
31 fclose(fp);
32 return 0;
33 }
追加
c
1 #include<stdio.h>
2 #include<string.h>
3
4 int main()
5 {
6 //打开
7 // FILE* fp=fopen("log.txt","w");
8 // FILE* fp=fopen("log.txt","r");
9 FILE* fp=fopen("log.txt","a");
10 if(fp == NULL)
11 {
12 perror("fopen");
13 return 1;
14 }
15
16 //写
17 int cnt=5;
18 while(cnt)
19 {
20 fprintf(fp,"%s:%d\n","hello linux",cnt--);
21 }
22
23 // //读
24 // char buffer[64];
25 // while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
26 // {
27 // buffer[strlen(buffer)-1]=0;
28 // puts(buffer);
29 // }
30
31 //关闭
32 fclose(fp);
33 return 0;
34 }
以w方式单词的打开文件,C会自动清空内部数据
c
1 #include<stdio.h>
2 #include<string.h>
3
4 int main()
5 {
6 //打开
7 FILE* fp=fopen("log.txt","w");
8 // FILE* fp=fopen("log.txt","r");
9 // FILE* fp=fopen("log.txt","a");
10 if(fp == NULL)
11 {
12 perror("fopen");
13 return 1;
14 }
15
16 // //写
17 // int cnt=5;
18 // while(cnt)
19 // {
20 // fprintf(fp,"%s:%d\n","hello linux",cnt--);
21 // }
22
23 // //读
24 // char buffer[64];
25 // while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
26 // {
27 // buffer[strlen(buffer)-1]=0;
28 // puts(buffer);
29 // }
30
31 //关闭
32 fclose(fp);
33 return 0;
34 }
这些都是C语言给我提供的一些对文件操作的接口。现在来看看OS给我们提供的关于对文件操作的系统调用的接口。
3.系统文件IO
3.1open
open有两个接口,首先来讲第二个接口
第一个参数:文件名
第三个参数:权限(如果创建一个文件,告诉系统该文件的权限是什么)
第二个参数:flags(是一个标记位,告诉系统以什么方式打开文件)
O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写)
flags有很多选项,这些选项是大写的,在我们印象中这样写都是宏。
C传标记位,一个int表示一个标记位,如果想传多个标记位怎么办呢?难道要传多个int吗?
显然这样处理不好。
正确解决方法:一个整数---->32个比特位---->通过比特位传递选项(一个比特位,一个选项,比特位位置不能重复)。
c
1 #include<stdio.h>
2 #include<string.h>
3
4 //每一个宏,对应的数值,只有一个比特位是1,彼此位置不能重叠
5 #define ONE (1<<0)
6 #define TWO (1<<1)
7 #define THERE (1<<2)
8 #define FOUR (1<<3)
9
10 void show(int flags)
11 {
12 if(flags & ONE) printf("one\n");
13 if(flags & TWO) printf("two\n");
14 if(flags & THERE) printf("there\n");
15 if(flags & FOUR) printf("four\n");
16 }
17
18 int main()
19 {
20
21 show(ONE);
22 printf("------------------\n");
23 show(ONE|TWO);
24 printf("------------------\n");
25 show(ONE|TWO|THERE);
26 printf("------------------\n");
27 show(ONE|TWO|THERE|FOUR);
28 printf("------------------\n");
29
30 return 0;
31 }
返回值:文件描述符(成功),-1(失败)
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
//打开
int fd=open("log.txt",O_WRONLY);
if(fd<0)
{
perror("open");
return 1;
}
//关闭
close(fd);
return 0;
}
文件不存在,并不会自动创建。
这是因为我们调用C接口,是对底层的封装,然后让底层帮我们创建,但是现在我们直接用的就是底层,如果不给具体选项,是不会帮我们自动创建的。
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
//打开
//O_CREAT 创建
int fd=open("log.txt",O_WRONLY|O_CREAT);
if(fd<0)
{
perror("open");
return 1;
}
//关闭
close(fd);
return 0;
}
虽然成功创建了文件,但是这个文件权限和我们用C接口创建文件的权限是不一样的,这个是上面所说的原因一样,现在用的是系统调用接口,需要直接告诉这个文件权限是什么。
cpp
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
//打开
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
//关闭
close(fd);
return 0;
}
文件不存在,用的是有第三个参数的接口。
文件存在(已经有了权限),用的是没有第三个参数的接口。
这里可能有个疑问。我给新创建的文件权限设置是0666,为什么新创建的文件权限权限是0664呢?
文件最终权限=起始权限(0666)&(~umask(普通用户0002))
所以新创建的文件权限是0664
那我们就想新创建的文件权限就是我们设置的权限怎么办呢?
umask(0)
清掉系统默认的umask
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
//打开
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
//关闭
close(fd);
return 0;
}
我们手动清楚了系统默认的umask,为什么这里还是0002呢?
注意,我们改掉的是子进程的权限,并不影响shell(还是0002)。
3.2close
关闭文件。
参数就是文件描述符。
3.3write
第一个参数:向那个文件里写
第二个参数:想写对应的缓冲区数据在哪里
为什么第二个参数用的是void呢?
语言层面把文件类型分为:1,文本文件 。2,二进制文件。
但是操作系统一律认为都是二进制文件,所以是void
第三个参数:缓冲区字节个数
返回值:成功时,写入内容的字节个数会被返回,失败时返回-1。
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
umask(0);
//打开
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
//写
const char* buffer="hello linux\n";
ssize_t num= write(fd,buffer,strlen(buffer)); //向文件中写入string,strlen(buffer)+1 需要加1吗?
//你以\0作为字符串的结尾,是C语言的规定,和我文件有什么关系呢?
if(num<0)
{
perror("write");
exit(2);
}
//关闭
close(fd);
return 0;
}
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
umask(0);
//打开
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
//写
//const char* buffer="hello linux\n";
const char* buffer="aaaaaa\n";
ssize_t num= write(fd,buffer,strlen(buffer));
if(num<0)
{
perror("write");
exit(2);
}
//关闭
close(fd);
return 0;
}
为什么是这样的结果呢?
C语言以w方式打开文件,会先清空文件内容再写。
但是open("log.txt",O_WRONLY|O_CREAT,0666);只是直接覆盖,并不会清理。
这是因为我们少传一个参数O_TRUNC。
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
umask(0);
//打开
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
//写
//const char* buffer="hello linux\n";
const char* buffer="aaaaaa\n";
ssize_t num= write(fd,buffer,strlen(buffer));
if(num<0)
{
perror("write");
exit(2);
}
//关闭
close(fd);
return 0;
}
以前我们C接口,fopen,以w/a方法打开文件,就是库里直接帮我们把下面工作做好了。
3.4read
第一个参数:从那个文件中读
第二个参数:读到那个缓冲区
第三个参数:读多少个字节
返回值:成功返回读取字节的个数,失败返回-1
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
umask(0);
//打开
//bin int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd=open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
//读
char buffer[1024];
ssize_t num=read(fd,buffer,sizeof(buffer)-1);
if(num > 0) buffer[num-1]=0; //0,\0,NULL--->0,,系统调用接口不管读的是什么类型文件,都会当作二进制处理,这里我们想读的是字符串,因此结尾加个\0
puts(buffer);
//关闭
close(fd);
return 0;
}
3.5lseek
重新定位读写文件的偏移量
这个函数和fseek类似,这里不再演示。
3.6总结
4.如何理解文件
文件操作的本质:进程和被打开文件的关系。
进程可以打开多个文件吗?--->可以--->系统中一定会存在大量的被打开的文件 --->被打开打开的文件,要不要被OS管理起来呢?--->要 ---> 如何管理?---> 先描述,在组织 ---> 操作系统为了管理对应打开的文件,必定要为文件创建对应的内核数据结构标识文件 --->struct _file{ } --->包含了文件大部分属性。
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#define FILE_NAME(number) "log.txt"#number
int main()
{
umask(0);
//打开
//int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
// int fd=open("log.txt",O_RDONLY);
int fd0=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd1=open(FILE_NAME(2),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd2=open(FILE_NAME(3),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd3=open(FILE_NAME(4),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd4=open(FILE_NAME(5),O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd0:%d\n",fd0);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
printf("fd3:%d\n",fd3);
printf("fd4:%d\n",fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
文件创建成功,并且返回各自对应的文件描述符。
但是奇怪的是:
a.为什么从3开始呢?0,1,2去哪里了?
b.为什么是连续的小整数呢?--->什么会是连续的小整数呢?---> 猜测可能和数组有关
这里就不得不提到,C程序默认会打开三个标准输入,标准输出,标准错误。
FILE* fp=fopen();问FILE是什么呢?
int fd=open(); int 是文件描述符
我们知道C:fopen...是对底层系统调用接口的封装,而底层用的是文件描述符。
FILE是一个结构体 ----> 必须有一个字段存放文件描述
C调用文件函数的接口不仅对系统调用接口的封装,与此同时C语言FILE结构体也封装了操作系统内的文件描述符。
c
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#define FILE_NAME(number) "log.txt"#number
int main()
{
umask(0);
printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
//打开
//int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
// int fd=open("log.txt",O_RDONLY);
int fd0=open(FILE_NAME(1),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd1=open(FILE_NAME(2),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd2=open(FILE_NAME(3),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd3=open(FILE_NAME(4),O_WRONLY|O_CREAT|O_TRUNC,0666);
int fd4=open(FILE_NAME(5),O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd0:%d\n",fd0);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
printf("fd3:%d\n",fd3);
printf("fd4:%d\n",fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
文件描述符的本质:就是数组的下标。
进程与文件直之间对应的关系:进程通过文件描述符来指向文件的。
5.文件描述符(fd)分配规则
c
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8
9 int main()
10 {
11 umask(0);
12 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
13 if(fd == -1)
14 {
15 perror("open");
16 exit(1);
17 }
18 printf("fd:%d\n",fd);
19
20 close(fd);
21
22 return 0;
23 }
c
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8
9 int main()
10 {
11 close(0);
12 umask(0);
13 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
14 if(fd == -1)
15 {
16 perror("open");
17 exit(1);
18 }
19 printf("fd:%d\n",fd);
20
21 close(fd);
22
23 return 0;
24 }
c
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8
9 int main()
10 {
11 close(2);
12 umask(0);
13 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
14 if(fd == -1)
15 {
16 perror("open");
17 exit(1);
18 }
19 printf("fd:%d\n",fd);
20
21 close(fd);
22
23 return 0;
24 }
对比三次运行结果,发现每次创建新文件fd都不同,在仔细发现这是由于close(fd)关闭文件,所导致的结果。
由图来解释上述原因
当我们关闭fd为0的文件,重新打开一个文件的时候,进程扫描文件描述符表:从小到大,按照循序寻找最小的且没有被占用的fd。(fd的分配规则)
c
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8
9 int main()
10 {
11 // close(0);
12 // close(2);
13 close(1);
14 umask(0);
15 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
16 if(fd == -1)
17 {
18 perror("open");
19 exit(1);
20 }
21 printf("fd:%d\n",fd);
22
23 close(fd);
24
25 return 0;
26 }
close(1),为什么没有打印新建文件fd呢?
printf("%d\n",fd); printf会把内容打印到stdout文件中。
但是close(1)关闭标准输出stdout--->显示器,int fd=open();新打开的文件fd是1。
stdout-->fd-->1,虽然我们手动关闭了stdout,但是系统并不知道,还以为fd为1的位置是stdout,但是这个位置现在已经被新打开的文件占用了,所以打印到了新打开的文件里。
c
1 #include<stdio.h>
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7 #include<stdlib.h>
8
9 int main()
10 {
11 // close(0);
12 // close(2);
13 close(1);
14 umask(0);
15 int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
16 if(fd == -1)
17 {
18 perror("open");
19 exit(1);
20 }
21 printf("fd:%d\n",fd);
22
23 //这里必须刷新一下,不然log.txt里面没有内容,这里和缓冲区有关,下面讲
24 fflush(stdout);
25 close(fd);
26
27 return 0;
28 }
本来应该打印到显示器文件中,但是确写到文件里了。------>重定向
有关重定向问题,下篇博文解释!