TDengine代碼學習(3):定時器實現

代碼介紹

學習TDengine ttimer.c 中的代碼。
首先看下用到的數據結構

#define MSECONDS_PER_TICK 5

typedef struct _tmr_ctrl_t {
  void *          signature;
  pthread_mutex_t mutex;            /* mutex to protect critical resource */
  int             resolution;       /* resolution in mseconds */
  int             numOfPeriods;     /* total number of periods */
  int64_t         periodsFromStart; /* count number of periods since start */
  pthread_t       thread;           /* timer thread ID */
  tmr_list_t *    tmrList;
  mpool_h         poolHandle;
  char            label[12];
  int             maxNumOfTmrs;
  int             numOfTmrs;
  int             ticks;
  int             maxTicks;
  int             tmrCtrlId;
} tmr_ctrl_t;

tmr_ctrl_t 這是定時器控制類,設置這類定時器的共有屬性,主要是調度粒度(resolution) 和 時間片數目(numOfPeriods)。

  • signature: signature 用來判斷這個定時器控制類是否被創建。
  • resolution:定時器的調度粒度,表示多少毫秒會查詢一次,單位爲毫秒(millisecond)。
  • numOfPeriods:時間片的數目,每個時間片的值爲resolution。
  • periodsFromStart:當前所在的時間片,用於獲取當前到期的時間片index,在創建新定時器用來計算超時時間。
  • tmrList: 創建一個類型爲tmr_list_t,大小爲 numOfPeriods 的數組。
  • poolHandle:內存池,用來獲取新的定時器類tmr_obj_t 內存塊。
  • ticks:當前時間片已過去的tick數,一個tick時間默認設爲 MSECONDS_PER_TICK 5毫秒
  • maxTicks:值爲 resolution / MSECONDS_PER_TICK,表示一個時間片需要幾個tick時間。
typedef struct {
  tmr_obj_t *head;
  int        count;
} tmr_list_t;

tmr_list_t 中head 是一個鏈表頭指針,count 保存鏈表中項的數目,鏈表中的項類型是tmr_obj_t。

typedef struct _tmr_obj {
  void *param1;
  void (*fp)(void *, void *);
  tmr_h               timerId;
  short               cycle;
  struct _tmr_obj *   prev;
  struct _tmr_obj *   next;
  int                 index;
  struct _tmr_ctrl_t *pCtrl;
} tmr_obj_t;

tmr_obj_t 就是定時器類,每次創建一個定時器,會按照設置的超時時間,計算出在tmrList 數組中的index,然後加入到tmrList[index]對應的鏈表中。

  • fp:定時器到期時調用的函數指針
  • param1: 定時器到期時調用函數的參數
  • cycle:表示要經歷多少侖後纔會到期,這個後面會說明。
  • prev,next:實現雙向鏈表的指針
  • index:在tmrList 數組中的index

定時器調度

每間隔一個tick時間,定時器的調度線程就會調用下面的taosTimerLoopFunc函數,會遍歷所有的定時器控制類,根據signature 值判斷定時器控制類是否已經被創建,當 “pCtrl->ticks >= pCtrl->maxTicks” 時表示當前時間片已到期,需要調用taosTmrProcessList函數處理該時間片鏈表上的定時器。

#define maxNumOfTmrCtrl 512
tmr_ctrl_t tmrCtrl[maxNumOfTmrCtrl];

void *taosTimerLoopFunc(int signo) {
  tmr_ctrl_t *pCtrl;
  int         count = 0;

  for (int i = 1; i < maxNumOfTmrCtrl; ++i) {
    pCtrl = tmrCtrl + i;
    if (pCtrl->signature) {
      count++;
      pCtrl->ticks++;
      if (pCtrl->ticks >= pCtrl->maxTicks) {
        taosTmrProcessList(pCtrl);
        pCtrl->ticks = 0;
      }
      if (count >= numOfTmrCtrl) break;
    }
  }

  return NULL;
}

