Linux i386 Boot Code HOWTO
Prev |  | Next

# 7. linux/init/main.c

I felt guilty writing this chapter as there are too many documents about it, if not more than enough. start_kernel() supporting functions are changed from version to version, as they depend on OS component internals, which are being improved all the time. I may not have the time for frequent document updates, so I decided to keep this chapter as simple as possible.

## 7.1. start_kernel()

    ///////////////////////////////////////////////////////////////////////////////
    asmlinkage void __init start_kernel(void)
    {
            char * command_line;
            extern char saved_command_line[];
    /*
     * Interrupts are still disabled. Do necessary setups, then enable them
     */
            lock_kernel();
            printk(linux_banner);

            /* Memory Management in Linux, esp. for setup_arch()
             * Linux-2.4.4 MM Initialization */
            setup_arch(&command_line);
            printk("Kernel command line: %s\n", saved_command_line);

            /* linux/Documentation/kernel-parameters.txt
             * The Linux BootPrompt-HowTo */
            parse_options(command_line);

            trap_init() {
    #ifdef CONFIG_EISA
                    if (isa_readl(0x0FFFD9) == 'E'+('I'<<8)+('S'<<16)+('A'<<24))
                            EISA_bus = 1;
    #endif
    #ifdef CONFIG_X86_LOCAL_APIC
                    init_apic_mappings();
    #endif
                    set_xxxx_gate(x, &func);    // setup gates
                    cpu_init();
            }
            init_IRQ();
            sched_init();
            softirq_init() {
                    for (int i=0; i<32: i++)
                            tasklet_init(bh_task_vec+i, bh_action, i);
                    open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
                    open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
            }
            time_init();

            /*
             * 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();
    #ifdef CONFIG_MODULES
            init_modules();
    #endif
            if (prof_shift) {
                    unsigned int size;
                    /* only text is profiled */
                    prof_len = (unsigned long) &_etext - (unsigned long) &_stext;
                    prof_len >>= prof_shift;
                    size = prof_len * sizeof(unsigned int) + PAGE_SIZE-1;
                    prof_buffer = (unsigned int *) alloc_bootmem(size);
            }

            kmem_cache_init();
            sti();

            // BogoMips mini-Howto
            calibrate_delay();

            // linux/Documentation/initrd.txt
    #ifdef CONFIG_BLK_DEV_INITRD
            if (initrd_start && !initrd_below_start_ok &&
                            initrd_start < min_low_pfn << PAGE_SHIFT) {
                    printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
                        "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
                    initrd_start = 0;
            }
    #endif

            mem_init();
            kmem_cache_sizes_init();
            pgtable_cache_init();

            /*
             * For architectures that have highmem, num_mappedpages represents
             * the amount of memory the kernel can use.  For other architectures
             * it's the same as the total pages.  We need both numbers because
             * some subsystems need to initialize based on how much memory the
             * kernel can use.
             */
            if (num_mappedpages == 0)
                    num_mappedpages =  num_physpages;

            fork_init(num_mempages);
            proc_caches_init();
            vfs_caches_init(num_physpages);
            buffer_init(num_physpages);
            page_cache_init(num_physpages);
    #if defined(CONFIG_ARCH_S390)
            ccwcache_init();
    #endif
            signals_init();
    #ifdef CONFIG_PROC_FS
            proc_root_init();
    #endif
    #if defined(CONFIG_SYSVIPC)
            ipc_init();
    #endif
            check_bugs();
            printk("POSIX conformance testing by UNIFIX\n");

            /*
             *      We count on the initial thread going ok
             *      Like idlers init is an unlocked kernel thread, which will
             *      make syscalls (and thus be locked).
             */
            smp_init() {
    #ifndef CONFIG_SMP
    #     ifdef CONFIG_X86_LOCAL_APIC
                    APIC_init_uniprocessor();
    #     else
                    do { } while (0);
    #     endif
    #else
                    /* Check Section 8.2. */
    #endif
            }

            rest_init() {
                    // init process, pid = 1
                    kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
                    unlock_kernel();
                    current->need_resched = 1;
                    // idle process, pid = 0
                    cpu_idle();     // never return
            }
    }

start_kernel() calls rest_init() to spawn an "init" process and become "idle" process itself.

## 7.2. init()

"Init" process:

    ///////////////////////////////////////////////////////////////////////////////
    static int init(void * unused)
    {
            lock_kernel();
            do_basic_setup();

            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..
             */
            free_initmem();
            unlock_kernel();

            if (open("/dev/console", O_RDWR, 0) < 0)        // stdin
                    printk("Warning: unable to open an initial console.\n");

            (void) dup(0);                                  // stdout
            (void) dup(0);                                  // stderr

            /*
             * 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)
                    execve(execute_command,argv_init,envp_init);
            execve("/sbin/init",argv_init,envp_init);
            execve("/etc/init",argv_init,envp_init);
            execve("/bin/init",argv_init,envp_init);
            execve("/bin/sh",argv_init,envp_init);
            panic("No init found.  Try passing init= option to kernel.");
    }

Refer to "man init" or SysVinit for further information on user-mode "init" process.

## 7.3. cpu_idle()

"Idle" process:

    /*
     * The idle thread. There's no useful work to be
     * done, so just try to conserve power and have a
     * low exit latency (ie sit in a loop waiting for
     * somebody to say that they'd like to reschedule)
     */
    void cpu_idle (void)
    {
            /* endless idle loop with no priority at all */
            init_idle();
            current->nice = 20;
            current->counter = -100;

            while (1) {
                    void (*idle)(void) = pm_idle;
                    if (!idle)
                            idle = default_idle;
                    while (!current->need_resched)
                            idle();
                    schedule();
                    check_pgt_cache();
            }
    }

    ///////////////////////////////////////////////////////////////////////////////
    void __init init_idle(void)
    {
            struct schedule_data * sched_data;
            sched_data = &aligned_data[smp_processor_id()].schedule_data;

            if (current != &init_task && task_on_runqueue(current)) {
                    printk("UGH! (%d:%d) was on the runqueue, removing.\n",
                            smp_processor_id(), current->pid);
                    del_from_runqueue(current);
            }
            sched_data->curr = current;
            sched_data->last_schedule = get_cycles();
            clear_bit(current->processor, &wait_init_idle);
    }

    ///////////////////////////////////////////////////////////////////////////////
    void default_idle(void)
    {
            if (current_cpu_data.hlt_works_ok && !hlt_counter) {
                    __cli();
                    if (!current->need_resched)
                            safe_halt();
                    else
                            __sti();
            }
    }

    /* defined in linux/include/asm-i386/system.h */
    #define __cli()                 __asm__ __volatile__("cli": : :"memory")
    #define __sti()                 __asm__ __volatile__("sti": : :"memory")

    /* used in the idle loop; sti takes one instruction cycle to complete */
    #define safe_halt()             __asm__ __volatile__("sti; hlt": : :"memory")

CPU will resume code execution with the instruction following "hlt" on the return from an interrupt handler.

## 7.4. Reference

- Linux Kernel 2.4 Internals

- Kerneldoc

- LDP HOWTO-INDEX

- Linux Device Drivers, 2nd Edition

Prev | Home | Next
linux/arch/i386/kernel/head.S |  | SMP Boot