Linux——基础IO(2)及动静态库多种方式使用及制作

news2024/11/13 15:16:36

 

目录

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个操作:

  1. 存储属性         内核先在inode Bitmap中找到一个空闲的inode节点(这里是131074),将其inode Bitmap中的0置为1。拿到inode编号,内核把文件信息属性记录到inode Table中。
  2. 存储数据         该文件需要存储在三个磁盘块,内核在Block Bitmap找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据,复制到300,下一块复制到500,以此类推。
  3. 记录分配情况         文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 建立目录映射         添加文件名及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”
        动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。

  1.         动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。
  2.         gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件
  3.         gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。
  4.         gcc 和 g++ 默认生成的可执行程序默认是动态连接的
  5.         -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.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^ -o $@ -std=c11
myprint.o:myprint.c
	gcc -c $^ -o $@ -std=c11
.PHONY:lib
lib:
	mkdir mylib mylib/include mylib/lib
	cp mymath.h myprint.h mylib/include/
	rm mymath.o myprint.o
	mv libhello.a mylib/lib/
.PHONY:clean
clean:
	rm mylib -rf

4.3 动态库的制作

        

 

        静态库的加载,是将代码拷贝进我们的可执行程序内,而程序具有地址空间,因为静态库的使用,需要拷贝到指定地址空间的位置!静态库在编制的时候必须按照程序以绝对地址的方式编址!

        动态库采用相对编址方案,不需要考虑程序的地址空间!因此动态库是由与地址无关的目标二进制文件形成!段地址 + 偏移方式

makfile

libhello.so:mymath.o myprint.o
	gcc -shared $^ -o $@
mymath.o:mymath.c
	gcc -fPIC -c $^ -o $@ -std=c11
myprint.o:myprint.c
	gcc -fPIC -c $^ -o $@ -std=c11
.PHONY:lib
lib:
	mkdir mysolib mysolib/include mysolib/lib
	mv 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.so

2 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.so

2 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 found
	libc.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 found
	libc.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 found
	libc.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 found
	libc.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++准标准库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/932179.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全(黑客)自学——日薪2700

以下是练习舞蹈时长两年半的苕皮哥的故事 你想想一个跨专业的自学三个月都能拿到日薪2700&#xff0c;你上你也行&#xff0c;那么接下来就是我给大家&#xff0c;整理的网络安全学习思路&#xff0c;让大家斩获高薪&#xff01; 前言&#xff1a; 想自学网络安全&#xff08;黑…

DataFrame.plot函数详解(四)

DataFrame.plot函数详解&#xff08;四&#xff09; 1. area DataFrame.plot.area(xNone, yNone, stackedTrue, **kwargs) df pd.DataFrame({sales: [3, 2, 3, 9, 10, 6],signups: [5, 5, 6, 12, 14, 13],visits: [20, 42, 28, 62, 81, 50], }, indexpd.date_range(start2020…

【算法系列篇】前缀和

文章目录 前言什么是前缀和算法1.【模板】前缀和1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 【模板】二维前缀和2.1 题目要求2.2 做题思路2.3 Java代码实现 3. 寻找数组的中心下标3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 除自身以外的数组的乘积4.1 题目要求4.2 做题思…

Linux学习之Ubuntu 20中OpenResty的nginx目录里内容和配置文件

参考的文章是《nginx配置详解》 可以参考我以前的文章安装OpenResty。 cd /usr/local/openresty切换目录&#xff0c;ls -l查看目录里边的内容。 我的系统中&#xff0c;nginx目录是/usr/local/openresty/nginx&#xff0c;在这个目录里边有一些目录&#xff0c;如下&#xff…

二进制数间按位逻辑运算按位逻辑与、逻辑或运算bitwise_and()bitwise_or()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 二进制数间按位逻辑运算 按位逻辑与、逻辑或运算 bitwise_and() bitwise_or() [太阳]选择题 下列代码最后一次输出的结果是&#xff1f; import numpy as np a, b 3, 8 print("…

linux————haproxy

一、概述 HAProxy是一个免费的负载均衡软件&#xff0c;可以运行于大部分主流的Linux操作系统上&#xff08;CentOS、Ubuntu、Debian、OpenSUSE、Fedora、麒麟、欧拉、UOS&#xff09;。 HAProxy提供了L4(TCP)和L7(HTTP)两种负载均衡能力&#xff0c;具备丰富的功能。HAProxy具…

小研究 - JVM 逃逸技术与 JRE 漏洞挖掘研究(三)

Java语言是最为流行的面向对象编程语言之一&#xff0c; Java运行时环境&#xff08;JRE&#xff09;拥有着非常大的用户群&#xff0c;其安全问题十分重要。近年来&#xff0c;由JRE漏洞引发的JVM逃逸攻击事件不断增多&#xff0c;对个人计算机安全造成了极大的威胁。研究JRE安…

