Writing an operating system kernel based on mykernel 2.0

Resource download: https://github.com/mengning/mykernel

Experiment content:

1. Configure the experimental environment and complete the Linux kernel compilation.

2. Modify the source code of the system, and implement a simple operating system kernel based on mykernel 2.0.

3. This paper briefly analyzes the core functions and operation mechanism of the operating system kernel.

Experimental environment:

Ubuntu 18.04.4 under VMWare virtual machine, linux-5.4.34 is used in the experiment.

1 kernel compilation

1.1 preparations

1.1.1 modify image source address

In order to save download time, domestic image source is used.

cd /etc/apt/
sudo cp sources.list sources.list.bk
sudo gedit sources.list
deb http://mirrors.aliyun.com/ubuntu bionic main multiverse restricted universe
deb http://mirrors.aliyun.com/ubuntu bionic-updates main multiverse restricted universe
deb http://mirrors.aliyun.com/ubuntu bionic-security main multiverse restricted universe
deb http://mirrors.aliyun.com/ubuntu bionic-proposed main multiverse restricted universe
deb http://mirrors.aliyun.com/ubuntu bionic-backports main multiverse restricted universe

Update apt source after modifying and saving sources.list.

sudo apt-get update

1.1.2 add hosts map

Add the mapping between GitHub resource domain name and corresponding IP address.

sudo vi /etc/hosts
151.101.76.133 raw.githubusercontent.com

1.1.3 install axel

Multithreaded download tool for downloading Linux kernel.

sudo apt install axel

1.2 download kernel patch

wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch

The patch files are as follows. According to the diff comparison, the following changes have been made:

  • Call the custom my timer handler function when the clock is interrupted, and print out<<<<<<<<
  • Declare my start kernel and my timer handler functions in start kernel. H and timer.h respectively
  • Calling my_start_kernel function in main.c
  • Add the compilation path of mykernel in Makefile
  • Create Makefile file for mykernel
  • Write myinterrupt.c and mymain.c files, simulate the system running in mymain.c, and output once every 100000 cycles
diff -Naur linux-5.4.34/arch/x86/kernel/time.c linux-5.4.34-mykernel/arch/x86/kernel/time.c
--- linux-5.4.34/arch/x86/kernel/time.c	2020-04-21 15:05:05.000000000 +0800
+++ linux-5.4.34-mykernel/arch/x86/kernel/time.c	2020-04-25 21:58:16.436717811 +0800
@@ -16,6 +16,7 @@
 #include <linux/irq.h>
 #include <linux/i8253.h>
 #include <linux/time.h>
+#include <linux/timer.h>
 #include <linux/export.h>
 
 #include <asm/vsyscall.h>
@@ -59,6 +60,7 @@
 static irqreturn_t timer_interrupt(int irq, void *dev_id)
 {
 	global_clock_event->event_handler(global_clock_event);
+    	my_timer_handler();
 	return IRQ_HANDLED;
 }
 
diff -Naur linux-5.4.34/include/linux/start_kernel.h linux-5.4.34-mykernel/include/linux/start_kernel.h
--- linux-5.4.34/include/linux/start_kernel.h	2020-04-21 15:05:05.000000000 +0800
+++ linux-5.4.34-mykernel/include/linux/start_kernel.h	2020-04-25 22:00:17.304717811 +0800
@@ -9,6 +9,7 @@
    up something else. */
 
 extern asmlinkage void __init start_kernel(void);
+extern void __init my_start_kernel(void);
 extern void __init arch_call_rest_init(void);
 extern void __ref rest_init(void);
 
diff -Naur linux-5.4.34/include/linux/timer.h linux-5.4.34-mykernel/include/linux/timer.h
--- linux-5.4.34/include/linux/timer.h	2020-04-21 15:05:05.000000000 +0800
+++ linux-5.4.34-mykernel/include/linux/timer.h	2020-04-25 21:56:45.064717811 +0800
@@ -193,6 +193,8 @@
 
 extern void init_timers(void);
 extern void run_local_timers(void);
+extern void my_timer_handler(void);
+
 struct hrtimer;
 extern enum hrtimer_restart it_real_fn(struct hrtimer *);
 
diff -Naur linux-5.4.34/init/main.c linux-5.4.34-mykernel/init/main.c
--- linux-5.4.34/init/main.c	2020-04-21 15:05:05.000000000 +0800
+++ linux-5.4.34-mykernel/init/main.c	2020-04-25 22:01:13.476717811 +0800
@@ -781,6 +781,7 @@
 	arch_post_acpi_subsys_init();
 	sfi_init_late();
 
