博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 動(dòng)態(tài)庫(kù)中的函數(shù)實(shí)現(xiàn)互斥調(diào)用

動(dòng)態(tài)庫(kù)中的函數(shù)實(shí)現(xiàn)互斥調(diào)用

發(fā)布人:電子禪石 時(shí)間:2021-12-09 來(lái)源:工程師 發(fā)布文章

(1條消息) 動(dòng)態(tài)庫(kù)中的函數(shù)實(shí)現(xiàn)互斥調(diào)用_盧平光的博客-CSDN博客_函數(shù)互斥


一直在糾結(jié)一個(gè)問(wèn)題:


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


首先明確下前提:


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

也就是說(shuō),想要在so內(nèi)實(shí)現(xiàn)一個(gè)不可重入的函數(shù)還是比較困難的,因?yàn)樗凶兞慷际仟?dú)立的,但是考慮如下場(chǎng)景:驅(qū)動(dòng)層給了一個(gè)視頻碼流錄制的接口,并且沒(méi)有在驅(qū)動(dòng)層做互斥,但實(shí)際上這個(gè)接口同一時(shí)間只可能被一個(gè)進(jìn)程調(diào)用,那么很明顯,串接到so中的接口必須實(shí)現(xiàn)該接口的原子調(diào)用。


解決思路:


由so創(chuàng)建一塊共享內(nèi)存,放置一份進(jìn)程共享的互斥鎖

第一步的動(dòng)作需要尋找一個(gè)合適點(diǎn)自動(dòng)完成,比如so加載時(shí)

各進(jìn)程在調(diào)用so接口時(shí),接口內(nèi)部使用該共享鎖完成互斥調(diào)用

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

1、互斥鎖共享

so中的互斥鎖屬于私有數(shù)據(jù),加載該so的進(jìn)程都會(huì)拷貝一份,那么就無(wú)法在進(jìn)程間共享使用該鎖。所以必須將鎖放到進(jìn)程間的共享內(nèi)存中,確保鎖只有一把。首先定義一個(gè)鎖結(jié)構(gòu)體

// 定義進(jìn)程鎖結(jié)構(gòu)體
typedef struct Mutex_Info {
    // 鎖以及狀態(tài)
    pthread_mutex_t lock;
    pthread_mutexattr_t lock_attr;
    // 在共享內(nèi)存中的標(biāo)識(shí)符
    int FLAG;
} mutex_info_t;

要想把鎖放到共享內(nèi)存,那么先創(chuàng)建一塊內(nèi)存

/**
 *  返回一片共享內(nèi)存標(biāo)識(shí)符,用于后續(xù)獲取該共享內(nèi)存,以及銷(xiāo)毀該共享內(nèi)存
 *  INDEX_OF_KEY —— 自定義的該共享內(nèi)存序號(hào)
 *  LENGTH —— 共享內(nèi)存大小
 */
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);

    // 創(chuàng)建共享內(nèi)存空間 
    //多個(gè)進(jìn)程調(diào)用該函數(shù),只要確保KEY相同,那么只會(huì)創(chuàng)建同一塊內(nèi)存
    const int FLAG = shmget(key, LENGTH, IPC_CREAT | 0666);
    return FLAG;
}

其中的init函數(shù)是鎖的初始化,注意需要設(shè)置其進(jìn)程間共享屬性

const int init_mutex(void* pthis) {
    mutex_info_t* mp = (mutex_info_t*)pthis;
    // 初始化鎖狀態(tài),設(shè)置狀態(tài)狀態(tài)為——進(jìn)程共享
    pthread_mutexattr_init(&(mp->lock_attr));
    pthread_mutexattr_setpshared(&(mp->lock_attr), PTHREAD_PROCESS_SHARED);
    // 用鎖狀態(tài)來(lái)初始化鎖
    pthread_mutex_init(&(mp->lock), &(mp->lock_attr));
    return 0;
}

有了以上兩個(gè)函數(shù),其實(shí)就可以寫(xiě)一個(gè)so被加載時(shí)自動(dòng)執(zhí)行的初始化函數(shù),這樣可以保證so的使用者不必關(guān)心內(nèi)存、鎖的創(chuàng)建、銷(xiāo)毀和使用。所以先插講下so加載時(shí)自動(dòng)執(zhí)行函數(shù)的方法。


2、so加載時(shí)自動(dòng)執(zhí)行函數(shù)



GNU C 的一大特色就是__attribute__ 機(jī)制。attribute 是一個(gè)編譯指令,可以設(shè)置函數(shù)屬性(Function Attribute )、變量屬性(Variable Attribute )和類(lèi)型屬性(Type Attribute )。

