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.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.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