Linux 基础 IO 收官:库的构建与使用、进程地址空间及核心知识点全解----《Hello Linux!》(11)

文章目录

前言

在前两篇内容中,我们先后打通了 Linux 基础 IO 的 "上层操作链路"(用户态接口、系统调用、缓冲区)与 "底层存储逻辑"(文件系统、inode、软硬链接),搞懂了 "程序如何操作文件" 以及 "文件如何存储在磁盘" 的核心问题。而基础 IO 的完整知识体系,还离不开两个关键延伸:一是代码复用的载体 ------ 库(静态库与动态库),二是程序运行的内存基础 ------ 进程地址空间,再加上内存管理、IO 数据拷贝、未定义行为等补充细节,才能真正吃透从 "代码编写" 到 "程序运行" 再到 "IO 交互" 的全流程逻辑。

本文作为 Linux 基础 IO 系列的收官篇,将聚焦三大核心模块:首先详解静态库与动态库的构建命令、链接方式、底层差异(如静态拷贝 vs 动态共享、加载机制),帮你搞懂 "为何静态库删除不影响程序,动态库却需要单独加载";其次深入剖析进程地址空间的本质,解释程序加载后虚拟地址与物理内存的映射关系,为理解 IO 操作的内存交互打下基础;最后补充内存管理(页框、slab 分配器)、IO 数据拷贝流程、函数参数求值顺序等关键细节,并附上精选作业题,帮你巩固全系列知识点。

库的使用是实际开发中代码复用的核心,进程地址空间是程序运行的内存基石,而补充知识点则能帮你规避开发中的 "坑"------ 这三部分共同构成了基础 IO 的完整知识闭环。无论你是想掌握库的实际部署技巧,还是想理解程序与内存、IO 的深层交互,这份收官指南都能帮你梳理逻辑、查漏补缺,最终形成 "用户态接口 - 内核机制 - 存储 / 内存底层" 的完整认知,为后续深入学习并发编程、网络 IO 等内容筑牢基础。

动态库和静态库

库的安装其实就是把库搞到系统的默认放这个的位置里去

库文件里面不能包含main函数,因为库是被别人调用着来用的

静态库

静态库的话就是把源代码编译之后打包成libxxx.a文件(静态库的命名lib .a必须要有哈)--从而让别人获得不到源文件(通常把静态库和头文件搞在这个文件里面给)

静态库的话是给单个程序一份,互不干扰(所以搞成可执行程序之后,把之前静态库删了都没事--因为可执行程序里面有一份)

权限一般是这样的:

(没有可执行权限)
生成静态库:ar -rc libmymath.a add.o sub.o -rc表示存在就替换,不存在就创建

链接:假如长这样的:

则需要gcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymathg++也是这么高搞)

解释:因为头文件和库和main.c不在目录的同一级或者系统默认存这些的位置,所以需要指定头文件和库的位置

-I:指定头文件的搜索目录。 -L:指定库文件的搜索目录

-l:指定要链接的库的名称。(这个不能缺少,而且除去lib 和.a才是库的名字)(一般库名字跟-l连在一起写,当然中间加个空格也行)

第三方库在使用的时候,必定要-l(无论是动态库还是静态库,无论放没放在系统默认位置)

gcc可以同时链接多个库

gcc默认是调用动态库,除非只有静态库或者-static
引申:在使用自定义头文件的时候,记得写路径(没在当前目录且同级的话)

其实也可以直接头文件写绝对路径就行了--但是可移植性差,一般不用

动态库(也被称为共享库)

就是把这个库同时给多个程序使用(属于共享的)

库名的格式:libxxx.so lib和.so必须要有

动态库的权限一般是这样的:

(有执行权限--因为它需要独自被加载到内存里)
生成动态库:

bash 复制代码
gcc -fPIC  -c  mylog.c     //生成mylog.o
 gcc -shared -o liblylog.so  mylog.o   //生成动态库liblylog.so

这里的-fPIC是产生位置无关码
     -shared是生成共享库格式

链接的话,跟上面的静态库是一样的

但是如果想要允许链接之后生成的可执行程序的话,必须先加载动态库,但操作系统不知道这个动态库在哪,现在只有编译器是知道的--所以目前加载器加载不了
引申:ldd命令可以显示可执行文件或共享库所依赖的 动态链接库列表

使用方法:ldd 文件名

fPIC

这个位置无关码的意思:直接用偏移量对库中函数进行编址