attribute 書(shū)寫(xiě)特征是:attribute 前后都有兩個(gè)下劃線(xiàn),并切后面會(huì)緊跟一對(duì)原括弧,括弧里面是相應(yīng)的__attribute__ 參數(shù)。

若將某一函數(shù)的聲明中添加 __ attribute__((constructor)) 屬性,那么它具有兩種運(yùn)行時(shí)機(jī)


若函數(shù)所在源文件被編譯為可執(zhí)行文件,那么該函數(shù)可以在main函數(shù)執(zhí)行前被調(diào)用

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

#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;
}

 注意正常退出才會(huì)自動(dòng)執(zhí)行destructor 的修飾的函數(shù)。


  • 若函數(shù)所在源文件被編譯為共享庫(kù),那么該函數(shù)可以在共享庫(kù)被其它進(jìn)程顯式dlopen或者隱式由操作系統(tǒng)加載時(shí)都會(huì)優(yōu)先執(zhí)行

  • //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))的這一特性可以被用在許多場(chǎng)合。比如某個(gè)so的功能實(shí)現(xiàn)需要預(yù)先映射一塊共享內(nèi)存,如果要求所有使用該so的進(jìn)程在加載時(shí)手動(dòng)去做這一步驟是非常不合理的,此時(shí)可以將內(nèi)存映射實(shí)現(xiàn)放在__ attribute__((constructor))屬性聲明的函數(shù)中,這樣每次so被加載就會(huì)自動(dòng)完成共享內(nèi)存的創(chuàng)建映射,對(duì)so的使用者完全透明,確實(shí)夠巧妙!

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

3、so加載時(shí)自動(dòng)創(chuàng)建共享內(nèi)存與互斥鎖

//全局變量
mutex_info_t tPtr = NULL;
__ attribute__((constructor)) static void pre_init(void)
{
      int flag = create_sharemem(127, sizeof(mutex_info_t ));
      //NULL參數(shù)代表由操作系統(tǒng)選擇共享內(nèi)存中的合適位置返回給內(nèi)存申請(qǐng)者,測(cè)試發(fā)現(xiàn)由于共享內(nèi)存一共就只有mutex_info_t 大小,
      //所以每次返回的地址相同 這也能保證so被多個(gè)進(jìn)程加載使用的是同一把鎖
      tPtr = (mutex_info_t *)shmat(FLAG, NULL, SHM_R | SHM_W);
      //內(nèi)存內(nèi)結(jié)構(gòu)體的FLAG如果不是共享內(nèi)存的索引,那么表示是第一次申請(qǐng)內(nèi)存,需要對(duì)鎖初始化
      //避免多次加載so時(shí)多次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;
      //共享內(nèi)存有引用計(jì)數(shù),所以多次寫(xiě)在so調(diào)用釋放共享內(nèi)存時(shí),只有最后計(jì)數(shù)為0時(shí)才會(huì)真正釋放
	  shmctl(tPtr->FLAG, IPC_RMID,NULL);
      shmctl(tPtr->FLAG, IPC_STAT,&shminfo);
      //雖然tPtr不能被進(jìn)程共享,但是每個(gè)進(jìn)程的SO唄加載時(shí)都會(huì)重新更新tPtr的值,所以可放心使用
      //當(dāng)內(nèi)存引用計(jì)數(shù)變?yōu)?時(shí)(實(shí)際測(cè)試是1),代表so不被使用,可銷(xiāo)毀鎖
      if(shminfo.shm_nattch == 1)
      {
            pthread_mutex_destory(&(tPtr->lock));
            pthread_mutexattr_destory(&(tPtr->lock_attr));
      }
}

有了保護(hù)機(jī)制,可以再寫(xiě)一個(gè)接口,接口假設(shè)不可重入:

void Asyncprint()
{
	pthread_mutex_lock(&(mp->lock));
	printf("now you can call me");
	sleep(10);
	printf("all me finish!");
	pthread_mutex_unlock(&(mp->lock));
}

以上函數(shù)模擬的場(chǎng)景是:Asyncprint執(zhí)行一次需要10s,中間不允許重入,so編譯方法:

gcc lock.c -fPIC -shared -o liblock.so

可以同時(shí)跑兩個(gè)相同進(jìn)程調(diào)用該so的接口,看看接口是否互斥調(diào)用

