[技術] SquashFS

Written on 3:25 下午 by Yu Lai

其實Squashfs已經用一陣子了,在用它之前是使用initramfs。
和initramfs相比,Squashfs較不佔記憶體的空間,
因為它是可以跑在mtd上的唯讀型壓縮檔案系統。
但也因為是唯讀的,所以在使用上仍需另外配置ramfs來提供Linux使用。
另外,initramfs和Kernel是包成同一個image的,而Squashfs是分開的。

Squashfs是一套供Linux核心使用的GPL開源唯讀壓縮檔案系統。
Squashfs能夠為檔案系統內的檔案、inode及目錄結構進行壓縮,
並支援最大1024千位元組的區段,以提供更大的壓縮比,
標準版的Squashfs採用gzip的數據壓縮。

Squashfs可以至http://squashfs.sourceforge.net/下載。

目前最新的是4.1版,是提供給Linux Kernel 2.6.29版本使用。
若使用較舊版本的Linux Kernel,請改用3.4版本。

以下是安裝Note,寫的有點亂,請見諒。
另外可以參考SquashFS HOWTO

1. 裝Linux Kernel上SquashFS patch。
# cd /opt/linux-2.6.xx
# patch -p1 < /opt/squashfs/kernel-patches/linux-2.6.xx/squashfs3.4-patch

2. 修改MTD配置,在Flash上配置出Kernel和Squashfs的partition出來。
(在此共切4個Partition,分別是0-Bootloader, 1-Kernel, 2-Rootfs, 3-Other)
若已經調整好或是使用類似Redboot的fis,可跳過這段。

3. 編Linux Kernel。
# make distclean
# make mrproper
# make menuconfig
在File systems->Miscellaneous file systems將Squashed filesystem啟動(bundled with the kernel)。
# make

4. 編出SquashFS tools (mksquashfs)。
# cd /opt/squashfs/squashfs-tools
# make

5. 將root file system透過mksquashfs來產生image。
# mksquashfs ./root_file_system rootfs.bin

6. 在Loader上用tftp把rootfs.bin載入後燒錄到Partition Rootfs對應在Flash上的Address。
e.g. # flash 0x00100000 0x80100000

7. 修改kernel command line。
請改成 "console=ttyS0 root=/dev/mtdblock2 rootfstype=squashfs"

PS1:
在root file system裡的/etc/fstab裡可加入
ramfs /tmp ramfs defaults 0 0,避免Linux無寫入的File System可以使用。

PS2:
配合mtd,在Linux下亦可以透過mtd tools直接upgrade root file system。
# flash_eraseall /dev/mtd2
# flashcp -v rootfs.bin /dev/mtd2

[技術] Dropbear - 輕量化SSH Server and Client

Written on 2:16 下午 by Yu Lai

最近工作上需要在嵌入式系統中加入SSH Server,本來是要使用OpenSSH,
但Code size差太大(1.4M vs 170K),於是改用了Dropbear。

載點: http://matt.ucc.asn.au/dropbear/dropbear.html

以下是安裝方法:

# ./configure --host=mips-linux --disable-zlib
# make

編譯完後會產生出以下的執行檔:

1. dropbear : SSH Server
2. dbclient : SSH Client
3. dropbearkey : Key Generator
4. dropbearconvert : OpenSSH Key Convertor

在使用上記得要有host key,才能執行dropbear,沒有的要請透過dropbearkey來產生。

# dropbearkey -t rsa -f /etc/dropbear/dropbear_rsa_host_key
# dropbear

[技術] 各家Toolchains的比較

Written on 4:04 下午 by Yu Lai

一般而言,
在開發Embedded System時大都需要用到不同平台的cross-compiler與Toolchains。
而在elinux.org有一篇介紹各家Toolchains的文章,就轉來參考囉。

Ref: http://elinux.org/Toolchains

以下是常見的幾個Toolchains的說明:

CodeSourcery
CodeSourcery develops Sourcery G++, an Eclipse based Integrated Development Environment (IDE) that incorporates the GNU Toolchain (gcc, gdb, etc.) for cross development for numerous target architectures. CodeSourcery provides a "lite" version for ARM, Coldfire, MIPS, SuperH and Power architectures. The toolchains are always very up to date. CodeSourcery contributes enhancements it makes to the GNU Toolchain upstream continually, making it the single largest (by patch count) corporate contributor.

DENX ELDK
The DENX Embedded Linux Development Kit (ELDK) provides a complete and powerful software development environment for embedded and real-time systems. It is available for ARM, PowerPC and MIPS processors and consists of:
Cross Development Tools (Compiler, Assembler, Linker etc.) to develop software for the target system.
Native Tools (Shell, commands and libraries) which provide a standard Linux development environment that runs on the target system.
Firmware (U-Boot) that can be easily ported to new boards and processors.
Linux kernel including the complete source-code with all device drivers, board-support functions etc.
Xenomai - RTOS Emulation framework for systems requiring hard real-time responses.
SELF (Simple Embedded Linux Framework) as fundament to build your embedded systems on.
All components of the ELDK are available for free with complete source code under GPL and other Free Software Licenses. Also, detailed instructions to rebuild all the tools and packages from scratch are included.
The ELDK can be downloaded for free from several mirror sites or ordered on CD-ROM for a nominal charge (99 Euro). To order the CD please contact office@denx.de
Detailed information about the ELDK is available here.

Buildroot
Buildroot is a complete build system based on the Linux Kernel configuration system and supports a wide range of target architectures. It generates root file system images ready to be written to flash. In addition to having a huge number of packages which can be compiled into the image, it also generates a cross toolchain to build those packages from source. Even if you don't want to use buildroot for your root filesystem, it is a useful tool for generating a toolchain. It should be noted however that it only supports uClibc. If you want to use glibc, you'll need something else.

[技術] 在Makefile中加入Check file size的機制

Written on 9:11 下午 by Yu Lai

今天在porting新板子的loader,
因為flash memory layout的關係,
整個loader大小才128kb。
而我沒注意到Image Size己經暴了一點,
沒想到flash還是寫成功了,
結果reboot後跑起來果然怪怪的。
連基本的printf都怪怪的,囧~
只能拿JTAG來救了,唉。

於是我想到要在Makefile裡加入檢查檔案大小的機制,
讓make時適時的提醒我檔案大小是否ok?

結果讓我搞了一個快下午還搞不定 @_@,
我試了老半天還是不知該怎麼把bash的if加到Makefile裡。
最後到GNU Makefile仔細的找了找才發現原來是我想錯了,
Makefile裡的command部份其實是呼叫sub shell執行命令,
然後判斷其return value。
所以要加入檢查檔案大小的機制連if都不必用到,
直接用test就可以了。

e.g.

check:
@echo -e "\nCheck redboot.img file size ....."
@test `stat -c %s boot/install/bin/redboot.img` -lt 129792
@echo "Ok."

[技術] 用GPIO實做I2C介面(Bit-Banging)

Written on 12:00 上午 by Yu Lai

最近工作上需要去讀寫I2C介面的IC,
是顆用來量測環境溫度的IC-LM75。
I2C是由2支腳-SDA和SCL所組成的並聯介面,
所有I2C的IC都接在這2支腳上,
I2C相關資訊可以自行到Googlewiki找找。

而我們的板子的CPU本身沒有I2C的介面,
它是透過2支GPIO腳來連接LM75,
所以就必須以軟體的方式來控制GPIO來模擬I2C的Signal,
以上的實做也被稱做Bit-Banging。

照著LM75的Datasheet和網路上相關的資料,
還蠻順利的完成整個Bit-Banging的實做。

以下就是相關實做的source code。

#define SCL_PORT  6
#define SDA_PORT 23

#define ADDR_LM75 0x94 /* 1001 010 x */
#define I2C_DELAY_TIME 10 /* us */

#define ACK 1
#define NO_ACK 0

首先是會用到的#define。
SCL腳位是使用GPIO6,SDA是使用GPIO23。
而LM75使用的Address則是固定的1001配上
板子上A2,A1,A0腳位所接的電位的010當前7個bit。
另外,由於I2C算是比較慢的介面,所以要有個delay time。
void i2c_init(void) {

/* Enable SDA & SCL gpio port */
gpio_enable(SDA_PORT);
gpio_enable(SCL_PORT);

/* Set SDA & SCL to High */
gpio_set(SDA_PORT, __HIGH__);
gpio_set(SCL_PORT, __HIGH__);

}

這裡是將GPIO腳位設定啟動,並將SDA和SCL電位設成HIGH,
完成Initial的動作。
void i2c_start(void) {

/* I2C start sequence is defined as
* a High to Low Transition on the data
* line as the CLK pin is high */

gpio_set(SDA_PORT, __HIGH__); /* SDA: High */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);

gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);

}

void i2c_stop(void) {

/* I2C stop sequence is defined as
* data pin is low, then CLK pin is high,
* finally data pin is high. */

gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
gpio_set(SDA_PORT, __HIGH__); /* SDA: High */

}

如source code裡我comment所寫的,
I2C介面溝通的start就是SDA和SCL都是由本來保持的High變成Low所開始。
而stop就是SDA和SCL由High-Low傳遞資料之間變為High持續下去。
void i2c_write(unsigned char data) {

/* An I2C output byte is bits 7-0
* (MSB to LSB). Shift one bit at a time
* to the MDO output, and then clock the
* data to the I2C Slave */

unsigned char i;

/* Write to slave */
for(i = 0; i < 8; i++) {
gpio_set(SDA_PORT, (data&0x80)?1:0); /* Send data bit */
data <<= 1; /* Shift one bit */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}

/* Read ACK bit from slave */
gpio_get(SDA_PORT);
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);

}

