20169203《Linux内核原理与分析》第五周作业

  本周的实验主要是通过gdb工具来调试查看Linux x86的内核代码,首先需要对gdb有一定的了解:

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但如果你是在UNIX平台下做软件,你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。

一般来说,GDB主要帮忙你完成下面四个方面的功能:

    1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
    2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
    3、当程序被停住时,可以检查此时你的程序中所发生的事。
    4、动态的改变你程序的执行环境。

  实验的第一部是打开shell执行如下命令:

  cd LinuxKernel/

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

可以打开MenuOS系统

  输入help命令,可以看到系统支持三个简单命令:help、version、quit

  我们再另外开一个shell窗口 进入LinuxKernel目录,输入如下命令:

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

其中

# -S freeze CPU at startup (use ’c’ tostart execution) 在系统启动的时候冻结CPU,使用c键继续执行后续操作

# -s shorthand for -gdb tcp::1234 打开远程调试端口,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项

执行结果如下

再打开另一个终端

  执行gdb命令:

  file linux-3.18.6/vmlinux

  target remote:1234

  break start_kernel

  c

既可以在start_kernel处设置断点

系统执行到start_kernel()函数,输入list命令可以查看之后的代码

对于start_kernel()函数

/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();//lockdep是一个内核调试模块,用来检查内核互斥机制(尤其是自旋锁)潜在的死锁问题。
set_task_stack_end_magic(&init_task);//手工创建的PCB,0号进程即最终的idle进程。
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();//canary值的是用于防止栈溢出攻击的堆栈的保护字。
trap_init();//对内核陷阱异常进行初始化。
mm_init();//初始化内核内存分配器。
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();//初始化调度器数据结构,并创建运行队列。
/* Do the rest non-__init'ed, we're now alive */
rest_init();//start_kernel()函数中调用的最后一个函数。

对于rest_init()函数

int pid;//定义pid变量存放进程号
rcu_scheduler_starting();
RCU(Read-Copy Update)//锁机制启动。
/*
* 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);//第一个用户态进程kernel_init
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd内核线程,它的作用是管理和调度其它内核线程。
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);//获取kthreadd的线程信息,获取完成说明kthreadd已经创建成功。
rcu_read_unlock();
complete(&kthreadd_done);//通过一个complete变量(kthreadd_done)来通知kernel_init线程。

 

对于qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img 指令 qemu是一个快速的动态译指的虚拟机。他支持多种处理器指令集的模拟。bzImage 压缩的内核映像 initrd 是在系统引导过程中挂载的一个临时根文件系统就是rootfs.img

对于file linux-3.18.6/vmlinux  vmlinux 它是是未压缩的内核,即编译出来的最原始的文件用于kernel调试,产生system.map符号表,不能用于直接加载 调试的程序用file打开

对于kernel_thread()函数  int kernel_thread (int ( * fn )( void * ), void * arg, unsigned long flags); 1. 内核线程是通过系统调用clone()来实现的,使用CLONE_VM标志(用户还可以 提供其他标志,CLONE_PID,CLONE_FS,CLONE_FILES等),因此内核线程与调用 的进程(current)具有相同的进程空间.2. 由于调用进程是在内核里调用kernel_thread(),因此当系统调用返回时,子进程也处于 内核态中,而子进程随后调用fn,当fn退出时,子进程调用exit()退出,所以子进程是在 内核态运行的. 3. 由于内核线程是在内核态运行的,因此内核线程可以访问内核中数据,调用内核函数. 运行过程中不能被抢占.

Linux在无进程概念的情况下将一直从初始化部分的代码执行到start_kernel,然后再到其最后一个函数调用rest_init。从rest_init开始,Linux开始产生进程,因为init_task是人为制造出来的其pid=0,所以它并不能称之为一个真正的进程。在rest_init函数中,内核将产生第一个真正的进程pid=1.在这之后所有的用户态进程的祖先都应该是rest_init产生的1号进程.在初始化的最后内核再调用scheule()就能使得整个系统运转起来了.对于schedule(),内核必须知道什么时候才能调度schedule(),内核提供了一个need_reched()标志来标明是否需要重新执行一次调度.对于Linux的调度策略有这几个特点1高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;3.保证公平和避免饥渴;4.SMP调度:调度程序必须支持多处理系统;5.软实时调度:系统必须有效的调用实时进程,但不保证一定满足其要求.

原文地址:https://www.cnblogs.com/lxy666666/p/5987354.html