linux内核学习(14)好无聊的代码

现在我们还在关于机器代码里晃悠呢,昨天分析了arch/x86/kernel/head_32.S里面的代码,汇编的时代已经过去了,现在起,代码都是C的,很爽啊。我们离大本营越来越近了,设备模型就在前方不远处,不过还没继续分析完,估计你还在迷糊,在哪里呢?呵呵,很快的,这一切会在一瞬间就走完,不要太过于留恋太多无聊的东西。现在该是时候跳到昨天说的那里了。

来自arch/x86/kernel/head32.c:
void __init i386_start_kernel(void)
{
#ifdef CONFIG_X86_TRAMPOLINE
    /*
     * But first pinch a few for the stack/trampoline stuff
     * FIXME: Don't need the extra page at 4K, but need to fix
     * trampoline before removing it. (see the GDT stuff)
     */
    reserve_early_overlap_ok(PAGE_SIZE, PAGE_SIZE + PAGE_SIZE,
                     "EX TRAMPOLINE");
#endif

    reserve_early(__pa_symbol(&_text), __pa_symbol(&__bss_stop), "TEXT DATA BSS");

#ifdef CONFIG_BLK_DEV_INITRD
    /* Reserve INITRD */
    if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
        /* Assume only end is not page aligned */
        u64 ramdisk_image = boot_params.hdr.ramdisk_image;
        u64 ramdisk_size  = boot_params.hdr.ramdisk_size;
        u64 ramdisk_end   = PAGE_ALIGN(ramdisk_image + ramdisk_size);
        reserve_early(ramdisk_image, ramdisk_end, "RAMDISK");
    }
#endif

    /* Call the subarch specific early setup function */
    switch (boot_params.hdr.hardware_subarch) {
    case X86_SUBARCH_MRST:
        x86_mrst_early_setup();
        break;
    default:
        i386_default_early_setup();
        break;
    }

    /*
     * At this point everything still needed from the boot loader
     * or BIOS or kernel text should be early reserved or marked not
     * RAM in e820. All other memory is free game.
     */
# 其实上面这部分看不懂没关系,因为对太底层的东西,我们可以暂时不去动它,毕竟里面太复杂,还不是我们这个年纪
# 可以啃得动的,我们还需要积累很多经验才能从容应对,而且我们的大本营就在前方不远了
    start_kernel();
}

start_kernel函数,它预示着我们马上就要飞出机器相关的部分,来到一个非常自由无拘无束的地方--机器无关代码。

来自init/main.c:
asmlinkage void __init start_kernel(void)
{
    char * command_line;
    extern const struct kernel_param __start___param[], __stop___param[];

    smp_setup_processor_id();

    /*
     * Need to run as early as possible, to initialize the
     * lockdep hash:
     */
    lockdep_init();
    debug_objects_early_init();

    /*
     * Set up the the initial canary ASAP:
     */
    boot_init_stack_canary();

    cgroup_init_early();

    local_irq_disable();
    early_boot_irqs_off();
    early_init_irq_lock_class();

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
    tick_init();
    boot_cpu_init();
    page_address_init();
    printk(KERN_NOTICE "%s", linux_banner);
    setup_arch(&command_line);
    mm_init_owner(&init_mm, &init_task);
    setup_command_line(command_line);
    setup_nr_cpu_ids();
    setup_per_cpu_areas();
    smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */

    build_all_zonelists(NULL);
    page_alloc_init();

    printk(KERN_NOTICE "Kernel command line: %s/n", boot_command_line);
    parse_early_param();
    parse_args("Booting kernel", static_command_line, __start___param,
           __stop___param - __start___param,
           &unknown_bootoption);
    /*
     * These use large bootmem allocations and must precede
     * kmem_cache_init()
     */
    pidhash_init();
    vfs_caches_init_early();
    sort_main_extable();
    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();
    /*
     * Disable preemption - early bootup scheduling is extremely
     * fragile until we cpu_idle() for the first time.
     */
    preempt_disable();
    if (!irqs_disabled()) {
        printk(KERN_WARNING "start_kernel(): bug: interrupts were "
                "enabled *very* early, fixing it/n");
        local_irq_disable();
    }
    rcu_init();
    radix_tree_init();
    /* init some links before init_ISA_irqs() */
    early_irq_init();
    init_IRQ();
    prio_tree_init();
    init_timers();
    hrtimers_init();
    softirq_init();
    timekeeping_init();
    time_init();
    profile_init();
    if (!irqs_disabled())
        printk(KERN_CRIT "start_kernel(): bug: interrupts were "
                 "enabled early/n");
    early_boot_irqs_on();
    local_irq_enable();

    /* Interrupts are enabled now so all GFP allocations are safe. */
    gfp_allowed_mask = __GFP_BITS_MASK;

    kmem_cache_init_late();

    /*
     * HACK ALERT! This is early. We're enabling the console before
     * we've done PCI setups etc, and console_init() must be aware of
     * this. But we do want output early, in case something goes wrong.
     */
    console_init();
    if (panic_later)
        panic(panic_later, panic_param);

    lockdep_info();

    /*
     * Need to run this when irqs are enabled, because it wants
     * to self-test [hard/soft]-irqs on/off lock inversion bugs
     * too:
     */
    locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start && !initrd_below_start_ok &&
        page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
        printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
            "disabling it./n",
            page_to_pfn(virt_to_page((void *)initrd_start)),
            min_low_pfn);
        initrd_start = 0;
    }