unsigned char i2c_read(unsigned char send_ack) {

unsigned char i, data;

data = 0x00;

/* Read from slave */
for(i = 0; i < 8; i++) {
data <<= 1; /* Shift one bit */
data |= gpio_get(SDA_PORT); /* Read data bit */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}

/* Send ACK bit to slave */
if(send_ack)
gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
else
gpio_set(SDA_PORT, __HIGH__); /* SDA: High */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);

return data;

}

接著就是傳送的過程了,
一般I2C都是由高位先寫入(這個不一定,要看Datasheet)。
在寫入時,Master(指CPU)先把SDA設成寫入的bit,
再把SCL依照我們delay的時間來做clock signal的產生(low->high->low->...)。
接著重複上面的動作依序把整個byte都寫入。
寫完整個byte後要讀取一下Slave(指LM75)所回傳的ACK bit。

而在讀取時,一樣先讀入SDA的電位當成data的bit,
再把SCL產生clock signal給Slave,
Slave在收到clock signal後會在SDA上變動電位把資料依序傳出來。
所以同樣的重複上面的動作就可以把組合出整個byte的資料。
當Master讀完byte後也要有回傳ACK的動作來告知是否繼續有下一個byte要讀取。
int get_lm75_temp(void) {

unsigned char msb = 0x00, lsb = 0x00;

i2c_start();
i2c_write(ADDR_LM75); /* Ask LM75 write */
i2c_write(0x00);

i2c_start();

i2c_write(ADDR_LM75+1); /* Ask LM75 read */
msb = i2c_read(ACK);
lsb = i2c_read(NO_ACK);
i2c_stop();

return (msb << 8) | lsb;

}

最後,照著LM75的Datasheet,
Master先傳送address byte給Slave,
Slave在收到address後會先比對自己A2,A1,A0,
若符合再依照address byte的最後一個bit來回應之後的動作,
若為0則是write動作,若為1則是read動作。

所以我們要讀取現在溫度資料時,
CPU先把address byte+0給寫到LM75,告知接下來要寫入register設定,
接著把0x00寫入表示要讀取第0個register,也就是現在的溫度。
再來就是重新開始先寫入address byte+1給LM75,告知要讀取資料。
接著就把溫度資料讀出,共2個byte,所以讀了2次囉。
最後就是把資料組合起來return出去囉。

[技術] 執行檔的檔案格式轉換

Written on 6:04 下午 by Yu Lai

最近為了產生flash的image raw file,
需要將compiler編好的srec格式檔或elf檔式檔進行轉換。
上網找了老半天,原來我們的gnu binutils就有提供這些功能了 ^^。

使用方法如下:
objcopy -I -O

例1: 將srec轉成binary raw file
objcopy -I srec -O binary image.srec image.bin

例2: 將elf32-bigmips轉成binary raw file
objcopy -I elf32-bigmips -O binary image.srec image.bin

[技術] Busybox使用心得

Written on 11:58 上午 by Yu Lai

最近為了產品的新功能以及業務反應的功能,
我重新配置了busybox來達到以上需求。
之前的busybox是由嘉義那邊的同事安裝好打包過來的,
也沒把source code一起帶過來,加上版本也有點舊了,
所以我就安裝新版的當做順便更新囉。

以下是安裝過程中遇到的問題與心得的筆記,
就當做分享與記錄囉。

首先,busybox可以到[http://busybox.net/]下載回來。
然後解開後,如果有需要透過cross-compile來編譯的話,
可以在Makefile裡找到ARCH與CROSS_COMPILE配置它,
另外也可以在Makefile.flags裡直接配置CC、AR和LD等變數。

設定好後直接執行make menuconfig來進行busybox的applet設定囉。
這裡有幾個比較要注意的地方,在Build Options中,我是建議把
Busybox設成Build BusyBox as a static binary (no shared libs)。
雖然比較佔空間,但省下來搞library的時間就夠值得了。
而在設定Login/Password Management Utilities的時候,
為了免去配置glibc的麻煩,最好設置使用busybox自己的password
和shadow文件的功能(Use internal password and group functions
rather than system functions)。
同時要把(login)和(Support for login scripts)打勾,
這樣login才會正常運作。

接著直接使用make和make install就可以把busybox編譯出來啦。
編譯好busybox後並把buxybox copy到rootfs中,
接著要配置好rootfs以配合busybox的運作。
這裡要配置的有etc/inittab、etc/init.d/rcS
以及etc/passwd和etc/shadow(有勾選的話)。

最後就把rootfs包起來燒到target裡就可以啦。

(PS: 以上要配置的檔案可以在網路上找到許多範例,改天再補進來吧)

[技術] 嵌入式linux下常見的文件系統

Written on 2:14 下午 by Yu Lai

嵌入式linux下常見的文件系統
• RomFS:只讀文件系統,可以放在ROM空間,也可以在系統的RAM中,嵌入式linux中常用來作根文件系統
• RamFS:利用VFS自身結構而形成的內存文件系統,使用系統的RAM空間
• JFFS/JFFS2:為Flash設計的日誌文件系統
• Yaffs:專門為Nand Flash設計
• proc:為Kernel和Module將Message發送給Process提供一種機制,可以查看系統Module裝載的信息
• devFS:設備文件系統

Linux上的Ext2fs
• 支持4 TB存儲、文件名稱最長1012字符
• 可選擇邏輯塊
• 快速符號鏈接
• Ext2不適合flash設備
• 是為象IDE 設備那樣的塊設備設計的,邏輯塊大小必須是512 byte、1 KB、2KB等
• 沒有提供對基於扇區的擦除/寫操作的良好管理
• 如果在一個扇區中擦除單個字節,必須將整個扇區複製到RAM,然後擦除,再重寫入
• 在出現電源故障時,Ext2fs 是不能防止崩潰的
• 文件系統不支持損耗平衡,縮短了flash的壽命

jffs/jffs2文件系統的優缺點
• 日誌文件系統
• 提供了更好的崩潰、掉電安全保護
• jffs2支持對flash的均勻磨損
• 在扇區級別上執行閃存擦除/寫/讀操作要比Ext2文件系統好
• 文件系統接近滿時,JFFS2 會大大放慢運行速度——垃圾收集

Nand上yaffs文件系統的優勢
• 專門為Nand flash設計的日誌文件系統
• jffs/jffs2不適合大容量的Nand flash
• jffs的日誌通過jffs_node建立在RAM中,佔用RAM空間:對於128MB的Nand大概需要4MB的空間來維護節點
• 啟動的時候需要掃瞄日誌節點,不適合大容量的Nand flash
• FAT系統沒有日誌編譯yaffs文件系統
• mtd的最新補丁升級?
• 接口更新,適合與yaffs
• 與原有的mtd驅動程序不兼容,需要重寫
• 如果使用舊mtd驅動需要定義Makefile中MTD_OLD = -DCONFIG_YAFFS_USE_OLD_MTD
• 參考文檔: yaffs-rootfs-howto
• 最新版的yaffs網站:
http://www.aleph1.co.uk/armlinux/projects/yaffs

使用yaffs文件系統
• 通過cat /proc/yaffs命令可以看到yaffs系統的相關信息
• mount -t yaffs /dev/mtdblock/0 /mnt/yaffs

[技術] 嵌入式系統入學測驗

Written on 10:19 上午 by Yu Lai

原文出處 A 'C' Test: The 0x10 Best Questions for Would-be Embedded Programmers: http://www.embedded.com/2000/0005/0005feat2.htm

以下是網路上找到中文版,作者不詳。

C語言測試是徵選嵌入式系統程式員過程中必須而且有效的方法。這些年我既參加也組織了許多這種測試,在這過程中我意識到這些測試能為面試者和被面試者提供許多有用訊息,此外,撇開面試的壓力不談,這種測試也是相當有趣的。

從被面試者的角度來講,你能了解許多關於出題者或監考者的情況。這個測試只是出題者為顯示其對ANSI標準細節的知識而不是技術技巧而設計嗎?這個愚蠢的問題嗎?如要你答出某個字符的ASCII值。這些問題著重考察你的系統調用(invoke)和記憶體分發策略方面的能力嗎?這反映出出題者也許花時間在微處理機上而不在嵌入式系統上。

如果上述任何問題的答案是“是”的話,那麼我知道我得認真考慮我是否應該去做這份工作。

從面試者的角度來講,一個測試也許能從多方面揭示應試者的素質。最基本的,你能了解應試者C語言的水準。不管怎麼樣,看一下這人如何回答他不會的問題也是滿有趣。應試者是以好的直覺做出明智的選擇,還是只是瞎蒙呢?當應試者在某個問題上卡住時是找藉口呢,還是表現出對問題的真正的好奇心,把這看成學習的機會呢?我發現這些訊息與他們的測試成績一樣有用。

有了這些想法,我決定出一些真正針對嵌入式系統的考題,希望這些令人頭痛的考題能給正在謀職的人一點幫住。這些問題都是我這些年實際碰到的。其中有些題很難,但它們應該都能給你一點啟發。

這個測試適用於不同水準的應試者,大多數初級水準的應試者的成績會很差,經驗豐富的程式員應該有很好的成績。為了讓你能自己決定某些問題的偏好,每個問題沒有分發分數,如果選擇這些考題為你所用,請自行按你的意思分發分數。


==============================================


預處理器 (Preprocessor)

1.用預處理指令#define 聲明一個常數,用以表示1年中有多少秒 (忽略閏年問題)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情︰

a.#define 語法的基本知識 (例如︰不能以分號結束,括號的使用,等等)
b.懂得預處理器將為你計算常數表達式的值,因此,直接寫出你是如何計算一年中有多少秒會比直接計算出實際的值更清晰。
c.意識到這個表達式將使一個16位元的機器產生整數型溢位 - 因此要用到長整型符號L,告訴編譯器這個常數是的長整型數。
d.如果你在你的表達式中用到UL (表示無符號長整型) ,那麼你有了一個好的起點。記住,第一印象很重要。

2.寫一個“標準”巨集MIN ,這個巨集輸入兩個參數並返回較小的一個。

#define MIN(A, B) ((A) <= (B) ? (A) : (B))
這個測試是為下面的目的而設的︰

a.標識#define在巨集中應用的基本知識。這是很重要的,因為在行內(inline)運算子變為標準C的一部分之前,巨集是方便產生行內程式碼的唯一方法,對於嵌入式系統來說,為了能達到要求的性能,行內程式碼經常是必須的方法。
b.三元運算子的知識。這個運算子存在C語言中的原因是它使得編譯器能產生比if-then-else更優化的程式碼,了解這個用法是很重要的。
c.懂得在巨集中小心地把參數用括號括起來。我也用這個問題開始討論巨集的副作用,例如︰當你寫下面的程式碼時會發生什麼事?

least = MIN(*p++, b);

3.預處理器標識#error的目的是什麼?

如果你不知道答案,請看參考文獻1。這問題對區分一個正常的伙計和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當然如果你不是在找一個書呆子,那麼應試者最好希望自己不要知道答案。


==============================================


無窮迴圈 (Infinite loops)

4.嵌入式系統中經常要用到無窮迴圈,你怎麼樣用C編寫無窮迴圈呢?

這個問題用幾個解決方案。我首選的方案是︰

while(1)
{
...
}
一些程式員更喜歡如下方案︰
for(;;)
{
...
}
這個實作方式讓我為難,因為這個語法沒有確切表達到底怎麼回事。如果一個應試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的基本原理。如果他們的基本答案是︰“我被教著這樣做,但從沒有想到過為什麼。”這會給我留下一個壞印象。
第三個方案是用 goto︰
Loop:
...
goto Loop;
應試者如給出上面的方案,這說明或者他是一個組合語言程式員 (這也許是好事) 或者他是一個想進入新領域的BASIC/FORTRAN程式員。


==============================================


數據宣告 (Data declarations)

5.用變數a給出下面的定義:
a)一個整型數 (An integer)
b)一個指向整數的指標 (A pointer to an integer)
c)一個指向指標的指標,它指向的指標是指向一個整型數 (A pointer to a pointer to an integer)
d)一個有10個整數型的陣列 (An array of 10 integers)
e)一個有10個指標的陣列,該指標是指向一個整數型的 (An array of 10 pointers to integers)
f)一個指向有10個整數型陣列的指標 (A pointer to an array of 10 integers)
g)一個指向函數的指標,該函數有一個整數型參數並返回一個整數 (A pointer to a function that takes an integer as an argument and returns an integer)
h)一個有10個指標的陣列,該指標指向一個函數,該函數有一個整數型參數並返回一個整數 (An array of ten pointers to functions that take an integer argument and return an integer)


