Linux:软硬链接和动静态库

hello,各位小伙伴,本篇文章跟大家一起学习《Linux:软硬链接和动静态库》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
如果本篇文章对你有帮助,还请各位点点赞!!!

话不多说,开始正题:

文章目录

创建软硬链接

软连接:

bash 复制代码
ln -s 目标文件 软连接文件

看到file-sort.link有自己的inode,这就说明file-sort.link是一个独立的文件,向file.txt里写入东西:

发现可以从软链接中获取file.txt的内容,接下来创建硬链接:

bash 复制代码
ln 目标文件 软连接文件

发现硬链接的inode和目标文件的一样,说明硬链接不是一个独立的文件,发现目标文件的数字1变成了2,要是把硬链接给删掉:

又变回数字1了

理解软硬链接

  • 软链接实际上就是保存你目标文件的路径,就像我们Windows的快捷方式,点击桌面的快捷方式就可以打开文件
  • 硬链接本质就是一组文件名和已经存在的文件的映射关系,可以理解为给文件多取了一个新的名字
  • 为什么数字会变成2?其实这里的数字表示的是映射关系的个数,在这里有两个文件名指向目标文件,所以数字就会变成2,但要是把硬链接给删除,数字就变回了1
  • 实际上就是inode里维护着一个引用计数(也叫做硬链接数)记录着有多少个文件名指向着文件,所以要把一个文件真正的删除,就要把所有指向该文件的文件名全部删除,也就是引用计数为0
  • 要是把原先文件名给删掉,那不就相当于给我文件进行重命名了吗?但是要注意,这时候的软链接就失效了,因为该软链接找不到文件名了

为什么要有软硬链接?

软链接:

可以直接将可执行程序通过软链接直接链接到/usr/bin/目录底下,这样就可以直接执行可执行程序不需要带路径了

想要删掉软链接也可以使用unlink:

删除后再运行code就找不到了

也可以对目录进行软链接,比如有些目录路径太深了,就可以使用软链接到当前目录,所以软链接最主要的作用就是方便操作

对于硬链接,创建一个空目录,这个目录默认就有两个链接数:

这是因为对于这个目录有两个文件名指向它,一个是empty,另一个是.,没错这个.就是表示当前目录的意思,所以数字是2

当我在该空目录底下创建一个新目录dir:

就会发现链接数变成了3,那是因为新目录dir有..来指向上级目录,所以多了一个链接数

但是根目录有点特殊,因为是被特殊处理过的,当在根目录想继续切换到上级目录:

bash 复制代码
cd ..

是停留在根目录的,因为根目录这个..是特殊处理过的,也不影响链接数,也删不掉

硬链接可以对大文件进行备份并且不需要进行拷贝,节省空间的拷贝

但是,Linux中不允许对目录新建硬链接:

因为会形成环形路径:

软链接就不会,因为系统不会对软链接进行解析,读到软链接就不继续操作了,但是操作系统就可以创建目录的硬链接.和..嘛,你别管,操作系统就是老大

静态库

方法一:安装到系统里

先创建一些头文件和.o文件来创建静态库:

my_stdio.c:

c 复制代码
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

mFILE *mfopen(const char *filename, const char *mode)
{
    int fd = -1;
    if(strcmp(mode, "r") == 0)
    {
        fd = open(filename, O_RDONLY);
    }
    else if(strcmp(mode, "w")== 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);
    }
    if(fd < 0) return NULL;
    mFILE *mf = (mFILE*)malloc(sizeof(mFILE));
    if(!mf) 
    {
        close(fd);
        return NULL;
    }

    mf->fileno = fd;
    mf->flag = FLUSH_LINE;
    mf->size = 0;
    mf->cap = SIZE;

    return mf;
}

void mfflush(mFILE *stream)
{
    if(stream->size > 0)
    {
        // 写到内核文件的文件缓冲区中!
        write(stream->fileno, stream->outbuffer, stream->size);
        // 刷新到外设
        fsync(stream->fileno);
        stream->size = 0;
    }
}

