sysprog
      • 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
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • 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
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Help
Menu
Options
Versions and GitHub Sync 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
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
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
# 2025-04-29 問答簡記 ![image](https://hackmd.io/_uploads/S10cDraZ0.png) "**You never fail until you stop trying.**" ― Albert Einstein (當你停下嘗試的腳步,就意味著失敗) ## 從 `ioctl` 介面談作業系統和程式語言的演化 ![image](https://hackmd.io/_uploads/ry9agnpkxe.png) 在 [UNIX Version 7 的手冊](https://s3.amazonaws.com/plan9-bell-labs/7thEdMan/vol1/man2.bun.txt)中,`ioctl` 系統呼叫宣告為如下: ```c ioctl(int fildes, int op, struct sgttyb *argp); ``` 利用獨立的宣告,把 `argp` 指向的真實型別交給程式設計師決定,並未提供任何「泛用指標」機制 (搭配看 [ioctl(2)](https://man.freebsd.org/cgi/man.cgi?manpath=Unix+Seventh+Edition&query=ioctl&sektion=2))。該寫法源於 [K&R C](https://en.wikipedia.org/wiki/C_(programming_language)#K&R_C) 時代缺乏 `void *`;當時若要傳遞任意資料,多半改用 `char *` 或任何合適的結構體指標。 `struct sgttyb` 是 UNIXv7 終端機 (tty) 子系統的核心資料結構,用以封裝速度、特殊字元與模式旗標等設定。終端機驅動程式將目前狀態保存在這個結構體中,應用程式若想查詢或修改終端機行為,必須把 `sgttyb` 實例的位址傳入 `ioctl`。其典型流程如下: 1. 準備 `struct sgttyb buf` 變數 2. 以 `ioctl(fd, TIOCGETP, &buf)` 取得現有參數,核心把內部狀態複製到使用者空間的 `buf` 3. 程式修改 `buf.sg_flags`, `buf.sg_ispeed`, `buf.sg_ospeed` 等欄位後,再呼叫 `ioctl(fd, TIOCSETP, &buf)` 或 `TIOCSETN` 把更新後的值寫回核心 在 UNIXv7 中,該呼叫方式正式取代更早期的 `stty` 與 `gtty` 系統呼叫,後二者不過是內部直接轉呼叫 `ioctl` 的封裝。因此 `ioctl` 的第三參數必須接受 `struct sgttyb *`,而當時尚無 `void *`,於是只能明確寫出結構體指標型別或退而使用 `char *`。 所有與終端機相關的 `ioctl` 程式碼(例如 `TIOCGETP`, `TIOCSETP`, `TIOCSETN`)在 `<sgtty.h>` 中定義,並規定第三參數應指向 `sgttyb`;唯有透過這個結構體,核心與使用者程式才能在單一系統呼叫中交換多個屬性值。例如 `sg_ispeed` 與 `sg_ospeed` 以常數 0–15 編碼對應 50, 75, 110, ..., 9600 及二個外部擴充速率的 baud rate,而 `sg_flags` 的位元組合則控制 raw 模式、行緩衝模式、大小寫自動轉換與本地 `echo` 等行為。 > 參見: [Seventh Edition Unix terminal interface](https://en.wikipedia.org/wiki/Seventh_Edition_Unix_terminal_interface?utm_source=chatgpt.com) 由於這整套介面將結構體本身視為交換資料的載體,所以 `struct sgttyb` 與 UNIXv7 的 `ioctl` 介面形成緊密耦合:沒有 `sgttyb` 的欄位定義,就無法解釋 `ioctl` 控制碼的語意;而離開 `ioctl`,`sgttyb` 也失去與核心溝通的管道。 Linux 的手冊 [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html) 仍保留這段歷史說明: > the third argument is an untyped pointer to memory. It’s traditionally `char *argp` (from the days before `void *` was valid C) 同頁亦列出數個舊版原型宣告:UNIXv7 採取 `struct sgttyb *`、4.3BSD 直接寫成 `char *`,而 SysV Release 3/4 則允許「省略型別」或使用省略號。 ANSI C (C89,[ISO/IEC 9899:yyyy](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf)) 正式引入 `void` 與 `void *`。標準在 §6.2.5 給出定義: > The void type comprises an empty set of values; it is an incomplete object type that cannot be completed. 在 [ISO/IEC 9899:yyyy](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf) §6.3.2.3 指定 `void *` 的轉換規則: > A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer. 這段文字賦予 `void *` 作為真正「泛用指標」的語意,消除過去 `char *` 需要顯式 cast 的繁瑣與風險。 [ISO/IEC 9899:yyyy](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf) 標準同時限制 `void *` 無法直接做指標運算 (§6.5.6 Additive operators): > For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type. 因此,欲做位元組位移仍須先轉回 `char *`。 C 語言演化後,glibc 與多數 BSD 系統把 `ioctl` 的第三參數改宣告為 `void *`,而 Linux 核心為了與系統呼叫 ABI 相容,內部仍維持 `unsigned long arg`,真正的位址或數值由巨集 `_IOR` / `_IOW` / `_IOWR` 處理和判斷;使用者空間呼叫時再以 `void *` 帶入。 LKMPG 的 [pull request #314](https://github.com/sysprog21/lkmpg/pull/314) 儘管沒被收錄,但引來上述的討論,值得留意。 --- ## 善用人工智慧工具 > [DeepWiki: LKMPG](https://deepwiki.com/sysprog21/lkmpg) Cognition AI 推出一款名為 DeepWiki 的工具,針對 GitHub 上的程式碼倉庫,藉由人工智慧技術,產生詳盡的專案說明文件,還配備互動式圖表,例如類別階層圖、元件相依圖,輔助使用者視覺化理解專案結構。以 LKMPG 來說,儘管視覺化的展現很有限 (Makefile 其實不值得探討,真正的內文在 LaTeX 內文),使用者可在 DeepWiki 頁面下方,進行提問和要求對比特定概念間的落差,效果尚可,不過讀者務必參照第一手材料來驗證。 --- ## 討論《Demystifying the Linux CPU Scheduler》 > [筆記](https://hackmd.io/@salmonii/rJUas6NJle) ### HenryChaing - [x] SPSC ```shell $ ps -e -o pid,ppid,pgid,comm | less ``` ### devarajabc server 如何及時更新設定檔? ### EricccTaiwan - `getpid()` 永遠 return 成功,因為有 process 才能執行 `getpid()` - softlookup 是核心防止 CPU 被長時間執行任務完全佔據的手段,但 softlookup 的偵測是可關閉 --- :::warning 討論 [Homework3](https://hackmd.io/@sysprog/linux2025-homework3) ::: ## BigMickey69 > [kxo](https://hackmd.io/@BigMickey/linux2025-homework3) ### 原本 kernel <--> user 通訊成本為何較高? -> 因為 Page size 。 主記憶體管理單元 (MMU) 的最小單位管理為 Page , 所有記憶體讀取、寫入、配置記憶體時都是它為單位。一般消費者級別的處理器中, Page 大小為 4 Kib。 kernel 與 user 溝通時,其通訊時,資料會寫於一 device file 之中,寫入資料越大不只是需要花比較多時間寫入(差別很小),更是提升了資料跨越 Page 的風險,一旦跨越就得花更多時間處理第二個 Page。 #### 為甚麼只是多一個 Page 卻會增加通訊成本? Linux 核心的責任是 page walking,若此新的一頁不在 轉譯後備緩衝區 (Translation Lookaside Buffer or TLB) 上,就得走 (walk) 過一遍。若是甚至還沒被 touch 過的記憶體而觸發 Page fault,就得配置實體記憶體、映射到 process address space 中、更新 Page table 等... TLB 也是 cache,也會有 cache miss (TLB miss) shared memory 的實作手段: 將不同的 virtual memory area (VMA) 映射 (map) 到同一個 physical pages ### 遊戲的棋盤該用哪個單字: table vs. board :::info **🔹Table:** Oxford English Dictionary: "A flat surface, especially one that is used for a particular activity such as eating, writing, or playing games." Cambridge Dictionary: "A flat surface, usually supported by legs, used for putting things on." **🔹Board:** Oxford English Dictionary: "A flat, thin, rectangular object used as a surface to play games on, especially board games." Cambridge Dictionary: "A flat object or surface on which a game is played." ::: 在我們假設的情況(遊戲), Board 是遊戲進行的地方,有明確規則、卡牌或是旗子放置位子、棋盤等。換句話說 Board 就是遊戲本身。Table 較抽象, Table 可視為遊玩空間,可有可無的存在 ### NULL == `(void *) 0` 在核心模式中,要如何解讀? 在核心模式中,乃至 C lib 中使用 `#include <stdlib.h>`,此式為 True。定義上 NULL 為指向 0 的指標,而 0 為非法地址。 結論:NULL 就是 ((void *) 0),代表「沒有指向任何記憶體」的指標 ### 為何要有多種 page size? `TODO: 去算盤書找為何當初設計用 4KB` [Computer Organization and Design: The Hardware/Software Interface, 5/e](https://drive.google.com/file/d/1pSJeDf3QJ8m530Sw0n7qdoy8_dmnThFQ/view?usp=drive_link) 中的第 5.6 章 ``` Many design choices in virtual memory systems are motivated by the high cost of a page fault. A page fault to disk will take millions of clock cycles to process. (The table on page 378 shows that main memory latency is about 100,000 times quicker than disk.) This enormous miss penalty, dominated by the time to get the first word for typical page sizes, leads to several key decisions in designing virtual memory systems: ■ Pages should be large enough to try to amortize the high access time. Sizes from 4 KiB to 16 KiB are typical today. New desktop and server systems are being developed to support 32 KiB and 64 KiB pages, but new embedded systems are going in the other direction, to 1 KiB pages. ■ Organizations that reduce the page fault rate are attractive. The primary technique used here is to allow fully associative placement of pages in memory. ... ``` 算盤書中寫道 Page size 是為了 armortize 存取時間,具體為甚麼 4 kiB 得從別處尋找答案。 一個 2009 年 IEEE 國際資訊重用與整合的會議論文 [Using 4KB Page Size for Virtual Memory is Obsolet](https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=5211562) 中提出 4KiB Page size 已過時的概念,並提供許多實驗數據來佐證他的論點,最後提出了兩個結論: - 16 KiB base pages greatly reduce iTLB misses with minimal memory overhead. - 256 KiB superpages help with large data segments (reducing dTLB misses). 第二點有問題,因為他最大只有測到 256 KiB ,因此實際上 superpages 理想大小極有可能會更大。但就論第一點,若 16 KiB 有助於減少 iTLB ,那麼為甚麼現代消費者級別處理器仍然使用 4 KiB? 以下是幾個可能的原因: - 相容性 - 幾十年前 x86 架構就將 4 KiB 作為 Page 大小,從作業系統、虛擬機、啟動程式到 malloc/mmap、JIT、垃圾回收等等等都假設這個單位。 - 記憶體碎片 Memory Fragmentation - Page 大小越大越會遇到記憶體碎片的問題 - 記憶 - 以 16 KiB 為單位設定讀/寫/執行權限將更加不精確,提升資安的風險 - 早已有 superpage 等技術 - 若有更大的 Page size 需求,現代 Intel 與 AMD 處理器可透過 THP 或 MAP_HUGETLB 等使用 2 MiB/1 GiB 的巨大 Page,當然 superpage 本身也有許多性能相關問題 https://en.wikipedia.org/wiki/CPU_cache (PIPT, VIPT, VIVT) ![translation](https://hackmd.io/_uploads/r1hO37ezgg.png) ## ? https://hackmd.io/@sysprog/c-linked-list --> 列出解說錄影的錯誤 TODO: 用 indirect pointer 實作 insert_tail ``` void insert_tail(List *list, Node *New_node) { Node *prev = NULL; Node **indirect = &list->head; //多了一個指向head的指標 while (*indirect!= NULL){ indirect = &(*indirect)->next; } *indirect = New_node; } `//此程式碼解決了需要判斷原為空link或非空link執行不同的指令 Now! 等你寫程式啊,同學 ``` ## weiso131 > [kxo](https://hackmd.io/@weiso131/linux2025-homework3) commit [98edb0](https://github.com/weiso131/kxo/commit/98edb068e9b806b4c2cb14b2ea03d22ad76af7a8) commit message 的問題是「沒有探討如何重現 (reproduce) 你發現的疑慮」 如何讓 `xo-user` 可在 non-root (一般使用者) 權限執行? $\to$ 變更 `/dev/kxo` 裝置檔案的存取權限後,是否有安全/正確性顧慮? 從 ``` crw------- 1 root root 235, 0 May 1 20:48 kxo ``` 改為 ``` crw-rw-rw- 1 root root 235, 0 May 1 20:48 kxo ``` 多個使用者 (account) 「同時」開啟 bill, amy, johnny TODO: per-account state https://en.wikipedia.org/wiki/Linux_namespaces UID 100 101 102 [ Amy ] [ Johnny ] [ Bill ] Ctrl-Q Ctrl-P 數學分析著重於,用幾個 bit 得以表示 state transition (encoding) MVC pattern Claude sonnet-3.7 sandbox 鑑賞 (!) ### 沒有探討如何重現 (reproduce) 關於這個問題,仔細觀察我的 commit [98edb0](https://github.com/weiso131/kxo/commit/98edb068e9b806b4c2cb14b2ea03d22ad76af7a8) 第一段其實有提到: ``` when the user space program was paused using a keyboard event and then immediately interrupted with Ctrl+C, subsequent executions or other windows running the program ``` 這邊其實就有討論這個錯誤是如何發生的,然而 `a keyboard event` 似乎沒有 `Ctrl + P` 來的明確。 所以我使用 git rebase 修改 commit message [ef25b4d](https://github.com/weiso131/kxo/commit/ef25b4dc69e175d0a9259c59021a322ff9bf9fd1) ### 更改 kxo 權限 在 main.c 加入此函式 ```c static char* kxo_devnode(const struct device *dev, umode_t *mode) { if (mode) *mode = 0666; return NULL; } ``` 並且在 `kxo_init` 做更改 ```diff #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) kxo_class = class_create(THIS_MODULE, DEV_NAME); #else kxo_class = class_create(DEV_NAME); #endif + kxo_class->devnode = kxo_devnode; ``` 在終端機執行 `ls -l /dev` ``` crw-rw-rw- 1 root root 510, 0 5月 5 22:59 kxo ``` 可以看到它的權限已經變更為所有人都能讀寫 然而,若是我直接執行 `./xo-user` ,會發生段錯誤 之後 kxo 將無法移除 過一段時間風扇就開始運轉,使用 `top` 觀察 cpu 使用情形 ``` PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 479 root 20 0 0 0 0 I 33.9 0.0 0:26.29 kworker/u32:14-kxod 4684 root 20 0 0 0 0 I 16.6 0.0 0:22.61 kworker/u32:0-events_freezable_pwr_efficient 4388 root 20 0 0 0 0 I 15.9 0.0 0:30.89 kworker/u32:6-kxod ``` 發現 kxo 相關的任務佔用了不少 cpu 資源 觀察 sysfs 的讀取權限 ``` ls -l /sys/class/kxo/kxo/kxo_state -rw-r--r-- 1 root root 4096 5月 7 01:35 /sys/class/kxo/kxo/kxo_state ``` 發現它在使用者端是只能讀不能寫 然而,嘗試更改其權限會出現編譯錯誤 ``` BUILD_BUG_ON_ZERO((perms) & 2) ``` 基於安全考量,不能將 sysfs 的權限設定成全域可寫入 ### per-account state #### 棋盤狀態 - 核心模組: 儲存當前棋盤與相關狀態 - 使用者: 儲存當前棋盤狀態、所有棋局的移動紀錄 - 棋盤資料傳遞: 誰下了哪一步棋 在我原本的 kxo 專案,為了讓不同的使用者觀察到同一個盤面,且在任一使用者按下 Ctrl + Q 時同步輸出紀錄,移動紀錄是被存放在核心的。 然而,現在的情況是考慮多個使用者獨立的狀態,而移動紀錄也只會被存取一次,那將移動紀錄存放在核心空間就顯得不必要,而且移動到使用者端會有以下優點: 1. 在任何使用者按下 ctrl + Q 之後,就不需要再跟核心模組有任何互動,這能減少核心模組的工作負擔 2. 減少核心 heap 空間浪費 3. 讓使用者端保有棋盤紀錄,能讓資料傳遞只需要 `誰下了哪一步棋` 此時核心模組的主要功能就是裁判,詳細功能如下: 1. 檢查新的這步棋是否合法 2. 判斷勝負 3. namespace 4. 使用者身分驗證 第一個功能考慮到之後需要做到 `人工智慧程式可指定運作於使用者/核心層級` 從使用者傳過來的移動不能保證是合法的,因此有驗證的必要 第二個功能是原本就存在的功能 第三、四個功能會獨立出來討論 #### namespace - 使用者 id - 使用者當前棋盤狀態 - 使用者自己的 kfifo namespace 對應的資料結構可利用 user id 進行搜尋 而搜尋的時間複雜度與使用者的數量上限則與使用哪種資料結構有關 可能的方法有 1. 陣列 2. 鏈結串列 3. 鏈結串列組成的陣列 對於這個專案,我認為使用陣列可能就足夠使用。如果使用者的數量過多,可能會導致每個使用者的服務品質不佳。因此需要限制最大的連線人數。在這個前提下就可以直接使用陣列來儲存 namespace 。 至於如何快速取得這個陣列哪些位置是空的,則可以使用鏈結串列做到 O(1) 的操作。 關於為什麼會選擇讓每個使用者擁有自己的 kfifo 。一個很明顯的好處是可以減少 lock 的使用, `kfifo` 雖然提供 `kfifo_out_peek` 可以先判斷 kfifo 的第一項是不是當前使用者在等待的資料,再決定要不要拿出來。 但 `kfifo` 只保證 SPSC 的情況下是 lock free 的,這種情況得加上 lock。 再來,觀察程式碼與其運作行為,可以發現 workqueue 是 first in first out , ai1, ai2, drawboard 彼此之間是不會發生競爭的。然而,如果 time_interrupt 遇到已經分出勝負的情況,就會有競爭的問題,producer 跟 consumer lock 就是這樣才需要存在。 目前程式的問題就是,判斷勝負的部份是 time_interrupt 處理,若直接將 time_interrupt 需要 lock 的部份刪掉,非常有可能(基本上是每次)會發生 ai 已經分出勝負但沒有執行 drawboard 把資料放到 kfifo,導致最後一步使用者總是看不到。 如果能將判斷勝負的東西放在最後一步丟進 kfifo 再處理 應該是能把 consumer, producer lock 全部拿掉(這種情況符合 Single producer single consumer) - 參考第十一周測驗題 根據上面的論述,若每個使用者擁有自己的 kfifo ,是能做到 lock free 的。 #### 使用者身分驗證 ### 用幾個 bit 得以表示 state transition ## rota1001 如何從 userspace 「控制」kxo (LKM; Linux kernel module)? 註冊 `struct file_operations`,提供從 userspace 存取 kxo 的 VFS operation 利用 cookie 進行使用者的識別 hash? salt session ID > https://hackmd.io/J5wYE-XzSkaekqe-OQw6mg TODO: 分析 assume sizeof(cookie) <= word size session-aware 原本之所以會認為 cookie 的大小會卡到安全性的上界是因為以為他的長度是 32 位元,如果是這種量級的話那麼如果以一個固定的 cookie 字串是在「理論上」能被枚舉出來的(因為所有狀態總共只有 $2^{32}$ 種)。那這種情況下只能增加 cookie 的長度,或是讓核心提供一種使用者看不到也不能修改的識別使用者的方式。而 64 位元的 cookie 其實是沒辦法被枚舉的,所以會出現的安全移慮主要會出現在被 hash 的資料可以被枚舉,或者是雜湊函數可以被逆向破解。 以下討論的範疇是假設是所有使用者都只能透過 VFS 這個界面去和外界互動,中間的呼叫是不會被攔截和修改的。 這樣的假設下,如果 cookie 被驗證是正確的,那麼資料就一定是可信任的(指的是不會只有資料被修改但 cookie 沒被修改的情況,但不代表能無條件信任資料裡的資訊,譬如使用者這樣的資訊要放在 cookie 中而非 data 中),所以要思考的事情是怎麼將 `cookie` 和 `data` 放在 64 位元中,並且改進查詢的效率。先不去管 `cookie` 怎麼生成的,只要知道它是一個指定大小的隨機數。至於 `user_id` 的部份可以由核心模組分配,這樣的話可以思考怎樣分配會比較有效率。首先思考使用者數量在一定範圍內,之後在拓展,先以最多 64 個使用者為例。將 0 到 63 的號碼放進一個鏈結串列中(實作上在拿第一次的號碼時可以記個數字就好,回收的號碼再放進去),分配新的使用者可以從裡面拿一個 `user_id`,用完放回去(這裡可以有個定期檢查某個使用者是否太久沒有和核心模組交互的機制,防止有使用者意外斷線造成 zombie user id),這樣如果使用者沒有超過 64 就能成功拿到和別人不重複的 `user_id`。把 `user_id` 限定在一定範圍內有個好處,就是他們的 `cookie` 和其他資訊可以存在陣列中(或者是一個結構體的指標的陣列),可以做到 $O(1)$ 的查詢。那如果超過 64 的話怎麼處理呢?這裡的概念就是一個區塊一個區塊去分配,區塊大小由預期的使用者的數量決定,盡量選 2 的羃,好處是可以通過位元運算來快速尋找在哪個區塊與在那個區塊的偏移量多少。這樣能同時做到不需要一開始就分配所有的空間,但是分配完卻一樣可以做到 $O(1)$ 的查詢。以這裡 64 來說的話,如果出現第 65 個使用者的話,那麼就會分配第 2 個區塊,接下來產生 64 號到 127 號,分別對應到第 2 個區塊的偏移量 0 到 63。這樣就獲得了一個產生 `user_id` 和儲存對應資料的方式。 接下來是傳輸和驗證機制,一開始核心模組會產生一個 `user_id` 和一個 `cookie`,並傳給使用者,接下來使用者每次與核心模組溝通都要將 `user_id`、`cookie` 和 `data` 傳給核心模組。至於這三者要怎麼分配那 64 bit 取決於使用者的生命週期、使用者的最大數量和資料的最大長度。密碼學的安全只要做到計算安全,也就是在一個使用者的生命週期中他的 `cookie` 不會被窮舉出來就好。分配給 `cookie` 50 個位元在任何情況都是一個非常足夠的數字了。就算 1 秒能完成 $10^9$ 次的呼叫(實際上不可能達成),期望上也需要不間斷的呼叫 6.5 天才能窮舉的出來: $$\frac{1}{2^{50}}\times\frac{2^{50}(2^{50}+1)}{2}\times \frac{1}{86400\times 10^9}\approx 6.5$$ 要做到有效程度的惡意行為是很容易觀察和防範的。這件事情我想到一個和前面的定期回收 `user_id` 合併處理的方式,我們把 `user_id` 分成 3 區,分別在 `active_list`、`now_list` 和 `bin_list` 三個鏈結串列中。我們把時間分成一個一個的週期,一個週期要是 `cookie` 被窮舉出來的機率極低的一段時間。一開始,所有被分配的 `user_id` 都被放在 `active_list` 裡面,每當一個時間週期結束,`now_list` 中的東西會被放到 `bin_list` 中,而 `active_list` 會變成新的 `now_list`,`active_list` 會變成空的。另外,被回收的 `user_id` 會放到 `bin_list` 裡面。(簡單來說就是會定期把 `now_list` 中的 `user_id` 做回收)。在核心模組裡面紀錄的資訊要包括這個使用者現在在哪個鏈結串列裡面。使用者被要求至少在每個時間週期內要與核心模組要一個新的 `cookie`,當做了這件事情後,這個 `user_id` 會被放到 `active_list` 中。另外,只有在 `now_list` 和 `active_list` 中的使用者是被認可的使用者。我想,要求一個使用者以分鐘以上的時間單位更換一次 `cookie` 是符合效益的要求。 那用了 50 個位元,譬如以 kxo 這個使用情境,4 位元拿來輸入指令是夠用的,10 位元來放 `user_id` 可以支援最多 1024 個使用者。 依照機率,可以在時間週期、`cookie`、使用者數量和資料大小間做平衡,而如果需要有更多的使用者和更多的指令的話,可以寫一個結構體,裡面放 `cookie`、`user_id` 和 `data`,傳入時傳結構體的指標。 :::success TODO:看看 Linux 核心中的 bpf 是怎麼做的 ::: 這裡去看了一個 bpf 的使用案例,在 [`linux/samples/bpf/cookie_uid_helper_example.c`](https://github.com/torvalds/linux/blob/ebd297a2affadb6f6f4d2e5d975c1eda18ac762d/samples/bpf/cookie_uid_helper_example.c#L264) 中看到它呼叫 `bpf_map_lookup_elem` 的時候帶入了 `cookie` 這個參數: ```cpp res = bpf_map_lookup_elem(map_fd, &cookie, &dataEntry); ``` 追進去後發現它把 `cookie` 放在 `attr.key`,把 `dataEntry` 放在 `value` 裡面,進行系統呼叫: ```cpp int bpf_map_lookup_elem(int fd, const void *key, void *value) { ... attr.key = ptr_to_u64(key); attr.value = ptr_to_u64(value); ... ret = sys_bpf(BPF_MAP_LOOKUP_ELEM, &attr, attr_sz); ... } ``` 於是去看了系統呼叫的[定義](https://github.com/torvalds/linux/blob/ebd297a2affadb6f6f4d2e5d975c1eda18ac762d/kernel/bpf/syscall.c#L5939),會發現它呼叫了 `__sys_bpf`,看到 [`BPF_MAP_LOOKUP_ELEM`](https://github.com/torvalds/linux/blob/ebd297a2affadb6f6f4d2e5d975c1eda18ac762d/kernel/bpf/syscall.c#L5818) 這個選項下會去呼叫 `map_lookup_elem`: ```cpp case BPF_MAP_LOOKUP_ELEM: err = map_lookup_elem(&attr); ``` 在其中,會取出 `key`,去呼叫 `bpf_map_copy_value`: ```cpp err = bpf_map_copy_value(map, key, value, attr->flags); ``` 然後裡面會按照 `map->map_type` 選擇不同的查詢方式,譬如說之前小考出現過的 bloom filter 也是其中一個選項(`BPF_MAP_TYPE_BLOOM_FILTER`)。我沒有每個函式追進去看,不過看上下文來說他是允許不同型態的雜湊表,並且會查詢 key 對應的元素值,放進 value 指向的位置(至於 bloom filter 就是直接回傳存不存在)。所以,cookie 是作為雜湊表的 key 來使用的。 ```cpp if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH || map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH) { err = bpf_percpu_hash_copy(map, key, value); } else if (map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) { err = bpf_percpu_array_copy(map, key, value); } else if (map->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) { err = bpf_percpu_cgroup_storage_copy(map, key, value); ... ``` 簡單來說,它一開始會生一個 `cookie` 給使用者,並且有一個雜湊表可以來查詢這個使用者對應的資料。 接下來來比較這個作法和我的作法。首先是不可能用 bloom filter 實作,因為如果只檢測 `cookie` 存不存在的話核心模組無法確定對方是什麼使用者(攻擊者可以使用自己的 `cookie` 卻對其他使用者做操作),所以要使用的雜湊表必須是可以回傳 value 的。在看上面 `cookie_uid_helper_example.c` 中的實作的時候,發現他的 `cookie` 是由 `getsockopt` 產生的,並且後面使用 `bpf_map_lookup_elem` 去看看雜湊表裡面有沒有這個 `cookie`,如果有的話就代表這個 socket 的資訊已經存在了,就回傳錯誤。所以我判斷這裡並不是出於資安考量去做這件事情,而是使用一些資訊做 hash 之後判斷這個東西存不存在以防申請到同樣的資源。那在我看來,以 kxo 這樣的使用情境下,使用 hash 作為 key 只是增加查詢的成本而已。使用 `user_id` 這樣簡單的索引值就能得到相同的效果了,而且如果要增加安全性(增加 `cookie` 的長度)的話,不需要對查詢方面做太多的變更(他的成本取決於使用者的數量,所以成本也不會增加),只是隨著 `cookie` 長度線性的增加儲存空間和比較時間而已。另外,如果有要對使用者做管理的需求的話(譬如上面所述,想移除一些太久沒互動的使用者),那麼使用 `user_id` 可以做到較好的管理。否則,如果使用雜湊表又另外增加這種機制,那還不如一開始就不使用雜湊表。

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