【Linux】缓冲区+磁盘+动静态库

一、缓冲区

1、缓冲区的概念

缓冲区的本质就是一段用作缓存的内存

2、缓冲区的意义

节省进程进行数据IO的时间。进程使用fwrite等函数把数据拷贝到缓冲区或者外设中。

3、缓冲区刷新策略

3.1、立即刷新(无缓冲)——ffush()

情况很少,比如调用printf后,手动调用fflush刷新缓冲区。

3.2、行刷新(行缓冲)——显示器

显示器需要满足人的阅读习惯,故采用行刷新的策略而不是全缓冲的策略。

虽然全缓冲的刷新方式,可以大大降低数据IO的次数,节省时间。但若数据暂存于缓冲区,等缓冲区满后再刷出,当人阅读时面对屏幕中出现的一大堆数据,很难不懵逼。所以显示器采用行刷新的策略,既保证了人的阅读习惯,又使得数据IO效率不至于太低

3.3缓冲区满后刷新(全缓冲)——磁盘文件

3.4、特殊的刷新情况

用户强制刷新或进程退出。

4、同一份代码输出到屏幕或者文件上不同

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main()
{
    
    
    printf("hello printf\n");//先打印至stdout缓冲区中
    fprintf(stdout,"hello fprintf\n");
    fputs("hello fputs\n",stdout);
 
    const char* msg="hello write\n";
    write(1,msg,strlen(msg));
    fork();//生成子进程
    return 0;
}

在这里插入图片描述

运行上方代码生成的可执行文件,在显示器上是正常打印,但是将运行结果重定向至文本,会发现C接口的打印函数打印了两次。

这个现象和缓冲区有关,从侧面说明了缓冲区并不存在内核中,否则write也会打印两次。用户级语言层面提供的缓冲区在FILE指向的stdin/stdout/stderr中,FILE结构体会包含fd和缓冲区。需要强制刷新时,调用fflush(FILE);关闭文件时,调用fclose(FILE*)。参数为FILE就是为了刷新FILE指向的FILE结构体中的缓冲区。

上方代码现象的解释

1、stdout默认采用行刷新策略,每条打印函数都带了’\n’,所以在fork之前,数据已经全部被打印到了显示器,缓冲区被并没有数据,当代码运行到fork创建子进程时,子进程对应的缓冲区当然也是没有任何数据。

2、当写入的是磁盘文件时,采用的是全缓冲的刷新策略,程序运行到fork时,缓冲区并没有被写满,数据仍存在于缓冲区中,当然被创建的子进程也拷贝了一份缓冲区的数据,当父子进程退出时,父子进程缓冲区中的数据将被刷新。所以出现了C接口函数被打印了两份的现象。

3、上面的过程与系统调用write无关,write没有FILE,使用的是fd,当然就没有C提供的缓冲区。

5、仿写File

5.1、MyStdio.h

#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__

#include <string.h>
#define SIZE 1024
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4
typedef struct IO_FILE
{
    
    
    int fileno;
    char outbuffer[SIZE];//输出缓冲区
    int flag;//标记即时刷新,行刷新,还是全缓冲
    int out_pos;

}_FILE;

_FILE* _fopen(const char* filename, const char * flag);
int _fwrite(_FILE* fp,const char* s,int len);
void _fclose(_FILE* fp);

#endif 

5.2、MyStdio.c

#include "MyStdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#define FILE_MODE 0666
_FILE* _fopen(const char* filename, const char * flag)
{
    
    
    assert(filename);
    assert(flag);
    int f = 0;
    int fd = -1;
    if(strcmp(flag,"w") == 0) //只写方式打开
    {
    
    
        f = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(filename,f,FILE_MODE);
    }
    else if(strcmp(flag,"a") == 0)//追加方式打开
    {
    
    
        f = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(filename,f,FILE_MODE);
    }
    else if(strcmp(flag,"r") == 0)//只读方式打开
    {
    
    
        f = O_RDONLY;
        fd = open(filename,f);
    }
    else 
    {
    
    
        return NULL;
    }
    //文件打开失败
    if(fd == -1) return NULL;

    _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
    //申请空间失败
    if(fp == NULL) return NULL;
    fp->fileno = fd;
    //fp->flag = FLUSH_LINE;
    fp->flag = FLUSH_ALL;
    fp->out_pos = 0;
    return fp;
 
}
int _fwrite(_FILE* fp,const char* s,int len)
{
    
    
    memcpy(&fp->outbuffer[fp->out_pos],s,len);
    fp->out_pos += len;

    if(fp->flag & FLUSH_NOW)
    {
    
    
        write(fp->fileno,s,len);
        fp->out_pos = 0;
    }
    else if(fp->flag & FLUSH_LINE)
    {
    
    
        if(fp->outbuffer[fp->out_pos-1] == '\n')
        {
    
    
            write(fp->fileno,s,len);//目前先考虑结尾是斜杠n的情况,中间有斜杠n的不考虑
            fp->out_pos = 0;
        }
    }
    else if(fp->flag & FLUSH_ALL)
    {
    
    
        if(fp->out_pos == SIZE)
        {
    
    
            write(fp->fileno,s,len);
            fp->out_pos = 0;
        }
    }
    
    return len;
}

