一、说在前面:在嵌入式中呢,首先uboot的最终目的是启动我们的内核,内核的目的是启动我们的应用程序。怎么启动的呢?
我们来分析下init_post()这个函数;
static int noinline init_post(void)
{
.......
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 (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}//如果uboot中的bootargs中有设置init,if就会被执行。而且不带返回。
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
//如果bootargs中没设置init,内核就会执行以上目录中的init之一
panic("No init found. Try passing init= option to kernel.");
//如果都没有init程序,内核启动失败
}
二、根文件系统的构建
对于嵌入式根文件系统的构建,从零开始构建不太现实,我们利用busybox这开源项目来构建我们根文件系统中的各个目录以及所需的文件。我们自己在其基础进行添加修改,形成最终我们想要的根文件系统。
busybox中init程序分析(linuxrc)
- 读取配置文件
- 解析配置文件
- 执行用户程序
int init_main(int argc, char **argv)
{
......
//设置信号处理函数,即有什么信号过来或发送,就执行相应的函数
signal(SIGHUP, exec_signal);
signal(SIGQUIT, exec_signal);
signal(SIGUSR1, shutdown_signal);
signal(SIGUSR2, shutdown_signal);
signal(SIGINT, ctrlaltdel_signal);
signal(SIGTERM, shutdown_signal);
signal(SIGCONT, cont_handler);
signal(SIGSTOP, stop_handler);
signal(SIGTSTP, stop_handler);
......
console_init();//终端设备初始化
......
if (argc > 1
&& (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
) {
/* Start a shell on console */
new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
/* Not in single user mode -- see what inittab says */
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions(i.e., runs INIT_SCRIPT and then starts a pair
* of "askfirst" shells */
//new_init_action函数:创建一个init_action结构,并填充,然后加入init_action_list链表
parse_inittab();//从/etc/inittab文件中读取,并加入init_action_list链表,
}
......
run_actions(SYSINIT);
//run_actions函数执行相关命令,即从init_action_list中读取init_action结构,不同命令不同执行执行方式。对于RESPAWN | ASKFIRST命令有退出则重新执行。具体命令作用看源码。
.......
}
/etc/inittab文件
id : runlevels : action : process
id:终端设备:/dev/id
runlevels:这一项忽略
action :何时执行process
process:应用程序或脚本
/etc/init.d/rcS文件
这是一个脚本文件,可以在里面添加我们想自动执行的命令,比如配置ip地址,挂载/etc/fstab中的指定的文件系统
#!/bin/sh
ifconfig eth0 192.168.1.110
mount -a #这条命令是指挂载/etc/fstab 中指定的文件系统
注:rcS文件是可执行的,是在inittab文件中指定的,若不能执行则
chmod +x /etc/init.d/rcS
/etc/fstab文件
该文件指定需要挂载的文件系统,当rcS文件被执行到mount -a时,相应的文件系统被挂载。
让我们对fstab的用法进行一个详细的了解。一个典型的entry有下面的fields (fields用空格或tab分开):
- file systems 要挂载的分区或存储设备.
- dir 是 file systems的挂载位置。
- type 要挂载设备或是分区的文件系统类型,支持许多种不同的文件系统:ext2, ext3, ext4, reiserfs, xfs,
jfs, smbfs, iso9660, vfat, ntfs, swap 及 auto。 设置成auto类型,mount
命令会猜测使用的文件系统类型,对 CDROM 和 DVD 等移动设备是非常有用的。 - options 挂载时使用的参数,注意有些mount 参数是特定文件系统才有的。一些比较常用的参数有: auto - 在启动时或键入了
mount -a 命令时自动挂载。 noauto - 只在你的命令下被挂载。 exec - 允许执行此分区的二进制文件。 noexec -
不允许执行此文件系统上的二进制文件。 ro - 以只读模式挂载文件系统。 rw - 以读写模式挂载文件系统。 user -
允许任意用户挂载此文件系统,若无显示定义,隐含启用 noexec, nosuid, nodev 参数。 users - 允许所有
users 组中的用户挂载文件系统. nouser - 只能被 root 挂载。 owner - 允许设备所有者挂载. sync -
I/O 同步进行。 async - I/O 异步进行。 dev - 解析文件系统上的块特殊设备。 nodev -
不解析文件系统上的块特殊设备。 suid - 允许 suid 操作和设定 sgid
位。这一参数通常用于一些特殊任务,使一般用户运行程序时临时提升权限。 nosuid - 禁止 suid 操作和设定 sgid 位。
noatime - 不更新文件系统上 inode 访问记录,可以提升性能(参见 atime 参数)。 nodiratime -
不更新文件系统上的目录 inode 访问记录,可以提升性能(参见 atime 参数)。 relatime - 实时更新 inode
access 记录。只有在记录中的访问时间早于当前访问才会被更新。(与 noatime 相似,但不会打断如 mutt
或其它程序探测文件在上次访问后是否被修改的进程。),可以提升性能(参见 atime 参数)。 flush - vfat
的选项,更频繁的刷新数据,复制对话框或进度条在全部数据都写入后才消失。 defaults - 使用文件系统的默认挂载参数,例如 ext4
的默认参数为:rw, suid, dev, exec, auto, nouser, async. - dump dump 工具通过它决定何时作备份. dump 会检查其内容,并用数字来决定是否对这个文件系统进行备份。 允许的数字是 0 和
1 。0 表示忽略, 1 则进行备份。大部分的用户是没有安装 dump 的 ,对他们而言 dump 应设为 0。 - pass fsck 读取 pass 的数值来决定需要检查的文件系统的检查顺序。允许的数字是0, 1, 和2。
根目录应当获得最高的优先权 1, 其它所有需要被检查的设备设置为 2. 0 表示设备不会被 fsck 所检查。