contributed by < chengingx
>
linux2022
〈UNIX 作業系統 fork/exec 系統呼叫的前世今生〉提到 clone 系統呼叫,搭配閱讀〈Implementing a Thread Library on Linux〉,以下嘗試撰寫一套使用者層級的執行緒函式庫,程式碼可見: readers.c
預期執行輸出: (順序可能會變異)
main()
觀察 main(),可以看見有
lock
, rwlock
print_lock
n_readers_in
, n_writers_in
readers[5]
, writers[5]
lock
, rwlock
為名 mutex_t
的物件print_lock
為名 spin_t
的物件lock
為 volatile
,其中 volatile
會抑制編譯器最佳化,要求編譯器每次使用該變數時,都要從記憶體地址中讀出最新內容,但不能保證 CPU 不會遭遇重排
接著觀察 mutex_init
與 spin_init
怎麼使用 mutex_t
物件與 spin_t
物件
volatile int out 這個 output operand 用來 ?
GCC-Inline-Assembly-HOWTO 提到 asm 的閱讀方式
接著觀察 f1
, f2
,根據 Linux kernel synchronization,提到
n_readers
用於紀錄現在有多少 readern_readers_in
有多少 reader 進行讀取動作n_writers_in
有多少 writer 進行寫入動作第一個 reader 需要 acquire rwlock
才能進行讀取,也就是說只要第一個 reader 拿到 rwlock
後,其它 readers 就能進行讀取
writer 需要 rwlock
才可以進行寫入,也就是沒有其它 reader 在讀取或是其它 writer 在寫入才能拿到 rwlock
TODO
TCB block 包含
首先 acquire glock_lock
,當 t
或 routine
為空指標 release glock_lock
,回傳錯誤代碼 EINVAL
用於表示 invalid argument,接著進行初始化 init()
init()
依據 TGID 透過 list_insert
加入到 linked list 中,從 Demystifying the Linux CPU Scheduler 1.3.1 可以知道 getpid()
與 gettid()
差異
getpid()
: get the TGID (the group leader PID, identifying the whole process)gettid()
: get the PID (which identifies a single thread, not a group)alloc_stack()
mmap 分配一塊大小為 STACK_SZ
+ GUARD_SZ
的空間
終於到了重頭戲,以下為 clone
的 function prototype,這是用來創建一個 ("child") process
stack
: 用於表示 child process 使用 stack 的位址,因為 Linux 的 stack 是由高位址到低位址長,所以指向高位址 thread_stack + STACK_SZ + GUARD_SZ
flags
: CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID
parent_tid
Where to store child TID, in parent's memorychild_tid
: Where to store child TID, in child's memoryflags
CLONE_VM
: the calling process and the child process run in the same memory spaceCLONE_FS
: the caller and the child process share the same filesystem informationCLONE_FILES
: the calling process and the child process share the same file descriptor tableCLONE_SIGHAND
: the calling process and the child process share the same table of signal handlersCLONE_THREAD
: the child is placed in the same thread group as the calling processCLONE_SYSVSEM
: then the child and the calling process share a single list of System V semaphore adjustment (semadj) values (see semop(2)).CLONE_PARENT_SETTID
: Store the child thread ID at the location pointed to by parent_tid (clone()) or cl_args.parent_tid (clone3()) in the parent's memory. The store operation completes before the clone call returns control to user spaceCLONE_CHILD_CLEARTID
: Clear (zero) the child thread ID at the location pointed to by child_tid (clone()) or cl_args.child_tid (clone3()) in child memory when the child exits, and do a wakeup on the futex at that addressEXP1
是 pid_t *parent_tid
,EXP1 = node->tid
EXP2
是 pid_t *child_tid
,EXP2 = node->tid
根據 第 4, 5, 6 週課堂問答簡記 提到
在呼叫 clone() 時給的 flags 中包含 CLONE_PARENT_SETTID 與 CLONE_CHILD_CLEARTID。
因此若不是傳送建立執行緒時新增的 node * 物件 insertedNode 中的 tid 的話,就無法在 thread_kill() 或是 killAllThreads() 函式中透過檢查 tid 來確認執行緒是否已經結束。
有個問題,為何使用編譯器最佳化 (-O2) 會 segmentation fault ?
__attribute__((noinline))
告訴編譯器,編譯時,對指定函數採取 noinline
或 always_inline
,更多 attribute 在 Declaring Attributes of Functions
asm
參考 Synchronization #2 Dave Eckhardt
mov $1, %%eax
看作綠色的圓圈xchg %%al, (%%rdi)
中 rdi
為藍色的方框,準備用 0 跟綠色圓圈交換,al
是標註 1 的綠色圓圈al
就是標註 0 的紅色圓圈test %%al,%%al
,透過 bitwise AND 得到 0,也就是 ZF 為 1,接著 je end
al
仍然是 1,test %%al,%%al
,透過 bitwise AND 得到 1, ZF 為 0,jmp mutexloop
繼續 loopfutex
節錄自 man futex
TEST (x86 instruction) explained that the
SF
is set to the most significant bit of the result of the AND. If the result is 0, theZF
is set to 1, otherwise set to 0.
al
is the least significant byte of eax register, which is typically used to return values from function calls
xchg
: atomic exchangetest
: 做 bitwise AND,當 lock
為 1 時,ZF 為 0 表示有人在使用 lockjne
: jump if not equal,ZF 為 0 繼續 loopTODO
If we have a constant stream of readers and a waiting writer, the writer will starve.
原先版本
改寫版本
sequence
相當於 version,透過 read_seqbegin
得到 sequence
,倘若 seq_lock->sequence
與這個 sequence
的值不相同,代表有 writer 正在寫入新的版本,因此 reader 需要再讀一次得到最新版本才能離開
從 read_seqbegin
可以看見,當 sequence
為偶數時才能讀,奇數代表有 writer 正在寫入,當 writer 寫完 sequence
才變成偶數