让自己内部函数不采用绝对编址的方法去指向动态库里面的这个函数,而是只表示这个函数在库里面的偏移量就行了

这个设计的原因:动态库被加载到内存地址空间里面的位置是不固定的

让动态库能被加载的方法

1.拷贝到系统默认的库路径/lib64 或者/usr/lib64/(一般云服务器都是前者才是)

--一般用的是第一种方法

2.在系统默认的库路径下建立软链接

3.将自己库所在的路径(不用写库名,而是上一级)添加到系统的环境变量LD_LIBRARY_PATH里面

(这个环境变量里面存的就是动态库的搜索路径)

4.在/etc/ld.so.conf.d里面建立自己的动态库路径的配置文件,然后再ldconfig一下

bash 复制代码
eg: 先在这个路径底下创建 xxxxx.conf
然后在里面写动态库所在的路径就行了(不是写到库名哈)  --eg:用vim去写

问题: 为啥动态库需要被加载,但是静态库不用?

因为动态库是被程序共享的,只有一份,存在物理内存里面,程序通过共享区对应的页表去访问它的,所以需要被加载

静态库在链接时是被完整地拷贝到了可执行文件里面,可执行文件外部的那个静态库已经没用了

进程地址空间的深入了解

程序在没有被加载到内存前,其内部也是有地址的,采用的是平坦模式(0-4GB),里面的地址从0开始这样(这个地址叫做虚拟地址--但是一般在这里喜欢叫成逻辑地址)
程序加载后:

会先把程序的入口地址(是虚拟地址)给CPU,然后发生缺页中断,就会把对应的数据加载到内存里,更新页表,把物理地址和虚拟地址连起来--这样就有了物理地址了
引申:CPU内读取到的指令,内部可能有数据,也可能有虚拟地址(虚拟地址的话交给MMU去用页表去查)

几点补充

物理内存和磁盘交换数据都是以4KB为单位的,物理内存里面的叫页框,磁盘里面的叫页帧

以4KB为单位的好处:

1.减少IO次数,也就是减少访问外设的次数--硬件层面

2.因为局部性原理,所以有预加载机制--软件层面
如果需要存储的数据远小于4KB的话,4KB还可以被细分--比如Linux用的是slab分配器
局部性原理:在访问数据的时候,有极大概率周围的数据之后也会被用到
操作系统管理内存的方法:

操作系统是可以看到内存的物理地址的,对这个物理内存也是采用的先描述再组织

把每个页框的信息放到一个struct page里面,然后搞一个数组去存struct page,就把对内存的管理变成了对数组的管理了

所以访问内存的过程,就是去找这个4KB对应的结构体,然后就能在系统中找到对应的物理页框

所有申请内存的动作,其实都是在访问存page的数组
基数树,基树或者叫字典树的概念:

cpp 复制代码
有一个结构体 eg: 
struct slot
{
int level;
void* slot[3];
}
slot里面继续存slot结构体这样,到最后里面才存数据

Linux中,我们的每一个进程,打开的每一个文件都有自己的inode和文件页缓冲区

当然,struct_file里面还有其他的文件属性和其他东西哈,比如:file_operators

所以:从外设写到磁盘要三次拷贝

eg:用C语言写的话,C语言缓冲区拷贝给内核的缓冲区,内核缓冲区再拷贝给文件缓存区,文件缓存区再给磁盘

引申:write到磁盘则只用两次拷贝

数据还没给到磁盘,还在内存里面的话,是不算保存了的--关机直接就没了

操作系统把数据给磁盘需要通过驱动:操作系统把刷写请求排队(靠的越近的数据在一块,刷写效率高),把这个排的队交给驱动去刷写
函数参数的求值顺序是未定义的行为--要看编译器

cpp 复制代码
eg:printf("%d,%d",div(10,0),myerrno);
不知道是先执行的div还是先搞得myerrno
这个跟,表达式区分哈

作业部分

cpp 复制代码
 Linux下两个进程可以同时打开同一个文件,这时如下描述错误的是:(D)
