File*
其实是一个结构体
- 文件描述符
FD
:索引到对应的磁盘文件 - 文件读写位置指针
FP_POS
,如果同时读写需要注意文件指针的位置 - I/O缓冲区
BUFFER
:保存内存指针,默认大小是8kb,用于减小我们对硬盘操作的次数。因为我们对硬盘的操作是ms
级别的,而我们电子设备的操作是ns
级别的。 - 刷新缓冲区到硬盘上:
fflush
、缓冲区已满、文件正常关闭、return main、exit main - Linux系统函数没有缓冲区,C库函数自带缓存
在Linux中,inode
保存文件的属性,里面有一个结构体struct stat
,其中记录了文件的各种信息,但是没有保存文件名。
文件名保存在denty
(目录项)中,每一个文件名对应一个inode
编号。每一个硬链接都是一个denty
以Linux32为系统为例:
- 文件描述符:
- Linux为每一个运行的程序分配0-4G的内存
- 0-3G是用户区,3-4G是内核区,内核区不允许用户去访问
- 文件描述符位于内核区中的PCB进程控制块中,0-1023,每个位置储存一个文件。0\1\2默认是打开的,分别是
STDIN_FiLENO 、STDOUT_FILENO 、STDERR_FILENO
,每打开一个新文件,会多储存一个文件描述符。是一个栈
查看文件格式:file 文件
虚拟地址
用户区:0-3G
- 受保护的内存(0-4K)none指针指向这个位置
- ELF段
- .text 代码段,二进制机器指令,包含main函数、静态库
- .rodata段 和.text一样都是ro(只读)权限,在链接的时候完成数据段合并
- .data段 已经初始化的全局变量
- .bss未初始化的全局变量,和.data段一样是rw(读写)权限
- 堆空间:保存全局变量,用
malloc
或者new
在堆上分配内存 - 共享库: 动态库,对库的调用是相对的地址。
- 栈空间:从上面开始分配内存,保存局部变量
- 命令行参数:main函数的参数
- 环境变量:env查看
CPU使用虚拟地址与物理空间映射的作用:
- 方便编译器和操作系统安排程序的地址分布:程序可以使用一系列连续的虚拟地址访问内存中不连续的内存缓冲区
- 方便进程之间的隔离:不同进程之间彼此隔离,一个进程中的代码无法更改正在另一项进程的物理内存
- 方便OS使用内存:程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常为4KB)保存到此案文件。数据或代码页会根据需要在物理内存和磁盘之间移动
printf函数
printf
函数
调用write
函数将文件描述符传递
应用层运行write
函数
系统调用sys_write()
函数,从用户态转化为内核态
内核层设备驱动函数
常用的系统应用函数
man 章节号 需要查找的函数 // 查看Linux手册中的函数
open函数
int open(const char *pathname, int flags);// The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only,or read/write, respectively.int open(const char *pathname, int flags, mode_t mode);
//mode:给创建文件设定访问权限
返回一个文件描述符,-1意思是发生了错误,errno
会被赋予错误信息,使用需要包含errno.h
错误宏定义的位置:
第1-34个错误:usr/incclude/asm-generic/errno-base.h
第35-133个错误:/usr/include/asm-generic/errno.h
void perror(const char *s)
用来将上一个函数发生错与原因输出到标准设备
编写函数的时候可以使用章节+ shift+k
查看函数的man
文档
例如:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h> //open的头文件
#include<unistd.h> //close的头文件
#include<stdlib.h> //exit的头文件
#include<stdio.h> //perror的头文件
int main()
{int fd; //用于保存文件描述符fd = open("main.c",O_RDWR);if(fd==-1){perror("open file");exit(1);}//创建新文件fd = open("newfile.c",O_RDWR | O_CREAT,777);//实际上文件的属性是775,因为本地有一个掩码,给定的权限将会和掩码有一个取反按位与的操作,实际上相当于减法//nmask获取本地掩码printf("新文件的文件描述符:%d\n",fd);//关闭文件int ret = close(fd);if(ret==-1){perror("close file");exit(1);}elseprintf("ret=%d\n",ret);return 0;
}
获取本地掩码:umask
修改本地掩码:umask xxxx
O_CREAT
需要将掩码取反再将权限按位与
通过O_CREAT 与O_EXCL
和起来使用判断文件是否已经存在,例如:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h> //open的头文件
#include<unistd.h> //close的头文件
#include<stdlib.h> //exit的头文件
#include<stdio.h> //perror的头文件
int main()
{int fd; //用于保存文件描述符
/*fd = open("main.c",O_RDWR);if(fd==-1){perror("open file");exit(1);}//创建新文件fd = open("newfile.c",O_RDWR | O_CREAT,777);//实际上文件的属性是775,因为本地有一个掩码,给定的权限将会和掩码有一个取反按位与的操作,实际上相当于减法//nmask获取本地掩码printf("新文件的文件描述符:%d\n",fd);
*/fd = open("myhelloc.c",O_RDWR|O_CREAT|O_EXCL , 777);if(fd==-1){perror("open file");exit(1);}
//关闭文件int ret = close(fd);if(ret==-1){perror("close file");exit(1);}elseprintf("ret=%d\n",ret);return 0;
}
文件清空、截断为0O_TRUNC
一定要注意对返回值做一个判断,这样出错的时候就能知道哪里出错。
read()
#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);
返回值:
- -1 读文件失败,设置errno
- 0文件读取了
- x 读取了x个字符
write()
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
lseek()
#include <sys/types.h>#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
whence as follows:SEEK_SETThe file offset is set to offset bytes.SEEK_CURThe file offset is set to its current location plus offset bytes.SEEK_ENDThe file offset is set to the size of the file plus offset bytes.
使用举例:将一个文件的内容拷贝到另一个文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h> //Open
#include<unistd.h> //Close
#include<stdlib.h> //exit
#include<stdio.h> //perror
#include<string.h>
#define MAXN 1005
char buffer[MAXN]; //缓冲区
int fd_in,fd_out; //用于保存文件描述符void Open()
{fd_in=open("file_in",O_RDONLY);if(fd_in == -1){perror("Open fin:");exit(1);}//fd_out=open("file_out",O_WRONLY | O_CREAT | O_EXCL,0777);fd_out=open("file_out",O_WRONLY | O_CREAT,0644);if(fd_out == -1){perror("Open fout:");exit(1);}
}void Close()
{int ret=close(fd_in);if(ret==-1){perror("Close fin");exit(1);}if(ret==-1){perror("Close fout");exit(1);}
}void Work()
{int cnt=read(fd_in,buffer,MAXN);if(-1==cnt){printf("读取文件失败");}while(cnt) //没有读取到文件末尾{write(fd_out,buffer,cnt);cnt=read(fd_in,buffer,MAXN);}
}
int main()
{Open();Work();Close();
}
经验:
open
函数如果使用O_CREAT
参数,则需要指定使用权限(八进制数字,需要在权限前面加0),如果只使用O_CREAT
和O_RDWR
参数配合,如果文件已经存在就会打开之前的文件,不会创建新文件,如果再配合O_EXCL
参数,那么如果已经存在文件就会报错。- 文件的实际权限是指定权限减去掩码
- 不要眼高手低,即使看起来比较简单的东西还是需要多动手实践,才能发现自己的问题
lseek
#include <sys/types.h>#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);
lseek() repositions the file offset of the open file description associated
with the file descriptor fd to the argument offset according to the direc‐
tive whence as follows:SEEK_SETThe file offset is set to offset bytes.SEEK_CURThe file offset is set to its current location plus offset bytes.SEEK_ENDThe file offset is set to the size of the file plus offset bytes.
- 获取文件长度
ret=lseek(fd,0,SEEK_END)
- 文件拓展:只能向后拓展文件,不能向前
int ret=lseek(fd,2000,SEEK_END);
//文件拓展需要最后做一次写操作,随便写一点东西就可以
//得到一个空洞文件,先得到预定大小的文件然后使用多线程操作
write(fd,"a",1);