动态链接库中的全局变量

想给自己之前的日志库,加一个染色的功能

gcc 有一个自己的特性,叫 __thread,介绍可以看这里,https://gcc.gnu.org/onlinedocs…,是可以用来修饰一个全局变量,从而让这个全局变量在每个线程都有一份拷贝,例子如下:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>

__thread int n = 0;

int ns[] = {1, 2};

void* thread_func(void *arg) {
    int tid = (int)syscall(__NR_gettid);
    printf("1, tid = %d, n = %d\n", tid, n);
    n = *((int*)arg);
    printf("2, tid = %d, n = %d\n", tid, n);
    while (1) {
        printf("3, tid = %d, n = %d\n", tid, n);
        sleep(1);
    }
}

int main() {
    for (int i = 0; i < sizeof(ns)/sizeof(ns[0]); i++) {        
        pthread_t tid;
        pthread_create(&tid, NULL, thread_func, &ns[i]);
        printf("thread %d created\n", tid);
    }
    while (1) {
        sleep(1);
    }
    return 0;
}

跑起来之后的输出是这样的

thread 1082132800 created
thread 1090525504 created
1, tid = 11508, n = 0
2, tid = 11508, n = 1
3, tid = 11508, n = 1
1, tid = 11509, n = 0
2, tid = 11509, n = 2
3, tid = 11509, n = 2
3, tid = 11508, n = 1
3, tid = 11509, n = 2
3, tid = 11508, n = 1
3, tid = 11509, n = 2
3, tid = 11508, n = 1
3, tid = 11509, n = 2

用在日志库中,成为这个样子

#include <net/if.h>
#include <arpa/inet.h>
#include "logger.h"

__thread int m_iLoggerLogTraceOn;// 是否开启染色日志