A.两个进程中分别产生生成两个独立的fd
B.两个进程可以任意对文件进行读写操作,操作系统并不保证写的原子性
C.进程可以通过系统调用对文件加锁,从而实现对文件内容的保护
D.任何一个进程删除该文件时,另外一个进程会立即出现读写失败
//若进程已经打开文件,文件被删除时,并不会影响进程的操作,因为进程已经具备文件的描述信息
E.两个进程可以分别读取文件的不同部分而不会相互影响
F.一个进程对文件长度和内容的修改另外一个进程可以立即感知
//文件内容的修改是直接反馈至磁盘文件系统中的
//因此当文件内容被修改,其他进程因为也是针对磁盘数据的操作,因此可以立即感知到
cpp 复制代码
以下描述正确的是(B)  --这个选项要知道
A.文件描述符和文件流指针没有任何关系
B.文件流指针结构中封装了文件描述符//文件流指针指的是eg:C语言封装的结构体FILE
C.通过open打开文件返回的FILE *fp可以直接使用read读取数据
//open返回的是文件描述符
D.通过open打开文件返回的FILE *fp可以直接使用fread读取数据
cpp 复制代码
以下描述正确的是 [多选](ABD)
A.程序中打开文件所返回的文件描述符, 本质上在PCB中是文件描述符表的下标
B.多个文件描述符可以通过dup2函数进行重定向后操作同一个文件
C.在进程中多次打开同一个文件返回的文件描述符是一致的
//连续open同一个文件,返回的文件描述符是不一样的
D.文件流指针就是struct _IO_FILE结构体,该结构体当中的int _fileno 保存的文件描述符, 是一对一的关系
cpp 复制代码
bash中,需要将脚本demo.sh的标准输出和标准错误输出重定向至文件demo.log,以下哪些用法是正确的 [多选]
(ABCD)
A.bash demo.sh &>demo.log
B.bash demo.sh >&demo.log
//这俩都是把12同时重定向到demo.log里面
C.bash demo.sh >demo.log 2>&1
D.bash demo.sh 2>demo.log 1>demo.log
cpp 复制代码
下面关于Linux文件系统的inode描述错误的是:(A)
A.inode和文件名是一一对应的
B.inode描述了文件大小和指向数据块的指针
C.通过inode可获得文件占用的块数
D.通过inode可实现文件的逻辑结构和物理结构的转换
cpp 复制代码
 Linux中包括两种链接:硬链接(Hard Link)和软连接(Soft Link),下列说法正确的是(A)
A.软连接可以跨文件系统进行连接,硬链接不可以
//因为软连接里面存的是目标文件的路径
B.当删除原文件的时候软连接文件仍然存在,且指向的内容不变
C.硬链接被删除,磁盘上的数据文件会同时被删除
D.硬链接会重新建立一个inode,软链接不会
cpp 复制代码
使用In命令将生成了一个指向文件old的符号链接new
如果你将文件old删除,是否还能够访问文件中的数据?(A)

A.不可能再访问
B.仍然可以访问
C.能否访问取决于文件的所有者
D.能否访问取决于文件的权限

//因为这里说了是符号链接,所以是软链接
cpp 复制代码
关于静态库与动态库的区别,以下说法错误的是(A)
A.加载动态库的程序运行速度相对较快
B.静态库会被添加为程序的一部分进行使用
C.动态库可用节省内存和磁盘空间
D.静态库重新编译,需要将应用程序重新编译
cpp 复制代码
下面哪一个不是动态链接库的优点?(B)
A.共享
B.装载速度快
C.开发模式好
D.减少页面交换
相关推荐
一起养小猫2 小时前
Flutter for OpenHarmony 实战:碰撞检测算法与游戏结束处理
算法·flutter·游戏
板面华仔2 小时前
机器学习入门(一)——KNN算法
人工智能·算法·机器学习
RisunJan2 小时前
Linux命令-let(执行算术运算)
linux·服务器
进击的小头2 小时前
创建型模式:组合模式(C语言实现与嵌入式实战)
c语言·开发语言·组合模式
Fcy6482 小时前
C++ 11 新增特性(下)
开发语言·c++·c++11·lambda·包装器
倔强的石头1062 小时前
多模融合重塑文档数据库:金仓数据库 MongoDB 兼容版的技术实践
数据库·mongodb·kingbase
猿小羽2 小时前
Spring AI + MCP 实战:构建标准化、可扩展的 AI Agent 架构体系
java·spring boot·llm·架构设计·ai agent·spring ai·mcp
闻缺陷则喜何志丹2 小时前
【数论】P12191 [蓝桥杯 2025 省研究生组] 01 串|普及+
c++·数学·蓝桥杯·数论·洛谷
m0_635647482 小时前
Qt中使用opencv库imread函数读出的图片是空
开发语言·c++·qt·opencv·计算机视觉