一 内核启动
uboot -> start_kernel -> rest_init() -> kernel_thread(kernel_init)-->kernel_init_freeable()-->run_init_process ->
1,start_kernel 函数(trunk/build_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/linux-ramips_rt305x/linux-3.10.36/init/main.c)
- <pre name="code" class="cpp">asmlinkage void __init start_kernel(void)
- {
- char * command_line;
- extern const struct kernel_param __start___param[], __stop___param[];
- /*
- * Need to run as early as possible, to initialize the
- * lockdep hash:
- */
- lockdep_init();
- smp_setup_processor_id();
- debug_objects_early_init();
- /*
- * Set up the the initial canary ASAP:
- */
- boot_init_stack_canary();
- cgroup_init_early();
- local_irq_disable();
- early_boot_irqs_disabled = true;
- /*
- * Interrupts are still disabled. Do necessary setups, then
- * enable them
- */
- boot_cpu_init();
- page_address_init();
- pr_notice("%s", linux_banner);//-->[ 0.000000] Linux version 3.10.36 (hui@ubuntu) (gcc version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 r40773) ) #51 Wed Aug 20 07:55:04 PDT 2014
- setup_arch(&command_line);
- mm_init_owner(&init_mm, &init_task);
- mm_init_cpumask(&init_mm);
- 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, NULL);
- page_alloc_init();
- pr_notice("Kernel command line: %s\n", boot_command_line);//-->[ 0.000000] Kernel command line: console=ttyS0,57600 rootfstype=squashfs,jffs2
- parse_early_param();
- parse_args("Booting kernel", static_command_line, __start___param,
- __stop___param - __start___param,
- -1, -1, &unknown_bootoption);
- jump_label_init();
- /*
- * These use large bootmem allocations and must precede
- * kmem_cache_init()
- */
- setup_log_buf(0);
- 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 (WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixing it\n"))
- local_irq_disable();
- idr_init_cache();
- perf_event_init();
- rcu_init();
- tick_nohz_init();
- radix_tree_init();
- /* init some links before init_ISA_irqs() */
- early_irq_init();
- init_IRQ();
- tick_init();
- init_timers();
- hrtimers_init();
- softirq_init();
- timekeeping_init();
- time_init();
- profile_init();
- call_function_init();
- WARN(!irqs_disabled(), "Interrupts were enabled early\n");
- early_boot_irqs_disabled = false;
- local_irq_enable();
- 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) {
- pr_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();
- debug_objects_mem_init();
- kmemleak_init();
- 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_RUNTIME_SERVICES))
- 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();
- if (efi_enabled(EFI_RUNTIME_SERVICES)) {
- efi_late_init();
- efi_free_boot_services();
- }
- ftrace_init();
- /* Do the rest non-__init'ed, we're now alive */
- rest_init(); //------------------------------------------------->
- }
在这里,启动应用程序
- static int __ref kernel_init(void *unused)
- {
- kernel_init_freeable();
- /* need to finish all async __init code before freeing the memory */
- async_synchronize_full();
- free_initmem();
- mark_rodata_ro();
- system_state = SYSTEM_RUNNING;
- numa_default_policy();
- flush_delayed_fput();
- if (ramdisk_execute_command) {
- if (!run_init_process(ramdisk_execute_command))
- return 0;
- pr_err("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) {
- if (!run_init_process(execute_command))
- return 0;
- pr_err("Failed to execute %s. Attempting defaults...\n",
- execute_command);
- }
- if (!run_init_process("/etc/preinit") ||
- !run_init_process("/sbin/init") ||
- !run_init_process("/etc/init") ||
- !run_init_process("/bin/init") ||
- !run_init_process("/bin/sh"))
- return 0;
- panic("No init found. Try passing init= option to kernel. "
- "See Linux Documentation/init.txt for guidance.");
- }
- static noinline void __init kernel_init_freeable(void)
- {
- /*
- * Wait until kthreadd is all set-up.
- */
- wait_for_completion(&kthreadd_done);
- /* Now the scheduler is fully set up and can do blocking allocations */
- gfp_allowed_mask = __GFP_BITS_MASK;
- /*
- * init can allocate pages on any node
- */
- set_mems_allowed(node_states[N_MEMORY]);
- /*
- * init can run on any cpu.
- */
- set_cpus_allowed_ptr(current, cpu_all_mask);
- cad_pid = task_pid(current);
- smp_prepare_cpus(setup_max_cpus);
- do_pre_smp_initcalls();
- lockup_detector_init();
- 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)
- pr_err("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..
- */
- /* rootfs is available now, try loading default modules */
- load_default_modules();
- }
启动第一个应用,init
trunk\build_dir\target-mipsel_24kec+dsp_uClibc-0.9.33.2\procd-2014-03-18\initd\init.c
- int
- main(int argc, char **argv)
- {
- pid_t pid;
- sigaction(SIGTERM, &sa_shutdown, NULL);
- sigaction(SIGUSR1, &sa_shutdown, NULL);
- sigaction(SIGUSR2, &sa_shutdown, NULL);
- early();//-------->early.c
- cmdline();
- watchdog_init(1); //------->../watchdog.c
- pid = fork();
- if (!pid) {
- char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
- if (debug < 3) {
- int fd = open("/dev/null", O_RDWR);
- if (fd > -1) {
- dup2(fd, STDIN_FILENO);
- dup2(fd, STDOUT_FILENO);
- dup2(fd, STDERR_FILENO);
- if (fd > STDERR_FILENO)
- close(fd);
- }
- }
- execvp(kmod[0], kmod);
- ERROR("Failed to start kmodloader\n");
- exit(-1);
- }
- if (pid <= 0)
- ERROR("Failed to start kmodloader instance\n");
- uloop_init();
- preinit(); //-------------->watchdog.c
- uloop_run();
- return 0;
- }
trunk\build_dir\target-mipsel_24kec+dsp_uClibc-0.9.33.2\procd-2014-03-18\state.c
- static void state_enter(void)
- {
- char ubus_cmd[] = "/sbin/ubusd";
- switch (state) {
- case STATE_EARLY:
- LOG("- early -\n");
- watchdog_init(0);
- hotplug("/etc/hotplug.json");
- procd_coldplug();
- break;
- case STATE_INIT:
- // try to reopen incase the wdt was not available before coldplug
- watchdog_init(0);
- LOG("- ubus -\n");
- procd_connect_ubus();
- LOG("- init -\n");
- service_init();
- service_start_early("ubus", ubus_cmd);
- procd_inittab();
- procd_inittab_run("respawn");
- procd_inittab_run("askconsole");
- procd_inittab_run("askfirst");
- procd_inittab_run("sysinit");
- break;
- case STATE_RUNNING:
- LOG("- init complete -\n");
- break;
- case STATE_SHUTDOWN:
- LOG("- shutdown -\n");
- procd_inittab_run("shutdown");
- sync();
- break;
- case STATE_HALT:
- LOG("- reboot -\n");
- reboot(reboot_event);
- break;
- default:
- ERROR("Unhandled state %d\n", state);
- return;
- };
- }
二 shell脚本启动:
openwrt是通过一系列shell脚本进行启动流程的组织,下面是启动流程的提纲。如
果想详细了解启动的过程,则需要仔细走读脚本文件。1. 在make menuconfig 选择target平台 Broadcom BCM947xx/953xx [2.4]
2. linux内核的配置文件由下面两个文件组成
target/linux/generic-2.4/config-default
target/linux/brcm-2.4/config-default
3. 在配置文件中可以看到
CONFIG_CMDLINE="root=/dev/mtdblock2 rootfstype=squashfs,jffs2
init=/etc/preinit noinitrd console=ttyS0,115200"
因此,linux内核启动后,首先运行/etc/preinit脚本
4. preinit脚本位置在
package/base-files/files/etc/preinit
- #!/bin/sh
- # Copyright (C) 2006 OpenWrt.org
- # Copyright (C) 2010 Vertical Communications
- [ -z "$PREINIT" ] && exec /sbin/init
- export PATH=/bin:/sbin:/usr/bin:/usr/sbin
- pi_ifname=
- pi_ip=192.168.1.1
- pi_broadcast=192.168.1.255
- pi_netmask=255.255.255.0
- fs_failsafe_ifname=
- fs_failsafe_ip=192.168.1.1
- fs_failsafe_broadcast=192.168.1.255
- fs_failsafe_netmask=255.255.255.0
- fs_failsafe_wait_timeout=2
- pi_suppress_stderr="y"
- pi_init_suppress_stderr="y"
- pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
- pi_init_cmd="/sbin/init"
- . /lib/functions.sh
- boot_hook_init preinit_essential
- boot_hook_init preinit_main
- boot_hook_init failsafe
- boot_hook_init initramfs
- boot_hook_init preinit_mount_root
- for pi_source_file in /lib/preinit/*; do
- . $pi_source_file
- done
- boot_run_hook preinit_essential
- pi_mount_skip_next=false
- pi_jffs2_mount_success=false
- pi_failsafe_net_message=false
- boot_run_hook preinit_main
5. preinit脚本是一系列脚本的入口,这一系列脚本放在下面的目录:
package/base-files/files/lib/preinit
target/linux/brcm-2.4/base-files/lib/preinit
编译完成后,会统一放在rootfs的/lib/preinit目录下,
03_init_hotplug_failsafe_brcm 40_init_shm
05_failsafe_config_switch_brcm 40_mount_devpts
05_init_interfaces_brcm 40_mount_jffs2
05_mount_skip 40_run_failsafe_hook
05_set_failsafe_switch_brcm 41_merge_overlay_hooks
10_check_for_mtd 50_choose_console
10_essential_fs 50_indicate_regular_preinit
10_indicate_failsafe 60_init_hotplug
10_indicate_preinit 70_initramfs_test
15_mount_proc_brcm 70_pivot_jffs2_root
15_set_preinit_interface_brcm 80_mount_root
20_check_jffs2_ready 90_init_console
20_device_fs_mount 90_mount_no_jffs2
20_failsafe_net_echo 90_restore_config
20_failsafe_set_boot_wait_brcm 99_10_failsafe_login
30_device_fs_daemons 99_10_mount_no_mtd
30_failsafe_wait 99_10_run_init
由于脚本众多,因此openwrt的设计者将这些脚本分成下面几类:
preinit_essential
preinit_main
failsafe
initramfs
preinit_mount_root
每一类函数按照脚本的开头数字的顺序运行。
6. preinit则执行下面的两类脚本
boot_run_hook preinit_essential
boot_run_hook preinit_main
7. preinit执行的最后一个脚本为99_10_run_init,运行
exec env - PATH=$pi_init_path $pi_init_env $pi_init_cmd
pi_init_cmd为
pi_init_cmd="/sbin/init"
因此开始运行busybox的init命令
8. busybox的init命令执行inittab的脚本,该脚本来自
package/base-files/files/etc/inittab
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K stop
tts/0::askfirst:/bin/ash --login
ttyS0::askfirst:/bin/ash --login
tty1::askfirst:/bin/ash --login
sysinit为系统初始化运行的 /etc/init.d/rcS S boot脚本
shutdown为系统重启或关机运行的脚本
tty开头的是,如果用户通过串口或者telnet登录,则运行/bin/ash --login
askfirst和respawn相同,只是在运行前提示"Please press Enter to activate
this console."
9. 当前启动转到运行 /etc/init.d/rcS S boot,该脚本来自
package/base-files/files/etc/init.d/rcS
和preinit类似,rcS也是一系列脚本的入口,其运行/etc/rc.d目录下S开头的的所
有脚本(如果运行rcS K stop,则运行K开头的所有脚本)
K50dropbear S02nvram S40network S50dropbear S96led
K90network S05netconfig S41wmacfixup S50telnet S97watchdog
K98boot S10boot S45firewall S60dnsmasq S98sysntpd
K99umount S39usb S50cron S95done S99sysctl
上面的脚本文件来自:
package/base-files/files/etc/init.d
target/linux/brcm-2.4/base-files/etc/init.d
还有一些脚本来自各个模块,在install时拷贝到rootfs,比如dropbear模块
package/dropbear/files/dropbear.init
这些脚本先拷贝到/etc/init.d下,然后通过/etc/rc.common脚本,将init.d的脚
本链接到/etc/rc.d目录下,并且根据 这些脚本中的START和STOP的关键字,添加
K${STOP}和S${START}的前缀,这样就决定了脚本的先后的运行次序。
10.可以看出,openwrt的启动主要是两个阶段,preinit主要是完成系统的初始化
(如文件系统的准备、模块的加载),rcS主要依次 启动各个模块。
附:脚本走读的一些技巧
a. rootfs目录在build_dir/target-mipsel_uClibc-0.9.30.1/root-brcm-2.4,可以直接在该目录下走读shell脚本。
b. openwrt的shell脚本比较复杂,因此看脚本时可以通过添加 set -x和echo等命令,直接看shell脚本的结果,而不要花太多的时间硬看脚本,主要是理解其主要的意思和设计思路。
-
顶