宋寶華談 ARM 的嵌入式 Linux 移植體驗之三:作業系統
在筆者撰寫的《C 語言嵌入式系統編程修鍊之道》一文中,主要陳訴的軟體架構是單任務無作業系統平台的,而本文的側重點則在於講述作業系統嵌入的軟體架構,二者的區別如
嵌入式作業系統並不總是必須的,因為程式完全可以在裸板上運行。儘管如此,但對於複雜的系統,為使其具有任務管理、定時器管理、儲存空間管理、資源管理、事件管理、系統管理、訊息管理、隊列管理和中斷處理的能力,提供多任務處理,更好的分配系統資源的功能,很有必要針對特定的硬體平台和實際應用移植作業系統。鑒於 Linux 的原始碼開放性,它成為嵌入式作業系統領域的很好選擇。國內外許多知名大學、公司、研究機構都加入了嵌入式Linux的研究行列,推出了一些著名的版本:
·RT-Linux 提供了一個精巧的即時核心,把標準的 Linux 核心作為即時核心的一個進程同使用者的即時進程一起調度。RT-Linux 已成功地應用於太空梭的空間資料採集、科學儀器測控和電影特技影像處理等廣泛的應用領域。如 NASA(美國國家宇航局)將裝有 RT-Linux 的裝置放在飛機上,以測量 Georage 咫風的風速;
·uCLinux(Micro-Control-Linux,u 表示 Micro,C 表示 Control)去掉了MMU(記憶體管理)功能,應用於沒有虛擬記憶體管理的微處理器/微控制器,它已經被成功地移植到了很多平台上。
本章涉及的 mizi-linux 由韓國 mizi 公司根據 Linux 2.4 核心移植而來,支援 S3C2410A 處理器。
1.Linux 核心要點
和其他動作系統一樣,Linux 包含進程調度與處理序間通訊(IPC)、記憶體管理(MMU)、虛擬檔案系統(VFS)、網路介面等,給出了 Linux 的組成及其關係:
Linux 核心原始碼包括多個目錄:
(1)arch:包括硬體特定的核心代碼,如 arm、mips、i386 等;
(2)drivers:包含硬體驅動代碼,如 char、cdrom、scsi、mtd 等;
(3)include:通用標頭檔及針對不同平台特定的標頭檔,如 asm-i386、asm-arm 等;
(4)init:核心初始化代碼;
(5)ipc:處理序間通訊代碼;
(6)kernel:核心核心代碼;
(7)mm:記憶體管理代碼;
(8)net:與網路通訊協定棧相關的代碼,如 ipv4、ipv6、ethernet 等;
(9)fs:檔案系統相關代碼,如 nfs、vfat 等;
(10)lib:庫檔案,與平台無關的 strlen、strcpy 等,如在 string.c 中包含:
char * strcpy(char * dest,const char *src)<br />{<br />char *tmp = dest;<br />while ((*dest++ = *src++) != '/0')<br />/* nothing */;<br />return tmp;<br />}
(11)Documentation:文檔
在 Linux 核心的實現中,有一些資料結構使用非常頻繁,對研讀核心的人來說至為關鍵,它們是:
1.task_struct
Linux 核心利用 task_struct 資料結構代表一個進程,用 task_struct 指標形成一個 task 數組。當建立新進程的時候,Linux 為新的進程分配一個 task_struct 結構,然後將指標儲存在 task 數組中。發送器維護 current 指標,它指向當前正在啟動並執行進程。
2.mm_struct
每個進程的虛擬記憶體由 mm_struct 結構代表。該結構中包含了一組指向 vm-area_struct 結構的指標,vm-area_struct 結構描述了虛擬記憶體的一個地區。
3.inode
Linux 虛擬檔案系統中的檔案、目錄等均由對應的索引節點(inode)代表。
2.Linux 移植項目
mizi-linux 已經根據 Linux 2.4 核心針對 S3C2410A 這一晶片進行了有針對性的移植工作,包括:
(1)修改根目錄下的 Makefile 檔案
a.指定目標平台為 ARM:
#ARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/)<br />ARCH := arm
b.指定交叉編譯器:
CROSS_COMPILE = arm-linux-
(2)修改 arch 目錄中的檔案
根據本章第一節可知,Linux 的 arch 目錄存放硬體相關的核心代碼,因此,在 Linux 核心中增加對 S3C2410 的支援,最主要就是要修改 arch 目錄中的檔案。
a.在 arch/arm/Makefile 檔案中加入:
ifeq ($(CONFIG_ARCH_S3C2410),y)<br />TEXTADDR = 0xC0008000<br />MACHINE = s3c2410<br />Endif
b.在 arch/arm/config.in 檔案中加入:
if [ "$CONFIG_ARCH_S3C2410" = "y" ]; then<br />comment 'S3C2410 Implementation'<br />dep_bool ' SMDK (MERI TECH BOARD)' CONFIG_S3C2410_SMDK $CONFIG_ARCH_S3C2410<br />dep_bool ' change AIJI' CONFIG_SMDK_AIJI<br />dep_tristate 'S3C2410 USB function support' CONFIG_S3C2410_USB $CONFIG_ARCH_S3C2100<br />dep_tristate ' Support for S3C2410 USB character device emulation' CONFIG_S3C2410_USB_CHAR $CONFIG_S3C2410_USB<br />fi # /* CONFIG_ARCH_S3C2410 */
arch/arm/config.in 檔案還有幾處針對 S3C2410 的修改。
c.在 arch/arm/boot/Makefile 檔案中加入:
ifeq ($(CONFIG_ARCH_S3C2410),y)<br />ZTEXTADDR = 0x30008000<br />ZRELADDR = 0x30008000<br />endif
d.在 linux/arch/arm/boot/compressed/Makefile 檔案中加入:
ifeq ($(CONFIG_ARCH_S3C2410),y)<br />OBJS += head-s3c2410.o<br />endif
加入的結果是 head-s3c2410.S 檔案被編譯為 head-s3c2410.o。
e.加入 arch/arm/boot/compressed/ head-s3c2410.S 檔案
#include <linux/config.h><br />#include <linux/linkage.h><br />#include <asm/mach-types.h></p><p>.section ".start", #alloc, #execinstr</p><p>__S3C2410_start:</p><p>@ Preserve r8/r7 i.e. kernel entry values<br />@ What is it?<br />@ Nandy</p><p>@ Data cache, Intstruction cache, MMU might be active.<br />@ Be sure to flush kernel binary out of the cache,<br />@ whatever state it is, before it is turned off.<br />@ This is done by fetching through currently executed<br />@ memory to be sure we hit the same cache</p><p>bic r2, pc, #0x1f<br />add r3, r2, #0x4000 @ 16 kb is quite enough...<br />1: ldr r0, [r2], #32<br />teq r2, r3<br />bne 1b<br />mcr p15, 0, r0, c7, c10, 4 @ drain WB<br />mcr p15, 0, r0, c7, c7, 0 @ flush I & D caches</p><p>#if 0<br />@ disabling MMU and caches<br />mrc p15, 0, r0, c1, c0, 0 @ read control register<br />bic r0, r0, #0x05 @ disable D cache and MMU<br />bic r0, r0, #1000 @ disable I cache<br />mcr p15, 0, r0, c1, c0, 0<br />#endif</p><p>/*<br />* Pause for a short time so that we give enough time<br />* for the host to start a terminal up.<br />*/<br />mov r0, #0x00200000<br />1: subs r0, r0, #1<br />bne 1b
該檔案中的彙編程式碼完成 S3C2410 特定硬體相關的初始化。
f.在 arch/arm/def-configs 目錄中增加設定檔
g.在 arch/arm/kernel/Makefile 中增加對 S3C2410 的支援
no-irq-arch := $(CONFIG_ARCH_INTEGRATOR) $(CONFIG_ARCH_CLPS711X) /<br />$(CONFIG_FOOTBRIDGE) $(CONFIG_ARCH_EBSA110) /<br />$(CONFIG_ARCH_SA1100) $(CONFIG_ARCH_CAMELOT) /<br />$(CONFIG_ARCH_S3C2400) $(CONFIG_ARCH_S3C2410) /<br />$(CONFIG_ARCH_MX1ADS) $(CONFIG_ARCH_PXA)<br />obj-$(CONFIG_MIZI) += event.o<br />obj-$(CONFIG_APM) += apm2.o
h.修改 arch/arm/kernel/debug-armv.S 檔案,在適當的位置增加如下關於 S3C2410 的代碼:
#elif defined(CONFIG_ARCH_S3C2410)</p><p>.macro addruart,rx<br />mrc p15, 0, /rx, c1, c0<br />tst /rx, #1 @ MMU enabled ?<br />moveq /rx, #0x50000000 @ physical base address<br />movne /rx, #0xf0000000 @ virtual address<br />.endm</p><p>.macro senduart,rd,rx<br />str /rd, [/rx, #0x20] @ UTXH<br />.endm</p><p>.macro waituart,rd,rx<br />.endm</p><p>.macro busyuart,rd,rx<br />1001: ldr /rd, [/rx, #0x10] @ read UTRSTAT<br />tst /rd, #1 << 2 @ TX_EMPTY ?<br />beq 1001b<br />.endm
i.修改 arch/arm/kernel/setup.c檔案
此檔案中的 setup_arch 非常關鍵,用來完成與體繫結構相關的初始化:
void __init setup_arch(char **cmdline_p)<br />{<br />struct tag *tags = NULL;<br />struct machine_desc *mdesc;<br />char *from = default_command_line;</p><p>ROOT_DEV = MKDEV(0, 255);</p><p>setup_processor();<br />mdesc = setup_machine(machine_arch_type);<br />machine_name = mdesc->name;</p><p>if (mdesc->soft_reboot)<br />reboot_setup("s");</p><p>if (mdesc->param_offset)<br />tags = phys_to_virt(mdesc->param_offset);</p><p>/*<br />* Do the machine-specific fixups before we parse the<br />* parameters or tags.<br />*/<br />if (mdesc->fixup)<br />mdesc->fixup(mdesc, (struct param_struct *)tags,<br />&from, &meminfo);</p><p>/*<br />* If we have the old style parameters, convert them to<br />* a tag list before.<br />*/<br />if (tags && tags->hdr.tag != ATAG_CORE)<br />convert_to_tag_list((struct param_struct *)tags,<br />meminfo.nr_banks == 0);</p><p>if (tags && tags->hdr.tag == ATAG_CORE)<br />parse_tags(tags);</p><p>if (meminfo.nr_banks == 0) {<br />meminfo.nr_banks = 1;<br />meminfo.bank[0].start = PHYS_OFFSET;<br />meminfo.bank[0].size = MEM_SIZE;<br />}</p><p>init_mm.start_code = (unsigned long) &_text;<br />init_mm.end_code = (unsigned long) &_etext;<br />init_mm.end_data = (unsigned long) &_edata;<br />init_mm.brk = (unsigned long) &_end;</p><p>memcpy(saved_command_line, from, COMMAND_LINE_SIZE);<br />saved_command_line[COMMAND_LINE_SIZE-1] = '/0';<br />parse_cmdline(&meminfo, cmdline_p, from);<br />bootmem_init(&meminfo);<br />paging_init(&meminfo, mdesc);<br />request_standard_resources(&meminfo, mdesc);</p><p>/*<br />* Set up various architecture-specific pointers<br />*/<br />init_arch_irq = mdesc->init_irq;</p><p>#ifdef CONFIG_VT<br />#if defined(CONFIG_VGA_CONSOLE)<br />conswitchp = &vga_con;<br />#elif defined(CONFIG_DUMMY_CONSOLE)<br />conswitchp = &dummy_con;<br />#endif<br />#endif<br />}
j.修改 arch/arm/mm/mm-armv.c 檔案(arch/arm/mm/ 目錄中的檔案完成與 ARM 相關的 MMU 處理)
修改
init_maps->bufferable = 0;
為
init_maps->bufferable = 1;
要輕而易舉地進行上述馬拉松式的核心移植工作並非一件輕鬆的事情,需要對 Linux 核心有很好的掌握,同時掌握硬體特定的知識和相關的彙編。幸而 mizi 公司的開發人員們已經合力為我們完成了上述工作,這使得小弟們在將 mizi-linux 移植到自身開發的電路板的過程中只需要關心如下幾點:
(1)核心初始化:Linux 核心的進入點是 start_kernel() 函數。它初始化核心的其他部分,包括捕獲,IRQ 通道,調度,裝置驅動,標定延遲迴圈,最重要的是能夠 fork"init" 進程,以啟動整個多任務環境。
我們可以在 init 中加上一些特定的內容。
(2)裝置驅動:裝置驅動佔據了 Linux 核心很大部分。同其他動作系統一樣,裝置驅動為它們所控制的硬體裝置和作業系統提供介面。
本文第四章將單獨講解驅動程式的編寫方法。
(3)檔案系統:Linux 最重要的特性之一就是對多種檔案系統的支援。這種特性使得 Linux 很容易地同其他動作系統共存。檔案系統的概念使得使用者能夠查看存放裝置上的檔案和路徑而無須考慮實際物理裝置的檔案系統類型。 Linux 透明的支援許多不同的檔案系統,將各種安裝的檔案和檔案系統以一個完整的虛擬檔案系統的形式呈現給使用者。
我們可以在 K9S1208 NAND FLASH 上移植 cramfs、jfss2、yaffs 等 FLASH 檔案系統。
3. init 進程
在 init 函數中"加料",可以使得 Linux 啟動的時候做點什麼,例如廣州友善之臂公司的 demo 板在其中加入了公司資訊:
static int init(void * unused)<br />{<br />lock_kernel();<br />do_basic_setup();</p><p>prepare_namespace();</p><p>/*<br />* Ok, we have completed the initial bootup, and<br />* we're essentially up and running. Get rid of the<br />* initmem segments and start the user-mode stuff..<br />*/<br />free_initmem();<br />unlock_kernel();</p><p>if (open("/dev/console", O_RDWR, 0) < 0)<br />printk("Warning: unable to open an initial console./n");</p><p>(void) dup(0);<br />(void) dup(0);</p><p>/*<br />* We try each of these until one succeeds.<br />*<br />* The Bourne shell can be used instead of init if we are<br />* trying to recover a really broken machine.<br />*/</p><p>printk("========================================/n");<br />printk("= Friendly-ARM Tech. Ltd. =/n");<br />printk("= http://www.arm9.net =/n");<br />printk("= http://www.arm9.com.cn =/n");<br />printk("========================================/n");</p><p>if (execute_command)<br />execve(execute_command,argv_init,envp_init);<br />execve("/sbin/init",argv_init,envp_init);<br />execve("/etc/init",argv_init,envp_init);<br />execve("/bin/init",argv_init,envp_init);<br />execve("/bin/sh",argv_init,envp_init);<br />panic("No init found. Try passing init= option to kernel.");<br />}
這樣在 Linux 的啟動過程中,會額外地輸出:
========================================<br />= Friendly-ARM Tech. Ltd. =<br />= http://www.arm9.net =<br />= http://www.arm9.com.cn =<br />========================================
4.檔案系統移植
檔案系統是基於被劃分的存放裝置上的邏輯上單位上的一種定義檔案的命名、儲存、組織及取出的方法。如果一個 Linux 沒有根檔案系統,它是不能被正確的啟動的。因此,我們需要為 Linux 建立根檔案系統,我們將其建立在 K9S1208 NAND FLASH 上。
Linux 的根檔案系統可能包括如下目錄(或更多的目錄):
(1)/bin (binary):包含著所有的標準命令和應用程式;
(2)/dev (device):包含外設的檔案介面,在 Linux 下,檔案和裝置採用同種地方法訪問的,系統上的每個裝置都在 /dev 裡有一個對應的裝置檔案;
(3)/etc (etcetera):這個目錄包含著系統設定檔案和其他的系統檔案,例如 /etc/fstab(file system table)記錄了啟動時要 mount 的 filesystem;
(4)/home:存放使用者主目錄;
(5)/lib(library):存放系統最基本的庫檔案;
(6)/mnt:使用者臨時掛載檔案系統的地方;
(7)/proc:linux 提供的一個虛擬系統,系統啟動時在記憶體中產生,使用者可以直接通過訪問這些檔案來獲得系統資訊;
(8)/root:超級使用者主目錄;
(9)/sbin:這個目錄存放著系統管理程式,如 fsck、mount 等;
(10)/tmp(temporary):存放不同的程式執行時產生的臨時檔案;
(11)/usr(user):存放使用者應用程式和檔案。
採用 BusyBox 是縮小根檔案系統的好辦法,因為其中提供了系統的許多基本指令但是其體積很小。眾所周知,瑞士軍刀以其小巧輕便、功能眾多而聞名世界,成為各國軍人的必備工具,並廣泛應用於民間,而 BusyBox 也被稱為嵌入式 Linux 領域的"瑞士軍刀"。
此地址可以下載 BusyBox:http://www.busybox.net,當前最新版本為 1.1.3。編譯好 busybox 後,將其放入 /bin 目錄,若要使用其中的命令,只需要建立 link,如:
ln -s ./busybox ls<br />ln -s ./busybox mkdir
4.1 cramfs
在根檔案系統中,為保護系統的基本設定不被更改,可以採用 cramfs 格式,它是一種唯讀快閃記憶體檔案系統。製作 cramfs 檔案系統的方法為:建立一個目錄,將需要放到檔案系統的檔案 copy 到這個目錄,運行"mkcramfs 目錄名 image 名"就可以產生一個 cramfs 檔案系統的 image 檔案。例如如果目錄名為 rootfs,則正確的命令為:
mkcramfs rootfs rootfs.ramfs
我們使用下面的命令可以 mount 產生的 rootfs.ramfs 檔案,並查看其中的內容:
mount -o loop -t cramfs rootfs.ramfs /mount/point
此地址可以下載 mkcramfs 工具:http://sourceforge.net/projects/cramfs/。
4.2 jfss2
對於 cramfs 快閃記憶體檔案系統,如果沒有 ramfs 的支援則只能讀,而採用jfss2(The Journalling Flash File System version 2)檔案系統則可以直接在快閃記憶體中讀、寫資料。jfss2 是一個日誌結構(log-structured)的檔案系統,包含資料和原資料(meta-data)的節點在快閃記憶體上順序地儲存。jfss2 記錄了每個擦寫塊的擦寫次數,當快閃記憶體上各個擦寫塊的擦寫次數的差距超過某個預定的閥值,開始進行磨損平衡的調整。調整的策略是,在記憶體回收時將擦寫次數小的擦寫塊上的資料移轉到擦寫次數大的擦寫塊上以達到磨損平衡的目的。
與 mkcramfs 類似,同樣有一個 mkfs.jffs2 工具可以將一個目錄製作為 jffs2 檔案系統。假設把 /bin 目錄製作為 jffs2 檔案系統,需要啟動並執行命令為:
mkfs.jffs2 -d /bin -o jffs2.img
4.3 yaffs
yaffs 是一種專門為嵌入式系統中常用的快閃記憶體裝置設計的一種可讀寫的檔案系統,它比 jffs2 檔案系統具有更快的啟動速度,對快閃記憶體使用壽命有更好的保護機制。為使 Linux 支援 yaffs 檔案系統,我們需要將其對應的驅動加入到核心中 fs/yaffs/,並修改核心設定檔。使用我們使用 mkyaffs 工具可以將 NAND FLASH 中的分區格式化為 yaffs 格式(如 /bin/mkyaffs /dev/mtdblock/0 命令可以將第 1 個 MTD 塊裝置分區格式化為 yaffs),而使用 mkyaffsimage(類似於 mkcramfs、mkfs.jffs2)則可以將某目錄產生為 yaffs 檔案系統鏡像。
嵌入式 Linux 還可以使用 NFS(網路檔案系統)通過乙太網路掛接根檔案系統,這是一種經常用來作為調試使用的檔案系統啟動方式。通過網路掛接的根檔案系統,可以在主機上產生 ARM 交叉編譯版本的目標檔案或二進位可執行檔,然後就可以直接裝載或執行它,而不用頻繁地寫入 flash。
採用不同的檔案系統啟動時,要注意通過第二章介紹的 BootLoader 修改啟動參數,如廣州友善之臂的 demo 提供如下三種啟動方式:
(1)從 cramfs 掛接根檔案系統:root=/dev/bon/2();
(2)從移植的 yaffs 掛接根檔案系統:root=/dev/mtdblock/0;
(3)從乙太網路掛接根檔案系統:root=/dev/nfs。
5.小結
本章介紹了嵌入式 Linux 的背景、移植項目、init 進程修改和檔案系統移植,通過這些步驟,我們可以在嵌入式系統上啟動一個基本的 Linux。
原文連結:http://dev.yesky.com/153/2527653.shtml。