先計算出當前時間片的index 爲 “pCtrl->periodsFromStart % pCtrl->numOfPeriods”,獲取鏈表 pCtrl->tmrList[index],然後遍歷鏈表:

  • 如果當前節點的cycle值>0,表示該定時器要在下一輪纔到期,這邊只將cycle值減一。因爲在定時器插入鏈表時,是按照cycle值從小到大連接,所以該節點後面的節點cycle值肯定大於0,也將cycle值減一。
  • 如果當前節點的cycle值爲0,表示該定時器到期了,將該定時器節點從鏈表中刪除,釋放內存。

最後將periodsFromStart值加一,指向下一個時間片。
這裏有個小疑問:爲什麼不將periodsFromStart 值每次加一時 直接取numOfPeriods的餘數,而要將periodsFromStart 定義爲 int64 類型?

void taosTmrProcessList(tmr_ctrl_t *pCtrl) {
  unsigned int index;
  tmr_list_t * pList;
  tmr_obj_t *  pObj, *header;

  pthread_mutex_lock(&pCtrl->mutex);
  index = pCtrl->periodsFromStart % pCtrl->numOfPeriods;
  pList = &pCtrl->tmrList[index];

  while (1) {
    header = pList->head;
    if (header == NULL) break;

    if (header->cycle > 0) {
      pObj = header;
      while (pObj) {
        pObj->cycle--;
        pObj = pObj->next;
      }
      break;
    }

    pCtrl->numOfTmrs--;
    tmrTrace("%s %p, timer expired, fp:%p, tmr_h:%p, index:%d, total:%d", pCtrl->label, header->param1, header->fp,
             header, index, pCtrl->numOfTmrs);

    pList->head = header->next;
    if (header->next) header->next->prev = NULL;
    pList->count--;
    header->timerId = NULL;
    //ignore timer function processing code
    tmrMemPoolFree(pCtrl->poolHandle, (char *)header);
  }
  pCtrl->periodsFromStart++;
  pthread_mutex_unlock(&pCtrl->mutex);
}

定時器結構示例

如果定時器的超時時間爲 mseconds, 則對應的cycle 和 index 計算公式如下:

period = mseconds / resolution
cycle = period / numOfPeriods
index = (period + periodsFromStart) % numOfPeriods

假設創建的定時器控制類的resolution 設爲100毫秒,時間片數目numOfPeriods設爲5個,則遍歷一輪時間片的時間爲500毫秒,初始化時periodsFromStart值爲0, ticks值也爲0
這個時候,我們創建三個定時器

  • 定時器1的超時時間mseconds爲100毫秒,則period值爲1,cycle值爲0,index值爲1
  • 定時器2的超時時間mseconds爲600毫秒,則period值爲6,cycle值爲1,index值爲1
  • 定時器3的超時時間mseconds爲800毫秒,則period值爲8,cycle值爲1,index值爲3

這個時候的定時器狀態如圖1所示:
periodsFromStart值爲0
可以看到定時器1和定時器2 都在tmrList[1]所指向的鏈表中,而且按照cycle值由小到大。
定時器3 在tmrList[3]所指向的鏈表中
圖1 timer1
時間過去200毫秒,這個時候定時器控制類剛好過去兩個時間片,定時器狀態如圖2所示
periodsFromStart值爲2
時間片index 爲1的鏈表已經被處理過,定時器1已經超時,從鏈表中刪除,定時器2的cycle值減一。
圖2 timer2

定時器精度誤差

在創建一個定時器的時候,計算公式只考慮當前所在的時間片index periodsFromStart,而沒有考慮當前時間片已經過去的ticks 值,在調度線程中也沒有考慮這點,所以定時器的實際超時時間會有一個 (0,resolution] 範圍的誤差。
比如上面的例子,假設時間控制類resolution 爲100毫秒,目前的 periodsFromStart值爲0, 新建一個定時器的超時時間mseconds爲100毫秒,則所在時間片的
index 爲1。

  • 如果此時的ticks值是0,則需要經過200毫秒這個定時器會超時,有resolution(100毫秒)的誤差。
  • 如果此時的ticks值是10(即這個時間片時間已過去50毫秒),則需要經過150毫秒這個定時器會超時,有50毫秒的誤差。

