0
点赞
收藏
分享

微信扫一扫

[linux内核] 2.menuos搭建及内核启动过程调试



文章目录

  • ​​1.MenuOS​​
  • ​​2.linux内核目录​​
  • ​​3.内核编译安装步骤​​
  • ​​4.构建MenuOs​​
  • ​​5.跟踪调试 Linux 内核的启动过程​​
  • ​​5.1 跟踪内核启动​​
  • ​​5.2 源码分析​​
  • ​​5.3 内核启动总结​​

1.MenuOS

menuos是基于linux内核源码构造的一个简单的操作系统。它和上一篇文章中提到的mykernel没有任何关系。
上一篇文章mykernel是在硬件起初上模拟一个cpu环境,用来学习内核的时钟中断。
这篇文章的menuos就是在官方的linux源码基础加上老师写的一个内存文件镜像和一个应用程序(menu)实现的一个操作系统。

2.linux内核目录

linux内核根目录
[linux内核] 2.menuos搭建及内核启动过程调试_linux

arch/:与体系结构相关的子目录列表,比如 arm、x86、MIPS、PPC等。alpha、arm、arm64等不同目录分别支持不同的CPU和体系结构。

block/:存放关于块设备管理的代码

crypto/:存放常见的加密算法的C语言代码

Documentation/:存放一些文档

drivers/:驱动目录

firmware/:固件

fs/:文件系统

include/:头文件目录,存放公共的头文件

init/:存放内核启动时的初始化代码
init目录下的main.c中的start_kernel函数就是内核的入口,start_kernel函数之前使用汇编进行硬件初始化

ipc/:进程间通信

kernel/:存放内核本身需要的一些核心代码文件

lib/:公用的库文件。注意在内核编程中不能用C语言标准库函数,这里的lib目录下的库函数就是用来替代那些标准库函数的。

mm/:内存管理

net/:网络相关

3.内核编译安装步骤

(1)安装依赖包
(2)下载源码
(3).config:准备配置文件
(4)make menuconfig:配置内核选项
(5)make [-j #]
(6)make modules_install:安装模块
(7)make install:安装内核相关文件
(8)安装 bzImage
(9)生成 initramfs 文件
(10)编辑grub的配置文件

相关解释

qemu仿真kerne

bzImage是vmLinux经过gzip压缩后的文件,是压缩的内核映像,(b->big)
bzImage 适用于大内核,zImage 适用于小内核

vmLinux是编译出来的最原始的内核ELF文件

根文件系统一般包括内存根文件系统磁盘根文件系统

initrd是“initial ramdisk”的简写,
普通Linux系统在启动时,是boot loader将存储介质中的initrd文件加载到内存,
内核启动时先访问initrd文件系统(内存根文件系统),然后再切换到磁盘文件系统。

4.构建MenuOs

补充
IA32是Intel32位体系结构,个人理解是一个架构规范

x86一开始指Intel推出的一套通用的计算机指令集
AMD为了提高性能推出了64位指令集叫amd64
后来intel也推出了64位指令集,叫x86-64

i386是32位cpu处理器型号,从16位80286扩展而来;
80286又是从16位8086发展而来兼容其所有功能

个人理解为:在i386等的处理器芯片上加上32位指令集(x86)就构成了一个cpu,
而这两者都是遵循IA32体系结构

这里构造的 MenuOS 系统是由 Linux 内核镜像和根文件系统集成起来的。本次实验只使用initrd根文件系统。
这里的根文件系统也比较简单,只是创建了一个 rootfs.img,其中只有一个 init 的功能采用一个自己写好的menu名字的程序。

安装rootfs的过程中gcc命令会生成init可执行文件,通过qemu相关参数设置为第一个用户态的进程。
通过把这个init文件放到rootfs目录下,再使用cpio方式将其打包成一个镜像文件,
就制作成一个简单的内存根文件系统镜像。

总结
观察搭建MenuOS三个步骤及一个qemu启动命令来看。
menuos就是先下载一个linux官方的源码并编译安装好,然后再安装老师给的项目做成一个rootfs文件系统镜像。
最后通过qemu虚拟机将内核和这个文件系统镜像放进去并设置内核从磁盘加载的内存根文件系统换成我们做好的这个文件系统(其中调用程序menu),从而模拟了一个新的操作系统。
后面要进行调试跟踪,重新配置编译 Linux 内核,使之携带调试信息。

5.跟踪调试 Linux 内核的启动过程

5.1 跟踪内核启动

使用qemu虚拟机,链接内核镜像,并设置加载内核文件系统镜像为刚才做好的rootfs.img。
​​​-s​​​监听1234端口
​​​-S​​CPU 初始化之前冻结起来

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s

重新打开一个终端,打开gdb,链接1234端口,并设置断点
[linux内核] 2.menuos搭建及内核启动过程调试_网络_02

5.2 源码分析

首先看下​​start_kernel​​函数中比较重要的部分。注意start_kernel函数之前使用汇编进行硬件初始化。

asmlinkage __visible void __init start_kernel(void) 
{
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
set_task_stack_end_magic(&init_task); //初始化第一个进程pcb
...
trap_init(); //初始化中断向量
mm_init(); //内存管理的初始化
sched_init(); //调度模块的初始化
...
rest_init();
}

先看一下里面​​set_task_stack_end_magic(&init_task); //初始化第一个进程pcb​​​。
在​​​\linux-3.18.6\init\init_task.c​​中看到 ​init_task​ 的定义。

struct task_struct init_task = INIT_TASK(init_task);

变量init_task是用宏 INIT_TASK 对其进行初始化的,并没有通过fork。这里需要注意,如果没有fork,就算0号进程。
init_task 是唯一没有通过 fork 方式产生的进程。

之后在函数​​start_kernel​​​中进行了其他一系列初始化工作,最后调用了​​rest_init();​​函数。

​rest_init​ 函数定义

static noinline void __init_refok rest_init(void)
{
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.
*/
kernel_thread(kernel_init, NULL, CLONE_FS);
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();
complete(&kthreadd_done);

/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}

先调用了​kernel_thread(kernel_init, NULL, CLONE_FS);​​​fork创建了1号内核线程。
又调用​pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);​​​创建了2号内核线程。
最后执行了​init_idle_bootup_task(current);​​演变成idle进程。

​kernel_init​​​函数中执行了​​ret = run_init_process(ramdisk_execute_command);​​而括号中的变量就是设置的文件系统镜像的init文件程序。是第一个用户态进程。

​kthreadd​​ 函数的是管理和调度其他内核线程,因此所有的内核线程都是直接或者间接地以 kthreadd 为父进程的。

5.3 内核启动总结

当开机启动进入start_kernel前通过了一些汇编代码。

在start_kernel函数(0号进程执行的)中创建了init_task进程(由于不是通过fork创建的,所以init_task还是0号进程),做好了一些初始化的工作后,最后调用了rest_init()函数。

在这个函数中先创建了1号内核线程,其中执行了文件系统镜像(第一个用户态进程);
然后又创建了2号内核线程,其中是管理和调度其他内核线程,因此所有的内核线程都是直接或者间接地以 kthreadd 为父进程的。

在rest_init函数最后,init_task演变成了idle进程。


举报

相关推荐

0 条评论