Posts Tagged ‘Kernel内核分析’

用 qemu 来调试 Kernel

January 13th, 2016

引言

  • kgdb 方式

kgdb 的方式需要两台电脑,一台是宿主机,另一台是开发机,在开发机上编译打好补丁的内核代码,然后拷贝到宿主机上运行。注:目前 kgdb 支持的版本比较低了,好像在 2.6.19 左右,如果需要调试高版本的内核比较麻烦,而且需要通过串口方式调试,必须需要两台电脑,安装配置也比较麻烦,不过该方式调试比较准确,不会因为优化问题而无法查看变量。

  • uml 方式

uml ( user mode linux kernel ),是一种在用户态调试内核的方式,该调试方式在 2.6 就进入主线了。在源码包中,进入 arch/um 文件夹,就能看到该方式。该方式存在问题是无法调试硬件相关,如果你只需要调试调度、调试文件系统等,那么你可以使用,该方式比较简单,可自行百度。

  • printk 方式

这个方式也就是说在想调试的地方打印调试信息,需要反复的编译,反复增减调试信息是比较繁琐的一个地方。

qemu 调试内核

建议不要从源中拉版本安装,因为可能源中的版本太低,这个问题困扰了我很久,如果版本太低的话,导致文件系统加载的时候会出现故障,会出现以下提示: cannot load filesystem…
首先从 http://wiki.qemu.org/Download 下载最新版的 qemu 源码,我下的是: qemu-2.4.0.tar.bz2 版本,按照下列方式安装就可以了。

$tar -xvf qemu-2.4.0.tar.bz2
$cd qemu-2.4.0
$./configuration
$make install

我就默认安装的,并没有修改安装地址,如果有需要的话请自行定制。

源码配置

$tar -xvf linux-source**
$cd linux-source**
$vim Makefile

编辑 Makefile 文件,将所有 -O2 优化方式修改为 -O0 ,这样可以部分减少查看变量时的 optimized 提示(也就是说变量被编译器优化了,放到寄存器了,无法打印)。

$make menuconfig

接下来修改内核调试选项

kernel将上述选项都选中。 然后就是 make bzImage。这样就会在 arch/i386/boot/ 下生成 bzImage 文件,内核部分就结束了。

文件系统制作

这块我主要是借鉴了网上的一个帖子: http://blog.csdn.net/wesleyluo/article/details/7943087 该帖子详细讲解了如何制作根文件系统,如果你遇到跟我一样的问题,就是制作的根文件系统无法使用的话,也就是提示找不到 filesystem ,那么请你转到 buildroot 工具,制作根文件系统。

gdb 调试

接下来就是调试你的内核啦,不要太激动啊,因为你还是有可能遇到文件系统无法加载啊, qemu 调试报错啊等等问题。
制作一个脚本来快速启动 qemu 调试

#!/bin/bash
qemu-system-i386 -kernel linux_path/arch/i386/bzImage -hda rootfs.ext2 -append "root=/dev/sda rw" -s -S

关于这个 shell 可能有些疑惑, -kernel 就是使用后边的 bzImage 作为内核镜像。 -hda 我的理解就是作为硬盘引导项, -s 是 gdb 调试的快捷方式相当于 -gdb tcp::1234, 打开一个 gdbserver 在 TCP 端口 1234.-S 选项是启动之后就暂停,等待用户命令。

然后新打开一个终端,输入

$gdb linux_path/vmlinux

等待 gdb 把符号加载完成,加载提示:

$Reading symbols from ..../vmlinux ...done

这样就加载完成了,接下来输入:

$target remote localhost:1234
$b start_kernel
$c

就开始运行然后停在了 start_kernel , OK 大功告成了。

参考:

http://blog.csdn.net/wesleyluo/article/details/7943087

内核函数copy_process()分析

January 11th, 2016

内核通过调用函数copy_process()创建进程,copy_process()函数主要用来创建子进程的描述符以及与子进程相关数据结构。这个函数内部实现较为复杂,在短时间内,对于内部详细代码原理和实现并不能全部理解。因此,接下来的分析侧重于copy_process()的执行流程。

1. 定义返回值变量和新的进程描述符。

2.  对clone_flags所传递的标志组合进行合法性检查。当出现以下四情况时,返回出错代号:

  • CLONE_NEWNS和CLONE_FS同时被设置。前者标志表示子进程需要自己的命名空间,而后者标志则代表子进程共享父进程的根目录和当前工作目录,两者不可兼容。
  • CLONE_NEWUSER和CLONE_FS同时被设置。
  • CLONE_THREAD被设置,但CLONE_SIGHAND未被设置。如果子进程和父进程属于同一个线程组(CLONE_THREAD被设置),那么子进程必须共享父进程的信号(CLONE_SIGHAND被设置)。
  • CLONE_SIGHAND被设置,但CLONE_VM未被设置。如果子进程共享父进程的信号,那么必须同时共享父进程的内存描述符和所有的页表(CLONE_VM被设置)。

3. 调用security_task_create()和后面的security_task_alloc()执行所有附加的安全性检查。

4. 调用dup_task_struct()为子进程分配一个内核栈、thread_info结构和task_struct结构。

 p = dup_task_struct(current);
        if (!p)
               goto fork_out;

这个dup_task_struct函数首先定义创建了指向task_struct和thread_inof结构体的指针。然后让子进程描述符中的thread_info字段指向ti变量;最后让子进程thread_info结构中的task字段指向tsk变量。然后返回tsk,这个时候子进程和父进程的描述符中的内容是完全相同的。在后面的代码中,我们将会看到子进程逐渐与父进程区分开。

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
        struct task_struct *tsk;
        struct thread_info *ti;
        int node = tsk_fork_get_node(orig);
        int err;
 
        tsk = alloc_task_struct_node(node);
        if (!tsk)
                return NULL;

        ti = alloc_thread_info_node(tsk, node);
        if (!ti)
                goto free_tsk;

        err = arch_dup_task_struct(tsk, orig);
        if (err)
                goto free_ti;
        tsk->stack = ti;

5. 开始设置子进程的task_struct

根据clone_flags的值继续更新子进程的某些属性。将 nr_threads++,表明新进程已经被加入到进程集合中。将total_forks++,以记录被创建进程数量。

这部分工作还包含初始化双链表、互斥锁和描述进程属性的字段等,其中包括大量的与cgroup相关的属性,。它在copy_process函数中占据了相当长的一段的代码,不过考虑到task_struct结构本身的复杂性,也就不奇怪了。

如果上述过程中某一步出现了错误,则通过goto语句跳到相应的错误代码处;如果成功执行完毕,则返回子进程的描述符p。do_fork()执行完毕后,虽然子进程处于可运行状态,但是它并没有立刻运行。至于子进程合适执行这完全取决于调度程序schedule()。

 

http://lxr.free-electrons.com/source/kernel/fork.c#L1242

Kernel中定时器与时间管理

December 24th, 2013

linux kernel时钟就是系统定时器以某种频率自行触发,当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理。 » Read more: Kernel中定时器与时间管理

内核开发特点&进程管理(读书笔记)

December 11th, 2013

在看内核编程前,几点重要的建议: » Read more: 内核开发特点&进程管理(读书笔记)