int Logger::Init(eLogLevel level, const char* pLogFileDir, const char* pLogFileBaseName, uint32_t uiLogFileRollingSize, uint32_t uiMaxCharPerLine, uint32_t uiLogBufferLines)
{
    pthread_mutex_lock(&m_stInitLock);
    if (m_bInited || level == LOG_LEVEL_OFF)
    {
        pthread_mutex_unlock(&m_stInitLock);
        return 0;
    }

然后加了一个修改的接口

void Logger::setLogTraceOn(int iTraceOn)
{
    printf("set trace switch to %d\n", iTraceOn);
    m_iLoggerLogTraceOn = iTraceOn;
    printf("after set, iLoggerLogTraceOn(%p) is %d, tid is %d\n", &m_iLoggerLogTraceOn, m_iLoggerLogTraceOn, (int)syscall(__NR_gettid));
}

在各个函数处也加了跟踪

void Logger::LogToMem(eLogLevel level, const char* format, ...)
{
    printf("trace on(%p) : %d, tid is %d\n", &m_iLoggerLogTraceOn, m_iLoggerLogTraceOn, (int)syscall(__NR_gettid));
    if (m_bInited && (level >= m_eLevel || m_iLoggerLogTraceOn))
    {
        va_list args;
        va_start(args, format);            
        char *pWritePtr = m_pLogBufferMem+((__sync_add_and_fetch(&m_uiWriteLineCount, 1) % m_uiLogBufferLines) * m_uiMaxCharPerLine);
        vsnprintf(pWritePtr+1, m_uiMaxCharPerLine-2, format, args); 
        *(pWritePtr) = 1;
        va_end(args);
    }        
}
void Logger::LogToStdout(eLogLevel level, const char* format, ...)
{    
    printf("trace on(%p) : %d, tid is %d\n", &m_iLoggerLogTraceOn, m_iLoggerLogTraceOn, (int)syscall(__NR_gettid));
    if (m_bInited && (level >= m_eLevel || m_iLoggerLogTraceOn))
    {
        va_list args;
        va_start(args, format);
        vprintf(format, args);
        va_end(args);
    }        
}

然后在业务中这么使用

    if (pSession->strAppUser == "BAAEB881F489FC59")
    {
        Logger::setLogTraceOn(1);
        LOG_INFO("appuser match, turn logger trace on");
    }

然而,跑起来之后是这样的

set trace switch to 1
after set, iLoggerLogTraceOn(0x797bf0) is 1, tid is 9696
[2015-03-26 14:28:20] [INFO][            OnRequestData@          src/BusiObj.cpp,0136]  appuser match, turn logger trace on
trace on(0x43b0693c) : 0, tid is 9696
trace on(0x43b0693c) : 0, tid is 9696
trace on(0x43b0693c) : 0, tid is 9696
trace on(0x43b0693c) : 0, tid is 9696
trace on(0x43b0693c) : 0, tid is 9696

注意到一个神奇的现象,同一个变量,在同一个线程里面,指针不一样了

考虑到之前做函数耗时测试,和代码覆盖度测试的时候,遇到过由于进程关系复杂(又多进程又线程的)引起的问题,考虑是否由于这个原因引起的问题,于是写了这么一个测试

来看这个:

t2.cpp

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

__thread int n = 0;

extern "C" {
    void busi(int arg) {    
        printf("1, tid = %d, n = %d\n", (int)syscall(__NR_gettid), n);
        n = arg;
        printf("2, tid = %d, n = %d\n", (int)syscall(__NR_gettid), n);
        while (1) {
            printf("3, tid = %d, n = %d\n", (int)syscall(__NR_gettid), n);
            sleep(1);
        }
    }
}

f1.cpp

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <unistd.h>

int ns[] = {1, 2};

typedef void(*busi_func_ptr_t)(int);
busi_func_ptr_t busi_func_ptr ;

void* thread_func(void *args) {
   int arg = *((int*)args); 
   printf("0, arg = %d\n", arg);
   (*busi_func_ptr)(arg);
}

int main() {    
    int fork_ret = fork();
    if (fork_ret == 0) {
        void* dll = dlopen("./t2.so", RTLD_LAZY);
        if (dll == NULL) {
            printf("%s\n", dlerror());
            abort();
        }
        busi_func_ptr = (busi_func_ptr_t)dlsym(dll, "busi");
        char *err = dlerror();
        if (err != NULL) {
            printf("%s\n", err);
            abort();
        }
        for (int i = 0; i < sizeof(ns)/sizeof(ns[0]); i++) {
            pthread_t tid;
            pthread_create(&tid, NULL, thread_func, &ns[i]);
            printf("thread %d created\n", tid);
        }
        while (1) {
            sleep(1);
        }
        dlclose(dll);
    } else {
        printf("chid proc %d\n", fork_ret);
        while (1) {
            sleep(1);
        }
    }
    return 0;
}

makefile

all:
	g++ t2.cpp -O3 -shared -o t2.so -fPIC
	g++ f1.cpp -O3 -o f1 -ldl -pthread	

makefile 带 O3 是为了跟实际情况保持一致

运行输出如下

thread 1082132800 created
chid proc 12856
thread 1090525504 created
0, arg = 1
1, tid = 12857, n = 0
2, tid = 12857, n = 1
3, tid = 12857, n = 1
0, arg = 2
1, tid = 12858, n = 0
2, tid = 12858, n = 2
3, tid = 12858, n = 2
3, tid = 12857, n = 1
3, tid = 12858, n = 2
3, tid = 12857, n = 1
3, tid = 12858, n = 2

看起来是没有问题的,又考虑到,我们的日志是先编译成一个静态的 lib.a 的库,然后在编译到 lib.so 的动态库中,最后由框架加载运行的,于是试了一下这么搞

我写了一个 test.cpp


public:

	//与tcpd的接口
	char * OnRequestData(const char * p_pcResData,
		                 int p_iResDataLen,
		                 int& p_iRetDataLen,
		                 int& p_iDataBufLen, 
		                 const char *p_pcHostIP, 
		                 const char * p_pcBusiBuf,
		                 int& piProcessedDataLen, int fd)
    {
        Logger::setLogTraceOn(1);
        printf("on request123\n");
        LOG_DEBUG("on request123");
        LOG_ERR("on request123");
        Logger::setLogTraceOn(0);
        printf("on request456\n");
        LOG_DEBUG("on request456");
        LOG_ERR("on request456");
        Logger::setLogTraceOn(1);
        printf("on request789\n");
        LOG_DEBUG("on request789");
        LOG_ERR("on request789");
        Logger::setLogTraceOn(0);
        return NULL;
    }
	
	int    OnDllDataUpdate(const char * p_pcDataBuf, int p_iDataBufLen) {}
	bool   FreeMemBlock(char * p_pcMemBlock, int p_iMemBlockLen) {
        return true;
    }
	bool   InitDllWithCfgFile(const char *p_pcCfgFileName) {
        printf("%s\n", p_pcCfgFileName);
        Logger::Init(Logger::LOG_LEVEL_INFO, "./", "stdout");
        Logger::setLogTraceOn(1);
    }

然后把含有日志库的通用组件的静态库和这个 test.cpp 放在同一个目录中,编出一个动态库

g++ test.cpp libcommontool.a -shared -o lib.so -fPIC

然后用 tcpd 去加载这个动态库

这个是 Init 函数打出来的

CALL DLL INIT INTERFACE
./main.cfg
set trace switch to 1
after set, iLoggerLogTraceOn(0x51be60) is 1, tid is 15719

这个是 OnRquest 函数打出来的

set trace switch to 1
after set, iLoggerLogTraceOn(0x52f280) is 1, tid is 15721
on request123
trace on(0x4090193c) : 0, tid is 15721
trace on(0x4090193c) : 0, tid is 15721
[2015-03-26 14:49:15][ERROR][            OnRequestData@                 test.cpp,0026]  on request123
set trace switch to 0
after set, iLoggerLogTraceOn(0x52f280) is 0, tid is 15721
on request456
trace on(0x4090193c) : 0, tid is 15721
trace on(0x4090193c) : 0, tid is 15721
[2015-03-26 14:49:15][ERROR][            OnRequestData@                 test.cpp,0030]  on request456
set trace switch to 1
after set, iLoggerLogTraceOn(0x52f280) is 1, tid is 15721
on request789
trace on(0x4090193c) : 0, tid is 15721
trace on(0x4090193c) : 0, tid is 15721
[2015-03-26 14:49:15][ERROR][            OnRequestData@                 test.cpp,0034]  on request789
set trace switch to 0
after set, iLoggerLogTraceOn(0x52f280) is 0, tid is 15721

于是到此就陷入了僵局,查资料说 gcc 在编译动态链接库的时候对全局变量的处理有一些特殊,看到这里,http://blog.csdn.net/zxh821112/article/details/8969541,还有,http://blog.csdn.net/yuyin86/article/details/10239479,http://bbs.chinaunix.net/thread-1620536-1-1.html,和这里,https://www.owent.net/2014/01/cpp%E5%8F%88%E4%B8%80%E5%9D%91-%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E4%B8%AD%E7%9A%84%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F.html,不过这些帖子在讨论,主要是集中在一个动态库被多个进程引用的时候的赋值问题,跟我们这个场景还不太一样

——————

2015-3-26 20:34:46 update http://lists.freebsd.org/piper… 这里有一篇,之前没发现

———————

2015-3-27 11:33:43 update 最后确认是编译器的问题,生产环境的编译器是 4.1.2 的老古董,换一个 gcc 4.8.2 问题就烟消云散了

Leave a Reply

Your email address will not be published. Required fields are marked *