# 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

* **說明**
* 因為是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

* **說明**
* 跟上面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)