学习IO相关操作, 系统调用接口, 文件描述符, 理解重定向功能,认识软硬链接, 对比区别.
1. FILE *fopen(const char *pathname, const char *mode)
描述 : 以指定的方式打开一个文件
参数:
pathname: 路径
mode : 打开方式
r : 以只读方式打开文件
r+ : 以读写方式打开文件
w : 以只写方式打开,不存在则创建 , 否则清空原有内容
w+ : 以读写方式打开,不存在则创建, 并且清空原有内容
a : 以追加写方式打开, 不存在则创建
a+ : 以追加读写的方式打开, 不存在则创建
返回值:
成功返回文件流指针, 失败返回NULL, 并且设置errno
2. size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
描述 : 向文件中写入数据
参数:
ptr : 要写入的字符串size : 写入的块大小
nmemb : 写入的块个数
stream : 文件流指针
3. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
描述 : 从文件中读取数据
参数:
ptr : 要读取的字符
size : 读取的块大小
nmemb : 读取的块个数
stream : 文件流指针
4. int fseek(FILE *stream, long offset, int whence);
描述 : 从文件读写位置从whence开始偏移offset个字节
参数:
stream :文件流指针
offset : 偏移字节
whence : 偏移起始位置
SEEK_SET : 从文件起始位置开始偏移
SEEK_CUR: 从文件当前位置开始偏移
SEEK_END : 从文件末尾位置开始偏移
5. int fclose(FILE *stream);
描述 : 关闭打开的文件
参数 :
stream : 文件流指针
文件流指针 FILE*
代码:
#include
#include
int main()
{
FILE *fp = fopen("./csdn.txt","w+");
if(fp == NULL)
{
perror("fopen error");
return -1;
}
char buf[1024] = "my name is qujiale";
fwrite(buf,strlen(buf),1,fp);
fseek(fp,0,SEEK_SET);
memset(buf,0x00,1024);
fread(buf,1024,1,fp);
printf("buf:%s\n",buf);
fclose(fp);
return 0;
}
运行结果:
1. int open(const char *pathname, int flags, mode_t mode)
头文件 :
#include
参数 :
pathname : 路径
flags : 表明打开方式
O_RDONLY : 以只读方式打开文件
O_WRONLY : 以只写方式打开文件
O_RDWR : 以读写方式打开文件
O_CREAT : 如果文件不存在则创建
O_TRUNC : 截断文件 (清空原有内容)
O_APPEND : 以追加方式打开
mode : 如果创建文件, 给出创建权限
返回值:
成功返回一个文件描述符, 失败返回-1
2. ssize_t write(int fd, const void *buf, size_t count);
头文件 :
#include
参数 :
fd : 文件描述符
buf : 要写入的数据
count : 要写入的数据长度
返回值 :
成功返回实际写入的字节长度, 失败返回-1
3. ssize_t read(int fd, void *buf, size_t count);
头文件 :
#include
参数 :
fd : 文件描述符
buf : 要读取到的变量
count : 要读取的子节数
返回值 :
成功返回实际读取到的子节数, 失败返回-1
4. off_t lseek(int fd, off_t offset, int whence);
头文件 : #include
同库函数接口
5. int close(int fd);
头文件 : #include
同库函数接口
文件描述符
代码 :
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("./syscsdn.txt", O_RDWR | O_CREAT, 0664);
if(fd < 0)
{
perror("open error");
}
char buf[1024] = "my name is qujiale";
int ret = write(fd, buf, strlen(buf));
if(ret < 0)
{
perror("write error");
}
lseek(fd, 0, SEEK_SET);
memset(buf, 0x00, sizeof(buf));
ret = read(fd, buf, 1024);
if(ret < 0)
{
perror("read error");
}
printf("buf:%s\n",buf);
fclose(fd);
return 0;
}
运行结果 :
库函数 : fopen, fwrite, fseek, fclose, fread
系统调用接口 : open, write, seek, close, read.
我们来看一张图 :
我们很明显的可以看到库函数和系统调用属于上下级的调用关系, 库函数最终还是要调用系统调用接口, 我们可以认为库函数就是对系统调用接口的一层封装, 方便我们二次开发.
概念
进程通过struct file结构体来描述打开的文件--使用了struct file *fd_array[] , 文件描述符就是这数组的下标--用户打开文件,操作系统通过file结构体描述文件 , 并将指针添加进入fd_array[]中 , 向用户返回这个文件描述信息在数组中的位置(下标) , 用户操作文件的时候,将这个下标传递给操作系统 , 操作系统通过下标找到文件描述信息进而操作文件, 所以其实文件描述符就是一个从0 开始的整数
分配原则
最小未使用原则
默认从3开始, 因为一个进程启动之后, 默认会打开三个文件
0,1,2 会默认占用, 所以是从3开始的
标准输入 标准输出 标准错误 0 1 2 文件描述符 STDIN_FILENO
STDOUT_FILENO STDERR_FILENO stdin stdout stderr 文件流指针
1. 文件流指针中包含了文件描述符这个成员变量
2. 文件流指针结构体中描述了一个缓冲区, 我们叫做用户态缓冲区
如果我们 exit() 退出一个进程的时候, 这个时候会刷新一下缓冲区, 将内容打印到标准输出中, 也就是显示器
如果我们_exit() 退出一个进程的时候, 这个时候不会刷新缓冲区, 因为_exit()是系统调用接口, 没有缓冲区来打印.
关系
文件流指针 : 库函数的操作句柄
文件描述符 : 系统调用接口的操作句柄
文件描述符实际上是文件流指针FILE*结构体的一个成员变量 fileno
操作文件的具体流程
通过文件流指针操作文件的时候其实就是通过文件流指针FILE* fp 找到文件描述符 int fd, pcb中有一个结构体指针指向file_struct, 这个结构体中的array数组中有每个文件对应的信息, 我们通过fd找到array数组中fd下标对应的文件信息, 进而再去操作文件, 值得注意的是, 文件流指针是在用户空间, 但是文件描述符所对应的文件信息都在内核里面, 文件流指针只是封装了文件描述符. 所以我们发起系统调用之后相当于从用户态切换到了内核态
写入文件
写入文件流指针fp(FILE*)所包含的缓冲区(用户态缓冲区)中 -> 刷新缓冲区/缓冲区满了 ->通过fp中的成员变量文件描述符fd找到文件的描述信息(file_struct的array数组中) -> 然后写入到文件
进程从用户态切换到内核态的方法
发起系统调用
概念:
针对文件描述符进行重定向, 改变文件描述符这个下标所对应的文件描述信息,操作相同的描述符,但是具体操作的文件已经改变
我们先来看一段代码
#include
#include #include int main() { close(1); int fd = open("./csdn.txt", O_RDWR); if(fd < 0) { perror("open error"); } printf("fd:%d\n",fd); close(fd); return 0; } 我们关闭了标准输出, 然后创建fd, 这时候我们运行程序, 应该是输出一个1, 因为要遵循最小未使用原则, 但是我们来看运行结果
可以看到什么也没有打印, 那么这是为什么呢?
因为标准输出文件现在已经被关闭了.所以现在打印不出来了. 现在1号描述符对应的不是显示器文件了, 而是我们写的csdn.txt文件, 所以这个时候应该将数据写到csdn.txt了, 那我们来看一下csdn.txt
我们能看到, 还是什么都没有, 这又是为什么呢?
因为\n的作用不仅仅是换行, 并且如果当前操作的文件是标准输出文件, 则换行还可以刷新缓冲区, 但是如果当前操作的文件不是显示器(一个特殊文件), 那么\n就只有换行的功能, 并没有刷新缓冲区的功能. 并且close()是一个系统调用接口, 不具备(用户态)缓冲区, 所以更不会刷新缓冲区, 所以这个时候我们要手动刷新缓冲区.
在close(fd)之前加上fflush(stdout);
再次运行:
可以看到, 这次我们就成功的把这句话写进去了
我们发现,本来应该输出到显示器上的内容,输出到了文件 csdn.txt 当中,其中,fd=1。这种现象叫做输出重定向
本质
既然我们知道了的上面这种现象叫做重定向, 那么重定向的本质是什么呢?
重定向指的是描述符的重定向,描述符并没有改变, 改变的是描述符对应的文件信息
dup2系统调用
int dup2(int oldfd, int newfd);
让newfd 也指向 oldfd 所指向的文件, 并且如果newfd本身已经打开了文件, 则关闭原来的文件, newfd 和 oldfd 最终都指向oldfd所对应的文件
示例 :
#include
#include #include int main() { int fd = open("./csdn.txt", O_RDWR); if(fd < 0) { perror("open error"); } //打印之前进行重定向 dup2(fd,1); printf("fd:%d\n",fd); fflush(stdout); close(fd); return 0; } 运行结果:
这个时候printf打印, 就把fd等于3写入到csdn.txt中了
定义 : 磁盘上的文件管理系统
除了交换分区之外, 每一个磁盘分区都有一个文件系统.
在文件系统中是怎样管理文件的呢,
以linux下的ext2文件系统为例:
将磁盘分成很多个数据块, 每一块的大小是4K
划分:
inode节点 : 每个文件都有一个inode节点, 描述文件的信息, 比如 : 大小/ 权限/ 时间/ 占用块个数/ 链接数/ 数据地址
data : 文件的具体数据
inode_bitmap : 标记哪些inode节点使用了, 哪些inode节点没有使用
data_bitmap : 数据块位图区域, 标记哪些数据块已经使用了, 哪些还没有使用, 是一个位图
超级块 : 记录文件系统信息, inode节点有多少, 数据块有多少个等等, 记录文件系统的统筹信息.
文件的存储/读取过程
文件的存储过程:
通过inode_bitmap在inode区域获取空闲inode节点,通过data_bitmap获取空闲数据块在inode节点中记录文件信息以及数据块位置,并且将文件数据写入到数据块中,将自己的目录项信息添加到所在目录文件中目录项 : 文件名 + inode节点号
文件的读取过程:
cat a.txt,在目录项文件中通过文件名获取文件inode节点号(文件唯一),通过inode节点号在inode区域中找到inode节点,通过inode节点中的的数据块地址信息在指定数据块读取数据软链接/硬链接文件
概念:
软链接文件: 就像是一个文件的快捷方式,是一个独立的文件, 存放源文件的路径, 操作软链接会通过这个路径找目录项
硬链接文件: 一个文件的名字(目录项), 与原文件公用同一个inode节点
如何创建 :
软链接文件 : ln -s tmp.txt tmp.soft
硬链接文件: ln tmp.txt tmp.hard
区别 :
1. 删除原文件, 软链接文件将失效, 硬链接无影响(链接数-1)2. 软链接可以跨分区创建, 硬链接不可以 , 因为硬链接针对inode节点号, 每个分区的文件系统都有可能不同, 有可能就没有inode节点, 就算两个分区文件系统是一样的, 也有可能造成inode节点冲突, 所以硬链接不能跨分区.
3. 软链接可以对目录创建 , 硬链接不可以 , 因为在linux下, 只有一个目录结构, 目录本来就是跨分区的, 但是硬链接不能跨分区, 所以也不能对目录创建, 本质上还是因为inode节点的原因.
4. 软链接针对的是目录项, 硬链接针对的是inode节点
联系 :
对软链接和硬链接进行操作, 源文件均会改变
附加:
我们ls -l 看到的文件信息都是inode节点里面建立的, 以及stat
以上就是IO相关的一些知识点, 感谢观看^_^