動態庫中的函數實現互斥調用
(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
*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。
模擬電路文章專題:模擬電路基礎