一個典型的 buddy system. 代碼在env/env_alloc.c緩存
ALLOC_LAYOUT: 管理整塊內存(即bdb的某個region)的 數據結構. 放於此內存 開頭位置. SH_TAILQ_HEAD(__addrq) addrq; // address queue. 按地址排序. 用於內存塊 的分裂和 合併. 全部的內存塊 都會放入address queue中. #define DB_SIZE_Q_COUNT 11 SIZEQ_HEAD sizeq[DB_SIZE_Q_COUNT]; // size queue. 用於快速查找可用內存. 11個queue, 從1024字節 到 1M.
// 每一個queue中 的內存塊 按長度 從大到小 排序. size queue中放的是 當前沒有使用的內存. ALLOC_ELEMENT: 放於每個內存塊的開頭 SH_TAILQ_ENTRY addrq; // 其在address queue中的entry SH_TAILQ_ENTRY sizeq; // 其在size queue中的entry uintmax_t len; // 此內存塊 總的長度 uintmax_t ulen; // 用戶可用的長度 // 僅當 剩餘內存(空閒內存塊大小 - 用戶須要的大小) 大於 SHALLOC_FRAGMENT, 作內存分割 #define SHALLOC_FRAGMENT (sizeof(ALLOC_ELEMENT) + 64) // 對指定的內存大小len, q返回對應的 size queue. head指向當前region的 ALLOC_LAYOUT. #define SET_QUEUE_FOR_SIZE(head, q, i, len) do { \ for (i = 0; i < DB_SIZE_Q_COUNT; ++i) { \ q = &(head)->sizeq[i]; \ if ((len) <= (u_int64_t)1024 << i) \ break; \ } \ } while (0) // 一個ALLOC_ELEMENT 佔用的內存大小. 需作對齊 #define DB_ALLOC_SIZE(len) \ (size_t)DB_ALIGN((len) + sizeof(ALLOC_ELEMENT), sizeof(uintmax_t))
REGINFO->head爲 ALLOC_LAYOUT. 初始化其 address queue, size queue.
剩下的內存 初始化爲 一個 ALLOC_ELEMENT, 放入address queue 和 sizeq[DB_SIZE_Q_COUNT - 1]
note: 有可能 region 初始化大小 不足1M, 放入sizeq[DB_SIZE_Q_COUNT - 1]
沒有關係, 第一次分配內存 會把其放入 合適的size queue.數據結構
基於 堆的內存分配(malloc), 這裏不討論.ui
head = infop->head; // 此region的ALLOC_LAYOUT對象 total_len = DB_ALLOC_SIZE(len); // 需分配的內存大小 SET_QUEUE_FOR_SIZE(head, q, i, total_len); // 找到對應的size queue for (elp = NULL;; ++q) { // 若在當前size queue沒找到, 則去 更大的size queue裏找 SH_TAILQ_FOREACH(elp_tmp, q, sizeq, __alloc_element) { // 遍歷當前size queue if (elp_tmp->len < total_len) // size queue中元素從大到小排序. 若當前元素不夠大, 則後面的確定也不夠 break; elp = elp_tmp; // 此內存塊知足條件. 緩存此ALLOC_ELEMENT.如有更合適的, 則elp會被從新賦值. if (elp_tmp->len - total_len <= SHALLOC_FRAGMENT) // 使用當前內存塊, 若無需分割. 確保找到大小恰好合適的 free內存(無法再小了, 或無須分割) break; } if (elp != NULL || ++i >= DB_SIZE_Q_COUNT) // 在當前queue中找到; 或者查完全部size queue都未找到. break; } if (elp == NULL) { // 查完全部size queue未找到合適內存的狀況 ... // 若是能夠, extend當前region內存塊, retry. 不然 退出. }
// 下面是已經找到合適內存的狀況 SH_TAILQ_REMOVE(q, elp, sizeq, __alloc_element); // 將其從 對應的size queue 移除. if (elp->len - total_len > SHALLOC_FRAGMENT) { // 須要作內存分割 frag = (ALLOC_ELEMENT *)((u_int8_t *)elp + total_len); // 分割後的新內存塊 frag->len = elp->len - total_len; frag->ulen = 0; elp->len = total_len; // 原內存塊縮小了 SH_TAILQ_INSERT_AFTER( // 分割獲得的新內存塊正好 放入 原內存塊的address queue以後. &head->addrq, elp, frag, addrq, __alloc_element); __env_size_insert(head, frag); // 新內存塊 放入size queue } p = (u_int8_t *)elp + sizeof(ALLOC_ELEMENT); elp->ulen = len; // elp爲返回給用戶的內存塊
head = infop->head; // 此region的ALLOC_LAYOUT對象 p = ptr; elp = (ALLOC_ELEMENT *)(p - sizeof(ALLOC_ELEMENT)); // 獲得須要釋放內存的ALLOC_ELEMENT對象 elp->ulen = 0; // ulen爲0, 表示此內存再也不使用 // address queue中元素按 其 地址排序. if ((elp_tmp = SH_TAILQ_PREV(&head->addrq, elp, addrq, __alloc_element)) != NULL && // elp_tmp設爲 此elp 在address queue以前的 元素 elp_tmp->ulen == 0 && // 以前的元素 未被使用 (u_int8_t *)elp_tmp + elp_tmp->len == (u_int8_t *)elp) { // 且兩段地址 相鄰 SH_TAILQ_REMOVE(&head->addrq, elp, addrq, __alloc_element); // 把當前內存 和以前的內存 作合併. 當前內存塊 移除 addrq SET_QUEUE_FOR_SIZE(head, q, i, elp_tmp->len); // 獲得 以前元素所在 的size queue SH_TAILQ_REMOVE(q, elp_tmp, sizeq, __alloc_element); // 前面的內存塊 移除 其對應的 size queue elp_tmp->len += elp->len; // 作合併 elp = elp_tmp; // elp 指向 合併後的內存地址 } if ((elp_tmp = SH_TAILQ_NEXT(elp, addrq, __alloc_element)) != NULL && // elp_tmp設爲 此elp 在adress queue以後的 元素 elp_tmp->ulen == 0 && // 以後的元素 未被使用 (u_int8_t *)elp + elp->len == (u_int8_t *)elp_tmp) { // 且兩段地址 相鄰 SH_TAILQ_REMOVE(&head->addrq, elp_tmp, addrq, __alloc_element); // 把當前內存和 以後的內存 作合併.後面內存塊移除addrq SET_QUEUE_FOR_SIZE(head, q, i, elp_tmp->len); // 獲得以後元素 所在的size queue SH_TAILQ_REMOVE(q, elp_tmp, sizeq, __alloc_element); // 後面的內存塊 移除 size queue elp->len += elp_tmp->len; // 作合併. elp 指向 合併後的內存地址 } __env_size_insert(head, elp); // 合併後的 內存塊入 size queue
__env_size_insert(): 將某個 ALLOC_ELEMENT 插入其對應的 size queue中.spa
SET_QUEUE_FOR_SIZE(head, q, i, elp->len); // 找到其對應的size queue SH_TAILQ_FOREACH(elp_tmp, q, sizeq, __alloc_element) // 遍歷此size queue if (elp->len >= elp_tmp->len) // size queue元素從大到小排序, elp大小位於 當前元素和前一個元素之間(前一個元素可能爲空). 須要插入到當前elp_tmp以前 break; if (elp_tmp == NULL) SH_TAILQ_INSERT_TAIL(q, elp, sizeq); // elp插入隊尾 else SH_TAILQ_INSERT_BEFORE(q, elp_tmp, elp, sizeq, __alloc_element); // elp插入到當前elp_tmp以前
注: env_alloc/env_alloc_free 都未加鎖. 對其的調用須要 加相應region的鎖.code