Linux下的基础IO
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下的基础IO相关的知识,希望对你有一定的参考价值。
目录
一.C语言文件IO操作(库函数)
文件分为文本文件和二进制文件。
文本文件:在内存中什么样,保存在硬盘时需要转化,需要转化成字符,这个操作需要程序员做。
二进制文件:在内存中什么样,在硬盘就什么样。
1.打开文件fopen
fopen打开文件失败返回NULL,打开成功返回一个C语言的文件指针FILE*。
fopen参数:
path:打开文件的路径,默认当前路径。当前路径是指可执行程序所在的路径。但是在VS中为源文件所在的路径,可执行程序在debug目录下。
mode:为打开文件的方式。
文件打开方式 含义 如果指定文件不存在 "r"(只读) 打开一个已经存在的文本文件,读取文件内容 出错 "w"(只写) 打开一个文本文件,往该文件中写入数据。 会建立一个新文件 "a"(追加) 打开一个文本文件,在文件最后添加数据。 出错 "rb"(只读) 打开一个已经存在的二进制文件,读取文件内容 出错 "wb"(只写) 打开一个二进制文件,往该文件中写入数据。 建立一个新文件 "ab"(追加) 打开一个二进制文件,在文件最后添加数据。 出错 "r+"(读写) 打开一个文本文件,可对该文件读和写 出错 "w+"(读写) 打开一个文本文件,可对该文件读和写 建立一个新文件 "a+"(读写) 打开一个文本文件,可在该文件末尾读和写 建立一个新文件 "rb+"(读写) 打开一个已经存在的二进制文件,可对该文件读写 出错 "wb+"(读写) 打开一个二进制文件,可对该文件读写 建立一个新文件 "ab+"(读写) 打开一个已经存在的二进制文件,可在该文件末尾读和写 建立一个新文件 总结:r是读文件,w是写文件,a是追加文件,带+号为可读可写。带b为对二进制文件操作,不带b是对文件文件操作。打开文件为w方式时,文件不存在会建立新文件。
2.写入二进制文件fwrite和读取二进制文件fread
后面有代码演示。
3.关闭文件fclose
1 #include<stdio.h>
2 #include<string.h>
3
4 int main(){
5 FILE* fp=fopen("log.txt","w");
6 if(NULL==fp){
7 perror("fopen fail");
8 return 1;
9 }
10 const char *str="hello linux\\n";
11 fwrite(str,strlen(str),1,fp);
12 fclose(fp);
13
14
15 return 0;
16 }
1 #include<stdio.h>
2 #include<string.h>
3
4 int main(){
5 FILE* fp=fopen("log.txt","r");
6 if(NULL==fp){
7 perror("fopen fail");
8 return 1;
9 }
10 char *buf[1024];
11 fread(buf,1024,1,fp);
W> 12 printf("%s",buf);
13 //const char *str="hello linux\\n";
14 //fwrite(str,strlen(str),1,fp);
15 fclose(fp);
16
17
18 return 0;
19 }
二.系统文件IO(系统调用接口)
1.打开文件open系统调用
pathname:要打开或者创建的目标文件。
flags:打开文件的方式。
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
这三个常量,必须指定一个,并且只能指定一个。
O_CREAT:若文件不存在,则创建该文件。需要使用mode选项来指明创建文件的权限。即需要用到上面的第二个有三个参数的open函数。
O_APPEND:追加写
O_TRUNC:将文件内容清空。
flags参数类型是一个整形,上面的各种方式其实都是宏定义。每一个宏代表的数化成二进制后,都只有特定的一位为1.不同的位代表不同的功能。即可以将上面个方式按位或,实现不同的功能呢在一起。
mode:为创建新文件的权限,为8进制。但是一般输入4位,在最开始输入0。否则,C语言会将其看作10进制。
返回值:打开成功:新打开文件的文件描述符。
打开失败:返回-1。
2.读文件read和写文件write
3. 关闭文件close
2 #include<string.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<unistd.h>
7
8 int main(){
9 int fd=open("log.txt",O_WRONLY|O_CREAT,0644);
10 if(-1==fd){
11 perror("open fail");
12 return 1;
13 }
14 const char *str="hello world\\n";
15 write(fd,str,strlen(str));
close(fd)
16 return 0;
17 }
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
8 int main(){
9 int fd=open("log.txt",O_RDONLY|O_CREAT,0644);
10 if(-1==fd){
11 perror("open fail");
12 return 1;
13
14 }
15 char *buf[1024];
16 read(fd,buf,1024);
W> 17 printf("%s",buf);
18 //const char *str="hello world\\n";
19 //write(fd,str,strlen(str));
close(fd);
20 return 0;
21 }
总结:
上面的fopen,fclose,fread,fwrite都是C标准库中的库函数。
而,open,close,read,write属于系统提供的接口,为系统调用接口。
库函数与系统调用的关系,可以认为时一种上下级关系。即大多数库函数都是系统调用的封装。
三.文件描述符
1.系统管理文件的原理
我们如果想了解文件描述符的含义,首先我们得知道系统时如何管理文件的。
- 系统为什么要管理文件?
文件是由进程打开的,一个进程可以打开多个文件。理论上来说,多个进程可以打开一个文件,但是这样会导致文件内容混乱,一般不这样做。所以我们可以理解进程和文件的关系为1对多的关系。
系统中会有很多进程,也可能会打开很多的文件,为了使文件不会再系统中出现混乱,所以需要系统管理文件。
- 系统如何管理文件?
先描述和组织。
描述文件:Linux操作系统是由C语言编写的,所以操作系统描述文件是通过结构体来描述文件的。这个结构体名字为struct_file。
组织文件:系统用一种数据结构,将描述的文件链接起来。用链表,将每一个struct_file连接起来。
这样的管理方式就像系统对进程的管理方式一样。
- 文件与进程的关系?
文件是由进程打开的,并且一个进程可以打开多个文件。所以这个进程要与文件组织的链表要产生关联或者说有一种映射关系。由于进程和文件是一对多的关系,所以进程是用数组与文件组织的链表进行关联的。
所以我们得到了一张图:
2.文件描述符是什么
结论:文件描述符实际就是files_struct里数组的下标。
- 为什么上面代码会等于3。
因为Linux系统默认情况下会默认打开三个文件,标准输入,标准输出,标准错误。这三个文件一斤在数组中分配好了下标,分别是0,1,2。
3.文件描述符的分配规则
关闭文件描述符为0的文件时,再立马打开一个文件,我们发现,此时新打开文件的文件描述符为0。
文件描述符的分配规则:
对于进程来讲,默认的文件描述符从3开始分配。因为0,1,2默认分配给了标准输入,标准输出,标准错误。
但是,文件描述符分配时从最小的没有使用过的数组下标开始分配的。
四.如何理解Linux下一切皆文件
- 每一个硬件最主要的操作是读和写。但是每一个硬件实现读和写的实现方法不一样,例如:访问网卡和访问硬盘的方法不一样。
- Linux下一切皆文件,每一个硬件都可以用一个文件来描述,然后组织管理。
- 为什么说Linux下一切皆文件?
每一个硬件都会对应一个struct_file,都会有对应不同访问的方法。 OS看到的时虚拟层,都是一个个的文件(struct_file)。
五.总结,进程如何操作一个文件
- 进程打开一个文件
OS会在创建一个struct_file描述这个文件,再将该文件连接到系统管理文件的链表中。找到进程PCB指针file指向的files_struct里的数组。根据文件描述符的分配规则,往数组中填入struct_file的地址。
注意:并不是文件不存在,创建文件时才创建struct_file,而是打开文件时创建,因为之前没有打开过该文件。打开文件是将硬盘中的文件加载到内存。磁盘有自己的文件系统。
- 进程往文件写入数据
open系统调用:找到该进程,通过PCB找到files_struct,得到文件描述符fd。
write系统调用:通过文件描述符fd,找到该文件的结构体,即找到该文件。要写入数据,就是调用该文件的写入数据的函数指针,调用写入函数。
六.重定向
重定向符号">"。
1.重定向原理
printf是C语言的库函数,是往文件描述符为1的文件中打印数据。
重定向原理">":
内核将标准输出(stdout)文件描述符为1的文件关闭,再用O_WRONLY | O_CREAT | O_TRUNC 打开方式打开一个文件。按照文件描述符的分配规则,是从数组下标最小并且未使用的下标分配,将文件描述符1分配给了新打开的文件。但是这个操作是操作系统做的,在应用层并不知道标准输出被替换了,还会继续往文件描述符为1的文件打印。
追加重定向原理">>":
和重定向原理相同,只是打开文件的方式不同,使用的是O_WRONLY | O_CREAT | O_APPEND方式打开的文件。
输入重定向"<":
内核关闭文件描述符为0的文件,将标准输入关闭。用O_RDONLY 方式打开文件。 按照文件描述符的分配规则,将文件描述符0分配给了新打开的文件。然后会读取文件描述符为0文件的内容。
重定向原理示意图:
2.使用dup2系统调用
实现重定向需要想将文件描述符为1的文件关闭,再打开一个文件。
使用dup2系统调用,不需要先关闭文件描述符为1的文件。
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6
7 int main(){
8 //close(1);
9 int fd=open("log.txt",O_WRONLY|O_CREAT,0644);
10
11 if(-1==fd){
12 perror("open fail");
13 return 1;
14 }
15 dup2(fd,1);//将数组下标为fd位置内容拷贝给数组下标1处
16 close(fd); //防止文件描述符泄漏
17 printf("%d\\n",fd);
18 fflush(stdout);
19 close(fd);
20
21 return 0;
22 }
七.理解FILE
FILE是C标准库里的,表示文件的一种类型。本质是一个结构体。
C语言中的标准输入,标准输出,标准错误为
- FILE* stdin
- FILE* stdout
- FILE* stderr
他们的类型都是FILE*的。
1.发现问题
重定向后:
为什么往stdout打印重定向后也往文件了打印?
- 打开文件实质上都需要通过文件描述符来打开文件,所以FILE结构体中一定含有文件描述符。
- printf时往stdout打印。
- 实际上stdin,stdout,stderr的FILE结构体中的文件描述符为0,1,2。当发生重定向时,stdout里的文件描述符还是1,但是1指向的文件发生了改变。
为什么重定向后,printf和fprintf(库函数)后输出了两次,但是write只输出了一次?
- 我们知道在C语言中将数据写入文件需要经过缓冲区,一般C库函数写入文件时是全缓冲方式的,写入显示器是行缓冲方式的。
- 一开始是将数据写到显示器上,为行缓冲方式,当重定向到普通文件后,缓冲方式变成了全缓冲。
- 当执行到fork前,printf和fprintf数据保存在缓冲区里。保存在缓冲区里的数据还是父进程的数据。
- 执行fork时,创建子进程,子进程会将父进程的数据发生写时拷贝。再刷新数据后,父子进程都将各自缓冲区的数据刷新出来了。
- write没有打印两份,说明,write并没有自己的缓冲区,已经将数据打印了。
没有重定向只输出一次是因为,行缓冲已经将数据冲缓冲区刷新出去了。创建子进程时,已经没有这个数据了。
上面结论还说明:缓冲区并不是操作系统提供的,而是语言提供的。如printf和fprintf缓冲区时有C语言提供的。
FILE库函数代码:
以上是关于Linux下的基础IO的主要内容,如果未能解决你的问题,请参考以下文章
java内存流:java.io.ByteArrayInputStreamjava.io.ByteArrayOutputStreamjava.io.CharArrayReaderjava.io(代码片段