分享

嵌入式 Linux根文件系统移植(三)——根文件系统构建

 wusiqi111 2018-12-24

一、busybox简介

        BusyBox 是一个集成了一百多个最常用linux命令和工具的开源软件,是嵌入式系统开发中创建根文件系统的工具BusyBox 包含了一些简单的工具,例如lscatecho等等,还包含了一些更大、更复杂的工具,例grepfindmount以及telnet

Busybox采用busybox 1.24.2版本

1、busybox程序目录分析

目录

说明

applets

主要是实现applets框架的文件

applets_sh

一些有用的脚本,例如:dos2unixunix2dos

archival

与压缩有关的命令源文件,例如:bzip2gzip

configs

自带的一些默认配置文件

console-tools

与控制台相关的一些命令,例如:setconsole

coreutils

常用的核心命令,例如:catrm

editors

常用的编辑命令,例如:vidiff

findutils

用于查找的命令,例如:findgrep

init

init进程的实现源文件

networking

与网络相关的命令,例如:telnetlarp

shell

shell相关的实现,例如:ashmsh

util-linux

Linux下常用的命令,主要是与文件系统相关的,例如:mkfs_ext2

libbb

绝大多数非启动且在各个Busybox命令(applets)中共享的代码

2busybox程序框架

        BusyBox程序架构BusyBox的运行提供了基本支持,程序架构主要代码在applets/applets.c文件 在定义ENABLE_BUILD_LIBBUSYBOX宏为真的情况下,busybox程序的入口main函数在/applets/applets.c;如果定义宏ENABLE_BUILD_LIBBUSYBOX为假时,busybox的入口main函数在/libbb/appletlib.c文件中。默认,busybox入口函数main位于/libbb/appletlib.c文件。

#if ENABLE_BUILD_LIBBUSYBOX
int lbb_main(char **argv)
#else
int main(int argc UNUSED_PARAM, char **argv)
#endif
{
#if !BB_MMU
/* NOMMU re-exec trick sets high-order bit in first byte of name */
if (argv[0][0] & 0x80) {
re_execed = 1;
argv[0][0] &= 0x7f;
}
#endif
#if defined(SINGLE_APPLET_MAIN)
/* Only one applet is selected in .config */
if (argv[1] && is_prefixed_with(argv[0], "busybox")) {
/* "busybox <applet> <params>" should still work as expected */
argv++;
}
/* applet_names in this case is just "applet\0\0" */
lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
return SINGLE_APPLET_MAIN(argc, argv);
#else
lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv));
applet_name = argv[0];
if (applet_name[0] == '-')
applet_name++;
applet_name = bb_basename(applet_name);
parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
run_applet_and_exit(applet_name, argv);
/*bb_error_msg_and_die("applet not found"); - sucks in printf */
full_write2_str(applet_name);
full_write2_str(": applet not found\n");
/* POSIX: "If a command is not found, the exit status shall be 127" */
exit(127);
#endif
}

    run_applet_and_exit函数将根据applet的名字,找到相应的applet,将执行相应命令的xx_main函数,然后直接退出。在run_applet_and_exit中,所调用的find_applet_by_name中用bsearchapplets进行搜索,并返回applet。Busybox支持的命令的名称和命令的函数定义在include/applet_tables.h文件中。

二、init解析

1init简介

        init进程是由内核启动的第一个(也是唯一一个)用户进程(进程ID1)init进程根据配置文件决定启动哪些程序,例如:执行某些脚本、启动shell或运行用户程序等。init是后续所有进程的发起者,例如:init进程启动/bin/sh程序后,我们才能够在控制台上输入各种命令。

    init进程的执行程序通常都是/sbin/init如果我们想让init执行自己想要的功能,那么有两种途径:第一,使用自己的init程序,这包括使用自己的init替换/sbin/下的init程序或者 修改传递给内核的参数,指定”init=xxx”这个参数,让init环境变量指向自己的init程序;第二,就是修改init的配置文件,因为init 程序的很大一部分的功能都是按照其配置文件执行的。

        一般而言,在Linux系统中有两种init程序:BSD initSystem V initBSD System V是两种版本的UNIX系统,目前大多数Linux发行版本使用的都是System V init。在嵌入式系统中常使用的是Busybox集成的init程序

