基础 IO
一.C文件IO相关接口
我们首先来看C文件的读写操作。
include <stdio.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
while(1);
fclose(fp);
return 0;
}
在这个程序中我们打开文件后一直while运行,这里的 fopen 只表明为 myfile ,我们通过另一个窗口输入 ls /proc/【进程id】 可以观察到正在运行进程的信息。
我们通过ps ajx | grep test 可以查看到当前进程 test 的运行pid,再通过 ls/proc/ 【进程id】 就可以查看到进程信息,其中 cwd 指明了当前的运行路劲。我们发现程序会记录下当前路径,当我们创建文件或者打开文件时,系统会在记录的路径下寻找打开。
1.1 fopen,fwrite,fclose
fopen:
pathname:要打开的文件名字符串
mode:访问文件的模式(可读可写追加)
fwrite:
ptr: 写入数据的指针
size:指针的大小
nmemb:读写数据项的个数
stream:读入到的文件
fclose:
stream:要关闭的文件
1.2 文件的权限模式
文件的权限分为 可读 可写 追加三种。
读模式下可以获取文件中的内容数据,写模式下可以对文件进行写入。
r 为只读模式,r+ 为读写模式。r+ 若打开的是空文件就会返回NULL,会保留原有的内容。
w为只写模式,w+为读写模式。w下不能读取文件内容,只能对文件进行写入,若当前文件不存在会在当前路径下创建该文件。w+下可以对文件进行读写,若文件不存在会创建新文件,对文件进行写入时会对文件进行清空操作。
a为追加模式,a在写入后无读权限,a的指针指向文件末尾,对文件进行内容追加。a+可以读取写入文件的内容,并且不会清空文件内容。
1.3 文件描述符
在Linux中,文件描述符是一种用于访问文件或输入输出的抽象概念。在进程PCB中,存在一个*files 指针指向一个文件结构体,文件结构类似于数组,会将文件进行排序,将文件分为0,1,2,3,4等数字,这些数字就代表了文件。我们只要拿着这些数组下标,就能对文件进行访问。
而其中比较特殊的是0,1,2 这三个数。0代表着输入文件 stdin ,1 代表输出文件 stdout ,3 代表错误文件 stderr。从3 开始就是用户文件。进程在运行时,会默认打开0,1,2 这三个文件。
1.4 C默认输入输出流
C默认会打开stdin stdout stderr 三个文件,这三个文件是封装Linux底层0,1,2 三个接口。当我们在使用 scanf 函数时会将内容存到 sdtin 中,printf 时会将打印内容存放到stdout 中,perror 时会存放到stderr 中。这三个流的类型都是 FILE* ,Linux的系统调用接口不会直接给用户使用,而是根据不同的语言进行不同的封装,这样就实现了多种语言操控Linux。
二.系统文件 I/O
根据上文描述 stdin stdout stderr 是C语言封装了Linux 的底层系统调用创造出来的。现在我们来讲解一下几个系统调用接口。
2.1 open write close
我们在用man手册时,若接口是系统调用,需要在前面加上数字2。
open:
pathname:要打开或创建的路径名称
flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
O_CREAT:文件不存在时,创建文件(不能单独使用O_RDONLY创建文件)
O_APPEND:追加写文件
若成功则返回新打开的文件描述符,若失败返回-1.
write:
fd:文件标识符,需要写入的文件。
buf:用数组来存储需要写入文件的内容。
count:写入文件内容的字节数大小。
若write成功则返回实际写入的字节数,若失败返回-1.
close:
fd:需要关闭的文件的文件标识符。
2.2 重定向
首先我们来讲述一下文件描述符的分配规则。
来看下面代码:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { close(0); //close(2); int fd = open("myfile", O_RDONLY); if(fd < 0){ perror("open"); return 1; } printf("fd: %d\n", fd); close(fd); return 0; }
当我们尝试关闭了0或者2时,我们发现输出的结果为 fd:0 或者 fd:2 ,可见,文件描述符在分配时会找到数组中最小的一个小标,作为新的文件描述符。
若我们关闭关闭 1 时,当我们输入想输入到显示器上的内容输入到了文件中,这种现象就叫做输出重定向。常见的重定向有 > >> <
>:
echo "hello world" > test.txt 将hello world 写入文件中,若文件不存在则创建,写入后会对文件进行覆盖。
>>:
echo “hello world” >> test.txt 将hello world 追加写入文件中,指针指向文件末尾,不会对文件内容覆盖。
<:
cat < test.txt 对文件进行命令输入。如此示例是对文件内容进行打印。
重定向完成后,我们从键盘读入的数据就会直接被输入到文件中。
重定向的本质就是修改特定的文件描述符。
2.3 dup2系统调用
dup2:
dup2 是一个系统调用函数,其作用是复制文件描述符,到指定的新文件描述符。该函数会返回新的文件描述符,若出错就返回-1.
代码示例:
include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
lose(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
三. 理解文件缓冲区
缓冲区是内存空间的一部分,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或者输出的数据。我们在读写文件时,如果不开辟对文件操作的缓冲区,直接通过系统调用进行读写操作,那么每次对文件进行操作时,CPU都要进行状态切换,从用户空间到内核空间,会产生一定时间消耗。为了减少系统调用次数,我们从先将数据放到缓冲区中,等产生一定量之后再进行刷新缓冲区,这样可以减少读写的次数。在数据刷新到缓冲区之前,CPU也可以做其他的工作。
标准I/O 提供了三种类型的缓冲区,全缓冲,行缓冲,无缓冲区。
全缓冲区:要求填满缓冲区后才进行I/O操作,对于磁盘文件操作通常使用全缓冲的方式访问。
行缓冲区:当输入输出遇到换行符时,会被刷新,若是超过了固定的1024字符也会进行刷新。
无缓冲区:直接进行系统调用,通过是stderr,这样可以更快地将错误信息显示到屏幕。
四. Linux下一切皆文件
在Linux下所有的硬件设备,目录,进程通信,系统配置被抽象成文件。这样对于用户来说不管操作硬盘,键盘都可以用统一的命令进行操作,就像处理普通文档一样,我们不需要再去专门学习这些单独的设备的各种用法,可以用统一的一套API就可以解决问题。