目录
预备知识
复习C文件IO相关操作
printf相关函数
fprintf
snprintf
读取文件
系统文件IO操作
open函数
umask()函数
open函数返回值
1.你真的理解文件原理和操作了吗? 不是语言问题,是系统问题
2.是不是只有C/C++有文件操作呢? 不是,Java,python,go都有,他们的文件操作方法是不一样的?如何处理这种现象呢? 有没有一种统一的视角,看待所有的语言文件从操作呢?
3.操作文件的时候,第一件事情,就是打开文件,打开文件时做什么呢?如何理解呢?
4. 文件 = 内容 + 属性 -> 针对文件的操作,对内容的操作,对属性的操作
5.当文件没有被操作的时候,文件一般会 在什么位置? 磁盘!
6.当我们对文件进行操作的时候, 文件需要在哪里? 内存,为什么呢? 因为 CPU和内存交互
7.当我们对文件进行操作的时候,文件需要提前被load到内存,load是 内容还是属性? 属性,因为一个文件至少得有属性
8.当我们对文件进行操作的时候,文件需要提前被load到内存,是不是只有你一个人在load呢?不是, 内存中一定存在大量的不同文件的属性
9.所以综上, 打开文件本质就是将需要的文件属性加载到内存中,OS内部一定会同时存在大量的被打开的文件,那么操作系统要不要管理这些被打开的文件呢? 要, OS需要先描述,在组织。
先描述,构建在内存中的文件结构体struct file{struct file* next},就可以从磁盘来,被打开的文件
a.每一个被打开的文件,都要在OS内对应文件对象的struct结构体,可以将所有的struct file结构体用某种数据结构链接起来--,在OS内部,对被打开的文件进行管理,就被转换成为了对链表的增删查改。
结论:文件被打开,OS要为被打开的文件,创建对应的内核数据结构
struct file
{
//各种属性
//各种链接关系
}
10.文件其实可以被分开两大类: 磁盘文件,被打开的文件( 内存文件)
11.文件被打开,是谁打开呢?OS,但是是谁让OS打开的呢?用户(进程为代表的)
12.我们之前的所有的文件操作,都是进程和被打开文件的关系
13.都是进程和被打开文件的关系: struct stak_struct和 struct file
下面是用C语言实现对文件log.txt进行操作:
#include
#define LOG "log.txt"
int main()
{
// w:默认写方式打开文件,如果文件不存在,就创建它
// 默认如果只是打开,文件内容会自动被清空
// 同时,每次进行写入的时候,都会从最开始进行写入
FILE *fp = fopen(LOG, "w");
if (fp == NULL)
{
perror("fopen fail");
return 1;
}
// 正常进行文件操作
const char *msg = "hello linux\n";
int cnt = 5;
while (cnt)
{
fputs(msg, fp);
cnt--;
}
fclose(fp); // 关闭文件
return 0;
}
成功创建了log.txt文件,打开文件
printf 默认是向显示器读取
int main()
{
// w:默认写方式打开文件,如果文件不存在,就创建它
// 1. 默认如果只是打开,文件内容会自动被清空
// 2. 同时,每次进行写入的时候,都会从最开始进行写入
FILE *fp = fopen(LOG, "w");
if (fp == NULL)
{
perror("fopen fail");
return 1;
}
// 正常进行文件操作
const char *msg = "hello linux";
int cnt = 5;
while (cnt)
{
fprintf(fp, "%s:%d:phw\n", msg, cnt);
cnt--;
}
fclose(fp); // 关闭文件
return 0;
}
fprintf(stdout, "%s:%d:phw\n", msg, cnt); // Linux一切皆文件,stdout也对应一个文件,显示器文件
写入到buffer缓冲里
下面测试一下将msg改成phw
这里得出结论, “w"为覆盖式写入
追加式写入"a"选项
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写O_TRUNC:清空文件内容
返回值:
成功:新打开的文件描述符
失败:-1
下面是标志位的举例程序:
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0X8
#define FIVE 0X10
void Print(int flags)
{
if (flags & ONE)
printf("hello 1\n");
if (flags & TWO)
printf("hello 2\n");
if (flags & THREE)
printf("hello 3\n");
if (flags & FOUR)
printf("hello 4\n");
if (flags & FIVE)
printf("hello 5\n");
}
int main()
{
printf("-------------------\n");
printf(ONE);
printf("-------------------\n");
printf(TWO);
printf("-------------------\n");
printf(FOUR);
printf("-------------------\n");
printf(ONE | TWO);
printf("-------------------\n");
printf(ONE|TWO|THREE);
printf("-------------------\n");
printf(ONE|TWO|THREE|FOUR|FIVE);
printf("-------------------\n");
return 0;
}
open函数测试:
#include
#include
#include
#include
#define LOG "log2/txt"
// 系统方案
int main()
{
int fd = open(LOG, O_WRONLY);
printf("fd:%d\n", fd);
return 0;
}
文件不存在
#include
#include
#include
#include
#include
#include
#include
#define LOG "log2/txt"
// 系统方案
int main()
{
int fd = open(LOG, O_WRONLY |O_CREAT);
if (fd == -1)
{
printf("fd:%d,errno:%d,errstring:%s\n", fd, errno, strerror(errno));
}
else
{
printf("fd:%d,errno:%d,errstring:%s\n", fd, errno, strerror(errno));
}
close(fd);
return 0;
}
我们在使用open函数的时候不仅要O_WRONLY (写)还要创建O_CREAT
但是这种方式创建的文件,是没有权限的。
其中参数mode就是权限
因为umask默认权限的原因
umask() 函数的参数为一个八进制数,它的每一位分别表示对应的文件权限是否会被屏蔽掉,例如,umask(022) 表示屏蔽掉写入权限和执行权限。
umask(0)这意味着没有任何权限被屏蔽掉。
将mask初始化为0
成功将文件的权限设置成自己想要的
这里的strlen不需要+1,\0是C语言的规定,不是文件的规定,\0会被解释成乱码
O_WRONLY | O_CREAT 默认不会对原始文件内容做清空,需要加上O_TRUNC
O_APPEND | O_CREAT 不会追加写,需要加上O_WRONLY
#include
#include
#include
#include
#include
#include
#include
#include
#define LOG "log.txt"
// 系统方案
int main()
{
umask(0);
int fd = open(LOG, O_RDONLY, 0666);
if (fd == -1)
{
printf("fd:%d,errno:%d,errstring:%s\n", fd, errno, strerror(errno));
}
else
{
printf("fd:%d,errno:%d,errstring:%s\n", fd, errno, strerror(errno));
}
char buffer[1024];
// 这里无法做到按行读取,我们是整体读取的
ssize_t n = read(fd, buffer, sizeof(buffer) - 1); //使用系统接口来进行IO的时候,一定要注意\0的问题
if (n > 0)
{
buffer[n] = '\0';
printf("%s\n", buffer);
}
close(fd);
return 0;
}
在认识返回值之前,先来认识一下两个概念: 系统调用和库函数
上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆一下我们讲操作系统概念时,画的一张图
系统调用接口和库函数的关系,一目了然。 所以,可以认为,f系列的函数,都是对系统调用的封装,方便二次开发。