測試效果

照例自己寫個測試熟悉下。實際代碼中有限制,最少的時間片數目numOfPeriods爲10。

http timer ctrl is initialized, tmrCtrlId:0
malloc pObj:0x78c180
http 0x7fff072545f4, timer is reset, fp:0x401a4b, tmr_h:0x78c180, cycle:0, index:1, total:1 numOfFree:14
malloc pObj:0x78c1c0
http 0x7fff072545f8, timer is reset, fp:0x401a7f, tmr_h:0x78c1c0, cycle:1, index:1, total:2 numOfFree:13
malloc pObj:0x78c200
http 0x7fff072545fc, timer is reset, fp:0x401a7f, tmr_h:0x78c200, cycle:1, index:8, total:3 numOfFree:12
http 0x7fff072545f4, timer expired, fp:0x401a4b, tmr_h:0x78c180, index:1, total:2
timerFunc1 id[1]
http 0x7fff072545f8, timer expired, fp:0x401a7f, tmr_h:0x78c1c0, index:1, total:1
timerFunc2 id[2]
http 0x7fff072545fc, timer expired, fp:0x401a7f, tmr_h:0x78c200, index:8, total:0
timerFunc2 id[3]

完整測試代碼

在linux 下面運行測試。
編譯需要加上 -lpthread 選項。例子如下:
gcc -o timer timer.c -lpthread

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>


#define mpool_h void *
typedef void *tmr_h;

typedef struct {
	int				numOfFree;
	int 			first;
	int 			numOfBlock;
	int 			blockSize;
	int *			freeList;
	char * 			pool;
	pthread_mutex_t	mutex;
} pool_t;

#define maxNumOfTmrCtrl  	16
#define MSECONDS_PER_TICK 	5

typedef struct _tmr_obj
{
	void *param1;
	void (*fp)(void *, void *);
	tmr_h				timerId;
	short				cycle;
	struct _tmr_obj * 	prev;
	struct _tmr_obj * 	next;
	int                 index;
	struct _tmr_ctrl_t *pCtrl;
} tmr_obj_t;

typedef struct
{
	tmr_obj_t * head;
	int			count;
} tmr_list_t;

typedef struct _tmr_ctrl_t
{
	void * 			signature;
	pthread_mutex_t mutex;
	int				resolution;
	int 			numOfPeriods;
	unsigned int	periodsFromStart;
	tmr_list_t *	tmrList;
	mpool_h			poolHandle;
	char            label[12];
	int 			maxNumOfTmrs;
	int 			numOfTmrs;
	int 			ticks;
	int				maxTicks;
} tmr_ctrl_t;


tmr_ctrl_t tmrCtrl[maxNumOfTmrCtrl];


mpool_h memPoolInit(int maxNum, int blockSize);
char * memPoolMalloc(mpool_h handle);
void memPoolFree(mpool_h handle, char *p);
void memPoolCleanup(mpool_h handle);


