# Linux-Project2
###### tags: `linux`
第9組
111522030 李孟潔
111522061 鄭伊涵
111526003 張友安
## Outline
[ToC]
## Kernel Space
Kernel Space的詳情 : https://hackmd.io/@linuxWarrior/H1So0AlHo
## User Space
### The 1st version
``` c=
#include <syscall.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <stdlib.h>
#include <dlfcn.h>
int init_data = 30;
int non_init_data;
long ret = 0;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
void* our_func()
{
printf("Hello World!\n");
}
int main(){
int local_data = 30;
int* heapAddress = malloc(sizeof(int));
char test = '\n';
printf("start! %c\n", test);
void* fHandle;
void* func;
fHandle = dlopen("/lib/x86_64-linux-gnu/libc.so.6",RTLD_LAZY);
printf("libc base: %lx\n",*(size_t*)fHandle);
if (!fHandle) {
printf ("%s\n", dlerror());
exit(1);
}
func = dlsym(fHandle, "printf");
static __thread int tls = 0;
if(dlerror() != NULL){
printf("%s\n", dlerror());
exit(1);
}
size_t* va[7] = {
(size_t*)&init_data,
(size_t*)&non_init_data,
(size_t*)&our_func,
(size_t*)&local_data,
(size_t*)func,
(size_t*)heapAddress,
(size_t*)&tls
};
size_t* pa[7];
ret = syscall(333, va, pa, 7);
printf("\nPid of the process is = %d\nAddresses info:", getpid());
char printArray [7][20] = {"Data", "BSS", "Code", "Stack",
"Library", "heap", "thread"};
for(int i=0;i<7;i++){
printf("\n %d) %8s (va/pa) = %16p / %20p", i+1, printArray[i], va[i], pa[i]);
}
tls++;
printf("\n\n");
if(ret > 0) printf("error!!!QQQQQQQ");
sleep(30);
return 0;
}
```
### Process
* **設計流程**
1. 基本架構與project1一樣,只是將multi thread的部分刪除。
2. 在程式最後面添加sleep(),讓兩個process同時執行時方便我們進行比較。
3. 執行程式使用下方執行方式,將其中一個放背景執行,實現同時兩個processes執行同個code,並觀察processes間記憶體的分享方式。
```c=
./getpa2 & getpa2
```
### Result of the 1st ver.

* **結果分析**
1. 觀察後發現兩個processes的Code以及Library的physical addresses是相同的。
2. 其中Data、BSS的virtual addresses雖然相同,但他們的physical addresses為不同。
### The 2nd version
``` c=
#include <syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <stdlib.h>
#include <dlfcn.h>
int init_data = 30;
int non_init_data;
long ret = 0;
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
void* our_func()
{
printf("Hello World!\n");
}
int main(){
int local_data = 30;
int* heapAddress = malloc(sizeof(int));
char test = '\n';
printf("start! %c\n", test);
void* fHandle;
void* func;
fHandle = dlopen("/lib/x86_64-linux-gnu/libc.so.6",RTLD_LAZY);
printf("libc base: %lx\n\n",*(size_t*)fHandle);
if (!fHandle) {
printf ("%s\n", dlerror());
exit(1);
}
func = dlsym(fHandle, "printf");
static __thread int tls = 0;
if(dlerror() != NULL){
printf("%s\n", dlerror());
exit(1);
}
pid_t PID = fork();
if(PID == -1)
{
printf("error\n");
}
else if(PID == 0)
{
printf("It's child process, PID is %d.", getpid());
size_t* va[7] = {
(size_t*)&init_data,
(size_t*)&non_init_data,
(size_t*)&our_func,
(size_t*)&local_data,
(size_t*)func,
(size_t*)heapAddress,
(size_t*)&tls
};
size_t* pa[7];
ret = syscall(333, va, pa, 7);
char printArray [7][20] = {"Data", "BSS", "Code", "Stack",
"Library", "heap", "thread"};
for(int i=0;i<7;i++){
printf("\n %d) %8s (va/pa) = %16p / %20p", i+1, printArray[i], va[i], pa[i]);
}
tls++;
printf("\n\n");
sleep(10);
}
else if(PID > 0)
{
printf("It's parent process!, PID is %d.", getpid());
size_t* va[7] = {
(size_t*)&init_data,
(size_t*)&non_init_data,
(size_t*)&our_func,
(size_t*)&local_data,
(size_t*)func,
(size_t*)heapAddress,
(size_t*)&tls
};
size_t* pa[7];
ret = syscall(333, va, pa, 7);
char printArray [7][20] = {"Data", "BSS", "Code", "Stack",
"Library", "heap", "thread"};
for(int i=0;i<7;i++){
printf("\n %d) %8s (va/pa) = %16p / %20p", i+1, printArray[i], va[i], pa[i]);
}
tls++;
printf("\n\n");
pid_t rpid = wait(NULL);
printf("Catch the child process pid : %d\n",rpid);
}
if(ret > 0) printf("error!!!QQQQQQQ");
return 0;
}
```
### Process
* **設計流程**
1. 2nd ver.與1st ver.的差別在於開啟兩個processes的方式,這個版本使用的是fork()去開啟一個新的child process。
2. 將fork()回傳的PID用if-else去判斷目前執行程式的是parent process或child process。(PID>0 -> parent ; PID=0 -> child)
3. 最後結束時parent process會呼叫wait(),會去抓取已經結束的child process的資源並且回傳該child的PID。
```c=
./getpa2_fork
```
### Result of the 2nd ver.

