---
# System prepended metadata

title: Linux-Project2
tags: [linux]

---

# Linux-Project2

###### tags: `linux`

第9組 
111522030 李孟潔
111522061 鄭伊涵
111526003 張友安

## Outline
[ToC]

&nbsp;
## 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.
![](https://i.imgur.com/sQ084KV.jpg)

* **結果分析**
    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.
![](https://i.imgur.com/DELOauy.jpg)
* **結果分析**
    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
* **說明**
![](https://i.imgur.com/JwiOkZJ.png)

    一般來說，每個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機制進行處理。

* **流程如下：**
![](https://i.imgur.com/67wODMU.png)



## 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)
