# Linux-Project3 ###### tags: `linux` 第9組 111522030 李孟潔 111522061 鄭伊涵 111526003 張友安 ## Outline [ToC] ## Project3 - https://staff.csie.ncu.edu.tw/hsufh/COURSES/FALL2022/linux_project_3.html ## Enviroment * VMware * Ubuntu 16.04 Lts * Linux Kernel 4.15.1 ## Q1 Kernel Space ### 新增System call ```c= core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ get_CPUnumber/ ``` ### 新增system call到system call table ```c= 333 common get_CPU_number sys_get_CPU_number ``` ### 定義system call的prototype ```c= asmlinkage long sys_get_CPU_number(int* number); ``` ### System call -get_CPU_number ```c= #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/sched.h> SYSCALL_DEFINE1(get_CPU_number, unsigned int*, number){ unsigned int cpuNum = 10; cpuNum = task_cpu(current); copy_to_user(number, &cpuNum, sizeof(unsigned int)); printk("cpu number in kernel: %u\n", cpuNum); return 0; } ``` ## Q1 User space ```c= #include <syscall.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main(){ unsigned int init_num = 5678; unsigned int* cpu_number = &init_num; syscall(333, cpu_number); printf("cpu number in user: %u\n", *cpu_number); return 0; } ``` ### Process * **設計流程及說明:** 在user space先初始化我們用來存放cpu number的變數為任意數字,以確保之後透過system call取得的cpu number是否有正卻執行(未初始化預設為0),接著呼叫我們所編譯的system call(333),該system call透過呼叫task_cpu取得當前執行的cpu number,其取得途徑如下: * 1. 透過參數current取得當前process的task_struct,current在kernel中被定義為get_current(),而該函式可取得當前process的thread_info,再以此取得task_struct。 * 2. 將current作為參數引入task_cpu後,會根據是否定義CONFIG_THREAD_INFO_IN_TASK來決定從task_struct或是從task_thread_info取得cpu number。 ## Q1 Result of get_CPU_number program 以下是我們執行10次的結果,可以看到cpu 0和cpu 1都有被使用到。 ```c= cya@ubuntu:~/Desktop$ gcc test.c -o test cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 1 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 1 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 0 cya@ubuntu:~/Desktop$ ./test cpu number in user: 1 ``` ### Kernel message in dmesg: 在syscall裡printk以確認結果是否符合預期 ```c= [446089.547747] cpu number in kernel: 0 [446090.895565] cpu number in kernel: 0 [446091.858955] cpu number in kernel: 1 [446092.628468] cpu number in kernel: 0 [446093.720960] cpu number in kernel: 0 [446094.690959] cpu number in kernel: 0 [446095.422937] cpu number in kernel: 1 [446096.009711] cpu number in kernel: 0 [446096.674835] cpu number in kernel: 0 [446097.295066] cpu number in kernel: 1 ``` ## Kernel Space ### /linux-4.15.1/include/linux/sched.h 在此檔案中找到struct task_struct,並且在thread_struct前面加上long cs_counter。 會在thread_struct前面加上cs_counter,而不是在最後一行加的原因,是task_struct的最後一段warning有表明thread_struct包含variable-sized structure,所以thread_struct必須是task_struct的最後一行。 ```c= /* manually add*/ long cs_counter; ``` ### /linux-4.15.1/kernel/sched/core.c 在此檔案中找到schedule函式(asmlinkage __visible void __sched schedule(void)),在struct task_struct *tsk = current; 後面加一行tsk->cs_counter++; 用來表示在這個process作context switch時,會將值+1。 ```c= /*manually add*/ tsk -> cs_counter++; ``` ### 新增System call 若要新增多個system call, 不同的system call的code必須存放在不同的資料夾,若是放在相同的資料夾,則會無法新增第二個system call。 因此我們在syscall_start此資料夾裡存放start_to_count的system call,在syscall_stop此資料夾裡存放stop_to_count的system call。 ```c= core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ syscall_start/ syscall_stop/ ``` ### 新增system call到system call table ```c= 333 common start_to_count sys_start_to_count 334 common stop_to_count sys_stop_to_count ``` ### 定義system call的prototype ```c= asmlinkage long sys_start_to_count(void); asmlinkage long sys_stop_to_count(void); ``` ### SystemCall-start to count 此system call乃將cs_counter的值歸零 ```c= #include <linux/sched.h> #include <asm/current.h> #include <linux/printk.h> #include <linux/kernel.h> #include <linux/syscalls.h> SYSCALL_DEFINE0(start_to_count){ printk("num: %ld\n", current->cs_counter); current -> cs_counter = 0; return 0; } ``` ### SystemCall-stop to count 此system call會回傳當前process的context switch次數(cs_counter)。 ```c= #include <linux/sched.h> #include <asm/current.h> #include <linux/printk.h> #include <linux/kernel.h> #include <linux/syscalls.h> SYSCALL_DEFINE0(stop_to_count){ printk("%ld\n", current -> cs_counter); return current -> cs_counter; } ``` ## User Space ### I/O-bound program ```c= #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <unistd.h> #include <syscall.h> #define ON 1 #define OFF 0 int main() { long ret,a; int b = 0; int button = ON; unsigned long diff; struct timeval start; struct timeval end; ret = syscall(333); //start to count gettimeofday(&start, NULL); while(button == ON) { sleep(5); printf("[%d ]\n",b++); gettimeofday(&end, NULL); diff = ((end.tv_sec) - (start.tv_sec)); if(diff / 60 >= 2) { button = OFF; break; } } a = syscall(334); //stop to count printf("During the past 2 minutes the process makes %ld times process switches.\n",a); return 0; } ``` ### Process * **設計流程** * 首先,第一個是I/O-bound的user space program。我們會在迴圈簡單印出一個遞增的數字,每次迴圈都會讓程式先sleep 5秒。 * 在程式正式進入迴圈之前,我們會先叫我們所編譯的system call(333),該system call是正式開始計算程式context switches的次數(start to count)。 * 我們用gettimeofday去得到我們想要的區間程式執行的時間,如果程式執行2分鐘,就會讓迴圈停止。 * 迴圈結束,我們會叫另一個我們編譯的system call(334),回傳我們在task_struct所新增的cs_counter欄位。 ### Result of I/O-bound program ![](https://i.imgur.com/PsvsMkL.png) * **說明** * 因為是I/O-bound,所以會有輸出。進行的輸出共有24次,也代表程式進入sleep也是24次。 * system call最終回傳的context switches次數也是24次。根據我們的觀察,程式在呼叫sleep時會讓程式進入TASK_INTERRUPTIBLE的狀態,然後呼叫schedule_timeout再進入schedule執行context switches。 * 所以在此程式中,迴圈進行了24次,sleep也呼叫了24次,所以context switches進行了24次。 * 以下是sleep在kernel中的code。 ```c= static inline void sleep(unsigned sec) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(sec * HZ); } ``` ### CPU-bound program ```c= #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <unistd.h> #define ON 1 #define OFF 0 int main() { long ret,a; int b = 0; int button = ON; unsigned long diff; struct timeval start; struct timeval end; ret = syscall(333); gettimeofday(&start, NULL); while(button == ON) { b = b + 1; gettimeofday(&end, NULL); diff = ((end.tv_sec) - (start.tv_sec)); if(diff / 60 >= 2) { button = OFF; break; } } a = syscall(334); printf("During the past 2 minutes the process makes %ld times process switches.\n",a); return 0; } ``` ### Process * **設計流程** * 跟上面I/O-bound很相似,只是不需要有output以及sleep在迴圈裡,而是進行CPU的運算(b=b+1)。 * 其餘取得程式執行時間,以及呼叫system call的部分都與I/O-bound一樣。先叫start to count,結束時叫stop to count,並且回傳context switches的次數。 ### Result of CPU-bound program ![](https://i.imgur.com/eulU575.png) * **說明** * 跟上面I/O-bound相比,CPU-bound的context switches次數就多很多。 * 根據我們的觀察,我們是假設在做CPU運算時,所占用的CPU資源會比較多,因為花費大部分時間在CPU上,scheduler為了平均分配每個process的CPU資源,所以會一直進行context switches,導致CPU-bound的次數相對上面的多出很多。 ## Reference [Task_struct](https://elixir.bootlin.com/linux/v4.15.1/source/include/linux/sched.h#L520) [Task_struct](https://elixir.bootlin.com/linux/v4.15.1/source/kernel/sched/core.c#L3427) [Warning of thread_struct](https://elixir.bootlin.com/linux/v4.15.1/source/include/linux/sched.h#L1101) [IO & CPU](https://stackoverflow.com/questions/868568/what-do-the-terms-cpu-bound-and-i-o-bound-mean)