linux核心學習(14)好無聊的代碼

來源:互聯網
上載者:User

現在我們還在關於機器代碼裡晃悠呢,昨天分析了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非常靈活,特別是對於裝置驅動。建議如果你還對於核心模組沒有一個概念性的認識,找本書看看,然後順便知道怎麼進行核心驅動編寫,也看看,一般書裡都會講的,其實沒多少內容,幾天就可以搞定。

想到馬上我就可以分析日思夜想的裝置模型這塊內容了,還是比較興奮,不過裡面博大精深的內容,估計得花很多時間,因此,我不曉得後面的文章還會接得緊嗎,但是為了把裝置模型分析得更加透徹些,我覺得花多寫時間是物超所值的。

我們已經走完了核心啟動到初始化的全過程了,你學到了多少,我老實說,我寫的這些文章必須建立在對很多知識都有一定的瞭解的基礎上的,而且已經分析過代碼,只是有很多地方不是很懂,那麼看看這些文章還蠻管用的。對於很多想學習核心原始碼的你,但是還有許多條件都沒跟上,那麼千萬別著急著去看原始碼,這樣會事倍功半。下篇文章,如果要寫,我會寫些學習核心的基本條件和素養,為什麼還有素養,呵呵,自己的看法吧!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.