本文主要是對韋東山老師的《嵌入式Linux應用開發完全手冊》中第17章中的小結,以及一些自己的經驗看法。
概要:本文講述了Linux根目錄中各個目錄的作用和内容、busybox工具的使用、核心在加載init程序後進行的操作、mdev是如何被加入Linux的、glibc庫的移植。
Linux檔案系統概述
Linux中沒有像windows的C、D、E盤之分,隻有一個根檔案系統,其它檔案系統通過挂載到根檔案系統中的某個目錄下,當挂載目錄中的其它檔案将會變成不可通路,是以挂載點最好是空目錄。
根檔案目錄結構
為了在安裝軟體時能夠預知檔案、目錄的存放位置,為了讓使用者友善地找到不同類型檔案的位置,在構造根檔案系統時,建議遵循FHS标準(Filesystem Hierarchy Standard)。它頂一個檔案系統中目錄、檔案分類存放的原則,定義了系統運作時所需的最小檔案、目錄的集合,并舉例了不遵循這些原則的例外情況及其原因。FHS并不是一個強制的标準。FHS文檔可以從網站http://www.pathname.com下載下傳。
-
/bin目錄
該目錄下存放的是所有使用者(系統管理者和普通使用者)都可以使用的指令,這些指令在加載其它檔案系統之前就需要使用,是以該目錄必須和根檔案系統在同一個分區中。
/bin目錄下常用指令:cat、chgrp、chmod、cp、ls、sh、kill、mount、umount、mkdir、mknod、test。
-
/sbin目錄
該目錄中存放的是系統指令,隻有系統管理者可以使用,系統指令還可以存放在/usr/sbin、/usr/local/sbin目錄下。/sbin目錄下的系統指令主要用來啟動系統、修複系統等。該目錄必須要和根檔案系統在同一分區下。
/sbin下常用指令:shutdown、reboot、fdisk、fsck等。不是急迫需要使用的指令存放在/usr/sbin中,本地安裝的系統指令安裝在/usr/local/sbin。
-
/dev目錄
建立裝置檔案的方法有3種:
(1)手動建立
在制作根檔案系統的時候,在/dev目錄下就要建立好要使用的裝置檔案,之後在系統運作時動态添加裝置時,要通過查閱/proc/devices來獲得主裝置号,然後再在/dev目錄下建立裝置節點。
(2)使用devfs檔案系統
這種方法已經過時了,《嵌入式Linux應用開發完全手冊》裡面有較詳細介紹。
(3)使用udev
udev是個使用者程式,它能夠根據系統狀态動态地更新裝置檔案,包括裝置檔案的建立和删除。在busybox中有一個mdev指令,它是udev的簡化版本,适合嵌入式應用。
- /etc目錄
該目錄下存放了各種配置檔案。在PC的Linux系統中該目錄下的檔案非常多,但是這些檔案是可以删選的。在嵌入式應用中,可以根據需求大大減少該目錄下的檔案。
/etc目錄下的子目錄
目錄 | 描述 |
---|---|
opt | 用來配置opt下的程式 |
X11 | 用來配置X windows |
sgml | 用來配置SGML |
xml | 用來配置XML |
/etc 目錄下的檔案
檔案 | 描述 |
---|---|
export | 用來配置NFS檔案系統,隻有當作為NFS伺服器時使用 |
fstab | 用來指明”mount -a”執行時,需要挂載的檔案系統 |
mtab | 用來顯示已經加載的檔案系統,通常是/proc/mounts的連結檔案 |
ftpusers | 啟動FTP伺服器時,用來配置使用者的通路權限 |
group | 使用者的組檔案 |
inittab | init程序的配置檔案 |
ld.so.conf | 其它共享庫的路徑 |
passwd | 密碼檔案 |
- /lib目錄
該目錄下存放共享庫和可加載子產品,共享庫用于運作根檔案系統中的可執行程式。其它不是根檔案系統所必須的庫檔案可以放在其他目錄,比如/usr/lib、/usr/X11R6/lib、/var/lib.
/lib目錄中的内容
目錄/檔案 | 描述 |
---|---|
libc.so.* | 動态連接配接C庫 |
ld* | 連接配接器、加載器 |
modules | 核心可加載模式存放的目錄 |
- /home目錄
使用者目錄,它是可選的,存放使用者相關的配置檔案。
- /root目錄
root使用者的目錄,存放root使用者相關的配置檔案。
- /usr目錄
/usr目錄的内容可以存放在另一個分區中,系統啟動後子再挂接到根檔案目錄下。裡面存放的是共享、隻讀的程式和資料,這表明/usr目錄下的内容可以在多個主機間共享。
/usr目錄中的内容
目錄 | 描述 |
---|---|
bin | 很多使用者指令存放在這個目錄下 |
include | C程式的頭檔案,這在PC上進行開發時才用得到,在嵌入式系統中不需要 |
lib | 庫檔案 |
local | 本地目錄 |
sbin | 非必須的系統指令 |
share | 架構無關的資料 |
X11R6 | XWindow系統 |
game | 遊戲 |
src | 源代碼 |
- /var目錄
與/usr相反,裡面存放的是可變的資料,比如spool目錄(mail、news、列印機等用的),log檔案、臨時檔案。
- /proc目錄
這是一個空目錄,作為proc檔案系統的挂載點。
- /mnt目錄
用于臨時挂載某個檔案系統,通常是空目錄,也可以在裡面建立空的子目錄,比如/mnt/cdrom、/mnt/hdal等,用來臨時挂接CD光牒和硬碟。
- /tmp
用于存放臨時檔案,通常是空檔案。一些需要生成臨時檔案的程式要用到/tmp目錄,是以/tmp目錄必須存在并可以通路。為減少對FLASH的操作,當在/tmp目錄下挂接記憶體檔案系統。
移植Busybox
Busybox概述:
Busybox相比同類軟體的優點:比較小巧,動态連接配接的Busybox隻有幾百KB,靜态連接配接的隻有1MB左右;Busybox按子產品設計,可以很容易地加入、去除某些指令。
Busybox支援uClibc庫和glibc庫,對Linux2.2.x之後的核心支援良好。
Busybox做的工作是,在/bin目錄、/sbin目錄下建立各種指令。
init程序介紹及使用者程式啟動過程
init程序是由核心啟動的第一個(也是唯一一個)使用者程序(程序ID為1),它根據配置檔案決定啟動哪些程式,比如執行某些腳本、啟動shell、運作使用者指定的程式等。init程序是後續所有的程序的發起者,比如init程序啟動/bin/sh程式後,才能在控制台上輸入各種指令。
在Linux系統中有兩種init程式:BSD init和System V init。這兩種程式各有優缺點,現在大多數Linux發行版使用System V init。但在嵌入式領域,通常使用Busybox的init。
1.核心如何啟動init程序
static noinline int init_post(void)
__releases(kernel_lock)
{
...
if (sys_open((const char __user *) "/dev/console", O_RDWR, ) < )
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup();
(void) sys_dup();
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.");
}
接下來分析該段代碼:
if (sys_open((const char __user *)
"/dev/console", O_RDWR, ) < )
負責打開裝置檔案/dev/console,這時候mdev還沒有啟動,是以必須手動建立該裝置檔案,當打開失敗時,為打開/dev/null,是以也必須手動建立該裝置檔案。
(void) sys_dup();
(void) sys_dup();
将檔案描述符0複制給檔案描述符1,2,這樣标準輸入、标準輸出、标準錯誤都都對應同一個裝置。
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",ramdisk_execute_command);
}
ramdisk_excute _command有三種取值:
- 如果指令行參數指定”rdinit = …”,則ramdisk_excute _command就取改值。
- 否則,如果/init存在,ramdisk_excute _command就等于“/init”.
- 否則,ramdisk_excute _command為空。
注意:run_init_process函數是不傳回的,是以一旦成功就沒有
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);
}
execute_command有兩種取值:
- 如果指令行參數指定”init = …”,則execute_command就等于這個參數。
- 否則,execute_command為空,該語句不執行。
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
然後,會檢視這4個目錄下面有沒有init和sh,Busybox建立的init程式在/sbin下,是以真正會執行的是
run_init_process("/sbin/init");
2.Busybox init程序的啟動過程
Busybox的init程序分為兩個階段:初始化階段、執行inittab指令階段。
我們無法控制初始化階段的執行,但是我們能控制inittab指令執行階段。
初始化階段
- 設定信号處理函數
- 初始化控制台
- 解析inittab
當核心沒有傳遞給init程序環境變量CONSOLE、console時,init程序将會沿用之前打開的console,同時回去檢測console能否使用,若不能這打開/dev/null裝置。因為init程序不與使用者互動,是以會将console關閉。之後會解析inittab檔案,inittab檔案的詳細資訊可以在Busybox的/Example/inittab.txt檔案中。
inittab介紹
inittab的每一個條目都确定一個子程序,并确定程序的啟動方式,條目格式如下:
<id>
表示程序選擇控制台,如果省略,使用init程序所使用的控制台。
<runlevels>
對Busybox沒有意義。
<action>
決定了程序在哪一個階段進行執行。
<process>
将要執行的程序。前面有-,表示該程序是互動型的,比如-/bin/sh.
action屬性的詳細分析
action名稱 | 執行條件 | 說明 |
---|---|---|
sysinit | 系統啟動之後最先執行 | 隻執行一次 |
wait | 系統執行完sysinit程序之後 | 隻執行一次 |
once | 系統執行完wait程序之後 | 隻執行一次 |
respawn | 啟動完once程序後(不必等待其執行結束) | init程序檢測發現子程序退出時,重新啟動它 |
askfirst | 啟動完respawn程序後 | 與respwn相似,不過init程序先輸出”Please press Enter to activate this console“後執行 |
shutdown | 當系統關系時 | 即重新開機、關閉系統指令時 |
restart | Busybox配置了CONFIG_FEATURE_USE_INITTAB,并且init程序接收到SIGHUP信号時 | 先重新讀取、解析/etc/inittab檔案,在執行restart程式 |
ctraltdel | 按下CTRL+Alt+DEL組合鍵時 | - |
在系統啟動前期,init程序首先啟動sysinit、wait、once;在系統正常運作期間。init程序執行respawn、askfirst子程序,并且監視它們,發現某個子程序退出時重新啟動它;在系統退出時,執行shutdown、restart、ctraltdel(之一或全部)
如果根檔案系統中沒有/etc/inittab檔案,Busybox init 程式将使用如下的預設inittab條目:
# ::sysinit:/etc/init.d/rcS
# ::askfirst:/bin/sh
# ::ctrlaltdel:/sbin/reboot
# ::shutdown:/sbin/swapoff -a
# ::shutdown:/bin/umount -a -r
# ::restart:/sbin/init
3.編譯安裝Busybox
編譯Busybox和編譯核心的方式相似,也提供了圖形界面的配置方式。
- 修改Makefile
CROSS_COMPILE ?=
ARCH ?= $(SUBARCH)
修改成
CROSS_COMPILE ?= arm-linux-
ARCH ?= arm
- 配置Busybox
Busybox配置選項分類
配置項類型 | 說明 |
---|---|
Busybox Setting–>General Configuration | 一些通用的設定,一般不用理會 |
Busybox Setting–>Build Option | 連接配接方式、編譯方式 |
Busybox Setting–>Debugging Option | 調試選項,使用Busybox時将列印一些調試資訊,一般不選 |
Busybox Setting–>Installation Options | Busybox 的安裝路徑,不需設定,可以在指令行中指定 |
Busybox Setting–>Busybox Library Tuning | Busybox的性能微調,比如設定在控制台可以輸入的最大字元個數,一般采用預設值 |
Archival Utilities | 各種壓縮、解壓縮工具,根據需要選擇相關指令 |
Coreutils | 核心的指令,比如ls、cp |
Console Utilities | 控制台相關的指令,比如清屏指令clear。知識提供一些友善而已,可以不理會 |
Debian Utilities | Debian指令 |
Editors | 編輯指令,一般都選種vi |
Finding Utilities | 查找指令,一般不用 |
Init Utilities | init程式的配置選項,比如是否讀取inittab檔案,使用預設配置即可 |
Login/Password Management Utilities | 登入、使用者賬戶、密碼等方面的指令 |
Linux Ext2 FS Progs | Ext2 檔案系統的一些工具 |
Linux Module Utilities | 加載/解除安裝子產品的指令,一般都選中 |
Linux System Utilities | 一些系統指令,比如顯示核心列印資訊 |
Miscellaneous Utilities | 一些雜項 |
Networking Utilities | 網絡相關的指令,可以選擇一些方面調試的指令,比如ping、tftp |
Process Utilities | 程序相關的指令,比如ps、free、kill、top,為友善調試,可以全都選中 |
Shells | 有多種shell,比如msh、ash等,一般選擇ash |
System Logging Utilities | 系統記錄(log)方面的指令 |
Runit Utilities | 沒有用到 |
ipsvd Utilities | 監聽TCP、DPB端口,發現有新的連接配接時啟動某個程式 |
- 編譯和安裝Busybox
編譯Busybox
安裝Busybox
4.使用glibc庫
在開發闆上運作的動态連接配接的程式,包括我們自己編譯的Busybox,都需要動态連接配接庫,而且動态連接配接庫的加載需要動态加載器。是以我們的lib主要有動态庫檔案、動态庫加載器。
接下來确定需要哪些動态庫檔案和動态加載器,在我們的交叉工具鍊下有很多的庫檔案。
glibc庫可以在http://ftp.gnu.org/gnu/libc/下載下傳
glibc庫的組成
-
加載器
動态程式啟動前,它們被用來加載動态庫:如ld-2.3.4.so、ls-linux.so.2
-
目标檔案(.o)
比如cetl.o、crti.o、crtn.o等。在生成應用程式時,這些像一般的目标檔案一樣被連接配接。
-
靜态庫檔案(.a)
比如靜态數學庫libm.a、靜态C++庫libstdc++.a等,編譯靜态檔案時會連接配接它們。
-
動态庫檔案(.so、.so.[0-9]*)
比如動态數學庫libm.so、動态C++庫libstdc++.so等,它們可能是一個連結檔案。
-
libtool庫檔案(.la)
在連接配接庫檔案時,這些檔案會被用到,比如它們列出了目前庫檔案所依賴的其它庫檔案,程式運作時無需這些檔案。
-
gconv目錄
裡面是有頭字元集的動态庫,比如ISO8859-1.so。
-
ldscripts目錄
裡面是各種連接配接腳本,在編譯應用程式時,它們被用來指定程式的運作位址、各段的位置等。
檢視Busybox需要哪些動态連結庫
方法一:
這種方法能夠得到Busybox需要的動态庫,但是不能知道Busybox需要哪些動态加載器。
方法二:
需要有ldd.host工具
這種方法能夠确定動态加載器。
其實我們可以找出glibc中的動态所有加載器,然後将其全部拷貝。
在交叉工具鍊目錄執行如下指令:
然後将所需的動态庫和動态庫加載器全部都拷貝到我們制作的根檔案系統的/lib下。
建立/etc目錄
在/etc下必須要建立三個檔案/etc/init.d/rcS、/etc/inittab、/etc/fstab.
-
建立/etc/inittab檔案
内容如下
# /etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::crtlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
- 建立/etc/init.d/rcS檔案
rcS是一個腳本檔案,是init程序啟動的第一個程式。
#!/bin/sh
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
上面的代碼完成了兩個内容,一個是挂在了/etc/fstab中列舉的所有檔案系統;另一部分都是為了完成mdev啟動需要的必要代碼。
- 建立/etc/fstab檔案
# device mount-point type optons dump fsck order
proc /proc proc defaults
tmpfs /tmp tmpfs defaults
sysfs /sys sysfs defaults
tmpfs /dev tmpfs defaults
device : 要挂載的裝置
mount-point:挂載點
Type:檔案系統的類型
options:挂載參數
dump和fsck order :用來機額定控制dump、fsck程式的行為
詳細介紹請看:《嵌入式Linux應用開發完全手冊》
建立/dev裝置
我們采用mdev動态建立裝置檔案,是以隻需要建立在mdev運作前用到的兩個裝置檔案。
#mknod console c 5 1
#mknod null c 1 3
主次裝置号是規定好的。
建立其它的目錄
mdev介紹
mdev是udev的簡化版本,它通過讀取核心資訊來建立裝置節點檔案。mdev能夠初始化/dev目錄,而且支援熱插拔事件。要使用mdev必須要支援sysfs檔案系統,同時為了減少對flash的操作,還要支援tmpfs檔案系統。在配置核心的時候要選中兩個檔案系統。
mdev的實作過程在《Linux裝置驅動程式》的Linux裝置模型一節有比較詳細的介紹。mdev的用法參考busybox源碼中的/doc/mdev.txt。
Mdev has two primary uses: initial population and dynamic updates. Bothrequire sysfs support in the kernel and have it mounted at /sys. For dynamic updates, you also need to have hotplugging enabled in your kernel.
mdev需要的代碼如下:
mount -t proc proc /proc
mount -t sysfs sysfs /sys
/*下列三行可選*/
mount -t tmpfs -o size=k,mode= tmpfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
至此,最小的根檔案系統已經做完,可以之間拿來當作NFS網絡檔案系統,如果需要将其制作成yaffa2檔案系統,需要進一步處理。