構建調試Linux內核網絡代碼的環境MenuOS系統

         該實驗是基於Ubuntu18.0.4和Linux5.0.1完成的  

      1、安裝,編譯Linux內核

        1.1下載內核源代碼linux

mkdir LinuxKernel  #建立LinuxKernel根目錄
cd ~/LinuxKernel/
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz #下載Linux內核,這裏用的是5.0.1版本
xz -d linux-5.0.1.tar.xz #解壓
tar -xvf linux-5.0.1.tar
cd linux-5.0.1

        1.2安裝依賴包git

#若是已經安裝過就忽略這一步
sudo apt-get install build-essential sudo apt-get install libelf-dev sudo apt-get install libncurses-dev sudo apt-get install flex sudo apt-get install bison
sudo apt-get install libssl-dev

       1.3配置編譯須要的信息github

#進入解壓出來的目錄
cd /linux-5.0.1/
#使用現存內核的配置文件:(xxx處使用TAB補全)
sudo cp /boot/config-xxx -r .config
#應用現存配置文件
sudo make oldconfig
#僅安裝已有module
sudo make localmodconfig
#配置其餘編譯選項
sudo make menuconfig
#這個地方可能會報錯
Your display is too small to run Menuconfig!
It must be at least 19 lines by 80 columns.
make[1]: *** [menuconfig] Error 1
make: *** [menuconfig] Error 2
這裏須要點擊Terminal->Preferences->Unnamed,而後將Initial terminal size,columns的數值增大,而後從新打開終端,執行命令

                               

     接着會出現如下界面,依次選擇 Kernel hacking ->Compile-time checks and compiler options ->[ ]Compile the kernel with debug info vim

而後按Y鍵,選擇 Save ,選擇Exit直到退出。服務器

1.4 編譯網絡

sudo make
#或者使用sudo make -j*,*位cpu核數

     而後就等待編譯完成,時間取決於機器性能,通常須要20分鐘到數小時app

1.5 升級內核函數

#安裝modules
sudo make modules_install
#安裝
sudo make install
#重啓虛擬機
#查看內核版本

sudo shutdown -r now
uname -a

     能夠看到內核已經更新爲Linux 5.0.1性能

       2、製做根文件目錄

2.1 QEMU虛擬機加載內核flex

cd ~/LinuxKernel/
sudo apt install qemu#安裝qemu命令
qemu-system-x86_64 -kernel linux-5.0.1/arch/x86_64/boot/bzImage

2.2 構造MenuOS

#建立MenuOS根目錄
mkdir rootfs
#下載MenuOS
git clone https://github.com/mengning/menu.git

  這裏若是git clone的速度太慢,能夠修改/etc/hosts/內容,再刷新DNS

nslookup github.global.ssl.fastly.net
nslookup github.com
#這裏會返回兩個IP地址
sudo vim/etc/hosts
#按i進入插入模式,而後將xxxx
github.global.ssl.fastly.net xxxx github.com
添加至最後一行,而後按ESC,輸入:wq!保存退出
sudo /etc/init.d/networking restart
#刷新DNS

2.3 安裝libc6-dev-i386並初始化根目錄

cd menu
sudo apt-get install libc6-dev-i386#在64位環境下編譯32位
#注意在初始化根目錄以前要修改Makefile的內容,由於實驗樓的系統是Linux 3.18.6,這裏要改成5.0.1,修改以後以下
#qemu-system-x86_64 -kernel ../linux-5.0.1/arch/x86_64/boot/bzImage -initrd ../rootfs.img
#注意在Makefile修改命令時開頭需加上TAB鍵,不然會報錯
make rootfs

獲得結果以下,輸入help能夠看到當前MenuOS中有help,version,quit,time,time-asm等命令

接下來須要驗證MenuOS的網絡能夠正常工做,能夠經過在MenuOS上完成TCP客戶端和服務器發送和接收hello/hi

2.4 驗證MenuOS的網絡

