本章开始,我们将进入Linux文件相关的学习与操作,从复习回顾C语言的文件操作接口,再从操作系统角度出发,学习系统调用接口。再了解虚拟文件系统,内核管理文件的数据结构。最后通过学习的操作文件的系统接口来模拟实现C语言的文件操作接口,最后对之前实现的shell进程改进。目标已经确定,接下来就要搬好小板凳,准备开讲了…
chdir
可以更改工作路径。chdir
就可以更改,哪个进程调用这个函数就更改哪个进程的当前路径。在源代码路径下是不对的,更准确的说法是在当前进程所对应的路径下。只不过默认的一个进程的工作路径是在,当前自己所处的路径,不过路径是可以改的。所以cd、pwd查看的时候,这个进程路径变化或者不变,,说白了就是在更改自己进程的当前路径。
在C语言中,fopen
函数用于打开文件,并返回一个指向文件的指针。下面是常见的几个选项:
1. "r":以只读方式打开文件。文件必须已经存在才能成功打开。
2. "w":以写入方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则清空文件内容。
3. "a":以追加方式打开文件。如果文件不存在,则创建新文件;如果文件已存在,则在文件末尾追加内容。
4. "rb":以二进制只读方式打开文件。类似于 "r",但以二进制模式读取文件。
5. "wb":以二进制写入方式打开文件。类似于 "w",但以二进制模式写入文件。
6. "ab":以二进制追加方式打开文件。类似于 "a",但以二进制模式追加内容。
这些选项可以根据需要选择,用于读取、写入或追加文件的内容。此外,还可以使用其他选项和模式来进行更高级的文件操作,例如对文件进行读写结合(“r+”、“w+”、“a+”)或者以二进制方式进行读写操作(“rb+”、“wb+”、“ab+”)。请注意,打开文件后应该使用
fclose
函数关闭文件指针,以释放相关资源。
通过fopen打开文件,r选项,再通过fgets按行读取文件:
#include
#include
int main()
{
//1. 默认这个文件会在哪里形成呢?
//2. r, w, r+, w+, a, a+
//(r+ 和 w+ 都叫做既读又写,只不过w+多了一个功能,就是文件不存在会自动创建)
//3. 关注一下文件清空的问题
FILE* fp = fopen("log.txt", "r");
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64] = { 0 };
while(fgets(buffer, sizeof(buffer), fp) != NULL)
{
printf("echo : %s\n", buffer);
}
fclose(fp);
return 0;
}
#include
#include
int main()
{
FILE* fp = fopen("log.txt", "a"); //写入
if(fp == NULL)
{
perror("fopen");
return 1;
}
const char* msg = "Hello World";
int cnt = 1;
while(cnt <= 5)
{
fprintf(fp, "%s: %d\n", msg, cnt++);
}
fclose(fp);
return 0;
}
以w打开的时候文件就被清空了,如果不存在就创建。
所有的语言都对系统接口做了封装,封装了系统接口。
所有的跨平台的语言,必须通过自己的方案,对所有的系统接口做相关封装,设计好自己对应语言当中的IO接口。
任何一个语言,只要在同一个平台下跑,底层接口是不变的(Linux, Window…)。
我们为什么要学习系统级接口的原因:
open、 close、 read、 write
四个系统调用接口。
open函数的介绍:
两个同名函数,这是同名函数,难道是函数重载吗?我们来看看GPT的回答:
演示一下:
#include
#define PRINT_A 0x1 //0000 0001
#define PRINT_B 0x2 //0000 0010
#define PRINT_C 0x4 //0000 0100
#define PRINT_D 0x8 //0000 1000
#define PRINT_DFL 0x0
//等同于系统级open
void Show(int flags)
{
if(flags & PRINT_A) printf("hello A\n");
if(flags & PRINT_B) printf("hello B\n");
if(flags & PRINT_C) printf("hello C\n");
if(flags & PRINT_D) printf("hello D\n");
if(flags == PRINT_DFL) printf("hello Default\n");
}
int main()
{
printf("PRINT_DFL:\n");
Show(PRINT_DFL);
printf("PRINT_A\n");
Show(PRINT_A);
printf("PRINT_B\n");
Show(PRINT_B);
printf("PRINT_A 和 PRINT_B\n");
Show(PRINT_A | PRINT_B);
printf("PRINT_C 和 PRINT_D\n");
Show(PRINT_C | PRINT_D);
printf("PRINT all:\n");
Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D);
return 0;
}
上述代码,模拟了用宏做标记为的实现。
返回值:
O_TRUNC
是open
函数中的一个标志位,表示截断(truncate
)文件。w
方式打开文件的时候,会清空的!O_APPEND
是C语言中a
方式打开文件,追加!O_TRUNC标志的含义是,如果指定的文件已经存在,那么在打开该文件的同时会将其内容清空(即截断文件)。如果指定的文件不存在,则会创建一个新的空文件。
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
//有点像就近原则
umask(0);
//fopen("log.txt", "w"); //底层的调用的是open, O_WRONLY | O_CREAT | O_TRUN和这些选项,还要设置属性
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd : %d\n", fd);
int cnt = 0;
//const char* str = "Hello World\n";
const char* str = "aaa";
while(cnt < 2)
{
//不能带\0,这是C语言的用法, 文件不认识\0
write(fd, str, strlen(str));
cnt++;
}
return 0;
}
创建一个文件的话,这个文件要受到Linux权限的约束的。
不然权限是乱的,如图所示:
不然不同的地方使用这段代码,umask可能都不同,需要我们认为控制,若不这样,很可能造成不同地方跑同样的代码,创造出来的文件权限不同。
为什么我们write往文件里写的时候,写的长度为什么不带上最后的‘\0’呢?
在Linux中,有几个默认打开的文件是常见的:
我们可以通过read接口,读取键盘输入的内容:
int main()
{
char buffer[1024];
ssize_t s = read(0, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = '\0';
printf("echo: %s", buffer);
}
return 0;
}
//验证0,1,2就是标准IO
int main()
{
const char* str = "Hello World!\n";
write(1, str, strlen(str));
write(2, str, strlen(str));
return 0;
}
验证0, 1, 2 和stdin, stdout, stderr的对应关系:
//0,1,2和stdin, stdout, stderr的对应关系
int main()
{
printf("stdin: %d\n",stdin->_fileno);
printf("stdout: %d\n",stdout->_fileno);
printf("stderr: %d\n",stderr->_fileno);
return 0;
}
为什么fd是从3开始的?
我们看到stdin,stdout,stderr
三个都是FILE的指针,在我们之前学习C语言知道,FILE是个结构体,是描述文件的一个结构体。
0,1, 2, 3, 4, 5…其实是数组下标!凡是用fd返回的,用的都是系统接口,操作系统提供的返回值!
open/ read/write/close - 要么是得到fd要么用到fd。
一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构 — 先描述!
struct file
。read, write
等,一定传入了fd。0,1, 2 -> stdin, stdout, stderr -> 键盘,显示器,显示器(这些都是硬件!)也用上面将的struct file
来标识对应的文件吗??是的!!
如何知道这些struct file
对应的操作方法是不一样的?
struct file
的时候识别底层文件的类型拿到底层文件的读写方法,用自己的函数指针指向就可以了。struct file
。struct file
,然后将struct file
关联起来。file
的时候,具体指向底层哪个对应的设备完全取决于对应的读写方法指向的哪个方法。上层使用同一个对象,指针指向不同的对象,最终就能调用不同的方法,可以理解为多态的前身。