Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1919467
  • 博文数量: 376
  • 博客积分: 2147
  • 博客等级: 大尉
  • 技术积分: 3642
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-06 10:47
文章分类

全部博文(376)

文章存档

2019年(3)

2017年(28)

2016年(15)

2015年(17)

2014年(182)

2013年(16)

2012年(115)

我的朋友

分类: 嵌入式

2014-07-14 22:59:07

修改Linux内核,让Linux启动后不执行init进程,而执行自己编写的程序。

  当内核被引导并进行初始化之后,内核就可以启动自己的第一个用户空间应用程序init进程。

Linux内核初始化过程 
l        内核映像: 
在Bootloader的第二阶段会调用内核镜像,当内核映像被加载到内存中,并且Bootloader阶段 2 的引导加载程序释放控制权之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。 
l        内核引导过程: 
        当 bzImage(用于 i386 映像)被调用时,我们从/arch/i386/boot/head.S 的 start 汇编例程开始执行。这个例程会执行一些基本的硬件设置,并调用 /arch/i386/boot/compressed/head.S 中的 startup_32 例程。此例程会设置一个基本的环境(堆栈等),并清除 Block Started by Symbol(BSS)。然后调用一个叫做 decompress_kernel 的 C 函数(在 /arch/i386/boot/compressed/misc.c 中)来解压内核。当内核被解压到内存中之后,就可以调用它了。这是另外一个 startup_32 函数,但是这个函数在 /arch/i386/kernel/head.S 中。 
       在这个新的 startup_32 函数(也称为清除程序或进程 0)中,会对页表进行初始化,并启用内存分页功能。然后会为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用。然后调用 start_kernel 函数(在 init/main.c 中),它会将您带入与体系结构无关的 Linux 内核部分。实际上,这就是 Linux 内核的 main 函数。 
    通过调用 start_kernel,会调用一系列初始化函数来设置中断,执行进一步的内存配置,并加载初始 RAM 磁盘。最后,要调用 kernel_thread(在 arch/i386/kernel/process.c 中)来启动 init 函数,这是第一个用户空间进程(user-space process)。最后,启动空任务,现在调度器就可以接管控制权了(在调用 cpu_idle 之后)。 
l        深入分析内核源代码 
    根据上面对内核引导过程的分析,我们可以进入Linux内核源代码跟踪其具体过程。这里我们将图示从./arch/i386/kernel/head.S的Startup_32开始的过程。 

Init/main.c中的asmlinkage void __init start_kernel(void)函数最后调用rest_init();

static noinline void __init_refok rest_init(void)

    __releases(kernel_lock)

{

    int pid;

 

    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);

    numa_default_policy();

    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

    unlock_kernel();

 

    /*

     * The boot idle thread must execute schedule()

     * at least once to get things moving:

     */

    init_idle_bootup_task(current);

    rcu_scheduler_starting();

    preempt_enable_no_resched();

    schedule();

    preempt_disable();

 

    /* Call into cpu_idle with preempt disabled */

    cpu_idle();

}

 

我们可以看到kernel_thread函数的定义,我们关心的是rest_init函数中对kernel_thread的调用,从上面我们可以看到这样的语句:      
          kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
        在这里,kernel_init(再调用init)当作函数指针传给kernel_thread,这样init将被执行,也就成为Linux启动后第一个用户进程。可以设想,如果我们将自己的函数指针传给kernel_thread,并作适当的初始化,就可以实现我们实验目的。

 

Kernel_thread函数在/arch/i386/kernel/process.c中:

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

{

    struct pt_regs regs;

 

    memset(®s, 0, sizeof(regs));

 

    regs.ARM_r1 = (unsigned long)arg;

    regs.ARM_r2 = (unsigned long)fn;

    regs.ARM_r3 = (unsigned long)do_exit;

    regs.ARM_pc = (unsigned long)kernel_thread_helper;

    regs.ARM_cpsr = SVC_MODE;

 

    return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);

}

EXPORT_SYMBOL(kernel_thread);

 

2.      修改Linux内核,创建自己的init进程 
      经过上面的分析,我们找到init的原型,它在init/main.c中:static int kernel_init(void *);
       它的定义在init/main.c最后,具体内容如下:

static int __init kernel_init(void * unused)