int mfwrite(const void *ptr, int num, mFILE *stream)
{
    // 1. 拷贝
    memcpy(stream->outbuffer+stream->size, ptr, num);
    stream->size += num;

    // 2. 检测是否要刷新
    if(stream->flag == FLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size-1]== '\n')
    {
        mfflush(stream);
    }
    return num;
}

void mfclose(mFILE *stream)
{
    if(stream->size > 0)
    {
        mfflush(stream);
    }
    close(stream->fileno);
}

my_stdio.h:

c 复制代码
#pragma once

#define SIZE 1024

#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2

struct IO_FILE
{
    int flag; // 刷新方式
    int fileno; // 文件描述符
    char outbuffer[SIZE];
    int cap;
    int size;
    // TODO
};

typedef struct IO_FILE mFILE;

mFILE *mfopen(const char *filename, const char *mode);
int mfwrite(const void *ptr, int num, mFILE *stream);
void mfflush(mFILE *stream);
void mfclose(mFILE *stream);

my_strinng.c:

c 复制代码
#include "my_string.h"

int my_strlen(const char *s)
{
    const char *end = s;
    while(*end != '\0')end++;
    return end - s;
}

my_string.h:

c 复制代码
#pragma once

int my_strlen(const char *s);

对.c文件进行编译:

c 复制代码
gcc -c *.c

创建静态库:

bash 复制代码
ar -rc lib库名字.a.版本号

必须是以lib开头.a结尾,后面的版本号可以写可以不写:

将头文件拷贝到系统的头文件里,将新建的静态库拷贝到系统的静态库里:

接下来在另一个目录里面尝试使用我们所导入的静态库:

c 复制代码
#include <my_stdio.h>
#include <my_string.h>
#include <stdio.h>

int main()
{
    char *str = "hello";
    int len = my_strlen(str);
    printf("len: %d\n", len);
    mFILE *fp = mfopen("log.txt", "a");
    if(fp == NULL) return 1;
    
    mfwrite(str, len, fp);
    mfclose(fp);
    return 0;
}

发现链接报错!报错信息显示并不认识所用的函数,这是为什么?

首先,在lib64/里面并不只有一个库,编译器怎么知道你用的是哪一个库

这不对吧,那为什么我们平时用gcc的时候并不需要告诉编译器要用哪个库呢?

那是因为gcc是C语言编译,肯定是要认识C标准库的啊

所以我们要告诉编译器要用哪个库,无论你怎么操作,你都只是第三方库:

bash 复制代码
gcc test.c -llibmystdio.a

怎么还是不行?我已经告诉他了啊,那是因为告诉错了,库的名字是要去掉前缀和后缀:

bash 复制代码
gcc test.c -lmystdio

方法二:和源文件在一起

懂了吗?要是在目录中已经提供了.h和静态库该怎那么使用呢?首先就是吧头文件改回"":

c 复制代码
#include "my_stdio.h"
#include "my_string.h"
#include <stdio.h>

int main()
{
    char *str = "hello";
    int len = my_strlen(str);
    printf("len: %d\n", len);
    mFILE *fp = mfopen("log.txt", "a");
    if(fp == NULL) return 1;

    mfwrite(str, len, fp);
    mfclose(fp);
    return 0;
}
bash 复制代码
gcc test.c -lmystdio

竟然发现找不到库?明明已经在当前目录了啊,这就说明编译器在查找库的时候,并不会在当前目录下查找,所以引入另一个选项-L:

bash 复制代码
gcc test.c -L. -lmystdio

在-L之后指明路径,告诉编译器不仅要在系统库里查找,还要在我知名的路径下查找,在这里我写的是.,表示当前目录下

方法三:使用带路径的库

Makefile:

bash 复制代码
libmystdio.a:my_stdio.o my_string.o
	ar -rc $@ $^
%.o:%.c
	gcc -c $<
.PHONY:clean
clean:
	rm -rf *.a *.o output
.PHONY:output
output:
	mkdir -p stdc/include
	mkdir -p stdc/lib
	cp -f *.h stdc/include
	cp -f *.a stdc/lib