#endif
    page_cgroup_init();
    enable_debug_pagealloc();
    kmemleak_init();
    debug_objects_mem_init();
    idr_init_cache();
    setup_per_cpu_pageset();
    numa_policy_init();
    if (late_time_init)
        late_time_init();
    sched_clock_init();
    calibrate_delay();
    pidmap_init();
    anon_vma_init();
#ifdef CONFIG_X86
    if (efi_enabled)
        efi_enter_virtual_mode();
#endif
    thread_info_cache_init();
    cred_init();
    fork_init(totalram_pages);
    proc_caches_init();
    buffer_init();
    key_init();
    security_init();
    dbg_late_init();
    vfs_caches_init(totalram_pages);
    signals_init();
    /* rootfs populating might need page-writeback */
    page_writeback_init();
#ifdef CONFIG_PROC_FS
    proc_root_init();
#endif
    cgroup_init();
    cpuset_init();
    taskstats_init_early();
    delayacct_init();

    check_bugs();

    acpi_early_init(); /* before LAPIC and SMP init */
    sfi_init_late();

    ftrace_init();

    /* Do the rest non-__init'ed, we're now alive */
# 看到上面这些没有,基本都是xxx_init(),我乐个操!当我傻逼啊,不会上你的当,去一个一个揭开看,还是刚才那句老话,
# 我们的年纪还不够
    rest_init();
}

跳到rest_init函数:
static noinline void __init_refok rest_init(void)
    __releases(kernel_lock)
{
    int pid;
# rcu这种东西实在恶心,关于多处理器平台同步、互斥的
    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_init。
# 关于进程、线程的概念可以看看书籍,至于进程管理这部分,我们是不会涉及的
# 只需要知道函数用法即可
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    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);
    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}

我们来看看kernel_init:
static int __init kernel_init(void * unused)
{
    /*
     * Wait until kthreadd is all set-up.
     */
    wait_for_completion(&kthreadd_done);
    /*
     * init can allocate pages on any node
     */
    set_mems_allowed(node_states[N_HIGH_MEMORY]);
    /*
     * 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();

    smp_init();
    sched_init_smp();
# 看到这里了,这个函数就将揭开初始化设备模型的面纱
    do_basic_setup();
# 下面的飘过~~
    /* Open the /dev/console on the rootfs, this should never fail */
    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);
    /*
     * 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;
}

跟踪do_basic_setup()进去:
# 哎哟,没几行嘛,找到我们关注的
static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    usermodehelper_init();
    init_tmpfs();
# 我们日思夜想,万里长征不怕难,终于找到了,我们的大本营,也就是我们的重点分析目标
# 现在我该用什么样的方式表达我的激动,真是无法表达,因为这一切来之不易
# 我们避开了多少的复杂代码才来到这里的,别让我们失望
# 这个初始化函数建立了一个非常强大的设备模型系统
# 为PC里的设备提供着源源不断的溪流
# 它是支持这PC里所有的设备
# 这就是设备模型!这也是它的魅力所在
    driver_init();  /*build device module subsystem*/

    init_irq_proc();
    do_ctors();

# 这个初始化函数,可以调用内核里存放在初始化节里的初始化函数。
    do_initcalls();/*init devices*/
}

现看看这个函数do_initcalls():
# 这些外部变量下面讲
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)
{
    initcall_t *fn;

    for (fn = __early_initcall_end; fn < __initcall_end; fn++)
        do_one_initcall(*fn);

    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
}
下面这点内容就是介绍初始化节,这个名字是我自己取的,听起来形象点,如果编译内核,我们就知道有个链接脚本文件vmlinux.lds,这个文件是由存放在arch/x86/kernel/vmlinux.lds.S的汇编语言写的文件编译出来的。在这个文件里面存放的信息,把整个各个目录里编译出来的内核目标文件(一般为.o后缀)按照vmlinux.lds的链接信息进行链接组成一个完整的内核vmlinux,说白点,就是重组。也就是说内核代码中的数据段、bss段、data段等一些自定义的段都会按照链接脚本的信息进行链接。

