## Linux Project 2 <!-- [toc] --> :::info #### 組長: - 111504512 賴詠文 #### 組員: - 111504507 黃子銘 - 109401553 楊雲杰 - 111504008 郭奕宏 ::: ### 題目 #### Wait Queue - A Wait Queue is a synchronization mechanism in the Linux kernel used to put processes to sleep while waiting for a specific condition to be met. Once the condition is satisfied, the processes are awakened. It is commonly used to avoid busy-waiting, thereby improving system efficiency. Under normal circumstances, a function using a wait queue must know the name or the pointer of the wait queue. This is because a wait queue is represented as a variable (usually of type wait_queue_head_t ), and the function needs the address of this variable to perform operations. > In this lab, you need to implement a custom wait queue-like functionality in kernel space, allowing user applications to operate through the system call. #### output example The target output is as follows: threads exit the wait queue in FIFO order. ``` enter wait queue thread_id: 0 enter wait queue thread_id: 1 enter wait queue thread_id: 2 enter wait queue thread_id: 6 enter wait queue thread_id: 7 enter wait queue thread_id: 8 enter wait queue thread_id: 9 enter wait queue thread_id: 5 enter wait queue thread_id: 3 enter wait queue thread_id: 4 start clean queue ... exit wait queue thread_id: 0 exit wait queue thread_id: 1 exit wait queue thread_id: 2 exit wait queue thread_id: 6 exit wait queue thread_id: 7 exit wait queue thread_id: 8 exit wait queue thread_id: 9 exit wait queue thread_id: 5 exit wait queue thread_id: 3 exit wait queue thread_id: 4 ``` ### 助教給的 Base Code :::spoiler User Space ```c // Except for syscall(xxx, 1); and syscall(yyy, 2); , please do not modify any other parts of the code. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <sys/syscall.h> #define NUM_THREADS 10 void *enter_wait_queue(void *thread_id) { fprintf(stderr, "enter wait queue thread_id: %d\n", *(int *)thread_id); /* // your syscall here syscall( xxx , 1); */ fprintf(stderr, "exit wait queue thread_id: %d\n", *(int *)thread_id); } void *clean_wait_queue() { /* // your syscall here syscall( xxx , 2); */ } int main() { void *ret; pthread_t id[NUM_THREADS]; int thread_args[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { thread_args[i] = i; pthread_create(&id[i], NULL, enter_wait_queue, (void *)&thread_args[i]); } sleep(1); fprintf(stderr, "start clean queue ...\n"); clean_wait_queue(); for (int i = 0; i < NUM_THREADS; i++) { pthread_join(id[i], &ret); } return 0; } ``` ::: :::spoiler Kernel Space ```c // Here is a program example. You are free to modify the contents of this file as needed. static int enter_wait_queue(void) { return 0; } static int clean_wait_queue(void) { return 0; } SYSCALL_DEFINE1(call_wait_queue, int, id) { switch (id){ case 1: enter_wait_queue() break; case 2: clean_wait_queue(); break; } return 0; } ``` ::: ### 環境 ![image](https://hackmd.io/_uploads/SJmur5aV1l.png) ### 目標 - 實作一個自定義 wait queue,讓 user 可以透過呼叫 system call 時,帶不同參數決定是要把當前 thread 丟進 自定義 wait queue 中還是把自定義 wait queue 中的全部 thread 喚醒 - 喚醒的順序需要是 FIFO <!-- > 因為 linux 中沒有 thread 概念,是用 process 透過相同 id 以及共享記憶體等方式模擬 thread,所以本篇不會特別區分 thread 和 process --> ### 觀念介紹 - 以下是 wait queue 在 linux kernel 中的結構 - 使用 circular doubly linked list 結構儲存每個在 wait queue 中的 thread - 第一個 node 是 wait queue 的 head,不代表任何 thread - 當 wait queue 為空時,`next` 和 `prev` 會指向自己 - flags - 0 (non-exclusive):被喚醒時會繼續喚醒 queue 中的下一個 node - 1 (exclusive):被喚醒時不會順便叫下一個一起起床 - 通常同一個 queue 每個 node 的 flag 都會一樣 ![image](https://hackmd.io/_uploads/SJV8uq6V1e.png) ### 實作 (Kernel Space) 1. 定義一個 wait queue head 名稱為 `my_wait_queue` ```c DECLARE_WAIT_QUEUE_HEAD(my_wait_queue); ``` - DECLARE_WAIT_QUEUE_HEAD 在 linux kernel 中的定義如下,會幫我們初始化好一個 wait queue 的 head,我們只需要提供變數名稱即可 ![image](https://hackmd.io/_uploads/rkw4a5pEJe.png) 2. 定義 enter_wait_queue() - 把 thread 加入 wait queue ```c no_wait = 0; // resource 沒拿到 (全域變數) ret = wait_event_interruptible_exclusive(my_wait_queue, no_wait); ``` - 把 no_wait 設為 0,表示 resource 還沒拿到,我要睡覺。即使被叫起,`no_wait=0` 依然會繼續睡(起床重睡!,`___wait_event` for 迴圈在做的事) - 0 表示資源尚未取得,執行緒需要進入等待。 1 表示資源已經取得,可以喚醒所有等待的執行緒。 - `wait_event_interruptible_exclusive` 定義如下,最終會呼叫 `___wait_event` - exclusive 會將 node enqueue 到最後面;non-exclusive 則相反。dequeue 時則固定會從第一個開始取出,所以用 exclusive 才會有 FIFO 的效果 - interruptible 則不影響結果,因為我們不會用 signal 去喚醒他 (我覺得,還沒驗證) - condition 代表要 wait 的資源是否取得,因為我們要讓他一直待在 wait queue 中直到我們把它叫醒(`___wait_event` for 迴圈會一直檢查),所以設為 0 (false) - ![image](https://hackmd.io/_uploads/B1yRyia4yx.png) - ![image](https://hackmd.io/_uploads/HkOczsTE1l.png) 3. 定義 clean_wait_queue() - 把 wait queue 中的 node 依照 FIFO 取出 - 透過 while 迴圈檢查 wait queue 是否為空 (head 有沒有指向自己),然後就一直 dequeue wait queue 中最前面的 node 直到沒有其他 node - no_wait 設為 1,表示不會再起床重睡了 - msleep:linux kernel 中使用的 sleep,因為在執行這個 syscall 的時候可能會在同一個 CPU 時間片段同時 wake up queue 中的很多 thread 進入 run queue 競爭 CPU 時間片段,這樣執行的順序可能會變,所以應該用 sleep 確保 wake up 的 thread 會先在 CPU 執行,再 wake 下一個 thread。 <!-- - msleep:linux kernel 中使用的 sleep,確保 user mode 有足夠的時間輸出 `exit wait queue thread_id: ?` 再叫醒下一個 node。 --> ```c no_wait = 1; // resource 拿到了 while(my_wait_queue.head.next != &my_wait_queue.head) { wake_up_interruptible(&my_wait_queue); msleep(100); } ``` - `wake_up_interruptible` 定義如下,對應到 `enter_wait_queue()` 中的 `wait_event_interruptible_exclusive` 就是喚醒 node - ![image](https://hackmd.io/_uploads/ry2WKsaVJl.png) - ![image](https://hackmd.io/_uploads/ryeprYo64yx.png) - ![image](https://hackmd.io/_uploads/SyE_FipNJg.png) - ![image](https://hackmd.io/_uploads/ry_19saEye.png) ### 實作 (User Space) - 助教給的 sample code 裡面的 syscall number 改成你在 `/usr/src/linux-5.15.137/arch/x86/entry/syscalls/syscall_64.tbl` 中設定的數字即可,以我自己為例是 `451` - ![image](https://hackmd.io/_uploads/SJ218meB1e.png) ### final code (Kernel Space) ```c #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/wait.h> #include <linux/delay.h> DECLARE_WAIT_QUEUE_HEAD(my_wait_queue); int no_wait = 0; static int enter_wait_queue(void) { int ret; no_wait = 0; // resource 沒拿到 (全域變數) ret = wait_event_interruptible_exclusive(my_wait_queue, no_wait); printk("enter wait queue return = %d\n", ret); if(ret) return 0; return 1; } static int clean_wait_queue(void) { no_wait = 1; // resource 拿到了 while(my_wait_queue.head.next != &my_wait_queue.head) { wake_up_interruptible(&my_wait_queue); msleep(100); } return 1; } SYSCALL_DEFINE1(call_my_wait_queue, int, id) { switch (id){ case 1: enter_wait_queue(); break; case 2: clean_wait_queue(); break; } return 0; } ``` ### final code (User Space) ```c // Except for syscall(xxx, 1); and syscall(yyy, 2); , please do not modify any other parts of the code. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <sys/syscall.h> #define NUM_THREADS 10 void *enter_wait_queue(void *thread_id) { fprintf(stderr, "enter wait queue thread_id: %d\n", *(int *)thread_id); syscall(451 , 1); fprintf(stderr, "exit wait queue thread_id: %d\n", *(int *)thread_id); } void *clean_wait_queue() { syscall(451 , 2); } int main() { void *ret; pthread_t id[NUM_THREADS]; int thread_args[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { thread_args[i] = i; pthread_create(&id[i], NULL, enter_wait_queue, (void *)&thread_args[i]); } sleep(1); fprintf(stderr, "start clean queue ...\n"); clean_wait_queue(); for (int i = 0; i < NUM_THREADS; i++) { pthread_join(id[i], &ret); } return 0; } ``` ### 設定syscall相關檔案 & 編譯 kernel - 我是參考這邊的教學 https://hackmd.io/aist49C9R46-vaBIlP3LDA <!-- 1. `cd /usr/src/linux-5.15.137/` 2. `sudo make localmodconfig` - 這邊會跳出一大堆問你要不要裝的套件,全部按enter跳過就好 - 抓 kernel 需要編譯的部分(有更動的部分)進去 config 檔中,以減少編譯需要的時間,動的東西越多,需要編譯越久 3. `nproc` - 查看虛擬機中有多少核可以用 4. `sudo make -jn` - n替換為你想用多少核一起編譯,通常抓 1/2~3/4 5. 懶得寫 --> ### 輸出結果 ![image](https://hackmd.io/_uploads/BJX-0op4ke.png) 完成!!因為是多執行緒所以 enter 的順序會是 random,但是因為是 FIFO 加上我 dequeue 有 sleep,所以 exit 會是跟 enter 一樣的順序 ### 參考資料 - [許富皓教授的 wait queue 投影片](https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fstaff.csie.ncu.edu.tw%2Fhsufh%2FCOURSES%2FFALL2024%2FlinuxLecture_3_9-8.ppt&wdOrigin=BROWSELINK) - [bootlin linux source code](https://elixir.bootlin.com/linux/v5.15.137/source/kernel/sched/wait.h) - [設定&編譯 linux kernel](https://hackmd.io/aist49C9R46-vaBIlP3LDA) - [sleep in linux kernel](https://blog.csdn.net/q28292929/article/details/127665877)