目录
0. 前言
1. 文件存储设备—磁盘
1.1 文件及存储介质
1.2 磁盘结构
1.3 磁盘存储结构
1.4 磁盘的抽象(虚拟、逻辑)结构
1.5 磁盘分区管理
2. 理解文件系统
2.1 Linux磁盘文件管理
2.2 文件inode属性及Data block数据追溯
2.3 inode编号及文件名
2.4 创建、删除及查看文件filesystem运作
3. 软硬链接
3.1 创建软硬链接
3.2 软硬链接的区别
4. 动静态库
4.1 动态链接和静态链接
4.2 静态库的制作
4.3 动态库的制作
4.3 静态库的使用方法
4.3.1(不太建议)方法一,安装第三方静态库到系统路径下
4.3.2 方法二,硬链接静态库
4.4 动态库的使用方法
4.4.1 动态库的加载过程
4.4.2 (不太建议)方法一,安装第三方动态库到系统路径下
4.4.3 方法二,设置环境变量$LD_LIBRARY_PATH
4.4.4 方法三,修改配置文件/etc/ld.so.conf.d/
4.4.5 (推荐)方法三,在系统路径下建立软连接
4.4.6 其他方法,设置登录脚本
4.5 为什么要有库?推荐库
0. 前言
有关上篇文章 基础IO(1) 问题遗留解答及总结:
问题如下:
1. 如下述代码,重定向后,使用C标准库函数写入后,使用系统调用接口close关闭文件后,运行后,其重定向文件内部没有数据!而在close之前使用C标准库提供的fflush函数后,文件内部便有了数据,作何解释?
#include<iostream> #include<cstdio> #include<unistd.h> #include<cstring> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h>int main(){close(1);int fd = open("log.txt", O_CREAT | O_TRUNC | O_WRONLY, 0666);if(fd == -1){perror("open");return 1;}printf("hello printf\n");close(fd);return 0; }
运行结果:
[customer@VM-4-10-centos 2review]$ ./myfile [customer@VM-4-10-centos 2review]$ ll total 20 -rw-rw-r-- 1 customer customer 0 Aug 24 15:45 log.txt -rw-rw-r-- 1 customer customer 76 Aug 24 15:43 Makefile -rwxrwxr-x 1 customer customer 8856 Aug 24 15:43 myfile -rw-rw-r-- 1 customer customer 355 Aug 24 15:43 myfile.cc [customer@VM-4-10-centos 2review]$ cat log.txt [customer@VM-4-10-centos 2review]$
而当加入fflushC标准库函数后,其文件内部有结果:
[customer@VM-4-10-centos 2review]$ clear [customer@VM-4-10-centos 2review]$ ./myfile [customer@VM-4-10-centos 2review]$ ll total 24 -rw-rw-r-- 1 customer customer 14 Aug 24 15:47 log.txt -rw-rw-r-- 1 customer customer 76 Aug 24 15:47 Makefile -rwxrwxr-x 1 customer customer 9008 Aug 24 15:47 myfile -rw-rw-r-- 1 customer customer 375 Aug 24 15:47 myfile.cc [customer@VM-4-10-centos 2review]$ cat log.txt hello printf [customer@VM-4-10-centos 2review]$
解释如下:由上篇文章可知,用户级缓冲区是由C标准库提供和维护的,向显示器刷新策略为行刷新,向磁盘文件内部刷新策略为全刷新,由于重定向stdout为磁盘文件,因此数据被保存在缓冲区,而系统调用接口close及操作系统底层并不知道C所提供的缓冲区的概念,因此必须在使用C标准库所提供的fflush函数,刷新到内核缓冲区,才会得到结果!
2. 可知C语言C++程序,默认打开三个流,C(stdin,stdout,stderr )C++(cin,cout,cerr),in out分别对应着标准输入和标准输出,那么stderr和stdout,都是对应显示器文件又有什么区别呢?看下述代码
#include<iostream> #include<cstdio> #include<unistd.h> #include<cstring> #include<sys/stat.h> #include<sys/types.h> #include<fcntl.h>int main(){printf("hello printf 1\n");fprintf(stdout, "hello fprintf 1\n");fprintf(stderr, "hello fprintf 2\n");const char* s1 = "hello write 1\n";write(1, s1, strlen(s1));const char* s2 = "hello write 2\n";write(2, s2, strlen(s2));std::cout << "hello cout 1" << std::endl;std::cerr << "hello cout 2" << std::endl; return 0; }
运行结果如下:
[customer@VM-4-10-centos 2review]$ ./myfile hello printf 1 hello fprintf 1 hello fprintf 2 hello write 1 hello write 2 hello cout 1 hello cout 2
有运行结果可知,C语言stdout stderr C++语言cout cerr都是往显示器打印,进行如下测试结果:
[customer@VM-4-10-centos 2review]$ clear [customer@VM-4-10-centos 2review]$ ./myfile>ok.txt 2>err.txt [customer@VM-4-10-centos 2review]$ cat ok.txt hello write 1 hello printf 1 hello fprintf 1 hello cout 1 [customer@VM-4-10-centos 2review]$ cat err.txt hello fprintf 2 hello write 2 hello cout 2
解释如下:stdin和stderr分别对应着显示器文件,stdin封装文件描述符为1的被创建打开的显示器文件,stderr封装文件描述符为2的被创建打开的显示器文件,因此在运行程序时,都会向显示器输出,而重定向时,默认重定向关闭的是stdin,即1号文件描述符的文件,当将2号文件描述符也进行重定向,此时便可以将进程运行错误信息和成功信息分别根据其文件描述符不同重定向至不同的文本文档,形成日志文件。
基础IO(1)总结如下:
Linux一切皆文件,是一种设计则学,内核将文件的全部数据内容和属性(包括操作方法使用函数指针形成运行时多态)全部封装到struct file结构体,当打开文件,OS针对文件系统会根据所打开文件的进程的PCB创建相应的struct file,将文件加载到内存,并加载当文件管理的数据结构中,并将struct file结构体的指针填入到对应进程PCB的struct files*所指向的struct files中的struct file* array[],从而对文件的操作底层调用文件file的操作方法,进程创建时,会继承父进程的文件描述符,而缓冲区则是标准库所提供的用户级缓冲区,可通过一般策略和特殊策略,减少IO预备次数及频繁使用次数,提高效率!
1. 文件存储设备—磁盘
在计算机上除了被打开的文件,有没有没有被打开的文件? 磁盘——磁盘级文件
学习磁盘文件侧重点:
单个文件系统 —— 这个文件在哪里?这个文件的其他属性是什么?...
站在系统角度 —— 一共有多少个文件?各自属性在哪里?如何快速找到?我们还可以存储多少个文件?如何快速找到指定的文件? ...
如何进行对磁盘文件分门别类的存存储,用来支持更好的存取?
1.1 文件及存储介质
内存 —— 掉电易失存储介质
磁盘 —— 永久性存储介质 - SSD、光盘、U盘、磁带、flash
(服务器主流的依然是磁盘,性价比高,存储容量大,价格便宜)
磁盘是一个外设,磁盘还是计算机中唯一的机械设备!(速度慢,OS一定会有提速方式)
1.2 磁盘结构
磁盘具有:磁盘盘片,磁头,伺服系统,音圈马达...等各种硬件组成
音圈马达运行,盘片旋转,磁头左右快速摇摆,便是寻址过程!
一个磁盘(如一个 1T 的机械硬盘)由多个盘片(如下图中的 0 号盘片)叠加而成。
盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。
物理性质 : 盘面上会存储数据 —— 计算机只认识二进制(两态) —— 磁铁物质(正负极) —— 盘面上存在大量的磁性介质 —— 向磁盘写入,本质就是改变盘片上的正负性(磁头上电子信号放电改变)
1.3 磁盘存储结构
每个盘片被划分为一个个磁道,每个磁道又划分为一个个扇区。
下图显示的是一个盘面,盘面中一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。扇区是磁盘的最小组成单元,通常是512字节。(由于不断提高磁盘的大小,部分厂商设定每个扇区的大小是4096字节)
硬盘通常由重叠的一组盘片构成,每个盘面都被划分为数目相等的磁道,并从外缘的“0”开始编号,具有相同编号的磁道形成一个圆柱,称之为磁盘的柱面。磁盘的柱面数与一个盘面上的磁道数是相等的。由于每个盘面都有自己的磁头,因此,盘面数等于总的磁头数。
如下图:
存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数
上图最后一例图中磁盘是一个 3个圆盘6个磁头,7个柱面(每个盘片7个磁道) 的磁盘,图3中每条磁道有12个扇区,所以此磁盘的容量为:
存储容量 6 * 7 * 12 * 512 = 258048
每个磁道的扇区数一样是说的老的硬盘,外圈的密度小,内圈的密度大,每圈可存储的数据量是一样的。新的硬盘数据的密度都一致,这样磁道的周长越长,扇区就越多,存储的数据量就越大。
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。所有盘面中相对位置相同的磁道组成柱面
其中,最内侧磁道上的扇区面积最小,因此数据密度最大。
磁盘的物理地址
由上,可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。
在“文件的物理结构”中,经常提到文件数据存放在外存中的几号块(逻辑地址),这个块号就可以转换成(柱面号,盘面号,扇区号)的地址形式。
可根据该地址读取一个“块”,操作如下:
① 根据“柱面号”移动磁臂,让磁头指向指定柱面;
② 激活指定盘面对应的磁头;
③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写。
上述论述部分参考文章:
硬盘基本知识(磁头、磁道、扇区、柱面) - js王 - 博客园 (cnblogs.com)
在物理上,如何把数据写入到指定的扇区里?如何找到一个指定扇区?
1. 在哪一个磁道上(柱面)上?(cylinder)
2. 在哪一个面上(盘面)(对应的就是那个磁头)?(head)
3. 在哪一个扇区上?(sector)
上述寻址方式成为CHS寻址
如果有CHS寻址方式,就能找到任意一个扇区上的任意位置,那么所有的位置就都可以找到了
1.4 磁盘的抽象(虚拟、逻辑)结构
磁带中的圆形结构 可以转换为 线性结构
因此磁盘盘片,就可以想象成线性结构
因此想要访问某个扇区,本质就转换为数组下标,只需要知道下标即可!
上述这种寻址方式称为LBA寻址( 逻辑区块寻址(Logical Block Addressing))
此时,访问扇区本质就是将LBA转换为CHS
操作系统访问磁盘 —— LBA寻址 —— CHS寻址
1.5 磁盘分区管理
可知磁盘内容容量大,如何进行有效管理磁盘?
采用分区管理 —— 分而治之,方法相同
对将每个分区划分为更小的分区
最终对磁盘文件的管理 就转化为 对块组的管理
2. 理解文件系统
2.1 Linux磁盘文件管理
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的!
虽然磁盘的基本单位是扇区(512byte),但是操作系统(文件系统)和磁盘进行IO的基本单位是4kb(块大小)(4 * 1024byte)
—— 为什么不以512byte为单位呢?
1. 太小了,会导致多次IO,进而导致效率的降低
2. 如果操作系统使用和磁盘一样的大小,万一磁盘的基本大小改变了,OS源码要不
要改变呢?会导致强耦合!将硬件和软件进行解耦!
文件 = 内容 + 属性, 而Linux存储是将内容和属性分开进行存储的!
- 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了
- Date Blocks:存储文件内容,一个数据块为4kb
- inode Table:inode是一个大小为128字节的空间,保存的是对应文件的属性,如文件大小,所有者,最近修改时间等,而inode Table该块组内,是所有文件inode属性空间的集合,因为需要标识唯一性,所以每一个inode块都有一个编号,及每个文件inode属性空间内都有一个唯一标识,inode编号,一般而言,一个文件,一个inode,一个inode编号
- Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成
- GDT,Group Descriptor Table:块组描述符,描述块组属性信息,这个块组多大,已经使用多少了,有多少个inode,已经占用了多少,还剩多少,一共有多少个block,使用了多少block......
- 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
- inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用
我们将块组分割成上面内容,并且写入相关的管理数据!每一个块组同样如此,整个分区就被写入了文件系统信息!(格式化过程)
2.2 文件inode属性及Data block数据追溯
可知,一个文件只对应一个inode属性节点,inode编号!
一个文件只能有一个Data block吗?答案是,不一定!
哪些Data block属于同一个文件?找到文件,只要找到对应的inode编号,就能找到该文件的inode属性集合,可是,文件的内容如何找到呢?如下图
只要知道inode编号,便可以通过inode Table找到其属性集合,而属性集合中包含了其使用的Data block,便可以找到文件的内容!
对于大型文件,其inode属性中block全部使用,仍不够,其数据如何存储呢?
答案是: 不是所有的Data block只能用于存储文件数据,也可以存储其他块的块号!使用间接索引的方式,就可以找到大量的Data block
2.3 inode编号及文件名
找到文件 - 找到文件inode编号 - 找到特定分区 - 根据inode Table找到inode属性 - 根据inode属性找到Data block
怎么知道inode编号?文件名和inode的关系?
Linux中,inode属性里,没有文件名这样的说法!
[root@localhost linux]# touch hello [root@localhost linux]# ls -ia hello 131023 . 131055 .. 131074 hello
如上图,目录及inode的映射关系:
1. 一个目录下,可以保存很多文件,但是这些文件没有重复的文件名!
2. 目录是文件,因此目录需要有自己的inode,有自己的Data block,而目录的Data block存储的是文件名和对应inode编号的映射关系,都具有唯一性,互为Key值!
进入目录需要x权限,而在目录下创建文件需要w权限,必须要有w权限,才能将映射关系写入到目录的Data block内,才能创建文件!显示文件名和属性需要具有r权限,要拿到目录下文件的inode及属性,必须得到目录Data block内的映射关系,因此必须具备r权限,得到目录下的文件名和对应的inode,进而找到inode属性,进行显示!
总: 找到inode编号,一定是依托目录结构的,在目录Data block保存了对应的inode编号及文件名映射
2.4 创建、删除及查看文件filesystem运作
创建一个新文件主要有一下4个操作:
- 存储属性 内核先在inode Bitmap中找到一个空闲的inode节点(这里是131074),将其inode Bitmap中的0置为1。拿到inode编号,内核把文件信息属性记录到inode Table中。
- 存储数据 该文件需要存储在三个磁盘块,内核在Block Bitmap找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据,复制到300,下一块复制到500,以此类推。
- 记录分配情况 文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
- 建立目录映射 添加文件名及inode(来自filesystem)映射到目录的Data Block中
删除一个文件主要操作:
找到目录对应的Data Block,找到文件名映射的inode,找到对应inode Table属性将其对应数据块Block Bitmap置为零(并不将数据清除,下次写入直接覆盖),将inode Bitmap置为零(inode Table也不清楚,下次使用直接覆盖),将目录映射关系去除,完成删除!
因为只是将inode对应的Block Bitmap、inode Bitmap置为0,目录Data Block清除映射关系,所以在文件数据及inode属性未被覆盖的情况下,获取到已删除文件的inode,可以进行数据恢复!
查看一个文件主要操作:
ls显示文件名,直到找到当前目录的inode及Data Block,便可以找到该目录下各个文件的映射关系,便可以找到各个文件的inode属性
注意:
文件系统是在分区或格式化时被写入的,进而分成对应的块!其中inode Bitmap128kb 及 Data Block是固定的
Linux下创建文件失败的原因:
1. 文件分区下Data Block已经被全部使用,而inode Bitmap未被全部使用
2. 文件分区下inode Bitmap全部被使用,Data Block未被全部使用
3. 软硬链接
3.1 创建软硬链接
软链接指令:ln -s 链接文件 新文件
硬链接指令:ln 链接文件 新文件
[customer@VM-4-10-centos 3review]$ ln -s testLink1.txt soft.txt [customer@VM-4-10-centos 3review]$ ln -s /usr/bin/ls bySearch [customer@VM-4-10-centos 3review]$ ln testLink2.txt hard.txt [customer@VM-4-10-centos 3review]$ ls -il total 0 1317283 lrwxrwxrwx 1 customer customer 11 Aug 25 16:41 bySearch -> /usr/bin/ls 1317280 -rw-rw-r-- 2 customer customer 0 Aug 25 16:39 hard.txt 1317282 lrwxrwxrwx 1 customer customer 13 Aug 25 16:39 soft.txt -> testLink1.txt 1317279 -rw-rw-r-- 1 customer customer 0 Aug 25 16:39 testLink1.txt 1317280 -rw-rw-r-- 2 customer customer 0 Aug 25 16:39 testLink2.txt
3.2 软硬链接的区别
1. 软链接具有独立的inode -> 软链接是一个独立的文件
特性:可以理解为,软链接的文件内容,是指向文件对应的路径
应用:软链接相当于windows下的快捷方式
2. 硬链接没有独立inode -> 硬链接不是一个独立的文件
特性:其inode属性中有一个数字,表示硬链接数,每删除硬链接数减一,直至0
硬链接,不是真正的创建新文件,就是在指定的目录下,建立了文件名和指定的inode的映射关系,仅此而已,硬链接相当于对同一个文件起别名,在inode属性中,有一个引用计数,记录硬链接数,当硬链接数为0,此文件才会被真正删除!
因此,删除文件 除了rm命令,也可以使用unlink命令!
为什么新建一个目录,它的硬链接数为2?
因为在目录内部,有隐藏文件 . ,其inode和dir的inode相同,记录了该dir目录的路径,因此在执行cd .命令时,就相当于 cd dir,而..是上级目录的硬链接 cd .. 就相当于 cd 上级绝对路径
4. 动静态库
在这里涉及到一个重要的概念:函数库
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而 没有定义函数的实现,那么,是在哪里实现“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
4.1 动态链接和静态链接
函数库一般分为静态库和动态库
- 动态链接 DLL:将库中我要的方法的地址,填入我的可执行程序中,建立关联,节省资源。
- 静态链接SLL:将库中方法的实现,直接拷贝到我们的可执行程序中,占用资源。
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。
- 动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。
- gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件
- gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。
- gcc 和 g++ 默认生成的可执行程序默认是动态连接的
- -static : 表明使用静态链接的方式形成可执行程序
yum install -y glibc-static #C语言静态库安装
yum install -y libstdc++-static #C++静态库安装
4.2 静态库的制作
静态库制作:库是要提供给别人使用的,因此库中没有main函数,库的发布文件一般为一个目录,其目录下包括include(主要保存头文件),lib(主要保存.a文件)
静态库文件.a的创建:ar -rc libxxx.a xxx.o xxx.o 其中形成的.a文件名开头必须是lib
生成静态库 [root@localhost linux]# ar -rc libmymath.a add.o sub.o ar是gnu归档工具,rc表示(replace and create) 查看静态库中的目录列表[root@localhost linux]# ar -tv libmymath.a rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o t:列出静态库中的文件 v:verbose 详细信息
如下:
myprint.h
#pragma once #include<stdio.h> #include<time.h>extern void Print(const char* str);
myprint.c
#include"myprint.h"void Print(const char* str){printf("%s[%d]\n", str, (int)time(NULL)); }
mymath.h
#pragma once #include<stdio.h>extern int addToTarget(int from, int to);
mymath.c
#include"mymath.h"int addToTarget(int from, int to){int sum = 0;for(int i = from; i <= to; i++){sum += i;}return sum; }
makefile
libhello.a:mymath.o myprint.oar -rc $@ $^ mymath.o:mymath.cgcc -c $^ -o $@ -std=c11 myprint.o:myprint.cgcc -c $^ -o $@ -std=c11 .PHONY:lib lib:mkdir mylib mylib/include mylib/libcp mymath.h myprint.h mylib/include/rm mymath.o myprint.omv libhello.a mylib/lib/ .PHONY:clean clean:rm mylib -rf
4.3 动态库的制作
静态库的加载,是将代码拷贝进我们的可执行程序内,而程序具有地址空间,因为静态库的使用,需要拷贝到指定地址空间的位置!静态库在编制的时候必须按照程序以绝对地址的方式编址!
动态库采用相对编址方案,不需要考虑程序的地址空间!因此动态库是由与地址无关的目标二进制文件形成!段地址 + 偏移方式
makfile
libhello.so:mymath.o myprint.ogcc -shared $^ -o $@ mymath.o:mymath.cgcc -fPIC -c $^ -o $@ -std=c11 myprint.o:myprint.cgcc -fPIC -c $^ -o $@ -std=c11 .PHONY:lib lib:mkdir mysolib mysolib/include mysolib/libmv libhello.so mysolib/lib/cp myprint.h mymath.h mysolib/include/rm mymath.o myprint.o .PHONY:clean clean:rm mysolib -rfv
运行结果:
[customer@VM-4-10-centos mylib_so]$ ls makefile mymath.c mymath.h myprint.c myprint.h mysolib [customer@VM-4-10-centos mylib_so]$ tree mysolib/ mysolib/ |-- include | |-- mymath.h | `-- myprint.h `-- lib`-- libhello.so2 directories, 3 files
4.3 静态库的使用方法
静态库的使用三种方法:
测试代码main.c
#include"mymath.h" #include"myprint.h"int main(){Print("hello Linux!");int res = addToTarget(1, 100);printf("res : %d\n", res);return 0; }
4.3.1(不太建议)方法一,安装第三方静态库到系统路径下
关于gcc头文件的默认搜索路径是/usr/include,库文件的默认搜索路径是/lib64 or /usr/lib64
将需要使用的静态库拷贝到系统默认路径下,就叫做库的安装
[customer@VM-4-10-centos usrlib]$ clear [customer@VM-4-10-centos usrlib]$ sudo cp ./mylib/include/*.h /usr/include/ [sudo] password for customer: [customer@VM-4-10-centos usrlib]$ sudo cp ./mylib/lib/*.a /usr/lib64/ [customer@VM-4-10-centos usrlib]$ ls /usr/lib64/libhello.a /usr/lib64/libhello.a
此时重新编译main.c,发现扔报错
[customer@VM-4-10-centos usrlib]$ gcc main.c -std=c11 /tmp/ccp2na2X.o: In function `main': main.c:(.text+0xe): undefined reference to `Print' main.c:(.text+0x1d): undefined reference to `addToTarget' collect2: error: ld returned 1 exit status [customer@VM-4-10-centos usrlib]$
因为对于自己写的库,属于第三方库,不是语言提供的,也不是系统自带的!使用第三方库,必须要告诉其链接的库属于哪一个库!
库取名字,去掉前缀lib,去掉后缀.a,剩下的为名字
[customer@VM-4-10-centos usrlib]$ gcc main.c -l hello [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693036772] res : 5050
4.3.2 方法二,硬链接静态库
可知编译器搜索头文件首先在系统路径下/usr/include/进行搜索,其次在当前路径下搜索,而静态库文件的头文件在其自己icnlude目录下,因此必须告诉编译器头文件include的位置!同时库也没有在系统路径下安装,且属于第三方库,因此也需要告诉编译器,库文件library的位置!而库文件lib路径下可能具有大量的库,因此最后还指明我们要引入的库!
[customer@VM-4-10-centos usrlib]$ ls main.c mylib [customer@VM-4-10-centos usrlib]$ gcc main.c -I ./mylib/include/ -L ./mylib/lib/ -l hello [customer@VM-4-10-centos usrlib]$ ls a.out main.c mylib [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693037426] res : 5050
4.4 动态库的使用方法
测试代码仍为静态库中的main.c文件
当使用硬链接动态库的方式进行链接时,若库目录下lib文件夹具有动静态库,且库名字相同都为hello,那么默认链接的是静态库,还是动态库呢?
[customer@VM-4-10-centos usrlib]$ gcc main.c -I ./mysolib/include/ -L ./mysolib/lib/ -l hello [customer@VM-4-10-centos usrlib]$ tree mysolib/ mysolib/ |-- include | |-- mymath.h | `-- myprint.h `-- lib|-- libhello.a`-- libhello.so2 directories, 4 files [customer@VM-4-10-centos usrlib]$
GCC默认使用动态库
[customer@VM-4-10-centos usrlib]$ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e26ad8b59222f53ba2bdf91d719bc49aa1ddd4e8, not stripped [customer@VM-4-10-centos usrlib]$ ./a.out ./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory [customer@VM-4-10-centos usrlib]$ c
但是通过硬链接使用动态库,发现运行错误,动态链接失败!
把动态库移除,再次使用硬链接方式进行链接,ldd
[customer@VM-4-10-centos usrlib]$ mv mysolib/lib/libhello.so ./ [customer@VM-4-10-centos usrlib]$ gcc main.c -I ./mysolib/include/ -L ./mysolib/lib/ -l hello [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693040732] res : 5050 [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffd969f0000)libc.so.6 => /lib64/libc.so.6 (0x00007f25e53df000)/lib64/ld-linux-x86-64.so.2 (0x00007f25e57ad000) [customer@VM-4-10-centos usrlib]$ [customer@VM-4-10-centos usrlib]$ gcc main.c -I ./mysolib/include/ -L ./mysolib/lib/ -l hello -static l[customer@VM-4-10-centos usrlib]$ ldd a.out not a dynamic executable [customer@VM-4-10-centos usrlib]$
如果只有静态库,默认使用静态库的静态链接方式将数据拷贝到可执行程序内,如果还有动态库,默认链接动态库!带上-static,优先使用静态库,静态链接!
4.4.1 动态库的加载过程
由上图分析,可知此时动态库并没有加载到内存,因此使用此动态库运行的进程在访问页表映射时,找不到加载的动态库,运行错误!
虽然告诉了gcc库所链接位置,但是运行加载的时候,就和gcc没有关系了!因此需要告诉操作系统加载器,加载动态库!(为什么C语言C++动态库没有告诉系统呢?因为语言提供的动态库默认在系统路径下安装,系统通过配置文件自动加载,静态库不存在加载问题)
[customer@VM-4-10-centos usrlib]$ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e26ad8b59222f53ba2bdf91d719bc49aa1ddd4e8, not stripped [customer@VM-4-10-centos usrlib]$ ./a.out ./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory [customer@VM-4-10-centos usrlib]$ [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffc42d86000)libhello.so => not foundlibc.so.6 => /lib64/libc.so.6 (0x00007f6082517000)/lib64/ld-linux-x86-64.so.2 (0x00007f60828e5000) [customer@VM-4-10-centos usrlib]$
4.4.2 (不太建议)方法一,安装第三方动态库到系统路径下
[customer@VM-4-10-centos usrlib]$ gcc main.c -I ./mysolib/include/ -L ./mysolib/lib/ -l hello [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffc42d86000)libhello.so => not foundlibc.so.6 => /lib64/libc.so.6 (0x00007f6082517000)/lib64/ld-linux-x86-64.so.2 (0x00007f60828e5000) [customer@VM-4-10-centos usrlib]$ ls a.out main.c mylib mysolib [customer@VM-4-10-centos usrlib]$ sudo cp mysolib/lib/libhello.so /usr/lib64/ [sudo] password for customer: [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693042665] res : 5050 [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffdcb586000)libhello.so => /lib64/libhello.so (0x00007f49a8942000)libc.so.6 => /lib64/libc.so.6 (0x00007f49a8574000)/lib64/ld-linux-x86-64.so.2 (0x00007f49a8b44000) [customer@VM-4-10-centos usrlib]$
系统路径下,库文件会通过配置文件,有操作系统进行自动加载,将动态库加载到内存!
4.4.3 方法二,设置环境变量$LD_LIBRARY_PATH
设置环境变量$LD_LIBRARY_PATH ,加载动态库,其中:表示拼接,不会覆盖以前的环境变量
[customer@VM-4-10-centos usrlib]$ ls /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/ libhello.a libhello.so [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007fffe1beb000)libhello.so => not foundlibc.so.6 => /lib64/libc.so.6 (0x00007fa6eca0c000)/lib64/ld-linux-x86-64.so.2 (0x00007fa6ecdda000) [customer@VM-4-10-centos usrlib]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/ [customer@VM-4-10-centos usrlib]$ echo $LD_LIBRARY_PATH :/home/customer/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/ [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693043273] res : 5050 [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffe31bce000)libhello.so => /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/libhello.so (0x00007fda446ec000)libc.so.6 => /lib64/libc.so.6 (0x00007fda4431e000)/lib64/ld-linux-x86-64.so.2 (0x00007fda448ee000) [customer@VM-4-10-centos usrlib]$
此时设置的环境变量,是内存级的环境变量,因此一旦退出xshell,此环境变量便会失效!
4.4.4 方法三,修改配置文件/etc/ld.so.conf.d/
/etc/ld.so.conf.d/ 此路径保存的是可以允许自定义配置搜索库路径的永久解决方案
因此可以再此文件夹内添加自己的配置文件,将库的所在目录的绝对路径写入,调用ldconfig,重新加载配置文件,此时便永久性的加载了动态库,即使xshell重启,也不会销毁!
[customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffd06b37000)libhello.so => not foundlibc.so.6 => /lib64/libc.so.6 (0x00007f709dccb000)/lib64/ld-linux-x86-64.so.2 (0x00007f709e099000) [customer@VM-4-10-centos usrlib]$ ls /etc/ld.so.conf.d/ bind-export-x86_64.conf dyninst-x86_64.conf kernel-3.10.0-1160.71.1.el7.x86_64.conf mariadb-x86_64.conf [customer@VM-4-10-centos usrlib]$ sudo touch /etc/ld.so.conf.d/testso.conf [sudo] password for customer: [customer@VM-4-10-centos usrlib]$ ls /etc/ld.so.conf.d/ bind-export-x86_64.conf dyninst-x86_64.conf kernel-3.10.0-1160.71.1.el7.x86_64.conf mariadb-x86_64.conf testso.conf [customer@VM-4-10-centos usrlib]$ sudo vim /etc/ld.so.conf.d/testso.conf [customer@VM-4-10-centos usrlib]$ sudo cat /etc/ld.so.conf.d/testso.conf /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/ [customer@VM-4-10-centos usrlib]$ sudo ldconfig [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693043789] res : 5050 [customer@VM-4-10-centos usrlib]$ ldd a.out linux-vdso.so.1 => (0x00007ffd3c7e1000)libhello.so => /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/libhello.so (0x00007fdbd86bf000)libc.so.6 => /lib64/libc.so.6 (0x00007fdbd82f1000)/lib64/ld-linux-x86-64.so.2 (0x00007fdbd88c1000) [customer@VM-4-10-centos usrlib]$ echo $LD_LIBRARY_PATH :/home/customer/.VimForCpp/vim/bundle/YCM.so/el7.x86_64 [customer@VM-4-10-centos usrlib]$
此时即使环境变量LD_LIARARY_PATH没有此路径,也可以运行使用该库的程序!
4.4.5 (推荐)方法三,在系统路径下建立软连接
通过软链接方式,是系统默认加载库文件时,找到路径将第三方动态库load至内存!
[customer@VM-4-10-centos usrlib]$ sudo ln -s /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/libhello.so /usr/lib64/libhello.so [customer@VM-4-10-centos usrlib]$ ls /usr/lib64/libhello.so -l lrwxrwxrwx 1 root root 80 Aug 26 18:08 /usr/lib64/libhello.so -> /home/customer/LinuxLearn/second/28lesson/4review/usrlib/mysolib/lib/libhello.so [customer@VM-4-10-centos usrlib]$ ./a.out hello Linux![1693044544] res : 5050 [customer@VM-4-10-centos usrlib]$
unlink删除链接文件,或者删除文件u
4.4.6 其他方法,设置登录脚本
本质还是设置环境变量,在用户根目录下,隐藏文件.bashrc导环境变量 和 .bash_profile,其中.bash_profile调用.bashrc
4.5 为什么要有库?推荐库
站在使用库的角度,库的存在,可以大大减少我们开发的周期,提高软件本身的质量!
站在写库的人的角度:1. 使用简单 2. 代码安全
推荐的库
1. ncurses -- 字符的界面库 -- centos 7 yum 安装necurses
2. Boost -- C++准标准库