一、文件操作回顾
1.代码回顾
include <stdio.h>
int main()
{
FILE* fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
2.提炼对文件的理解,初期
打开文件,实质是进程打开文件。
文件没有被打开时,他在磁盘上
进程可以打开很多文件,
文件=属性+内容
> | “w”权限打开文件 |
>> | “a”权限打开文件 |
3.理解文件
1.操作文件,实质是进程在操作文件。进程和文件的关系。
文件->在磁盘上->磁盘是外设->外设是硬件->向文件写入,本质上是向硬件中写入
->用户没有权利向硬件中写入,OS是硬件的管理者->通过OS向文件中写入
->OS必须给我们提供系统调用(OS不允许用户访问)->访问文件,我们也可以使用系统调用!
->fopen/fwrite/fread/fprintf/scanf/printf/cin/cout ?->我们使用的语言都是对系统调用接口的封装!
二、使用系统调用
1.open函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数打开失败时返回-1, 成功时返回一个文件描述符
1.flags: (位图)
32比特位。用比特位来进行标志位的传递,是OS设计很多系统调用接口的常见方法。
系统默认掩码0002。
参数说明
O_WRONLY | 写方式打开 |
O_CREAT | 不存在就创建 |
O_TRUNC | 存在就清空 |
O_APPEND | 追加写入 |
2.id说明
0 |
标准输入,键盘 |
1 | 标准输出,显示器 |
2 | 标准错误,显示器 |
下面的数字则是用户创建的文件描述符 |
0 1 2默认打开 --- linux一切皆文件
而C语言中
FILE是C语言提供的结构体类型,一定封装文件描述符
FILE* stdin | 标准输入 |
FILE* stdout | 标准输出 |
FILE* stderr | 标准错误 |
int main()
{
FILE* fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return 1;
}
int fd = fp->_fileno;
printf("id/fd:%d\n", fd);
fwrite("hello", 1, 5, fp);
fclose(fp);
return0;
}
文件描述符的本质: 内核的进程:文件映射关系数组的下标。
证明:内核代码
2.标记位传参
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void Print(int flag)
{
if(flag&ONE)
printf("ONE\n");
if(flag&TWO)
printf("TWO\n");
if(flag&THREE)
printf("THREE\n");
if(flag&FOUR)
printf("FOUR\n");
}
int main()
{
Print(ONE);
printf("\n");
Print(ONE|TWO);
printf("\n");
Print(ONE|TWO|THREE);
printf("\n");
Print(ONE|TWO|THREE|FOUR);
printf("\n");
}
3.write函数
ssize_t write(int fd, const void *buf, size_t count);
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//写方式打开|不存在就创建|存在就清空
if(fd < 0)
{
perror("open");
return 1;
}
const char* message = "hello linux txt!\n";
write(fd, message, strlen(message));
return 0;
}
三、输出重定向
重定向的本质,是在内核中改变文件描述符表示特定下标的内容
1.本来应该输出到显示器上内容,却输出到了文件中
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(1);
//这里的fd分配规则是:最小的,没有被占用的文件描述符
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0) { perror("open"); return 1;}
//输出重定向
printf("fd:%d\n", fd);
printf("fd:%d\n", fd);
printf("fd:%d\n", fd);
printf("fd:%d\n", fd);
printf("fd:%d\n", fd);
fprintf(stdout, "hello fprintf!\n");
const char* s = "hello write!\n";
fwrite(s, strlen(s), 1, stdout);
fflush(stdout);
close(fd);
return 0;
}
2.本来应该从键盘输入的内容,却从文件中输入了
int main()
{
close(0);
int fd = open("log.txt",O_RDONLY);
if(fd < 0) return 1;
printf("fd: %d\n", fd);
char buffer[64];
fgets(buffer, sizeof buffer, stdin);
printf("%s\n",buffer);
return 0;
}
1.dup
in(int argc, char *argv[])
{
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0) {perror("open"); return 1;}
dup2(fd, 1);
fprintf(stdout, "%s\n",argv[1]); //stdout -> 1 -> screen
return 0;
}
四、缓冲区
1.什么是缓冲区?
就是一段内存空间 (这个空间谁提供?) --- 初步理解
2.为什么需要缓冲区
主要是为了提高用户的响应速度!
缓冲区的刷新策略:
1. 立即刷新
2. 行刷新(行缓冲) \n
3. 满刷新(全缓冲)
特殊情况
1. 用户强制刷新(fflush)
2. 进程退出
C语言结构体FILE 不仅封装了文件描述符,还封装了缓冲区。
当往显示器上打印时,默认行刷新。
当往磁盘上打印时,默认全刷新。
所以当我们重定向将内容打印到文件时,如果在结尾处加上fork(),因为刷新也算改变,子进程默认发生写时拷贝,从而将数据copy两份。而我们只要在fork()前加上一个fflush(stdout),将数据提前强制刷新,就没有问题了。系统调用函数则也没有这个问题,因为数据是去内核缓冲区,到操作系统内核了。
1 - stdout 2 - stderr。 1和2对应的都是显示器文件,但是他们两个是不同的,如同认为,同一个显示器文件,被打开了2次。