2、init的启动过程

    kernel启动的最后就是调用kernel_init函数启动init进程最终在init_post函数中调用run_init_process函数运行指定的init程序,代码在kernel/init/main.c文件中

static int __init kernel_init(void * unused)
{
wait_for_completion(&kthreadd_done);
lock_kernel();
set_mems_allowed(node_states[N_HIGH_MEMORY]);
set_cpus_allowed_ptr(current, cpu_all_mask);
init_pid_ns.child_reaper = current;
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
start_boot_trace();
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);
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();
}
init_post();
return 0;
}
static noinline int init_post(void)
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
 
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. "
      "See Linux Documentation/init.txt for guidance.");
}

        run_init_process函数一旦执行创建进程成功,将不会返回,而是通过操作内核栈进入用户空间。所以init_post函数尾部并没有运行了四个init进程,而是根据优先级,一旦某一个init程序运行成功,就不往下继续执行了

    /sbin/init程序在busybox中由init/init.c文件中init_main函数实现。

int init_main(int argc UNUSED_PARAM, char **argv)
{
if (argv[1] && strcmp(argv[1], "-q") == 0) {
return kill(1, SIGHUP);
}
if (!DEBUG_INIT) {
/* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
if (getpid() != 1
 && (!ENABLE_FEATURE_INITRD || applet_name[0] != 'l') /* not linuxrc? */
) {
bb_error_msg_and_die("must be run as PID 1");
}
#ifdef RB_DISABLE_CAD
/* Turn off rebooting via CTL-ALT-DEL - we get a
 * SIGINT on CAD so we can shut things down gracefully... */
reboot(RB_DISABLE_CAD); /* misnomer */
#endif
}
die_func = sleep_much;
/* Figure out where the default console should be */
console_init();
set_sane_term();
xchdir("/");
setsid();
/* Make sure environs is set to something sane */
putenv((char *) "HOME=/");
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */
if (argv[1])
xsetenv("RUNLEVEL", argv[1]);
#if !ENABLE_FEATURE_EXTRA_QUIET
/* Hello world */
message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);
#endif
/* Check if we are supposed to be in single user mode */
if (argv[1]
 && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
) {
new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
parse_inittab();
}
#if ENABLE_SELINUX
if (getenv("SELINUX_INIT") == NULL) {
int enforce = 0;
putenv((char*)"SELINUX_INIT=YES");
if (selinux_init_load_policy(&enforce) == 0) {
BB_EXECVP(argv[0], argv);
} else if (enforce > 0) {
/* SELinux in enforcing mode but load_policy failed */
message(L_CONSOLE, "can't load SELinux Policy. "
"Machine is in enforcing mode. Halting now.");
return EXIT_FAILURE;
}
}
#endif
/* Make the command line just say "init"  - thats all, nothing else */
strncpy(argv[0], "init", strlen(argv[0]));
/* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
while (*++argv)
nuke_str(*argv);
/* Set up signal handlers */
if (!DEBUG_INIT) {
struct sigaction sa;
/* Stop handler must allow only SIGCONT inside itself */
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGCONT);
sa.sa_handler = stop_handler;
sigaction_set(SIGTSTP, &sa); /* pause */
sigaction_set(SIGSTOP, &sa); /* pause */
bb_signals_recursive_norestart(0
+ (1 << SIGINT)  /* Ctrl-Alt-Del */
+ (1 << SIGQUIT) /* re-exec another init */
#ifdef SIGPWR
+ (1 << SIGPWR)  /* halt */
#endif
+ (1 << SIGUSR1) /* halt */
+ (1 << SIGTERM) /* reboot */
+ (1 << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
+ (1 << SIGHUP)  /* reread /etc/inittab */
#endif
, record_signo);
}
run_actions(SYSINIT);
check_delayed_sigs();
/* Next run anything that wants to block */
run_actions(WAIT);
check_delayed_sigs();
run_actions(ONCE);
while (1) {
int maybe_WNOHANG;
maybe_WNOHANG = check_delayed_sigs();
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
maybe_WNOHANG |= check_delayed_sigs();
/* Don't consume all CPU time - sleep a bit */
sleep(1);
maybe_WNOHANG |= check_delayed_sigs();
if (maybe_WNOHANG)
maybe_WNOHANG = WNOHANG;
while (1) {
pid_t wpid;
struct init_action *a;
wpid = waitpid(-1, NULL, maybe_WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %d) exited. "
"Scheduling for restart.",
a->command, wpid);
}
maybe_WNOHANG = WNOHANG;
}
} /* while (1) */
}

    init程序的执行流程如下:

