代碼介紹
學習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
時間過去200毫秒,這個時候定時器控制類剛好過去兩個時間片,定時器狀態如圖2所示
periodsFromStart值爲2
時間片index 爲1的鏈表已經被處理過,定時器1已經超時,從鏈表中刪除,定時器2的cycle值減一。
圖2
定時器精度誤差
在創建一個定時器的時候,計算公式只考慮當前所在的時間片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)
{
}
}