目录
一、C文件接口
二、系统文件I/O
1 .接口介绍
2 .open函数返回值
3 . 文件描述符fd
4 . 文件描述符的分配规则
5 .重定向
6 .使用 dup2 系统调用
7 .FILE
三、缓冲区
一、C文件接口
写文件:
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("myfile", "w");
if (!fp) {
printf("fopen error!\n");
}
const char* msg = "hello bit!\n";
int count = 5;
while (count--) {
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
读文件:
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("myfile", "r");
if (!fp) {
printf("fopen error!\n");
}
char buf[1024];
const char* msg = "hello bit!\n";
while (1) {
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if (s > 0) {
buf[s] = 0;
printf("%s", buf);
}
if (feof(fp)) {
break;
}
}
fclose(fp);
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
const char *msg = "hello fwrite\n";
fwrite(msg, strlen(msg), 1, stdout);
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
return 0;
}
C 默认会打开三个输入输出流,分别是 stdin, stdout, stderr仔细观察发现,这三个流的类型都是 FILE*, fopen 返回值类型,文件指针
二、系统文件I/O
文件等于内容加上属性,所以 对于文件的操作就是对于内容的操作和对其属性的操作,当我们没有使用文件时,它是保存在磁盘当中。一旦我们对文件操作时,他就会从磁盘 加载到内存当中。
我们在对文件进行操作的时候,文件需要内加载到内存当中,但是是否只有我们一个人在使用呢?也就是这个文件是否有多个人在对其进行打开操作?答案肯定是不可能只有一个人也就是一个进程在使用的,因为就连我们平时操作Linux时,我都能对一个文件进行多次打开。而且我在一个程序当中是可以打开多个文件的,那么我们可以得到一个结论: 进程和文件的对应关系是1:n。
并且,在Liunx下,一切皆文件。
综上,系统会打开多个进程,而一个进程又会操作多个文件,那么系统中会充斥非常多的文件,这些文件是如何被管理的呢?
加载文件:
首先,我们知道文件在没有被操作时是存储在磁盘当中的,只有在被调度时才会从磁盘加载到内存当中,然后呢?内存当中必然到处都是乱七八糟的文件,必须得有一个管理的操作。
看到管理大家必须的像是触发了关键词一样,那就是先描述,再组织。没错操作系统对于文件的管理如同进程一样,都是先描述再组织,那么它同样是有自己抽象出来的结构体用于装载自己的信息。
struct file
{
//文件属性
//文件各种关系
}
我们的文件内容与我们的管理并没有太多的关系,那么我们就让他存储在内存当中,甚至在刚准备打开文件的时候只需要将文件的各种属性告知操作系统都行,内容慢慢的加载。
文件是由操作系统打开的,也就是进程让操作系统打开的,那么这样我们的对于文件的操作也就变为了进程与文件的操作。
在系统当中,进程和文件都是被组织起来的数据结构,那么他们之间的交互就变成了两个结构体的操作------struct tast_struct和struct file。
所以整个文件加载到内存当中的过程就相当于把文件指针(struct file*)放进struct tast_struct结构体当中.
文件结构体里面只存有文件已经加载在内存当中的地址,也就是整个文件管理和内存管理的关系只有这样的联系,并且这个联系是随时能够被更改的。
那么这样做之后有什么好处呢?实现了文件管理和内存管理的解耦操作,也就是两者都互相并不关心彼此是如何操作的,两者之间都只需要一个固定的方式进行交互。
然后我们再认识一些系统调用接口:
写文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open");
return 1;
}
int count = 5;
const char* msg = "hello bit!\n";
int len = strlen(msg);
while (count--) {
write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数
据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
读文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
const char* msg = "hello bit!\n";
char buf[1024];
while (1) {
ssize_t s = read(fd, buf, strlen(msg));//类比write
if (s > 0) {
printf("%s", buf);
}
else {
break;
}
}
close(fd);
return 0;
}
1 .接口介绍
pathname: 要打开或创建的目标文件flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行 “或” 运算,构成 flags 。他是一个位图结构参数 :O_RDONLY : 只读打开O_WRONLY : 只写打开O_RDWR : 读,写打开这三个常量,必须指定一个且只能指定一个O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限O_APPEND : 追加写返回值:成功:新打开的文件描述符失败: -1
2 .open函数返回值
3 . 文件描述符fd
int _fileno;//封装的文件描述符
Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0 , 标准输出 1 , 标准错误 2.0,1,2 对应的物理设备一般是:键盘,显示器,显示器
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
4 . 文件描述符的分配规则
5 .重定向
这里有两种写法:
6 .使用 dup2 系统调用
其中两个参数表示:将oldfd去指向newfd,表示了关闭原来的newfd,成为现在的oldfd。也就是说将newfd位置的文件地址改为oldfd的文件地址。
7 .FILE
因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过 fd 访问的。所以 C 库当中的 FILE 结构体内部,必定封装了 fd。
我们可以通过下面代码得出FILE结构体里面的fd:
三、缓冲区
#include <stdio.h>
#include <string.h>
int main()
{
const char* msg0 = "hello printf\n";
const char* msg1 = "hello fwrite\n";
const char* msg2 = "hello write\n";
printf("%s", msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}
运行结果:
hello printfhello fwritehello write
hello writehello printfhello fwritehello printfhello fwrite
一般 C 库函数写入文件时是全缓冲的,而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至 fork 之后但是进程退出之后,会统一刷新,写入文件当中。但是 fork 的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。write 没有变化,说明没有所谓的缓冲。
1. 立即刷新2. 行刷新 ——显示器,照顾了用户的查看习惯3. 全缓冲 ——缓冲区写满,才刷新,用于普通文件特殊情况:进程退出,系统自动刷新,或者用相关函数进行强制刷新。
缓冲区:每一个文件都有自己对应的语言级缓冲区,在进行数据交换的时候提高效率。不仅提高了用户的效率,还提高了系统IO的效率。