文件系统(软硬链接 动静态库 动态库加载的过程)

软硬链接

先看现象:先创建一个文件,并写入内容
在这里插入图片描述
建立软链接:

ln -s file_target1.txt file_soft.link

在这里插入图片描述
inode不一样,所以都是独立的文件:
在这里插入图片描述
建立硬链接:

ln file_target2.txt file_hard.link

在这里插入图片描述
发现硬链接的inode一样,且中间的数字为2
在这里插入图片描述
总结特征:
1.软链接是一个独立的文件,因为有独立的inode number
2.硬链接不是一个独立的文件,因为没有独立的inode number,用的是目标文件的inode
3.属性中有一列硬链接数。

软链接

软连接的内容:目标所指的路径
这跟windows下的快捷方式一样:
在这里插入图片描述
用路径也可以运行
在这里插入图片描述
把目标文件删掉后,软链接也跑不起来了
在这里插入图片描述

软链接有什么用?

路径比较多的话,可以迅速找到一个文件
这里的myls是ls的软链接,可以运行./myls运行ls
在这里插入图片描述
平常使用的各种库就是软链接
在这里插入图片描述
综上所述:与windows的快捷方式相似

硬链接

先看现象:会发现这个数字就是引用计数(磁盘级的引用计数:有多少个文件名字符串指向同一个inode)
在这里插入图片描述
根据inode一样可知:硬链接就是在指定的目录下,添加一个新的文件名与inode number的映射!(跟指针差不多,两个指针指向同一块空间)

硬链接有什么用?

为什么新创建的目录文件的引用计数是2、普通文件的引用计数是1呢?
在这里插入图片描述
每个目录文件里面都有".“和”. ."
一个".“指向当前路径,发现dir中的”.“的inode和dir目录本身的inode一样
在这里插入图片描述
在dir中创建一个文件,发现引用计数变成了3:dir本身,dir目录下的”.“,otherdir目录下的”. ."
在这里插入图片描述
这就是为什么cd . . 就是返回上级路径
看现象:发现Linux系统中,不能给目录建立硬链接
在这里插入图片描述
原因:root.hard就是"/"
在这里插入图片描述
Linux可以给自己建立硬链接,因为".“与”. .“名字是固定内置好的。(删不掉”.“与”. .“,是因为系统维护好的)
还有一个作用:备份。先建立硬链接,后删除源文件,但依旧能访问到源文件的内容(跟某软件删不掉的原理相似)
在这里插入图片描述
综上所述,硬链接的作用是:
1.构建Linux的路径结构,让我们可以使用”.“和”. ."来进行路径定位
2.一般用硬链接来做文件备份

动静态库

我们常用的都是C、C++的标准库:strcpy、string、STL…这些函数或类必须有实现
在这里插入图片描述
ldd可以看见所调用的库
在这里插入图片描述
查看C标准库就是软链接
在这里插入图片描述
一个程序运行不仅需要二进制可执行文件(a.out),还需要库。

Linux中的动静态库

Linux中 .so(动态库) .a(静态库)
windows中 .dll(动态库) .lib(静态库)
Linux中的指令基本都是C语言写的,都用一个C语言库
在这里插入图片描述

以下面两个所写的库为例子
mymath.h

#pragma once

int Add(int,int);
int Sub(int,int);

mymath.c

#include "mymath.h"

int Add(int x,int y)
{
    return x+y;
}

int Sub(int x,int y)
{
    return x-y;
}

下面两个是在fopen、fwrite、fclose、fflush简易版库
mystdio.h

#pragma once

#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define uint32_t unsigned int
#define LINE_SIZE 1024  //缓冲区大小
//124是标记位
#define FLUSH_NOW 1  //直接刷新
#define FLUSH_LINE 2  //行刷新
#define FLUSH_FULL 4  //全缓冲

struct _myFILE
{
    uint32_t flags; //标记位
    int fileno;
    //缓冲区
    char cache[LINE_SIZE];
    int cap;  //容量大小
    int pos;  //下次写的位置
};

typedef struct _myFILE myFILE;

myFILE* my_fopen(const char* path,const char* flag);

ssize_t my_fwrite(myFILE* fp,const char* data,int len);
void my_fflush(myFILE* fp);
void my_fclose(myFILE* fp);

mystdio.c

#include "mystdio.h"


