基础IO
C语言文件操作函数汇总
下面是C语言的文件操作函数的接口:
对文件进行写入操作:
1 #include<stdio.h>
2
3 int main()
4 {
5 FILE* fp =fopen("t.txt","w");
6 if(fp == NULL)
7 {
8 perror("fopen fail\n");
9 return 1;
10 }
11
12 fputs("亚索\n",fp);
13 fclose(fp);
14 return 0;
15 }
cat 看一下就已经写入成功了。
对文件读取操作:
1 #include<stdio.h>
2
3 int main()
4 {
5 FILE* fp =fopen("t.txt","r");
6 if(fp == NULL)
7 {
8 perror("fopen fail\n");
9 return 1;
10 }
11 char buffer[64];
12 fgets(buffer,sizeof(buffer),fp);
13 printf("%s",buffer);
14 fclose(fp);
15 return 0;
16 }
运行程序:刚刚写入的就读取并打印在显示器上了。
C的文件操作就简单使用一下,后面会补上关于文件操作的。
默认打开的3个输入输出流
进程在运行的时候都会打开默认打开3个输入输出流,分别是标准输入流,标准输出流,标准错误流,对应到C语言中就是stdin,stdout,stderr,stdin对应的设备是键盘,stdout和stderr对应的设备是显示器。
查看man手册我们课题看到这3个都是FILE*
所以当C程序被运行起来,OS就会打开这3个输入输出流,我们就可以调用printf类似的函数对显示器和键盘进行对用的输入输出的操作了。
系统IO
操作文件除了语言提供的接口,我们还可以使用系统调用接口。上面的C语言中的文件接口函数叫库函数。在前面的讲系统时画的一张图:
C的库函数就是对系统调用进行封装,方便二次开发。
open函数
open的函数原型:
int open(const char *pathname, int flags, mode_t mode);
open的头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
open函数的第一个参数
pathname:要打开或创建的目标文件
当pathname以路径的方式给出,则但需要创建文件时在pathname路径下进行创建
若是以文件名方式给出,则但需要创建文件时,默认在当前路径。
open函数的第二个参数
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构flags。
常用的选项:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
打开文件时可以传入多个参数,可以用“ | ”隔开。
例如:用只写的方式打开文件当文件不存在时
O_WRONLY | O_CREAT
open函数的第三个参数
mode:表示创建文件的默认权限
但实际上创建出来的文件还会受到umask的影响,实际创建的文件权限为:mode&(~umask),umask的默认值一般是0002.若想不受影响,则需要在创建文件前将umask的值设为0
umask(0)
open函数的返回值
我们来打印看看文件描述符:
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5
6
7 int main()
8 {
9
10 umask(0);
11 int fd1 = open("g.txt1",O_RDONLY | O_CREAT,0666);
12 int fd2 = open("g.txt2",O_RDONLY | O_CREAT,0666);
13 int fd3 = open("g.txt3",O_RDONLY | O_CREAT,0666);
14 int fd4 = open("g.txt4",O_RDONLY | O_CREAT,0666);
15 printf("fd1:%d\n",fd1);
16 printf("fd2:%d\n",fd2);
17 printf("fd3:%d\n",fd3);
18 printf("fd4:%d\n",fd4);
19 return 0;
20 }
如果打开一个不存在的文件呢?
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5
6 int main()
7 {
8 int fd = open("x.txt",O_RDONLY);
9 printf("fd:%d\n",fd);
10
11 return 0;
12 }
运行直接是返回-1.
文件描述符
文件是由进程运行时打开的,一个进程可以打开多个文件。在系统中可能存在大量的进程,那系统中也存在很多文件,为了区分已经打开的文件属于哪个特定的进程,所以还要建立进程和文件的对应关系。
当文件加载到内存时,系统会创建struct flie,并用双链表连接起来,同时还有file_struct,而file_struct中放的是结构体指针数组,每个每个进程都有一个指针file*,指向file_struct.Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2. 0,1,2对应的物理设备一般是:键盘,显示器,显示器,从3开始指向新的文件。所以本质上文件描述符就是该数组的下标。因此,只要拿着文件描述符就可以找到对应的文件。
文件描述符的规则
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 {
9 int fd = open("t.txt",O_RDONLY);
10 if(fd < 0)
11 {
12 perror("open");
13 return 1;
14 }
15
16 printf("fd:%d\n",fd);
17 close(fd);
18 return 0;
19 }
先关闭0
close(0)
//关闭2试试
close(2);
综上可以得出结果:文件描述符的规则在file_struct数组中找到当前没有被使用的最小的一个下标,作为新的文件描述符。
重定向
先来一段代码:
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include<unistd.h>
6
7 int main()
8 {
9 close(1);
10 int fd = open("p.txt",O_WRONLY|O_CREAT,00644);
11 if(fd < 0)
12 {
13 perror("open");
14 return 1;
15 }
16
17 printf("this is linux!\n");
18 fflush(stdout);
19
20 close(fd);
21 return 0;
22 }
我们关掉1,本来时打印在显示器上的却在文件中,这就是重定向。
重定向的本质
关闭1时,1就不再指向标准输出了而是指向了新的文件,此时往显示器上输出就会在新的文件中。
dup2函数
我们直接把3的内容拷贝到2中,也就完成了重定向。Linux提供的系统调用就是dup2这个函数。
dup2函数:
int dup2(int oldfd, int newfd);
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 {
9 int fd = open("y.txt",O_WRONLY|O_CREAT,00644);
10 if(fd < 0)
11 {
12 perror("open");
13 return 1;
14 }
15 close(1);
16 dup2(fd,1);
17
18 printf("this is linux!\n");
19 fprintf(stdout,"hello fprintf\n");
20
21 close(fd);
22 return 0;
23 }
数据被输入到了y.txt中了,完成了重定向
FILE
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7 int main()
8 {
9 const char *msg0="hello printf\n";
10 const char *msg1="hello fwrite\n";
11 const char *msg2="hello write\n";
12 printf("%s", msg0);
13 fwrite(msg1, strlen(msg0), 1, stdout);
14 write(1, msg2, strlen(msg2));
15 fork();
16 return 0;
17 }
程序运行的结果:
对程序进行重定向
我们发现只有系统调用的write打印了1遍,printf,fwrite都输出了2遍。一般C库函数写入文件是全缓冲,写入显示器是行缓冲
重定向影响了缓冲的方式,重定向到普通文件时,数据的缓冲方式由全缓冲变成了行缓冲。我们放入到缓冲区的数据不会被立即刷新,甚至fork之后。
到进程退出后会统一刷新,写入文件中,但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,就产生两份数据。write没有变化就说明没有缓冲。
注意:
这里说的缓冲区都是用户级缓冲区。这个缓冲区是有C语言库函数提供的
感兴趣的可以研究FILE的结构体。
本篇文章暂时到这里就结束了。由于博主水平有限,如有错误,还请指出,万分感谢!!!