zoukankan      html  css  js  c++  java
  • 20189220 余超《Linux内核原理与分析》第四周作业

    构造一个简单的Linux系统MenuOS

    第三章基础知识

    • 计算机的三大法宝:存储计算机,函数调用堆栈,中断。
    • 操作系统的两把宝剑:中断上下文,进程上下文。
    • Linux内核源码的目录结构:
      arch目录:arch目录是linux内核目录中比较重要的一个目录,因为arch目录中的代码可以使Linux内核支持不同的CPU和体系结构。
      block目录:存放Linux存储体系中关于块设备管理的代码。
      crypto目录:存放常见的加密算法的代码。
      drivers目录:驱动目录,里面分别存放了Linux内核支持的所有硬件设备的驱动源代码。
      fs目录:文件目录,里面列出了Linux支持的各种文件系统。
      include目录:头文件目录,存放公共的头文件。
      init目录:init是初始化的意思,存放Linux内核启动时的初始化的源代码。
      ipc目录:IPC就是进程间的通信,ipc目录里面是Linux支持的ipc的代码实现。
      kernel目录:kernel的意思是内核,就是Linux内核,这个文件夹存放内核本身需要的一些核心代码文件。
      mm目录:存放Linux的内存管理代码。

    构造一个简单的Linux内核

    1.使用实验楼虚拟机打开shell,构建Linux系统MenuOS

    cd LinuxKernel/  
    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img  
    

    2.用gdb跟踪调试Linux内核的启动

    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)
     -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
    

    另开一个shell窗口,输入gdb,然后运行下面的代码

    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之前,也可以在之后  
    

    我在做这步实验的时候,出现了下面的问题

    后面我发现是因为理解错了打开另一个shell这句话,我打开另一个终端了,所以里面没有那个文件,应该是直接在这个终端里面点击鼠标右键水平分割一个shell。

    3.在 start_kernel 处设置断点,并用list命令查看代码

    4.在 rest_init 处设置断点,并用list命令查看代码

    内核启动的代码分析

    1.我们首先看一下start_kernel()的源代码:

    asmlinkage __visible void __init start_kernel(void)
    501{
    502	char *command_line;
    503	char *after_dashes;
    504
    505	/*
    506	 * Need to run as early as possible, to initialize the
    507	 * lockdep hash:
    508	 */
    509	lockdep_init();
    510	set_task_stack_end_magic(&init_task);
    511	smp_setup_processor_id();
    512	debug_objects_early_init();
    513
    514	/*
    515	 * Set up the the initial canary ASAP:
    516	 */
    517	boot_init_stack_canary();
    518
    519	cgroup_init_early();
    520
    521	local_irq_disable();
    522	early_boot_irqs_disabled = true;
    

    这里我们看到了一个void lockdep_init(void) 函数,lockdep是一个内核调试模块,用来检查内核互斥机制(尤其是自旋锁)潜在的死锁问题。
    接下来是看到init_task,其在文件linux-3.18.6/init/init_task.c中定义如下:

    struct task_struct init_task = INIT_TASK(init_task);

    可见它其实就是一个task_struct,与用户进程的task_struct一样。相当于《Linux内核分析(二)》中的PCB结构体。
    init_task中保存了一个进程的所有基本信息,如进程状态,栈起始地址,进程号pid等,其特殊之处在于它的pid=0,也就是通常所说的0号进程,0号进程就是我们这样通过手工创建出来的。也就是start_kernel()创建了0号进程。
    0号进程的任务范围是从最早的汇编代码一直到start_kernel()的执行结束。

    2.接下来看一下start_kernel()的源代码:

    403	kernel_thread(kernel_init, NULL, CLONE_FS);
    404	numa_default_policy();
    405	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    406	rcu_read_lock();
    407	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    408	rcu_read_unlock();
    409	complete(&kthreadd_done);
    410
    411	/*
    412	 * The boot idle thread must execute schedule()
    413	 * at least once to get things moving:
    414	 */
    415	init_idle_bootup_task(current);
    416	schedule_preempt_disabled();
    417	/* Call into cpu_idle with preempt disabled */
    418	cpu_startup_entry(CPUHP_ONLINE);
    419}
    420
    421/* Check for early params. *
    

    通过rest_init()新建kernel_init和kthreadd内核线程。调用kernel_thread()创建1号内核线程
    在rest_init()函数中有这样一句话:
    kernel_thread(kernel_init, NULL, CLONE_FS);
    其中kernel_thread()的源码在文件linux-3.18.6/kernel/fork.c中定义,如下:

    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    {
        return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
            (unsigned long)arg, NULL, NULL);
    }
    

    这里相当于fork出了新进程来执行kernel_init()函数。
    3.pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);创建PID为2的内核线程

    483int kthreadd(void *unused)
    484{
    485	struct task_struct *tsk = current;
    486
    487	/* Setup a clean context for our children to inherit. */
    488	set_task_comm(tsk, "kthreadd");
    489	ignore_signals(tsk);
    490	set_cpus_allowed_ptr(tsk, cpu_all_mask);
    491	set_mems_allowed(node_states[N_MEMORY]);
    492
    493	current->flags |= PF_NOFREEZE;
    494
    495	for (;;) {
    496		set_current_state(TASK_INTERRUPTIBLE);
    497		if (list_empty(&kthread_create_list))
    498			schedule();
    499		__set_current_state(TASK_RUNNING);
    500
    501		spin_lock(&kthread_create_lock);
    502		while (!list_empty(&kthread_create_list)) {
    503			struct kthread_create_info *create;
    504
    505			create = list_entry(kthread_create_list.next,
    506					    struct kthread_create_info, list);
    507			list_del_init(&create->list);
    508			spin_unlock(&kthread_create_lock);
    509
    510			create_kthread(create);
    511
    512			spin_lock(&kthread_create_lock);
    513		}
    514		spin_unlock(&kthread_create_lock);
    

    kthreadd函数的任务是管理和调度其他内核线程 kernel_thread。for 循环中运行 kthread_create_list 全局链表中维护的 kthread, 在create_kthread()函数中,会调用 kernel_thread 来生成一个新的进程并被加入到此链表中,因此所有的内核线程都是直接或者间接的以 kthreadd 为父进程。

    总结

    1.Linux内核启动的一些流程
    start_kernel( )函数完成了Linux内核的初始化工作。几乎每天内核部件都是用这个函数进行初始化的,我们只是说道了其中的一小部分:
    1.调用sched_init()函数来初始化调度程序
    2.调用build_all_zonelists()函数俩初始化内存管理
    3.调用page_alloc_init()函数来初始化伙伴系统分配程序
    4.调用trap_init()函数和init_IRQ()函数以初始化IDT
    5.调用softing_init()函数初始化TASKLET_SOFTIRQ和HI_SOFTIRQ(软中断)
    6.调用time_init()初始化系统日期时间
    7.调用kmem_cache_init()函数初始化slab分配器(普通和高速缓存)
    8.调用calibrate_delay()函数用于确定CPU时钟(延迟函数)
    9.调用kernel_thread()函数为进程1创建内个线程,这个内核线程又会创建其他的内核线程并执行/sbin/init程序
    在start_kernel()开始执行之后会显示linux版本,除此之外,在init程序和内核线程执行的最后阶段还会显示很多其他信息。最后,就会在控制台上出现熟悉的登陆提示,通知用户Linux内核已经启动正在运行。

    2.内核的进程分析
    在本实验中,我分析了Linux系统的启动过程。最初执行的进程即是0号进程init_task,它是被静态产生的,内存栈的位置固定,执行一些初始化的工作。一直到start_kernel开始调用执行sched_init(),0号进程被init_idle(current, smp_processor_id())进程初始化成为一个idle task,变成上一次实验中的进程一样的,通过一个while循环不断执行,只要运行栈里没有别的进程它就执行,循环中不断检测运行栈里是否有其他进程并通过schedule函数进行调度。
    其中idle进程的产生为:idle是一个进程,其pid号为 0。其前身是系统创建的第一个进程,也是唯一一个没有通过fork()产生的进程。它在本实验中,具体是由init/main.c中start_kernel函数的set_task_stack_end_magic(&init_task)这一行开始实现的。其中的init_task就是手工创建的PCB,pid=0的进程,也就是最终的idle进程。
    而1号进程的产生为:而到了kernel_thread(kernel_init, NULL, CLONE_FS);则通过fork()建立了pid=1的1号进程,也叫init进程,它是第一个用户态进程,它会继续完成剩下的初始化工作,成为系统中的其他所有进程的祖先。而创建了1号进程后,随着init_idle_bootup_task(current);等函数的调用,0号进程就演变成了idle进程。而idle进程就是当系统没有进程需要执行的时候来调度用的。所以start_kernel里、rest_init里创建了0号进程,该进程在系统初始化时候建立,并在系统运行过程中一直存在;而由0号进程,生成了1号进程,以及之后的许许多多的进程。最后进入了cpu_startup_entry。这个其实就是调用了cpu_idle。其实里面就是在while循环里调用了0号进程。

  • 相关阅读:
    Nuxt.js 踩坑记录(2) 使用sequelize时,提示install mysql2,安装了仍然不能解决问题
    Nuxt.js 踩坑记录,(1)引入fs包报错
    JS手写call、bind、apply
    手写Promise简易版
    generator函数
    ["1","2","3"].map(parseInt)结果
    改变对象转换为原始值的方式
    instanceof判断问题
    e.target和e.currentTarget区别
    java设计模式--适配器模式
  • 原文地址:https://www.cnblogs.com/yuchao123/p/9876423.html
Copyright © 2011-2022 走看看