答案是︰

a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人們經常聲稱這裡有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間裡,我確定我知道這個問題的答案。應試者如果不知道所有的答案(或至少大部分答案),那麼也就沒有為這次面試做準備,如果該面試者沒有為這次面試做準備,那麼他又能為什麼出準備呢?


==============================================


Static

6.關鍵字static的作用是什麼?

這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用︰
a.在函數本體內(in Function Block),一個被宣告為靜態的變數,在這一函數被呼叫過程中維持其值不變。
b.在一個Block(ie. {...} )內 (但在函數體外),一個被宣告為靜態的變數可以被Block內所有的函數存取, 但不能被Block外的其它函數存取。它是一個本地的全局變量。
c.在Block內,一個被聲明為靜態的函數,只可被這一Block內的其它函數呼叫。也就是這個函數被限制在宣告它的Block的本地範圍內使用。大多數應試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化資料和程式碼範圍的好處和重要性。


==============================================


Const(常量)

7.關鍵字const有什麼含意?

我只要一聽到被面試者說︰“const意味著常數”,我就知道我正在和一個業餘者打交道。去年Dan Saks已經在他的文章裡完全概括了const的所有用法,因此ESP(譯者︰Embedded Systems Programming)的每一位讀者應該非常熟悉const能做什麼和不能做什麼。如果你從沒有讀到那篇文章,只要能說出const意味著“只讀”就可以了。儘管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)如果應試者能正確回答這個問題,我將問他一個附加的問題︰下面的聲明都是什麼意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前兩個的作用是一樣,a是一個常數型整數。
第三個意味著a是一個指向常數型整數的指標(也就是,整型數是不可修改的,但指標可以)。
第四個意思a是一個指向整數的常數型指標(也就是說,指標指向的整數是可以修改的,但指標是不可修改的)。
最後一個意味著a是一個指向常數型整數的常數型指標(也就是說,指標指向的整數是不可修改的,同時指標也是不可修改的)。
如果應試者能正確回答這些問題,那麼他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 const,也還是能很容易寫出功能正確的程式,那麼我為什麼還要如此看重關鍵字const呢 ?我有以下的幾個理由︰
a。關鍵字const的作用是給讀你程式碼的人傳達非常有用的訊息,實際上,宣告一個參數為常量是為了告訴了程式員這個參數的應用目的。如果你曾花很多時間清理其它人留下的垃圾(記憶體回收),你就會很快學會感謝這點多餘的訊息。(當然,懂得用const的程式員很少會留下的垃圾讓別人來清理的)
b。透過給優化器一些附加的訊息,使用關鍵字const也許能產生更緊湊的程式碼。
c。合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的程式碼修改。簡而言之,這樣可以減少bug的出現。


==============================================


Volatile(易變的)

8. 關鍵字volatile有什麼含意?並給出三個不同的例子。

一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在暫存器裡的備份。下面是volatile變量的幾個例子︰
a.並行設備的硬體暫存器 (如︰狀態暫存器)
b.一個中斷服務次程序中會訪問到的非自動變數(Non-automatic variables)
c.多執行緒應用中被幾個任務(task)共享的變數
回答不出這個問題的人是不會被僱佣的。(os:還好我不是讓這個人面試.....)我認為這是區分C程式員和嵌入式系統程式員的最基本的問題。搞嵌入式的家伙們經常同硬體、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內容將會帶來災難。
假設被面試者正確地回答了這是問題 (嗯,懷疑是否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
Q 一個參數可以同時是const也是volatile嗎?解釋為什麼。
Q 一個指標可以是volatile 嗎?解釋為什麼。
Q 下面的函數有什麼錯誤︰

int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案︰
A 是的。舉的例子是"只讀的狀態暫存器"。它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。
A 是的。儘管這並不很常見。舉的例子是當一個執行中的次程序修該一個指向一個buffer 的指標時。
A 這段程式碼有點變態。這段程式碼的目的是用來返指標*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的程式碼︰
int square(volatile int *ptr)
{
int a, b;
a = *ptr;
b = *ptr;
return a * b;
}
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段程式碼可能返回不是你所期望的平方值!正確的程式碼如下︰
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}


==============================================


位元操作 (Bit Manipulation)

9.嵌入式系統總是要用戶對變量或暫存器進行位操作。給定一個整型變量a,寫兩段程式碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。

對這個問題有三種基本的回應
- 不知道如何下手。該被面者從沒做過任何嵌入式系統的工作。
- 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的程式碼在不同編譯器之間是不可移植的,同時也保證了的你的程式碼是不可重新使用的。我最近不幸看到Infineon為其較複雜的通信晶片寫的驅動程式,它用到了bit fields因此完全對我無用,因為我的編譯器採用其它的模式來實現bit fields。從道德講︰永遠不要讓一個非嵌入式的家伙沾到實際硬體的邊。
- 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法, 是應該被用到的方法。
最佳的解決方案如下︰

#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void) {
a = BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}
一些人喜歡為設定和清除值而定義一個掩碼同時定義一些說明常數,這也是可以接受的。我希望看到幾個要點︰說明常數、= 和 &=~ 操作。


==============================================


存取固定的記憶體位置 (Accessing fixed memory locations)

10.嵌入式系統經常具有要求程式員去存取某特定的記憶體位置的特點。在某工程中,要求設定一個絕對位址為0x67a9的整數型變數的值為0xaa55。編譯器是一個純粹的ANSI編譯器。寫程式碼去完成這一任務。

這一問題測試你是否知道為了存取一絕對位址把一個整數型強製轉型 (typecast) 為一指標是合法的。這一問題的實作模式隨著個人風格不同而不同。 典型的類似程式碼如下︰

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一個較艱澀的方法是︰
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。


==============================================


中斷 (Interrupts)

11.中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展-讓標準C支持中斷。具代表的事實是,產生了一個新的關鍵字 __interrupt。下面的程式碼就使用了__interrupt關鍵字去定義了一個中斷服務次程序(ISR),請評論一下這段程式碼的。