A、使用console_init函数初始化控制台,打开文件/dev/console作为保准输入,然后将文件描述符复制给文件描述符012

B、使用parse_inittab函数解析inittab配置文件

C、设置信号处理过程

D、执行sysinitwaitonce,然后在while(1)死循环中去执行respwanaskfirst


3、init配置文件


    init的配置文件inittab的语法格式如下:

    :id:rstate:action:process

  A、id 字段是最多 4 个字符的字符串,用来唯一标志表项。
   B、rstate(run state)字段定义该记录项被调用时的运行级别,rstate 可以由一个或多个运行级别构成,也可以是空,空则代表运行级别 0-6。当请求init改变运行级别时,那些 rstate 字段中不包括新运行级别的进程将收到 SIGTERM 警告信号,并且最后被杀死

C、action 字段告诉init执行的动作,即如何处理process字段指定的进程,Busybox定义了八种action:

action

含义

sysinit

init提供初始化命令脚本的路径指定的进程在访问控制台之前执行

respawn

如果 process字段指定的进程不存在,则启动该进程,init 不等待处理结束,而是继续扫描inittab文件中的后续进程,当这样的进程终止时,init 会重新启动它,如果这样的进程已存在,则什么也不做。

askfirst

类似respawn,主要用途是减少系统上执行的终端应用程序的数量会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动进程之前等待用户按下“enter”

wait

告诉init必须等到相应的进程执行完成之后才能继续执行启动 process 字段指定的进程,并等到处理结束才去处理inittab中的下一记录项。

once

仅执行相应的进程一次,而且不会等待它执行完成启动process字段指定的进程,不等待处理结束就去处理下一记录项。

ctratldel

当按下Ctrl+Alt+Delete组合键时,执行相应的进程

shutdown

当系统关机时,执行相应的进程

restart

init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身

   D、<process>:要执行的程序,可以为可执行程序也可以是脚本,如果<process>字段前面有”-”字符,代表这个程序是可交互的,例如:/bin/sh程序

4inittab实例

#first:run the system script file

::sysinit:/etc/init.d/rcS

::askfirst:-/bin/sh

::ctrlaltdel:-/sbin/reboot

#umount all filesystem

::shutdown:/bin/umount -a -r

#restart init process

::restart:/sbin/init

三、busybox制作根文件系统

1、修改Makefile

Arch = arm

CROSS_COMPILE = arm-linux-

2Busybox工程配置

make menuconfigbusybx进行配置

Busybox Settings--->

    Build Options--->

        [*]Build BusyBox as a static binary(no shared libs)

 

Busybox Settings--->

Busybox Library Tuning--->

            [*]vi-style line editing commands

            [*]Fancy shell prompts


Linux Module Utilities--->

    [ ]Simplified modutils

    [*]insmod

    [*]rmmod

    [*]lsmod

    [*]modprobe

    [*]depmod

 

Linux System Utilities--->[*]mdev

    [*]Support /etc/mdev.conf

    [*]Support subdirs/symlinks

    [*]Support regular expressions substitutions when renaming dev

    [*]Support command execution at device addition/removal

    [*]Support loading of firmwares

3、编译工程

make -j4

编译过程报错如下:

coreutils/lib.a(sync.o): In function `sync_main':

sync.c:(.text.sync_main+0x7c): undefined reference to `syncfs'

collect2: ld returned 1 exit status

make: *** [busybox_unstripped] Error 1

错误分析:

Coreutils模块下的sync.c文件中syncfs未定义,取消sync支持

修改如下:

   Coreutils  --->

     [ ] sync    

继续编译,成功

4inittab文件制作

#first:run the system script file

::sysinit:/etc/init.d/rcS

::askfirst:-/bin/sh

::ctrlaltdel:-/sbin/reboot

