# Linux OS Project 3 Write Up
第24組,組員:黃詩云、羅傳郡、林孟玄
## Kernel 與 Ubuntu 版本
Linux Kernel:6.0.6
Ubuntu:18.04
## 第一題
### User Space Code
呼叫我們所新增的system call得到我們的cpu number, 再用linux內建的getcpu函式做驗證
```C=
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <syscall.h>
#include <sys/sysinfo.h>
int main(){
int cpu=-1;
cpu=syscall(336);
printf("cpu number (syscall) %d\n", cpu);
unsigned node;
syscall(SYS_getcpu, cpu, node, NULL);
printf("cpu number (default) %u\n", cpu);
return 0;
}
```
### Kernel Space Code
從/usr/src/linux-6.0.8/include/linux/sched.h找到task_struct, 接著在task_struct中找到struct thread_info
再到/arch/x86/include/asm/thread_info.h中的struct thread_info找到u32 cpu這個屬性
最後用current->thread_info.cpu得到我們想要的資訊
```C=
#include <linux/kernel.h>
#include <linux/syscalls.h>
SYSCALL_DEFINE0(getcpunumber){
int cpu_number=current->thread_info.cpu;
return cpu_number;
}
```
## 執行結果截圖

## 第二題
### User Space Code
linux kernel裡process進入wait state分為兩種情況,一種情況是主動睡眠就是sleep這種方式進入,另一種是被動,通常是等待某種資源。
這兩種情況,process都會讓出CPU,但是主動睡眠是可以被信號喚醒的,喚醒後就會進入ready state,這時候還是沒有真正得到CPU來運行,等到沒有更高優先級的process,就會進入running state
所以我們利用sleep去模擬處理I/O的時間
I/O intensive
```C=
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <syscall.h>
#include <sys/time.h>
void main(){
int total=0;
int count=0;
struct timeval t;
gettimeofday(&t, NULL);
int start_time=t.tv_sec;
int current_time;
syscall(337);
while(1)
{
sleep(0.01);
printf("%d\n", count++);
gettimeofday(&t, NULL);
current_time=t.tv_sec;
if((current_time-start_time)>=120)
{
break;
}
}
total=syscall(338);
printf("the total of context switch %d\n", total);
}
```
cpu intensive
```C=
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <syscall.h>
#include <sys/time.h>
void main()
{
int total;
int count=0;
struct timeval t;
gettimeofday(&t, NULL);
int start_time=t.tv_sec;
int current_time;
syscall(337);
while(1)
{
printf("%d\n", count++);
gettimeofday(&t, NULL);
current_time=t.tv_sec;
if((current_time-start_time)>=120)
{
break;
}
}
total=syscall(338);
printf("the total of context switch %d\n", total);
}
```
### Kernel Space Code
我們先去usr/src/linux-6.0.6/kernel/sched/core.c找到static void __sched notrace __schedule, 接著在呼叫context_switch這個函式之前新增我們計算次數的變數next->process_switches_count++
再來新增兩個system call分別為開始計算以及結束計算
```C=
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/sched.h>
#include <linux/init_task.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/slab.h>
SYSCALL_DEFINE0(startcountswitch){
current->process_switches_count=0;
return;
}
```
```C=
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/sched.h>
#include <linux/init_task.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/slab.h>
SYSCALL_DEFINE0(stopcountswitch){
return current->process_switches_count;
}
```
## 執行結果截圖
I/O intensive

CPU intensive

從結果得知I/O intensive遠比CPU intensive的context switch還多
##
## 補充
### 1.sleep()
```C=
unsigned int sleep(unsigned int seconds);
```
sleep()可以讓Process主動主動睡眠
以下是由kernel實作的code
```C=
asmlinkage long sys_nanosleep(struct timespec __user *rqtp, struct timespec __user *rmtp)
{
struct timespec t;
unsigned long expire;
long ret;
expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);
current->state = TASK_INTERRUPTIBLE;
expire = schedule_timeout(expire);
}
```
算出超過的時間, state改為TASK_INTERRUPTIBLE, 再呼叫schedule_timeout
接著新增一個timer, 然後呼叫system call, Process進到waiting state
等到一定的時間後,Process會wake。
waiting state是TASK_INTERRUPTIBLE, 可中斷
這種狀態的Process可以被喚醒, 而TASK_UNINTERRUPTIBLE是不能被信號喚醒的。
```C=
fastcall signed long __sched schedule_timeout(signed long timeout)
{
struct timer_list timer;
unsigned long expire;
// 算出超過時間
expire = timeout + jiffies;
init_timer(&timer);
// 超過時間
timer.expires = expire;
timer.data = (unsigned long) current;
timer.function = process_timeout;
// 增加timer
add_timer(&timer);
schedule();
// 删除timer
del_singleshot_timer_sync(&timer);
timeout = expire - jiffies;
out:
return timeout < 0 ? 0 : timeout;
}
```
process_timeout函式
```C=
static void process_timeout(unsigned long __data)
{
wake_up_process((task_t *)__data);
}
```
### 2.gettimeofdate()
gettimeofdate()的實作方式先傳入struct timeval *tv和struct timezone *tz, 不過現在第二個參數填NULL即可, 接著函式裡會呼叫一個system call, sys_gettimeofday
```C=
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
int ret = sys_gettimeofday(tv, tz);
if (ret < 0) {
SET_ERRNO(-ret);
ret = -1;
}
return ret;
}
```
```C=
asmlinkage long sys_gettimeofday(struct __kernel_old_timeval __user *tv,
struct timezone __user *tz);
```
### 3. Process切換
當中斷發生時會呼叫schedule()函式, 接著schedule()函式會呼叫next = pick_next_task(rq, prev), 所做的工作就是排班演算法, 選擇要執行的下一個Process
接著, 根據排班演算法得到要執行的Process後,呼叫context_switch(rq, prev, next), 完成進程上下文切換
其中, switch_to(prev,next, prev), 會保存Process當時的變數值到暫存器
```C=
#define switch_to(prev, next, last)
do {
unsigned long ebx, ecx, edx, esi, edi;
asm volatile("pushfl\n\t" /* save flags */
"pushl %%ebp\n\t" /* save EBP */
"movl %%esp,%[prev_sp]\n\t" /* save ESP */
"movl %[next_sp],%%esp\n\t" /* restore ESP */
"movl $1f,%[prev_ip]\n\t" /* save EIP */
"pushl %[next_ip]\n\t" /* restore EIP */
__switch_canary
__retpoline_fill_return_buffer
"jmp __switch_to\n" /* regparm call */
"1:\t"
"popl %%ebp\n\t" /* restore EBP */
"popfl\n" /* restore flags */
: [prev_sp] "=m" (prev->thread.sp),
[prev_ip] "=m" (prev->thread.ip),
"=a" (last),
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi)
__switch_canary_oparam
: [next_sp] "m" (next->thread.sp),
[next_ip] "m" (next->thread.ip),
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
"memory");
} while (0)
```