__interrupt double compute_area(double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}

這個函數有太多的錯誤了,以至讓人不知從何說起了︰
- ISR 不能返回一個值。如果你不懂這個,那麼你不會被雇用的。(os : 囧)
- ISR 不能傳遞參數。如果你沒有看到這一點,你被雇用的機會等同第一項。
- 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓多餘的暫存器入棧(PUSH入堆疊),有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
- 與第三點一脈相承,printf()經常有重入和性能上的問題。 如果你丟掉了第三和第四點,我不會太為難你的。但如果你能得到後兩點,那麼你的被雇用前景越來越光明了。


==============================================


程式碼例子 (Code examples)

12.下面的程式碼輸出是什麼,為什麼?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}

這個問題測試你是否懂得C語言中的整數自動轉型原則,我發現極少有開發者懂得這些東西。不管如何,這unsigned int的答案是輸出是 "> 6"。原因是當表達式中存在有符號類型和無符號類型時所有的操作數都自動轉換為無符號類型(unsigned)。因此-20變成了一個非常大的正整數,所以該表達式計算出的結果大于6。這一點對於應當頻繁用到無符號數據類型的嵌入式系統來說是非常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。

13.評價下面的程式碼片斷︰

unsigned int zero = 0;
unsigned int compzero = 0xFFFF; /*1's complement of zero */

對于一個int型不是16位的處理器為說,上面的程式碼是不正確的。應編寫如下︰

unsigned int compzero = ~0;

這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在我的經驗裡,好的嵌入式程式員非常準確地明白硬體的細節和它的限制,然而PC機程式往往把硬體作為一個無法避免的煩惱。
到了這個階段,應試者可能完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應試者不是很好,那麼這個測試就在這裡結束了。但如果顯然應試者做得不錯,那麼我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優秀的應試者能做得不錯。提出這些問題,我希望更多看到應試者應付問題的方法,而不是答案。不管如何,你就當是這個娛樂吧…


==============================================


動態記憶體分發 (Dynamic memory allocation)

14.儘管不像非嵌入式計算機那麼常見,嵌入式系統還是有從堆積(heap)中動態分發內存的過程的。那麼嵌入式系統中,動態分發記憶體可能發生的問題是什麼?
這裡,我期望應試者能提到記憶體碎片,碎片收集的問題,變量的生命週期等等。這個主題已經在ESP雜誌中被廣泛地討論過了(主要是 P.J. Plauger,他的解釋遠遠超過我這裡能提到的任何解釋)。我拿出這麼一個小題目給應試者︰
下面的程式碼片段的輸出是什麼,為什麼?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");

這是一個有趣的問題。最近在我的一個同事不經意把0值傳給了函數malloc,得到了一個合法的指標之後,我才想到這個問題。這就是上面的程式碼,該程式碼的輸出是"Got a valid pointer"。我用這個問題來開始討論,看看被面試者是否想到怎樣做才是正確的。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。


==============================================


Typedef

15.Typedef 在C語言中頻繁用以宣告一個已經存在的資料型態的同義字。也可以用預處理器做類似的事。例如,思考一下下面的例子︰

#define dPS struct s *
typedef struct s * tPS;

以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指標。哪種方法更好呢? (如果有的話)為什麼? 這是一個非常微妙的問題,任何人答對這個問題 (正當的原因) 是應當被恭喜的。
答案是︰typedef更好。思考下面的例子︰
dPS p1, p2;
tPS p3, p4;

第一個擴展為
struct s * p1, p2;

上面的程式碼定義p1為一個指向結構的指標,p2為一個實際的結構,這也許不是你想要的。
第二個例子正確地定義了p3 和p4 兩個指標。


==============================================


艱澀的語法

16.C語言允許一些令人震驚的結構,下面的結構是合法的嗎,如果是,它做些什麼?

int a = 5, b = 7, c;
c = a+++b;

這個問題將做為這個測驗的一個愉快的結尾。不管你相不相信,上面的例子是完全合法的。問題是編譯器如何處理它?水準不高的編譯作者實際上會爭論這個問題,根據最處理原則,編譯器應當能處理儘可能所有合法的用法。因此,上面的程式碼被處理成︰
c = a++ + b;

因此,這段程式碼執行後
a = 6, b = 7, c = 12

如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當作問題。我發現這個問題的最大好處是這是一個關於程式碼編寫風格,程式碼的可讀性,程式碼的可修改性的好的話題。


==============================================


好了,伙計們,你現下已經做完所有的測試了。這就是我出的C語言測試題,我懷著愉快的心情寫完它,希望你以同樣的心情讀完它。如果是認為這是一個好的測試,那麼盡量都用到你的謀職的過程中去吧。天知道也許過個一兩年,我就不做現下的工作,也需要找一個。
Nigel Jones 是一個顧問,現下住在Maryland。他很高興能收到讀者的來信,他的email位址是: NAJones@compuserve.com


==============================================

[技術] 製作 ARM9 的 Bootstrap Root Filesystem

Written on 7:40 下午 by Yu Lai

本文出處: www.jollen.org
已取得原作者授權使用

《Jollen的Root Filesystem建置技術系列》

製作ARM9的Bootstrap Root Filesystem

作者/陳俊宏

http://www.jollen.org

更新日期:2007/1/23

在「完整註明出處」的前提下(註明方式說明),您能立即擁有轉貼與引用的授權,且毋需知會作者。

目的

製作 bootstrap root filesystem(base root filesystem)以提供一個最簡單、陽春且可開機的環境;製作完成的系統可開機到shell模式,並可使用 busybox 提供的指令。

準備工作

首先,您必須準備一台 host 開發環境,並安裝好 cross toolchain;接著,由於本文是做實機測試,因此,如果您沒有 ARM9 開發板,可以考慮使用 Qemu 來做模擬測試。

以下的操作示範,只節錄重點指令片段,您可能必須根據自己的整體實作流程,來微調指令的順序,或是參數等。

Step 1:建立工作目錄

建立一個專用的工作目錄,命名為 arm9.so-busybox/:

# mkdir arm9.so-busybox/

# cd arm9.so-busybox/

接著在 arm9.so-busybox/ 目錄下建立 4 個子目錄:

# mkdir src/ install/ mnt/ pub/ build/

實際進行 root filesystem 實作時,我們應該養成將檔案分類擺放的好習慣。以本專案為例,build/ 目錄用來編譯程式,src/ 目錄用來存放原始程式碼,install/ 目錄則用來擺放我們最後的 root filesystem。

Step 2:建立目錄架構

根據 FHS 的目錄架構標準,在 root filesystem 目錄下(install/)建立目錄階層架構:

# cd install/

# mkdir bin/ dev/ etc/ mnt/ proc/ sbin/ usr/

另外還有二個必要的目錄:/var 與 /tmp,由於這二個目錄都需要具備寫入權限,所以在這裡我們是以 ramdisk 的做法來 mount 這二個目錄。

Step 3:建立裝置檔

在 root filesystem 的 dev/ 目錄下建立必要的裝置檔:

crw------- 1 root root 5, 1 1月 1 1970 console

crw------- 1 root root 29, 0 1月 1 1970 fb0

crw------- 1 root root 1, 3 1月 1 1970 null

brw------- 1 root root 1, 0 1月 1 1970 ram0

crw------- 1 root root 5, 0 1月 1 1970 tty

crw------- 1 root root 4, 0 1月 1 1970 tty0

此階段使用 mknod 指令來完成。請先切換到 root filesystem 的 dev/ 目錄下,接著執行以下指令:

# mknod console c 5 1

# mknod fb0 c 29 0

# mknod null c 1 3

# mknod ram0 1 0

# mknod tty c 5 0

# mknod tty0 c 4 0

對於需要產生大量 device file 的場合來說,可以改用 genext2fs 的 ‘-D’ 參數來製作。詳見 Jollen’s Blog:[使用 genext2fs 的 '-D'(device file table)來建立 root filesystem]。

Step 4:加入Busybox

編譯並安裝 Busybox(動態程式庫方式)。將取得的Busybox原始碼解壓縮至 project 目錄裡的 src/ 子目錄下,以下是幾個注意事項:

本教學文件使用 Busybox 1.3.1

Busybox 1.3.0 開始,使用 Linux Kernel 的 Makefile(因為開始支援 CONFIG_DESKTOP)。Cross compile 時,需要修改 Makefile 如下:

ARCH ?= arm

CROSS_COMPILE ?= /opt/crosstool/gcc-3.4.1-glibc-2.3.3/arm-9tdmi-linux-gnu/bin/arm-9tdmi-linux-gnu-

CROSS_COMPILE 的設定是 cross toolchain 的「PREFIX」,視您的 toolchain 而定。您可由 http://www.jollen.org/kit/ 下載本文所使用的 GCC 3.4.1 ARM9 toolchain,以使用與本文完全相同的修改。

Busybox 整合了常用的指令與工具,我們可以設定 Busybox,以勾選我們需要的功能選項。進入 Busybox 的設定選單:

# make menuconfig

請注意,init 與 shell 是必選的項目,請檢查是否有勾選這二個功能。同時,也別忘了設定 Busybox 的安裝路徑,將安裝路徑指到我們 root filesystem 目錄下。

接著直接進行編譯(cross compile):

# make

編譯完成後,將 Busybox 安裝至我們的 root filesystem 目錄(即 Step 2 的 install/ 目錄):

# make install

