目录
基础IO
重谈文件
重谈C语言的文件操作
系统文件IO
理解文件
文件描述符fd
0 & 1 & 2
文件描述符的分配规则
重定向
使用 dup2 系统调用
在minishell中添加重定向功能
缓冲区
理解缓冲区
再次理解缓冲区
基础IO
重谈文件
1、空文件,也要占磁盘的空间。
2、文件 = 内容 + 属性。
3、文件操作:等于对内容和对属性进行操作。
4、标定一个问题,必须使用:文件路径 + 文件名【唯一性】
5、如果没有指明对应文件路径,默认是当前路径进行文件访问。
6、当我们把fopen,fclose,fread,fwrite等接口写完后,代码编译之后,形成的二进制可执行文件后,但是没有运行,文件对应的操作有没有被执行呢?
答:没有被执行,本质:进程对文件的操作!
7、一个文件如果没有被打开,可以直接进行文件访问吗?
答:不能!一个文件要被访问,就必须先打开!
重谈C语言的文件操作
//打开文件 FILE * fopen ( const char * filename, const char * mode ); //关闭文件 int fclose ( FILE * stream );
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
-
向文件中写入数据:
#include <stdio.h>
#include <stdlib.h>
#define FILE_NAME "log.txt"
int main(){
// 打开文件.
FILE* pf = fopen(FILE_NAME, "w");
if(pf == NULL){
perror("fopen");
exit(1);
}
// 打开文件成功.
int count = 5;
while(count > 0){
fprintf(pf, "Hello FILE: %d\n", count);
--count;
}
// 关闭文件资源.
fclose(pf);
pf = NULL;
return 0;
}
从文件中读取数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_NAME "log.txt"
int main() {
FILE* pf = fopen(FILE_NAME, "r");
if(pf == NULL){
perror("fopen");
exit(1);
}
// 程序走到这里说明打开文件成功.
char buffer[64] = {0};
while(fgets(buffer, sizeof(buffer) - 1, pf) != NULL){
buffer[strlen(buffer) - 1] = 0;
puts(buffer);
}
// 关闭文件资源.
fclose(pf);
pf = NULL;
return 0;
}
系统文件IO
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
// C语言中fopen -> 对应的系统文件中的open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 创建文件
int creat(const char *pathname, mode_t mode);
// 打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// pathname: 要打开或创建的目标文件
// flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
// 参数:
// O_RDONLY: 只读打开
// O_WRONLY: 只写打开
// O_RDWR : 读,写打开
// 这三个常量,必须指定一个且只能指定一个
// O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
// O_APPEND: 追加写
// 返回值:
// 成功:新打开的文件描述符
// 失败:-1
// C语言中fclose -> 对应的系统文件中的close
#include <unistd.h>
// 关闭文件
int close(int fd);
// 写入数据系统接口.
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
// 读取数据系统接口.
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
-
使用write写入数据(以只写的方式打开)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "log.txt"
// 使用系统接口创建文件.
int main(){
// 这里执行umask将系统的umask设置为0,如果不进行设置那么 下面的0666 & 系统umask
umask(0);
// 创建文件,并且打开文件。
// 创建文件失败会返回1.
// O_WRONLY - 只写
// O_CREAT - 创建.
// O_TRUNC - 创建后清空文件内容.
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0){
// perror("open");
printf("%s\n", strerror(errno));
exit(1);
}
// 写入数据
int count = 5;
char outBuffer[64] = {0};
// 系统只接收文本数据,所有要将所有的数据统一转为文本,统一接入的文件中.
while(count > 0){
sprintf(outBuffer, "%s:%d\n", "Hello SYS_FILE", count--);
write(fd, outBuffer, strlen(outBuffer));
}
// 关闭对应文件资源.
close(fd);
return 0;
}
-
使用write写入数据(以追加的方式打开)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "log.txt"
// 使用系统接口创建文件.
int main(){
// 这里执行umask将系统的umask设置为0,如果不进行设置那么 下面的0666 & 系统umask
umask(0);
// 创建文件,并且打开文件。
// 创建文件失败会返回1.
// O_WRONLY - 只写
// O_CREAT - 创建.
// O_TRUNC - 创建后清空文件内容.
// O_APPEND - 追加选项,要删除O_TRUNC
// int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
// perror("open");
printf("%s\n", strerror(errno));
exit(1);
}
// 写入数据
int count = 5;
char outBuffer[64] = {0};
// 系统只接收文本数据,所有要将所有的数据统一转为文本,统一接入的文件中.
while(count > 0){
sprintf(outBuffer, "%s:%d\n", "Hello SYS_FILE", count--);
write(fd, outBuffer, strlen(outBuffer));
}
// 关闭对应文件资源.
close(fd);
return 0;
}
-
使用read读取数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "log.txt"
int main(){
// 设定系统的umask.
umask(0);
// 打开文件.
int fd = open(FILE_NAME, O_RDONLY);
if(fd < 0){
printf("%s\n", strerror(errno));
exit(1);
}
// 读取文件中的内容.
char buffer[1024] = {0};
ssize_t ret = read(fd, buffer, sizeof(buffer)-1);
if(ret > 0){
// 在这里读取完毕后需要进行处理一下:
// 因为在C语言中的 \0 = 0 = NULL 都是 0.
buffer[ret] = 0;
printf("%s\n", buffer);
}
close(fd);
return 0;
}
理解文件
文件操作的本质:进程和被打开文件的关系。
进程可以打开多个文件,系统中存在大量的被打开的文件,需要被操作系统OS进行管理,操作系统会对他们进行先描述,在组织,操作系统为了管理对应打开的文件,必定要为文件创建对应的内核数据结构i标识文件{struct file ()} 包含了文件大部分的属性。
文件描述符fd
通过对open函数的学习,我们知道了文件描述符就是一个小整数
0 & 1 & 2
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器
文件描述符的本质:是数组下标
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件 。
文件描述符的分配规则
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
printf("open df:%d\n", fd);
// 关闭文件.
close(fd);
return 0;
}
// 这段程序打印结果是3.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
close(0);
// close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
printf("open df:%d\n", fd);
// 关闭文件.
close(fd);
return 0;
}
// 这段程序打印结果是0.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// close(0);
close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
printf("open df:%d\n", fd);
// 关闭文件.
close(fd);
return 0;
}
// 这段程序打印结果不显示.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// close(0);
// close(1);
close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
printf("open df:%d\n", fd);
// 关闭文件.
close(fd);
return 0;
}
// 这段程序打印结果是2.
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符 .
文件描述符的规则是:从小到大,按照循序寻找最小的且没有被占用的fd进行分配。
重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// close(0);
close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
printf("open df:%d\n", fd);
// 关闭文件.
close(fd);
return 0;
}
// 这段程序打印结果不显示.
// 打印到了文件中.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// close(0);
close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
fprintf(stdout, "open df:%d\n", fd);
fflush(stdout);
// 关闭文件.
close(fd);
return 0;
}
// 观察代码,将内容打印到文件中.
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <
那重定向的本质是什么呢?
使用 dup2 系统调用
dup2可以将内核中的两个文件描述符,进行拷贝(拷贝的是内容).
// 接口.
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd); // (常用)
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
实例代码:打开重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// 这里全部屏蔽****
// close(0);
// close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0){
perror("open");
}
// 重定向:本来要打印到显示器的内容,打印到文件中.
dup2(fd,1);
fprintf(stdout, "open df:%d\n", fd);
fflush(stdout);
// 关闭文件.
close(fd);
return 0;
}
实例代码:追加重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
// 这里全部屏蔽****
// close(0);
// close(1);
// close(2);
// 设定子程序的umask
umask(0000);
// 打开文件,设定选择项,权限设置为666
// int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fd < 0){
perror("open");
}
// 重定向:本来要打印到显示器的内容,打印到文件中.
dup2(fd,1);
fprintf(stdout, "open df:%d\n", fd);
fflush(stdout);
// 关闭文件.
close(fd);
return 0;
}
实例代码:输入重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main(){
umask(0000);
int fd = open(FILE_NAME, O_RDONLY);
if(fd < 0){
perror("open");
}
// 重定向:将原本键盘输入,改变为文件输入.
dup2(fd, 0);
char buffer[64] = {0};
while(1){
printf("> ");
if(fgets(buffer, sizeof(buffer), stdin) == NULL) break;
printf("%s", buffer);
}
close(fd);
return 0;
}
在minishell中添加重定向功能
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#define NUM 1024
#define OPT_NUM 64
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define TrimSpace(pStart) \
do{ \
while(isspace(*pStart)) pStart++; \
}while(0)
char lineCommand[NUM]; // 数组buffer.
char* myArgv[OPT_NUM]; // 指针数组buffer.
int lastCode = 0;
int lastSig = 0;
int redirType = NONE_REDIR;
char* redirFile = NULL;
void CommandCheak(char* lineCommand){
// 确保指针不是空指针.
assert(lineCommand);
// 找到 指向头的指针,指向尾巴的指针.
char* pStart = lineCommand;
char* pEnd = pStart + strlen(lineCommand);
// 开始检查用户输入的指令,
while(pStart < pEnd){
if(*pStart == '>'){
*pStart = '\0';
pStart++;
// 这里直接判断是不是追加重定向.
if(*pStart == '>'){
redirType = APPEND_REDIR;
pStart++;
}
else{
redirType = OUTPUT_REDIR;
}
TrimSpace(pStart);
redirFile = pStart;
break;
}
else if(*pStart == '<'){
*pStart = '\0';
pStart++;
TrimSpace(pStart);
// 填写重定向信息:
redirType = INPUT_REDIR;
redirFile = pStart;
break;
}
else{
// 条件不满足,继续往下前进.
pStart++;
}
}
}
int main(){
// 循环shell.
while(1){
/* 初始化 */
redirFile = NULL;
redirType = NONE_REDIR;
/* 模式输出shell提示符: */
printf("用户名@主机名 当前路径#:");
fflush(stdout); // 立即刷新.
/* 获取用户输入,例如:ls -a -l 这种 */
const char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
(void)s;
/* 打印发现会有两个\n - 这里需要进行处理一下 */
lineCommand[strlen(lineCommand) - 1] = 0;
#if 0
printf("%s\n", lineCommand);
#endif
// 命令检查.
CommandCheak(lineCommand);
/* 字符获取完成,需要进行分割 */
myArgv[0] = strtok(lineCommand, " ");
int i = 1;
// ls 特殊处理.
if(myArgv[0] != NULL && strcmp(myArgv[0], "ls") == 0){
myArgv[i++] =(char*)"--color=auto";
}
while(myArgv[i++] = strtok(NULL, " "));
// cd 特殊处理.
if(myArgv[0] != NULL && myArgv[1] && strcmp(myArgv[0], "cd") == 0){
chdir(myArgv[1]);
continue;
}
// echo 特殊处理.
if(myArgv[0] != NULL && myArgv[1] != NULL && strcmp(myArgv[0], "echo") == 0){
if(strcmp(myArgv[1], "$?") == 0){
printf("%d %d\n", lastCode, lastSig);
}
else{
printf("%s\n",myArgv[1]);
}
}
#if 0
i = 0;
while(myArgv[i] != NULL){
printf("%s\n", myArgv[i]);
i++;
}
#endif
/* 子程序执行对应的命令 */
pid_t id = fork();
assert(id != -1);
// 子程序 - 执行.
if(id == 0){
// 因为命令是子程序执行的,真正重定向的工作一定是子程序来完成的.
// 如何重定向,是父进程要给子程序提供信息.
// 重定向不会影响父进程.
switch(redirType){
case NONE_REDIR:
// 这里什么都不需要做.
break;
case INPUT_REDIR:
{
// 打开文件.
int fd = open(redirFile,O_RDONLY);
if(fd < 0){
perror("open");
exit(2);
}
// 重定向文件更改.
dup2(fd, 0);
}
break;
case OUTPUT_REDIR:
case APPEND_REDIR:
{
// 去掉系统的umask.
umask(0000);
int flags = O_WRONLY | O_CREAT;
if(redirType == APPEND_REDIR){
flags |= O_APPEND;
}
else{
flags |= O_TRUNC;
}
// 打开文件.
int fd = open(redirFile, flags, 0666);
if(fd < 0){
perror("open");
exit(2);
}
dup2(fd, 1);
}
break;
default:
printf("Bug?\n");
break;
}
execvp(myArgv[0], myArgv);
exit(1);
}
/* 父进程监控 */
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0){
(void)ret;
lastCode = ((status >> 8) & 0xFF);
lastSig = (status & 0x7F);
}
}
return 0;
}
缓冲区
int main(){
// 打印信息:C函数的接口.
printf("Hello Printf!\n");
fprintf(stdout, "Hello Fprintf!\n");
fputs("Hello Fputs!\n", stdout);
// 打印信息,系统的接口.
const char* msg = "Hello System Write!\n";
write(1, msg, strlen(msg));
return 0;
}
// 现象:
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell // 打印4次
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello System Write!
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell > log.txt
[ShaXiang@VM-8-14-centos C_LessonExercise]$ cat log.txt
Hello System Write! // 打印4次
Hello Printf!
Hello Fprintf!
Hello Fputs!
int main(){
// 打印信息:C函数的接口.
printf("Hello Printf!\n");
fprintf(stdout, "Hello Fprintf!\n");
fputs("Hello Fputs!\n", stdout);
// 打印信息,系统的接口.
const char* msg = "Hello System Write!\n";
write(1, msg, strlen(msg));
fork();
return 0;
}
// 现象
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello System Write!
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell > log.txt
[ShaXiang@VM-8-14-centos C_LessonExercise]$ cat log.txt
Hello System Write!
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello Printf!
Hello Fprintf!
Hello Fputs!
理解缓冲区
缓冲区就是一段内存空间。
一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据 的缓冲方式由行缓冲变成了全缓冲。
而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后但是进程退出之后,会统一刷新,写入文件当中。
但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。
write 没有变化,说明没有所谓的缓冲 。
综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区, 都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是 C,所以由C标准库提供 。
int main(){
// 打印信息:C函数的接口.
printf("Hello Printf!\n");
fprintf(stdout, "Hello Fprintf!\n");
fputs("Hello Fputs!\n", stdout);
// 解释:
// 代码结束之前,进行创建子进程:
// 1、如果我们没有进行重定向(>)我们看到了4条消息,sydout默认使用的是行刷新策略,在进程fork之前,三条C语言打印函数将数据进行打印输出,到显示器(外设上),FILE内部就不存在对应的数据了。
// 2、如果我们进行了重定向(>)写入文件不在是显示器,而是普通的文件,采用的刷新策略是全缓冲,之前的3条C语言函数虽然带了\n,但是不足以将stdout缓冲区写满,数据并没有进行刷新。在执行fork()的时候,stdout属于父进程,创建子进程的时候,紧接着就是进程的退出,谁先退出,一定要进行缓冲区刷新(就是修改)。这是执行写时拷贝,数据最终会显示两份。
// write为什么没有呢?
// 上面的过程和write无关,write没有FILE,而是使用的fd,就是没有C语言提供的缓冲区。
// 打印信息,系统的接口.
const char* msg = "Hello System Write!\n";
write(1, msg, strlen(msg));
return 0;
}
再次理解缓冲区
模拟C语言实现一个缓冲区的代码来理解:
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4
// 自定义文件类型
typedef struct _FILE{
int _flags; // 刷新方式.
int _fileno;
int _capacity; // buffer的容量.
int _size; // buffer的已使用量.
char _buffer[SIZE];
}_FILE;
// 自定义文件接口.
_FILE* _Open (const char* pathName, const char* mode);
void _Fwrite(const void* ptr, int num, _FILE* fp);
void _Fclose(_FILE* fp);
#include "myStdio.h"
// 自定义文件接口.
_FILE* _Open (const char* pathName, const char* mode){
// 判断模式处理.
int flags = 0;
if(strcmp(mode, "r") == 0){
flags |= O_RDONLY;
}
else if(strcmp(mode, "w") == 0){
flags |= (O_WRONLY | O_CREAT | O_TRUNC);
}
else if(strcmp(mode, "a") == 0){
flags |= (O_WRONLY | O_CREAT | O_APPEND);
}
else{
// TODO.
}
// 打开文件接口处理.
int fd = 0;
if(flags & O_RDONLY)
fd = open(pathName, flags);
else
fd = open(pathName, flags, 0666);
if(fd < 0){
printf("测试!\n");
const char* strErr = strerror(errno);
write(1, strErr, strlen(strErr));
return NULL;
}
// 文件打开成功,申请文件空间.
_FILE* fp = (_FILE*)malloc(sizeof(_FILE));
if(fp == NULL){
const char* strErr = strerror(errno);
write(1, strErr, strlen(strErr));
return NULL;
}
// 默认初始化.
fp->_flags = SYNC_LINE; // 默认设置为行刷新.
fp->_fileno = fd;
fp->_capacity = SIZE;
fp->_size = 0;
memset(fp->_buffer, 0, SIZE);
return fp;
}
void _Fwrite(const void* ptr, int num, _FILE* fp){
// 1.写入到缓冲区中.
memcpy(fp->_buffer + fp->_size, ptr, num);
fp->_size += num;
// 2.判断是否刷新.
if(fp->_flags == SYNC_NOW){
write(fp->_fileno, fp->_buffer, fp->_size);
fp->_size = 0;
}
else if(fp->_flags == SYNC_LINE){
if(fp->_buffer[fp->_size - 1] == '\n'){
write(fp->_fileno, fp->_buffer, fp->_size);
fp->_size = 0;
}
}
else if(fp->_flags == SYNC_FULL){
if(fp->_size == fp->_capacity){
write(fp->_fileno, fp->_buffer, fp->_size);
fp->_size = 0;
}
}
else{
// TODO.
}
}
void _Fflush(_FILE* fp){
if(fp->_size > 0){
write(fp->_fileno, fp->_buffer, fp->_size);
fsync(fp->_fileno);
}
}
void _Fclose(_FILE* fp){
_Fflush(fp);
close(fp->_fileno);
}
#include "myStdio.h"
int main(){
_FILE* pf = _Open("log.txt", "w");
if(pf == NULL){
return 1;
}
const char* msg = "Hello ShaXiang! ";
int cnt = 10;
while(1)
{
_Fwrite(msg, strlen(msg), pf);
sleep(1);
if(cnt == 0)
break;
cnt--;
}
_Fclose(pf);
return 0;
}