Sunday, April 24, 2011

Linux 內核編譯說明

Linux 內核編譯說明

(http://blog.roodo.com/eshingx/archives/1731986.html)


[內容為個人存檔參考用]

1.Linux內核源碼結構: 
內核源碼中主要包含以下子目錄:
    arch
:包含了與體系結構相關的代碼
對應於每一個支援的體系結構,有一個相應的子目錄如i386armalpha等。
其每個體系結構子目錄下包含幾個主要的子目錄:
    kernel
:包含與體系結構相關的內核代碼
    mm  包含與體系結構相關的記憶體管理代碼
    lib  包含與體系結構相關的庫代碼
    documentation:包含內核的文檔
    drivers :包含設備驅動代碼。每類設備有相應的子目錄,如char blocknet
    fs  包含檔系統的代碼。每個支援的檔系統有相應的子目錄,如 ext2proc
    include :內核頭檔,對每一種體系結構,分別有相應的子目錄。
    init  包含內核初始化代碼
    lib  包含內核的庫代碼
    mm :包含記憶體管理代碼
    kernel :包含內核管理代碼
    net :包含網路部分的代碼 

2.系統引導的過程 
pc機上系統啟動過程:
    系統加電以後bois對系統完成監測設置後將控制權交給硬碟上MBR中的 BootLoader在這裏即是lilogrub等。
    BootLoader 將作業系統代碼調入記憶體,然後將控制權交給arch/i386/boot中的Setup.S這段程式。
    Setup.S 這段程式在386實模式下對系統進行基本的檢測和設置後轉入保護模式把控制權交給Head.S     Head.S 建立記憶體管理和中斷管理的框架後調用init/main.c中的start_kernel()函數在start_kernel執行完成後用戶就可以登錄和使用linux了。Start_kernel()函數在init /main.c 中定義。
 
Start_kernel
的流程中的主要步驟:
    setup_arch(&command_line);
用於和處理器、記憶體等最基本的硬體相關部分的初始化。  arch/i386/kernel/setup.c 中定義;
    parse_options(command_line); 把啟動時得到的參數從命令行的字串中分離出來並賦給相應的變數。在 init/main.c 中定義;
    trap_init(); 對中斷向量表進行初始化。在 arch/i386/kernel/trap.c 中定義;
    init_IRQ(); 與中斷有關的初始化,  arch/i386/kernel/i8259.c 中定義;
    sched_init(); 進程調度初始化。  kernel/sched.c 中定義;
    softirq_init(); kernel/softirq.c 中定義;
    time_init(); 時間部分初始化。  arch/i386/kernel/time.c 中定義;
    console_init(); 對終端初始化。  drivers/char/tty_io.c 中定義;
    buffer_init(mempages); 對用於指示塊緩存的buff free list 初始化。 fs/buffer.c 中定義;
    mem_init(); 記憶體管理初始化。  arch/i386/mm/init.c中定義;
    rest_init(); 此函數中調用
    kernel_thread(init,NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函數時會調用init/main.c中的init()函數 init()函數中將會建立dbflushkswapd兩個新的內核線程。初始化tty1設備。尋找/etc/init/sbin/init /bin/init來建立一個init進程。
Init
進程根據/etc/inittab檔進行檔系統檢查、啟動系統守護進程為聯機終端建立getty進程,執行/etc/rc下的命令檔。
此後getty會在終端上顯示login提示符,以等待用戶登錄。

3.
使用make建立內核
    1.使用make menuconfig命令:
使用以下編譯選項:
    Processor type and features --->     (Pentium-Pro/Celeron/Pentium-II) Processor family     (3GB) Maximum Virtual Memory     General setup --->     (ELF) Kernel core (/proc/kcore) format     [*] Kernel support for ELF binaries     File systems --->     [*] /proc file system support     [*] Second extended fs support     ATA/IDE/MFM/RLL support --->     [*] ATA/IDE/MFM/RLL support     [*] Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support     [*] Include IDE/ATA-2 DISK support     Character devices --->     [*] Virtual terminal     Console drivers --->     [*] VGA text console
生成內核bzImage 大小為 309892 byte; 此內核可在pc上成功引導系統
此命令生成一個檔 .config 其中根據你在menuconfig中的選擇定義了相應的變數。在Makefile檔中將會包含這個檔。

2.
使用make dep命令 建立依賴關係。

3.
使用make bzImage 命令建立內核。
    如設置正確將在arch/i386/boot/目錄下生成內核bzImage

4.make bzImage
的流程簡單說明
    當我們使用make命令時,make程式將首先找到當前目錄下的Makefile檔。根據Makefile檔的語法進行處理。
    在主Makefile檔中包含了arch/i386/Makefile我們make的目標bzImage即在該檔中定:
    bzImage: vmlinux     @$(MAKEBOOT) bzImage #此命令將解釋為:make -C arch/i386/boot bzImage 現在make需要先去建立目標 vmlinux 然後再執行 arch/i386/boot/ 目錄下的 make bzImae。我們現在假設vmlinux目標已生成,則 arch/i386/boot目錄下的make程式將執行如下操作:
    tools/build -b bbootsect bsetup compressed/bvmlinux.out ./ bzImage 即將vmlinux tools/build工具壓縮成目標檔 bzImage(在此過程中,還會構建build 程式,將vmlinux轉換成bvmlinux.out等,可參考tools compressed目錄下的Makefile檔)。
生成vmlinux目標:
在主目錄下Makefile檔中vmlinux生成規則如下:
    vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs     $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o \     --start-group \     $(CORE_FILES) \     $(DRIVERS) \     $(NETWORKS) \     $(LIBS) \     --end-group \     -o vmlinux #生成vmlinux     $(NM) vmlinux |grep –v \(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)    \|\(LASH[RL]DI\) | sort > System.map #此命令根據vmlinux生成System.map文件
在當前設置下此ld連接命令被解釋為:
    ld -m elf_i386 -T /home/arm/linux/arch/i386/vmlinux.lds -e stext arch/i386/kernel/head.o arch/i386/kernel/init_task.o init/main.o init/version.o --start-group     arch/i386/kernel/kernel.o arch/i386/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o drivers/char/char.o drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/media/media.o drivers/ide/idedriver.o drivers/video/video.o net/network.o /home/arm/linux/arch/i386/lib/lib.a /home/arm/linux/lib/lib.a /home/arm/linux/arch/i386/lib/lib.a --end-group -o vmlinux
即連接程式ld 將各個.o檔連接成目標檔 vmlinux
此命令中用到的各個 .omake程式會根據Makefile檔的規則去自動生成,下面簡單介紹一下由ipc目錄生成ipc.o過程:
ipc目錄下Makefile檔內容如下:
    O_TARGET := ipc.o     obj-y := util.o     obj-$(CONFIG_SYSVIPC) += msg.o sem.o shm.o     include $(TOPDIR)/Rules.make #包含的Rules.make檔中為通用的規則;
如我們在make menuconfig時選中了SYSVIPC選項,則 .config檔中將定義變數 CONFIG_SYSVIPC=y; objy 就等於util.o msg.o sem.o shm.o;根據Rules.make將四個.c檔編譯為 .o文件。再將四個.o檔連接成 ipc.o文件。在我們的當前設置中沒有選擇SYSVIPC;將只使用util.o一個檔生成目標 ipc.o ;而util.outil.c生成。
    當所有需要的.o檔生成以後,由ld將其連接生成vmlinux檔,再將其壓縮成我們所需要的內核檔 bzImageMake程式就執行完了。

initrd.img, vmlinux, zImage, Image 的區別?

vmlinux 

(http://zh.wikipedia.org/wiki/Vmlinux)

  在linux系統中,vmlinux(vmlinuz)是一個包含linux kernel的靜態連結的執行檔,檔案型態可能是linux接受的執行檔格式之一(ELFCOFFa.out),vmlinux若要用於除錯時則必須要在開機前增加symbol table。

位置

  一般來說,核心的位置會在檔案系統的 root 目錄下,然而當 bootloader 必須使用 BIOS 的硬碟驅動程式,在一些i386的機器上必須要放在前 1024 個磁柱內。
  為了克服這個限制,Linux 發行版鼓勵使用者建立一個磁區用來存放 bootloader 與核心相關的開機檔案,例如 GRUB, LILO 與 SYSLINUX ... 。而這個磁區通常會掛載到系統的/boot 上,這是 FHS (Filesystem Hierarchy Standard) 標準內定義的。

Compression

  一般來說,當建立一個可啟動的核心時,此核心會先經過 zlib 演算法壓縮,而在核心內會包含一個相當小的解壓縮程式 stub,當 stub 解壓縮核心程式的時候會對 console 視窗印出"點"來表示解壓縮進度。
  解壓縮所花費的時間在開機時間中所佔程度來說其實是相當小的,而在早期的 bzImage 的發展中對於核心的大小會有所限制(特別是 i386 架構),在此情況下壓縮則是必須的。
  開機映像檔的名稱並不重要,通常習慣上稱為 vmlinuz 或 zImage。

bzImage

  隨著 linux Kernel 的成長,核心的內容日益增加超越了原本的限制大小。bzImage (big zImage) 格式則為了克服此缺點開始發展,利用將核心切割成不連續的記憶體區塊來克服大小限制。
  bzImage 格式仍然是以 zlib 演算法來做壓縮,雖然有一些廣泛的誤解就是因為以 bz- 為開頭,而讓人誤以為是使用 bzip2 壓縮方式(bzip2 套件所帶的工具程式通常是以 bz- 為開頭的,例如 bzless, bzcat ...)。
  bzImage 檔案是一個特殊的格式,包含了 bootsect.o + setup.o + misc.o + piggy.o 串接。piggy.o 包含了一個 gzip 格式的 vmlinux 檔案(可以參看 arch/i386/boot/下的 compressed/Makefile piggy.o)


initrd.img 和vmlinux的作用分别是什么?
(http://hi.baidu.com/ueszx/blog/item/c91929b7bc3feff230add124.html)


    编译安装完内核后在/boot下没有initrd.img....
    vmlinuz自然就是内核了,initrd.img是一个小的映象,包含一个最小的linux系统。通常的步骤是先启动内核,然后内核挂载 initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init... ...。
    initrd.img当然是可选的了,如果没有initrd.img,内核就试图直接挂载root分区。之所以要有initrd,那是为了启动的时候有更大的灵活性。比如,你把ext3支持编译成模块了。偏偏你的root分区又是ext3的。这下就麻烦了。 因为内核需要挂载root分区之后才能加载ext3支持。但是没有ext3支持就没法挂载root分区。initrd就是用来解决这个问题的。
    类似的用这个东西还可以做其他的事情,比如从usb盘启动linux也会面临上面类似的问题。用initrd就能搞定了。甚至,我想在有些嵌入式设备里面都不需要真正的root分区,用initrd就足够搞定一切了。

    主要是为了解决vmlinuz太大的问题,用initrd可以解决这个问题。否则的话在2.6的内核中启动会失败的。

vmlinux, zImage, Image的区别
(http://hi.baidu.com/ueszx/blog/item/c91929b7bc3feff230add124.html) 

 从下面的内核编译过程,可以看出vmlinux, arch/arm/boot/compressed/vmlinux, arch/arm/boot/Image, arch/arm/boot/zImage 之间的区别
 
  LD      vmlinux
  SYSMAP  System.map
  SYSMAP  .tmp_System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  AS      arch/arm/boot/compressed/head.o
  GZIP    arch/arm/boot/compressed/piggy.gz
  AS      arch/arm/boot/compressed/piggy.o
  CC      arch/arm/boot/compressed/misc.o
  AS      arch/arm/boot/compressed/head-xscale.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage

  vmlinux 是一个elf格式的文件
  Image 是经过objcopy 处理了的只包含内核代码,数据的一个文件, 已经不是elf格式的了。此时还没有经过压缩
  arch/arm/boot/compressed/vmlinux 是经过压缩的Image和加入了解压头的elf格式的文件
  arch/arm/boot/zImage 是经过objcopy处理了的可以直接下到对应的地址执行的内核镜像


 

Tuesday, April 19, 2011

阻斷服務攻擊(Denial of Service)

http://jck11.pixnet.net/blog/post/7470786

DoS(Denial of Service)
係對特定的遠端主機服務,傳送大量或特殊的網路封包,造成遠端主機資源耗盡或服務中止的狀況。

Ping of Death
當OS接受到ICMP的回應請求時(ping),會將ICMP封包內容複製一份再送回給請求端,如果ICMP過大且數量很多時,OS要一一回應ICMP請求,可能會耗盡資源而當機。

Teardrop
資料在網路上傳送會經過不斷的切割,而目的地在收到封包時再予以重組回原來的資料。透過打造小封包,遠端主機在接收後重組大封包時,未能仔細檢查封包內容,而造成重組封包時的嚴重錯誤,最後就可能造成當機。

SYN Flood
TCP的三向交握協定中,遠端主機收到SYN旗標封包時,如果該埠有開放,應回應SYN ACK封包。
如果攻擊者送了大量的SYN旗標的封包給遠端主機,且封包的來源IP是偽造的,
一旦遠端主機收到這類封包,適必會回應SYN ACK封包給假造的來源,並等待來源回送ACK封包,才能完成三向交握, 然而,封包來源為偽造,故不可能收得到ACK封包,
遠端主機因不能分辯封包來源是否為真,會保有一小段的時間來等待ACK封包.
當SYN Flood大量且連續不斷的傳送SYN封包,遠端主機可能因累責過多的未完成三向交握連線, 最後因資源的限制,造成其它正常的封包要求被拒。

Monday, April 11, 2011

cross、u-boot、kernel、ramdisk

(Ref: http://maozheng11.blog.hexun.com/22338272_d.html)
一、构建自己的交叉编译环境
二、u-boot移植
三、编译内核
四、构建文件系统
过程:
一、构建自己的交叉编译环境
    嵌 入式设备由于不具备一定的处理器能力和存储空间,程序开发一般用PC来完成,然后将可执行文件下载到嵌入式系统中运行。这是目前嵌入式程序开发的不二选择 ——Host/target模式。但这引发了一个问题:由于Host和Target的处理器体系结构不同,我们不能直接用PC上既有的程序开发工具,必须 使用跨平台开发工具,即在Host上生成能在Target上运行格式的目标文件。以下我会简单的说一下怎么构建交叉编译环境,当然如果不想麻烦也可以到网 上去下载。

    先下载binutils、gcc、glibc、linux-kernel等包和几个插件,可到 http://weintroub.com/~coldwell/toolchain/处下载,先解压glibc-2.3.5,打开INSTALL文件, 看其中对gcc、awk、sed等软件的要求,此处为:
GNU `make' 3.79 or newer
GCC  3.2 or newer
GNU `binutils' 2.13 or later
GNU `texinfo' 3.12f
GNU `awk' 3.0, or some other POSIX awk
Perl 5
GNU `sed' 3.02 or newer
GNU `autoconf' 2.53 or higher
GNU `gettext' 0.10.36 or later

1、设置环境变量
TARGET=arm-linux
PREFIX=/usr/arm
SYSROOT=${PREFIX}/sysroot
export ARCH=arm
export CROSS_COMPILE=${TARGET}-
export PATH=$PATH:${PREFIX}/bin
2、建立二进制工具
mkdir -p ${PREFIX}/src
cd ${PREFIX}/src
tar zxvf binutils-2.16.tar.gz
mkdir -p BUILD/binutils-2.16
cd BUILD/binutils-2.16
../../binutils-2.16/configure --prefix=${PREFIX} --target=${TARGET} --with-sysroot=${SYSROOT} && make && make install && echo ok (如果最后一行显示ok则表示安装成功)
3、建立内核头文件
cd ${PREFIX}/src
tar zxvf linux-2.6.10.tar.gz
ln -s linux-2.6.10 linux
zcat 2.6.10-at91.patch.gz | patch -d linux -p1
cd linux
make at91rm9200dk_defconfig
make include/linux/version.h
mkdir -p ${SYSROOT}/usr/include
cp -a ${PREFIX}/src/linux/include/linux ${SYSROOT}/usr/include/linux
cp -a ${PREFIX}/src/linux/include/asm-arm ${SYSROOT}/usr/include/asm
cp -a ${PREFIX}/src/linux/include/asm-generic ${SYSROOT}/usr/include/asm-generic
4、安装glibc头文件
cd ${PREFIX}/src
tar zxvf glibc-2.3.5.tar.gz
patch -d glibc-2.3.5 -p1 <ioperm.c.diff
cd glibc-2.3.5
tar xvfz ../glibc-linuxthreads-2.3.5.tar.gz
cd ..
mkdir -p BUILD/glibc-2.3.5-headers
cd BUILD/glibc-2.3.5-headers
../../glibc-2.3.5/configure --prefix=/usr --host=${TARGET} --enable-add-ons=linuxthreads --with-headers=${SYSROOT}/usr/include && make cross-compiling=yes install_root=${SYSROOT} install-headers && echo ok
touch ${SYSROOT}/usr/include/gnu/stubs.h
touch ${SYSROOT}/usr/include/bits/stdio_lim.h
5、建立初始编译器
cd ${PREFIX}/src
tar jxvf gcc-3.4.4.tar.bz2
patch -d gcc-3.4.4 -p1 < flow.c.diff
patch -d gcc-3.4.4 -p1 < t-linux.diff
mkdir -p BUILD/gcc-3.4.4-stage1
cd BUILD/gcc-3.4.4-stage1
../../gcc-3.4.4/configure --prefix=${PREFIX} --target=${TARGET} --enable-languages=c --with-sysroot=${SYSROOT} && make && make install && echo ok
6、建立c库
cd ${PREFIX}/src
mkdir -p BUILD/glibc-2.3.5
cd BUILD/glibc-2.3.5
BUILD_CC=gcc CC=${CROSS_COMPILE}gcc AR=${CROSS_COMPILE}ar RANLIB=${CROSS_COMPILE}ranlib AS=${CROSS_COMPILE}as LD=${CROSS_COMPILE}ld ../../glibc-2.3.5/configure --prefix=/usr --build=i386-redhat-linux --host=arm-linux --target=arm-linux --without-__thread --enable-add-ons=linuxthreads --with-headers=${SYSROOT}/usr/include && make && make install_root=${SYSROOT} install && echo ok
7、建立全套编译器
cd ${PREFIX}/src
mkdir -p BUILD/gcc-3.4.4
cd BUILD/gcc-3.4.4
../../gcc-3.4.4/configure --prefix=${PREFIX} --target=${TARGET} --enable-languages=c,c++ --with-sysroot=${SYSROOT} && make && make install && echo ok
8、测试
1)编写一个HelloWorld程序,然后arm-linux-gcc helloworld.c -o helloworld
2)file helloworld
aa: ELF 32-bit LSB executable, ARM, version 1, for GNU/Linux 2.0.0, dynamically linked (uses shared libs), not stripped
如果显示出上面的输出,说明你编译了一个能在arm体系结构下运行的helloworld,证明你的编译工具做成功了。

二、u-boot移植
BootLoader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状 态,以便为最终调用操作系统内核准备好正确的环境。BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。
由于 BootLoader的实现依赖于CPU的体系结构,因此大多数BootLoader都分为stage1和stage2两大部分。依赖于CPU体系结构的 代码,比如设备初始化代码等,通常都放在stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2则通常用C语言来实现,这样 可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。
BootLoader的stage1通常包括以下步骤(以执行的先后顺序):
1、硬件设备初始化。
2、为加载BootLoader的stage2准备RAM空间。
3、 拷贝BootLoader的stage2到RAM空间中。(注:由于stage2通常是C语言执行代码,因此在考虑空间大小时,除了stage2可执行映 象的大小外,还必须把堆栈空间也考虑进来。一般情况下都是分出1M的RAM给它们,我的习惯是把RAM顶层的1M空间空出来分给它们,即 0x21f00000到0x21ffffff,当然也可以把RAM起始位置的1M空间分给他们)
4、设置好堆栈。
5、跳转到stage2的C入口点。
BootLoader的stage2通常包括以下步骤(以执行的先后顺序):
1、初始化本阶段要使用到的硬件设备。
2、检测系统内存映射(memory map)。
3、将kernel映像和根文件系统映像从flash上读到RAM空间中。
4、为内核设置启动参数。
5、调用内核。

如今能支持RAM cpu的BootLoader有好多个,其中支持类型最多,且最出名的是blob和u-boot,本篇就介绍一下我用u-boot往at91rm9200开发板上移植的过程:
1、cp -R at91rm9200dk myboard
1)cp at91rm9200dk.c myboard.c
2)vi Makefle
将COBJS   := at91rm9200dk.o at45.o flash.o 改为 COBJS   := myboard.o at45.o flash.o
3)添加Nor Flash驱动,芯片型号是MBM29LV320BE(FUJITSU)
vi flash.c
1>由于这个型号的Nor Flash有71个扇区,且前9个是8K,后62个是64K,因此加入
OrgDef OrgMBM29LV320BE[] =
{
    {   8,  8*1024 },   /*   8 *  8 kBytes sectors */
    { 63, 64*1024 },    /* 63 * 64 kBytes sectors */
};
2>加入
#define RESET           0xF0
3>
在函数void flash_identification (flash_info_t * info)中加入:
/* Read Reset  */
    MEM_FLASH_ADDR1 = FLASH_CODE1;
    MEM_FLASH_ADDR2 = FLASH_CODE2;
    MEM_FLASH_ADDR1 = RESET;

/* Go to Autoselect mode */
    MEM_FLASH_ADDR1 = FLASH_CODE1;
    MEM_FLASH_ADDR2 = FLASH_CODE2;
    MEM_FLASH_ADDR1 = ID_IN_CODE;

/* Vendor type */
    info->flash_id = FUJ_MANUFACT & FLASH_VENDMASK;
    printf("FUJ and AMD\n");

/* AMD Flash */
    info->flash_id |= AMD_ID_LV320B & FLASH_TYPEMASK;
    printf("MBM29LV320B (32Mbit)\n");
4>
在函数ulong flash_init (void)中加入:
/* MBM29LV320BE Flash */
        pOrgDef = OrgMBM29LV320BE;
        flash_nb_blocks = sizeof (OrgMBM29LV320BE) / sizeof (OrgDef);
5>
在函数void flash_print_info (flash_info_t * info)中加入:
case (FUJ_MANUFACT & FLASH_VENDMASK):
        printf("FUJ");
        break;

case (AMD_ID_LV320B & FLASH_TYPEMASK):
        printf("MBM29LV320BE (32Mbit\n)");
        break;
6>
在函数int flash_erase (flash_info_t * info, int s_first, int s_last)中加入:
if ((info->flash_id & FLASH_VENDMASK) != (FUJ_MANUFACT & FLASH_VENDMASK))
{
        return ERR_UNKNOWN_FLASH_VENDOR;
}
2、回到u-boot目录下,打开Makefile文件,加入(为了能make myboard_config):
myboard_config  :   unconfig
    @$(MKCONFIG) $(@:_config=) arm arm920t myboard NULL at91rm9200
3、在u-boot目录下,打开MAKEALL文件,在
LIST_ARM9=" \
    at91rm9200dk    cmc_pu2                     \
    ap920t      ap922_XA10  ap926ejs    ap946es     \
    ap966       cp920t      cp922_XA10  cp926ejs    \
    cp946es     cp966       lpd7a400    mp2usb      \
    mx1ads      mx1fs2      netstar     omap1510inn \
    omap1610h2  omap1610inn omap730p2   sbc2410x    \
    scb9328     smdk2400    smdk2410    trab        \
    VCMA9       versatile   versatileab versatilepb \
    voiceblue                            \
"
加入 myboard,即:
LIST_ARM9=" \
    at91rm9200dk    cmc_pu2                     \
    ap920t      ap922_XA10  ap926ejs    ap946es     \
    ap966       cp920t      cp922_XA10  cp926ejs    \
    cp946es     cp966       lpd7a400    mp2usb      \
    mx1ads      mx1fs2      netstar     omap1510inn \
    omap1610h2  omap1610inn omap730p2   sbc2410x    \
    scb9328     smdk2400    smdk2410    trab        \
    VCMA9       versatile   versatileab versatilepb \
    voiceblue   myboard                         \
"
4、cd include/configs,cp at91rm9200dk.h myboard.h,打开myboard.h文件
1)加入#define CONFIG_SKIP_LOWLEVEL_INIT (由于板子会自动进行初始化,所以此处要跳过去,否则会成为死循环)
2)由于我的板子上的Nor Flash是4M,所以把
#define PHYS_FLASH_SIZE  0x200000
改为
#define PHYS_FLASH_SIZE  0x400000
3)由于我的板子上的Nor Flash共71个扇区,所以把
#define CFG_MAX_FLASH_SECT      256
改为
#define CFG_MAX_FLASH_SECT      71
5、
export BUILD_DIR=/home/lb/abc
make distclean
make myboard_config
make all
在/home/lb/abc中会生成三个文件u-boot.bin、u-boot和u-boot.srec,我只需要u-boot.bin文件。

三、编译内核
Bootloader将内核加载到内存中,设定一些寄存器,然后将控制权交由内核,该过程中,关闭MMU功能。通常,内核都是以压缩的方式存放,如zImage,这里有两种解压方法:
1、使用内核自解压程序。
2、在Bootloader中增加解压功能。
使用该方法时内核不需要带有自解压功能,而使用Bootloader中的解压程序代替内核自解压程序。其工作过程与内核自解压过程相似:Bootloader把压缩方式的内核解压到内存中,然后跳转到内核入口处开始执行。

内核有多种启动方式,如下:
XIP(EXECUTE IN PLACE)是指直接从存放代码的位置上启动运行。
1、非压缩,非XIP
非XIP方式是指在运行之前需对代码进行重定位。该类型的内核以非压缩方式存放在Flash中,启动时由Bootloader加载到内存后运行。
2、非压缩,XIP
该 类型的内核以非压缩格式存放在ROM/Flash中,不需要加载到内存就能运行,Bootloader直接跳转到其存放地址执行。Data段复制和BSS 段清零的工作由内核自己完成。这种启动方式常用于内存空间有限的系统中,另外,程序在ROM/Flash中运行的速度相对较慢。
3、RAM自解压
压 缩格式的内核由开头一段自解压代码和压缩内核数据组成,由于以压缩格式存放,内核只能以非XIP方式运行。RAM自解压过程如下:压缩内核存放于 ROM/Flash中,Bootloader启动后加载到内存中的临时空间,然后跳转到压缩内核入口地址执行自解压代码,内核被解压到最终的目的地址然后 运行。压缩内核所占据的临时空间随后被Linux回收利用。这种方式的内核在嵌入式产品中较为常见。
4、ROM自解压
解压缩代码也能够以 XIP的方式在ROM/Flash中运行。ROM自解压过程如下:压缩内核存放在ROM/Flash中,不需要加载到内存就能运行,Bootloader 直接跳转到其存放地址执行其自解压代码,将压缩内核解压到最终的目的地址并运行。ROM自解压方式存放的内核解压缩速度慢,而且也不能节省内存空间。

先下载一个内核原文件和相对应的ARM内核补丁。通常情况下ARM内核不要超过1M,所以对自己没有用的选项都可以不选。
1、
cd linux-2.6.*
zcat ../2.6.*-at91.patch.gz |patch -p1
2、
vi Makefile

ARCH     ?= $(SUBARCH)
CROSS_COMPILE ?=
改为
ARCH            ?= arm
CROSS_COMPILE   ?= 相应交叉编译环境 (我的是arm-linux-)
3、
make at91rm9200dk_defconfig
make menuconfig

(注:配置ARMlinux的方法和配置官方Linux内核差不多,以下是我对内核的配置,可供大家参考:
[System Type] 平台支持
   选择 ARM system type 为 Atmel AT91
   选择  Atmel AT91 System-on-Chip==>Atmel AT91 Processor 为 AT91RM9200
   其它选项都是用默认
[Gernel setup]通用配置
   选择 Sysctl support
   选择 System V IPC
   这两个选项我也没有深入研究,不知不选是不是也可以?其它的都不要选了。
[Memory Technology Devices(MTD)] MTD 配置
   选择 <*>Memory Technology Device(MTD) support 记着选择为“built-in”而不是“module”(前面的选项为“*”而不是“M”)
   选择 <*>Mtd partitioning support MTD 分区支持
   选择 <*>Caching block device access to MTD devices 支持 MTD作为BLOCK设备访问
   进入【RAM/ROM/FLASH chip drivers- 】选项选择加载 FLASH 的驱动
   选择 <*> Detect flash chips by Common Flash Interface (CFI) probe自动探测 CFI 接口的 FLASH 设备
   选择 <*> Support for AMD/Fujistu flash chips 选择对 AMD/Fujistu FLASH 设备的支持
   选择 <*> Support for ROM chips in bus mapping 支持 ROM 设备总线 MAP
   其它选项不选,退回到上一级。
   进入【Mapping drivers for chip access- 】选项
   选择 <*> CFI Flash device mapped on ARM Integrator/P720T 支持 ARM 的 FLASH 分区
   其它选项都不用选中。
[Networking Options] 网络选项
   本选项全部为 built-in 方式。
   选择<*> Packet socket
   选择<*> Kernel/User netlink socket
   选择<*> Unix domain sockets
   选择<*> TCP/IP networking
   选择<*> IP: multicasting 支持组播
   选择<*> IP: kernel level autoconfiguration
   选择<*> IP: DHCP support
   选择<*> IP: BOOTP support
   选择<*> IP: RARP support
   其它的都可以不选。
[File systems] 文件系统
   该目录下包含内核对各种文件系统的支持,可以根据需要选择想要的文件系统。
   选择<*> Second extended fs support
   选择<*> JFS filesystem support
   进入[Miscellaneous filesystems]选项
   选择<*> Jouralling Flash File System V2 (JFFS2) support内核支持 JFFS 和 JFFS2 文件系统
   进入【Network File Systems- 】 网络文件系统子选项
   选择<*> NFS file system support NFS 网络文件系统支持
   选择<*> Provide NFSv3 client support 支持 NFS 版本 3 客户端
   选择<*> Root file system on NFS 根文件系统采用 NFS
   退回上级菜单,其它的都没有选中。
   其它的配置可以根据目标系统的不同灵活配置,像IDE、Sound等和目标系统没关系的选项都可以去掉,因“板”而异吗。
)

make
4、
cp linux2.6.*/vmlinux /home/lb/kernel
cp linux2.6.*/arch/arm/boot/zImage /home/lb/kernel
5、
cd /home/lb/kernel
1)arm-linux-objcopy -O binary -S vmlinux linux.bin
2)gzip -v9 linux.bin
3)/home/lb/abc/tools/mkimage -A arm -O linux -T kernel -C gzip -a 0x20008000 -e 0x20008000 -d linux.bin.gz uImage

(
注:uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置,入口点在内存的那个位置以及映象名是什么。

参数说明:
-A 指定CPU的体系结构:
alpha Alpha
arm ARM
x86 Intel x86
ia64 IA64
mips MIPS
mips64 MIPS 64Bit
ppc PowerPC
s390 IBM S390
sh SuperH
sparc SPARC
sparc64 SPARC 64Bit
m68k MC68000

-O 指定操作系统类型,可以取以下值:
openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos

-T 指定映象类型,可以取以下值:
standalone、kernel、ramdisk、multi、firmware、script、filesystem

-C 指定映象压缩方式,可以取以下值:
none 不压缩
gzip 用gzip的压缩方式
bzip2 用bzip2的压缩方式

-a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
-e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映象名
-d 指定制作映象的源文件
)
得到了内核映射文件uImage

四、构建文件系统
在嵌入式Linux开发中, 往往需要为目标系统设置根文件系统, 这包括: 准备好根文件系统所需的内容, 选择目标系统的文件系统类型, 将根文件系统的内容转换为所用文件系统的格式, 将根文件系统安装到目标系统上。
常用的文件系统有:
1、ramdisk
ramdisk的优点: 读写速度非常快, 适合用来制作initrd。
ramdisk的缺点: 不具有永久性, 断电后无法保存. 且ramdisk大小不可更改, 浪费ram。
2、tmpfs
tmpfs是一个以虚拟内存为基础的文件系统,它的大小不是固定的, 这都是它区别于ramdisk的优点。
tmpfs以虚拟内存为基础, 所以它不同于ramdisk,ramdisk只能位于ram中, 而tmpfs可能位于交换分区中(Linux内核的虚拟内存资源同时来源于RAM和交换分区)。
tmpfs是个文件系统, 而不是块设备,同样和ramdisk比较: ramdisk是个块设备, 需要一个mkfs之类的命令将个做成某个格式的文件系统才能使用,而tmpfs是一个文件系统不是块设备,mount它即可使用。
3、cramfs
1)采用实时解压缩方式,但解压缩的时侯有延迟。
2)cramfs的数据都是经过处理、打包的,对其进行写操作有一定困难。所以cramfs不支持写操作,这个特性刚好适合嵌入式应用中使用Flash存储文件系统的场合。
3)在cramfs中,文件最大不能超过16MB。
4)支持组标识(gid),但是mkcramfs只将gid的低8位保存下来,因此只有这8位是有效的。
5)支持硬链接。但是cramfs并没有完全处理好,硬链接的文件属性中,链接数仍然为1.
6)cramfs的目录中,没有“.”和“..”这两项。因此,cramfs中的目录的链接数通常也仅有一个。
7) cramfs中,不会保存文件的时间戳(timestamps)信息。当然,正在使用的文件由于inode保存在内存中,因此其时间可以暂时地变更为最新时间,但是不会保存到cramfs文件系统中去。
8)当前版本的cramfs只支持PAGE_CACHE_SIZE为4096的内核。因此,如果发现cramfs不能正常读写的时侯,可以检查一下内核的参数设置。

在 嵌入式的环境之下,内存和外存资源都需要节约使用。如果使用ramdisk方式来使用文件系统,那么在系统运行之后,首先要把外存(Flash)上的映像 文件解压缩到内存中,构造起ramdisk环境,才可以开始运行程序。但是它也有很致命的弱点。在正常情况下,同样的代码不仅在外存中占据了空间(以压缩 后的形式存在),而且还在内存中占用了更大的空间(以解压缩之后的形式存在),这违背了嵌入式环境下尽量节省资源的要求。
使用cramfs就是一 种解决这个问题的方式。cramfs是一个压缩式的文件系统,它并不需要一次性地将文件系统中的所有内容都解压缩到内存之中,而只是在系统需要访问某个位 置的数据的时侯,马上计算出该数据在cramfs中的位置,将其实时地解压缩到内存之中,然后通过对内存的访问来获取文件系统中需要读取的数据。 cramfs中的解压缩以及解压缩之后的内存中数据存放位置都是由cramfs文件系统本身进行维护的,用户并不需要了解具体的实现过程,因此这种方式增 强了透明度,对开发人员来说,既方便,又节省了存储空间。

我选择的文件系统是ramdsik:
要先安装mtd(memory technology device),debian中安装mtd-tools,gentoo中安装mtd-utils。
1、建立内存映像文件ramdisk
1)mkdir -p /mnt/loop
2)dd if=/dev/zero of=/tmp/loop_tmp bs=1k count=3072
3)/sbin/losetup /dev/loop0 /tmp/loop_tmp
将设备与临时文件联系起来。如果出现“ioctl: LOOP_SET_FD: 设备或资源忙”的提示,说明设备还和一文件联系,可以用/sbin/losetup /dev/loop0来看,并可用-d来删除。
4)mkfs.ext2 –m0 /dev/loop0
mkfs.ext2将会自动判断设备容量的大小并相应地配置自身,-m0参数防止它给root保留空,这样会腾出更多地有用空间。
5)mount -t ext2 /dev/loop0 /mnt/loop
6)mkdir {bin,sbin,usr,dev,etc,lib,home,opt,root,src,tmp,var,mnt,proc,sys,initrd}
7)umount /mnt/loop
卸载此文件系统,得到的/tmp/loop_tmp就是ramdisk,可以将其改名为ramdisk
8)gzip - v9 /tmp/ramdisk
在/tmp中生成一个ramdisk.gz,这样一个内存映像文件就生成了。
2、编译busybox
1)
make defconfig
make menuconfig
2)busybox设置
1>在Build Options中的Build BusyBox as a static binary (no shared libs)是问是否将busybox静态链接,如果是glibc的话不选,如果是uclibc的话要选。
2>在Installation Options选项中,默认地, 运行 make install之后, BusyBox将被安装到./_install目录。
3>NFS是肯定要选的, 使用NFS将宿主机的文件系统mount到目标板上, 这是嵌入式Linux程序开发的一个重要方面。
4>由于可使用NFS, 能在宿主机上实现的功能就都不需要在目标板上实现了。
5>Debian Utilities 全不选, Editors全不选, System Logging Utilities全不选。
6>解压缩工具只安装解压工具,gzip、bzip2、tar工具(要求目标板能解压缩tar.bz2和tar.gz文件)。
7>shell使用ash。
3)编译及安装
make TARGET_ARCH=arm CROSS=arm-linux- -j2
make install
3、用busybox给ramdisk增加命令
1)
gunzip ramdisk.gz
mount -o loop ramdisk /mnt/ramdisk
2)
cd busybox1.4.1/_install
cp -rfv * /mnt/ramdisk
umount /mnt/ramdisk
gzip -v9 ramdisk
生 成最后带有busybox命令的内存映像文件ramdisk.gz,不过此时的ramdisk文件系统内仍缺少一些必需的东西,如/lib下的库文件、 /etc下的一些配置文件、/dev下的一些块设备和文件系统等,这都需要自己添加,如果觉得麻烦,可以下载一个现成的,然后在它的基础上作一些修改。

(注:BusyBox 将数以百计的常用Unix/Linux命令集成到一个可执行文件中(名为busybox)。它体积小巧, 功能却不失强大,常用Linux命令实现的功能它都能提供,它甚至还提供了tftp, http服务程序,尽管少数的Linux命令的某些选项BusyBox没能提供, 但这并不影响它在嵌入式Linux系统中的流行。
    BusyBox 可以与glibc或uClibc库进行链接编译, 可以采用动态链接或静态链接,即便采用与glibc的静态链接, 最终生成的busybox文件大小也能轻易控制在1MB之内,而采用uClibc动态链接的可执行文件就更小了,这非常适于存储空间紧张的嵌入式 Linux系统。由此, 有人将BusyBox称为嵌入式系统中的瑞士军刀,更为形象的比喻是: Linux系统中的单个命令是电路中的分立式元件, 而BusyBox是将它们集成在一起的IC: 功能不变, 体积却大为减小。)

Embedded中mkimage objcopy的Utilities.

  • mkimage:   (Ref: http://www.wretch.cc/blog/poemsoul/15441093, http://matttt-blog.blogspot.com/2010/01/mkimage.html, http://www.10pig.cn/bbs/showtopic.aspx?topicid=37703&page=end)

  mkimage這個工具為你的內核加上u-boot引導所需要的檔案頭(called uImage),具體做法如下︰
[root@localhost tftpboot]#mkimage -n 'linux- 2.6.14 ' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage zImage.img
 

Image Name: linux-2.6.14
Created: Fri Jan 12 17:14:50 2007
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1262504 Bytes = 1232.91 kB = 1.20 MB
Load Address: 0x30008000
Entry Point: 0x30008000


  
如上的作法即可產生u-boot載入時的訊息.因此在u-boot的命令列中執行tftp的動作並載入核心時,會產生如下訊息:
sbc2410=>tftp 0x31000000 zImage.img 

sbc2410=>bootm 0x31000000
 

## Booting image at 31000000 ...
Image Name: linun-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1263260 Bytes = 1.2 MB
Load Address: 30008000
Entry Point: 30008000
Verifying Checksum ... OK
OK

Starting kernel ...

  • objcopy (http://www.cmlab.csie.ntu.edu.tw/~daniel/linux/objcopy.html, http://blog.linux.org.tw/~jserv/archives/001767.html)
    copy and translate object files:
        * 複製 .o 檔 (obj file) 的內部內容到另一個檔案中 (.o 檔或是單純地做 hex dump)
        * 將一個 binary data (如JPEG 圖片) 做成 .o 檔
        * 把執行碼從 ELF 中抽取出來
        * 只抽取指定的 section 


Example 1:  將一個 JPEG 圖片檔 做成 .o 檔.
    $ objcopy -I binary -O elf32-i386 -B i386 image.jpg image.o
Example 2:  把執行碼從 ELF 中抽取出來,去除 symbol table, relocation information, .comment section, 和 .note section.
    $ objcopy -O binary -S -R .comment -R .note code.o code.bin
Example 3: 只抽取指定的 section.
    $ objcopy -O binary -j .text code.o code.bin


  • 程式減肥三步走 (http://linux.vbird.org/somepaper/20050117-jianfei.pdf)
    對於設計嵌入式 Linux 系統的研發人員來說,有一個問題是必須要考慮到的,那就是記憶體的空間。
我們知道嵌入式 Linux 系統所用的記憶體不是軟碟、硬碟、ZIP 盤、CD-ROM、DVD 這些? 所周知的大容量常規記憶體,它使用的是例如Rom,CompactFlash,M-Systems 的DiskOnChip,SONY 的MemoryStick,IBM 的MicroDrive 等體積極小,與主板上的BIOS大小相近,存儲容量很小的記憶體。所以怎樣盡可能的節省空間就顯的很重要。嵌入式系統的記憶體中放置的無非是內核,文件系 統,軟體,以及自己開發的程式。本文就從程式入手,以一個非常簡單的C 程式來作? 例子,通過三步來讓它減肥。
Hello.c:
    #include <stdio.h>
    int main ()
    {
        printf ("hello,world");
        return 0;
    }
我們先用正常的編譯方法來編譯,看看生成的程式的大小是多少
  #gcc – o hello hello.c
  #ls – l hello

-rwxr-xr-x 1 root root 11542 Nov 13 20:07 hello


從結果可以看到正常編譯後的程式大小是 11542Byte, 現在開始我們的三步減肥,看看到底效果如何。


步驟一:用gcc 的代碼優化參數
    代碼優化指的是編譯器通過分析源代碼,找出其中尚未達到最優的部分,然後對其重新進行組合,目的是改善程式的執行性能。GCC 提供的代碼優化功能非常強大,它通過編譯選項-On 來控制優化代碼的生成,其中n 是一個代表優化級別的整數。對於不同版本的GCC 來講,n 的取值範圍及其對應的優化效果可能並不完全相同,比較典型的範圍是從0 變化到2 或3。
    編譯時使用選項-O 可以告訴GCC 同時減小代碼的長度和執行時間,其效果等價於-O1。在這一級別上能夠進行的優化類型雖然取決於目標處理器,但一般都會包括線程跳轉(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優化。選項-O2 告訴GCC 除了完成所有-O1 級別的優化之外,同時還要進行一些額外的調整工作,如處理器指令調度等。選項-O3 則除了完成所有-O2 級別的優化之外,還包括迴圈展開和其他一些與處理器特性相關的優化工作。通常來說,數位越大優化的等級越高,同時也就意味著程式的運行速度越快。許多 Linux 程式師都喜歡使用-O2 選項,因? 它在優化長度、編譯時間和代碼大小之間,取得了一個比較理想的平衡點。


#gcc – O2 – o hello hello.c
#ls – l hello

-rwxr-xr-x 1 root root 11534 Nov 13 20:09 hello


優化過的程式的大小是 11534Byte,比正常編譯的結果11542Byte 似乎沒有小多少,不過不用著急,這才是第一步。我們接著往下進行。


步驟二:用strip 命令
我們知道二進位的程式中包含了大量的符號資訊(symbol table),有一部分是用來?gdb 除錯提供必要幫助的。可以通過readelf – S 查看到這些符號資訊。
#readelf -S hello
  Section Headers:
  [Nr] Name Type
  [ 0] NULL
  [ 1] .interp PROGBITS
  ...
  [10] .init PROGBITS
  ...
  [33] .strtab STRTAB
類似於.debug_xxxx 的就是用來gdb 除錯的。去掉它們不但不會影響程式的執行還可
以減小程式的size。這裏我們通過strip 命令拿掉它們。
#strip hello
#ls – l hello

-rwxr-xr-x 1 root root 2776 Nov 13 20:11 hello
 

程式立刻變成 2776Byte 了,效果不錯吧。讓我們再接再厲,進行最後一步。

步驟三:用objcopy 命令
  上一步的 strip 命令只能拿掉一般symbol table,有些資訊還是沒拿掉,而這些資訊對於程式的最終執行是沒有什? 影響的。如:.comment; .note.ABI-tag; .gnu.version就是完全可以去掉的。所以說程式還有簡化的餘地,我們可以使用objcopy 命令把它們抽取掉。
#objcopy – R .comment – R .note.ABI-tag – R .gnu.version hello hello1
#ls – l hello1

-rwxr-xr-x 1 root root 2316 Nov 13 20:23 hello1
 

到這一步,程式的減肥就完成了,我們可以看到程式由正常編譯的11542Byte, 一下子漸少到2316Byte,效果非常明顯。