基础IO
- C文件IO相关操作
- 磁盘文件与内存文件
- inode(index node)
- 硬链接与软连接
- 硬链接
- 软连接
- 总结
- 动静态库
- 静态库
- 动态库
- 总结
C文件IO相关操作
当前路径:进程运行的时候,所处的路径叫做当前路径
打开文件的时候,一定是进程运行的时候才打开的
在Linux中,一切皆是文件(显示器、键盘也是文件)
printf 打印 -> 写入到显示器文件中
任何进程在运行的时候,都会默认打开3个输入输出流:
stdin(标准输入流):键盘——0
这是程序默认接收输入的流,通常指从键盘输入的数据。程序从这个流中读取数据。你可以通过键盘输入字符或命令,程序会通过标准输入流获取这些信息。
stdout(标准输出流):显示器——1
这是程序默认输出结果的流,通常是输出到显示器上的文本。例如,你在屏幕上看到的打印信息或输出的结果,都是通过标准输出流传递的。
stderr(标准错误流):显示器——2
这是程序用来输出错误信息的流。不同于标准输出流,标准错误流专门用于显示程序错误、警告或其他异常情况的消息。虽然默认也输出到显示器,但stderr的输出通常被独立处理,以便于错误信息和正常输出分开管理。
在计算机中,流(stream)是一种用于在程序中处理输入和输出(I/O)数据的抽象概念。流可以是数据传输的通道,通过它,程序可以与外部世界进行交互。流的类型主要有两种:输入流(input stream)和输出流(output stream)。
系统函数参数传参标志位:int、32bit、理论上可以传递32种标志位
例如:
二进制序列中只有1bit是1
#define X 0x1
#define Y 0x2
#define Z 0x4
open(arg1, arg2 = X | Y , arg3) // 通过按位或 | 合并多个标志,生成一个整数值
{
// 若 arg2 包含 X,则 arg2 & X 结果为非零(条件为真)
if(arg2 & X)
{}
if(arg2 & Y)
{}
}
可以用bit位在参数中传送多个选项
这些宏特点是只有一个二进制位为1
int fd = open("log.txt", 0_WRONLY | 0_CREAT, 0666); // 一次可以传递2个标志位 用 | 的方式
关闭文件:
#include <unistd.h>
close(fd);
一个进程可以打开多个文件吗?——可以
系统中,任意时刻,都可能存在大量的已经打开的文件(打开的文件——文件管理——先描述在组织 )
磁盘文件与内存文件
1、磁盘文件(持久化存储层面)
磁盘文件是存储在硬盘(或者其他持久化存储介质)上的文件。
文件系统会为其维护文件名、文件内容、以及各种元信息(metadata),如大小、权限、创建/修改时间、所有者等。
当进程想要访问某个文件时,需要通过系统调用(如 open())让操作系统内核去查找磁盘上的该文件,并为进程创建相应的内核数据结构(如 struct file)来管理打开的文件。
磁盘文件:文件 = 内容 + 属性(即元信息,如修改日期 类型 等)
2、内存文件(进程打开后的内核层面)
当进程打开一个文件后,在内核中会分配一个与该文件对应的内存数据结构(通常是 struct file 或者跟它相关的结构体,比如 file、inode、dentry 等),也可以把它理解成“内存文件”或“打开文件的内存表示”。
这些数据结构包含了:
文件指针(文件偏移量,即当前读写的位置)。
指向文件元信息的指针(如 inode、dentry,里面包含文件在磁盘上的各种信息)。
缓冲区/缓存相关信息:操作系统会对文件进行缓存(page cache / buffer cache),以便加速读写。
引用计数等其他管理信息(比如有多少进程共享打开这个文件描述符)。
因为每个进程都可能打开多个文件,所以操作系统会限制单个进程能打开的最大文件描述符数。这个限制可以通过系统参数(如 ulimit -n)进行修改。
3、「内存文件」和「磁盘文件」的区别与联系
a)持久化 vs 临时性
磁盘文件是持久化的,保存在文件系统中,不会因为进程退出而消失(除非显式删除)。
内存文件(打开时对应的 struct file 等)只是进程运行时的状态,进程退出或关闭文件后,这些结构就被内核释放了。
b)内容与属性
磁盘文件不仅有真正的文件内容,还在文件系统中保存着各种元信息(权限、时间戳、所有者等)。
内存文件则是内核为该文件创建的管理结构,里面记录了当前读写位置、引用计数、缓存信息等,更多是“状态”和“指针”,本身并不真正存储文件的所有内容(实际内容还在内核的缓存区或者磁盘中)。
c)打开/关闭文件的过程
当用户通过 open() 打开一个磁盘文件时,内核会做以下工作:
找到文件系统中的对应文件(磁盘文件)。
创建/获取对应的 inode、dentry 等对象。
为该文件创建或复用 struct file 结构,并初始化读写偏移、权限检查、缓存等信息。
返回一个文件描述符(fd)给用户进程。
关闭文件时(如 close(fd)),内核会相应地更新内存数据结构的引用计数,如果引用计数为 0,就释放这些结构。磁盘文件则依旧保存在文件系统里。
d)文件读写时的数据流
当进程对一个已打开的文件进行读写时,通常会先访问内核的缓存(Page Cache/Buffer Cache)。如果缓存中没有数据或者缓存失效,则会触发对磁盘文件的实际 I/O。
这就是图中所说的“延迟 fd 读等待缓冲数据”“缓冲区”等概念——内核会将部分文件内容读到内存里,也会把进程写入的数据先放到内存缓存中,然后再同步回磁盘。
“一个进程可以打开多少个文件?可以无限吗?”
1、理论上如果没有操作系统和硬件的限制,打开文件数可以很多,但实际上操作系统会对单个进程以及系统整体可打开文件数做限制。
2、如果打开文件数过多,会导致系统资源耗尽(文件描述符表、内核缓存、内存等),影响稳定性。
磁盘文件与内存文件类似于程序与进程的关系
补充:内核
在计算机系统中,“内核”(Kernel)指的是操作系统的核心组件,它负责管理系统资源和硬件设备,并为用户程序提供各种服务。主要包括以下几个方面:
1、资源管理
内存管理:负责分配和回收内存空间,管理虚拟内存。
CPU调度:决定哪些进程或线程优先获得CPU时间。
设备管理:控制和协调硬件设备(如磁盘、网络、输入/输出设备)的使用。
2、系统调用接口
内核提供系统调用接口,用户程序通过这些接口请求操作系统执行底层操作,如文件读写、网络通信、进程创建等。
3、安全和权限控制
内核负责对系统资源进行保护,确保各个程序在受限的权限下运行,防止恶意访问和破坏。
简单来说,内核就像一个“大管家”,在后台协调各种系统资源,确保整个计算机系统能够高效、稳定地运行。
inode(index node)
什么是 inode?
inode 是文件系统内部的一个数据结构,用来保存一个文件的各种“描述信息”(叫做元数据),而不是文件的实际内容。
每个文件在磁盘上都有一个唯一的 inode(用一个数字来标识)
inode 里包含哪些信息?
文件大小:记录文件有多大。
所有者和权限:谁能读、写、执行这个文件。
时间戳:包括文件最后被访问、修改或状态改变的时间。
数据块指针:指向存储文件实际内容的磁盘块的位置。
硬链接计数:告诉我们有多少个文件名指向这个 inode。
文件系统结构中 inode 的位置
在一个文件系统中,磁盘通常被分成若干块。以 ext2 文件系统为例:
超级块:记录整个文件系统的总体信息。
块组:每个块组中包含了 inode 表(存储所有 inode 的地方)、inode 位图(标记 inode 是否被使用)、数据块位图(标记数据块是否被使用)和数据块(真正存放文件内容)。
文件操作中 inode 的作用
创建文件时:
分配一个空闲的 inode,并把文件的元数据写进去。
分配磁盘数据块,把文件内容写进去。
在目录中记录这个文件名与 inode 号的映射关系。
删除文件时:
系统会减少这个 inode 的硬链接计数。如果计数变成 0,系统会释放 inode 和占用的数据块。
inode(索引节点) 是文件系统中用于描述文件元数据的结构,包含以下信息:
文件大小、所有者、权限、时间戳(访问、修改、属性变更时间)。
文件数据块的存储位置(指向实际数据块的指针)。
硬链接数(记录有多少文件名指向该inode)。
唯一性:每个文件对应一个唯一的inode号,通过 ls -i 可查看
$ ls -i test.c
263715 test.c # inode号为263715
硬链接与软连接
硬链接
什么是硬链接?
硬链接就是让多个不同的文件名指向同一个 inode,也就是同一个文件。
每个文件名实际上只是这个 inode 的一个“别名”。
举例子讲解:
硬链接(Hard Link):想象你有一个房子(代表实际存储数据),它有一扇门(文件名A)供你进入。当你创建一个硬链接时,就相当于你在房子另一侧又装了一扇门(文件名B),这扇门直接通向同一个房子。无论你从哪扇门进入(A或B),看到的都是同一个房子(数据完全相同)。如果你拆掉一扇门(比如删除文件名A),房子依然存在,你还可以通过另一扇门(文件名B)进入。这就是硬链接:多个名字共享同一个数据块(文件的实际内容和相关信息),它们完全等价。
硬链接的特点
相同 inode 号:多个硬链接文件指向的 inode 号都是一样的。
删除文件:如果删除其中一个硬链接,只是减少了 inode 的链接计数;只要还有其他硬链接存在,文件内容就不会消失。
限制:
不能跨越不同的文件系统(因为 inode 是文件系统内部的)。
通常不允许对目录创建硬链接,防止造成目录结构混乱。
即:
共享数据:两个文件名共同引用同一份数据,修改任一文件的数据都会反映在另一文件上。
删除保护:删除其中一个文件名不会丢失数据,只要还有至少一个硬链接存在。
灵活管理:可以让同一文件在不同位置拥有不同的名称,便于系统管理和备份。
创建一个硬链接:
ln a.txt b.txt
# 这条命令的意思是创建一个硬链接,让文件 b.txt 指向与 a.txt 相同的文件数据。
# 也就是说,系统中会有两个文件名(a.txt 和 b.txt),但它们都指向同一个底层数据(同一个 inode)。
软连接
什么是软链接?
软链接类似于 Windows 系统中的“快捷方式”。它本身是一个独立的文件,里面存储了目标文件的路径。
软链接就像是路边的一块指示牌,上面写着“去房子A”。这块牌本身并不代表房子,而只是告诉你如何去找房子。软链接本身是一个独立的文件,它存储了目标文件的路径。如果目标房子(目标文件)搬走或者被拆除了,这块指示牌(软链接)依然存在,但已经找不到房子了,牌上的指引就无效了。
软链接的特点
独立 inode:软链接有自己的 inode,不与目标文件相同。
跨文件系统:可以指向其他文件系统中的文件。
删除目标文件:如果原文件被删除,软链接就找不到目标文件,这时称为“悬空链接”。
可以链接目录:软链接不仅可以链接文件,还可以链接目录。
即
独立存在:软连接有自己的文件标识(自己的 inode),但它的内容只是一条指向目标文件路径的指针。
跨分区:软连接可以指向其他磁盘分区的文件,因为它只是保存路径,不直接关联底层存储信息。
原文件删除问题:如果目标文件被删除了,软连接就“失效”了,因为它指向的地址已经没有内容。
如果你用 ln -s a.txt c.txt 创建软链接,c.txt 里面保存的是“a.txt”的路径。当你访问 c.txt 时,系统会读取里面的路径,然后找到 a.txt 的内容。删除时:如果 a.txt 被删除了,c.txt 依然存在,但它指向的位置已经没有数据了,访问 c.txt就会出错。
总结
为何需要两种链接?
场景互补:
硬链接:适合需要高效、稳定访问同一数据的场景(如日志文件的多个引用)。
软链接:适合需要动态路径管理或跨文件系统的场景(如版本切换、快捷方式)。
功能差异:
硬链接:直接绑定inode,无额外存储开销,但受限于文件系统和目录支持。
软链接:通过路径间接引用,灵活但依赖路径有效性。
数据安全:
硬链接:删除任一链接不影响数据,需所有链接删除后数据才释放。
软链接:目标文件删除后需手动修复链接,避免悬挂。
动静态库
静态库
什么是静态库?
先举个例子:你打算烤蛋糕,于是你购买了一个专门为烤蛋糕设计的“工具包”,里面包含了搅拌器、量杯等所有必要的工具。你买下这个工具包后,把里面的工具全部带回家,放在自己的厨房里使用。静态库就像这个“工具包”。当你编译程序时(烤蛋糕前的准备),编译器会把库中的代码(工具)直接复制到你的可执行文件(你的厨房中)。这样,不管你把可执行文件(厨房)搬到哪里,它里面都有所有需要的工具(库函数),不会因为找不到外部工具而影响烤蛋糕(程序运行)。
优缺点:
优点:程序独立,运行时不依赖其他外部文件。
缺点:每个程序都需要一份完整的工具包(库代码),占用空间较大。如果工具包有更新,你需要重新制作整个蛋糕(重新编译程序)。
静态库是一组已经编译好的目标代码,被打包成一个文件(在 Linux 上通常以 .a 结尾,在 Windows 上可能是 .lib)。
编译时:当你编译程序时,静态库的代码会直接复制到你的可执行文件中。
动态库
什么是动态库?
假设你住在一个小区,每个住户都在同一个公共厨房里烤蛋糕。公共厨房里已经配备了一整套专业的烤蛋糕工具。你只需要在烤蛋糕时去公共厨房借用这些工具,而不必自己购买和保存一整套。动态库就像公共厨房里的工具。当你编译程序时,程序只记录使用工具的“说明”(工具名称和使用方法),而实际的工具在运行时由操作系统从公共厨房中加载。这样,多个程序(多个住户)都可以共享同一套工具,而不必各自保存一份工具。
优缺点:
优点:程序体积较小,多个程序共享同一份工具;工具更新后,所有使用该工具的程序都能自动使用新版本(前提是接口没变)。
缺点:程序运行时必须能找到公共厨房(动态库文件),如果工具不在公共厨房或路径不对,程序就无法顺利运行。
总结
静态库和动态库主要都是用来封装和复用程序代码的工具,目的是让开发者不必重复编写相同的代码,而可以直接使用已经写好的代码模块。具体来说:
静态库:
用途:在编译时将库中的代码直接嵌入到你的可执行文件中。
作用:你编译出来的程序自带所有必要的代码,不需要在运行时依赖外部文件。
使用场景:适合不希望在部署时再额外管理库文件的情况,比如一些独立运行的程序或需要快速启动的应用。
特点:程序体积较大,更新库代码后需要重新编译依赖程序。
动态库:
用途:在程序运行时按需加载库中的代码。
作用:多个程序可以共享同一份动态库代码,从而节省磁盘空间和内存,更新库代码时只需替换库文件,而不必重新编译所有程序。
使用场景:适合多个程序共同使用同一套功能模块、需要灵活更新或者资源共享的情况。
特点:程序启动时需要找到并加载动态库,如果库文件缺失或路径不对,程序就无法正常运行。
静态库和动态库都是为了提高代码复用性和管理性,只不过静态库在编译时将代码固定进程序,而动态库则在运行时共享使用。