myFILE* my_fopen(const char* path,const char* flag)
{
    int flag1 = 0;
    int iscreate = 0; //  是否要创建文件
    mode_t mode = 0666;  //  权限
    if(strcmp(flag,"r") == 0)
    {
        flag1 = (O_RDONLY);
    }
    else if(strcmp(flag,"w") == 0)
    {
        flag1 = (O_WRONLY | O_CREAT | O_TRUNC);
        iscreate = 1; //需要创建文件
    }
    else if(strcmp(flag,"a") == 0)
    {
        flag1 = (O_WRONLY | O_CREAT | O_APPEND);
        iscreate = 1; //需要创建文件
    }

    int fd = 0;
    if(iscreate)
        fd = open(path,flag1,mode);
    else
        fd = open(path,flag1);

    if(fd < 0)  return NULL;

    myFILE* fp = (myFILE*)malloc(sizeof(myFILE));
    if(!fp)  return NULL;

    fp->flags = FLUSH_LINE;
    fp->fileno = fd;
    fp->cap = LINE_SIZE;
    fp->pos = 0;

    return fp;
}

ssize_t my_fwrite(myFILE* fp,const char* data,int len)  //ssize_t->有符号整数
{
    memcpy(fp->cache+fp->pos,data,len);  //假设直接拷贝成功
    fp->pos+=len;

    if((fp->flags&FLUSH_LINE)&&fp->cache[fp->pos-1] == '\n')
    {
        write(fp->fileno,fp->cache,fp->pos);
        fp->pos = 0;
    }
    return len;
}

void my_fflush(myFILE* fp)
{
    write(fp->fileno,fp->cache,fp->pos);
    fp->pos = 0;
}

在这里插入图片描述
以一个故事为线:
老师布置了一个写库的作业,你已经写完了,舍友不会想要你的源代码。
你:因为命名风格一样,老师可能发现抄袭,所以你直接把所有文件编成.o(所有方法的实现),发给舍友。
默认形成同文件.o

gcc -c [文件名.c]  

在这里插入图片描述
你舍友表示不会用,.o全是二进制文件,不知道如何用。于是你就把头文件给发过去了。头文件中包含着所有的函数声明。
在这里插入图片描述
但是你舍友还是不会用。
你:实现在.o里,.h跟实现手册一样。
于是又根据.h的方法写了main.c

#include "mymath.h"
#include "mystdio.h"
#include <stdio.h>
#include <string.h>

int main()
{
    int a = 10;
    int b = 20;
    printf("%d+%d=%d\n",a,b,Add(a,b));

    myFILE* fp = my_fopen("./myfile.txt","w");
    if(fp == NULL)  return 1;
    const char* message = "hello Linux\n";
    my_fwrite(fp,message,strlen(message));
    my_fclose(fp);
    return 0;
}

发现报错信息是没有找到函数实现,因为只编译了你的文件,舍友的文件没有被编译
在这里插入图片描述
把main.c->main.o
在这里插入图片描述
总结:头文件是一个手册,提供函数的声明,告诉用户怎么用。.o提供实现,我们只需要补上一个main,调用头文件提供的方法,然后和.o进行链接,就能形成可执行
故事进入第二阶段:
老师看你和你的舍友能力挺强,于是安排新任务,需要写100个源文件。
于是你故技重施,把100个.o发给舍友,然后舍友误删了几个,编不过了。
可以打包一起发送,但是舍友不会解包。你说行吧,用ar命令
r是replace,c是create,相当于把所有的.o文件打包,并且舍友不需要解包。

ar -rc [文件名].a *.o

在这里插入图片描述
可以直接编译,因为系统自动帮助你把main.c->main.o。
在这里插入图片描述
综上所述:所谓的库就是打包*.o
库的作用:提高开发效率。(你舍友只拿着函数说明书就能运行,不用自己编写源代码)

静态库 && 安装库

依旧使用ar命令,ar是gnu归档工具

ar -rc [文件名].a [文件名].o

故事进入第三阶段:
把.h文件放进include中,把.a文件放进lib中
在这里插入图片描述
给舍友发过去了lib,舍友想直接用这个库,需要安装到系统里(只需要把头文件拷贝到头文件的搜索里,.a文件也是一样)。