2.4.1 將TCP網絡通訊程序的服務端集成到MenuOS 系統中

cd ~/LinuxKernel/ 
git clone https://github.com/mengning/linuxnet.git
cd linuxnet/lab2
make
cd ../../menu/#這裏要修改Makefile文件
make rootfs

2.4.2 將TCP網絡通訊程序的客戶端集成到MenuOS系統中

cd ~/LinuxKernel/
cd linuxnet/lab3
make rootfs#Make rootfs以前要修改Makefile
#未修改以前qemu -kernel ../../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img
#修改以後 qemu-system-x86_64 -kernel ../../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img

如今能夠獲得如下結果,輸入replyhi,輸入hello,收到了hi,證實MenuOS的網絡能夠正常工做

      3、gdb調試

3.1 重啓QEMU

cd ~/LinuxKernel/menu
#先修改Makefile,在qemu-system-x86_64 -kernel ../linux-5.0.1/arch/x86_64/boot/bzImage -initrd ../rootfs.img
#末尾加上-append nokaslr -s -S
make rootfs

結果以下

3.2 鏈接gdb server並調試

    從新打開一個終端

gdb
file ~/LinuxKernel/linux-5.0.1/vmlinux
target remote:1234
#設置斷點對start_kernel進行跟蹤
break start_kernel
c #繼續運行
list#查看上下文

     結果以下

       

      結果顯示gdb能夠追蹤到start_kernel函數,斷點在init/mian.c的538行

     繼續在rest_init處設置斷點

break rest_init
c

         

        斷點在init/mian.c的398行

       咱們來簡要分析一下運行到start_kernel()的過程,start_kernel()其實是內核的彙編代碼和c代碼的交接處。

       截取一段start_kernel()的源碼

asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    set_task_stack_end_magic(&init_task);
...
...

  /* Do the rest non-__init'ed, we're now alive */
    arch_call_rest_init();
}

     當程序運行到start_kernel()時:

      (1)手工經過 set_task_stack_end_magic(&init_task)建立0號進程,init_task是0號進程上下文信息的描述符,是內核中全部進程、線程

的task_struct雛形,在內核初始化過程當中,經過靜態定義構造出了一個task_struct接口,取名爲init_task,init_idle()函數會把init_task加入到

cpu的運行隊列中去,在沒有其餘進程加入cpu隊列的時候,init_task會一直運行,當其餘進程加入進來的時候,init_task就會被設置成idle,

並使用調度函數將切換到新加入進來的進程上。

    (2)將內存管理模塊、中斷、調度模塊等模塊初始化。

    (3)start_kernrl函數運行到rest_init(),開始初始化進程並建立1號和2號進程。

        咱們接着看一下rest_init的部分源碼

noinline void __ref rest_init(void)
{
    struct task_struct *tsk;
    int pid;
    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    pid = kernel_thread(kernel_init, NULL, CLONE_FS);
    rcu_read_lock();
    tsk = find_task_by_pid_ns(pid, &init_pid_ns);
    set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
    rcu_read_unlock();
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    system_state = SYSTEM_SCHEDULING;
    complete(&kthreadd_done);
    schedule_preempt_disabled();
    /* Call into cpu_idle with preempt disabled */
    cpu_startup_entry(CPUHP_ONLINE);
}
pid = kernel_thread(kernel_init, NULL, CLONE_FS)建立了1號進程
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)建立了2號進程
cpu_startup_entry(CPUHP_ONLINE)進行cpu隊列進程的切換,將0號進程設置idle
這就是Linux下的三個特殊進程
0號進程由系統自動建立, 運行在內核態
1號進程由idle經過kernel_thread建立,在內核空間完成初始化後, 加載init程序, 並最終在用戶空間
2號進程由idle經過kernel_thread建立,並始終運行在內核空間, 負責全部內核線程的調度和管理

參考資料
https://blog.csdn.net/gatieme/article/details/51484562
https://github.com/mengning/net/tree/master/lab3
相關文章
相關標籤/搜索