void _fflush(_FILE* fp)
{
    
    
    if(fp->out_pos > 0)
    {
    
    
        write(fp->fileno,fp->outbuffer,fp->out_pos);
        fp->out_pos = 0;
    }
}
void _fclose(_FILE* fp)
{
    
    
    if(fp == NULL) return;
    _fflush(fp);
    close(fp->fileno);
    free(fp);
}

5.3、main.c

#include "MyStdio.h"
#define myfile "test.txt"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    
    
    _FILE* fp = _fopen(myfile,"a");
    if(fp == NULL) return 1;
    const char *msg = "hello world\n";
    int cnt = 5;
    while(cnt)
    {
    
    
        _fwrite(fp,msg,strlen(msg));
        sleep(1);
        cnt--;
    }
    _fclose(fp);
    return 0;
}

main.c中msg指向的字符串有无\n,这个程序对应的刷新策略不同。

二、文件系统

前面我们学习到的东西,全部都是在内存当中,但并不是所有的文件都被打开,大量的文件就在磁盘上静静的躺着,这批文件非常多,杂,乱,我们必须要对这些磁盘文件进行管理,我们把做这部分管理工作的操作系统模块称之为文件系统。现在我们把视角从内存迁移到磁盘上来看。

1、什么是磁盘

磁盘是计算机主要的存储介质,可以存储大量的二进制数据,并且断电后也能保持数据不丢失。早期计算机使用的磁盘是软磁盘(Floppy Disk,简称软盘),如今常用的磁盘是硬磁盘(Hard disk,简称硬盘)。

2、磁盘的物理结构

在这里插入图片描述

硬盘结构包括: 盘片、磁头、盘片主轴、控制电机、磁头控制器、数据转换器、接口、缓存等几个部份。. 所有的盘片 (一般硬盘里有多个盘片,盘片之间平行)都固定在一个主轴上。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。在每个盘片的存储面上都有一个磁头,磁头与盘片之间的距离很小 (所以剧烈震动容易损坏),磁头连在一个磁头控制器上,统一控制各个磁头的运动。. 磁头沿盘片的半径方向动作,而盘片则按照指定方向高速旋转,这样磁头就可以到达盘片上的任意位置了。

3、磁盘的存储结构

在这里插入图片描述

  • 扇区(sector):盘片被分成许多扇形的区域
  • 磁道(track):盘片上以盘片中心为圆心,不同半径的同心圆
  • 柱面(cylinder):硬盘中,不同盘片相同半径的磁道所组成的援助
  • 磁头(head):每个磁盘都有两个面,每个面都有一个磁头

磁盘上存储的基本单位是扇区,一般是512字节,数据是在扇区上存储的。在读写磁盘的时候,磁头找的是某一个面(哪一个磁头)的某一个磁道(哪一个柱面——距离圆心的半径**)的某一个扇区**(磁道上的一段)。只要我们能够找到磁盘上的盘面,柱面(磁道),扇区,即CHS地址,我们就能找到磁盘上的任意一个存储单元。

4、磁盘的逻辑抽象结构

理解文件系统,首先我们必须将磁盘想象成一个线性的存储介质,想想磁带:

在这里插入图片描述

磁带被卷起来时,就像磁盘一样是圆形的,里面存储的是数据,当我们把磁带拉直后,其就是线性的。我们把盘片想象成为线性的结构,就可以把盘片当成是数组,定位有关sector(扇区),只要找到下标LBA(逻辑块地址)就可以了。因此对磁盘的管理,就转化成为了对数组空间的管理,如图:

在这里插入图片描述

因此内存中的数据想要往磁盘里写入,在内存中只需要知道有关地址叫LBA(逻辑块地址),然后将LBA地址映射转换为CHS地址,再将内存中的数据配合CHS写到磁盘里,即可完成磁盘的写入。

问:如何将LBA地址转化为CHS地址?

现在假设磁盘有2片(4个面),一个面能存1000个数据,每个面有20个磁道,已知LBA地址是3234,那么写入的过程如下:

  • 3234 / 1000 = 3 —— 在第3面(H是3)
  • 3234 % 1000 = 234
  • 234 / 20 = 11 —— 在第11个磁道(C是11)
  • 234 % 20 = 14 —— 在第14个扇区(S是14)

综上

  • C:11
  • H:3
  • S:14

上述磁盘的每一个扇区的大小是512字节,但是有一个问题,OS表示每一次访问512字节很小,效率差,因此OS对进进行再一次抽象,以8个扇区为单位,整合为一个OS所认为的存储单元,所以大小就变成了4KB。OS在读写数据的时候,就会去这个存储单元中找。(IO的基本单位是4KB)

在这里插入图片描述

上述这样操作有两个好处

  1. 提高IO效率
  2. 不要让软件(OS)设计和硬件(磁盘)具有强相关性,换句话说,就是解耦合!

5、inode

5.1、inode说明

  • 在命令行中输入ls -l,即可显示当前目录下各文件的属性信息:

在这里插入图片描述

  • 其中,各列信息对应的文件属性如下:

在这里插入图片描述

在Linux操作系统中,文件的元信息和内容是分离存储的,其中保存元信息的结构称之为inode,因为系统当中可能存在大量的文件,所以我们需要给每个文件的属性集起一个唯一的编号,即inode号。也就是说,inode是一个文件的属性集合,Linux中几乎每个文件都有一个inode,为了区分系统当中大量的inode,我们为每个inode设置了inode编号。

在命令行当中输入ls -i,即可显示当前目录下各文件的inode编号。

在这里插入图片描述

  • 注意: 无论是文件内容还是文件属性,它们都是存储在磁盘当中的。

5.2、Linux ext2文件系统

  • 计算机为了更好的管理磁盘,会对磁盘进行分区。而对于每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于分区的其它区域,文件系统会根据分区的大小将其划分为一个个的块组(Block Group)。

在这里插入图片描述

注意

  • 启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。
  • 文件 = 内容 + 属性,二者都是数据,都要存储。Linux采用的是将内容和属性数据分开存储的方案,内容在block中(4KB),内容是可以无限增多的。属性数据在inode中(128字节),文件的属性是稳定的。

每个组块都有着相同的组成结构,每个组块都由超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成。

在这里插入图片描述

  • ①、Boot Block:与开机有关,里面包括了各种开机信息,有分区表,以及软件的位置信息。

  • ②、Block Group:整个时文件系统所划分的不同的组,每个组的结构构成都相同。

  • ③、Date blocks:以块为单位,进行文件的保存(所占的空间最大,80%左右)。

  • ④、inode Table:以128字节为单位,进行inode属性的保存。inode属性里面有一个inode编号,一般而言,一个文件,一个inode,一个inode编号。

  • ⑤、Block Bitmap:这里按位记录着Date Block(数据块)哪个被占用,哪个没被占用,每个bit位为0表示没被占用,为1表示被占用。

  • ⑥、inode Bitmap:这里按位记录着inode的使用情况。每个bit位为0表示没被占用,为1表示被占用。

  • ⑦、Group Descriptor Table(GDT):对块组进行描述,包含了有多少inode,起始的inode编号,有多少个inode被使用,有多少block被使用,还剩多少等待信息。

  • ⑧、Super Block:就是我们文件系统的顶层数据结构,记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。

问4:当我们创建一个文件时,OS操作系统做了什么?

  • 先查找inode Bitmap,找到一个比特位没有被使用,把此比特位由0置为1,在inode Table中找到对应的inode节点,向里面写入对应的属性,并且为它分配数据块,把数据写到数据块中,同时修改block Bitmap,并且建立inode和block的映射关系,最终返回该文件的inode。