+    	my_start_kernel();
 	/* Do the rest non-__init'ed, we're now alive */
 	arch_call_rest_init();
 }
diff -Naur linux-5.4.34/Makefile linux-5.4.34-mykernel/Makefile
--- linux-5.4.34/Makefile	2020-04-21 15:05:05.000000000 +0800
+++ linux-5.4.34-mykernel/Makefile	2020-04-25 22:02:47.144717811 +0800
@@ -1012,7 +1012,7 @@
 export MODORDER := $(extmod-prefix)modules.order
 
 ifeq ($(KBUILD_EXTMOD),)
-core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
+core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ mykernel/
 
 vmlinux-dirs	:= $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
 		     $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
diff -Naur linux-5.4.34/mykernel/Makefile linux-5.4.34-mykernel/mykernel/Makefile
--- linux-5.4.34/mykernel/Makefile	1970-01-01 08:00:00.000000000 +0800
+++ linux-5.4.34-mykernel/mykernel/Makefile	2020-04-25 17:14:13.537908421 +0800
@@ -0,0 +1,6 @@
+#
+# Makefile for the linux mykernel.
+#
+
+obj-y     = mymain.o myinterrupt.o
+
diff -Naur linux-5.4.34/mykernel/myinterrupt.c linux-5.4.34-mykernel/mykernel/myinterrupt.c
--- linux-5.4.34/mykernel/myinterrupt.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-5.4.34-mykernel/mykernel/myinterrupt.c	2020-04-25 19:09:50.612555999 +0800
@@ -0,0 +1,44 @@
+/*
+ *  linux/mykernel/myinterrupt.c
+ *
+ *  Kernel internal my_timer_handler
+ *
+ *  Copyright (C) 2013  Mengning
+ *
+ */
+#include <linux/kernel_stat.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/percpu.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/swap.h>
+#include <linux/pid_namespace.h>
+#include <linux/notifier.h>
+#include <linux/thread_info.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/posix-timers.h>
+#include <linux/cpu.h>
+#include <linux/syscalls.h>
+#include <linux/delay.h>
+#include <linux/tick.h>
+#include <linux/kallsyms.h>
+#include <linux/irq_work.h>
+#include <linux/sched.h>
+#include <linux/sched/sysctl.h>
+#include <linux/slab.h>
+
+#include <asm/uaccess.h>
+#include <asm/unistd.h>
+#include <asm/div64.h>
+#include <asm/timex.h>
+#include <asm/io.h>
+
+/*
+ * Called by timer interrupt.
+ */
+void my_timer_handler(void)
+{
+	pr_notice("\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n");
+}
diff -Naur linux-5.4.34/mykernel/mymain.c linux-5.4.34-mykernel/mykernel/mymain.c
--- linux-5.4.34/mykernel/mymain.c	1970-01-01 08:00:00.000000000 +0800
+++ linux-5.4.34-mykernel/mykernel/mymain.c	2020-04-25 19:10:27.635058000 +0800
@@ -0,0 +1,91 @@
+/*
+ *  linux/mykernel/mymain.c
+ *
+ *  Kernel internal my_start_kernel
+ *
+ *  Copyright (C) 2013  Mengning
+ *
+ */
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/kernel.h>
+#include <linux/syscalls.h>
+#include <linux/stackprotector.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/initrd.h>
+#include <linux/acpi.h>
+#include <linux/tty.h>
+#include <linux/percpu.h>
+#include <linux/kmod.h>
+#include <linux/vmalloc.h>
+#include <linux/kernel_stat.h>
+#include <linux/start_kernel.h>
+#include <linux/security.h>
+#include <linux/smp.h>
+#include <linux/profile.h>
+#include <linux/rcupdate.h>
+#include <linux/moduleparam.h>
+#include <linux/kallsyms.h>
+#include <linux/writeback.h>
+#include <linux/cpu.h>
+#include <linux/cpuset.h>
+#include <linux/cgroup.h>
+#include <linux/efi.h>
+#include <linux/tick.h>
+#include <linux/interrupt.h>
+#include <linux/taskstats_kern.h>
+#include <linux/delayacct.h>
+#include <linux/unistd.h>
+#include <linux/rmap.h>
+#include <linux/mempolicy.h>
+#include <linux/key.h>
+#include <linux/buffer_head.h>
+#include <linux/debug_locks.h>
+#include <linux/debugobjects.h>
+#include <linux/lockdep.h>
+#include <linux/kmemleak.h>
+#include <linux/pid_namespace.h>
+#include <linux/device.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/idr.h>
+#include <linux/kgdb.h>
+#include <linux/ftrace.h>
+#include <linux/async.h>
+#include <linux/sfi.h>
+#include <linux/shmem_fs.h>
+#include <linux/slab.h>
+#include <linux/perf_event.h>
+#include <linux/file.h>
+#include <linux/ptrace.h>
+#include <linux/blkdev.h>
+#include <linux/elevator.h>
+
+#include <asm/io.h>
+#include <asm/bugs.h>
+#include <asm/setup.h>
+#include <asm/sections.h>
+#include <asm/cacheflush.h>
+
+#ifdef CONFIG_X86_LOCAL_APIC
+#include <asm/smp.h>
+#endif
+
+
+void __init my_start_kernel(void)
+{
+    int i = 0;
+    while(1)
+    {
+        i++;
+        if(i%100000 == 0)
+            pr_notice("my_start_kernel here  %d \n",i);
+            
+    }
+}
diff -Naur linux-5.4.34/mykernel/README.md linux-5.4.34-mykernel/mykernel/README.md
--- linux-5.4.34/mykernel/README.md	1970-01-01 08:00:00.000000000 +0800
+++ linux-5.4.34-mykernel/mykernel/README.md	2020-04-25 22:18:46.512717811 +0800
@@ -0,0 +1,21 @@
+mykernel 2.0
+==========
+Develop your own OS kernel by reusing Linux infrastructure, based on x86-64/Linux Kernel 5.4.34.
+
+## Set up mykernel 2.0 in Ubuntu 18.04
+
+```
+sudo apt install build-essential
+sudo apt install axel
+sudo apt install qemu # install QEMU
+sudo apt install libncurses-dev bison flex libssl-dev libelf-dev
+wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.3.34.patch
+axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
+xz -d linux-5.4.34.tar.xz
+tar -xvf linux-5.4.34.tar
+cd linux-5.4.34
+patch -p1 < ../mykernel-2.0_for_linux-5.3.34.patch
+make defconfig # Default configuration is based on 'x86_64_defconfig'
+make -j$(nproc)
+qemu-system-x86_64 -kernel arch/x86/boot/bzImage
+```