此時,您應該可以在 root filesystem 目錄下看到 Busybox 所安裝的檔案。

Step 5:加入動態程式庫

編譯完成的 Busybox 已經是給 ARM9 執行的格式了,但我們的編譯設定是將Busybox 編譯成 shared library 架構,因此 Busybox 執行時需要以下的檔案:

˙ libc.so.6:C library標準程式庫。

˙ ld-linux.so.2:Native dynamic loader。

請由 toolchain 將以上二個檔案複製至 root filesystem 的 lib/ 目錄下:

# cd ../../install (切換至root filesystem根目錄)

# cp /opt/crosstool/gcc-3.4.1-glibc-2.3.3/arm-9tdmi-linux-gnu/arm-9tdmi-linux-gnu/lib/ld-linux.so.2 lib/ (複制native dynamic loader。以上命令請勿斷行)

# cp /opt/crosstool/gcc-3.4.1-glibc-2.3.3/arm-9tdmi-linux-gnu/arm-9tdmi-linux-gnu/lib/libc.so.6 lib/ (複製C library。以上命令請勿斷行)

Busybox 會因版本與功能選項設定的差異,而需要更多的程式庫。請使用 cross toolchain 的 objdump 指令來檢查 Busybox 的程式庫相依問題(無法使用 ldd 指令),並將所需的程式庫由 toolchain 複製到 root filesystem 的 lib/ 目錄下。

Step 6:加入系統檔案

加入2個重要的系統檔案於 etc/ 目錄下:

˙ fstab:mount table。

˙ inittab:系統初始表(init table)。

etc/fstab內容如下:

/dev/ram0 / ext2 defaults 1 1

none /proc proc defaults 0 0

/dev/ram1 /tmp ramfs defaults 0 0

/dev/ram2 /var ramfs defaults 0 0

fstab 第一行設定,目的在將 /dev/ram0 重新附掛成 ‘/’(root),此動作用意在於重新指定 ‘/’ 的檔案系統為 ext2。最後二行的目的是為了以 ramfs 來 mount 重要的二個目錄:/var 與 /tmp;如此一來,就算開機沒做 remount root(詳見後文說明),也能對 /tmp 與 /var 目錄做寫入的動作

etc/inittab內容如下:

:0:sysinit:/etc/rc.d/rc.init

:0:respawn:/bin/sh

根據這個 inittab 設定,當系統開機後便會進入 run level 0,在 run level 0 模式下,init process會執行2個動作:(1) 執行 /etc/rc.d/rc.init,此即「init script」;(2) 執行 /bin/sh,即進入 shell 模式。

在此我們並沒有參照 LSB 的標準來設定 run level,而且也沒有使用 getty 來讓使用者登入(多使用者模式)。

Step 7:編寫 Initial Script

根據 inittab 的設定,我們 root filesystem 的 init script 位於 /etc/rc.d/rc.init。以下提供一個供 Embedded Linux 使用的 init script 範本:

#!/bin/sh

# automount (/etc/fstab)

mount -a

# remount root

mount -o remount rw /

#

mkdir /var/lock

mkdir /var/lock/subsys

mkdir /var/run



# start other applications (Running application automatically during

# booting up.

# eg. /bin/thttpd –p 80 –d /var/www

當我們執行「mount –a」後,mount 便會去讀取前一步驟所設定的 fstab,並根據此表格的內容來做 mount 的動作。另外,這裡有一個 remount 的動作:

# mount -o remount rw /

此動作的目的是將 root(’/’)重新 mount 成可讀寫,此動作是選擇性的,若省略不做,請務必保持 /var 與 /tmp 目錄是能寫入的(建議以 ramdisk 方式實作為佳)。

若 root filesystem 未包含 inittab 設定檔,則 Busybox 會使用以下的內建設定:

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

::askfirst:/bin/sh

::ctrlaltdel:/sbin/reboot

::shutdown:/sbin/swapoff -a

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

::restart:/sbin/init

不過,還是建議編寫自己的 inittab 設定檔。

Step 8:製作 Root Filesystem 映像檔(Image File)

截至目前為止,我們的檔案系統已經擁有基本的系統指令與工具。接下來,我們即可將建置完成的 root filesystem 製作成 ext2 格式的映像檔。

以下提供二種 ext2fs image file 的製作方式:(1) 土方法;(2) 使用 genext2fs 工具。

先說明傳統的土方法。首先,先利用dd指令做出一個空白的檔案,大小為 4M(bytes):

# dd if=/dev/zero of=ext2fs bs=1k count=4096

我們將檔案命名為 ext2fs,接著再將 ext2fs 製作成 ext2 格式的檔案系統:

# mkfs.ext2 ext2fs

mke2fs 1.32(09-Nov-2002)

ext2fs is not a block special device.

Proceed anyway?(y,n)y

選擇y後出現以下畫面:

Filesystem label=

OS type: Linux

Block size=1024(log=0)

Fragment size=1024(log=0)

128 inodes, 1024 blocks

51 blocks(4.98%)reserved for the super user

First data block=1

1 block group

8192 blocks per group, 8192 fragments per group

128 inodes per group



Writing inode tables: done

Writing superblocks and filesystem accounting information: done



This filesystem will be automatically checked every 26 mounts or

180 days, whichever comes first. Use tune2fs -c or -i to override.

到這裡我們已經做好一個檔案格式為 ext2 的空白映像檔,再來只要將先前做好的 root filesystem 全部複製到 ext2fs 映像檔「裡面」即可。

先將 ext2fs 附掛至任一空目錄,例如 mnt/:

# mkdir mnt/

# mount -t ext2 -o loop ext2fs mnt/ (指定檔案系統為 ext2)

複製檔案系統時,我們不使用 cp 指令,而是利用 tar 來完成:

# cd install/

# tar cz * > ../install.tar.gz (將檔案系統做成tarball,同時也備份 root filesystem。)

# cd ..

# cd mnt/

# tar zxvf ../install.tar.gz (再將tarball解至映像檔)

接著將映像檔 umount 並壓縮即可:

# cd ..

# umount mnt/

# gzip -9c ext2fs > pub/ext2fs.gz

最後得到的 ext2fs.gz 即是完成品。請注意,若不使用 tar 來說,也應該使用 cpio 來複製檔案,避免使用 cp 指令。

使用 genext2fs

genext2fs 是一個 ext2 filesystem image file 的製作工具,可以讓我們很方便地將 root filesystem 製作成 image 檔。請由 genext2fs 的官方網站下載原始碼套件:

http://genext2fs.sourceforge.net/

編譯後可以取得 genext2fs 檔案,以下是將 install/ 目錄製作成 ext2fs image 檔的指令:

# genext2fs -b 8192 -i 1024 -d install/ ext2fs

執行後,會得到檔名為 ext2fs 的 image 檔,大小為 8 MB(透過 ‘-b’ 參數指定 image file 大小);接著同樣再用 gzip 將 ext2fs 檔壓縮即可。

Step 9:在 Target 端做測試