创建一个文件的时候,一定是在一个目录下。

  • 通过文件名(对应的inode编号)-> 找到自己所处的目录 -> 根据目录的inode,找到目录的data block -> 将文件名和inode编号的映射关系写入到目录的数据块中。

问5:删除一个文件,OS操作系统做了什么呢?

  • 找到自己的目录的inode,再找到自己目录的blocks,然后根据文件名的唯一性,以及它与inode的映射关系,找到对应的inode编号,然后再根据inode编号找到它对应的Block group,然后将该文件所对应的inode Bitmap和block Bitmap由1置0,就完成了文件的删除。最后在文件所处的,目录中,把文件名和inode所对应的映射关系去掉,此时这个文件就被删掉了。

  • 所以平时拷贝文件所花费的时间很长,而删除东西几秒钟就完事了,原因在于删除只是把标记该文件对应的属性和数据块相关的位图由1置0即可。

  • 因此,Linux并没有真正的清除数据,只是将inode Bitmap和block Bitmap由1置0,就相当于删除了。此外,想要恢复整个删除的数据也很容易,只要知道了这个inode,通过一些工具,把这个inode对应的inode Bitmap和block Bitmap由0恢复成1即可。

问6:为什么拷贝文件的时间耗费很长,而删除文件却很快?

  • 因为拷贝文件需要先创建文件,然后再对该文件进行写入操作,该过程需要先申请inode号并填入文件的属性信息,之后还需要再申请数据块号,最后才能进行文件内容的数据拷贝,而删除文件只需将对应文件的inode号和数据块号置为无效即可,无需真正的删除文件,因此拷贝文件是很慢的,而删除文件是很快的。

6、软硬链接

6.1、软链接

我们可以通过以下命令创建一个文件的软链接。

ln -s my.txt my.txt.soft

在这里插入图片描述

通过 ls -i -l 命令可以看到,软连接的inode号和源文件的inode号是不同的,并且软连接文件的大小比源文件的大小要小的多。

在这里插入图片描述

软连接又叫符号链接,软连接文件相当于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软连接文件的大小要比源文件小得多。软连接就类似于Windows操作系统当中的快捷方式。

  • 如下我使用mkdir -p指令在当前目录下创建嵌套的目录,并cd到d3目录:

在这里插入图片描述

  • 我在d3目录下创建一个可执行程序,并退回到date22目录下

在这里插入图片描述

  • 现在想在date22目录下执行d3目录下的mytest.c.exe可执行程序,按照以往的方法我们是这样的:

在这里插入图片描述

  • 现在我们就可以给mytest.c.exe建立软链接,即可通过软连接(快捷方式)的形式执行此可执行程序:

在这里插入图片描述

  • 综上,软连接就是一个快捷方式,上述my.exe是软连接,相当于是mytest.c.exe的快捷方式。

但是软连接文件只是其源文件的一个标记,当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能执行或是查看软连接的内容了。

在这里插入图片描述

问:既然软连接是一个独立文件,inode是独立的,那么软连接的文件内容是什么呢?

  • 软连接保存的内容就是指向的文件的所在路径!!!

6.2、硬链接

我们通过以下命令创建一个文件的硬链接。

在这里插入图片描述

硬链接就是单纯的在Linux指定的目录下,给指定的文件新增文件名和inode编号的映射关系。

在这里插入图片描述

  • 通过ls -i -l命令我们可以看到,硬链接文件的inode号与源文件的inode号是相同的,并且硬链接文件的大小与源文件的大小也是相同的,特别注意的是, 新创建的my.txt文件的硬链接数为1,可给my.txt文件建立硬链接后,其硬链接数变成了2。

与软连接不同的是,当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个。看如下的示例:

在这里插入图片描述

  • 我对可执行程序myfile建立硬链接,现在删除硬链接的源文件:

在这里插入图片描述

  • 总之,硬链接就是让多个不在或者同在一个目录下的文件名,同时能够修改同一个文件,其中一个修改后,所有与其有硬链接的文件都一起修改了。
    在这里插入图片描述

问1:什么是硬连接数?

  • 硬连接数本质就是该文件inode属性中的计数器count,标识有几个文件名和我的inode建立了映射关系。简言之,就是有几个文件名指向我的inode(文件本身)

问2:硬链接有什么用呢?

看如下我重新创建了一个目录和文件:

在这里插入图片描述