1.3 download kernel

sudo axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar

1.4 patch installation

cd linux-5.4.34
sudo apt install patch
patch -p1 < ../mykernel-2.0_for_linux-5.4.34.patch

1.5 compiling kernel

In the virtual machine environment, if the physical machine supports hyper threading, the virtual machine can be configured as dual core and four threads.

The compilation time is about four or five minutes. If the virtual machine is configured with single core and single thread by default, it will take a long time to compile using defconfig.

sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
make defconfig
make -j$(nproc)

1.6 installation of QEMU simulator

Here QEMU is used to simulate the hardware device, and run the compiled system by simulating a virtual machine running the operating system independently.

sudo apt install qemu # install QEMU
qemu-system-x86_64 -kernel arch/x86/boot/bzImage


So far, the Linux kernel has been compiled, and the general configuration process is as follows:

  • Download kernel patch
  • Download system kernel
  • By patching and compiling the system, observe the output of kernel operation and interrupt

2 modify the kernel

First, create the mypcb.h header file in the mykernel Directory:

  • Thread struct analog instruction pointer and stack pointer

  • PCB structure realizes process control block, mainly including process handle, status, stack, thread information, process function, etc., next links the process in the form of linked list

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*2

struct Thread {
    unsigned long		ip;
    unsigned long		sp;
};

typedef struct PCB{
    int pid;
    volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
    unsigned long stack[KERNEL_STACK_SIZE];
    struct Thread thread;
    unsigned long	task_entry;
    struct PCB *next;
}tPCB;

void my_schedule(void);

After that, create myinterrupt.c file to realize the interrupt effect and inter process switching:

#include "mypcb.h"

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;

/*
 * Called by timer interrupt.
 * it runs in the name of current running process,
 * so it use kernel stack of current running process
 */
void my_timer_handler(void)
{
    if(time_count%1000 == 0 && my_need_sched != 1)
    {
        printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
        my_need_sched = 1;
    } 
    time_count ++ ;  
    return;  	
}

