c語言、匯編、腳本、Makefile、Kconfig、設(shè)備樹
裝windows:BIOS界面、運(yùn)行系統(tǒng)內(nèi)核、安裝驅(qū)動(dòng)、裝軟件
移植就是給一個(gè)開發(fā)板裝一個(gè)系統(tǒng):學(xué)完這門課之后我們都是嵌入式的網(wǎng)管
嵌入式平臺:引導(dǎo)程序(u-boot)、uImage、寫驅(qū)動(dòng)(改驅(qū)動(dòng))、第三方源碼(相當(dāng)于一個(gè)應(yīng)用軟件)
* 什么是移植?軟件系統(tǒng)移植、硬件系統(tǒng)移植
一個(gè)平臺的代碼應(yīng)用到另一個(gè)不同體系的平臺上,需要做的工作就是移植
在移植過程中:
- 虛擬內(nèi)存空間分配:用戶空間、內(nèi)核空間(文件系統(tǒng),驅(qū)動(dòng),內(nèi)存管理,網(wǎng)絡(luò)協(xié)議棧,設(shè)備管理,中斷,進(jìn)程管理)
- 上層的open和內(nèi)核的open有什么關(guān)系?
struct file_operations
{
int (*open)(struct *file,);
}
交叉編譯器
- x86 arm 交叉編譯器:在x86平臺編寫代碼,然后通過交叉編譯器編譯后生成了適用于arm平臺的二進(jìn)制代碼
- arm-none-linux-gnueabi-gcc
編譯過程:
- 預(yù)處理 gcc -E 1.c -o 1.i 或 cpp(預(yù)處理器) 1.c -o 1.i
- 編譯 gcc -S 1.i -o 1.s 或 cc1(編譯器) 1.i -o 1.s
- 匯編 gcc -c 1.s -o 1.o 或 as(匯編器) 1.s -o 1.o
- 鏈接 gcc 1.o 或 ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o 1 1.o /usr/lib/i386-linux-gnu/crt1.o
- /usr/lib/i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.6/crtbegin.o -lc
- /usr/lib/gcc/i686-linux-gnu/4.6/crtend.o /usr/lib/i386-linux-gnu/crtn.o
- 鏈接器調(diào)用.o文件,.o文件中包含分段信息。
- .data .text .bss .rodata
nm 二進(jìn)制文件 顯示二進(jìn)制文件中符號表的信息
顯示的內(nèi)容包含3列:
內(nèi)存地址 屬于哪個(gè)分段 符號名稱
大寫字母代表全局
小寫字母代表局部
T D B R W
void fun() __atteibute__((weak));
//將fun函數(shù)變成弱標(biāo)號,如果沒有調(diào)用的話不會(huì)報(bào)錯(cuò),編譯器會(huì)直接忽略掉fun相關(guān)鏈接操作
strip 二進(jìn)制文件 清除二進(jìn)制文件中的符號 清除符號表的目的是為了節(jié)省資源
什么是符號?函數(shù)名或者變量名
符號什么作用? 符號是給鏈接器使用的或者說鏈接器通過符號的名字來找到了代碼分段的位置
鏈接器在生成可執(zhí)行文件時(shí)要使用符號,如果符號被清除,那么鏈接器失效。所以strip不要對中間文件進(jìn)行瘦身。
在操作開發(fā)板前要先對u-boot進(jìn)行配置。
u-boot有兩種模式:交互模式,自啟動(dòng)模式.
- 在配置時(shí)要進(jìn)入到交互模式中,一旦配置好之后使用自啟動(dòng)模式.
- 在交互模式中的命令操作:注意這里的命令不是shell命令。
設(shè)置環(huán)境變量內(nèi)容:
- setenv 環(huán)境變量名 內(nèi)容
- 例如:setenv serverip 172.21.1.250
刪除環(huán)境變量:
- setenv 環(huán)境變量名
setenv bootargs root=/dev/nfs nfsroot=172.21.1.250:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=開發(fā)板的ip地址
//使用nfs服務(wù),通過nfs服務(wù)掛載/rootfs,創(chuàng)建了init進(jìn)程
setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000
//tftp 41000000 uImage 在u-boot中通過tftp命令從Ubuntu的/tftpboot目錄下下載uImage文件到開發(fā)板的偏外內(nèi)存41000000地址處
//三星手冊給的偏外內(nèi)存最大地址范圍40000000 - A0000000 1.5G 我們實(shí)際用的是40000000 - 8000000 1G
//bootm 內(nèi)核鏡像地址 文件系統(tǒng)鏡像地址 設(shè)備樹二進(jìn)制文件地址 這三個(gè)地址順序不能改變.
//本質(zhì):1、類似于go命令有跳轉(zhuǎn)到指定地址的作用
2、可以給內(nèi)核傳遞參數(shù)
//2.4 2.6 3.0不支持設(shè)備樹 ———————— 3.1x可以選擇是否支持設(shè)備樹
u-boot移植:
配置過程:
- 1、自己去百度
- 2、每個(gè)源碼頂層目錄下都會(huì)有一個(gè)README文件,查看README文件知道配置方法為:make 板子名稱_config
- 我們?nèi)A清的板子叫做FS4412,這個(gè)板子包含了——SOC,外設(shè).
- 其中SOC還有分類,exynos就是SOC中的某一類,我們FS4412使用的SOC是exynos系列的芯片,這個(gè)芯片系列三星公司規(guī)定必須以origen為模板。
- 所以配置時(shí)可以執(zhí)行 make origen_config
- 為什么要配置?
- 因?yàn)槭褂昧薽ake命令所以會(huì)用到Makefile文件。
- 在使用一個(gè)大型Makefile時(shí),一定要先確定入口,千萬別從第一行開始讀。
- 通過make origen_config 可以知道當(dāng)前的Makefile入口是origen_config(也就是說我們要去尋找目標(biāo)或者偽目標(biāo)叫做origen_config)
Makefile的基本機(jī)制:
- Makefile的基本機(jī)制:顯示規(guī)則、隱式規(guī)則、變量、條件語句、函數(shù)
- 顯示規(guī)則語法:
- 目標(biāo)文件:依賴文件
- 生成目標(biāo)文件的命令
779 %_config:: unconfig
780 @$(MKCONFIG) -A $(@:_config=)
查找變量的方法:
- 1、/變量名
- 2、命令模式下 按#
- 3、通過ctags -R生成tags文件,然后ctrl ]
- 4、make -p Makefile > 隨便一個(gè)文件名
$(@:_config=) 以$@為基礎(chǔ)添加一個(gè)規(guī)則,而這個(gè)規(guī)則的作用就是將_config替換為空,所以最終%_config會(huì)變成origen
@$(MKCONFIG) -A $(@:_config=) ==> @頂層目錄/mkconfig -A origen
* 文件系統(tǒng)移植:根文件系統(tǒng)的制作以及文件系統(tǒng)鏡像的制作
line=`egrep -i "^[[:space:]]*${2}[[:space:]]" boards.cfg`
line=origen arm armv7 origen samsung exynos
$1 $6
//-i忽略大小寫
//[[:space:]]空格或者TAB
set $line 根據(jù)變量的內(nèi)容重置$#的值,當(dāng)前$#=6
57 CONFIG_NAME="${1%_config}" ==> CONFIG_NAME=origen
59 [ "${BOARD_NAME}" ] || BOARD_NAME="${1%_config}" ==> BOARD_NAME=origen
61 arch="$2" ==> arm
62 cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'` ==> armv7
63 spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'` spl_cpu=
64 if [ "$4" = "-" ] ; then
65 board=${BOARD_NAME}
66 else
67 board="$4" ==> board = origen
68 fi
69 [ $# -gt 4 ] && [ "$5" != "-" ] && vendor="$5" ==> samsung
70 [ $# -gt 5 ] && [ "$6" != "-" ] && soc="$6" ==> exynos
97 echo "Configuring for ${BOARD_NAME} board..."
113 cd ./include
114 rm -f asm
115 ln -s ../arch/${arch}/include/asm asm
123 ln -s ${LNPREFIX}arch-${soc} asm/arch <==> ln -s arch-exynos asm/arch
//當(dāng)我們創(chuàng)建軟鏈接時(shí)有一種用法是:如果當(dāng)前目錄下不存在源文件,會(huì)到目標(biāo)路徑下去尋找是否有源文件
128 ln -s ${LNPREFIX}proc-armv asm/proc
//到此為止我們發(fā)現(xiàn)產(chǎn)生了3個(gè)軟連接文件。無論換成什么平臺什么架構(gòu)我們只需要關(guān)心這些軟鏈接文件就可以,至于軟鏈接文件存放的路徑是隨平臺的改變而改變的。
134 ( echo "ARCH = ${arch}"
135 if [ ! -z "$spl_cpu" ] ; then
136 echo 'ifeq ($(CONFIG_SPL_BUILD),y)'
137 echo "CPU = ${spl_cpu}"
138 echo "else"
139 echo "CPU = ${cpu}"
140 echo "endif"
141 else
142 echo "CPU = ${cpu}"
143 fi
144 echo "BOARD = ${board}"
145
146 [ "${vendor}" ] && echo "VENDOR = ${vendor}"
147 [ "${soc}" ] && echo "SOC = ${soc}"
148 exit 0 ) > config.mk
在config.mk中存放5個(gè)變量的賦值:
ARCH = arm
CPU = armv7
BOARD = origen
VENDOR = samsung
SOC = exynos
config.mk文件會(huì)被頂層目錄的Makefile引用,然后將里面的變量變成全局
154 BOARDDIR=${vendor}/${board} ==> samsung/origen
164 > config.h 創(chuàng)建一個(gè)空的config.h頭文件
echo "#define CONFIG_SYS_ARCH \"${arch}\"" >> config.h
174 echo "#define CONFIG_SYS_CPU \"${cpu}\"" >> config.h
175 echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h
176
177 [ "${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h
178
179 [ "${soc}" ] && echo "#define CONFIG_SYS_SOC \"${soc}\"" >> config.h
180
181 cat << EOF >> config.h
182 #define CONFIG_BOARDDIR board/$BOARDDIR
183 #include <config_cmd_defaults.h>
184 #include <config_defaults.h>
185 #include <configs/${CONFIG_NAME}.h>
186 #include <asm/config.h>
187 #include <config_fallbacks.h>
188 #include <config_uncmd_spl.h>
189 EOF
將一些宏定義和系統(tǒng)頭文件存放到include/config.h頭文件中
配置u-boot的功能:產(chǎn)生了3個(gè)軟鏈接,include/config.mk include/config.h
常用的Makefile命名:GNUmakefile makefile Makefile
.mk .Linux .AIX
編譯過程:
- make all
428 all: $(ALL-y) $(SUBDIR_EXAMPLES)
//ALL-y = u-boot.bin 所以后續(xù)要去以u-boot.bin為目標(biāo)
411 ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map
443 $(obj)u-boot.bin: $(obj)u-boot
444 $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
445 $(BOARD_SIZE_CHECK)
//要生成u-boot.bin必須事先生成u-boot,所以要搜索u-boot目標(biāo)
570 $(obj)u-boot: depend \
571 $(SUBDIR_TOOLS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
572 $(GEN_UBOOT)
248 OBJS := $(addprefix $(obj),$(OBJS) $(RESET_OBJS-))
236 OBJS = $(CPUDIR)/start.o
B=
A=$(B)
//ifdef A 判斷的是變量A是否有內(nèi)容,如果有內(nèi)容成立,沒有內(nèi)容不成立
LDSCRIPT=arch/arm/cpu/u-boot.lds
561 GEN_UBOOT = \
562 UNDEF_LST=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \
563 sed -n -e 's/ .*(_u_boot_list_.*)/-u\1 /p'|sort|uniq`;\
564 cd $(LNDIR) && $(LD) $(LDFLAGS) $(LDFLAGS_$(@F)) \
565 $$UNDEF_LST $(__OBJS) \
566 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
567 -Map u-boot.map -o u-boot
// sed基本功能用來字符串替換的
// \1的作用是用來標(biāo)記的,標(biāo)記的是前面的括號內(nèi)部的內(nèi)容
// -u\1作用就是用-u替換標(biāo)記前面的內(nèi)容
/ /所以替換完成后是 -u_u_boot_list_.*
// -u\1的用法:sed -n -e 's/.*(_u_boot)_list_.*/-u\1' 替換后變成-u_u_boot_list_.*
// \1-u用法:sed -n -e 's/.*_u_boot-u'
// $(@F) <==> $(notdir $@)
驗(yàn)證:
- abc=/home/linux/Makefilex
- info=$(notdir $(abc))
start:
echo $(info)
最終結(jié)果為Makefilex,無論有沒有這個(gè)文件,我們關(guān)心的是能否去掉文件前的路徑
$(LDFLAGS_$(@F)) 轉(zhuǎn)化完成后為 LDFLAGS_u-boot
LDFLAGS_u-boot = -pie -T $(obj)u-boot.lds $(LDFLAGS_FINAL) -Ttext $(CONFIG_SYS_TEXT_BASE)
-pie -T u-boot.lds為了告訴鏈接器,根據(jù)u-boot.lds的內(nèi)容哪些分段是需要鏈接的哪些分段是需要拋棄的。
312 LDFLAGS_FINAL = -Bstatic 所有內(nèi)容進(jìn)行靜態(tài)鏈接
-Ttext 0x43e00000 一旦進(jìn)行鏈接后,u-boot.lds中的00000000地址會(huì)被替換成0x43e00000
--start-group $(__LIBS) --end-group 指定一組庫的二進(jìn)制文件
上面一堆操作我們重點(diǎn)關(guān)心鏈接器鏈接時(shí)用到哪些內(nèi)容?
- 1、分段,符號,分段和符號是一堆.o文件提供的
- 2、用到哪些分段u-boot.lds指定的
分段相對于符號來講是一個(gè)整體,分段是包含符號的。
u-boot.map會(huì)告訴鏈接器,在鏈接時(shí)使用到的符號對應(yīng)的地址是什么
鏈接最終的結(jié)果生成的u-boot可執(zhí)行文件,再通過objcopy將u-boot轉(zhuǎn)化為u-boot.bin
理論來講這個(gè)u-boot.bin可以燒寫到開發(fā)板上了。但是三星板子不允許。
* 我們最終用的u-boot-fs4412.bin包含u-boot.bin、bl1、bl2、填充代碼、trustzone
開發(fā)板——SOC、外設(shè)
- SOC內(nèi)部——CPU(cortex-a9)、外設(shè)控制器、IROM、IRAM
- 我們的SOC三星公司給取名為exynos4412
- 在SOC內(nèi)部的IROM中固化了一部分代碼叫做bl0,bl0的作用是將bl1加載到IRAM中運(yùn)行,bl1是三星提供的二進(jìn)制文件,作用是初始化硬件
同時(shí)將bl2加載到IRAM中運(yùn)行,bl2是u-boot.bin的前14k,這14K的主要作用是計(jì)算出片外內(nèi)存可用的高位地址,然后將u-boot.bin整體
搬移到計(jì)算出的高位地址中去運(yùn)行。
linux內(nèi)核:主版本號.次版本號.修訂版本號
通常情況下次版本號為偶數(shù)的是穩(wěn)定版本。www.kernel.org
2.4devfs 2.6sysfs 3.0 3.1x sysfs
選擇內(nèi)核版本時(shí),不要選擇比較老的版本,最好不要選擇最新版本。
linux重要的目錄:
- arch存放的是和體系架構(gòu)相關(guān)的代碼
- Documentation存放說明文檔
- drivers存放驅(qū)動(dòng)代碼
- init/main.c 存放了內(nèi)核啟動(dòng)代碼的c語言程序
- include 存放的是頭文件
linux內(nèi)核:
配置
- 方法:
- 1、
make exynos(芯片名稱)_defconfig
- 2、
cp arch/arm/configs/exynos_defconfig .config
- 1、
- 目的: 通過配置方法讓內(nèi)核的功能只支持當(dāng)前平臺。
如何對內(nèi)核進(jìn)行軟件裁剪:make menuconfig
- 注意:
- 1、在頂層目錄
- 2、屏幕不能太小
- 3、第一次在執(zhí)行make menuconfig時(shí)會(huì)不成功,可能是因?yàn)闆]安裝一個(gè)軟件包
進(jìn)入menuconfig后選項(xiàng)的種類主要使用3種:
- [] 有兩種狀態(tài)——選中和未選中
- <> 有三種狀態(tài)——選中和未選中、模塊
- () 可以存放十進(jìn)制整數(shù),十六進(jìn)制整數(shù)、字符串
- 內(nèi)核選項(xiàng)是如何添加到menuconfig中的?必須要先學(xué)習(xí)Kconfig語法
Kconfig中的屬性:
- source "路徑/Kconfig" 引用某個(gè)路徑下的Kconfig文件
- config 字符串
- 例如config ABC 我們會(huì)在頂層目錄下的.config文件中尋找到CONFIG_ABC這個(gè)變量。同時(shí)CONFIG_ABC還有可能出現(xiàn)在Makefile文件中
- bool "字符串" 這個(gè)字符串就是menuconfig中的一個(gè)選項(xiàng)名稱,bool代表了選項(xiàng)[]
- tristate "字符串" 代表了<>
- string "字符串" 代表了(),這個(gè)小括號中只能存放字符串
- int "字符串" 代表了(),這里存放十進(jìn)制整數(shù)
- hex "字符串" 代表了(),智力存放十六進(jìn)制整數(shù)
- menu,endmenu 這兩個(gè)配對使用,當(dāng)一個(gè)選項(xiàng)有子選項(xiàng)時(shí)使用
- default y | n | m | 字符串 | 整數(shù)
- depends on 字符串 當(dāng)前選項(xiàng)的出現(xiàn)要依賴另一個(gè)選項(xiàng)是否選中,而另一個(gè)選項(xiàng)是誰?通過字符串來尋找的
假設(shè)需要操作一個(gè)設(shè)備。而且內(nèi)核中已經(jīng)有驅(qū)動(dòng)來支持這個(gè)設(shè)備,但這個(gè)驅(qū)動(dòng)相關(guān)選項(xiàng)沒有選中,如何找到這個(gè)選項(xiàng)?
- 1、確定用到哪個(gè)具體的驅(qū)動(dòng)程序。假設(shè)這個(gè)驅(qū)動(dòng)程序叫做test.c
- 2、一旦找到驅(qū)動(dòng)程序所在路徑,進(jìn)入這個(gè)路徑下的Makefile中,尋找obj-$(CONFIG_ABC) += test.o
- 3、還在當(dāng)前目錄下進(jìn)入Kconfig文件,尋找ABC,找到ABC之后可以知道這個(gè)驅(qū)動(dòng)用哪個(gè)選項(xiàng)了