C库IO函数的工作流程
常用的C库IO函数有:fopen, fclose, fwrite, fread, fgets, fputs, fscanf, fprintf, fseek, fgetc, fputc, ftell, feof, flush...
其中fopen函数返回一个FILE*类型的文件指针,其他函数对该文件指针进行操作
1. FILE*类型细化
(1) 文件描述符(整型值)
用于索引到函数open对应的磁盘文件
(2) 文件读写指针位置
读写文件过程中指针的实际位置 --> fseek重置指针位置到文件开头
(3) I/O缓冲区(内存地址)
通过寻址找到对应的内存块 --> 为了提高程序操作文件的效率
c库函数维护该缓冲区
Linux库函数是没有包含该缓冲区,需要手动分配
2. 将缓冲取数据刷新到硬盘上的情况
(1) 强制刷新缓冲区: fflush
(2) 缓冲区已满:自动刷新
(3) 正常关闭文件
a. fclose
b. (main函数)return
c. (main函数)exit
3. 虚拟地址空间,32为系统每个进程启动会分配2^32 = 4G的虚拟空间
(1) 0-3G:用户空间 <从小到大表示>
a. 受保护的地址(0~4k)
空指针NULL即指向该区域
b. ELF字段<主要包含3部分>
Linux下的可执行程序格式为ELF --> 使用file <fileName>命令可以查看
.text段 --> 代码段(二进制机器指令)
其他段 --> 例如:只读数据段,符号段等
.data段 --> 已经初始化的全局变量
.bss --> 未初始化的全局变量
c. 堆空间
new和malloc分配空间在堆上
地址是从小地址向大地址分配
d. 共享库
包含c标准库和Linux系统IO函数等
动态链接库在该区域的地址是相对共享库加载的位置表示的(偏移量)
<静态链接库放在.text段的代码中>
e. 栈空间
栈空间分配内存是从大地址向小地址分配
f. 命令行参数
g. 环境变量(env)
(2) 3-4G: Linux内核区
a. PCB进程控制块 --> 0-1023的一个带标号数组 -- 默认打开前3个:0为标准输入,1为标准输出,2为标准错误
从3开始,每打开一个新文件占用一个文件描述符,而且是空闲的最小序号的描述符
虚拟地址空间与物理地址空间映射的作用
1. 方便编译器和操作系统安排程序的地址分布
程序可以使用相邻的虚拟地址来访问不连续的物理地址
2. 方便进程直接隔离
不同进程使用的虚拟地址彼此隔离,一个进程中的代码无法更改正在由其他进程使用的物理地址的内容
3. 方便操作系统使用内存
程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区
库函数与系统函数的关系
print函数的作用过程:
1. C标准函数 --> 标准输出(stdout) <转化为> FILE* -- [FD, FP_POS, BUFFER]
2. 标准输出(stdout) --> 调用write函数 -- 传递文件描述符
--> 应用层 <write(fd, "hello", 5)> -- 用户空间 -> 内核空间
--> 系统调用 sys_write() -- 调用设备驱动
--> 内核层 调用设备驱动函数
3. 内核层 --> 通过设备驱动操作硬件
--> 显示器显示
常用系统IO函数
1. open -- 查看man文档中关于open内容 man 2 open <open位于man文档第二章>
a. open返回一个文件描述符,如果打开失败,则返回-1,同时errno被赋值,用于指示何种错误
open(const char* pathname, int flags); -- flags标记打开方式O_RDONLY,O_WRONLY,O_RDWR和其他一些辅助操作
open(const char* pathname, int flags, mode_t mode);
//当文件存在时,使用2个参数的open;当文件不存在时,使用3个参数的open,其中mode_t指定文件创建时的访问权限(例如777)
creat(const char* pathname, mode_t mode);
b. 打开方式
必选项:O_RDONLY, O_WRONLY, O_RDWR
可选项:O_CREAT, O_TRUNC, O_EXCL, O_APPEND
O_CREAT --> 创建文件时填写的权限与文件实际的权限会有所不同
因为系统本地会有一个掩码,使用umask命令查看
实际权限为:参数设置权限 <按位与> 本地掩码(取反)
O_EXCL --> 与O_CREAT一起使用,用于判断文件是否已经存在
O_TRUNC --> 与O_RDWR一起使用,将文件清空
2. perror -- 打印errno错误信息
perror(const char* s); --> perror("Open Failed") => Open Failed: xxxxxx
参数s所指的字符串会先打印出来,后面加上系统定义的错误类型信息
3. read
a. 函数原型
ssize_t read(int fd, void *buf, size_t count);
b. 返回值
-1 --> 读取文件失败,同时errno会设置一个值
0 --> 读取完毕
>0 --> 读取的字节数
4. write
a. 函数原型
ssize_t write(int fd, void *buf, size_t count);
b. 返回值
>0 --> 写入的字节数
-1 --> 写入失败
5. lseek
a. 函数原型
off_t lseek(int fd, off_t offset, int whence);
b. 参数
offset: 文件指针的偏移量
whence: SEEK_SET, SEEK_CUR, SEEK_END
SEEK_SET: 将文件读写指针指向文件开头后offset的位置
SEEK_CUR:将文件读写指针指向当前位置后offset的位置
SEEK_END:将文件读写指针指向文件末尾后offset的位置
c. 文件拓展
int length = lseek(fd, 0, SEEK_END); --> 获取文件长度
int newL = lseek(fd, 2000, SEEK_END); --> 将文件拓展2000
//拓展文件最后需要执行一次写操作
write(fd, "anything", 8); --> 文件最后会有2008的长度,中间2000是空洞
即占用了空间,但是没有内容