陈民禾原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”,博客内容的具体要求如下:

一.上一周内容复习

    上一周主要学习了计算机的三大法宝:存储程序计算机、函数调用堆栈、中断。

    操作系统有两把宝剑:中断上下文的切换(也就是保存现场和恢复现场)和进程上下文的切换

二.Linux内核源代码简介

  • arch : 针对不同的计算机体系结构  
  • block : 块设备驱动
  • crypto:
  • documentation : 内核文档
  • drivers : 设备驱动
  • fs : 文件系统
  • include : 头文件
  • init : 初始化
  • ipc : 进程间通信
  • kernel : Linux大多数关键的核心功能都在此目录
  • lib : 库
  • mm : 内存管理
  • net : 网络协议
  • samples :
  • scripts: 配置内核的脚本
  • security :
  • sound : 音频设备驱动
  • usr :
  • virt :

   

   首先在最左边一列是arch/目录,arch目录是代码量相当庞大,因为它的内核支持很多体系结构,这里面的内容很复杂。arch/x86目录下的代码时我们重点关注的。我们可以在再回到根目录。根目录除了arch目录还有几个其他的目录,比如Documentation文档,drivers也有很多源代码,还有firmware,还有include,还有另外一个很关键的目录——init目录,init目录下一个很关键的目录是main.c,内核启动的相关的代码都在init目录下,我们在阅读普通的代码的时候都有一个main函数,我们从main函数开始读代码,同样呢,linux在init目录下有个main.c,这是整个Linux内核启动起点,起点不是main函数,是start_kernel,start_kernel函数相当于普通C程序的的main 函数,这个实际上是初始化linux内核的起点,这个地方是内核开始初始化了。

    我们再详细分析,我们再回过头来看net,那么我们比较关心的是arch下面的X86,还有init、kernel的代码,总之这个从软件工程来看是实现视图里面内核源代码的readme.readme里面有很多的内容,很多配置方法,执行make运行。make all noconfig是把所有的可选项目都给关闭了,ps:编译内核是make all。

三.下面是直接用自己的笔记本电脑虚拟机运行的方法

首先可以下载内核源代码编译内核

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/

git clone https://github.com/menging/menu.git

cd menu

gcc -o init linktable.c menu.c test.c -m32 -static -lpthread    /*用一个gcc来简单编译一下,静态的,32位的编译成的文件名叫init,init是第一个用户态进程,是1号进程*/

cd ../rootfs  

cp ../menu/init ./         //把init拷贝到rootfs的目录下面去

find . | cpio -o Hnewc | gzip -9> ../rootfs.img   //使用cpio这个方法把所有的文件打包成一个rootfs的镜像文件,这样根文件系统的镜像就制作好了

 启动MenuOS系统

cd ~/LinuxKernel/

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
1.在原来配置的基础上,make menuconfig选中如下选项重新配置Linux,使之携带调试信息

kernel hacking->

2.[*]compile the kernel with debug info //不用实验楼,需要做这一步

3.make重新编译,时间较长

使用gdb跟踪调试内核

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

# -S freeze CPU at startup(use 'c' to start execution)

#-s shorthand for -gdb tcp::1234   //若不想使用1234端口,则可以使用-gdb tcp::XXXX

打开另一个shell窗口

gdb

(gdb) file linux-3.18.6/vmlinux #在gdb界面中targe remote之前加载符号表

(gdb) target remote:1234# 建立gdb和gdbsever之间的连接,按c让qemu

(gdb)break start_kernel#断点的设置可以在target remote之前,也可以在之后

下面就是在实验楼里面这么把我们构造的一个简单的linux_kernel运行起来,这个内核源代码已经给编译好了,这里边还有一个rootfs,这里面有一个rootfs。img,我们前面的工作已经做好了,所以这里我们可以直接启动起来 qemu -kernel linux -3.18.6/arch/x86/boot/bzImage -initrd也就是指明一个根文件系统,根文件系统就是rootfs.img我们就这样将其启动起来,接下来呢,我们可以看到内核在启动,内核启动完了就开始执行了,可以在图中看到MenuOS的字样。

四.内核部分代码分析

里面的主要程序及功能:
setup_arch(&command_line) ,完成内存映像的初始化; 
page_alloc_init(),创建内核页表,映射所有物理内存和io空间;
trap_init(),初始化硬件中断,函数中设置了很多中断门; 
sched_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是一个函数,可以看到这个函数末尾的代码

if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

执行/sbin/init程序。init进程是Linux系统的1号进程,由Linux内核直接启动,是其他用户进程的祖先。然后新建kthread进程,即2号进程,是内核态进程的祖先。

五.下面是我的实验截图

启动Linux内核,加载出MenuOS

进入内核文件目录使用qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S命令启动内核,-S选项表示在CPU初始化时冻结内核。

在终端分割出一个窗口用于gdb调试

gdb break

file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表

target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行

break start_kernel # 断点的设置可以在target remote之前,也可以在之后

使用break命令在start_kernel处设置断点,按c继续执行内核到断点位置

此时使用list命令可以看到start_kernel位置的代码

 

 跟踪cpu_startup_entry断点,0号进程启动

 五.实验感想

     内核的启动过程可以简单地这么来看:start_kernel从内核一启动的时候它会一直存在,这个就是0号进程,idle就是一个while0,一直在循环着,当系统没有进程需要执行的时候就调度到idle进程,我们在windows系统上会经常见到,叫做system idle,这是一个一直会存在的0号进程,然后呢就是0号进程创建了1号进程,这个init_process是我们的1号进程也就是第一个用户态进程,也就是它默认的就是根目录下的程序,也就是常会找默认路径下的程序来作为1号进程,1号进程接下来还创建了kthreadd来管理内核的一些线程,这样整个程序就启动起来了。也就是所谓的道生一,一生二,二生三,三生万物。 

 

posted on 2016-03-13 20:31  20135124  阅读(384)  评论(0编辑  收藏  举报