为什么文件被创建出来,默认的硬连接数是1?

  • 如果硬链接数是0,那么就应该是被关闭的文件了,所以至少应该从1开始。此外,普通文件的文件名,本身就和自己的inode具有映射关系,且只有1个,所以文件的默认硬连接数是1。

为什么目录被创建出来,默认的硬连接数是2呢?

在这里插入图片描述

我们cd进入创建的目录,会发现目录中自动创建两个文件 . 和 …,仔细看这个inode编号,会发现 . 和mydir的inode编号是一样的,综上,自己本身的目录名mydir和自己本身的inode有一个映射关系,且任何一个目录里头都有一个 . ,它通过自己所处的目录和inode建立一个硬链接,所以目录的默认硬链接数是2。

在这里插入图片描述

  • 仔细看上图,会发现mydir目录下的 … 文件的inode和上级目录date22的inode是一样的,而mydir目录下的 . 文件和当前目录mydir的inode是一样的,综上,. 和 … 分别对应当前路径和上级路径。

所以我们也可以根据系统的硬连接数,不进入文件,从而估算出文件的目录数(一个目录下相邻的子目录数 = 该目录的硬连接数 - 2)。因此,硬链接的一个作用就是进行路径切换。

6.3、软硬链接的区别

区别如下

  1. 软连接是一个独立文件,有自己独立的inode和inode编号。硬链接不是一个独立文件,它和它的目标文件使用的是同一个inode。
  2. 软连接相当于快捷方式,硬链接本质没有创建文件,只是建立了一个文件名和已有的inode的映射关系,并写入当前目录。

6.4、软硬链接的删除

建议用unlink来删除软硬连接的文件(unlink也可以删除普通文件,与rm没什么区别)

6.5、文件的三个时间

在Linux中,我们可以使用命令 stat 文件名来查看对应文件的信息:

在这里插入图片描述

这其中包含了文件的三个时间信息:

  • Access: 文件最后被访问的时间。
  • Modify: 文件内容最后的修改时间。
  • Change: 文件属性最后的修改时间。

当我们修改文件内容时,文件的大小一般会随之改变,所以Modify的改变会带动Change一起改变,但丢该文件属性一般不会影响文件内容,所以一般情况下Change的改变不会带动Modify的改变。此外,我们可以使用touch命令把这三个时间都更新到最新状态。(当一文件存在时使用touch命令,此时touch命令的作用变为更新文件信息)。

三、动静态库

使用ldd可以显示可执行程序依赖的库。

在这里插入图片描述

查看程序是动静态的方法:

file 可执行程序

在这里插入图片描述

1、静态库的制作

一套完成的库包含1、库文件本身(二进制文件,人看不懂)2、头文件(文本类型,暴露库文件中的接口)3、说明文档。

/lib64        库文件的存放目录(有些是/usr/lib)
/usr/include  头文件的存放目录

1.1、静态库的生成

1、将所有库文件编译为.o(可重定向二进制目标文件),用户拿到每个模块的.o文件,自行链接即可。将所有的.o打包就是库

使用ar -rc对多个.o进行打包。ar是gun归档工具。

gcc -c mymath.c
ar -rc libmymath.a  mymath.o

对应的makefile

libmymath.a:mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^ 

.PHONE:clean
clean:
	rm -rf *.o *.a lib 

.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/mymathlib
	cp *.h lib/include 
	cp *.a lib/mymathlib

先把.c生成.o,再把.o打包成.a静态库。

使用ar -tv查看静态库中的内容:

在这里插入图片描述

1.2、用户如何使用静态库

在这里插入图片描述

将打包好的静态库文件给到用户,用户自己写一个main函数,即可使用output中的.h文件。

不过在编译时,需要执行如下命令:

gcc -o main main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath
-I:告诉编译器在./lib/include路径中找头文件
-L:告诉编译器在./lib/mymathlib路径找库
-l:跟库名称(去掉前缀lib,去掉后缀.so或.a)

makefile:

main:main.c 
	gcc -o main main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath
.PHONY:clean
clean:
	rm -f main

使用编译器提供的库并行不需要带这些选项,是因为编译器有自己的环境变量,能够找到位于/lib64库文件的存放目录和/usr/include头文件的存放目录。

可以将静态库和头文件放入这些目录或其他相关目录下,这就是一般软件的安装过程。但是不推荐(自己写的库什么水平没点数吗?放进去会污染标准库)。

