• Linux的so组件设计框架及逻辑


    组件这个名词在百度上的定义:组件(Component)是对数据和方法的简单封装

    这里不再赘述概念和定义,也不再过多纠结很多技术问题或者讨论。我们就实际组件应用关注的焦点和思考,做一个回顾,并整理Linux的so组件设计框架及逻辑。

    1. 组件关注点

    1.1 组件定义关注点

    1. 组件属性
    2. 组件方法

    1.2 组件业务关注点

    1. 支持多实例(线程安全)
    2. 无缝升级(无业务中断)
    3. 支持负荷分担(业务动态迁移)
    4. 支持性能监测
    5. 支持安全策略
    6. 支持可配置(所有参数调优)
    7. 支持优雅退出功能

    2. so特性

    从库的角度,大体分为静态链接库和动态链接库两大类。so就是linux下动态链接库的后缀。其特性可参考度娘

    1. 扩展了应用程序的特性;
    2. 可以用许多种编程语言来编写;
    3. 简化了软件项目的管理;
    4. 有助于节省内存;
    5. 有助于资源共享;
    6. 有助于应用程序的本地化;
    7. 有助于解决平台差异;
    8. 可以用于一些特殊的目的;

    3. so组件设计逻辑

    这里以Linux系统为例,但是Windows同样适用(可能代码上稍有差异)。

    我们为了解决组件的关注点,应用了动态链接库的特性,从逻辑层面可以解决业务需求。

    so动态链接库:

    1. 代码段共享,节省内存;
    2. 数据段每个进程拥有各自的私有数据副本;
    3. 组件方法提供了天然的API接口设计理念,反向要求设计者具备更高的业务抽象和解耦;
    4. 便于版本及项目管理;
    5. 多语言开发平台以及平台差异的天然兼容性;
    6. 析构和构造函数有助于组件的自身动态维护;

    4. so组件设计框架

    通过组件的构造和析构作为组件初始化和去初始化机制,当然为了更好的应对优雅退出及异常场景处理,在构造函数中需要做非常多的业务/异常场景的分析和钩子函数设计。

    这里针对构造、析构和接口问题进行框架介绍和范例举例:

    4.1 so组件范例

    • 构造函数
    • 析构函数
    • API接口
    //so_test.c
    #include <stdio.h>
    
    int Myfunc_test(void)
    {
    	printf("........Myfunc_test in %s........\n", __FILE__);
    	return(0);
    }
    
    static void __attribute__((constructor)) Myfunc_init(void)
    {
    	printf("Myfunc_init in %s\n", __FILE__);
    	return;
    }
    
    static void __attribute__((destructor)) Myfunc_fini(void)
    {
    	printf("Myfunc_fini in %s\n", __FILE__);
    	return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    //so_test.h
    #ifndef __SO_TEST_HEADER__
    #define __SO_TEST_HEADER__
    
    int Myfunc_test(void);
    
    #endif /* __SO_TEST_HEADER__ */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编译方法

    $ gcc -fPIC -shared -o libtestsubx.so so_test.c
    
    • 1

    4.2 so隐式调用范例

    #include <stdio.h>
    #include "so_test.h"
    
    int main(int argc, char** argv)
    {
    	printf("so main\n");
    	Myfunc_test();
    	sleep(5);
    	printf("so main exit\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    编译方法

    $ gcc -g so_main.c -o so_main -L. -ltestsubx
    
    • 1

    鉴于隐式调用收到so路径的限制,因此需要更好的管理路径,测试脚本如下

    #!/bin/sh
    export LD_PRELOAD=${pwd}libtestsubx.so:${LD_PRELOAD}
    export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
    ./so_main
    
    • 1
    • 2
    • 3
    • 4

    4.3 so显式调用范例

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <dlfcn.h>
    #include "so_test.h"
    
    int main(int argc, char** argv)
    {
    	void(*pTest)();      
    
    	char dir[256];
    	
    	char *path = getcwd(dir, 256);
    	if ( path == NULL ) {
    		printf("Failed get current dir\n");
    		return -1;
    	}
    	
    	strcat(path, "/");
    	strcat(path, "/libtestsubx.so");
    	printf("Current so is %s\n", path);
    
    	void*pdlHandle = dlopen(path, RTLD_LAZY);
    	if( pdlHandle == NULL ) {
    		printf("Failed load library\n");
    		return -1;
    	}
    
    	char* pszErr = dlerror();
    	if(pszErr != NULL) {
    		printf("%s\n", pszErr);
    		return -1;
    	}
    
    	pTest = dlsym(pdlHandle, "Myfunc_test");
    	pszErr = dlerror();
    	if( pszErr != NULL ) {
    		printf("%s\n", pszErr);
    		dlclose(pdlHandle);
    		return -1;
    	}
    
    	(*pTest)();
    
    	dlclose(pdlHandle);
    
    	return 0;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    编译方法

    $ gcc -g so_dlopen.c -o so_dlopen -L. -ldl
    
    • 1

    5 so组件设计建议

    根据实际应用的要求,选择隐式调用和显式调用。通常来说简单设计,隐式调用更加方便和直接。

    而从实际要对组件进行“1.2 组件业务关注点”这种复杂考虑,那么建议采用显式调用方法,并针对API进行管理(这里的API是指函数)。更多业务类解耦可以考虑消息API。

    在大型项目开发中,需要建立主体程序框架和基于主体程序框架的业务组件设计框架,以便针对业务的组件能够通过各种API进行解耦,独立进行开发、测试。

  • 相关阅读:
    Spring Boot 引起的“堆外内存泄漏”排查及经验总结
    C#8.0本质论第十章--合式类型
    关于ComfyUI的一些Tips
    Java性能优化-书写高质量SQL的建议(如何做Mysql优化)
    C语言——概述
    贪心,CF 1891C - Smilo and Monsters
    无法在CentOS7安装docker
    十四、Django框架使用
    抢先体验!星河社区ERNIE Bot SDK现已支持文心大模型4.0
    WIN11+OPENCV4.8 编译及下载失败处理方法
  • 原文地址:https://blog.csdn.net/lida2003/article/details/125513723