#umount all filesystem

::shutdown:/bin/umount -a -r

#restart init process

::restart:/sbin/init

5、rcS文件制作

#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin

//S表示系统为单用户模式

runlevel=S

prevlevel=N

//linux系统的umask

umask 022

//导出环境变量

export PATH runlevel prevlevel

//挂载所有的文件系统,/etc/fstab文件中定义的文件系统

mount -a

 

echo /sbin/mdev > /proc/sys/kernel/hotplug

//mdev配合linux驱动生成相应的/dev目录下的设备文件。

mdev -s

//HOSTNAME文件中设置主机名称

/bin/hostname -F /etc/sysconfig/HOSTNAME

//系统启动后设置IP

ifconfig eth0 192.168.6.210

6、fstab文件

# <file system> <mount point> <type> <options> <dump> <pass>

proc /proc proc defaults 0 0

sysfs /sys sysfs defaults 0 0

tmpfs /tmp tmpfs defaults 0 0

tmpfs /dev tmpfs defaults 0 0

7、profile文件

# No core files by default

ulimit -S -c 0 > /dev/null 2>&1

 

USER="`id -un`"

LOGNAME=$USER

PS1='[\u@\h \W]\# '

PATH=$PATH

 

HOSTNAME=`/bin/hostname`

 

export USER LOGNAME PS1 PATH

profile文件需要放到/etc目录下,busybox会自动调用

8、登录界面制作

inittab中用/bin/login或者/sbin/getty去替代/bin/sh

::sysinit:-/bin/getty

s3c2410_serial0:23456:respawn:/sbin/getty -L s3c2410_serial0 115200 vt100

添加/bin/getty后还需要添加密码验证文件

etc目录下添加passwdshadow文件,shadow文件中加密的字段可以为空

Passwd

root:x:0:0:root:/root:/bin/sh

Shadow

root::16732:0:99999:7:::

9、动态链接库的支持

非静态程序的运行需要动态链接库的支持,ARM交叉编译工具编译生成的应用程序要在嵌入式系统上运行必须有动态链接库的支持,因此需要将ARM的动态链接库文件拷贝到嵌入式系统上。ARM交叉编译工具编译过程中指定静态编译链接的程序则可以直接运行在嵌入式系统上。

如果嵌入式系统需要考虑空间节省可以使用arm-linux-strip去除动态链接库文件的符号表信息。

ARM工具链的lib目录中的动态链接库文件拷贝到根文件系统lib目录中


10、文件系统镜像制作

    制作不同文件系统镜像需要不同文件系统的镜像制作工具,文件系统镜像制作工具如下:

mkyaffs2image:制作yaffs2文件系统镜像,mkyaffs2image.rar

mkcramfs:制作cramfs文件系统镜像,cramfs-1.1.tar.gz

mkfs.jffs2 :制作jffs2文件系统镜像,mtd-utils

mkfs.ubifs:制作ubifs文件系统镜像,mtd-utils

将下载的工具包编译后得到的文件系统镜像制作工具拷贝到/usr/sbin目录。

制作各种文件系统镜像的命令如下:

yaffs2文件系统镜像:

mkyaffs2imagerootfsrootfs.yaffs2

jffs2文件系统镜像:

mkfs.jffs2  -r  rootfs  -o  rootfs.jffs2  -p  -l  -n  -e  0x4000

cramfs文件系统镜像:

mkcramfs rootfs rootfs.cramfs

ubifs文件系统镜像:

mkfs.ubifs -r rootfs -m 2048 -e 129024 -c 812 -o rootfs.ubifsext4

r:制定文件内容的位置 
-m:页面大小 
-e:逻辑擦除块大小 
-p:物理擦除块大小 
-c:最大的逻辑擦除块数量
以上的命令最多可以访问129024*812=100M空间

Mtd-utils编译报错处理:

ubifs-utils/mkfs.ubifs/compr.c:28:23: fatal error: lzo/lzo1x.h: No such file or directory

解决:yum install lzo-devel.x86_64

warning: uuid/uuid.h: No such file or directory

解决:yum install libuuid-devel

mtd-utils下载:

git clone git://git.infradead.org/mtd-utils.git 

其余文件系统镜像制作工具见附件

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多