这里出现的初始化节,应该说是放一些内核初始化时需要调用的函数地址。在vmlinux.lds中(注意vmlinux.lds必须编译后才能得到)
*(.initcallearly.init) __early_initcall_end = .;
*(.initcall0.init)
*(.initcall0s.init)
*(.initcall1.init)
*(.initcall1s.init)
*(.initcall2.init)
*(.initcall2s.init)
*(.initcall3.init)
*(.initcall3s.init)
*(.initcall4.init)
*(.initcall4s.init)
*(.initcall5.init)
*(.initcall5s.init)
*(.initcallrootfs.init)
*(.initcall6.init)
*(.initcall6s.init)
*(.initcall7.init)
*(.initcall7s.init)
__initcall_end = .;

对应这些节的还有一些宏,可以提供给那些需要内核在初始化时调用的模块。

来自include/linux/init.h:
#define __define_initcall(level,fn,id) /
    static initcall_t __initcall_##fn##id __used /
    __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)        __define_initcall("early",fn,early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)        __define_initcall("0",fn,0)

#define core_initcall(fn)        __define_initcall("1",fn,1)
#define core_initcall_sync(fn)        __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)        __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)    __define_initcall("2s",fn,2s)
#define arch_initcall(fn)        __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)        __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)        __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)    __define_initcall("4s",fn,4s)
#define fs_initcall(fn)            __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)        __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)        __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)        __define_initcall("6",fn,6)
#define device_initcall_sync(fn)    __define_initcall("6s",fn,6s)
#define late_initcall(fn)        __define_initcall("7",fn,7)
#define late_initcall_sync(fn)        __define_initcall("7s",fn,7s)

明白了吧,上面这些define就是起到定义的作用,我们展开一个说说,比如:subsys_initcall(usb_init)。
#define subsys_initcall(fn)        __define_initcall("4",fn,4) 

#define __define_initcall(level,fn,id) /
    static initcall_t __initcall_##fn##id __used /
    __attribute__((__section__(".initcall" level ".init"))) = fn

可以得到==>

static initcall_t   __initcallusb4__used    __attribute__((__section__(".initcall4.init")))= usb_init。至于这样的代码为什么会将usb_init这个初始化usb设备模块的函数放在".initcall4.init"这个初始化节里,恐怕得要学习一些gcc编译、链接方面的书籍了,当然,这些我们不去过问的。哦!对咯!这里还有个类型没说initcall_t,这个是函数指针,不说也知道的,在文件最上面有定义。

typedef int (*initcall_t)(void);
typedef void (*exitcall_t)(void);

好了,把知识点部分说完了,现在在来说那个do_initcalls函数也不是什么难事情。
# 对照上面初始化节,就可以发现定义在哪里了
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)
{
    initcall_t *fn;

    for (fn = __early_initcall_end; fn < __initcall_end; fn++)
        do_one_initcall(*fn);   # 继续看它是怎么做的,其实不看我们也能猜到!

    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
}

看do_one_initcall():
int __init_or_module do_one_initcall(initcall_t fn)
{
    int count = preempt_count();
    int ret;

    if (initcall_debug) 
        ret = do_one_initcall_debug(fn);
    else
        ret = fn();  # 肯定得这样调用嘛,不看我们也知道,呵呵!
# 下面的直接忽略
    msgbuf[0] = 0;

    if (ret && ret != -ENODEV && initcall_debug)
        sprintf(msgbuf, "error code %d ", ret);

    if (preempt_count() != count) {
        strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));
        preempt_count() = count;
    }
    if (irqs_disabled()) {
        strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
        local_irq_enable();
    }
    if (msgbuf[0]) {
        printk("initcall %pF returned with %s/n", fn, msgbuf);
    }

    return ret;
}

好了,现在万事具备,只差分析了。从上面的内容中,我们知道我们在干什么,必须得整理一遍思路,我们找到了分析设备模型最重要的函数driver_init(),我们还分析内核初始化时会调用初始化节里的函数,关于初始化节里的函数一般是模块初始化函数,不过我也没具体看过,因此也只能说说我知道。关于模块,是linux内核里的一个非常重要的特点,模块的静态编译和动态加载使得linux非常灵活,特别是对于设备驱动。建议如果你还对于内核模块没有一个概念性的认识,找本书看看,然后顺便知道怎么进行内核驱动编写,也看看,一般书里都会讲的,其实没多少内容,几天就可以搞定。

想到马上我就可以分析日思夜想的设备模型这块内容了,还是比较兴奋,不过里面博大精深的内容,估计得花很多时间,因此,我不晓得后面的文章还会接得紧吗,但是为了把设备模型分析得更加透彻些,我觉得花多写时间是物超所值的。

我们已经走完了内核启动到初始化的全过程了,你学到了多少,我老实说,我写的这些文章必须建立在对很多知识都有一定的了解的基础上的,而且已经分析过代码,只是有很多地方不是很懂,那么看看这些文章还蛮管用的。对于很多想学习内核源代码的你,但是还有许多条件都没跟上,那么千万别着急着去看源代码,这样会事倍功半。下篇文章,如果要写,我会写些学习内核的基本条件和素养,为什么还有素养,呵呵,自己的看法吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值