sudo cp mylib/include/*.h /usr/include/
sudo cp mylib/lib/*.a /lib64

在这里插入图片描述
安装后,就跟下载的安装包类似,发给舍友的lib可以不要了
这样头文件可以用<>

#include <mymath.h>
#include <mystdio.h>

发现依旧找不到函数
在这里插入图片描述
因为我们平常使用的是C/C++的库,gcc/g++默认是认识C/C++库。
libmyc.a --》别人写的(第三方库) --》gcc/g++不认识 --》> -l[库名] 表示链接某库
发现找不到库(-l后面可以带空格 -l libmyc.a)
在这里插入图片描述
依旧找不到库,gcc认为去前缀,去掉后缀,剩下来的才是库的名字
在这里插入图片描述
这样就可以运行了。
在这里插入图片描述
以上就完成了库的安装,不推荐把自己的库放进去。因为可能会污染库。
不安装,如何用库呢?
因为main.c与.h和.a不处在同一路径下,所以.c文件找不到.h和.a文件
在这里插入图片描述
-I是include的意思(这里是大写的i),程序员自己指定头文件的路径。
gcc也是进程,在运行的时候,pwd拼上后面的路径,找到了头文件所在地

gcc [文件名].c -I [路径]

运行不了,链接报错
在这里插入图片描述
gcc表示我只认系统默认的库文件/lib64 /usr/lib64,你的库(第三方的库)在哪不知道
-L是lib的意思,告诉gcc我的库在哪

gcc [文件名].c -I [路径] -L [路径]

还是链接报错
在这里插入图片描述
因为还没有告诉gcc你要找哪个库,就跟/lib64目录下有很多库,你得告诉gcc找哪个库
在这里插入图片描述
库名依旧是去掉前缀和后缀

gcc [文件名].c -I [路径] -L [路径] -l[库名]

在这里插入图片描述
总结:
在这里插入图片描述
问题来了:
头文件为什么不用指定具体名称?
头文件已经指定了,你的.c文件里面指定的
在这里插入图片描述
不指明-I也可以在.c文件里指明
注意<>表示在系统的路径下去找,现在要在当前的路径下找所以得用""

#include "mylib/include/mymath.h"
#include "mylib/include/mystdio.h"

在这里插入图片描述
还有一个问题:我们用的libmyc.a库,为什么没有显示出来?
在这里插入图片描述
编译时只指明静态库(libmy.c)时,形成可执行程序时能动态链接的就动态链接,只能静态链接的,就把实现拷贝到可执行程序(a.out)中。
-static的表示全部强制静态链接
在这里插入图片描述

动态库

想要实现动态库要加个选项fPIC

gcc -fPIC -c 文件名.c

动态库在发开中用的最频繁的,编译器自己支持形成动态库,直接使用gcc/g++

gcc *.o -o [库名].so

报错了,因为库中不能存在main函数
在这里插入图片描述
告诉gcc我要形成动态库:-shared

gcc -shared *.o -o [库名].so

在这里插入图片描述
也把动态库拷贝到库中
在这里插入图片描述

gcc main.c -I mylib/include/ -L mylib/lib -l myc

编译能编过,但是无法运行。
在这里插入图片描述
发现libmyc.so找不到
在这里插入图片描述
因为只告诉了gcc/g++,形成a.out是gcc的工作。但没有告诉操作系统,./a.out运行操作系统不知道库在哪。
综上:
动态库:动态库要在程序运行的时候,要找到动态库加载并运行!
静态库:编译期间,已经将库中的代码拷贝到我们的可执行程序的内部了!加载和库就没有关系了!
系统默认在/lib64下寻找,链接和运行时都在/lib64下寻找。
第一种方法:把动态库拷贝到/lib64路径下(不建议)
在这里插入图片描述
第二种方法:在/lib64中建立一个软链接
在这里插入图片描述
第三种:操作系统中有个环境变量LD_LIBRARY_PATH,其中存储的就是动态库的搜索路径(不推荐)

echo $LD_LIBRARY_PATH

在这里插入图片描述

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:[需要添加的路径]

在这里插入图片描述
在这里插入图片描述
第四种:ld的意思为Load,so的意思是库,conf的意思是配置文件,.d的意思是目录

ls /etc/ld.so.conf.d

这个目录当中存储的是在系统当中的配置文件。
在这里插入图片描述
把当前要用的库的路径添加到其中,就永久生效了
首先是root才能修改
在这里插入图片描述
其次在目录中命名一个文件,命名必须要以.conf结尾
在这里插入图片描述
再把路径写入其中
在这里插入图片描述
在root的权限下让配置文件生效:

ldconfig

就可以运行了
在这里插入图片描述

动态库 VS 静态库

动静态库的大小差很多
在这里插入图片描述
-static 的意义是强制将我们的程序静态链接,这就要求我们链接的任何库都必须提供对应的静态库版本。

用第三方库

用别的人库,应该提供什么呢?

sudo yum install ncurses

下载开发版本:

sudo yum install ncurses-devel

在这里插入图片描述
下面的代码具体在这个网址C语言实现简易文本编辑器(新手向)

#include <ncurses.h>
int main(){
    int key=0;
    initscr();
    noecho();
    keypad(stdscr,TRUE);
    key=getch();
    while(key!='$'){
        printw("%d\n",key);
        key=getch();
        refresh();
    }
    endwin();
}

因为是第三方库,所以编译使用命令时需要链接

gcc test.c -l ncurses

在这里插入图片描述

动态库加载

下面是调用动态库的简略版过程。
动态库被加载之后,要映射到当前进程的堆栈之间的共享区。
在这里插入图片描述
如果其他进程也需要该动态库,是不用重复加载的,都用这一份动态库。也称共享库
在这里插入图片描述
执行可执行程序,编译成功,没有加载运行时,二进制代码中有"地址"吗?
看现象:

#include <stdio.h>

int Sum(int top)
{
    int i = 1;
    int ret = 0;
    for(;i<=top;i++)
    {
        ret += 1;
    }
    return ret;
}

int main()
{
    int top = 100;
    int result = Sum(top);
    printf("result:%d\n",result);
    return 0;
}

看反汇编

objdump -S a.out > code.s

可以看出是有地址的。函数名也变成了地址
在这里插入图片描述
在可执行形成的时候,也规划好了地址所用的空间
在这里插入图片描述
Linux中的可执行程序都是ELF格式的可执行程序,形成的二进制时是有自己的固定格式的。固定格式中有elf可执行程序的头部,里面有很多可执行程序的属性。
在这里插入图片描述
如何编制呢?
假设编址范围是0000 0000 ~ FFFF FFFF (虚拟地址,又称为逻辑地址)
地址种类:绝对编址(从0000 0000依次往后排) 又称为平坦模式。上图就是绝对编址
相对编址:相对地址+偏移量 (所有函数的起始地址都为0,里面存的都是偏移量0,1,2…)
在这里插入图片描述

elf头部信息

下图画红线的是Linux中的加载器:先让操作系统把库加载到内存中,执行库里面的方法,把可执行程序拷贝到内存中。拷贝时有虚拟地址。程序中有头部信息,一定要被加载器进行解释、扫描代码、找到main的入口,并且在elf头文件中加载各个区(程序被划分各个区的)。
在这里插入图片描述
ELF+加载器:可以让我们找到各个区域区域的起始和结束地址与main函数的入口地址
第二个问题:
进程 = 内核数据结构 + 代码和数据。先有内核数据结构后再有代码和数据添加到内存中。mm_struct 是结构体对象,成员变量是由code_start、code_end…组成,那么成员变量的初始值从哪里来呢?
首先肯定不是操作系统固定规划的,你写了一个正文5行的hello world,他写了50w行的CSGO,正文段的结束肯定不一样。
只有可执行程序自己知道有多大,在elf头部信息中有代码段多大的信息,填入到mm_struct中。
综上:虚拟地址空间由OS、编译器、加载器共同组成。
故事:你在学校的宿舍比如是513的2号床(物理地址),学号是111(虚拟地址),线下找你肯定是拿着宿舍的地址找你,校方线上查看档案肯定是拿着学号找。所以你在学校有你的学号(虚拟地址),占有宿舍空间(物理地址),形成了映射关系。
CPU存在一个寄存器:pc指针,会保存要执行下一条指令的地址。(pc指向哪里,CPU就要执行哪里的代码)
在这里插入图片描述
CPU开始运行:CPU问pc指针我接下来要运行哪条指令。pc指针给出地址,因为是虚拟地址要根据进程查页表,然后找到了真实的物理地址去运行,同时把下一条指令的虚拟地址读到pc指针中,重复上述过程。
在这里插入图片描述
库函数与上述可执行文件的编址也一样,除了没有main函数
在这里插入图片描述
磁盘中的地址都是虚拟地址
在这里插入图片描述
只有物理地址是无法使用的,需要mm_struct中的栈和堆之间开辟一段空间(因为物理内存中已经有库了,所以开辟多少空间的大小也是确定的)填入库的虚拟地址,然后与页表建立映射关系。
在这里插入图片描述
假设虚拟地址是00000000~FFFFFFFF
在磁盘中,库中每一个函数都可以表示为myc:偏移量(lib库的地址+偏移量)
在这里插入图片描述
磁盘中的偏移量,加载到mm_struct中的偏移量是不变的。
在这里插入图片描述
综上:库被映射到地址的什么位置是不重要的,只要找到库的起始的地址,就能通过偏移量找到函数。
库函数调用,是在地址空间内来回调用。
上面说的库被加载进内存,那如何知道库有没有被加载呢?
首先我们要知道库不止一个,不止一个说明要管理,管理:先组织,再描述
在这里插入图片描述
内存中有一个描述库的结构体:什么时候被加载进去,谁映射的…
当有其他程序需要用到这个库的时候,遍历列表发现其有没有被加载,有加载直接映射。
总结动态库加载的过程:可执行程序先加载内存中,运行到需要调用库的地方,如果库不在,就缺页中断,根据路径找到库加载进来,通过映射得到我的库的起始地址,库的地址+偏移量找到方法(函数)。
静态库直接放进正文代码中。
-fPIC表示:让动态库独立加载,不和可执行程序拷贝在一起。在物理地址中任意加载,在地址空间中任意映射

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浅碎时光807

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值