Linux基础IO篇
Posted Suk_god
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux基础IO篇相关的知识,希望对你有一定的参考价值。
文章目录
C文件接口
- fopen函数
FILE* fopen(const char* path,const char* mode)
参数含义:
path:带路径的文件名称(也可以不带路径,此时默认在当前路径下查找)
mode:
r
:只读模式打开,文件流指针指向文件的起始位置
r+
:可读可写方式打开,文件流指针指向文件的起始位置
w
:只写模式打开,被打开文件若存在,则截断文件(清空文件内容),若不存在,创建该文件
w+
:可读可写模式打开,,被打开文件若存在,则截断文件(清空文件内容),若不存在,创建该文件
a
:追加写。若文件存在,文件流指针指向文件的末尾进行写。若不存在,创建文件
a+
:可以读也可以 追加写。文件存在,读的位置被初始化到文件头,追加写的时候依旧是在文件末尾追加 ,文件不存在,创建该文件返回值
成功:返回文件流指针FILE*
失败:返回NULL
对fopen的测试
-
场景一:通过
r
方式打开一个文件,观察其返回值的情况
插播:perror函数
详解:
errno
是系统当中的一个错误码,当我们在调用一个函数发生错误的时候,系统会给errno赋值为对应的错误码,该错误码是一个整型数据
每一个错误码都对应着一个错误信息 error msg
perror函数的作用就是将errno中的值进行解析,解析完毕后,打印输出。 -
场景二:将上述的打开方式更改为w,查看能否打开成功
观察发现,当前可执行程序的路径下会多出来一个1.txt文件
- fwrite函数
size_t fwrite(const void* ptr , size_t size , size_t nmemb, FILE* stream)
参数ptr:要往文件中写的内容
size:定义往文件中写的时候,一个快是多大,单位是字节(通常情况下都是1)
nmemb:期望写多少块
stream:文件流指针,指向被写入的文件返回值
返回成功读入文件的块的个数
- fread函数
size_t fread(void* ptr,size_t size,size_t nmemb,FILE* stream)
参数:ptr:从文件中读出来的数据存放在ptr中(该内存由程序员自己提供,必须合法)
size:定义从文件中读的时候,一个块是多大,单位是字节(通常定义1个字节)
nmemb:期望读多少块
stream:文件流指针(指向被读取的文件)返回值
返回成功读入的文件块的个数
对函数fwrite&fread的测试
-
情景一:提前向文件1.txt中写入hello world ,然后使用
w
的方式打开文件并写入 I like Linux!
-
情景二:提前向文件1.txt中写入hello world ,然后使用
a
的方式向文件中写入 I like Linux!
运行后查看1.txt的内容
-
情景三:使用
a+
的方式打开文件,将文件中的内容读取出来
结果:
画图解释一下原因:
那么这种情况应该如何解决??
很简单,就是在读之前将我们的文件流指针指向文件的首部。这样就可以避免上述情况的发生
如何移动呢?那就是接下来的主角fseek函数
的功能 -
fseek函数
int fseek(FILE* stream,long offset,int whence)
参数stream:文件流指针
offset:偏移量
whence:将文件流指针偏移到什么位置SEEK_SET:文件头部
SEEK_CUR:当前文件流指针的位置
SEEK_END:文件末尾返回值
成功 -----> 0
失败 -----> -1
我们在上述的基础上,加入fseek函数再查看结果
结果可以正常输出
注意:为什么读在每次读一块的时候,期望读到的块数是数组长度-1呢?
解释:
从文件中期望读多少字节,会有三种情况:
情况一:文件字节数量和期望字节数量一致
情况二:文件字节数量 大于 期望读的字节数量
情况三:文件字节数量 小于 期望读的字节数量
我们仔细分析前两种情况,如果我们提供的是数组的长度,而没有对其-1,那么在这两种情况下,数组会被存满并且数组的最后一个位置的\\0也不存在了,而是被文件中的某个字符占据了
,这样,我们在使用从文件中读取出来的内容的时候,也就会出错。
所以,对数组长度-1的目的就是在字符数组当中预留\\0的位置,防止在后续访问的时候越界访问,导致程序崩溃。
6. fclose函数
int fclose(FILE* stream)
参数要关闭的文件流指针
返回值
成功: 0
失败:EOF
系统调用文件接口
- open
int open(const char* pathname,int flags)
int open(const char* pathname , int flags, mode_t mode)
参数
pathname
:要打开或创建的目标文件
flags
:打开文件时,可以传入多个参数选项,用下面的一个或者多个进行或运算,构成flags必须指定一个且只能指定一个的常量
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开其他常量
O_CREAT:若文件不存在,则创建文件,需要使用mode选项来指明新文件的访问权限
O_APPEND:追加写
mode
:当创建一个新文件的时候,指定新创建文件的权限,传递一个8进制的数字(e.g:0664)返回值
成功:新打开的文件描述符,文件描述符又可以称为文件操作句柄,文件句柄
失败:-1
- write
ssize_t write(int fd , const void* buf , size_t count)
参数fd:文件描述符
buf:将buf指向的内容写到文件中去
count:期望写多少个字节返回值
返回写入的字节数量
- read
ssize_t read(int fd , void* buf,size_t count)
参数fd:文件描述符
buf:将文件中读到的内容写到buf所指向的空间中去
count:期望读多少字节返回值
返回读到的字节数量
- lseek
off_t lseek(int fd , off_t offset , int whence)
5. close
int close(int fd)
关闭文件描述符
综合测试:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
int fd = open("1.txt",O_RDWR|O_CREAT,0664);
if(fd < 0)
perror("open");
return 0;
const char* str = "I like Linux!";
write(fd,str,strlen(str));
lseek(fd,0,SEEK_SET);
char buf[1024] = 0;
read(fd,buf,sizeof(buf)-1);
printf("%s\\n",buf);
文件描述符
是什么
是一个小正数,没有负数。
可以在/proc/进程号/fd文件夹下查看,对应的就是它的文件描述符
验证:
- 打印观察文件描述符的值
- 查看/proc/[pid]/fd下的文件描述符信息
分配规则
最小未使用
通过代码验证一下:
验证思路:我们知道标准输出对应1号文件描述符,我们先将标准输出关闭,然后再通过open函数打开一个文件。查看该文件的文件描述符
内核角度理解
从task_struct的角度理解文件描述符在内核当中是什么
- struct task_struct :PCB进程控制块
- 在 struct task_struct结构体中有一个成员变量是一个结构体指针files,该指针指向的是一个文件描述信息的结构体
- 我们本次研究的主要对象就是files指向的那个结构体,所以,我们先找到该结构体的定义处
- 好的,到这一步是时候综合理解一下了。我们先通过画图的方式进行描述
总结:文件描述符在内核当中本质上就是task_struct结构体中的一个结构体指针files所指向的结构体struct files_struct中的结构体指针数组fd_array的数组下标
扩展
一个进程最多可以打开多少个文件描述符?
解答:
- 系统当中针对一个进程打开的文件描述符的数量是有限的
- 通过ulimit -a可以查看“open files”的大小,而“open files”的大小就是限制一个进程可以打开的文件描述符的数量
- 我的机器中看到的“open files”的大小是1024,也就是意味着我的机器创建出来的进程最大可以打开的文件数量是1024
- open fiels”的大小并不是没有办法改变。可以通过 ulinit -n [数字] 进行改变
e.g:ulimit -n 10000 就是将一个进程打开文件描述符的数量改为了10000
理解文件描述符和文件流指针的区别
- 从源码的角度理解,文件流指针(struct _IO_FILE)是什么
- 找到对应的源代码 /usr/include/stdio.h
- 我们发现FILE原来只是一个被重命名后的名称,真正的结构体是struct _IO_FILE,我们转到定义处发现它属于该路径下的libio.h
struct _IO_FILE的应用
图示解析:
- 将文件流指针和文件描述符联系起来
2.1读写缓冲区问题
struct _IO_FILE…这个结构体是C标准库当中的结构体,而该结构体当中维护的读写缓冲区就是进程终止部分提到的exit函数处理的那个缓冲区。
2.2 文件描述
文件流指针对应的结构体struct _IO_FILE 这个结构体内部的成员变量 int _fileno保存了对应的文件描述符的数值
代码验证:
前提:使用fopen打开一个文件1.txt,输出FILE结构体内的成员变量 fileno ,与/proc/[pid]/fd下的文件描述符对比
#include<stdio.h>
#include<unistd.h>
int main()
FILE* fp = fopen("1.txt","w+");
if(NULL == fp)
perror("fopen");
return 0;
printf("_fileno is %d\\n",fp->_fileno);
while(1)
sleep(1);
return 0;
图示解析:
重定向
符号
- >
清空重定向 - >>
追加重定向
接口
int dup2(int oldfd , int newfd);
作用:将newfd的值重定向为oldfd,也即newfd拷贝oldfd
参数:两个参数均是文件描述符
返回值成功(做两件事情)
1、关闭newfd
2、让newfd指向oldfd对应的 struct file*失败(两种情况)
1、如果oldfd是一个非法或无效的文件描述符,则重定向失败,newfd没有变化
2、如果oldfd和newfd的值相等,则什么也不干
内核角度理解重定向
图示理解
重定向的代码验证
重定向标准输出到某一个文件当中
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
int fd = open("1.txt",O_RDWR|O_CREAT,0664);
if(fd < 0)
perror("open:");
return 0;
dup2(fd,1);
printf("kkkkkkk!\\n");
return 0;
动态库 && 静态库
库的概念
静态库&&动态库都是程序代码(二进制文件)的集合。一般为了方便将程序提供给第三方使用,就是将程序编写成为库文件提供给第三方(用户)使用。
动态库
- 特征
Windows系统下:没有前缀,后缀为dll
Linux系统下:前缀为lib,后缀为.so - 生成动态库
生成动态库的代码当中不需要包含main函数(程序入口函数)
使用gcc/g++ 编译器(注:需要在普通的编译命令下增加两个命令行参数)
参数1:
-fPIC
产生位置无关码
参数2:-shared
生成共享库格式
- 场景模拟
生成动态库 && 使用动态库
-
编写生成动态库所需要的文件(这里重点在于测试动态库,编写的文件比较简单,具有说明性即可)
-
编写Makefile文件,用来生成动态库
-
创建用户文件夹,在用户的程序中使用动态库文件
-
编写main.c对应的Makefile文件
-
make后,运行生成的可执行程序,查看运行结果
出错了!!!
分析出错原因:
本质上的原因就是程序在运行的时候无法找到动态库 -
下面我们就介绍一下如何找到动态库
-
让程序找到动态库的三种方式
4.1将动态库放到可执行程序的路径下(可以解决问题,但是不推荐)
4.2配置 LD_LIBRARY_PATH环境变量
LD_LIBRARY_PATH:一个库文件的环境变量,在~/.bash_profile
或者~/.bashrc
文件中
下面再次运行test_so
4.3放到系统库的路径下:/lib64
极力不推荐,不要将系统的库文件与用户使用的糅合在一块
不必尝试,极力不推荐!!
静态库
- 特征
Windows系统:没有前缀,后缀为.lib
Linux系统:前缀为lib
,后缀为.a
- 生成静态库文件(两个阶段)
2.1 第一阶段
使用gcc/g++将源码编译成为目标程序(.o文件)
2.2 第二阶段
使用 ar -rc 命令编译目标程序为静态库
ar -rc [静态库文件名称] [目标程序]
2.3 注意事项
3 . 场景模拟
创建user文件夹使用该静态库,观察结果
程序正常运行!!
简单的文件系统
- ls -l 命令可以列出当前路径下的所有文件&&文件夹的详细信息
既然文件有这么多的信息,我们知道所有的信息都要存储。
文件的内容势必会被存放在磁盘当中
描述文件属性信息的内容也是需要保存在磁盘当中 - Linux ext2文件系统
2.1图示
详述每一个模块:
- 超级块(Super Block)
存放文件系统本身的结构信息。记录的主要信息有:block 和 inode的总量,未使用的block 和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间、最后一次检查磁盘的时间等其他文件系统的信息。
super Block的信息被破坏,可以说整个文件系统结构就被破坏了 - GDT,块组描述符
描述块组属性信息 - 块位图(Block Bitmap)
记录Data block中哪个数据块已经被占用,哪个数据块没有被占用 - 存储文件内容(Data blocks)
画图理解块位图与Data blocks之间的关系
- inode位图(inode Bitmap)
每个bit表示一个inode是否空闲可用 - i节点表(inode table)
存放文件属性,如文件大小、所有者、最近修改时间等
2.2 创建一个新文件主要的4个操作
我们以下图为例进行分析
步骤1:存储属性
inode节点号查看:ll -i
命令列表中第一个就是inode节点号
先找到一个空闲的 i 节点(265458)。内核将文件信息记录到其中
步骤2:存储数据
该文件需要存储到3个磁盘块,内核找到了3个空闲块(300,500,800),将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推
步骤3:记录分配情况
文件内容按顺序300 500 800 存放,内核在inode上的磁盘分布区记录了上述块列表。
步骤4:添加文件名到目录
新的文件名ABC,内核将入口(265458,ABC)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
软链接 & 硬链接
软链接
-
是什么
目标文件的快捷方式 -
如何生成软链接文件
使用命令ln -s 源文件 软链接文件
-
使用的注意事项
3.1修改软链接文件,源文件也会被修改,修改源文件,软链接文件也会被改变
3.2源文件如果被删除,软链接文件还在的,修改软链接文件。会重新建立源文件,重新建立链接关系(这种情况慎重考虑,如果之前的源文件在程序中作用很大,删除后,通过这种方式新生成的源文件与原来的已经不一样了。可能会导致程序崩溃)
建议:在删除源文件的时候,将软链接文件也一并删除掉,以防后患 -
结合文件系统理解
4.1软链接文件和源文件是拥有不一样的节点号的
硬链接
- 是什么
目标文件的替身 - 生成方式
使用命令ln 源文件 硬链接文件
- 结合文件系统理解
3.1源文件与硬链接文件的inode节点号相同
3.2多个文件引用同一个inode节点的时候,inode节点内部的引用计数会++
当文件删除的时候,引用计数会–,直到引用计数减为0的时候,才会释放inode节点
模拟:将源文件test.c删掉,查看硬链接文件的情况
以上就是基础IO篇的相关总结,感觉对自己有帮助的老板们还请一键三连~~感谢感谢
以上是关于Linux基础IO篇的主要内容,如果未能解决你的问题,请参考以下文章
Linux篇第九篇——基础IO(系统文件IO+文件描述符+重定向+文件系统+软硬链接)