* **結果分析**
1. 同1st ver.,兩個processes的Code及Library的physical address都是相同的。
2. 與上方不同的是,兩個processes的所有virtual addresses都是相同的,但除了Code跟Library之外的physical addresses都是不同的。(virtual address相同不代表physical address會相同)
## 加分題
### Question
1. Parent process在執行wait4() or waitpid()時,為何要從已結束的child process收集資訊?
2. 會收集那些資訊? 其用途為何?
### Answer
- wait(): 當process執行wait()時,會暫停當前執行中的process,wait()會分析the child process of current process是否已經exit,如果找到已變成zombie process的child process,就會收集該child process的訊息,收集完後會將該zombie process銷毀。
-- 參數status會儲存child process退出時的訊息(如task_struct, thread_info等)。
- waitpid(): waitpid()與wait()相似,但比wait()多了兩個參數(pid and options)。
-- pid: ID of waiting child process
-- options: control waitpid() by additional selection
- wait(): 可以獲得child process的狀態訊息。
- wait4(): 可以獲得child process的狀態訊息外,還能透過參數rusage取得child process資源的使用訊息(Ex: total user CPU time, total system CPU time, # of page fault, # of received signal etc.)
- 用途: 目的是為了回收及釋放資源,當child process被終止後,若parent process沒有回收及釋放資源的話,會變成仍占用著資源的zombie process。
## 補充
### System call: sleep()
```c=
static inline void sleep(unsigned sec)
{
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(sec * HZ);
}
```
* **說明**
1. sleep()會將當前process的狀態改為TASK_INTERRUPTIBLE。
2. 呼叫schedule_timeout,再藉由前者呼叫schedule()來執行context switch。
### System call中的mmap(): mmap_pgoff(...)
```c=
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, pgoff)
{
struct file *file = NULL;
unsigned long retval;
if (!(flags & MAP_ANONYMOUS)) {
audit_mmap_fd(fd, flags);
file = fget(fd);
if (!file)
return -EBADF;
if (is_file_hugepages(file))
len = ALIGN(len, huge_page_size(hstate_file(file)));
retval = -EINVAL;
if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file)))
goto out_fput;
} else if (flags & MAP_HUGETLB) {
struct user_struct *user = NULL;
struct hstate *hs;
hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
if (!hs)
return -EINVAL;
len = ALIGN(len, huge_page_size(hs));
/*
* VM_NORESERVE is used because the reservations will be
* taken when vm_ops->mmap() is called
* A dummy user value is used because we are not locking
* memory so no accounting is necessary
*/
file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
VM_NORESERVE,
&user, HUGETLB_ANONHUGE_INODE,
(flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
if (IS_ERR(file))
return PTR_ERR(file);
}
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:
if (file)
fput(file);
return retval;
}
```
* **說明**
1. sys_mmap會調用sys_mmap_pgoff函數,最終do_mmap函數實現具體的功能。
2. 函數經過檢查,會調用get_unmapped_area(file, addr, len, pgoff, flags)獲取未映射的vma,也就是一段可用的虛擬區間,最後調用mmap_region,判斷是否合法或是和跟其他vma合併,返回address。
### SIGNAL的處理機制
* **說明**
1. SIGNAL在Linux中是processes之間互相傳遞訊息的方法。
2. SIGNAL用來通知process發生了非同步事件。processes之間可以互相通過系統調用kill發送軟中斷信號。kernel也可以因為內部事件而給進程發送信號,通知process發生了某個事件。注意,SIGNAL只是用來通知某process發生了什麼事件,並不給該process傳遞任何資料。
3. 收到SIGNAL的process對各種SIGANL有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理常式,對於需要處理的SIGNAL,process可以指定處理函數,由該函數來處理。第二種方法是,忽略某個SIGNAL,對該SIGNAL不做任何處理,就象未發生過一樣。第三種方法是,對該SIGNAl的處理保留系統的預設值,這種缺省操作,對大部分的SIGNAL的缺省操作是使process得終止。
4. 進程通過系統調用signal來指定進程對某個信號的處理行為。
### Segmentation Fault vs Page Fault
* **說明**
1. Segmentation Fault:
記憶體區段錯誤,也稱存取權限衝突(access violation),它會出現在當程式企圖存取CPU無法定址的記憶體區段時。當錯誤發生時,硬體會通知作業系統產生了記憶體存取權限衝突的狀況。
2. Page Fault:
當作業系統要存取某個page時,會先跟page table中搜尋該page的PTE,這個PTE會儲存page所對應到的frame,以及其他關於這個page的資訊,例如此page目前是不是正存在於physical memory中(是不是有frame可以使用)。若要存取的分頁不存在於physical memory中,就會發生page fault。
3. 實際上,segmentation fault與page fault是截然不同的兩件事! Segmentation fault意味著程序試圖要去存取無效或非法的記憶體位址,Page fault意味著程序要存取的資料目前尚未在physical memory中,因此需要透過MMU將所需要的page放到swap進frame裡。前者為非法存取,程序一般會停止並且跳error message,後者則為合法的,若page fault發生的次數越少,代表作業系統運作的效能越好,減少讀寫次數也能延長硬體壽命!
### fork() vs do_fork()
* **說明**
1. fork():
此system call可以建立child process,child process會被配置與parent不同的memory space(by kernel),他的資料內容都會來自於parent的copy。需要#include <unistd.h>
2. do_fork():
此fork的作用機制與c library裡fork的作用機制不同,library中的fork會藉由parameter的值放入process的stack中進行參數的傳遞。而sytem call的fork是通過中斷process,從user mode切換到kernel mode的一種特殊的function call,藉由eax去判別屬於第幾個system call function,此system call是透過CPU的register來進行參數傳遞。
[do_fork identifier- Linux Source Code](https://elixir.bootlin.com/linux/v3.7/source/kernel/fork.c#L1555)
### Copy On Write
* **說明**

一般來說,每個process都有屬於自己私人的記憶體空間,如heap, stack, BSS, data 等,但是 processes 之間也可能會使用到相同的資源,例如在寫C language使用的libc。這些不會修改到資源就可以透過virtual address及MMU等提供的位址轉換機制得以使用相同資源而不作不必要的複製。
而在實作上多個process使用相同資料時,在一開始只會loading一份資料,並且被標記成read-only。當有 process 要寫入時則會對kernel觸發page fault,進而使得 page fault handler處理copy on write的操作。
fork()system call 會建立新的 process,當父行程 fork()一個子行程之後,子行程並沒有馬上複製父行程的資料,而是暫時與父行程共用,在自己的page table上標示這部分的資料只讀不寫,直到某一方要修改其中的資料的時候,會引發分頁錯誤, do_wp_page()函數會處理那些行程試圖修改、標示為只讀的頁面,重新分配記憶體頁面並且複製就的頁面內容到新的頁面中。
也就是說,在Linux當中它會先複製原先process 的 mm_struct, vm_area_struct以及page table,並且讓每個page的flag設為 read-only 。最後,當有作更改時則會利用COW機制進行處理。
* **流程如下:**

## Reference
[wait(), waitpid(), wait4](https://www.cnblogs.com/tongye/p/9558320.html)
[wait(), waitpid(), wait4](https://blog.csdn.net/qq_40073459/article/details/104453740)
[replace wait4() by waitpid()](https://stackoverflow.com/questions/35316374/why-did-wait4-get-replaced-by-waitpid)
[fork()](https://wenyuangg.github.io/posts/linux/fork-use.html)
[sleep()](https://elixir.free-electrons.com/linux/v3.9/source/drivers/staging/comedi/drivers/me_daq.c#L179)
[signal](https://lyt0112.pixnet.net/blog/post/347001917-linux-%E4%BF%A1%E8%99%9Fsignal%E8%99%95%E7%90%86%E6%A9%9F%E5%88%B6)
[segmentation fault](https://grantliblog.com/2022/05/05/%E6%B7%BA%E8%AB%87-c-%E4%B8%AD%E7%9A%84segmentation-fault-%E9%8C%AF%E8%AA%A4/)
[page fault](https://magiclen.org/page-fault/)
[do_fork](https://blog.csdn.net/gatieme/article/details/51569932)
[system call vs normal function call](https://blog.csdn.net/weixin_40710708/article/details/105370454)
[常用的system call](https://hackmd.io/@dZfCcN4hT8aUuDPv3B8CWQ/B1zxcLJmK)
[fork觀念由淺入深](https://wenyuangg.github.io/posts/linux/fork-use.html)
[copy on write](https://hackmd.io/@linD026/Linux-kernel-COW-Copy-on-Write)