//main.c
//gcc main.c -L. -llock -lpthread -o 1.exe
//gcc main.c -L. -llock -lpthread -o 2.exe
//可同時(shí)使用ipcs -m c查看共享內(nèi)存信息
#include <stdio.h>
#include "lock.h"
int main()
{
   Asyncprint();
}

同時(shí)運(yùn)行1.exe 2.exe看打?。?/span>

在這里插入圖片描述

完整源碼:

//main.c 
//gcc lock.c -fPIC -shared -o liblock.so    編譯動(dòng)態(tài)庫(kù)
//gcc main.c -L.  -llock  -lpthread -o 1.out 編譯可執(zhí)行程序1
//gcc main.c -L.  -llock  -lpthread -o 2.out 編譯可執(zhí)行程序2
//ipcs -m shell中查看當(dāng)前shm情況
#include <stdio.h>
#include <sys/shm.h>
#include "lock.h"
int main() {

    //方式1 可執(zhí)行程序自己調(diào)用函數(shù)分配共享內(nèi)存,創(chuàng)建互斥鎖
	//此時(shí)需刪除lock.c中的constructor與desstructor函數(shù)
    /*mutex_info_t * mp = create_mutex_package(111);
    shmctl(557060, IPC_RMID, NULL)
    asyncprint();
    destory_mutex_package(mp); 
	*/
	//方式2 內(nèi)存與互斥鎖均由so加載時(shí),其自己的constructor與desstructor函數(shù)負(fù)責(zé)創(chuàng)建與銷(xiāo)毀
	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;
/**
 *  返回一片共享內(nèi)存標(biāo)識(shí)符,用于后續(xù)獲取該共享內(nèi)存,以及銷(xiāo)毀該共享內(nèi)存
 *  INDEX_OF_KEY —— 自定義的該共享內(nèi)存序號(hào)
 *  LENGTH —— 共享內(nèi)存大小
 */
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);

    // 創(chuàng)建共享內(nèi)存空間
    const int FLAG = shmget(key, LENGTH, IPC_CREAT | 0666);

    return FLAG;
}



// 初始化進(jìn)程鎖結(jié)構(gòu)體
const int init_mutex(void* pthis) {
    mutex_info_t * mp = (mutex_info_t *)pthis;
    // 初始化鎖狀態(tài),設(shè)置狀態(tài)狀態(tài)為——進(jìn)程共享
    pthread_mutexattr_init(&(mp->lock_attr));
    pthread_mutexattr_setpshared(&(mp->lock_attr), PTHREAD_PROCESS_SHARED);
    // 用鎖狀態(tài)來(lái)初始化鎖
    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參數(shù)代表由操作系統(tǒng)選擇共享內(nèi)存中的合適位置返回給內(nèi)存申請(qǐng)者,測(cè)試發(fā)現(xiàn)由于共享內(nèi)存一共就只有mutex_info_t 大小,
      //所以每次返回的地址相同 這也能保證so被多個(gè)進(jìn)程加載使用的是同一把鎖
      tPtr = (mutex_info_t *)shmat(FLAG, NULL, SHM_R | SHM_W);
      //內(nèi)存內(nèi)結(jié)構(gòu)體的FLAG如果不是共享內(nèi)存的索引,那么表示是第一次申請(qǐng)內(nèi)存,需要對(duì)鎖初始化
      //避免多次加載so時(shí)多次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;
      //共享內(nèi)存有引用計(jì)數(shù),所以多次寫(xiě)在so調(diào)用釋放共享內(nèi)存時(shí),只有最后計(jì)數(shù)為0時(shí)才會(huì)真正釋放
	  shmctl(tPtr->FLAG, IPC_RMID,NULL);
      shmctl(tPtr->FLAG, IPC_STAT,&shminfo);
      //雖然tPtr不能被進(jìn)程共享,但是每個(gè)進(jìn)程的SO唄加載時(shí)都會(huì)重新更新tPtr的值,所以可放心使用
      //當(dāng)內(nèi)存引用計(jì)數(shù)變?yōu)?時(shí)(實(shí)際測(cè)試是1),代表so不被使用,可銷(xiāo)毀鎖
      if(shminfo.shm_nattch == 1)
      {
            pthread_mutex_destory(&(tPtr->lock));
            pthread_mutexattr_destory(&(tPtr->lock_attr));
      }
}



原文鏈接:https://blog.csdn.net/ludashei2/article/details/115061988


*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。

模擬電路文章專(zhuān)題:模擬電路基礎(chǔ)


關(guān)鍵詞: linux

相關(guān)推薦

技術(shù)專(zhuān)區(qū)

關(guān)閉