Linux内核分析——构造一个简单的Linux内核MenuOS

pianogirl 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、Linux内核源代码

http://codelab.shiyanlou.com/xref/linux-3.18.6/

值得关注的几个目录:

/arch

- 该目录中包含和硬件体系结构相关的代码,每种平台占一个相应的目录。
- 和32位PC相关的代码存放在x86目录下。
- 每种平台至少包含3个子目录:kernel(存放支持体系结构特有的特征实现)、lib(存放体系结构特有的对通用函数的实现)、mm(存放体系结构特有的内存管理程序的实现),除了这3个子目录之外,大多数体系结构在必要的情况下还有一个boot子目录,包含了在这种硬件平台上启动内核所使用的部分或全部平台特有代码。

/init

- 内核启动相关代码 -> main.c
- Linux内核启动初始化的起点就位于main.c中的函数start_kernel,相当于普通程序的main函数。

/kernel

- 存放linux内核最核心的代码,用于实现系统的核心模块,包括进程管理、进程调度器、中断处理、系统时钟管理、同步机制等。
- 该目录中的代码实现这些核心模块的主体框架,独立于具体的平台和系统架构。
- 核心模块与平台相关的代码放在arch/中。

其它

/Documentation
    这个目录下面没有内核代码,只有很多质量参差不齐的文档,但往往能够给我们提供很多的帮助。
/drivers
    显卡、网卡、SCSI适配器、PCI总线、USB总线和其他任何Linux支持的外围设备或总线的驱动程序都可以在这里找到。
/fs
    虚拟文件系统(VFS,Virtual File System)的代码,和各个不同文件系统的代码都在这个目录中。
    Linux支持的所有文件系统在fs目录下面都有一个对应的子目录。
/include
    这个目录包含了内核中大部分的头文件。
/ipc
    IPC,即进程间通信(interprocess communication)。它包含了共享内存、信号量以及其他形式IPC的代码。
/lib
    库代码,实现了一个标准C库的通用子集,包括字符串和内存操作的函数(strlen、mmcpy和其他类似的函数)以及有关sprintf和atoi的系列函数。
    与arch/lib下的代码不同,这里的库代码都是使用C编写的,在内核新的移植版本中可以直接使用。
/mm
    包含了体系结构无关部分的内存管理代码,体系相关的部分位于arch/*/mm目录下。
/net
    网络相关代码,实现了各种常见的网络协议,如TCP/IP、IPX等。
/scripts
    该目录下没有内核代码,只包含了用来配置内核的脚本文件。
    当运行make menuconfig或者make xconfig之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。
/block
    block层的实现。
/crypto
    内核本身所用的加密API,实现了常用的加密和散列算法,还有一些压缩和CRC校验算法。
/security
    这个目录包括了不同的Linux安全模型的代码,比如NSA Security-Enhanced Linux。
/sound
    声卡驱动以及其他声音相关的代码。
/usr
    实现了用于打包和压缩的的cpio等。

README:

提供内核的各种编译方法、生成文件的查看方法。例如:
    INSTALLING 如何安装内核源代码
    make mrproper 清理安装时生成的中间代码

二、构造一个简单的Linux系统

1、启动内核

实验楼帮我们省去了以下步骤,然而理解这一段是有帮助的:

//下载内核源代码编译内核
cd ~/LinuxKernel/
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
xz -d linux-3.18.6.tar.xz
tar -xvf linux-3.18.6.tar
cd linux-3.18.6
make i386_defconfig
make # 一般要编译很长时间,少则20分钟多则数小时

//制作根文件系统
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread # 系统会默认启动init,即第一个用户态进程,1号进程
cd ../rootfs
cp ../menu/init ./ #拷贝
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img # 镜像

//启动MenuOS系统
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

这里写图片描述

**一句话启动Linux内核:
qemu -kernel (该版本的Kernel所在路径) -initrd (rootfs.img)**

qemu :相当于打开一个虚拟机
kernel:启动一个内核,位置由其后的文件名指定。
initrd指令:挂了一个ramdisk虚拟硬盘,是内核的重要补充,rootfs.img就是这个虚拟硬盘,内有分区,然后启动其中的init.init是由之前编译而成,gcc -o命名为init。

2、简单分析

start_kernel()函数,相当于C中的main:

void start_kernel(void)
    {
    ………………
    page_address_init();
    // 内存相关的初始化
    trap_init();
    mm_init();
    ………………
    // 调度初始化
    sched_init();
    ………………
    rest_init();  //其他初始化函数
    }

rest_init() 函数:

void rest_init(void)
{
    int pid;
    ………………
    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);

    init_idle_bootup_task(current);
    schedule_preempt_disabled();
    cpu_startup_entry(CPUHP_ONLINE);
}

rest_init()这个函数调用系统函数 kernel_thread() 创建 1 号进程,即 init 进程,是用户态所有进程的祖先,然后,新建 kthreadd 进程,是内核态所有进程的祖先。最后,通过 cpu_startup_entry 函数启动 0 号进程。

分析过程:
首先,几乎所有的内核模块均会在start_kernel进行初始化。在start_kernel中,会对各项硬件设备进行初始化,包括一些page_address、tick等等,直到最后需要执行的rest_init中,会开始让系统跑起来。
那rest_init这个过程中,会调用kernel_thread()来创建内核线程kernel_init,它创建用户的init进程,初始化内核,并设置成1号进程,这个进程会继续做相关的系统初始化。
然后,start_kernel会调用kernel_thread并创建kthreadd,负责管理内核中得所有线程,然后进程ID会被设置为2。
最后,会创建idle进程(0号进程),不能被调度,并利用循环来不断调号空闲的CPU时间片,并且从不返回。

三、使用GDB调试器

使用gdb跟踪调试内核

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
//-S freeze CPU at startup (use ’c’ to start execution) cpu初始化之前把它冻结起来
//-s shorthand for -gdb tcp::1234 在1234端口上建立了一个gdb server
若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项

另开一个shell窗口

gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

GDB常用命令:

这里写图片描述
用b设置断点,用c继续运行到下一个断点:

四、其他

关于这次学习

一个字——晕。仅仅有C基础是远远不够的,操作系统、组成原理、汇编、编译原理……一系列课都没学过,造成我对计算机底层的东西非常非常陌生。计算机启动过程、引导、进程……这些对我来说都是要恶补的盲点。
对这样一个Linux启动的过程还是一知半解,搜了几个大神博客也是看不懂。。。在此还是把他们的博客链在下方吧:
http://www.euryugasaki.com/archives/1034
http://www.cnblogs.com/hyq20135317/p/5260671.html
http://www.cnblogs.com/20135202yjx/p/5262907.html
https://www.shiyanlou.com/courses/reports/989631

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值