本步驟以 Jollen-Kit! 為例,Jollen-Kit! 是由 www.jollen.org 所推出的 ARM9 training board,詳細介紹請參考 [http://www.jollen.org/kit/]。請注意,本階段的操作,視 target device 的不同而不同,因此以下示範只適用於 Jollen-Kit! 或是其他的 SMDK2410 平臺。

步驟 8 所得到的 ext2.gz 必須再包裝成 U-Boot 的格式,才能透過 U-Boot 載入到 RAM,以成為 kernel 的 initial ramdisk(initrd):

# mkimage -A arm -O linux -T ramdisk -C none -a 0x30800000 -e 0x30800000 -n ramdisk -d ext2fs.gz urootfs.img

執行後可得到 urootfs.img 檔案,在測試階段為了方便起見,我們可以直接將 urootfs.img 載到 RAM 做測試;U-Boot 指令如下:

jollen.org # tftpboot 32000000 urootfs.img; tftpboot 30F00000 uimage.img; bootm 30F00000 32000000

urootfs.img 是我們製作的 root filesystem,uimage.img 則是給 Jollen-Kit! 使用的 Linux kernel(pre-built)。

[技術] 學習使用SkyEye仿真

Written on 9:08 上午 by Yu Lai

[轉自Experiences of Code (http://blog.csdn.net/zblue78)]

學習使用SkyEye仿真

--------------------------------------------------------------------------------


SkyEye是一個可以運行嵌入式操作系統的硬件仿真工具,這樣就可以在沒有硬件條件下來進行嵌入式系統的開發。

以下操作均在Fedora Core 1.0里通過。

Skyeye項目資源列表
http://gro.clinux.org/projects/skyeye/


文檔摘要:
1、什麼是SkyEye?
2、SkyEye可以做什麼事情?
3、安裝SkyEye
4、安裝arm-elf交叉編譯器
5、測試你的arm-elf-gcc編譯器
6、執行你的hello程序
7、編譯並運行uClinux-dist-20030909.tar.gz
8、加入網絡功能
9、安裝完成SkyEye後,下一步將做什麼?


1、什麼是SkyEye?

SkyEye是開源軟件的一個項目,SkyEye的目標是在Linux和Windows操作系統裡提供一個完全的仿真環境。SkyEye仿真環境相當於一個嵌入式計算機系統,你可以在SkyEye裡運行一些嵌入式Linux操作系統,如ARMLinux,uClinux,uc/OS-II(ucos-ii)等,並能分析和調試它們的源代碼。

如果你想知道關於SkyEye和嵌入式系統更詳細的信息,請訪問下面的站點:
www.SkyEye.org
http://www.skyeye.org/index_cn.html

通過SkyEye能仿真下面的硬件:
CPU核心:ARM7TDMI, ARM720T, ARM9, StrongARM, XScale
CPU: Atmel AT91/X40, Cirrus CIRRUS LOGIC EP7312, Intel SA1100/SA1110, Intel XScale PXA 250/255, CS89712, samsung 4510B, samsung 44B0(還不全)
內存: RAM, ROM, Flash
周邊設備: Timer, UART, ne2k網絡芯片, LCD, 觸摸屏等

目前能在SkyEye上運行下面的操作系統和系統軟件:
uC/OSII-2.5.x(支持網絡)
uClinux(基於Linux2.4.x內核, 支持網絡)
ARM Linux 2.4.x/2.6.x
lwIP on uC/OSII
基於uC/OSII, uClinux, ARM Linux的應用程序


2.SkyEye可以做什麼事情?
1. 通過SkyEye可以幫助促進嵌入式系統的學習,在不需要額外硬件的情況下學習和分析uclinux操作系統和其它嵌入式操作系統,如ucosII等。
2. SkyEye可用於嵌入式系統的教學。
3. 希望通過skyeye促進操作系統的研究,如ucosII,uclinux+RTAI,uclinux2.5.x等。
4. 可以基於SkyEye進行仿真特定硬件模塊的研究。
5. SkyEye可以作為嵌入式集成開發環境開發嵌入式系統(當然需要對SkyEye做大量的工作)。
註:引自陳渝《SkyEye Project FAQ》


3、安裝SkyEye

到http://gro.clinux.org/projects/skyeye/下載skyeye-0.7.0.tar.bz2包:

tar jxvf skyeye-v0.7.0.tar.bz2

進入解壓後的skyeye目錄,如果SkyEye的版本低於0.6.0,則運行下面的命令:

./configure --target=arm-elf --prefix=/usr/local --without-gtk-prefix --without-gtk-exec-prefix --disable-gtktest

如果SkyEye的版本高於0.6.0,則運行下面的命令:

./configure --target=arm-elf --prefix=/usr/local

接下來執行:

make
make install

安裝完成後執行skyeye

注意:
a.如果你使用的是Mandrake Linux發行版,那麼你在編譯SkyEye時遇到錯誤,並且錯誤與readline, ncurse, termcap等有關,你可以試試下面的方法:

ln -s /usr/include/ncurses/termcap.h /usr/local/include/termcap.h

接著再make和make install看能否成功!
b.如果你的Linux發行版是Debian Linux,那麼不要使用gcc 2.95或是gcc 3.0,請使用gcc 3.2+
c.gcc的版本要在2.96或以上
d.如果SkyEye的版本大於0.6.0,那麼使用LCD仿真需要在Linux系統裡安裝GTK軟件。


4、安裝arm-elf交叉編譯器

下載arm-elf-tools-20030314.sh
ftp://166.111.68.183/pub/embed/uclinux/soft/tools/arm
或到
ftp://166.111.8.229/OS/Embeded

執行:
chmod a+x arm-elf-tools-20030314.sh
然後:
./arm-elf-tools-20030314.sh

ls /usr/local/bin/

你應能看到以arm-elf開頭的可執行文件,其中arm-elf-gcc就是用來編譯你目標平台的編譯器的,當然還有一些小工具,後面將一一講來。


5、測試你的arm-elf-gcc編譯器

先寫一個小程序hello.c


#include

int main(void)
{
int i;

for(i = 0; i < 6; i++){

printf("i = %d ",i);

printf("Hello, embedded linux!\n");
}

return 0;
}


然後執行:

arm-elf-gcc -Wl,-elf2flt -o hello hello.c

-elf2flt參數是將elf文件格式轉為flat文件格式,這個工具是在你安裝交叉編譯器產生的。

或者你可以寫個Makefile文件,執行:

make

這裡是我的Makefile文件,僅供參考:

# begin

CC = arm-elf-gcc

CFLAGS = -D__PIC__ -fpic -msingle-pic-base -O2 -pipe -Wall -g
LDFLAGS = -Wl,-elf2flt

LIBS =
OBJS = hello.o

all: hello

hello: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o hello $(OBJS) $(LIBS)

clean:
rm -rf *.o *.elf *.gdb hello

# end


如果編譯通過,就會產生hello可執行文件。用下面的命令:

file hello

你會發現,它是BFLT(binary FLAT),你目標平台所支持的文件格式。


6、執行你的hello程序

這裡,我們將借助genromfs這個小工具來完成測試,這個工具就是你在安裝交叉編譯器時產生的,你可以直接使用它。

到http://gro.clinux.org/projects/skye...0.4.tar.bz2包:

tar jxvf skyeye-binary-testutils-1.0.4.tar.bz2

cd testsuits/at91/uclinux2(當然你還可以用別的)

mkdir romfs(建一個目錄,後面用)

mount -o loop boot.rom /mnt/xxx

cp -r /mnt/xxx/* romfs

另外,把你編譯好的可執行程序拷貝到/romfs/bin目錄裡,這裡就是hello了!

genromfs -f boot.rom -d romfs/

註:可以用genromf -h來獲得幫助!

OK!執行下面的命令:

skyeye linux

(skyeye)target sim

(skyeye)load

(skyeye)run

kernel start.....

很熟悉了吧。。。

cd /bin

hello

可以看到結果了嗎?

其實到了這一步,你就可以開發自己的程序了!


7、編譯並運行uClinux-dist-20030909.tar.gz

到ftp://166.111.68.183/pub/embed/uclinux/soft/
或到ftp://166.111.8.229/OS/Embeded/uclinux/pub/uClinux/dist下載
uClinux-dist-20030909.tar.gz

假設把它下載到/usr/src/目錄下,然後依次執行下面的命令:

tar zxvf uClinux-dist-20030909.tar.gz
cd uClinux-dist/

在圖形方式下可用命令make xconfig

在命令行方式下用命令make menuconfig

vendor/product中選擇GDB/ARMulator
kernel版本選擇2.4
然後save and exit

運行下面這兩條命:
make dep
make

此時在/usr/src/uClinux-dist/linux-2.4.x目錄下會生成可執行文件linux
在/usr/src/uClinux-dist/images/會生成romfs.img等文件

在uClinux-dist目錄下建立仿真AT91的skyeye配置文件skyeye.conf,內容如下:
cpu: arm7tdmi
mach: at91
mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000
mem_bank: map=M, type=RW, addr=0x01000000, size=0x00400000
mem_bank: map=M, type=R, addr=0x01400000, size=0x00400000, file=images/romfs.img
mem_bank: map=M, type=RW, addr=0x02000000, size=0x00400000
mem_bank: map=M, type=RW, addr=0x02400000, size=0x00008000
mem_bank: map=M, type=RW, addr=0x04000000, size=0x00400000
mem_bank: map=I, type=RW, addr=0xf0000000, size=0x10000000


這個時候就可以用skyeye來調試運行kernel了,在/usr/src/uClinux-dist執行如下命令:

skyeye linux-2.4.x/linux

(skyeye)target sim

(skyeye)load

(skyeye)run

kernel start.....

注意:
要在skyeye.conf所在目錄下執行skyeye linux-2.4.x/linux


8、加入網絡功能
a.用root用戶進行操作。
b.你要看你的/lib/modules/'uname -r'/kernel/drivers/net/目錄裡有沒有tun.o
如果沒有的話你就需要編譯你的linux內核來獲得tun.o了。
c.(1)運行tun設備模塊:

#insmod /lib/modules/'uname -r'/kernel/drivers/net/tun.o

如果你沒有該設備,那你就要用下面的命令來創建它:

#mkdir /dev/net
#mknod /dev/net/tun c 10 200

(2)運行vnet(虛擬集線器)設備模塊(這一步不是必需的):
獲取vnet的源碼,然後創建設備:

#mknod /dev/net/vnet c 10 201
#chmod 666 /dev/net/vnet

創建vnet.o
#make vnet.o

插入模塊vnet.o
#insmod vnet.o

進入test目錄,用test來測度vnet.o
#cd test
#make
#./testvnet1

d.配置skyeye.conf文件

cpu: arm7tdmi
mach: at91
mem_bank: map=M, type=RW, addr=0x00000000, size=0x00004000
mem_bank: map=M, type=RW, addr=0x01000000, size=0x00400000
mem_bank: map=M, type=R, addr=0x01400000, size=0x00400000, file=images/romfs.img
mem_bank: map=M, type=RW, addr=0x02000000, size=0x00400000
mem_bank: map=M, type=RW, addr=0x02400000, size=0x00008000
mem_bank: map=M, type=RW, addr=0x04000000, size=0x00400000
mem_bank: map=I, type=RW, addr=0xf0000000, size=0x10000000
# format: state=on/off mac=xx:xx:xx:xx:xx:xx ethmod=tuntap/vnet hostip=dd.dd.dd.dd
net: state=on, mac=0:4:3:2:1:f, ethmod=tun, hostip=10.0.0.1


下面將對上面的一些參數作下說明:
state=on/off意思是仿真的NIC(網絡接口板)是有線的還是無線的;
mac=仿真適配器的MAC地址;
ethmod=tuntap/vnet在主機環境裡使用的虛擬設備;
hostip=意思是主機環境與keyeye交互用的IP
格式: state=on/off mac=xx:xx:xx:xx:xx:xx ethmod=tuntap/vnet hostip=dd.dd.dd.dd

For example:
#set nic info state=on/off mac=xx:xx:xx:xx:xx:xx ethmod=tuntap/vnet hostip=dd.dd.dd.dd
net: state=on, mac=0:4:3:2:1:f, ethmod=tun, hostip=10.0.0.1

net: state=on, mac=0:4:3:2:1:f, ethmod=vnet, hostip=10.0.0.1

注意:
如果你想在同一時刻運行兩個或更多的skyeye,那麼請為每一個skyeye使用不同的skyeye.conf

e.運行skyeye linux-2.4.x/linux

9、安裝完成SkyEye後,下一步將做什麼?

1、對於嵌入式操作系統的初學者和入門者和入門的學生而言,他們可以先看一些有關操作系統和嵌入式操作系統方面的教材和書籍,如與uC/OS、Minix、uClinux、Linux相關的書籍等。然後可以在Skyeye上開發一些簡單的應用程序例子(如進程間通信、進程優先級、死鎖情況、網絡應用等),對某些操作系統功能(如進程調度、內存管理、網絡子系統、文件子系統等)進行簡單的修改和擴展,並通過Skyeye進行運行和調試,看看會發生什麼情況。

2、對於有一定經驗的軟件工程師而言,在SkyEye上完成一定的應用系統原型開發是值得一做的事情。比如移植或開發一個文件子系統或網絡子系統到一個特定的操作系統中,相信比在一個真實的開發板上開發要容易一些。在Skyeye上進行一些操作系統的移植和開發(如移植RTLinux、RTAI等其它操作系統到Skyeye上)也是很有挑戰性的工作。

3、對於硬件工程師而言,對Skyeye進行擴充,設計新的硬件仿真(如USB、IDE硬盤等)使得Skyeye的硬件仿真功能更加強大,支持更多功能的軟件,是很有意義的事情。

參考:
SkyEye項目站點裡的一篇中文文檔;
陳渝《SkyEye Project FAQ》;
skyeye-0.7.0中的README文檔。

後記:
為了讓大家能快速上手,進行實際的開發工作,我趕湊了一篇文檔,很粗糙。但我堅信隨著更多的有經驗的人的加入;隨著我們自己水平的提高,一定會出現更多、更好的文章來。就讓我們快點行動起來吧!

最後,我再次建議大家看一下《嵌入式Linux技術與應用》這本書。

可以到http://www.skyeye.org/document.htm或是
ftp://166.111.68.183/pub/embed/skyeye/document/或是
http://www.huihoo.org/mirrors/skyeye/
下載文檔,可以獲得更多有關skyeye和嵌入式Linux開發的知識和經驗。

[技術] 一步一步教你在skyeye上運行uboot

Written on 9:00 上午 by Yu Lai

[轉自Experiences of Code (http://blog.csdn.net/zblue78)]

一步一步教你在skyeye上運行uboot
by faif

1. 簡介

skyeye是一個很好的,基於各種ARM系列CPU的,SOC和主板級的模擬器。uboot是一個可以在各種cpu(arm,mips,powerpc)的主板上運行的引導程序,相當於PC機的BIOS但是又遠遠的強於普通的BIOS,比如支持網絡引導,引導各種內核,甚至一個簡單的shell,等等。他們兩個都是基於GPL的開源自由軟件。

這篇文章教你怎樣在最少量的修改代碼的情況下,用skyeye模擬EP7312並在上面運行uboot,給接觸嵌入系統的新手一個感性的認識。

2. 建立開發環境

2.1 skyeye模擬器的安裝

開發環境是建立在Linux上的。首先下載安裝skyeyes-0.8.5.1的源代碼包,解壓,按照裡面的readme安裝,注意你的linux要有gtk的支持。安裝的時候要以root的身份。在各種linux發行版下的安裝注意事項參照論壇的相關帖子。安裝成功以後,把skyeye的目標目錄加入你的路徑,這樣你就可以在任何目錄下執行skyeye模擬器了。

2.2 交叉編譯器的安裝

交叉編譯器是運行在主機上編譯另外一種體系結構的編譯器。比如,我的主機是linux在x86上,我現在要編譯基於ARM的代碼,所以我就不能用普通的編譯器而需要交叉編譯器。我曾經試過自己從gcc的源代碼構建交叉編譯器,很麻煩和耗時。uboot的作者同樣也開發了一個很好的交叉編譯器叫ELDK(Embedded linux development kit)。我就使用這個,當然你也可以使用其他嵌入式公司提供的。你可以從以下的網址查看提供ELDK的鏡像:

ELDK Availability: http://www.denx.de/twiki/bin/view/DULG/ELDKAvailability

ELDK有三個版本分別編譯MIPS,PPC和ARM。我從下面的鏡像下載了基於ARM的交叉編譯器:

http://sunsite.utk.edu/ftp/pub/linux/eldk/3.1/arm-linux-x86/iso/

文件為"arm-2004-11-09.iso",它支持ARM7, ARM9, XScale, AT91RM9200 and other ARM based systems。

安裝交叉編譯器,我將交叉編譯器安裝到自己的目錄「/opt/x86-arm/」裡面:
/mnt/cdrom/install -d /opt/x86-arm/

等待安裝結束以後設置好用戶環境:


export PATH="${PATH}:/opt/x86-arm/usr/bin:/opt/x86-arm/bin"
export CROSS_COMPILE=arm-linux-

這樣你在任何目錄也可以訪問交叉編譯器了。

測試:

arm-linux-gcc -o testarm test.c
file testarm
testarm: ELF 32-bit LSB executable, ARM, version 1 (ARM), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped.

說明你編譯好的文件是ARM上的代碼了。你可以用arm-linux-gcc來編譯你的文件了。

3. 修改Uboot

從uboot的網站上可以下載到最新的uboot源代碼,你可以從以下的網址下載

http://u-boot.sourceforge.net/
ftp://ftp.denx.de/pub/u-boot/

uboot的源碼結構清晰,註釋詳細,是學習嵌入系統的很好的例子。我下載的是最新的U-Boot-1.1.2。因為我們要模擬EP7312的芯片,而uboot已經支持一個基於EP7312的板子了,所以我們只要對uboot裡面有關EP7312的板子的配置略作修改就可以了。uboot裡面有關主板的配置文件都在"include/configs/.h"下,所以我們找到include/configs/ep7312.h,對它進行修改。

找到
#define CONFIG_DRIVER_CS8900 1
改為
#define CONFIG_DRIVER_CS8900 0

找到
#define CONFIG_COMMANDS (CONFIG_CMD_DFL | CFG_CMD_JFFS2)
改為
#define CONFIG_COMMANDS (CONFIG_CMD_DFL) /*Skyeye doesn't have jffs2*/


然後回到uboot的根目錄下,配置,編譯:

make ep7312_config
make all

等待結束以後我們會發現u-boot.bin和u-boot兩個文件,其中u-boot.bin是raw的二進制文件。u-boot是ELF格式的。

4. 配置skyeye,並運行uboot

首先,新建一個目錄代表你的EP7312的主板。這樣也可以保持文件的清潔和有序。

mkdir board01

將你剛才編譯成功的u-boot.bin拷貝到這個目錄下來。skyeye支持raw binary和ELF的格式,這裡我們用raw binary的格式。

編輯skyeye.conf,這個文件是用來配置主板的,詳細說明見skyeye的相關文檔。我的skyeye.conf如下:

#skyeye config file for uboot
cpu: arm720t
mach: ep7312

mem_bank: map=I, type=RW, addr=0x80000000, size=0x00010000

#skyeye for uboot flash 16M bank 1
mem_bank: map=M, type=RW, addr=0x00000000, size=0x01000000, file=./u-boot.bin,boot=yes

#skyeye for uboot sdram 16m bank 1
mem_bank: map=M, type=RW, addr=0xc0000000, size=0x01000000

注意這裡的內存的地址和容量的分配都是根據uboot裡面的ep7312的配置文件調整的。這樣也可以是我們對uboot的代碼修改做到最小。

這時候你的skyeye-ep7312主板就配置好了。你可以試著運行了。在你現在的目錄下打入:
skyeye

然後在skyeye的界面下打入:

target sim
run

這時候你可以看到uboot的啟動界面,和提示符,如果你鍵入「hlep」,可以查看所有uboot支持的命令,鍵入「version」可以查看當前uboot的版本,等等。

5. 進一步的工作

由於本文是最基本的介紹性的一步一步的指導。有很多工作還要去嘗試。比如:

× 由於現在skyeye還不支持flash內存,所以我們是不是可以修改uboot上ep7312相對flash的代碼來臨時滿足我們的需要,不然的話,對於在uboot上面的環境參數的設置,我們只能去修改源碼裡面的缺省參數。

× Uboot支持8019AS的以太網控制器,skyeye也支持了這個硬件的模擬,我們要進一步的使uboot的網絡也在skyeye上用起來?

× 對於模擬flash的開發也可以用uboot來測試。uboot裡面各種板子有大量的flash驅動程序。

還望各位高手指教,我進一步修改和提高。

[技術] 學習嵌入式Linux的筆記和體會

Written on 8:53 上午 by Yu Lai

[轉自Experiences of Code (http://blog.csdn.net/zblue78)]

為大家學習的方便,我把這篇文章從基礎版貼過來了。裡面的東西滿有趣的,大家可以試試!


以下是我初次學習嵌入式linux的筆記和體會,製作了軟盤minicom應用,在此基礎上也清楚了軟盤Linux的實現,並利用busybox實際製作了軟盤上的Linux系統。希望能給新手一些幫助和啟發,同時也請高手批評指正。
yihui, eazi@163.com 2004年2月12日

一個典型的桌面Linux系統包括3個主要的軟件層---linux內核、C庫和應用程序代碼。
內核是唯一可以完全控制硬件的層,內核驅動程序代表應用程序與硬件之間進行會話。內核之上是C庫,負責把POSIX API轉換為內核可以識別的形式,然後調用內核,從應用程序向內核傳遞參數。應用程序依靠驅動內核來完成特定的任務。
在設計嵌入式應用的時候,可以不按照這種層次,應用程序越過C庫直接和內核會話,或者把應用和內核捆綁在一起,甚至可以把應用寫為內核的一個線程,在內核中運行,雖然這樣在移植上帶來了困難,但考慮嵌入式系統對尺寸要求小的特點,是完全可行的。不過我們使用三層軟件結構的模式來學習嵌入式linux將會是我們認識更清晰,簡單可行並使應用具有彈性。

快速入門,最簡單的建立嵌入式Linux應用的方法就是從我們使用的桌面Linux入手,安裝一個喜愛的版本,把我們的某個應用作為初始化的一部分,框架就算完成了。當然,嵌入式linux應用遠比我們的桌面版本功能簡單專一,它也許就是一個用於足彩的終端機,或是一個數碼音頻播放器,這些系統除了使用嵌入式CPU外,僅僅再需要一個串口,網口等少量的輸入輸出接口就可以完成它們特定的應用了。在軟件上,它可以按照三層的概念由內核裝載器,定制的內核和較少的為特定任務設計的靜態連接的應用程序組成。之所以使用靜態連接的應用程序,是因為少量的靜態連接程序所要的存儲空間,比同樣數量的動態連接的程序所佔的空間小,這個平衡點需要我們在實際開發中去獲取。也許你正在設計的是個PDA,它的應用程序較多,那麼你很可能就要使用動態連接程序來減少存儲空間。在你的/bin或者/sbin目錄下,用廠列表看看bash,ifconfig,vi...,也許只用幾十K,當你運行 ldd /bin/bash 時,你會看到它們都和好幾個庫文件相連。好了,這樣看來,我們得把PC想像成一個嵌入式硬件平台,再重新製作一個特定功能的嵌入式linux。

再進行實際操作之前,先來搞清楚幾個基礎知識。
內核裝載器Loader,它的作用是把內核從外部存儲器,移動到內存中。它只作這個事情,一旦完成了調入內核的工作,Loader就跳轉到內核位置開始執行。不同架構有不同的Loader,在x86結構的PC上,通常使用的loader有LILO,GRUB,syslinux,syslinux在嵌入式linux中也同樣工作。其他非x86架構的應用中,你必須使用專門的loader,或者自己編寫loader來裝入內核。也有不使用loader的情況,系統加電以後,內核直接從燒錄有映像的Flash上開始執行。
內核,一旦內核開始執行,它將通過驅動程序初始化所有硬件,這可以從我們的pc機監視器的輸出看出來,每個驅動程序都打印一些有關它的信息。初始化完成後,計算機就準備運行嵌入式應用。也許一個,也許是多個應用程序組成了嵌入式應用,但通常首先調用的是init(通過loader 向核心傳入init=/program 可以定制首先運行的程序)。桌面linux中,init會讀取/etc/inittab文件,來決定執行級別和哪些腳本和命令。嵌入式應用中,可以根據實際的情況決定是否使用標準的init執行方式,也許這個init是個靜態程序,它能夠完成我們的嵌入應用的特定任務,那完全不用考慮inittab了。
initrd文件系統,initrd以一種把內核從存儲介質裝入到內存的相同的機制來裝入一個小型文件系統。這個文件系統最好是以壓縮的方式存儲在介質上的,解壓縮到RAM盤上。通過使用initrd,包含有核心驅動和啟動腳本的小文件系統,就可以直接從介質上和內核一起啟動起來,內核屆壓縮這個文件系統,並執行這個文件系統上叫做/linuxrc的腳本文件,這個腳本通常會把啟動過程中所需要的驅動程序裝入。腳本退出以後,initrd文件系統也卸下了,啟動過程進入真正初始化過程。對於嵌入式來講,可以將需要的應用軟件都運行在這個initrd文件系統上,只要/linxrc文件不結束,內核啟動過程的其他部分就不會繼續。
做個試驗:


cp /boot/initrd-2.4.20.img /tmp
cd /tmp
mv initrd-2.4.2-.img initrd.img.gz
gunzip initrd.img.gz
mount -o loop initrd.img /mnt
cd /mnt
ls
cat linuxrc
可以看到裡面執行了加載了兩個模塊的操作,你在啟動linxu的時候會看見屏幕打印信息。

入門試驗,製作一個簡單的應用:我們使用一張軟盤啟動一台假象的只有一個串口,鍵盤輸入,顯示輸出的x86架構的linux系統,執行的特定應用就是運行minicom,通過串口撥號。需要軟件: minicom-xx.src.tar.gz 和 syslinux-xx.tar.gz,xx代表版本號 ,開始之前,在主目錄建立一個目錄,來釋放這兩個軟件包:

cd
mkdir -p project/minilinux
cd project/minilinux
tar zxvf minicom-xx.src.tar.gz
tar zxvf syslinux-xx.tar.gz


1、裁減linux內核(需要系統安裝內核文件包)

配置內核的時候,我們需要選擇這些:摸塊編入內核,386處理器、物理內存off、支持ELF、標準PC軟盤、支持RAM盤(4096)、支持initial RAM disk (initrd)、虛你終端、虛擬終端控制台、標準串口、ext2文件系統、控制台驅動,VGA text console、DOS FAT、MSDOS文件系統,其他的都可以不要,這樣內核編出來較小。
步驟:

cd /usr/src/linux
make mrproper
make xconfig
make dep && make bzImage

得到 /usr/src/linux/arch/i386/boot/目錄的內核文件bzIamge。

2、編譯一個靜態的minicom ,把它作為將來的linuxrc

cd minicom-xx/src
vi Makefile
修改下面這行
minicom: $(minicom_OBJECTS) $(minicom_DEPENDENCIES)
rm -f minicom 下面的行加上 -static,連接為靜態程序
(LINK) -static $(minicom_LDFLAGS) $(minicom_OBJECTS) $(minicom_LDADD) $(LIBS)

vi minicom.c
找到 if (real_uid==0 && dosetup==0 ) 刪除這個判斷條件語句,主要是用於權限判斷的,因為這個嵌入應用不關注權限問題,否則會出錯。
make
得到可執行程序,用ldd 檢查一下是不是靜態程序。


3、準備initrd壓縮文件image.gz

dd if=/dev/zero of=image bs=1k count=4096
losetup /dev/loop0 image
mke2fs -m 0 /dev/loop0
mounmt -t ext2 /dev/loop0 /mnt/
mkdir -p /mnt/dev
mkdir -p /mnt/usr/share/terminfo/l/
cd /dev
cp -a consle null tty tty0 zero mem /mnt/dev
cp -P /usr/share/terminfo/l/linux /mnt/usr/share/terminfo/l/linux
cp ~/project/minilinux/mincom/src/minicom /mnt/linuxrc
umount /mnt
losetup -d /dev/loop0
sync
gzip -9 image


4、製作軟盤引導,並拷貝文件 bzimage image.gz 到軟盤

A.使用grub

fdformat /dev/fd0
mke2fs /dev/fd0
mount /mnt/fd0 /mnt/floppy
mkdir -p /mnt/floppy/boot/grub
cp /boot/grub/stage1 /boot/grub/stage2 /mnt/floppy/boot/grub
執行 grub,在軟盤上創建引導
grub > root (fd0)
grub > setup (fd0)
grub > quit

cp /usr/src/linux/arch/i386/boot/bzImge /mnt/floppy
cp ~/porject/minilinux/image.gz /mnt/floppy

編輯 /mnt/floppy/boot/grub/grub.conf
default =0
timeout-=10
title minilinux
root (fd0)
kernel /bzImage
initrd /image.gz

卸下軟盤
umount /mnt/floppy



B. 使用syslinux

fdformat /dev/fd0
mkfs.msdos /dev/fd0
mount -t msdos /dev/fd0 /mnt/floppy

cp /usr/src/linux/arch/i386/boot/bzImge /mnt/floppy
cp ~/porject/minilinux/image.gz /mnt/floppy

cp syslinux-xx/ldlinxu.sys /mnt/floppy
cat > /mnt/floppy/syslinux.cfg
LABEL linux
KERNEL bzimage
APPEND initrd=image.gz

umont /mnt/floppy
syslinux-xx/syslinux /dev/fd0
sync


5、用軟盤啟動計算機,如果幸運,minicom的運行畫面出現在屏幕上。

到此,我們的單應用嵌入式linux做好了,但它還很簡陋,沒有什麼實際用途,但通過這個實驗,可以瞭解嵌入式系統的大致結構和開發過程。在進行實際的嵌入式開發時,通常要在PC機上借助嵌入式linux開發工具包,如:uclinux,bluecat等,對相應的硬件平台(目標機)進行軟件編寫編譯,調試成功後,將內核及應用程序寫入到目標機的存儲器中,從而完成整個應用。