目录
fopen:打开文件 FILE *fopen(const char *path,const char *mode)
open:int open(const char*pathname,int flags);
从task_struct的角度理解文件描述符是什么?通过查看源码可知:
(1)从源码的角度理解,文件流指针(struct _IO_FILE)是什么?
1.复习下C的文件接口
fopen:打开文件
FILE *fopen(const char *path,const char *mode)
path:带有路径的文件名称(如果说不带有路径,打开的文件可以是在当前路径下)
mode:打开文件的方式
下表为一些常见的mode
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w”
(只写)
|
为了输出数据,打开一个文本文件 (输出的时候会将之前的内容清空) | 建立一个新的文件 |
“a”(追加) | 向文本文件尾添加数据 | 出错 |
“rb”(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
ab”(追加) | 向一个二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+”(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+”(读写) | 为了读和写打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+”(读写) | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
fwrite:向文件当中写
size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE *stream);
参数:ptr:要往文件中写的内容
size:定义往文件中写的时候,一个块是多大,单位字节;(通常定义为一个字节)
nmemb:期望写多少块
stream:文件流指针
返回值:返回成功写入到文件的块数
通常情况下,代码调用时,这样设置:
size:1(1个块的大小是1字节)
nmemb:块的个数(期望写多少字节)
返回值:成功写入文件当中块的个数(成功写入文件中的字节数量)
fread:从文件当中读
size_t fread(void *ptr,size_t size,size_t nmemb,FILE*stream)
参数:ptr:将文件中的内容保存到ptr指向的内存空间当中(这个内存空间一定要是合法的(程序员自己定义的))
size:定义从文件中读的时候,一个块是多大,单位字节;(通常定义为一个字节)
nmemb:期望读多少块
stream:文件流指针
返回值:返回成功读入的文件块的个数
fseek:移动文件流指针的位置
参数:stream:文件流指针
offset:偏移量
whence:将文件流指针偏移到什么位置
SEEK_SET:文件头部
SEEK_CUR:当前文件流指针的位置
SEEK_END:文件末尾
返回值:成功:0
失败:-1
int fclose(FILE*fp);关闭文件
fp:文件流指针
作用:关闭文件流(关闭文件)
注意:打开的文件使用完成之后一定要记得关闭文件,否则
当文件句柄持续泄露的时候,最终进程就不能打开新的文件了;内存泄漏,导致进程占用的内存资源过多,如果持续泄漏,操作系统是没有办法满足进程申请内存资源的请求,操作系统就会强杀进程
2.系统调用的文件接口
open:
int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mdoe);
参数:pathname:要打开或创建的文件目录
flags:打开文件时可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
这三个常量,必须且只能指定一个
O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明文件的访问权限
O_APPEND:追加写
举一个例子:O_RDWR | O_CREAT:以可读可写的方式打开文件,若文件不存在则创建它。
mode:当创建一个新文件的时候指定新创建文件的权限;传递一个8进制的数字(就是权限,例如0664)
返回值:成功:新打开的文件描述符
失败:-1
write:写
ssize_t write(int fd,const void *buf,size_t count);
参数:fd:文件描述符
buf:将buf指向的内容写到文件当中去
count:期望写多少字节
返回值:返回写入的字节数量
read:读
ssize_t write(int fd,const void*buf,size_t count);
参数:fd:文件描述符
buf:将文件当中读到的内容写到buf指向的空间当中去
count:期望读多少字节
返回值:返回读到的字节数量
lseek:移动文件流指针
off_t lseek(int fd,off_t offset,int whence);
参数:fd:文件描述符
offset:偏移量,字节单位
whence:偏移的位置
SEEK_SET:文件头部
SEEK_CUR:当前文件流指针的位置
SEEK_END:文件末尾
返回值: 成功:返回偏移的位置,单位字节
失败:返回-1
close:
int close(int fd);
关闭文件描述符
3.文件描述符
(1)文件描述符:文件描述符就是一个小正数(没有负数)
(2)打印文件描述符的值
(3)文件描述符的分配规则:最小未使用
文件描述符不是固定进行分配,而是按照最小未使用原则进行分配的
由上图可知:当我们关闭0号文件描述符的时候,系统给我们fd分配的就是0
4.内核理解文件描述符
从task_struct的角度理解文件描述符是什么?
通过查看源码可知:
当我们创建一个进程的时候就会产生一个task_struct结构体,task_struct结构体中含有一个files_struct结构体指针,指向一个files_struct结构体,在这个files_struct结构体中有一个fd_array数组,文件描述符就是fd_array这个数组的下标。
面试题:一个进程最多打开多少个文件描述符 ?
这个问题的场景并不是在基础IO(面试官和你探讨文件操作的时候问道的),而大多数场景都是和面试官在讨论网络编程的时候,会被问到的。
- 系统当中针对一个进程打开的文件描述符的数量是有限制的。
- 通过ulimit -a可以查看到“open files”的大小,而“open files”的大小就是一个进程可以打开的文件描述符的数量。
- 我的机器中看到的“open files”的大小是1024/100001,也就是意味着,我的机器创建出来的进程最大打开的文件数量就是1024/100001
- “open files”的大小并不是没有办法改变的,是可以通过ulimit -n[count]进行改变。
5.理解文件描述符和文件流指针的区别
(1)从源码的角度理解,文件流指针(struct _IO_FILE)是什么?
文件流指针FILE*及struct _IO_FILE是一个由C标准库定义的结构体,这个结构体中的一个int _filen元素保存文件描述符的数值。
这个结构体数组中含有我们在exit函数执行的时候清理缓冲区时提到的那个缓冲区:
现在就知道为什么_exit函数不清理缓冲区吗?
因为缓冲区是C标准库定义的,而_exit函数是系统调用函数,当然不会清理缓冲区了。
(2)将文件描述符和文件流指针结合起来
文件流指针对应的结构体 struct _IO_FILE这个结构体内部的成员变量 int _fileno保存了对应的文件描述符数值。
总结:文件流指针是标准库操作文件句柄,文件描述符是系统调用接口。
文件流指针中包含了一个成员变量,这个成员变量中保存的就是文件描述符。
库函数内部封装的就是系统调用函数。
缓冲区是由C标准库定义的,是用户态的缓冲区,存在于文件流指针的结构体中。
6.重定向
(1)重定向的符号
>:清空重定向
>>:追加重定向
(2)重定向的接口
int dup2(int oldfd,int newfd);
作用:将newfd的值重定向为oldfd,既newfd拷贝oldfd
参数:oldfd/newfd均是文件描述符
成功:1.关闭newfd
2.让newfd指向oldfd对应的struct file*结构体
失败:1.如果oldfd是一个非法/无效的文件描述符,则重定向失败;newfd没有变化
2.如果oldfd和newfd的值相等,则什么事都没干
(3)重定向代码
重定向标准输出到1.txt文件当中
7.静态库&动态库
什么是库:静态库和动态库都是程序代码的集合。一般是为了方便将程序提供给第三方使用,就是将程序编写成二进制库文件提供给第三方(用户)使用调用着不必关心内部实现,只需要关心如何使用(如何调用)即可
(1)动态库
特征:win:没有前缀,后缀为dll
linux:前缀为lib,后缀为.so
生成:使用gcc/g++编译器,增加两个命令行参数
-fPIC
-shared
生成动态库的代码中不需要包含main函数
使用:编译可执行程序的时候,依赖动态库
-L [path] :指定动态库所在的路径
-l[动态库的名称(去掉前缀和后缀之后的名称)]:指定编译可执行程序时,依赖的动态库。
都有哪些方式让程序可以找到动态库:
1.将动态库放到可执行程序的路径下(不推荐)
2.配置LD_LIBBARY_PATH
动态库的环境变量:LD_LIBBARY_PATH(推荐的方式,公式中在客户机器部署的时候,常用的手段)
3.放到系统库的路径下:/lib64
(2)静态库
特征:win没有前缀,后缀为.lib
linux:前缀为lib,后缀为.a
生成:第一阶段:使用gcc/g++将源代码编译成为目标程序(.o)
第二阶段:使用ar -rc 命令编译目标程序成为静态库
ar -rc [静态库文件名称] [目标文件]
注意:如果直接使用源码时不行的
8.软硬链接
软连接:目标文件的快捷方式
生成:ln -s 源文件 软连接文件
注意事项:
- 修改软连接文件,源文件也会被修改
- 源文件如果被删除,软连接文件还在的,修改软连接文件,会重新建立源文件,重新建立链接关系(这种要慎重)一定要在删除源文件的时候,将软链接文件也删除掉
硬链接:目标文件的替身
生成:ln 源文件 硬链接文件
9.文件系统
stat可以看到更多信息
ext2文件系统:
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被 划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设 定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。
- Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
- 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可以说整个文件系统结构就被破坏了
- GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下
块位图( Block Bitmap ): Block Bitmap 中记录着 Data Block 中哪个数据块已经被占用,哪个数据块没有被占用 inode 位图( inode Bitmap ):每个 bit 表示一个 inode 是否空闲可用。 i 节点表 : 存放文件属性 如 文件大小,所有者,最近修改时间等 数据区:存放文件内容
[root@localhost linux]# touch abc[root@localhost linux]# ls -i abc263466 abc
1. 存储属性内核先找到一个空闲的 i 节点(这里是 263466 )。内核把文件信息记录到其中。2. 存储数据该文件需要存储在三个磁盘块,内核找到了三个空闲块: 300,500 , 800 。将内核缓冲区的第一块数据复制到 300 ,下一块复制到 500 ,以此类推。3. 记录分配情况文件内容按顺序 300,500,800 存放。内核在 inode 上的磁盘分布区记录了上述块列表。4. 添加文件名到目录新的文件名 abc 。 linux 如何在当前的目录中记录这个文件?内核将入口( 263466 , abc )添加到目录文件。文件名和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。