3.Linux文件
# 1. 前置理论
- 所有输入/输出设备(如网络、磁盘和终端)都表现为文件,所有输入和输出都是通过读取和写入适当的文件来执行的。这种从硬件设备到文件的映射接口称为I/O,它使所有输入和输出都能够以统一的方式执行。
- 应用程序通过请求内核打开相应的文件,内核返回一个非负整数,称为描述符,用于在文件的所有后续操作中标识该文件。
- 操作系统从文件中将数据加载到内存是通过一个batch(n个字节构成),目的是可以以一种统一的方式读写。当读取时,超过了文件中的batch数,则会返回
EOF
(它并不代表文件的真实结尾,真实的字符数会小于读取的字符;文件末尾也不会存在EOF
这个标识符,文件中的batch数是由进程维护的,这个在写入时就已经计算出来了。读取时会根据进程中的信息来判断到底需要读取多少个batch) - VFS虚拟文件系统:进程中,在task_struct中存在files_struct结构体,而这个结构体包含了一个文件描述符数组fd_array,这是一个指针数组,所谓的文件描述即是这个数组的下标。数组每个元素指向了内存中对应文件的管理结构体struct file。在struct file中包含了对文件进行读写等操作的函数指针,可以指向不同的函数,满足了对不同文件实现不同方式的读写。实现不同外设的驱动可以通过修改这些函数指针指向的函数,特制相应的操作。

- 文件描述符分配规则:系统在fd_array中找一个最小的未被使用的数组下标分配给新创建打开的文件。
- 文件包括文件内容和文件属性,新创建的文件即使没有写入,也会在磁盘上占用一部分空间用来存储文件的属性信息。
# 2. C标准库提供的函数接口
C标准库通过FILE类型的结构体来维护文件,结构体中包含了文件描述符,若想打印出某个文件的描述符,可以通过stdout-> _fileno
这个语句来获得。我们常说的标准输入、标准输出、标准错误其本质也是由FILE *
指向的文件
stdin ——键盘,其FILE结构体中包含的文件描述符fd=0 stdout ——显示器,其FILE结构体中包含的文件描述符fd=1 stderr —— 显示器,其FILE结构体中包含的文件描述符fd=2
在C标准库中,可以使用以下几个函数来创建和读取文件:
fopen
#include <stdio.h> FILE *fopen(const char *path, const char *mode); // FILE *fp = fopen("./test","w+");
1
2
3path:文件的路径,例如
“/usr/test.c”
mode: 打开文件的方式。
r
以只读的方式打开存在的文件;r+
对存在的文件可读可写;w
创建和重新写入;w+
可读可写,若文件不存在则创建 ;a
创建和附加写入a+
读和附加写入
flose
int fclose(FILE *fp);
1关闭文件描述符为fp的文件。
- 写入函数
int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); int sprintf(char *str, const char *format, ...); int fputc(int c, FILE *stream); //带c的,会将传入字符转化为十进制的ASCII码,返回写入字符的ASCII码 int putc(int c, FILE *stream); int putchar(int c); //相当于fputc(char, stdout); // # 例子 //int main() // { // FILE *fp = fopen("./test.txt","w+"); // std::string str = "hello world\n"; // for(int i = 0; i < str.size(); i++) // { // // int a = fputc(str[i], stdout); // // int a = putc(str[i], stdout); // int a = putchar(str[i]); // std::cout << ":" << a << " "; // } // str[str.size()] = '\n'; // return 0; // } // # 例子 int fputs(const char *s, FILE *stream); int puts(const char *s); // const char *p = "hello world!\n"; // fputs(p, stdout); // puts(p);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 读取函数
int fgetc(FILE *stream); //错误返回EOF;使用例子:putc(fgetc(stdin), stdout); int getc(FILE *stream); int getchar(void); //默认从stdin中读取 char *fgets(char *s, int size, FILE *stream); char *gets(char *s); int fscanf(FILE *stream, const char *format, ...);
1
2
3
4
5
6
7
8
- 判断成功
int feof(FILE *stream); //检测EOF(end-of-file) int ferror(FILE *stream);//检测错误 int fileno(FILE *stream);//返回流文件描述符
1
2
3
# 3. Linux系统提供的调用接口
# 3.1 创建并读写
open
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags, mode_t mode);
1
2
3
4
5pathname: 文件打开的路径;
flags:必须包含以下任意一种模式
O_RDONLY
(只读),O_WRONLY
(只写), orO_RDWR
(读写)。可选包含:O_APPEND
、O_CREAT
、O_EXCL
(保证了文件是新创建的,否则open失败)、O_TRUNC
(清空文件中原来的内容)mode:设定了文件的权限。类似于
-r-xrwxrwx
。实际结果与umask有关。返回值:返回了打开或创建的文件描述符。出错返回-1. 本质是进程PCB中关联文件的指针数组(指向内存中的文件结构体)的下标
close
#include <unistd.h> int close(int fd);
1
2
write
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
1
2fd :写入文件的描述符。
buf:将要写入的文件内容。
count:需要写入字符的个数。
返回值:成功返回写入的字符数;失败返回-1。
read
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
1
2buf:将要传入内容的空间。
返回值:成功返回读到的字节数,并且更新指向文件内容的指针(会继续从这个指针指向的位置读取);失败返回-1。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = open("./test.txt", O_CREAT | O_RDWR, 0644);
char buf[1024] = {0};
if (fd < 0)
{
printf("open file failed\n");
exit(-1);
}
const char* Str = "hello world!\n";
write(fd, Str,strlen(Str)); //系统调用函数和C不同,不用传入'\0'标志结尾
write(fd, Str,strlen(Str));
fd = open("./test.txt", O_CREAT | O_RDWR, 0644); //需要重新打开这个文件
ssize_t reN = read(fd, buf,sizeof(buf) - 1);
if(reN >= 0)
{
//buf[reN] = '\0'; //将'\0'写入buf以供C函数读取。也可不写,因为数组初始化为0
printf("%s", buf);
}
else
printf("error\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[test@VM-12-4-centos test1]$ ./main
hello world!
hello world!
[test@VM-12-4-centos test1]$ cat test.txt
hello world!
hello world!
2
3
4
5
6
# 3.2 重定向
#include <unistd.h> int dup2(int oldfd, int newfd);
1
2newfd是oldfd的拷贝,即将oldfd维护的文件同时交给newfd维护,newfd之前的内容被覆盖,此时文件描述符newfd被用于维护oldfd所维护的内容。
注意:dup2重定向的是标准输出的内容,这在缓冲区会详细说明。
#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("./input.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
dup2(fd, 0);
char *p = "hello world\n";
write(0, p, strlen(p));
close(fd);
fd = open("./input.txt", O_CREAT | O_RDWR, 0644);
dup2(1, fd);
write(fd, p, strlen(p));
close(fd);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[test@VM-12-4-centos dup2]$ ./test
hello world
[test@VM-12-4-centos dup2]$ cat input.txt
hello world
2
3
4
> # linux该符号表示输出重定向,重定向标准输出,若要重定向标准错误,则
xx > my.txt 2>&1 # 将文件描述符1的内容拷贝到2中,此时2也指向1指向的文件
2