HLS实现CORDIC算法计算正余弦并上板验证

硬件&#xff1a;ZYNQ7010 软件&#xff1a;MATLAB 2019b、Vivado 2017.4、HLS 2017.4、System Generator 2017.4 1、CORDIC算法计算正余弦 CORDIC算法详细分析网上有很多资料&#xff0c;它的主要思想是用一系列旋转去逼近目标角度&#xff0c;这一系列旋转的角度为 θ a r…

思乐直播系统短视频直播系统源码 直播短视频平台系统APP源码多功能后台系统

思乐直播系统&#xff0c;集直播、短视频等功能&#xff0c;根据市场趋势开发并推出思乐直播APP&#xff0c;APP功能丰富且可在后台管理系统进行配置&#xff0c;做到按需求来开启功能。APP使用起来方便快捷&#xff0c;随时随地开启直播、分享短视频。 整个系统具备非常完善、…

【C++杂货铺】探索vector的底层实现

文章目录 一、STL1.1 什么是STL?1.2 STL的版本1.3 STL的六大组件 二、vector的介绍及使用2.1 vector的介绍2.2 vector的使用2.2.1 vector的定义2.2.2 vector iterator2.2.3 vector空间增长问题2.2.4 vector增删查改 2.3 vector\<char\> 可以替代 string 嘛&#xff1f; …

垃圾识别工具箱、ChatGPT聊天微信小程序、大型商城电商源码

一、识别垃圾分类应用 垃圾识别工具箱微信小程序源码 前端&#xff1a;微信小程序 采用 uni-app 开发框架&#xff0c;uni-app 是一个使用 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、H5、以及各种小程序&#xff08;微信…

RetNet或成Transformer继承者?通向更快、更强、更经济的基础架构

导读 在计算机科学的发展史上&#xff0c;硬件算力、算法程序与计算数据总是螺旋上升。在硬件相同的条件下&#xff0c;算法的效率和优化程度决定了其利用硬件资源的能力&#xff0c;从而直接影响计算机的算力。因此&#xff0c;为了提升计算机系统带来的综合效益&#xff0c;计…

C++:基础

目录 1.C关键字 2.命名空间 1.命名空间定义 2.命名空间的使用 3.C输入与输出 4.缺省参数 1.缺省参数的概念 2.缺省参数的分类 5.函数重载 1.函数重载概念 2.为什么C支持函数重载&#xff0c;C语言不支持&#xff1f; 6.引用 1.引用的概念 2.引用的特性 3.常引用…

重要通知:9月1日起,微信小程序须完成备案后才可上架

微信官方通知 近日&#xff0c;工信部发布了《工业和信息化部关于开展移动互联网应用程序备案工作的通知》&#xff0c;8月9日&#xff0c;微信公众平台也发布了“关于开展微信小程序备案的通知”&#xff1a; 一、备案必要性 在中华人民共和国境内从事互联网信息服务的移动互…

隐式表达的更进一步:基于NeRF的形状可编辑方法

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 [paper]&#xff1a;https://arxiv.org/pdf/2303.09554 [code]&#xff1a;https://ktertikas.github.io/part_nerf 主要贡献&#xff1a; 设计了 PartNeRF&#xff0c;这是一种新颖的部件感知生成模型&…

idea上利用JDBC连接MySQL数据库(8.1.0版)

1.了解jdbc概念 JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API&#xff0c;可以为多种 关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准&#xff0c;据此可以构建 更高级的工具和接口&#…

MAVEN利器:一文带你了解IDEA中如何使用Maven

前言&#xff1a; 强大的构建工具——Maven。作为Java生态系统中的重要组成部分&#xff0c;Maven为开发人员提供了一种简单而高效的方式来构建、管理和发布Java项目。无论是小型项目还是大型企业级应用&#xff0c;Maven都能帮助开发人员轻松处理依赖管理、编译、测试和部署等…

《Zookeeper》源码分析(二十二)之 客户端核心类

目录 CliCommand数据结构parse()exec() ZooKeeperHostProviderZKClientConfigClientCnxnSocket数据结构构造函数 ClientCnxn数据结构构造函数start() CliCommand 数据结构 CliCommand定义了两个抽象方法&#xff0c;以CreateCommand为例来看下它的parse()和exec()方法。 先看…

c语言每日一练(11)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

在驱动中创建sysfs接口、procfs接口、debugfs接口

前言 在一些linux开发板中&#xff0c;经常可以看到通过echo的方式来直接控制硬件或者修改驱动&#xff0c;例如&#xff1a; //灯灭 echo 0 >/sys/class/leds/firefly:blue:power/brightness //灯亮 echo 1 >/sys/class/leds/firefly:blue:power/brightness 这是怎么…