void tmrProcessList(tmr_ctrl_t *pCtrl)
{
	int index;
	tmr_list_t * pList;
	tmr_obj_t * pObj, *header;

	pthread_mutex_lock(&pCtrl->mutex);
	index = pCtrl->periodsFromStart % pCtrl->numOfPeriods;
	pList = &pCtrl->tmrList[index];
	while(1)
	{
		header = pList->head;
		if(header == NULL) break;

		if(header->cycle > 0)
		{
			pObj = header;
			while(pObj)
			{
				pObj->cycle--;
				pObj = pObj->next;
			}
			break;
		}

		pCtrl->numOfTmrs--;
		printf("%s %p, timer expired, fp:%p, tmr_h:%p, index:%d, total:%d\n", pCtrl->label, header->param1, header->fp,
					 header, index, pCtrl->numOfTmrs);

		pList->head = header->next;
		if(header->next) header->next->prev = NULL;
		pList->count--;
		header->timerId = NULL;

		if (header->fp)
      		(*(header->fp))(header->param1, header);
		
		memPoolFree(pCtrl->poolHandle, (char *)header);
	}
	
	pCtrl->periodsFromStart++;
	pthread_mutex_unlock(&pCtrl->mutex);
	//printf("%s tmrProcessList index[%d]\n", pCtrl->label, index);
}

void * timerLoopFunc(void)
{
	tmr_ctrl_t *pCtrl;
	int i = 0;

	for(i = 0; i < maxNumOfTmrCtrl; i++)
	{
		pCtrl = tmrCtrl + i;
		if(pCtrl->signature)
		{
			pCtrl->ticks++;
			if(pCtrl->ticks >= pCtrl->maxTicks)
			{
				tmrProcessList(pCtrl);
				pCtrl->ticks = 0;
			}
		}
	}
}

void * processAlarmSignal(void *tharg)
{
	sigset_t 		sigset;
	timer_t	 		timerId;
	int signo;

	sigemptyset(&sigset);
	sigaddset(&sigset, SIGALRM);
	sigprocmask(SIG_BLOCK, &sigset, NULL);

	struct itimerval new_value, old_value;
	new_value.it_value.tv_sec = 0;
	new_value.it_value.tv_usec = 1000 * MSECONDS_PER_TICK;
	new_value.it_interval.tv_sec = 0;
	new_value.it_interval.tv_usec = 1000 * MSECONDS_PER_TICK;
	setitimer(ITIMER_REAL, &new_value, &old_value);

	while(1)
	{
		if(sigwait(&sigset, &signo))
		{
	      	printf("Failed to wait signal: number %d", signo);
	      	continue;
	    }
		timerLoopFunc();
	}
	return NULL;
}

void tmrModuleInit(void)
{
	pthread_t		thread;
	pthread_attr_t	tattr;
	
	memset(tmrCtrl, 0, sizeof(tmrCtrl));

	pthread_attr_init(&tattr);
	pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	if(pthread_create(&thread, &tattr, processAlarmSignal, NULL) != 0)
	{
		printf("failed to create timer thread");
    	return;
  	}

  	pthread_attr_destroy(&tattr);
}

void * tmrInit(int maxNumOfTmrs, int resolution, int longest, char * label)
{
	tmr_ctrl_t * pCtrl;
	int tmrCtrlId = 0;
	int i = 0;

	//tmrCtrlId = taosAllocateId(tmrIdPool);
	pCtrl = tmrCtrl + tmrCtrlId;

	memset(pCtrl, 0, sizeof(tmr_ctrl_t));
	strncpy(pCtrl->label, label, sizeof(pCtrl->label));
	pCtrl->maxNumOfTmrs = maxNumOfTmrs;

	if((pCtrl->poolHandle = memPoolInit(maxNumOfTmrs + 10,sizeof(tmr_obj_t))) == NULL)
	{
		printf("%s failed to allocate mem pool", label);
		memPoolCleanup(pCtrl->poolHandle);
		return NULL;
	}
	
	if(resolution < MSECONDS_PER_TICK) resolution = MSECONDS_PER_TICK;
	pCtrl->resolution = resolution;
	pCtrl->maxTicks = resolution / MSECONDS_PER_TICK;
	pCtrl->ticks = rand() / pCtrl->maxTicks;
	pCtrl->numOfPeriods = longest / resolution;
	if(pCtrl->numOfPeriods < 10) pCtrl->numOfPeriods = 10;

	pCtrl->tmrList = (tmr_list_t *)malloc(sizeof(tmr_list_t) * pCtrl->numOfPeriods);
	for(i = 0; i < pCtrl->numOfPeriods; i++)
	{
		pCtrl->tmrList[i].head = NULL;
		pCtrl->tmrList[i].count = 0;
	}

	pCtrl->signature = pCtrl;
	printf("%s timer ctrl is initialized, tmrCtrlId:%d\n", label, tmrCtrlId);
	return pCtrl;
}