void my_schedule(void)
{
    tPCB * next;
    tPCB * prev;

    if(my_current_task == NULL 
        || my_current_task->next == NULL)
    {
    	return;
    }
    printk(KERN_NOTICE ">>>my_schedule<<<\n");
    /* schedule */
    next = my_current_task->next;
    prev = my_current_task;
    if(next->state == 0)
    {        
    	my_current_task = next; 
    	printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);  
    	/* switch to next process */
    	asm volatile(	
        	"pushq %%rbp\n\t"
        	"movq %%rsp,%0\n\t"
        	"movq %2,%%rsp\n\t"
        	"movq $1f,%1\n\t"
        	"pushq %3\n\t" 
        	"ret\n\t"
        	"1:\t"
        	"popq %%rbp\n\t"
        	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
        	: "m" (next->thread.sp),"m" (next->thread.ip)
    	); 
    }  
    return;	
}

Finally, in mymain.c, other processes are generated according to process 0 fork, forming a circular call of the process:

#include "mypcb.h"

tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

void my_process(void);

void __init my_start_kernel(void)
{
    int pid = 0;
    int i;
    /* Initialize process 0*/
    task[pid].pid = pid;
    task[pid].state = 0;
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
    task[pid].next = &task[pid];
    /*fork more process */
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));
        task[i].pid = i;
	    task[i].thread.sp = (unsigned long)(&task[i].stack[KERNEL_STACK_SIZE-1]);
        task[i].next = task[i-1].next;
        task[i-1].next = &task[i];
    }
    /* start process 0 by task[0] */
    pid = 0;
    my_current_task = &task[pid];
	asm volatile(
    	"movq %1,%%rsp\n\t"
    	"pushq %1\n\t"
    	"pushq %0\n\t"
    	"ret\n\t"
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)
	);
} 

int i = 0;

void my_process(void)
{    
    while(1)
    {
        i++;
        if(i%10000000 == 0)
        {
            printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);
            if(my_need_sched == 1)
            {
                my_need_sched = 0;
        	    my_schedule();
        	}
        	printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
        }     
    }
}

The core of the above code is two embedded assembly codes. In mymain.c and myinterrupt.c, the start-up and inter process switching of process 0 are respectively realized:

asm volatile(
    	"movq %1,%%rsp\n\t" 	/* set task[pid].thread.sp to rsp */
    	"pushq %1\n\t" 	        /* push rbp */
    	"pushq %0\n\t" 	        /* push task[pid].thread.ip */
    	"ret\n\t" 	        /* pop task[pid].thread.ip to rip */
    	: 
    	: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)	/* input c or d mean %ecx/%edx*/
	);

In mymain.c, the top information of process 0 is stored in rsp register. By pressing the current process rbp and instruction pointer on the stack, the ret instruction is used to return the my process function pointed to by process 0 ip.

asm volatile(	
        	"pushq %%rbp\n\t" 	/* save rbp of prev */
        	"movq %%rsp,%0\n\t" 	/* save rsp of prev */
        	"movq %2,%%rsp\n\t"     /* restore  rsp of next */
        	"movq $1f,%1\n\t"       /* save rip of prev */	
        	"pushq %3\n\t" 
        	"ret\n\t" 	        /* restore  rip of next */
        	"1:\t"                  /* next process start here */
        	"popq %%rbp\n\t"
        	: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
        	: "m" (next->thread.sp),"m" (next->thread.ip)
    	); 

In myinterrupt.c, first stack the current rbp, then save the rsp information of the current process, and then update it to the rsp of the next process.

After that, the IP of the running process is pushed into the stack, and the IP of the next process is sent into the rip register through the ret instruction.

Finally, the base address of the switched process stack is recovered from the stack to the rbp register.

3 core functions

The Linux operating system mainly has the following core functions:

  • Process management

    Responsible for managing CPU resources so that processes can access the CPU in as fair a way as possible.

  • memory management

    Manage the Memory resources so that each process can share the Memory resources of the machine safely.

  • file system

    The Linux kernel abstracts the external devices with different functions into a unified file operation interface (open, close, read, write, etc.).

  • Network management

    Manage the network equipment of the system and realize various network standards.

  • Hardware driver

    The system operation is mapped to the physical device. In addition to the processor, memory and other individual entities, general device control operations are performed by addressing device related codes.

  • Interprocess communication

    Do not manage the hardware, only responsible for the communication between processes in the Linux system.

Tags: Linux sudo Makefile Ubuntu

Posted on Wed, 13 May 2020 01:02:14 -0400 by jack5100nv