contributed by < Julian-Chu >
linux2021
xs union 定義 16 個位元組,應用與實作細節如下:
字串長度小於或等於 15 個位元組,則放在 stack。 字串長度大於或等於 16 個位元組,則放在 heap (透過 malloc 系統呼叫配置所需字串大小)
放在 heap 的字串, 還有額外的判斷, 針對長度大於LARGE_STRING_LEN
的字串會進行複用, 同時將字串前 4 bytes 用於 reference count
需要解釋為何 reference counting 被納入考量
針對相同的長字串, 設計上利用參考指向同一塊記憶體空間, 避免複製浪費空間, 在沒有 reference count 的情況無法得知是否還有使用到這個字串的地方, 誤釋放記憶體空間會造成 dangling pointer
的風險。
union
與 bitfield
的綜合使用, 對 c 語言的彈性跟記憶體空間利用效率真是大開眼界, 同一段記憶體空間可以用來表達不同的結構的同時又可以共用結構。
typedef union {
/* allow strings up to 15 bytes to stay on the stack
* use the last byte as a null terminator and to store flags
* much like fbstring:
* https://github.com/facebook/folly/blob/master/folly/docs/FBString.md
*/
char data[16];
struct {
uint8_t filler[15],
/* how many free bytes in this stack allocated string
* same idea as fbstring
*/
space_left : 4,
/* if it is on heap, set to 1 */
is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1;
};
/* heap allocated */
struct {
char *ptr;
/* supports strings up to 2^MAX_STR_LEN_BITS - 1 bytes */
size_t size : MAX_STR_LEN_BITS,
/* capacity is always a power of 2 (unsigned)-1 */
capacity : 6;
/* the last 4 bits are important flags */
};
} xs;
Learn More →
struct {
uint8_t filler[15],
/* how many free bytes in this stack allocated string
* same idea as fbstring
*/
space_left : 4,
/* if it is on heap, set to 1 */
is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1;
};
space_left 使用了 4 bits , 剛好完整表達了在 stack 的字串長度為 0 - 15 (0b0000 - 0b1111), 將剩下的 4 bits, 用於 flag
TODO: 用 GDB 確認 xs 在記憶體實際的佈局的確符合預期
測試用的程式碼, break point 設在 return 0;
行, 利用 GDB x 指令查看記憶體內容。
int main(int argc, char *argv[])
{
xs string = *xs_tmp("foobarbar");
(&string)->is_ptr = 1;
// size = 19
xs mstr = *xs_tmp("xxxxxxxxxxxxxxxxxxx");
// size = 256
xs lstr = *xs_tmp("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
// ref + 1
xs_inc_refcnt(&lstr);
return 0;
}
查看 string in stack
// 查看 string 的記憶體位置,
// 發現最後一個 byte 與前部分記憶體配置圖不合
// space_left is_ptr is_large_string flag2 flag3
// 0110 1 0 0 0
// 0b01101000 = 104 實際上卻是 22
(gdb) x/16c &string
0x7fffffffde50: 102 'f' 111 'o' 111 'o' 98 'b' 97 'a' 114 'r' 98 'b' 97 'a'
0x7fffffffde58: 114 'r' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 22 '\026'
// 直接查看最後一個 byte , 發現 bits 配置應爲 flag3 flag2 is_large_string is_ptr space_left (0, 0, 0, 1, 0 1 1 0)
(gdb) x/t 0x7fffffffde5f
0x7fffffffde5f: 00010110
查看 medium string
// 查看 structure
(gdb) p mstr
$2 = {data = "\240\242UUUU\000\000\023\000\000\000\000\000@\021", {filler = "\240\242UUUU\000\000\023\000\000\000\000\000@", space_left = 1 '\001', is_ptr = 1 '\001',
is_large_string = 0 '\000', flag2 = 0 '\000', flag3 = 0 '\000'}, {ptr = 0x55555555a2a0 'x' <repeats 19 times>, size = 19, capacity = 5}}
// 查看指針指向的資料
(gdb) x/32c mstr->ptr
0x55555555a2a0: 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x'
0x55555555a2a8: 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x' 120 'x'
0x55555555a2b0: 120 'x' 120 'x' 120 'x' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
0x55555555a2b8: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
// 確認 size 爲 19
(gdb) x/16d &mstr
0x7fffffffde70: -96 -94 85 85 85 85 0 0
0x7fffffffde78: 19 0 0 0 0 0 64 17
// capacity 被拆開在兩個 byte 裡面, 需要取 0100000 前兩位 01
// 00010001 後四位 0001 組合 000101 可得 capacity = 5(2^5 = 32)
(gdb) x/2t 0x7fffffffde7E
0x7fffffffde7e: 01000000 00010001
查看 large string
(gdb) p lstr
$3 = {data = "ТUUUU\000\000\000\001\000\000\000\000@2", {filler = "ТUUUU\000\000\000\001\000\000\000\000@", space_left = 2 '\002', is_ptr = 1 '\001', is_large_string = 1 '\001',
flag2 = 0 '\000', flag3 = 0 '\000'}, {ptr = 0x55555555a2d0 "\002", size = 256, capacity = 9}}
// 查看 ptr 可以確認, 前四個 byte 用於計算 refernence count
(gdb) x/264c lstr->ptr
0x55555555a2d0: 2 '\002' 0 '\000' 0 '\000' 0 '\000' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a2d8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a2e0: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a2e8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a2f0: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a2f8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a300: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a308: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a310: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a318: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a320: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a328: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a330: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a338: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a340: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a348: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a350: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a358: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a360: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a368: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a370: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a378: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a380: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a388: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a390: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a398: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3a0: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3a8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3b0: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3b8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3c0: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3c8: 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a' 97 'a'
0x55555555a3d0: 97 'a' 97 'a' 97 'a' 97 'a' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
前述的記憶體配置示意圖在 bitfield 的部分皆錯誤, 更正後如下圖。
/* heap allocated */
struct {
char *ptr;
/* supports strings up to 2^MAX_STR_LEN_BITS - 1 bytes */
size_t size : MAX_STR_LEN_BITS,
/* capacity is always a power of 2 (unsigned)-1 */
capacity : 6;
/* the last 4 bits are important flags */
};
以 8 bytes 的 pointer to char 指向字串的記憶體位置, 用 size 來表示字串長度, capacity 表示記憶體空間的 power of two, 對於長度大於LARGE_STRING_LEN
的字串, 會將 ptr 指向的位置, 前 4 bytes(int) 用於紀錄reference count, 後 4 bits 沒有被指定到, 會復用上一個 struct 定義的 flags
利用目標字串的長度決定 xs 字串的儲存位置在 stack 或 heap ,
而針對 large string 與否的處理封裝在 xs_allocate_data
之中
xs *xs_new(xs *x, const void *p)
{
*x = xs_literal_empty();
size_t len = strlen(p) + 1;
if (len > NNN) { // NNN = 16, 需注意是 字串長度加上 null terminator
x->capacity = ilog2(len) + 1;
x->size = len - 1;
x->is_ptr = true;
xs_allocate_data(x, x->size, 0);
memcpy(xs_data(x), p, len);
} else {
memcpy(x->data, p, len);
x->space_left = 15 - (len - 1);
}
return x;
}
xs_allocate_data
初始化在 heap 中的字串, 根據字串的長度區分 medium string 跟 large string 後在依據 reallocate flag
分別做基於改變現有資料的記憶體大小 realloc
或是配置新記憶體空間malloc
static void xs_allocate_data(xs *x, size_t len, bool reallocate)
{
/* Medium string */
if (len < LARGE_STRING_LEN) {
x->ptr = reallocate ? realloc(x->ptr, (size_t) 1 << x->capacity)
: malloc((size_t) 1 << x->capacity);
return;
}
/* Large string */
x->is_large_string = 1;
/* The extra 4 bytes are used to store the reference count */
x->ptr = reallocate ? realloc(x->ptr, (size_t)(1 << x->capacity) + 4)
: malloc((size_t)(1 << x->capacity) + 4);
xs_set_refcnt(x, 1);
}
利用 macro 在初始化之前做檢查
/* Memory leaks happen if the string is too long but it is still useful for
* short strings.
*/
#define xs_tmp(x) \
((void) ((struct { \
_Static_assert(sizeof(x) <= MAX_STR_LEN, "it is too big"); \
int dummy; \
}){1}), \
xs_new(&xs_literal_empty(), x))
/* grow up to specified size */
xs *xs_grow(xs *x, size_t len)
{
char buf[16];
// 已配置的記憶體空間大於新的 len 不需處理
if (len <= xs_capacity(x))
return x;
// 當 data 存在 stack 的時候, 先將 data copy 到 buf
/* Backup first */
if (!xs_is_ptr(x))
memcpy(buf, x->data, 16);
// 由於 xs 初始化爲 stack 時會直接給到最大的 stack capacity, 再進行 grow 的話會轉為使用 heap
x->is_ptr = true;
// 直接將容量擴充到可以最小容納新長度字串的 2 的冪記憶體空間
x->capacity = ilog2(len) + 1;
if (xs_is_ptr(x)) {
// 已經配置過 heap 記憶體, 改變大小
xs_allocate_data(x, len, 1);
} else {
// 從 stack 轉換成 heap , 配置新的記憶體空間
xs_allocate_data(x, len, 0);
memcpy(xs_data(x), buf, 16);
}
return x;
}
static inline xs *xs_newempty(xs *x)
{
*x = xs_literal_empty();
return x;
}
static inline xs *xs_free(xs *x)
{
// 使用 heap 的情況下, 需要確定沒有 reference 後才可以釋放記憶體空間
if (xs_is_ptr(x) && xs_dec_refcnt(x) <= 0)
free(x->ptr);
return xs_newempty(x);
}
static bool xs_cow_lazy_copy(xs *x, char **data)
{
// 並非 large string 或沒有其他參考, 不用作 lazy copy
if (xs_get_refcnt(x) <= 1)
return false;
// 將 large string 的參考減一後, 配置新的記憶體空間給x->ptr
/* Lazy copy */
xs_dec_refcnt(x);
xs_allocate_data(x, x->size, 0);
// 將 data 指向的記憶體空間資料複製一份至新的x->ptr
// 將 data 指向新的副本
if (data) {
memcpy(xs_data(x), *data, x->size);
/* Update the newly allocated pointer */
*data = xs_data(x);
}
return true;
}
xs *xs_concat(xs *string, const xs *prefix, const xs *suffix)
{
size_t pres = xs_size(prefix), sufs = xs_size(suffix),
size = xs_size(string), capacity = xs_capacity(string);
char *pre = xs_data(prefix), *suf = xs_data(suffix),
*data = xs_data(string);
xs_cow_lazy_copy(string, &data);
// 現有配置的記憶體空間足夠容納新的字串
if (size + pres + sufs <= capacity) {
// 將原本 data 的記憶體位置加上 pres 大小 offset 後, 將data 的資料搬移過去, 相當於在 data 平移 pres 大小的 offset
memmove(data + pres, data, size);
// 將 pre 複製至上一步平移後騰出來的頭部空間
memcpy(data, pre, pres);
// 將 suf 放到平移後的 data 尾部
memcpy(data + pres + size, suf, sufs + 1);
// 依據 stack 或 heap 更新 size
if (xs_is_ptr(string))
string->size = size + pres + sufs;
else
string->space_left = 15 - (size + pres + sufs);
} else {
// 空間不足, 產生新的 xs
xs tmps = xs_literal_empty();
xs_grow(&tmps, size + pres + sufs);
// 取得新的 xs 的記憶體空間位置
char *tmpdata = xs_data(&tmps);
// 依序複製資料到對應的記憶體位置
memcpy(tmpdata + pres, data, size);
memcpy(tmpdata, pre, pres);
memcpy(tmpdata + pres + size, suf, sufs + 1);
// 將原本的string 佔用的空間釋放
xs_free(string);
// 將string指向新的 xs, 並設定新的 size
*string = tmps;
string->size = size + pres + sufs;
}
return string;
}
xs *xs_trim(xs *x, const char *trimset)
{
// 需要修剪的字串爲空
if (!trimset[0])
return x;
char *dataptr = xs_data(x), *orig = dataptr;
// 如果有需要產生副本則將 orig 指向新的副本
if (xs_cow_lazy_copy(x, &dataptr))
orig = dataptr;
/* similar to strspn/strpbrk but it operates on binary data */
uint8_t mask[32] = {0};
// 第一次看到以爲是類似 bloom filter 的方式, 看了課堂問答才知道是很巧妙的查表 ref: https://hackmd.io/@sysprog/rk9fG7zG_?fbclid=IwAR3rRvcKQtFMOmUQ8CRkUCKABhlk-gZRKC6M7S4BDh-XKMOa-uWCIcFix-s
#define check_bit(byte) (mask[(uint8_t)byte/8]&1 << (uint8_t)byte %8)
#define set_bit(byte) (mask[(uint8_t)byte/8]|=1<<(uint8_t)byte %8)
size_t i, slen = xs_size(x), trimlen = strlen(trimset);
for (i = 0; i < trimlen; i++)
set_bit(trimset[i]);
// 利用查表找出 string 在移除字母表裡的第一個字元跟最後一個字元
for (i = 0; i < slen; i++)
if (!check_bit(dataptr[i]))
break;
for (; slen > 0; slen--)
if (!check_bit(dataptr[slen - 1]))
break;
// 移動到不被刪除的第一個字元, 跟計算新字串的長度
dataptr += i;
slen -= i;
/* reserved space as a buffer on the heap.
* Do not reallocate immediately. Instead, reuse it as possible.
* Do not shrink to in place if < 16 bytes.
*/
memmove(orig, dataptr, slen);
/* do not dirty memory unless it is needed */
if (orig[slen])
orig[slen] = 0;
if (xs_is_ptr(x))
x->size = slen;
else
x->space_left = 15 - slen;
return x;
#undef check_bit
#undef set_bit
}
回頭去看之前嘗試閱讀的 redis 的程式碼, 做過 quiz3
後對這個 struct 設計有更進一步的了解
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
contributed by < Julian-Chu >
Oct 8, 2023α−1
Aug 12, 2023contributed by < Julian-Chu > K08: ktcp 自我檢查清單 參照 Linux 核心模組掛載機制,解釋 $ sudo insmod khttpd.ko port=1999 這命令是如何讓 port=1999 傳遞到核心,作為核心模組初始化的參數呢? 首先會看到 main.c 裡面對 port 參數進行宣告跟初始化 static ushort port = DEFAULT_PORT; module_param(port, ushort, S_IRUGO);
Jun 8, 2022contributed by < Julian-Chu > GitHub Data type 主要使用的 struct, 與去年不同改以 Linux Doubly Linked Lists 實作 lab0 typedef struct { char *value; struct list_head list; } element_t;
May 8, 2022or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up