contributed by < colinyoyo26
>
linux2020
第 1 題組的延伸問題
xs_tmp
在編譯時期檢查 x
是 string literal 以及長度小於 16 ,接著呼叫 xs_new
我想請問哪裡可以找到
xs_new(&xs_literal_empty(), "" x)
""
可以檢查x
是不是 string literal 的相關論述
已經查過 C11 規格書以及 Using the GNU Compiler Collection 沒有找到相關論述
用 gdb 做實驗,確認配置的字串在於 stack 還是 read-only section,即可反推是否為 string literal
實驗得到 string literal 是在 read-only section ,然後在 C11 規格書 5.1.1.2 Translation phases 找到 6. Adjacent string literal tokens are concatenated. 推測出這是編譯器報錯的原因
ilog2
回傳
xs_new
xs_literal_empty()
先初始化為空的短字串 (x->space_left
設為 15 其他設為 0),若字串長度大於 15 會把字串放到 heap , malloc
的空間為 (對齊下一個 2 的冪次方),否則會直接放到 stack 因為有 null terminator 所以不用在重設 is_ptr
xs_grow
函式目的為將 x
的容量增長到下個二的冪次方
xs_concat
函式目的為將 prefix
和 suffix
串接到 string
前後, string
的容量夠時只須將字串複製過去即可,否則需要 call xs_grow
增加容量到下個二的冪次方,再複製字串,以下是將用到 memmove
的原因
Linux Programmer's Manual
xs_trim
這個函式的目的是去除掉 x
內前後包含於 trimset
的字元,函式中兩個 macro 利用 hashing 的手法來檢查字元,首先把一個 char
分成 quotient (前 5 個 bits) ,和 remainder (後 3 個 bits) ,再來 set_bit
用於插入,check_bit
用於確認字元是否被插入
uint8_t
的 8 個 bits完整的 code 以及編譯方式請見 GitHub
首先是字串複製 xs_cpy
的實作以及測試輸出,先從記憶體配置開始
xs
memory layoutmalloc
空間給 x->ptr
時會多塞 sizeof(REFTYPE)
的空間放 reference count 並把指標移到字串開始位置
xs
的記憶體配置邏輯如下(xs
16 bytes, refcnt
sizeof(REFTYPE)
bytes)
改用 mermaid 或 Graphviz 重新製作上方圖例
已用 Graphviz 重繪
以下比較 folly::fbstring 的 CoW 機制,看以下五個函式和資料結構就能看出端倪了, ml_.data_
會指向 struct RefCounted
的 data_
當要取 refCount_
的值時,會利用計算從 struct RefCounted
到 data_
的 offset 得到 refCount_
的位址 (和 linux linked list 一樣的手法)
data[1]
是為了留空間給 \0
以下兩個函式回傳 reference count 和是否具有其他 reference
增減 reference count , xs_decr_ref
發現 reference count 歸零時會呼叫 xs_free
呼叫 xs_cpy
時如果不到需要 CoW 的字串長度,則直接複製字串,否則用 CoW 機制直接複製 meta data 並增加 reference count 其中 !~xs_refcnt(src)
是為了防止 overflow
若是字串要被修改時 (e.g. xs_trim
, xs_concat
) 會呼叫xs_cow
檢查是否有其他參考,若有則需要自己在開一個新的空間
測試程式:
參考執行結果:
接著是 strtok 的實作
xs_tok
先簡介 strtok
以下是 Linux Programmer's Manual
strtok()
function breaks a string into a sequence of zero or more nonempty tokens. On the first call to strtok()
the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str must be NULL
.第一次呼叫
strtok
需要給定欲處理的字串(放在str
),之後str
必須為NULL
delim
argument specifies a set of bytes that delimit the tokens in the parsed string. The caller may specify different strings in delim in successive calls that parse the same string.
delim
就是字元的集合,用於劃分欲解析字串
strtok()
returns a pointer to a null-terminated string containing the next token. This string does not include the delimiting byte.會回傳從第一個 non-delimiting byte. 下一個 delimiting byte (不包含) 之間的字串
\0
is encountered.會把下一個 delimiting byte 替換成
\0
我照著 strtok
的行為實作, laststr
指向欲處理字串的開頭, src_flag
用於分辨是否要更新 src->size
(或 src->leftspace
),並和 xs_trim
一樣用 hashing 的方法實作檢查 delim
因為會把下一個 delimiting byte 替換成
\0
所以第一次呼叫需要更新src->size
測試程式:
參考執行結果:
TODO: 考慮到之後會在多執行緒環境,用 strtok_r
實作手法改寫
已實作
xs_tok_r
(reentrant version ofxs_tok
),以及測試程式(用 pthread 測試)
xs_tok_r
測試程式:
參考執行結果:
這邊輸出順序不重要,重點是 "IM nth thread!!!" string 預期要是完整的
設計實驗呼叫複製字串函式數次計算時間以及 cache-misses 次數,實驗結果是 xs_cpy
with CoW 的 cache-missed 明顯最少,但是再這個 input 下 strcpy
的時間效率略好一些,更下面還有 make plot
視覺化不同字串長度下的效率比較
xs_cpy
w/o CoW$ make bench
xs_cpy
w/ CoWstrcpy
make plot
$ make plot
因為 CoW 機制下只有複製 metadata 所以時間趨勢如預期是呈現
我的實驗還沒有考慮到寫入 shared string 時的效能
call fork()
會先到以下函式設置 args
接著呼叫 _do_fork
vfork()
clone
等函式也都是設置對應的 args
再呼叫 _do_fork
_do_fork
接著看到 _do_fork
,會先經過以下條件式檢查是哪個 system call
然後呼叫 copy_process
建造 child process 並回傳指標
在 copy_process
內會呼叫 dup_task_struct
其中 current
是指向目前運行程式( parent process )的指標
喚醒 child process
如果是 vfork
要等待 child process 呼叫 exec
或結束
copy_process
忽略一開始的參數檢查, dup_task_struct
是真正建造 child process 的函式, parent process 會初始化 child process 後回傳指標 p
實現 CoW 的地方,放在後面說明
主要設置 childe process 的 PC (program counter) 以及 register
copy_thread_tls
會呼叫 copy_thread
,函式實作和 microarchitecture 息息相關Reference: - Linux kernel fork 函式
dup_task_struct
得知要從哪個 NUMA 節點配置記憶體
為 child process 從剛才得到的 NUMA 節點配置 struct task_struct
, stack
, vm_struct
的記憶體空間
這邊的 tsk_fork_get_node()
其實並「不一定」會回傳一個任一個 NUMA node。
假設,CONFIG_NUMA
已經設置tsk_fork_get_node()
仍然只會對於 kthreadd_task
返回預設的 NUMA node.
–- JulianATA
這裡的
stack
是 kernel stack
先完全複製當前行程的 struct task_struct
上方的資訊,我們可以發現一件有趣的事情。就算在一個已經設置CONFIG_NUMA
的系統中,仍然有可能回傳NUMA_NO_NODE
作為系統預計放置的記憶體的 NUMA node。
那這個 NUMA_NO_NODE
實際上是 -1 ,我們順著 alloc_task_struct_node
以及 alloc_thread_stack_node
走下去。
我們會發現,不論是前者透過 slab
, slob
, slub
的slab_alloc_node
, slob_alloc_node
, slub_alloc_node
的配置,或是後者通過 alloc_page_node
做分配,皆會通過一個 numa_mem_id()
來自 include/linux/topology.h
見註解!可以看到,無論是哪個情況下的 numa_mem_id
目標為回傳最近仍然有空間的 NUMA node。
由這件小事,其實可以延伸到一個目前的現況。在 Linux Kernel 裡面,支援對於 NUMA node 的「操作」,除了幾個比較簡單的 policy ,其實還沒有提供實質上的「策略」。
–- JulianATA
然後改變 child process 的 stack
以及 stack_vm_area
copy_mm
vfork
會直接進入條件式內 (呼叫 vfork 時 args.flags 會被設成 CLONE_VFORK | CLONE_VM
) 直接共用同一個 mmstruct
這就是為什麼用 vfork
child process 和 parent process 會共享記憶體空間的原因,一般的 fork
會呼叫 dup_mm
複製 parent process 的 mm_struct
dup_mm
先配置新的 mm_struct
並從 parent 複製資料
接著呼叫 dup_mmap
複製 page table , vm_area_struct
,這地方先不深入 trace
Reference: Understanding the Linux Kernel
這邊依循恐龍書的定義把 virtual page 叫做 page , physical page 叫做 frame
當 parent 或 child 嘗試寫入共用的 frame 時會觸發 page fault 然後通過 do_page_fault()
handle_mm_fault()
handle_pte_fault()
處理異常 (觸發 CoW)
Reference: 深入了解 Linux-COW 原理
這幾個函式還沒深入 trace