# Semester Project
This note includes the introduction and steps we've done for the semester project of the Linux course.
## Team Members
112552015 李昀磬<br>
112552024 胡淑旻<br>
111552012 周芊華
## Goal
* Add some system calls
* Write a multi-thread program with three threads (main thread, thread 1, and thread 2)
* Add new system calls to check which segments of a thread are shared by others
* (optional) Obtain the the size, star address, and end address of each thread segment
## Overview
* Built on macOS Monterey with **Apple M2** chip
* Work based on ubuntu-22.04-LTS-arm64
* Kernel version 5.15.0-91-generic
## Prework
### Get the Needed App and Setup
1. Go to UTM's official website and install the app
2. Download [ubuntu server 22.04.3 LTS](https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.3-live-server-arm64.iso?_ga=2.102778755.2002784953.1703913926-1870441787.1703913926) iso file
3. Launch UTM and click the plus sign to add a new VM
4. Click "Virtualize" then select the operating system "Linux"
5. Under "Boot ISO image", select the iso file you just downloaded
6. Use the default memory setting (4096 MB), set the number of CPU cores to 4 and click next
7. Set storage size to 64GB
8. Finish the rest of the settings and click save
### Install ubuntu
1. Launch the VM
2. Follow the steps to install ubuntu
3. Select "reboot the server" once its done, then the screen will turn black and seems like nothing running
4. Disconnect the server and go back to UTM
5. Scroll down to the bottom and empty the CD/DVD (in the dropdown menu, click clear)
### Install GUI (optional)
1. Launch your VM
2. Login by entering the user name and password you just set while installing ubuntu
3. In the terminal run the following commands
``` bash
# update apt
sudo apt update
# install ubuntu Desktop
sudo apt install ubuntu-desktop
# reboot
sudo reboot
```
4. Re-launch the VM and you'll now able to use ubuntu desktop
## Get The Kernel Source Code
### Check kernel verion
First, check your kernel version by typing the following:
``` bash
uname -r
```
Mine is **5.15.0-91-generic**.
### Download the kernel source
``` bash
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.9.tar.xz
```
By downloading a kernal of a higher version, the kernel gets automatically updated when you reboot the system after compiling.
### Extract tar.xz file
``` bash
unxz -v linux-5.16.9.tar.xz
```
### Verify Linux kernel tartball with pgp
``` bash
# First grab the PGP signature for linux-5.16.9.tar
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.9.tar.sign
# Try to verify it
gpg --verify linux-5.16.9.tar.sign
```
You'll get outputs like:

Grab the public key from the PGP keyserver in order to verify the signature
``` bash
gpg --keyserver keyserver.ubuntu.com --recv-keys $(YOUR_RSA_KEY)
gpg --no-default-keyring -a --export $(YOUR_RSA_KEY) | gpg --no-default-keyring --keyring ~/.gnupg/trustedkeys.gpg --import
```
Now verify gpg key again with the gpg command
``` bash
gpg --verify linux-5.16.9.tar.sign
```
Extract the Linux kernel tarball using the tar command below
``` bash
tar xvf linux-5.16.9.tar
```
:::warning
:bulb: A digital signature certifies and timestamps a document. If the document is subsequently modified in any way, a verification of the signature will fail.
A digital signature can serve the same purpose as a hand-written signature with the additional benefit of being tamper-resistant.
The GnuPG source distribution, for example, is signed so that users can verify that the source code has not been modified since it was packaged.
:::
## Install the required compilers and other tools
Since we're using Debian/Ubuntu:
``` bash
sudo apt-get install build-essential libncurses-dev bison flex libssl-dev libelf-dev
```
## Add New System Calls
### Define a new system call sys_my_syscall()
You're currently under the path `/home/${name}/linux-5.16.9`
Create a file
``` bash
mkdir my_syscall
vi my_syscall/my_syscall.c
```
Write the following code to the editor
``` c=
#include <linux/sched.h> // For current pointer and task_struct
#include <linux/printk.h> // For printk function
#include <linux/syscalls.h> // For asmlinkage
#include <linux/highmem.h> // For high memory management
#include <linux/hugetlb.h> // For huge pages support
// Function to convert a virtual address to a physical address
unsigned long virtophys(unsigned int virt) {
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
unsigned long page_addr, page_offset, phys;
// Traverse the page tables to find the corresponding physical address
pgd = pgd_offset(current->mm, virt);
if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
goto out;
p4d = p4d_offset(pgd, virt);
if (p4d_none(*p4d) || unlikely(p4d_bad(*p4d)))
goto out;
pud = pud_offset(p4d, virt);
if (pud_none(*pud) || unlikely(pud_bad(*pud)))
goto out;
pmd = pmd_offset(pud, virt);
if (pmd_huge(*pmd))
goto out;
if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
goto out;
pte = pte_offset_map(pmd, virt);
if (!pte_present(*pte)) {
pte_unmap(pte);
goto out;
}
// Calculate the physical address
page_addr = pte_val(*pte) & PAGE_MASK;
page_offset = virt & ~PAGE_MASK;
phys = page_addr | page_offset;
return phys;
out:
return 0;
}
// Define a new system call using the SYSCALL_DEFINE macro
SYSCALL_DEFINE1(my_syscall, unsigned int __user *, result) {
struct mm_struct *mm;
unsigned long start_phy, end_phy;
if (current->mm) {
mm = current->mm;
printk(KERN_INFO "Hello mysyscall\n");
printk(KERN_INFO "Process name is %s (pid %d)\n", current->comm, current->pid);
if (mm->mmap) {
// For each memory segment, convert its start and end virtual addresses
// to physical addresses and print them
start_phy = virtophys(mm->start_stack);
end_phy = virtophys(mm->start_stack + mm->stack_vm);
printk(KERN_INFO "Stack segment virtual start=0x%lx, end=0x%lx\n", mm->start_stack, mm->start_stack + mm->stack_vm);
printk(KERN_INFO "Stack segment physical start=0x%lx, end=0x%lx\n", start_phy, end_phy);
// ... Repeat for other memory segments (heap, data, and text)
// Convert the user-provided address to a physical address and copy it back to user space
unsigned long physicalAddress = virtophys((unsigned long)result);
if (copy_to_user(result, &physicalAddress, sizeof(physicalAddress))) {
return -EFAULT; // Use standard error code
}
}
}
return 0;
}
```
Create a Makefile in the my_syscall directory (beware of the uppercase)
``` bash
vi Makefile
```
Add the following code to ensure that the my_syscall.c file is compiled and included in the kernel source code.
```
obj-y := my_syscall.o
```
Add `my_syscall/` to the kernel’s Makefile
Go back to the parent directory and open "Makefile"
``` bash
cd ..
vi Makefile
```
Search for `core-y` in the document and add `my_syscall/` to the end of the line
```
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ my_syscall/
```
This is to tell the compiler that the source files of our new system call (sys_my_syscall()) are in present in the my_syscall directory.
### Add the new system call to the system call table
Go to `syscalls` and edit `syscall_64.tbl` (for 64-bit)
``` bash
vi arch/x86/entry/syscalls/syscall_64.tbl
```
And add the following to the last line
```
548 common my_syscall sys_my_syscall
```

### Add to header file
Go to `include/linux/` under the `linux-5.16.9/`
``` bash
vi include/linux/syscalls.h
```
Edit Linux syscall interface and add the following right before the *#endif*.
``` c
asmlinkage long sys_my_syscall(unsigned int __user *result);
```

This defines the prototype of the function of our system call. “asmlinkage” is a key word used to indicate that all parameters of the function would be available on the stack.
## Compile The Kernel
### Make sure it is clean before starting
The following command cleans up the directory.
``` bash
sudo make mrproper
```
### Configure the Linux kernel features and modules
Before start building the kernel, one must configure Linux kernel features. You must also specify which kernel modules (drivers) needed for your system. Just simply copy the existing config file for now.
``` bash
# go to the source code file
cd linux-5.16.9
# copy config file
cp -v /boot/config-$(uname -r) .config
```
If you just try to compile kernel, if outputs the error:

To fix this, open `.config`, comment out the `CONFIG_SYSTEM_TRUSTED_KEYS`, `CONFIG_MODULE` and `CONFIG_SYSTEM_REVOCATION_KEYS`

Type in the following to start kernel configuration
``` bash
sudo make menuconfig
```
Tap the **tab** key to navigate to **\<Load\>** and load whatever config you want.
After editing the configs, navigate to **\<Save\>** and save `.config` file.
Run the make command to compile kernel
``` bash
# -j specifies how many cores to use
# -s to output the errors only
sudo make -j4 -s
```
It outputs some warning about *the frame size is xxxx bytes larger than 1024*, so I edited the `.config` file, adjust `CONFIG_FRAME_WARN` from 1024 to 2048, and compile again.
It ended up with the following error:

Install **dwarves** to fix this.
``` bash
sudo apt-get install dwarves
```
Compile the kernel again, and got another error:

Add the following code to `scripts/pahole-flags.sh`
``` sh
if [ "${pahole_ver}" -ge "124" ]; then
# see PAHOLE_HAS_LANG_EXCLUDE
extra_pahole="${extra_paholeopt} --skip_encoding_btf_enum64"
fi
```
Although I gave 64GB for the VM, it still ran out of space. After checking the space allocated, I found that ubuntu only used half of the space. The remainder is reserved in case the user needs to create another logical volume in the future.
As a result, I extended the lvm by the following command:
``` bash
# without --resizefs, the command will only extend the logical volume but not the filesystem inside it.
sudo lvextend --resizefs -l +100%FREE ubuntu-vg/ubuntu-lv
```
## Install The Linux Kernel Modules and Install Kernel
``` bash
sudo make modules_install -j4
sudo make install -j4
```
This will generate three files into `/boot` directory and modify the kernel grub configuration file.

## (optional) Update grub Config
You need to modify Grub 2 boot loader configurations. The following commands are optional as make install does everything for your but included here for historical reasons only:
``` bash
sudo update-initramfs -c -k 5.16.9
sudo update-grub
```
## Reboot Your System
Reboot the computer and verify new Linux kernel after reboot
``` bash
reboot
# check this after reboot
uname -mrs
```

## Test System Call
Go to the home directory (`cd ~`), create a file called `userspace.c` and paste the following code.
The code is a multi-threaded program that uses a custom system call (defined as number 548).
``` c=
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#define MEMORY_SIZE 1000000
#define __NR_my_syscall 548
void print_result(int *result, char * str) {
for(int i = 0; i < MEMORY_SIZE; i += 2) {
if(result[i + 1] != 0) {
printf(" %s Virtual Address: 0x%x Physical Address: 0x%x\n", str , result[i], result[i + 1]);
}
}
}
void* child(void* arg) {
int* result = (int *)arg;
syscall(__NR_my_syscall, "child thread", result);
pthread_exit(NULL);
};
int main() {
// allocate memory for the results
int* result = calloc(MEMORY_SIZE, sizeof(int));
int* result_1 = calloc(MEMORY_SIZE, sizeof(int));
int* result_2 = calloc(MEMORY_SIZE, sizeof(int));
pthread_t thread1 , thread2;
// main thread which makes the system call
syscall(__NR_my_syscall, "I'm the main thread");
// creates two child threads
pthread_create(&thread1, NULL, child, (void *)result_1);
pthread_create(&thread2, NULL, child, (void *)result_2);
// main thread and its results
syscall(__NR_my_syscall, "Main thread", result);
print_result(result, "main thread");
// wait for thread1 to finish and print its results
pthread_join(thread1, NULL);
print_result(result_1, "thread1");
// wait for thread2 to finish and print its results
pthread_join(thread2, NULL);
print_result(result_2, "thread2");
return 0;
}
```
Compile and run the program
``` bash
gcc userspace.c
./a.out
```
Check the kernel log buffer
``` bash
sudo dmesg
```
## Results
We created three threads in total (main thread, thread 1, thread 2). According to the results, the threads share the Code, Lib, Data, and BSS segments. They don't share Stack and Heap.
## Reference
[UTM | Virtual Machine for Mac](https://mac.getutm.app)
[How to compile and install Linux Kernel 5.16.9 from source code - Vivek Gite](https://www.cyberciti.biz/tips/compiling-linux-kernel-26.html)
[Adding a Hello World System Call to Linux Kernel - Anubhav Shrimal
](https://medium.com/anubhav-shrimal/adding-a-hello-world-system-call-to-linux-kernel-dad32875872)
[Adding a New System Call - The Linux Kernel Documentation](https://docs.kernel.org/process/adding-syscalls.html)
[Kernel Build Guide - By: Isaiah Anyimi, Ona Igbinedion, Moriah Scott](https://bjohnson.lmu.build/private/KernelBuildGuide.pdf)
[gpg - sks-keyservers gone. What to use instead?](https://unix.stackexchange.com/questions/656205/sks-keyservers-gone-what-to-use-instead)
[Ubuntu 22.04 编译 Linux 5.16.5 内核报错:FAILED: load BTF from vmlinux: Invalid argument](https://blog.csdn.net/woay2008/article/details/132748659)
[process | OuyangSheng's Blog](https://0uyangsheng.github.io/2018/05/17/Linux-process/)
[Linux进程地址管理之mm_struct](https://www.cnblogs.com/muahao/p/7462611.html)
[C/C++ Linux pthread_join 用法與範例](https://shengyu7697.github.io/cpp-pthread_join/)