{

    lock_kernel();

    /*

     * init can run on any cpu.

     */

    set_cpus_allowed_ptr(current, cpu_all_mask);

    /*

     * Tell the world that we're going to be the grim

     * reaper of innocent orphaned children.

     *

     * We don't want people to have to make incorrect

     * assumptions about where in the task array this

     * can be found.

     */

    init_pid_ns.child_reaper = current;

 

    cad_pid = task_pid(current);

 

    smp_prepare_cpus(setup_max_cpus);

 

    do_pre_smp_initcalls();

    start_boot_trace();

 

    smp_init();

    sched_init_smp();

 

    do_basic_setup();

 

    /*

     * check if there is an early userspace init.  If yes, let it do all

     * the work

     */

 

    if (!ramdisk_execute_command)

        ramdisk_execute_command = "/init";

 

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {

        ramdisk_execute_command = NULL;

        prepare_namespace();

    }

 

    /*

     * Ok, we have completed the initial bootup, and

     * we're essentially up and running. Get rid of the

     * initmem segments and start the user-mode stuff..

     */

 

    init_post();

    return 0;

}

 

其中的init_post()函数调用init 。

static noinline int init_post(void)

    __releases(kernel_lock)

{

    /* need to finish all async __init code before freeing the memory */

    async_synchronize_full();

    free_initmem();

    unlock_kernel();

    mark_rodata_ro();

    system_state = SYSTEM_RUNNING;

    numa_default_policy();

 

    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

 

    (void) sys_dup(0);

    (void) sys_dup(0);

 

    current->signal->flags |= SIGNAL_UNKILLABLE;

 

    if (ramdisk_execute_command) {

        run_init_process(ramdisk_execute_command);

        printk(KERN_WARNING "Failed to execute %s\n",

                ramdisk_execute_command);

    }

 

    /*

     * We try each of these until one succeeds.

     *

     * The Bourne shell can be used instead of init if we are

     * trying to recover a really broken machine.

     */

    if (execute_command) {

        run_init_process(execute_command);

        printk(KERN_WARNING "Failed to execute %s.  Attempting "

                    "defaults...\n", execute_command);

    }

    run_init_process("/sbin/init");

    run_init_process("/etc/init");

    run_init_process("/bin/init");

    run_init_process("/bin/sh");

 

    panic("No init found.  Try passing init= option to kernel.");

}

可以看到,init函数首先做一些基本的初始化设置,清除初始化过程用到的内存空间后,就真正开始执行用户进程。首先尝试打开控制台,然后执行内核配置命令行参数中给的程序,完成一些初始化工作。这里我们可以看到一个if语句,这个execute_command是一个char* 类型的指针,在main.c中第426行的static void __init      parse_options(char *line)中,将命令行参数传给了它(第456行,execute_command= line;)像我们在配置内核时,指定这样的字眼:init=/linuxrc,这样linuxrc就是在这开始执行的,当然,它可能还会递归调用其它的需要执行的程序。如果没有设置参数,就尝试执行指定的初始进程,这也是真正的用户进程了。在上面的定义中我们可以看到,系统依次尝试执行/sbin/init, /ect/init/, /bin/init, /bin/sh进程,直到有一个运行成功;如果全都执行失败,系统也就启动失败。 
      下面开始我们的最终任务,那就是修改init,让系统启动时不执行/sbin/init。简单的方法是先建立自己的可执行程序,如/sbin/myinit,将init中对/sbin/init的调用改为对/sbin/myinit的调用即可。 
      这里给出本人对init的修改,如下:

将static int __init kernel_init(void * unused)改成static int __init my_kernel_init(void * unused)

再将init_post()函数中的

run_init_process("/sbin/init"); 改成run_init_process("/sbin/myinit");

 

在/sbin下新建可执行文件myinit,其源代码定义如下:

#!/bin/sh

 

echo "******************************"

echo "Haha,This is my init process !"

echo "Oh Yeah"

 

exec /sbin/init  #装入并执行/sbin/init,这样就跟默认的执行流程一样了

 

 

最后,转到/init/main.c的rest_init(void)函数,修改其对kernel_thread的调用如下, 
 kernel_thread(my_kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 这样,我们就实现了内核启动的第一个用户进程是我们自己定义的myinit函数。

阅读(1857) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~