Linux--库的制作与原理

目录

[1. 什么是库](#1. 什么是库)

[2. 静态库](#2. 静态库)

头文件和源文件示例准备:

[2.1 静态库生成](#2.1 静态库生成)

[2.2 静态库使用](#2.2 静态库使用)

[3. 动态库](#3. 动态库)

[3.1 动态库生成](#3.1 动态库生成)

[3.2 动态库使用](#3.2 动态库使用)

[3.3 库运行搜索路径](#3.3 库运行搜索路径)

[3.3.1 问题:](#3.3.1 问题:)

[3.3.2 解决方案:](#3.3.2 解决方案:)


正文开始:

1. 什么是库

库是写好的、现有的、成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

本质上来说库是⼀种可执行代码的⼆进制形式,可以被操作系统载入内存执行。库有两种:

  • 静态库 .aLinux.libwindows
  • 动态库 .soLinux.dllwindows

2. 静态库

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库。
  • ⼀个可执行程序可能用到许多的库,这些库运行有的是静态库,有的是动态库,而我们的编译默认为动态链接库,只有在该库下找不到动态.so的时候才会采用同名静态库。我们也可以使用 gcc 的 -static 强转设置链接静态库。

头文件和源文件示例准备:

mystdio.h:

cpp 复制代码
#pragma once

#include <stdio.h>

#define MAX 1024
#define NONE_FLUSH (1<<0)
#define LINE_FLUSH (1<<1)
#define FULL_FLUSH (1<<2)

typedef struct IO_FILE
{
    int fileno;
    int flag;
    char outbuffer[MAX];
    int bufferlen;
    int flush_method;
}MyFile;


MyFile *MyFopen(const char *path, const char *mode);
void MyFclose(MyFile *);
int MyFwrite(MyFile *, void *str, int len);
void MyFFlush(MyFile *);

mystdio.c:

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

static MyFile *BuyFile(int fd, int flag)
{
    MyFile *f = (MyFile*)malloc(sizeof(MyFile));
    if(f == NULL) return NULL;
    f->bufferlen = 0;
    f->fileno = fd;
    f->flag = flag;
    f->flush_method = LINE_FLUSH;
    memset(f->outbuffer, 0, sizeof(f->outbuffer));
    return f;
}

MyFile *MyFopen(const char *path, const char *mode)
{
    int fd = -1;
    int flag = 0;
    if(strcmp(mode, "w") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_APPEND;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "r") == 0)
    {
        flag = O_RDWR;
        fd = open(path, flag);
    }
    else
    {
        //TODO
    }
    if(fd < 0) return NULL;
    return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{
    if(file->fileno < 0) return;
    MyFFlush(file);
    close(file->fileno);
    free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{
    // 1. 拷贝
    memcpy(file->outbuffer+file->bufferlen, str, len);
    file->bufferlen += len;
    // 2. 尝试判断是否满足刷新条件!
    if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n')
    {
        MyFFlush(file);
    }
    return 0;
}
void MyFFlush(MyFile *file)
{
    if(file->bufferlen <= 0) return;
    // 把数据从用户拷贝到内核文件缓冲区中
    int n = write(file->fileno, file->outbuffer, file->bufferlen);
    (void)n;
    fsync(file->fileno);
    file->bufferlen = 0;

}

mystring.h:

cpp 复制代码
#pragma once

int my_strlen(const char *s);

mystring.c:

cpp 复制代码
#include "mystring.h"

int my_strlen(const char *s)
{
    const char *start = s;
    while(*s)
    {
        s++;
    }
    return s - start;
}

2.1 静态库生成

  • ar 是 gnu 归档工具, rc 表示 (replace and create)。.a静态库,本质是一种归档文件,不需要使用者解包,而用gcc/g++直接进行链接即可!
bash 复制代码
$ ar -tv libmystdio.a
rw-rw-r-- 1000/1000 2848 Oct 29 14:35 2024 my_stdio.o
rw-rw-r-- 1000/1000 1272 Oct 29 14:35 2024 my_string.o
  • t:列出静态库中的文件
  • v:verbose 详细信息

作为库的制作者,以下为简单静态库的制作示例:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux/26_0518$ ll
total 28
drwxrwxr-x  2 ztl ztl 4096 May 18 13:50 ./
drwxrwxr-x 22 ztl ztl 4096 May 18 13:39 ../
-rw-rw-r--  1 ztl ztl 1700 May 18 13:49 mystdio.c
-rw-rw-r--  1 ztl ztl  409 May 18 13:48 mystdio.h
-rw-rw-r--  1 ztl ztl  144 May 18 13:50 mystring.c
-rw-rw-r--  1 ztl ztl   44 May 18 13:49 mystring.h
ztl@hcss-ecs-c91e:~/linux/26_0518$ vim Makefile
bash 复制代码
//vim Makefile:

libmyc.a:mystdio.o mystring.o
	ar -rc $@ $^
mystdio.o:mystdio.c
	gcc -c $<
mystring.o:mystring.c
	gcc -c $<

.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/mylib
	cp -f *.h lib/include
	cp -f *.a lib/mylib
	tar czf lib.tgz lib

.PHONY:clean
clean:
	rm -rf *.o libmyc.a lib lib.tgz
bash 复制代码
ztl@hcss-ecs-c91e:~/linux/26_0518$ make
gcc -c mystdio.c
gcc -c mystring.c
ar -rc libmyc.a mystdio.o mystring.o
ztl@hcss-ecs-c91e:~/linux/26_0518$ ll
total 44
drwxrwxr-x  2 ztl ztl 4096 May 18 15:31 ./
drwxrwxr-x 22 ztl ztl 4096 May 18 13:39 ../
-rw-rw-r--  1 ztl ztl 4658 May 18 15:31 libmyc.a
-rw-rw-r--  1 ztl ztl  292 May 18 15:31 Makefile
-rw-rw-r--  1 ztl ztl 1700 May 18 13:49 mystdio.c
-rw-rw-r--  1 ztl ztl  409 May 18 13:48 mystdio.h
-rw-rw-r--  1 ztl ztl 3136 May 18 15:31 mystdio.o
-rw-rw-r--  1 ztl ztl  144 May 18 13:50 mystring.c
-rw-rw-r--  1 ztl ztl   44 May 18 13:49 mystring.h
-rw-rw-r--  1 ztl ztl 1264 May 18 15:31 mystring.o
ztl@hcss-ecs-c91e:~/linux/26_0518$ make output
mkdir -p lib/include
mkdir -p lib/mylib
cp -f *.h lib/include
cp -f *.a lib/mylib
tar czf lib.tgz lib
ztl@hcss-ecs-c91e:~/linux/26_0518$ ll
total 52
drwxrwxr-x  3 ztl ztl 4096 May 18 15:31 ./
drwxrwxr-x 22 ztl ztl 4096 May 18 13:39 ../
drwxrwxr-x  4 ztl ztl 4096 May 18 15:31 lib/
-rw-rw-r--  1 ztl ztl 4658 May 18 15:31 libmyc.a
-rw-rw-r--  1 ztl ztl 1790 May 18 15:31 lib.tgz
-rw-rw-r--  1 ztl ztl  292 May 18 15:31 Makefile
-rw-rw-r--  1 ztl ztl 1700 May 18 13:49 mystdio.c
-rw-rw-r--  1 ztl ztl  409 May 18 13:48 mystdio.h
-rw-rw-r--  1 ztl ztl 3136 May 18 15:31 mystdio.o
-rw-rw-r--  1 ztl ztl  144 May 18 13:50 mystring.c
-rw-rw-r--  1 ztl ztl   44 May 18 13:49 mystring.h
-rw-rw-r--  1 ztl ztl 1264 May 18 15:31 mystring.o

2.2 静态库使用

bash 复制代码
// 任意⽬录下,新建
// usercode.c,引⼊库头⽂件

#include "mystdio.h"
#include "mystring.h"
#include <stdio.h>

int main()
{
    const char *s = "abcdefg";
    printf("%s: %d\n", s, my_strlen(s));

    mFILE *fp = mfopen("./log.txt", "a");
    if(fp == NULL) return 1;

    mfwrite(s, my_strlen(s), fp);
    mfwrite(s, my_strlen(s), fp);
    mfwrite(s, my_strlen(s), fp);
    mfclose(fp);

    return 0;
}


// 场景1:头⽂件和库⽂件安装到系统路径下
$ gcc main.c -lmyc

// 场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下
$ gcc main.c -L. -lmyc

// 场景3:头⽂件和库⽂件有⾃⼰的独⽴路径
$ gcc main.c -I头⽂件路径 -L库⽂件路径 -lmyc

示例:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux$ ls
26_0111  26_0313  26_0410  26_0421  26_0507  26_0511  26_0515  lesson16  lesson18  lesson20  test.c   zhangsan
26_0112  26_0316  26_0411  26_0504  26_0508  26_0514  26_0518  lesson17  lesson19  test1.c   test.cc
ztl@hcss-ecs-c91e:~/linux$ cd zhangsan
ztl@hcss-ecs-c91e:~/linux/zhangsan$ cp ../26_0518/lib.tgz .  //模拟zhangsan用户拿到静态库
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ll                       //后的使用示范
total 12
drwxrwxr-x  2 ztl ztl 4096 May 18 16:02 ./
drwxrwxr-x 23 ztl ztl 4096 May 18 16:00 ../
-rw-rw-r--  1 ztl ztl 1790 May 18 16:02 lib.tgz
ztl@hcss-ecs-c91e:~/linux/zhangsan$ vim usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ll
total 16
drwxrwxr-x  2 ztl ztl 4096 May 18 16:04 ./
drwxrwxr-x 23 ztl ztl 4096 May 18 16:00 ../
-rw-rw-r--  1 ztl ztl 1790 May 18 16:02 lib.tgz
-rw-rw-r--  1 ztl ztl  586 May 18 16:04 usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ tar xzf lib.tgz  //解压缩包
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ll
total 20
drwxrwxr-x  3 ztl ztl 4096 May 18 16:05 ./
drwxrwxr-x 23 ztl ztl 4096 May 18 16:00 ../
drwxrwxr-x  4 ztl ztl 4096 May 18 15:31 lib/
-rw-rw-r--  1 ztl ztl 1790 May 18 16:02 lib.tgz
-rw-rw-r--  1 ztl ztl  586 May 18 16:04 usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ gcc usercode.c -I ./lib/include/ -L ./lib/mylib/ -l myc
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ll
total 40
drwxrwxr-x  3 ztl ztl  4096 May 18 16:06 ./
drwxrwxr-x 23 ztl ztl  4096 May 18 16:00 ../
-rwxrwxr-x  1 ztl ztl 16736 May 18 16:06 a.out*
drwxrwxr-x  4 ztl ztl  4096 May 18 15:31 lib/
-rw-rw-r--  1 ztl ztl  1790 May 18 16:02 lib.tgz
-rw-rw-r--  1 ztl ztl   586 May 18 16:04 usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ./a.out
buffer: hello myfile!!!
buffer: hello myfile!!!
buffer: hello myfile!!!
buffer: hello myfile!!!
buffer: hello myfile!!!

3. 动态库

  • 动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
  • ⼀个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的⼀个表,而不是外部函数所在目标文件的整个机器码
  • 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中, 这个过程称为动态链接(dynamic linking)
  • 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的⼀份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间

3.1 动态库生成

bash 复制代码
//Makefile

libmyc.so:mystdio.o mystring.o
	gcc -shared -o $@ $^
mystdio.o:mystdio.c
	gcc -fPIC -c $<
mystring.o:mystring.c
	gcc -fPIC -c $<

.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/mylib
	cp -f *.h lib/include
	cp -f *.so lib/mylib
	tar czf lib.tgz lib

.PHONY:clean
clean:
	rm -rf *.o libmyc.so lib lib.tgz
  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

示例:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux/26_0518$ ls
Makefile  mystdio.c  mystdio.h  mystring.c  mystring.h
ztl@hcss-ecs-c91e:~/linux/26_0518$ vim Makefile
ztl@hcss-ecs-c91e:~/linux/26_0518$ make
gcc -fPIC -c mystdio.c
gcc -fPIC -c mystring.c
gcc -shared -o libmyc.so mystdio.o mystring.o
ztl@hcss-ecs-c91e:~/linux/26_0518$ ls
libmyc.so  Makefile  mystdio.c  mystdio.h  mystdio.o  mystring.c  mystring.h  mystring.o
ztl@hcss-ecs-c91e:~/linux/26_0518$ make output
mkdir -p lib/include
mkdir -p lib/mylib
cp -f *.h lib/include
cp -f *.so lib/mylib
tar czf lib.tgz lib
ztl@hcss-ecs-c91e:~/linux/26_0518$ ls
lib  libmyc.so  lib.tgz  Makefile  mystdio.c  mystdio.h  mystdio.o  mystring.c  mystring.h  mystring.o

3.2 动态库使用

bash 复制代码
// 场景1:头⽂件和库⽂件安装到系统路径下
$ gcc main.c -lmyc

// 场景2:头⽂件和库⽂件和我们⾃⼰的源⽂件在同⼀个路径下
$ gcc main.c -L. -lmyc // 从左到右搜索-L指定的⽬录

// 场景3:头⽂件和库⽂件有⾃⼰的独⽴路径
$ gcc main.c -I头⽂件路径 -L库⽂件路径 -lmyc

$ ldd libmystdio.so // 查看库或者可执⾏程序的依赖
        linux-vdso.so.1 => (0x00007fffacbbf000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f8917335000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8917905000)

示例:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ll
total 12
drwxrwxr-x  2 ztl ztl 4096 May 18 16:48 ./
drwxrwxr-x 23 ztl ztl 4096 May 18 16:00 ../
-rw-rw-r--  1 ztl ztl  586 May 18 16:04 usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ cp ../26_0518/lib.tgz .
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ls
lib.tgz  usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ gcc -o usercode usercode.c -I lib/include/ -L lib/mylib/ -lmyc
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ls
lib  lib.tgz  usercode  usercode.c
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ./usercode
./usercode: error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ldd usercode
	linux-vdso.so.1 (0x00007fff07cf0000)
	libmyc.so => not found
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b7d8b400000)
	/lib64/ld-linux-x86-64.so.2 (0x00007b7d8b77c000)

3.3 库运行搜索路径

3.3.1 问题:

在上面运行usercode文件时会发现如下问题:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ./usercode
./usercode: error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ldd usercode
	linux-vdso.so.1 (0x00007fff07cf0000)
	libmyc.so => not found
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b7d8b400000)
	/lib64/ld-linux-x86-64.so.2 (0x00007b7d8b77c000)

在上述的操作中,不是明明已经说明了要在哪个路径下找库吗,为什么还是会找不到libmyc.so

原来在动态库的操作中,上述操作只是告诉了gcc去哪找库,而当我们的可执行程序准备运行的时候,该程序所依赖的库是需要被系统知道的。系统不等于gcc,所以会出错。

3.3.2 解决方案:

  • 拷贝 .so 文件到系统共享库路径下, ⼀般指 /usr/lib、/usr/local/lib、/lib64 或者开篇指明的库路径等
  • 向系统共享库路径下建立同名软链接
  • 更改环境变量: LD_LIBRARY_PATH (OS在运行程序时,要查找动态库,也会在该环境变量下查找动态库,注意其经常为空)
  • ldconfig方案:配置/ etc/ld.so.conf.d/ ,ldconfig更新

示例:

bash 复制代码
ztl@hcss-ecs-c91e:~/linux/zhangsan$ pwd
/home/ztl/linux/zhangsan
ztl@hcss-ecs-c91e:~/linux/zhangsan$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/ztl/linux/zhangsan/lib/mylib
ztl@hcss-ecs-c91e:~/linux/zhangsan$ echo ${LD_LIBRARY_PATH}
:/home/ztl/linux/zhangsan/lib/mylib:/home/ztl/linux/zhangsan/lib/mylib
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ldd usercode
	linux-vdso.so.1 (0x00007ffc21195000)
	libmyc.so => /home/ztl/linux/zhangsan/lib/mylib/libmyc.so (0x0000788eecc60000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000788eeca00000)
	/lib64/ld-linux-x86-64.so.2 (0x0000788eecc6c000)
ztl@hcss-ecs-c91e:~/linux/zhangsan$ ./usercode
buffer: hello myfile!!!
buffer: hello myfile!!!
buffer: hello myfile!!!