"); //-->
(1条消息) 动态库中的函数实现互斥调用_卢平光的博客-CSDN博客_函数互斥
一直在纠结一个问题:
如果一个函数使用互斥锁可以防止被调用时重入的情况,但是如果该函数以so的形式提供给使用者(其它进程),那么如何做到各进程间对于该函数的互斥调用呢?
首先明确下前提:
so被进程加载时,代码段共享,但是所有变量(局部、全局、静态变量)都是各进程copy一份私有使用。
也就是说,想要在so内实现一个不可重入的函数还是比较困难的,因为所有变量都是独立的,但是考虑如下场景:驱动层给了一个视频码流录制的接口,并且没有在驱动层做互斥,但实际上这个接口同一时间只可能被一个进程调用,那么很明显,串接到so中的接口必须实现该接口的原子调用。
解决思路:
由so创建一块共享内存,放置一份进程共享的互斥锁
第一步的动作需要寻找一个合适点自动完成,比如so加载时
各进程在调用so接口时,接口内部使用该共享锁完成互斥调用
————————————————
1、互斥锁共享
// 定义进程锁结构体 typedef struct Mutex_Info { // 锁以及状态 pthread_mutex_t lock; pthread_mutexattr_t lock_attr; // 在共享内存中的标识符 int FLAG; } mutex_info_t;
要想把锁放到共享内存,那么先创建一块内存
/** * 返回一片共享内存标识符,用于后续获取该共享内存,以及销毁该共享内存 * INDEX_OF_KEY —— 自定义的该共享内存序号 * LENGTH —— 共享内存大小 */ const int create_sharemem(const int INDEX_OF_KEY, const unsigned int LENGTH) { // 生成key const char* FILE_PATH = "./"; key_t key = ftok(FILE_PATH, INDEX_OF_KEY); // 创建共享内存空间 //多个进程调用该函数,只要确保KEY相同,那么只会创建同一块内存 const int FLAG = shmget(key, LENGTH, IPC_CREAT | 0666); return FLAG; }
其中的init函数是锁的初始化,注意需要设置其进程间共享属性
const int init_mutex(void* pthis) { mutex_info_t* mp = (mutex_info_t*)pthis; // 初始化锁状态,设置状态状态为——进程共享 pthread_mutexattr_init(&(mp->lock_attr)); pthread_mutexattr_setpshared(&(mp->lock_attr), PTHREAD_PROCESS_SHARED); // 用锁状态来初始化锁 pthread_mutex_init(&(mp->lock), &(mp->lock_attr)); return 0; }
有了以上两个函数,其实就可以写一个so被加载时自动执行的初始化函数,这样可以保证so的使用者不必关心内存、锁的创建、销毁和使用。所以先插讲下so加载时自动执行函数的方法。
2、so加载时自动执行函数
GNU C 的一大特色就是__attribute__ 机制。attribute 是一个编译指令,可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。
attribute 书写特征是:attribute 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__ 参数。
若将某一函数的声明中添加 __ attribute__((constructor)) 属性,那么它具有两种运行时机
若函数所在源文件被编译为可执行文件,那么该函数可以在main函数执行前被调用
————————————————
#include <stdio.h> #include <stdlib.h> static int * g_count = NULL; __attribute__((constructor)) void load_file() { printf("Constructor is called.\n"); } __attribute__((destructor)) void unload_file() { printf("destructor is called.\n"); } int main() { printf ("this is main function\n"); return 0; }
注意正常退出才会自动执行destructor 的修饰的函数。
若函数所在源文件被编译为共享库,那么该函数可以在共享库被其它进程显式dlopen或者隐式由操作系统加载时都会优先执行
//process source file #include <stdio.h> #include <string.h> #include <dlfcn.h> int main(int argc, char **argv) { void (*print)(); int (*add)(int, int); void *handle; if (argc < 2) return -1; handle = dlopen(argv[1], RTLD_LAZY); if (!handle) { printf("dlopen failed: %s\n", dlerror()); return -1; } print = dlsym(handle, "print"); if (!print) { printf("dlsym failed: %s\n", dlerror()); return -1; } print(); add = dlsym(handle, "add"); if (!add) { printf("dlsym failed: %s\n", dlerror()); return -1; } add(1, 2); dlclose(handle); return 0; } //libss.so source file #include <stdio.h> #include <string.h> void print() { printf("I am print\n"); } int add(int a, int b) { printf("Sum %d and %d is %d\n", a, b, a + b); return 0; } //static void king() __attribute__((constructor(101))); the following is also right static __attribute__((constructor(101))) void king() { printf("I am king\n"); }
gcc -Wall -shared -fPIC -o libss.so libss.c -ldl gcc -Wall -o udlopen udlopen.c ./udlopen libss.so I am king I am print Sum 1 and 2 is 3
__ attribute__((constructor))的这一特性可以被用在许多场合。比如某个so的功能实现需要预先映射一块共享内存,如果要求所有使用该so的进程在加载时手动去做这一步骤是非常不合理的,此时可以将内存映射实现放在__ attribute__((constructor))属性声明的函数中,这样每次so被加载就会自动完成共享内存的创建映射,对so的使用者完全透明,确实够巧妙!
————————————————
3、so加载时自动创建共享内存与互斥锁
//全局变量 mutex_info_t tPtr = NULL; __ attribute__((constructor)) static void pre_init(void) { int flag = create_sharemem(127, sizeof(mutex_info_t )); //NULL参数代表由操作系统选择共享内存中的合适位置返回给内存申请者,测试发现由于共享内存一共就只有mutex_info_t 大小, //所以每次返回的地址相同 这也能保证so被多个进程加载使用的是同一把锁 tPtr = (mutex_info_t *)shmat(FLAG, NULL, SHM_R | SHM_W); //内存内结构体的FLAG如果不是共享内存的索引,那么表示是第一次申请内存,需要对锁初始化 //避免多次加载so时多次init锁 if(tPtr->FLAG != flag) { tPtr->FLAG == flag; init_mutex(tPtr); printf("first make mem, init lock"); } else { printf("mem, lock has been init"); } } __ attribute__((destructor)) static void late_destory(void) { struct shmid_ds shminfo; //共享内存有引用计数,所以多次写在so调用释放共享内存时,只有最后计数为0时才会真正释放 shmctl(tPtr->FLAG, IPC_RMID,NULL); shmctl(tPtr->FLAG, IPC_STAT,&shminfo); //虽然tPtr不能被进程共享,但是每个进程的SO呗加载时都会重新更新tPtr的值,所以可放心使用 //当内存引用计数变为0时(实际测试是1),代表so不被使用,可销毁锁 if(shminfo.shm_nattch == 1) { pthread_mutex_destory(&(tPtr->lock)); pthread_mutexattr_destory(&(tPtr->lock_attr)); } }
有了保护机制,可以再写一个接口,接口假设不可重入:
void Asyncprint() { pthread_mutex_lock(&(mp->lock)); printf("now you can call me"); sleep(10); printf("all me finish!"); pthread_mutex_unlock(&(mp->lock)); }
以上函数模拟的场景是:Asyncprint执行一次需要10s,中间不允许重入,so编译方法:
gcc lock.c -fPIC -shared -o liblock.so
可以同时跑两个相同进程调用该so的接口,看看接口是否互斥调用
//main.c //gcc main.c -L. -llock -lpthread -o 1.exe //gcc main.c -L. -llock -lpthread -o 2.exe //可同时使用ipcs -m c查看共享内存信息 #include <stdio.h> #include "lock.h" int main() { Asyncprint(); }
同时运行1.exe 2.exe看打印:
完整源码:
//main.c //gcc lock.c -fPIC -shared -o liblock.so 编译动态库 //gcc main.c -L. -llock -lpthread -o 1.out 编译可执行程序1 //gcc main.c -L. -llock -lpthread -o 2.out 编译可执行程序2 //ipcs -m shell中查看当前shm情况 #include <stdio.h> #include <sys/shm.h> #include "lock.h" int main() { //方式1 可执行程序自己调用函数分配共享内存,创建互斥锁 //此时需删除lock.c中的constructor与desstructor函数 /*mutex_info_t * mp = create_mutex_package(111); shmctl(557060, IPC_RMID, NULL) asyncprint(); destory_mutex_package(mp); */ //方式2 内存与互斥锁均由so加载时,其自己的constructor与desstructor函数负责创建与销毁 asyncprint(); return 0; }
//lock.h #ifndef __LOCK_H_ #define __LOCK_H_ #include <pthread.h> typedef struct MUTEX_PACKAGE { pthread_mutex_t lock; pthread_mutexattr_t lock_attr; int FLAG; } mutex_info_t ; extern const void asyncprint(); #endif
lock.c //gcc lock.c -fPIC -shared -o liblock.so #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <assert.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include "lock.h" mutex_info_t * mp = NULL; int FLAG =0; /** * 返回一片共享内存标识符,用于后续获取该共享内存,以及销毁该共享内存 * INDEX_OF_KEY —— 自定义的该共享内存序号 * LENGTH —— 共享内存大小 */ const int create_sharemem(const int INDEX_OF_KEY, const unsigned int LENGTH) { // 生成key const char* FILE_PATH = "./"; key_t key = ftok(FILE_PATH, INDEX_OF_KEY); // 创建共享内存空间 const int FLAG = shmget(key, LENGTH, IPC_CREAT | 0666); return FLAG; } // 初始化进程锁结构体 const int init_mutex(void* pthis) { mutex_info_t * mp = (mutex_info_t *)pthis; // 初始化锁状态,设置状态状态为——进程共享 pthread_mutexattr_init(&(mp->lock_attr)); pthread_mutexattr_setpshared(&(mp->lock_attr), PTHREAD_PROCESS_SHARED); // 用锁状态来初始化锁 pthread_mutex_init(&(mp->lock), &(mp->lock_attr)); return 0; } __ attribute__((constructor)) static void pre_init(void) { int flag = create_sharemem(127, sizeof(mutex_info_t )); //NULL参数代表由操作系统选择共享内存中的合适位置返回给内存申请者,测试发现由于共享内存一共就只有mutex_info_t 大小, //所以每次返回的地址相同 这也能保证so被多个进程加载使用的是同一把锁 tPtr = (mutex_info_t *)shmat(FLAG, NULL, SHM_R | SHM_W); //内存内结构体的FLAG如果不是共享内存的索引,那么表示是第一次申请内存,需要对锁初始化 //避免多次加载so时多次init锁 if(tPtr->FLAG != flag) { tPtr->FLAG == flag; init_mutex(tPtr); printf("first make mem, init lock"); } else { printf("mem, lock has been init"); } } __ attribute__((destructor)) static void late_destory(void) { struct shmid_ds shminfo; //共享内存有引用计数,所以多次写在so调用释放共享内存时,只有最后计数为0时才会真正释放 shmctl(tPtr->FLAG, IPC_RMID,NULL); shmctl(tPtr->FLAG, IPC_STAT,&shminfo); //虽然tPtr不能被进程共享,但是每个进程的SO呗加载时都会重新更新tPtr的值,所以可放心使用 //当内存引用计数变为0时(实际测试是1),代表so不被使用,可销毁锁 if(shminfo.shm_nattch == 1) { pthread_mutex_destory(&(tPtr->lock)); pthread_mutexattr_destory(&(tPtr->lock_attr)); } }
原文链接:https://blog.csdn.net/ludashei2/article/details/115061988
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。