文章目录
- 回顾C文件接口
- 初步理解文件
- 理解文件
- 使用和并认识系统调用
- open
- 概述
- 标记位传参理解
- 返回值
- close
- write
- read
- 总结
- 文件描述符fd
- 0&1&2
- 理解
回顾C文件接口
C代码:
#include<stdio.h>
int main()
{
FILE *fp=fopen("log.txt","w");
if(NULL==fp)
{
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
我们要进行文件操作,前提是程序运行起来了,所谓的文件的打开和关闭是CPU执行我们的代码才被打开或者关闭的。
fopen()
函数:
初步理解文件
打开文件:本质上是进程打开文件
文件没有被打开的时候,在哪里?在磁盘上
一个进程能打开很多文件吗? 可以
系统中可不可以存在很多进程? 可以
因此,在很对情况下,操作系统内部一定存在大量的被打开的文件
操作系统要不要对这些被打开的文件进行管理? 要
如何管理?先描述,再组织
如果在磁盘新建一个文件,里面什么内容都没有,大小为0KB,那么这个文件占用空间吗?占,因为文件对应的创建时间、文件类型等对应的文件属性都存在。
因此,文件=内容+属性
理解文件
操作文件,本质上是进程在操作文件
文件没有被打开时,文件在磁盘上,磁盘属于外部设备,磁盘本质是一个硬件,向文件写入,本质是向硬件中写入,但是用户没有权利直接向硬件写入,硬件的管理者是操系统,因此用户不能绕过操作系统直接访问硬件,必须通过操作系统写入。操作系统给用户提供系统调用,我们用的C/C++/其他语言,都是对系统调用接口的封装。
访问文件除了可以使用已经封装好的函数,还可以使用系统调用。
使用和并认识系统调用
open
概述
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
pathname
: 要打开或创建的目标文件
flags
: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY
: 只读打开
O_WRONLY
: 只写打开
O_RDWR
: 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT
: 若文件不存在,则创建它。需要使用mode
选项,来指明新文件的访问权限
O_APPEND
: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
open
函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open
创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
标记位传参理解
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
//system call
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
}
操作系统本身就有一个权限掩码,现在在代码中又写了一个权限掩码。这里使用的是代码中的掩码,就近原则。自己设置了权限掩码就用自己设置的,没有就用系统的。因此最终创建的权限掩码就是666
int flag
:是32个比特位的,用比特位来进行标志位的传递。flag
标志位传递本质上是一个位图。
我们来设计一个传递为图标记位的函数:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define ONE 1 //1: 0000 0001
#define TWO (1<<1) //2: 0000 0010
#define THREE (1<<2) //3: 0000 0100
#define FOUR (1<<3) //4: 0000 1000
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(TWO);
printf("\n");
print(ONE|TWO);
printf("\n");
print(ONE|TWO|THREE);
printf("\n");
print(ONE|FOUR);
printf("\n");
return 0;
}
如果我们将打印替换成其他功能,那么就可以写出一个向指定函数传递标记位的方法。
返回值
RETURN VALUE
open() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).
返回成功,就帮我们创建一个新的文件描述符,就是一个整数,失败返回-1
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fda=open("loga.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fda:%d\n",fda);
int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fdb:%d\n",fdb);
int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fdc:%d\n",fdc);
int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fdd:%d\n",fdd);
return 0;
}
运行结果:
返回值是3 4 5 6,怎么不见0 1 2呢?
因为 0 1 2分别对应标准输入(键盘)、标准输出(显示器)、标准错误(显示器)
下文有具体介绍
close
#include <unistd.h>
int close(int fd);
write
man 2 write
可查看write函数
第一个参数 fd:表示待写入文件的文件描述符。
第二个参数 buf:指向待写入的文件内容。
第三个参数 count:待写入内容的大小,单位是字节。
返回值:实际上写入的字节数。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
umask(0);
//system call
int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
const char *message="hello linux file!\n";
write(fd,message,strlen(message));
close(fd);
return 0;
}
会发现确实是写入了hello linux file!
将上述代码中,写入hello linux file!
,修改成aaa
:
const char *message="aaa";
write(fd,message,strlen(message));
运行结果:
可见,默认不清空文件
那么,如何再次写入文件时,之前文件会被清空呢?
需要添加一个O_TRUNC
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
umask(0);
//system call
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
const char *message="aaa";
write(fd,message,strlen(message));
close(fd);
return 0;
}
运行结果:
追加写入:
int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
第一个参数 fd:要读取文件的文件描述符。
第二个参数 buf:指向一段空间,该空间用来存储读取到的内容。
第三个参数 count:参数二指向空间的大小。
总结
intfd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
写方式打开,不存在就创建,存在就先清空
int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
追加形式写入
文件描述符fd
0&1&2
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器
理解
在一个操作系统中有很多文件,操作系统需要对被打开文件进行管理,也就是需要创建对应的数据结构(struct_file
),对文件的管理转换成对链表的增删查改。
任何一个文件=内容+属性,磁盘中文件的属性来初始化struct_file
,将文件内容写入文件内核缓存里。
一个进程可以打开多个文件,对于进程来说怎么知道被打开文件与自己有关系,因此对应的task_struct
存在一个属性struct files_struct *files
,指向该类型的一个对象,该类型对象记录了当前进程所打开的所有文件新信息,操作系统中就存在一个结构体:struct file_struct
,里面存在一个指针数组,数组的内容指向当前进程所打开的文件结构对象,也就是指向当前进程打开的文件。我们称这个数组为文件描述符表,数组下标称为文件描述符
文件描述符fd的本质是什么?
内核进程:文件映射关系的数组下标。
无论读写,都必须在合适的时候,让操作系统把文件内容读到文件缓冲区中。
open在干什么??
- 创建file
- 开辟文件缓冲区的空间,加载文件数据(延后)
- 查进程的文件描述符表
- file地址填入对应的下标中
- 返回下标
读写函数本质是拷贝函数