博客專欄

        EEPW首頁 > 博客 > 動態庫中的函數實現互斥調用

        動態庫中的函數實現互斥調用

        發布人:電子禪石 時間:2021-12-09 來源:工程師 發布文章

        (1條消息) 動態庫中的函數實現互斥調用_盧平光的博客-CSDN博客_函數互斥


        一直在糾結一個問題:


        如果一個函數使用互斥鎖可以防止被調用時重入的情況,但是如果該函數以so的形式提供給使用者(其它進程),那么如何做到各進程間對于該函數的互斥調用呢?


        首先明確下前提:


        so被進程加載時,代碼段共享,但是所有變量(局部、全局、靜態變量)都是各進程copy一份私有使用。

        也就是說,想要在so內實現一個不可重入的函數還是比較困難的,因為所有變量都是獨立的,但是考慮如下場景:驅動層給了一個視頻碼流錄制的接口,并且沒有在驅動層做互斥,但實際上這個接口同一時間只可能被一個進程調用,那么很明顯,串接到so中的接口必須實現該接口的原子調用。


        解決思路:


        由so創建一塊共享內存,放置一份進程共享的互斥鎖

        第一步的動作需要尋找一個合適點自動完成,比如so加載時

        各進程在調用so接口時,接口內部使用該共享鎖完成互斥調用

        ————————————————

        1、互斥鎖共享

        so中的互斥鎖屬于私有數據,加載該so的進程都會拷貝一份,那么就無法在進程間共享使用該鎖。所以必須將鎖放到進程間的共享內存中,確保鎖只有一把。首先定義一個鎖結構體

        // 定義進程鎖結構體
        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


        *博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。

        模擬電路文章專題:模擬電路基礎


        關鍵詞: linux

        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 通化县| 铁力市| 柯坪县| 东乡族自治县| 临沂市| 宁安市| 綦江县| 龙口市| 福泉市| 宁武县| 封开县| 黔江区| 泾阳县| 永和县| 隆化县| 司法| 龙南县| 岳阳县| 新密市| 阜宁县| 邢台县| 邯郸县| 盐边县| 福清市| 仙游县| 娱乐| 通榆县| 永修县| 安义县| 福海县| 十堰市| 沿河| 仙桃市| 楚雄市| 黔西| 湘阴县| 清新县| 榕江县| 凌海市| 怀远县| 邛崃市|