把弄好的库和头文件全部打包在stdc里:

对于这种情况,我们该怎么使用库?其实很简单,首先就是告诉编译器头文件在哪里:

bash 复制代码
-I std/include //可以有空格
-Istd/include //也可以无空格

然后就是告诉编译器库在哪里:

bash 复制代码
-L std/lib //可以有空格
-Lstd/lib //也可以无空格

最后就是告诉编译器哪一个库:

bash 复制代码
-lmystdio

总结:

bash 复制代码
gcc -o test test.c -Istdc/include -Lstdc/lib -lmystdio

动态库

怎么形成动态库:

bash 复制代码
gcc -o lib动态库.so 依赖文件 -shared // -shared就是告诉编译器不是形成可执行文件,是形成动态库

Makefile

bash 复制代码
libmystdio.so:my_stdio.o my_string.o
	gcc -o $@ $^ -shared
%.o:%.c
	gcc -fPIC -c $<
.PHONY:clean
clean:
	rm -rf *.so *.o stdc*

对于动态库,在.c文件编译成.o文件时要多加一个选项-fPIC,意思是形成与位置无关码(后续讲动态库链接时解释)

方法一:把动态库安装到系统中

导入库中:

bash 复制代码
 sudo cp *.so /usr/lib/

我们可以通过ldd指令来查看该可执行程序所依赖的库:

bash 复制代码
ldd ./a.out

即使形成了可执行文件,动态库也不能删除,删了就跑不动了

方法二:和源文件在一起

操作和静态库一样,但是

他找不到?先继续看方法三:

方法三:使用带路径的库

和静态库方法一样,但是

还是找不到?

解决问题

第一个方法:在Ubuntu下,系统查找动态库是在/lib下查找的,所以要让我们的动态库加载到/lib目录下,这个工作很简单,可以直接拷贝:

bash 复制代码
sudo cp ./libmystdio.so /lib/

第二个方法:直接在/lib目录下创建软链接与我们的动态库进行链接就行了:

bash 复制代码
sudo ln -s /home/Sherry/linux_learn/23_day/so/libmystdio.so /lib/libmystdio.so

一定要注意:软链接一定要与动态库同名

此时就可以执行了

第三个方法:导入环境变量

有没有想过为什么系统自身的动态库不需要进行这么复杂的操作?那是因为系统会默认去环境变量查找动态库,这个环境变量叫做:

bash 复制代码
LD_LIBRARY_PATH

一般没有配置过文件的是没有的,我们可以自行导入环境变量:

bash 复制代码
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/Sherry/linux_learn/23_day/so

同时提供动静态库,系统会选择哪一个?

同时提供动静态库,系统会默认选择动态库,若要想使用静态库,就要带上:

bash 复制代码
-static

要注意:如果你强制要使用静态链接,那么就必须提供静态库!

要是你没有提供动态库,只有静态库,用了动态链接,那么gcc、g++都没得选,只能针对你提供的静态库局部性采用静态链接,程序也可以跑

原理上理解动态库

动态库其实也是个文件,存放在磁盘中,要是有程序使用到这个动态库,就要将动态库加载到内存里

一个程序对应一个主进程,在进程里有对应的虚拟内存,虚拟内存依赖页表来与内存进行映射关系,当磁盘中的动态库加载到内存中后,虚拟内存里的共享区就会通过页表产生映射关系,这样程序就可以找到动态库

当然,一个动态库并非只服务于一个程序,可以是上百个程序,然而动态库已经加载到内存里,所以,只需要虚拟内存通过页表产生映射关系即可,无需再次加载动态库,大大节省了空间,也就是为什么,动态库也被称为共享库,也就是为什么,优先使用动态库

你学会了吗?

好啦,本章对于《Linux:软硬链接和动静态库》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!

如你喜欢,点点赞就是对我的支持,感谢感谢!!!

相关推荐
律宏阔5 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔5 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴5 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千6 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei376 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei376 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei376 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW6 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW6 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux
yunwei376 小时前
eBPF 开发者教程: 简单的 XDP 负载均衡器
linux·后端·性能优化