2、动态库的制作

2.1、动态库的生成

同样的,将所有.o进行打包。

makefile:

dy-lib=libmymethod.so 
static-lib=libmymath.a

.PHONY:all
all: $(dy-lib) $(static-lib)

$(static-lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^


$(dy-lib):mylog.o Printf.o
	gcc -shared -o $@ $^
mylog.o:mylog.c
	gcc -fPIC -c $^
Print.o:Printf.c
	gcc -fPIC -c $^
 

.PHONE:clean
clean:
	rm -rf *.o *.a  *.so lib  

.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib 
	cp *.h mylib/include 
	cp *.a mylib/lib 
	cp *.so mylib/lib

注意:这里小编把动态库与静态库的制作放在一起了,可以只看动态库的制作

这样就得到了一个动态库libmymethod.so

2.2、将动态库和头文件合并

makefile:

在这里插入图片描述

2.3、用户如何使用动态库

makefile:

main:main.c
	gcc -o main main.c -I ./mylib/include/ -L ./mylib/lib/ -lmymath -lmymethod
.PHONY:clean
clean:
	rm -rf main

动静态库的使用方式是一样的。

但是运行可执行程序会报错:没有这个文件或目录

在这里插入图片描述

使用ldd命令发现缺少了自己写的动态库:因为makefile只是告诉编译器头文件和库的路径,编译能通过,但是运行又不是编译器来运行,当然不知道详细库路径!

在这里插入图片描述

静态库能运行是因为静态链接是将所有内容全部拷贝到源文件。动态库编译/运行都需要这些路径,运行时需要通过加载器,告诉操作系统库路径在哪里。

2.4、解决加载找不到动态库的四种方法

方案一:将动态库和头文件拷贝至对应的系统库路径(拷贝至/lib64)和头文件(拷贝至/usr/include)路径下(自己写的库不推荐,成熟的库可以推荐拷贝进去,否则会污染人家的库)

方案二:在系统的默认的库路径/usr/lib64 路径下建立软链接

方案三:更改环境变量LD_LIBRARY_PATH

用于指定查找共享库(动态链接库)时除了默认路径(./lib和./usr/lib)之外的其他路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/sqy/108-linux/MyLib/test/mylib/lib

当然这个环境变量在下次重新登录就没了,如果想让这个环境变量永久生效,可以把这个环境变量添加到登录相关的启动脚本里,下面两个都行,但是不建议,如果真要改,多开几个终端,防止改了之后登不上Linux:

vim ~/.bash_profile
vim ~/.bashrc

方案四:ldconfig

使用root进入 /etc/ld.so.conf.d

然后创建文件,写入库的路径

然后执行ldconfig就可以了

2.5、动态库的优缺点

优点

  • 更加节省内存并减少页面交换;

  • 库文件与程序文件独立,只要输出接口不变,更换库文件不会对程序文件造成任何影响,

    因而极大地提高了可维护性和可扩展性;

  • 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个库函数;

  • 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

缺点

  • 运行时依赖,否则找不到库文件就会运行失败
  • 运行加载速度相较静态库慢一些
  • 需要对库版本之间的兼容性做出更多处理

3、动静态库的总结

制作动静态库:

1、将所有的源文件编译为.o可重定向目标文件;

2、制作动静态库的本质就是将所有.o和头文件“打包”,静态库使用ar -rc,动态库使用-shared和gcc -fPIC

3、使用:include+.a或.so文件

静态库只能静态链接,动态库只能动态链接。一般需要提供动静态两种版本的库,gcc和g++优先默认使用动态库进行链接,想要静态链接,需要手动在编译指令后添加-static选项。

Linux操作系统中一定会存在动态库,操作系统中有很多命令是由C语言写的,它们采用动态链接。

无论是采用动态链接还是静态链接,程序在预编译的时候,都会把所包含的头文件进行展开,这里展开的仅仅是库中的声明;当程序在链接的时候,静态链接会将库函数的定义拷贝一份到程序的代码段中,而动态链接会将动态库中所需的定义通过地址偏移量的方式加载到内存而不是可执行程序中,可执行程序运行时将这些定义通过页表映射至共享区,所以动静态库的体积存在巨大的差距。

猜你喜欢

转载自blog.csdn.net/VHhhbb/article/details/134501358
今日推荐