void tmrReset(void (*fp)(void *, void*), int mseconds, void * param1, void * handle, tmr_h *pTmrId)
{
	tmr_obj_t *pObj, *cNode, *pNode;
	tmr_list_t * pList = NULL;
	int index, period;
	tmr_ctrl_t *pCtrl = (tmr_ctrl_t *)handle;

	if(handle == NULL || pTmrId == NULL) return;

	period = mseconds / pCtrl->resolution;
	if(pthread_mutex_lock(&pCtrl->mutex) != 0)
		printf("%s mutex lock failed, reason:%s", pCtrl->label, strerror(errno));

	pObj = (tmr_obj_t *)(*pTmrId);

	if(pObj && pObj->timerId == *pTmrId)
	{
		pList = &(pCtrl->tmrList[pObj->index]);
		if(pObj->prev)
			pObj->prev->next = pObj->next;
		else
			pList->head = pObj->next;

		if(pObj->next)
			pObj->next->prev = pObj->prev;

		pList->count--;
		pObj->timerId = NULL;
		pCtrl->numOfTmrs--;
		printf("reset pObj:%p\n", pObj);
	}
	else
	{
		pObj = (tmr_obj_t *)memPoolMalloc(pCtrl->poolHandle);
		*pTmrId = pObj;
		if(pObj == NULL)
		{
			printf("%s failed to allocate timer, max:%d allocated:%d", pCtrl->label, pCtrl->maxNumOfTmrs, pCtrl->numOfTmrs);
      		pthread_mutex_unlock(&pCtrl->mutex);
      		return;
		}
		printf("malloc pObj:%p\n", pObj);
	}

	pObj->cycle = period / pCtrl->numOfPeriods;
	pObj->param1 = param1;
	pObj->fp = fp;
	pObj->timerId = pObj;
	pObj->pCtrl = pCtrl;

	index = (period + pCtrl->periodsFromStart) % pCtrl->numOfPeriods;
	pList = &(pCtrl->tmrList[index]);
	pObj->index = index;
	cNode = pList->head;
	pNode = NULL;

	while(cNode != NULL)
	{
		if(cNode->cycle < pObj->cycle)
		{
			pNode = cNode;
			cNode = cNode->next;
		}
		else
			break;
	}

	pObj->next = cNode;
	pObj->prev = pNode;

	if(cNode != NULL)
		cNode->prev = pObj;

	if(pNode != NULL)
		pNode->next = pObj;
	else
		pList->head = pObj;

	pList->count++;
	pCtrl->numOfTmrs++;

	if (pthread_mutex_unlock(&pCtrl->mutex) != 0)
    	printf("%s mutex unlock failed, reason:%s", pCtrl->label, strerror(errno));

	printf("%s %p, timer is reset, fp:%p, tmr_h:%p, cycle:%d, index:%d, total:%d numOfFree:%d\n", pCtrl->label, param1, fp, pObj,
           pObj->cycle, index, pCtrl->numOfTmrs, ((pool_t *)pCtrl->poolHandle)->numOfFree);
	return;
}



