Shawn Hsiao
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2022q1 Homework4 (quiz4) contributed by < `shawn5141` > ## [測驗1](https://hackmd.io/@sysprog/linux2022-quiz4#%E6%B8%AC%E9%A9%97-1) :::success 延伸第 3 週測驗題的測驗 7,已知輸入必為大於 0 的數值 (即 x > 0),以下程式碼可計算 ⌈log2(x)⌉,也就是 ceil 和 log2 的組合並轉型為整數: ```cpp int ceil_log2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (EXP1) + 1; } ``` 請補完,使其行為符合預期。作答規範: EXP1 應以最簡潔的形式撰寫,且符合作業一排版規範 (近似 Linux 核心程式碼排版風格) EXP1 為表示式,限制使用 ^, &, |, <<, >> 這幾個運算子,可能會出現常數 EXP1 不該包含小括號 (即 ( 和 )) 為了自動批改的便利,變數出現的順序 (可從缺) 從左到右為 r, shift, x EXP1 不可包含 , 和 ; 這樣的分隔符號 (separator) ::: ### 延伸問題: #### 解釋上述程式碼運作原理 跟測驗七類似,ceil_log2是在用二分法去找MSB的位置。當最後剩下兩個位元,可以使用 x >> 1 來確認最高位元是0還是1。然後把原本都會做的`r | shift` 也直接拿到最後去當作return值。 ```cpp return (r | shift |x>>1) +1 ``` #### 改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless ```cpp= int ceil_log2(uint32_t x) { uint32_t r, shift; uint32_t tmp = ((uint32_t)(x-1) > x); x = x - 1 + tmp; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (r | shift | x >> 1) + ( ~tmp & 1); } ``` 使用 tmp 去紀錄 x 是否為0。如果是零的話,第 5 行就不要扣掉1,並且最後也把該加回來的 1 取消掉。 #### 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式),並解說應用案例 (提示: 在 Linux 核心排程器就有) ## [測驗2](https://hackmd.io/@sysprog/linux2022-quiz4#%E6%B8%AC%E9%A9%97-2) :::success 複習〈[你所不知道的 C 語言: bitwise 操作](https://hackmd.io/@sysprog/c-bitwise)〉,改寫[第 3 週測驗題](https://hackmd.io/@sysprog/linux2022-quiz3)的測驗 `11` 裡頭的 fls 函式 (fls 意謂 “find last set”),使其得以計算 [Find first set](https://en.wikipedia.org/wiki/Find_first_set): ```cpp #define BITS_PER_BYTE 8 #define BITS_PER_LONG (sizeof(unsigned long) * BITS_PER_BYTE) #include <stddef.h> static inline size_t ffs(unsigned long x) { if (x == 0) return 0; size_t o = 1; unsigned long t = ~0UL; size_t shift = BITS_PER_LONG; shift >>= 1; t >>= shift; while (shift) { if ((EXP2) == 0) { x >>= shift; EXP3; } shift >>= 1; t >>= shift; } return o; } ``` 請補完,使其行為符合預期。作答規範: * `EXP2` 和 `EXP3` 應以最簡潔的形式撰寫,且符合[作業一](https://hackmd.io/@sysprog/linux2022-lab0)排版規範 (近似 Linux 核心程式碼排版風格) * `EXP2` 和 `EXP3` 限制使用 ^, &, |, <<=, >>=, +=, -= 這幾個運算子,可能會出現常數 * `EXP2` 和 `EXP3` 不該包含小括號 (即 `(` 和 `)`) * 為了自動批改的便利,變數出現的順序 (可從缺) 從左到右為 `x`, `o`, `t`, `shift`,也就是說,你應該寫 `x ^ t` 而非 `t ^ x` * `EXP2` 和 `EXP3` 不可包含 `,` 和 `;` 這樣的分隔符號 (separator) ::: #### 解釋上述程式碼運作原理,並迴避 while, for, goto 等關鍵字以改寫出功能等價的實作 `EXP2 = x & t` `EXP3 = o |= shift` ```cpp= #define BITS_PER_BYTE 8 #define BITS_PER_LONG (sizeof(unsigned long) * BITS_PER_BYTE) static inline size_t ffs(unsigned long x) { size_t o = 1; unsigned long t = ~0UL; size_t shift = BITS_PER_LONG; shift >>= 1; t >>= shift; if ((word & t) == 0){ x >>= shift; o |= shift } shift >>= 1; t >>= shift; if ((word & t) == 0){ x >>= shift; o |= shift } shift >>= 1; t >>= shift; if ((word & t) == 0){ x >>= shift; o |= shift } shift >>= 1; t >>= shift; if ((word & t) == 0){ x >>= shift; o |= shift } shift >>= 1; t >>= shift; if ((word & t) == 0){ x >>= shift; o |= shift } shift >>= 1; t >>= shift; return o; } ``` #### 在 Linux 核心原始程式碼的 include/asm-generic/bitops 目錄找出相關程式碼,並探討應用案例 在[tools/include/asm-generic/bitops/__ffs.h](https://elixir.bootlin.com/linux/latest/source/tools/include/asm-generic/bitops/__ffs.h#L14) line 14 ```cpp= /** * __ffs - find first bit in word. * @word: The word to search * * Undefined if no bit exists, so code should check against 0 first. */ static __always_inline unsigned long __ffs(unsigned long word) { int num = 0; #if __BITS_PER_LONG == 64 if ((word & 0xffffffff) == 0) { num += 32; word >>= 32; } #endif if ((word & 0xffff) == 0) { num += 16; word >>= 16; } if ((word & 0xff) == 0) { num += 8; word >>= 8; } if ((word & 0xf) == 0) { num += 4; word >>= 4; } if ((word & 0x3) == 0) { num += 2; word >>= 2; } if ((word & 0x1) == 0) num += 1; return num; } ``` ## [測驗3](https://hackmd.io/@sysprog/linux2022-quiz4#%E6%B8%AC%E9%A9%97-3) :::success 考慮以下改寫自 Linux 核心的程式碼: ```cpp struct foo_consumer { int (*handler)(struct foo_consumer *self, void *); struct foo_consumer *next; }; struct foo { struct foo_consumer *consumers; unsigned long flags; }; #include <stdbool.h> /* * For foo @foo, delete the consumer @fc. * Return true if the @fc is deleted sfccessfully * or return false. */ static bool consumer_del(struct foo *foo, struct foo_consumer *fc) { struct foo_consumer **con; bool ret = false; for (con = &foo->consumers; *con; EXP4) { if (*con == fc) { *con = EXP5; ret = true; break; } } return ret; } ``` 請補完,使 `consumer_del` 行為符合註解規範。作答規範: 1. `EXP4` 和 `EXP5` 應以最簡潔的形式撰寫,且符合[作業一](https://hackmd.io/@sysprog/linux2022-lab0)排版規範 (近似 Linux 核心程式碼排版風格) 2. `EXP4` 和 `EXP5` 都包含指標操作 (如 `->`) ::: `EXP4 = *con = &(*con->next)` `EXP5 = con = fc->next` ```graphviz digraph g{ rankdir=LR; node [shape=record]; prev [label = "{<data>prev |<ref>}"]; fc [label = "{<data>fc |<ref>}"]; next [label = "{<data>next |<ref>}"]; node [shape=none] none1 [label = ""]; none2 [label = ""]; con; edge[weight=2]; none1 -> prev [arrowhead=vee]; prev:ref:c -> fc [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false]; fc:ref:c -> next [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false]; next:ref:c -> none2 [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false]; edge[weight=next]; con -> prev:ref; } ``` ### 1. 解釋上述程式碼運作原理,並探討這樣區分 foo 和 foo_consumer 的好處 因為使用indirect pointer `con`,所以在`*con == fc`時,其實`con`是指向`prev`的`next`。所以要跳過fc這一點的話,必須要讓`con`現在指到的`next`跳過`fc`,直接指到`next`,也就是fc->next。所以EXP5可以等於 `con = fc->next` 或者是 `con = (*con)->next` - 探討這樣區分 foo 和 foo_consumer 的好處 類似`list_head`的做法,能夠類似linked list的功能直接放到struct當中。這樣就只要實作一套foo_consumer的操作就可以了。 ### 在 Linux 核心原始程式碼相似技巧的程式碼 (例如 uprobe),並探討應用案例 > what is [upprobe](https://docs.kernel.org/trace/uprobetracer.html): 本質上是在指定的探測點(比如函數的某行, 函數的入口地址和出口地址, 或者內核的指定地址處)插入一組處理程序. 內核執行到這組處理程序的時候就可以獲取到當前正在執行的上下文信息, 比如當前的函數名, 函數處理的參數以及函數的返回值, 也可以獲取到寄存器甚至全局數據結構的信息 good article: https://blog.px.dev/ebpf-function-tracing/ ![](https://i.imgur.com/VSkAr5O.png) ```cpp= struct trace_probe { struct list_head list; struct trace_probe_event *event; ssize_t size; /* trace entry size */ unsigned int nr_args; struct probe_arg args[]; }; ``` --- ## [測驗4](https://hackmd.io/@sysprog/linux2022-quiz4#%E6%B8%AC%E9%A9%97-4) :::success 以下嘗試用 [lab0](https://hackmd.io/@sysprog/linux2022-lab0) 提及的 [setjmp](https://man7.org/linux/man-pages/man3/setjmp.3.html) 和 [longjmp](https://man7.org/linux/man-pages/man3/setjmp.3.html),用以實作〈[你所不知道的 C 語言: goto 和流程控制篇](https://hackmd.io/@sysprog/c-control-flow)〉闡述的 [coroutine](https://en.wikipedia.org/wiki/Coroutine),參考的程式執行輸出如下: ``` Task 0: n = 3 Task 1: n = 3 Task 0: resume Task 1: resume Task 0: resume Task 0: complete Task 1: resume Task 1: complete ``` 原始程式碼如下: (檔名 `jmp.c`) ```cpp= #include <setjmp.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "list.h" struct task { jmp_buf env; struct list_head list; }; static LIST_HEAD(tasklist); static void (**tasks)(void *); static int ntasks; static jmp_buf sched; static void task_add(struct list_head *tasklist, jmp_buf env) { struct task *t = malloc(sizeof(*t)); memcpy(t->env, env, sizeof(jmp_buf)); INIT_LIST_HEAD(&t->list); list_add_tail(&t->list, tasklist); } static void task_switch(struct list_head *tasklist) { jmp_buf env; if (!list_empty(tasklist)) { struct task *t = list_first_entry(tasklist, struct task, list); EXP6; memcpy(env, t->env, sizeof(jmp_buf)); free(t); longjmp(env, 1); } } static void task_join(struct list_head *tasklist) { jmp_buf env; while (!list_empty(tasklist)) { struct task *t = list_first_entry(tasklist, struct task, list); EXP7; memcpy(env, t->env, sizeof(jmp_buf)); free(t); longjmp(env, 1); } } void schedule(void) { static int i; srand(0xCAFEBABE ^ (uintptr_t) &schedule); /* Thanks to ASLR */ setjmp(sched); while (ntasks-- > 0) { int n = rand() % 5; tasks[i++](&n); printf("Never reached\n"); } task_join(&tasklist); } /* A task yields control n times */ void task0(void *arg) { jmp_buf env; static int n; n = *(int *) arg; printf("Task 0: n = %d\n", n); if (setjmp(env) == 0) { task_add(&tasklist, env); EXP8; } for (int i = 0; i < n; i++) { if (setjmp(env) == 0) { task_add(&tasklist, env); task_switch(&tasklist); } printf("Task 0: resume\n"); } printf("Task 0: complete\n"); longjmp(sched, 1); } void task1(void *arg) { jmp_buf env; static int n; n = *(int *) arg; printf("Task 1: n = %d\n", n); if (setjmp(env) == 0) { task_add(&tasklist, env); EXP9; } for (int i = 0; i < n; i++) { if (setjmp(env) == 0) { task_add(&tasklist, env); task_switch(&tasklist); } printf("Task 1: resume\n"); } printf("Task 1: complete\n"); longjmp(sched, 1); } #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) int main(void) { void (*registered_task[])(void *) = {task0, task1}; tasks = registered_task; ntasks = ARRAY_SIZE(registered_task); schedule(); return 0; } ``` 請補完,使行為符合註解規範。作答規範: 1. `EXP6`, `EXP7`, `EXP8`, `EXP9` 應以最簡潔的形式撰寫,且符合[作業一](https://hackmd.io/@sysprog/linux2022-lab0)排版規範 (近似 Linux 核心程式碼排版風格) 2. `EXP6` 和 `EXP7` 都包含 List API 的使用 3. `EXP8` 和 `EXP9` 應有函式呼叫 ::: ### 1. 解釋上述程式碼運作原理,舉出上述 [coroutine](https://en.wikipedia.org/wiki/Coroutine) 實作的限制 (注意 stack) ###### `task_add`: 除了init list_head,也讓每個task都有自己的jmp_buf env。最後透過 `list_add_tail` 把list_head加入list tail。 ###### `task_switch` 做的事情就是把放在第一個的list_head 取出來,然後透過longjmp跳過去。然後`task_switch` will use `longjmp(env, 1);` to jump to where setjmp(env) ###### `schedule` 透過`setjmp(sched);` 把當下的context存到shed這個jmp_buf中。並且開始呼叫task0 or task1 ###### `task0` 和`task1` 基本上長得一樣。一開始呼叫`setjmp(env)` 把local context存到 local的env中,然後呼叫`task_addf` 把自己這個task加到link list中,並把local env 的資料也一併放進去。(因為是`setjmp(env)==0`所以一開始呼叫時就會執行) 真的要分析的話,還是必須從ouput下手。 ``` Task 0: n = 3 (step1) Task 1: n = 3 (step2) Task 0: resume (step3) Task 1: resume (step4) Task 0: resume (step5) Task 0: complete (step6) Task 1: resume (step7) Task 1: complete (step8) ``` 從output可以發現 `Task 0` 印出 `n=3` (step1) 之後做了一次`context switch`,所以讓 `EXP8=longjmp(sched, 1)` 就會跳到scheduler的一開始,然後進到 `task1` 並印出 `n=3` (step2)後又跳回 `task0` ,代表 `EXP9=longjmp(sched, 1);`。 當 `task0` 呼叫 `task_switch` 的時候,會取出存放在 linked list head 的 task,然後在 `free(t)` 後,就跳到這個`env`存放的`context`。(跳回 `task0` 的時候,`task list` 中還有著 `task1` 的 list_head)。再跳到task0之後,就會進到for迴圈,然後透過 `setjmp` 再次設定 `env` ,並透過 `task_add` 把自己加到 `task list` 中,並透過 `task_switch` 取出 `task1` 並跳過去。`task1` 也執行同樣的事情,進到回圈然後設定 `setjmp(env)` ,然後再跳回`task0`。這次因為 `longjmp(env,1)` 所以 `setjmp(env)==0` 會是`false`,也就進到`printf("Task 0: resume\n");`這一行(step3)。然後再次迴圈,並設定 `setjmp(env)` 和把自己放到task list tail和進行 `context switch`到 task1。 接著因為是從longjmp跳過來的,所以就不進到 `setjmp(env)==0` 的`condition` 中,而是直接進到 `printf("Task 1: resume\n");` (step4)。並且開始第二次的回圈。因為n這邊是設定3,所以總共 `task0` 和 `task1` 共會印出 `resume` 兩次(第一次還沒印出來就context switch了)。最後在執行完回圈後,就會印出 `complete` (step6)。然後使用 `longjmp(sched, 1);` 跳回 `schedule` 最開始。這時候因為 `ntasks<0` 了,所以不會進到迴圈,而是直接跳到task_join,把剩餘的task處理完。 > EXP6= list_del(&list) > EXP7= list_del(&list) > EXP8= longjmp(sched, 1) > EXP9= longjmp(sched, 1) <details> <summary>完整程式</summary> ```cpp= #include <setjmp.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "list.h" struct task { jmp_buf env; struct list_head list; }; static LIST_HEAD(tasklist); static void (**tasks)(void *); static int ntasks; static jmp_buf sched; static void task_add(struct list_head *tasklist, jmp_buf env) { struct task *t = malloc(sizeof(*t)); memcpy(t->env, env, sizeof(jmp_buf)); INIT_LIST_HEAD(&t->list); list_add_tail(&t->list, tasklist); } static void task_switch(struct list_head *tasklist) { jmp_buf env; if (!list_empty(tasklist)) { struct task *t = list_first_entry(tasklist, struct task, list); list_del(&t->list); memcpy(env, t->env, sizeof(jmp_buf)); free(t); longjmp(env, 1); } } static void task_join(struct list_head *tasklist) { jmp_buf env; while (!list_empty(tasklist)) { struct task *t = list_first_entry(tasklist, struct task, list); list_del(&t->list); memcpy(env, t->env, sizeof(jmp_buf)); free(t); longjmp(env, 1); } } void schedule(void) { static int i; srand(0xCAFEBABE ^ (uintptr_t) &schedule); /* Thanks to ASLR */ setjmp(sched); while (ntasks-- > 0) { int n = 3; tasks[i++](&n); printf("Never reached\n"); } task_join(&tasklist); } /* A task yields control n times */ void task0(void *arg) { jmp_buf env; static int n; n = *(int *) arg; printf("Task 0: n = %d\n", n); if (setjmp(env) == 0) { task_add(&tasklist, env); longjmp(sched, 1); } for (int i = 0; i < n; i++) { if (setjmp(env) == 0) { task_add(&tasklist, env); task_switch(&tasklist); } printf("Task 0: resume\n"); } printf("Task 0: complete\n"); longjmp(sched, 1); } void task1(void *arg) { jmp_buf env; static int n; n = *(int *) arg; printf("Task 1: n = %d\n", n); if (setjmp(env) == 0) { task_add(&tasklist, env); longjmp(sched, 1); } for (int i = 0; i < n; i++) { if (setjmp(env) == 0) { task_add(&tasklist, env); task_switch(&tasklist); } printf("Task 1: resume\n"); } printf("Task 1: complete\n"); longjmp(sched, 1); } #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) int main(void) { void (*registered_task[])(void *) = {task0, task1}; tasks = registered_task; ntasks = ARRAY_SIZE(registered_task); schedule(); return 0; } ``` </details> ### 2. 對比 [concurrent-programs](https://github.com/sysprog21/concurrent-programs) 裡頭的 coroutine 程式碼,如 [tinync](https://github.com/sysprog21/concurrent-programs/blob/master/tinync) 和 [preempt_sched](https://github.com/sysprog21/concurrent-programs/blob/master/preempt_sched),嘗試引入 UNIX Signal 來做到 preemptive scheduling ### 3. 對比〈[A brief history of the Linux Kernel’s process scheduler: The very first scheduler, v0.01](https://dev.to/satorutakeuchi/a-brief-history-of-the-linux-kernel-s-process-scheduler-the-very-first-scheduler-v0-01-9e4)〉,解說早期 Linux 核心 CPU 排程器的行為,並利用上述程式碼來模擬 ## [測驗5](https://hackmd.io/@sysprog/linux2022-quiz4#%E6%B8%AC%E9%A9%97-5) :::success 〈[你所不知道的 C 語言:前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor)〉已提過若干 Linux 核心的巨集,不難發現這些巨集經歷多次演變。Linux 核心一度有個巨集 `ACCESS_ONCE`,其作用是確實地讀取所指定記憶體位址的內容值,且限這一次,其原始定義如下: ```cpp #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)) ``` > 注意 `volatile` 關鍵字的使用 `ACCESS_ONCE` 巨集的使用情境,可參照 Linux v4.0 的 `kernel/locking/mutex.c`: ```cpp while (true) { struct task_struct *owner; ... /* * If there's an owner, wait for it to either * release the lock or go to sleep. */ owner = ACCESS_ONCE(lock->owner); if (owner && !mutex_spin_on_owner(lock, owner)) break; ``` 然而,如果不使用 `ACCESS_ONCE` 巨集,程式碼如下: ```cpp while (true) { struct task_struct *owner; ... /* * If there's an owner, wait for it to either * release the lock or go to sleep. */ owner = lock->owner; if (owner && !mutex_spin_on_owner(lock, owner)) break; ``` 由於,編譯器偵測到第 8 行的 `owner = lock->owner` 在迴圈中沒有被更改,所以其最佳化機制可能將第 8 行的程式碼搬出 while 迴圈之外,如此不用每次都實際地讀取 `lock->owner`,其程式碼變成: ```cpp struct task_struct *owner = lock->owner; while (true) { ... if (owner && !mutex_spin_on_owner(lock, owner)) break; ``` 但問題來了,`lock->owner` 有可能被其它核心執行緒修改,從而造成資料不一致。因此使用 `ACCESS_ONCE` 巨集可以防止編譯器做相關最佳化工作,並確保每次都能到實體記憶體位址讀取。其做法便是將某參數暫時性地轉換成具備 `volatile` 的型態。如此,存取該參數在非引入 `ACESS_ONCE` 巨集之處 (不具 `volatile` 特性),仍可享用編譯器最佳化的好處。 在 [ACCESS_ONCE() and compiler bugs](https://lwn.net/Articles/624126/) 則提及 Linux 核心捨棄上述 `ACCESS_ONCE` 巨集,改為 `READ_ONCE` 巨集。在 Linux 核心原始程式碼曾存在以下註解: > `ACCESS_ONCE` will only work on scalar types. For union types, ACCESS_ONCE on a union member will work as long as the size of the member matches the size of the union and the size is smaller than word size. > > The major use cases of `ACCESS_ONCE` used to be > > 1. Mediating communication between process-level code and irq/NMI handlers, all running on the same CPU, and > 2. Ensuring that the compiler does not fold, spindle, or otherwise mutilate accesses that either do not require ordering or that interact with an explicit memory barrier or atomic instruction that provides the required ordering. 以下是可能的實作: ```cpp= #include <stdint.h> #include <string.h> #include <stdlib.h> #define __READ_ONCE_SIZE \ ({ \ switch (size) { \ case 1: \ *(uint8_t *) res = *(volatile uint8_t *) p; \ break; \ case 2: \ *(uint16_t *) res = *(volatile uint16_t *) p; \ break; \ case 4: \ *(uint32_t *) res = *(volatile uint32_t *) p; \ break; \ case 8: \ *(uint64_t *) res = *(volatile uint64_t *) p; \ break; \ default: \ memcpy((void *) res, (const void *) p, size); \ } \ }) static inline void __read_once_size(const volatile void *p, void *res, int size) { __READ_ONCE_SIZE; } #define READ_ONCE(x) \ ({ \ union { \ typeof(x) __val; \ DECL0; \ } __u; \ __read_once_size(&(x), __u.__c, sizeof(x)); \ __u.__val; \ }) ``` `READ_ONCE` 巨集會判斷變數的寬度,針對寬度為 1, 2, 4, 8 位元組的變數,將其轉換為對應的純量 (scalar) 型態並增加 volatile 關鍵字,而對於其他資料寬度的類型,改呼叫 `memcpy` 來避免編譯器對該變數進行最佳化。 請補完程式碼,使其運作符合預期。書寫規範: 1. 符合一致的程式碼風格,使用最精簡的方式撰寫 2. `DECL0` 為變數宣告,不該存在 `;` 和 `,` 分隔字元 ::: ### 1. 解釋上述程式碼運作原理,並佐以〈[ACCESS_ONCE() and compiler bugs](https://lwn.net/Articles/624126/)〉及〈[你所不知道的 C 語言:編譯器和最佳化原理篇](https://hackmd.io/@sysprog/c-compiler-optimization)〉進行說明 #### [volatile](https://blog.csdn.net/Roland_Sun/article/details/107365134) > 在C语言中,volatile关键字的作用是:声明这个变量易变,不要把它当成一个普通的变量,做出错误的优化。保证CPU每次都从内存重新读取变量的值,而不是用寄存器中暂存的值。注意,这里说的是寄存器中缓存的值,而不是CPU缓存中存的值。很多英文文档里面都说了Cache,容易让人产生误解。 #### DECL0 = char[1] __c 這邊的 `__u.__c` 是拿來當作指標儲存被`volatile`標注的`union`,所以使用char array。 ### 2. 研讀〈[Why the “volatile” type class should not be used](https://docs.kernel.org/process/volatile-considered-harmful.html)〉和〈[並行程式設計: Atomics 操作](https://hackmd.io/@sysprog/concurrency-atomics)〉,並分析近期 [include/asm-generic/rwonce.h](https://github.com/torvalds/linux/blob/master/include/asm-generic/rwonce.h) 中 `READ_ONCE` / `WRITE_ONCE` 巨集的實作和其演化 (使用 `git log` 觀察) :::danger 注意書寫規範:中英文間用一個半形空白字元或標點符號區隔 :notes: jserv ::: :::info 了解,感謝提醒。 :notes: Shawn :::

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully