1.
内核相关代码分析init_post()函数
static int noinline init_post(void)
{
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);
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.");
}
/dev/console 这里的console为在 uboot传进来的参数bootargs中console=/dev/ttySAC0
也是 内核打开的 第一个设备
sys_open((const char __user *) "/dev/console", O_RDWR, 0)
(void) sys_dup(0); dup表示复制的意思 打开设备
(void) sys_dup(0); dup表示复制的意思 打开设备
上边三行用于分别定义标准输入.标准输出.标准错误所指向的设备
execute_command 这个表示从命令行获得的参数代表内核将要执行的第一个程序
即 :run_init_process(execute_command);
如果bootargs里没有指定第一个执行程序的话就执行默认的程序中的一个
即 :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.");
该句通过上边的console所定义的设备输出
2. 关于uboot传递的 ”init= str“ 在内核里处理的相关代码如下
static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);
其中
__setup("init=", init_setup);
就是在 检查bootargs环境变量里边是否有init= str时
如果有则 将str赋值给execute_command
3.busybox构建根文件系统
当我们编译 busybox时会在 bin目录生成一个应用程序 busybos
这个busybos是 ls cp。。等命令的组合 而bin目录下的每一个命令均是到 busybox的链接
即:
# ls -al /bin/cp /bin/ls
lrwxrwxrwx 1 1000 1000 7 Jan 6 2010 /bin/cp -> busybox
lrwxrwxrwx 1 1000 1000 7 Jan 6 2010 /bin/ls -> busybox
bin目录下的ls cp 等命令执行时都相当于执行 busybox程序
而 busybos 会根据命令的不同进行不同的操作
例如执行ls 相当于执行busybox ls
执行cp 相当于执行busybox cp
对于内核执行的默认的第一个程序 /sbin/init 它也是都busybox的一个连接
4.分析busybox源码 分析/sbin/init
busybox源码里 每一个命令均对应一个 .C 文件 如 ls.c cp.c init.c
而每个.C文件里都有一个main函数 当我们在终端执行不同的命令时都会调用不同的main函数
这些函数都被编译生成busybos程序 根文件系统/bin /sbin里的命令均是执行busybox程序
详细分析/sbin/init
内核启动的第一个程序 而我们的最终目的是启动我们自己写的用户程序
所以有一个配置文件(/etc/inittab) 在这个配置文件里指定了 后续执行哪个应用程序
init程序的工作
:读取配置文件 解析配置文件 (根据配置文件)执行用户程序
对应的函数 parse_inittab(); (位于main函数里)
内容如下
:
。。。
file = fopen(INITTAB, "r");打开一个文件 INITTAB
INITTAB 宏定义为 "/etc/inittab" 所以可得该配置文件为 /etc/inittab
inittab的格式 Format for each entry: <id>:<runlevels>:<action>:<process>
例
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
tty2::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
分析 id项 用作终端 printf scanf stdio err..
runlevel 省略
action 何时执行 可取为 sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, and shutdown.
process 对应的想要执行的程序
后边的 if 语句用于说明 没有/etc/inittable时怎么执行
当没有/etc/table时执行下边代码
if (file == NULL) {
/* No inittab file -- set up some default behavior */
#endif
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
/* Prepare to restart init when a HUP is received */
new_init_action(RESTART, "init", "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
/* sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
return;
#if ENABLE_FEATURE_USE_INITTAB
}
上边代码就是将生成默认的init_action结构体 并将其加入init_action_list链表
new_init_action(a->action, command, id); 函数分析
分析该函数 代码如下
创建init_action 结构
struct init_action *new_action, *a, *last;
将init_action 结构放入 init_action_list 链表
如果该链表里有一个init_action 结构体 且 command id均与传入的command与id相同
则 覆盖 否则 将该结构体加入 init_action_list链表
接着这main函数中执行
run_actions(SYSINIT); 执行 init_action_list 链表里 action为SYSINIT的proces
waitfor(a, 0); 等待执行完毕
run(a);创建 process 子进程
wpid = waitpid(runpid, &status, 0); 等待子进程执行结束
delete_init_action(a); 从 init_action_list 链表里删除该 action 的结构体
run_actions(WAIT); 执行 init_action_list 链表里 action为WAIT的proces
同 SYSINIT
run_actions(ONCE); 执行 init_action_list 链表里 action为ONCE的proces
run(a);
delete_init_action(a);
while(1)
{
/* run the respawn stuff */
run_actions(RESPAWN); 执行 init_action_list 链表里 action为 RESPAWN 的proces
/* run the askfirst stuff */
run_actions(ASKFIRST); 执行 init_action_list 链表里 action为ASKFIRST 的proces
wpid = wait(NULL);
while (wpid > 0) {
a->pid = 0;
}
}
run_actions(action);函数分析
总结
最小根文件系统需要的内容
/dev/console /dev/null(当不设置程序的id时 程序的标准输入,标准输出,标准错误,将会定位到/dev/null里)
/etc/inittab(配置文件里指定了 process 我们要执行的用户process就在这里)
库 (init的很多函数都依靠 c库)
/sbin/init程序(指向busybox命令)
5.构建根文件系统
编译busybox
配置编译工具 在顶层的makefile里配置
Building:
=========
The BusyBox build process is similar to the Linux kernel build:
make menuconfig # This creates a file called ".config"
make # This creates the "busybox" executable
make CONFIG_PREFIX= xxx install # or make CONFIG_PREFIX=/path/from/root install
安装到指定的目录下
得到如下结果
book@book-desktop:/work/nfs_root/my_fs$ ls
bin linuxrc sbin usr
得到/sbin/init程序
book@book-desktop:/work/nfs_root/my_fs/sbin$ ls init -l
lrwxrwxrwx 1 book book 14 2013-08-02 23:28 init -> ../bin/busybox
创建设备 console null
mknod console c 5 1 创建字符设备 console 主设备号5 次设备号1 c表示字符设备
mknod null c 1 3
创建 /etc/inittab
mkdir etc
cd etc/
vi inittab
添加console::askfirst:-/bin/sh
安装c库
mkdir lib
cd /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
cp *.so* /work/nfs_root/my_fs/lib -d
*.so*为所以的动态库
.a 为静态库 这里只拷贝静态库 因为在 make menuconfig 配置时配置的动态库
-d 表示连接文件仍拷贝成连接文件 不加-d 时 连接文件将会变成真实内容 照成根文件系统过大
至此最小根文件系统作成
完善最小根文件系统
1. proc 将内核提供的虚拟文件系统挂接在这里 执行ps命令时会到这里提取信息
# mkdir proc
可以手动挂载 也可以 在写入inittab 启动自动挂载
方法 1 新建一个脚本 将其加入inittab文件 如下
vi /etc/init.d/rcS
在rcS脚本里写入 mount -t proc none /proc
chmod +x /etc/init.d/rcS
然后在inittab 添加 ::sysinit:/etc/init.d/rcS
类同可以添加其它命令
方法 2 使用 mount -a 同样在 /etc/init.d/rcS 里添加 mount -a
改命令依赖于/etc/fstab
fstab 格式 ( busybox 的fstab文件里)
#device mount-point type options dump fsck order
例如 挂载上边的proc
proc /proc proc default 0 0
2. 完善dev目录 创建设备文件
udev机制 自动创建dev下的设备 busybox里使用的是mdev 为udev的简化版
使用方法如下 (源码里的文档)
mkdir sys
在 fstab 里添加 sysfs /sys sysfs default 0 0
tmpfs /dev tmpfs default 0 0
然后
在rcS里添加 mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
网络挂接根文件系统
1. 服务器启动nfs服务
配置文件 虚拟机 /etc/exports中添加 要被挂接的目录
如
/work/nfs_root/my_fs
2.重启nfs服务
挂接命令 本机挂接 sudo mount -t nfs 10.20.17.2 :/work/nfs_root/my_fs /mnt
3.开发板手动挂接 (启动开发板后挂接)
mkdir /mnt
mount -t nfs -o nolock 10.20.17.2 :/work/nfs_root/my_fs /mnt
4.改uboot命令行参数从NFS启动
格式如下 参考 内核文档 /Document/nfsroot.TXT
bootargs=noinitrd root=/dev/nfs nfsroot=10.20.17.2:/work/nfs_root/my_fs ip=10.20.17.1:10.20.17.2:255.255.255.254:255.255.255.0::eth3::off init=/linuxrc console=ttySAC0
制作镜像文件
1. 制作yaffs映象 文件(这里指的是yaffs2)
/work/system/ yaffs_source_util_larger_small_page_nand.tar.bz2支持制作yaffs 和yaffs2 的镜像文件
解压该文件
进入 Development_util_ok/yaffs2/utils 执行 make 得到
book@book-desktop:/work/system/Development_util_ok/yaffs2/utils$ ls
Makefile mkyaffsimage.c yaffs_ecc.o yaffs_tagsvalidity.c
mkyaffs2image mkyaffsimage.o yaffs_packedtags1.c yaffs_tagsvalidity.o
mkyaffs2image.c nand_ecc.c yaffs_packedtags1.o
mkyaffs2image.o nand_ecc.o yaffs_packedtags2.c
mkyaffsimage yaffs_ecc.c yaffs_packedtags2.o
book@book-desktop:/work/system/Development_util_ok/yaffs2/utils$
其中 mkyaffs2imag 用于制作yaffs2 镜像
将其cp到 /usr/local/bin/目录中 并chmod +x mkyaffs2imag
回到 /work/nfs_root
执行 mkyaffs2imag my_fs my_fs_yaffs2 即可的得到根文件的镜像文件
烧写到 flash 即可