mpool_h memPoolInit(int numOfBlock, int blockSize)
{
	int i = 0;
	pool_t * pool_p = NULL;

	if(numOfBlock <= 1 || blockSize <= 1)
	{
		printf("invalid parameter in memPoolInit\n");
		return NULL;
	}

	pool_p = (pool_t *)malloc(sizeof(pool_t));
	if(pool_p == NULL)
	{
		printf("mempool malloc failed\n");
		return NULL;
	}

	memset(pool_p, 0, sizeof(pool_t));

	pool_p->blockSize = blockSize;
	pool_p->numOfBlock = numOfBlock;
	pool_p->pool = (char *)malloc((size_t)(blockSize * numOfBlock));
	pool_p->freeList = (int *)malloc(sizeof(int) * (size_t)numOfBlock);

	if(pool_p->pool == NULL || pool_p->freeList == NULL)
	{
		printf("failed to allocate memory\n");
		free(pool_p->freeList);
		free(pool_p->pool);
		free(pool_p);
	}

	pthread_mutex_init(&(pool_p->mutex), NULL);

	for(i = 0; i < pool_p->numOfBlock; i++)
		pool_p->freeList[i] = i;

	pool_p->first = 0;
	pool_p->numOfFree= pool_p->numOfBlock;

	return (mpool_h)pool_p;
}

char * memPoolMalloc(mpool_h handle)
{
	char * pos = NULL;
	pool_t * pool_p = (pool_t *)handle;

	pthread_mutex_lock(&pool_p->mutex);

	if(pool_p->numOfFree <= 0)
	{
		printf("mempool: out of memory");
	}
	else
	{
		pos = pool_p->pool + pool_p->blockSize * (pool_p->freeList[pool_p->first]);
		pool_p->first = (pool_p->first + 1) % pool_p->numOfBlock;
		pool_p->numOfFree--;
	}

	pthread_mutex_unlock(&pool_p->mutex);
	if(pos != NULL) memset(pos, 0, (size_t)pool_p->blockSize);
	return pos;
}

void memPoolFree(mpool_h handle, char * pMem)
{
	int index = 0;
	pool_t * pool_p = (pool_t *)handle;

	if(pool_p == NULL || pMem == NULL) return;

	pthread_mutex_lock(&pool_p->mutex);

	index = (int)(pMem - pool_p->pool) % pool_p->blockSize;
	if(index != 0)
	{
		printf("invalid free address:%p\n", pMem);
	}
	else
	{
		index = (int)((pMem - pool_p->pool) / pool_p->blockSize);
		if(index < 0 || index >= pool_p->numOfBlock)
		{
			printf("mempool: error, invalid address:%p\n", pMem);
		}
		else
		{
			pool_p->freeList[(pool_p->first + pool_p->numOfFree) % pool_p->numOfBlock] = index;
			pool_p->numOfFree++;
			memset(pMem, 0, (size_t)pool_p->blockSize);
		}
	}
	
	pthread_mutex_unlock(&pool_p->mutex);
}

void memPoolCleanup(mpool_h handle)
{
	pool_t *pool_p = (pool_t *)handle;

	pthread_mutex_destroy(&pool_p->mutex);
	if(pool_p->pool) free(pool_p->pool);
	if(pool_p->freeList) free(pool_p->freeList);
}

void timerFunc1(void *param, void *tmrId) 
{
	int id = *(int *)param;
	printf("%s id[%d]\n", __func__, id);
}

void timerFunc2(void *param, void *tmrId) 
{
	int id = *(int *)param;
	printf("%s id[%d]\n", __func__, id);
}

void test()
{	
	void *timerHandle = tmrInit(5, 100, 1000, "http");
	void *timer1 = NULL, *timer2 = NULL, *timer3 = NULL;
	int id1 = 1, id2 = 2, id3 = 3;

	tmrReset(timerFunc1, 100, (void *)&id1, timerHandle, &timer1);

	tmrReset(timerFunc2, 1100, (void *)&id2, timerHandle, &timer2);

	tmrReset(timerFunc2, 1800, (void *)&id3, timerHandle, &timer3);
}

int main()
{
	sigset_t 		sigset;

	sigemptyset(&sigset);
	sigaddset(&sigset, SIGALRM);
	sigprocmask(SIG_BLOCK, &sigset, NULL);
	
	tmrModuleInit();
	test();
	while(1)
	{	
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章