<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          ARM匯編必知必會

          作者: 時間:2016-11-21 來源:網(wǎng)絡(luò) 收藏
          ARM指令集:

          ADC 帶進位的32位數(shù)加法
          ADD 32位數(shù)相加
          AND 32位數(shù)的邏輯與
          B 在32M空間內(nèi)的相對跳轉(zhuǎn)指令
          BIC 32位數(shù)的邏輯位清零
          BKPT 斷點指令
          BL 帶鏈接的相對跳轉(zhuǎn)指令
          BLX 帶鏈接的切換跳轉(zhuǎn)
          BX 切換跳轉(zhuǎn)
          CDPCDP2 協(xié)處理器數(shù)據(jù)處理操作
          CLZ 零計數(shù)
          CMN 比較兩個數(shù)的相反數(shù)
          CMP 32位數(shù)比較
          EOR 32位邏輯異或
          LDCLDC2 從協(xié)處理器取一個或多個32位值
          LDM 從內(nèi)存送多個32位字到ARM寄存器
          LDR 從虛擬地址取一個單個的32位值
          MCRMCR2MCRR 從寄存器送數(shù)據(jù)到協(xié)處理器
          MLA 32位乘累加
          MOV 傳送一個32位數(shù)到寄存器
          MRCMRC2MRRC 從協(xié)處理器傳送數(shù)據(jù)到寄存器
          MRS 把狀態(tài)寄存器的值送到通用寄存器
          MSR 把通用寄存器的值傳送到狀態(tài)寄存器
          MUL 32位乘
          MVN 把一個32位數(shù)的邏輯“非”送到寄存器
          ORR 32位邏輯或
          PLD 預(yù)裝載提示指令
          QADD 有符號32位飽和加
          QDADD 有符號雙32位飽和加
          QSUB 有符號32位飽和減
          QDSUB 有符號雙32位飽和減
          RSB 逆向32位減法
          RSC 帶進位的逆向32法減法
          SBC 帶進位的32位減法
          SMLAxy 有符號乘累加(16位*16位)+32位=32位
          SMLAL 64位有符號乘累加((32位*32位)+64位=64位)
          SMALxy 64位有符號乘累加((32位*32位)+64位=64位)
          SMLAWy 32位有號乘累加((32位*16位)>>16位)+32位=32位
          SMULL 64位有符號乘累加(32位*32位)=64位
          SMULxy 32位有符號乘(16位*16位=32位)
          SMULWy 32位有符號乘(32位*16位>>16位=32位)
          STCSTC2 從協(xié)處理器中把一個或多個32位值存到內(nèi)存
          STM 把多個32位的寄存器值存放到內(nèi)存
          STR 把寄存器的值存到一個內(nèi)存的虛地址內(nèi)間
          SUB 32位減法
          SWI 軟中斷
          SWP 把一個字或者一個字節(jié)和一個寄存器值交換
          TEQ 等值測試
          TST 位測試
          UMLAL 64位無符號乘累加((32位*32位)+64位=64位)
          UMULL 64位無符號乘累加(32位*32位)=64位

          本文引用地址:http://www.ex-cimer.com/article/201611/319538.htm

          無論是體系結(jié)構(gòu)還是指令集,大家或多或少都應(yīng)該對X86匯編有些了解,而對于嵌入式領(lǐng)域已被廣泛采用的ARM 處理器,了解的可能并不多。如果你有興趣從事嵌入式方面的開發(fā),那么了解一些RISC 體系結(jié)構(gòu)和編的知識還是有必要的。這里,我們找出了這兩種體系結(jié)構(gòu)最明顯的不同之處,并對此進行介紹,讓大家對于RISC體系結(jié)構(gòu)的匯編有一個基本的了解。首先,我們就來看一看基于RISC的ARM的體系結(jié)構(gòu)。


          基于RISC 的ARM CPU
          ARM是一種RISC體系結(jié)構(gòu)的處理器芯片。和傳統(tǒng)的CISC體系結(jié)構(gòu)不同,RISC 有以下的幾個特點:
          ◆ 簡潔的指令集——為了保證CPU可以在高時鐘頻率下單周期執(zhí)行指令,RISC指令集只提供很有限的操作(例如add,sub,mul等),而復(fù)雜的操作都需要由這些簡單的指令來組合進行模擬。并且,每一條指令不僅執(zhí)行時間固定,其指令長度也是固定的,這樣,在譯碼階段就可以對下一條指令進行預(yù)取。
          ◆ Load-Store 結(jié)構(gòu)——這個應(yīng)該是RISC 設(shè)計中比較有特點的一部分。在RISC 中,CPU并不會對內(nèi)存中的數(shù)據(jù)進行操作,所有的計算都要求在寄存器中完成。而寄存器和內(nèi)存的通信則由單獨的指令來完成。而在CSIC中,CPU是可以直接對內(nèi)存進行操作的,這也是一個比較特別的地方。
          ◆ 更多的寄存器——和CISC 相比,基于RISC的處理器有更多的通用寄存器可以使用,且每個寄存器都可以進行數(shù)據(jù)存儲或者尋址。

          當(dāng)然,作為RISC 領(lǐng)域最成功的處理器,ARM也遵從上面的特點。這里,我們不妨來看一看在user 模式下,ARM處理器的體系結(jié)構(gòu),這對于我們了解其匯編語言是有好處的。而其它模式下只是有一些寄存器分組略有不同,大家可以在ARM的手冊上查到。這里要說明的是,盡管ARM處理器也支持16位指令,不過在下文中,我們都假定ARM處理器在32 位模式下工作。

          圖:user模式下ARM處理器體系結(jié)構(gòu)
          從圖中我們看到,在user 模式下,ARM CPU 有16個數(shù)據(jù)寄存器,被命名為r0~r15(這個要比x86的多一些)。r13~r15有特殊用途,其中:
          ◆ r13 - 指向當(dāng)前棧頂,相當(dāng)于x86的esp,這個東西在匯編指令中要用sp 表示
          ◆ r14 - 稱作鏈接寄存器,指向函數(shù)的返回地址。用lr表示,這和x86將返回地址保存在棧中是不同的
          ◆ r15 - 類似于x86的eip,其值等于當(dāng)前正在執(zhí)行的指令的地址+8(因為在取址和執(zhí)行之間多了一個譯碼的階段),這個用pc表示
          另外,ARM處理器還有一個名為cspr的寄存器,用來監(jiān)視和控制內(nèi)部操作,這點和x86 的狀態(tài)寄存器是類似的。具體的內(nèi)容就用到再說了。

          ARM 指令集
          ARM處理器可以支持3種指令集——ARM,Thumb和Jazelle。
          采用那種指令集,由cspr中的標(biāo)志位來決定。大體說來:
          ◆ ARM——這是ARM自身的32 位指令集
          ◆ Thumb ——這是一個全16 位的指令集,在16 位外部數(shù)據(jù)總線寬度下,這個指令集的效率要比32 位的ARM指令高一些。
          ◆ Jazelle ——這是一個8位指令集,用來加速Java字節(jié)碼的執(zhí)行
          整個ARM指令集由數(shù)據(jù)處理指令、分支指令、Load-Store指令、程序中斷指令和一些系統(tǒng)控制指令構(gòu)成,除了Load-Store指令外,其他部分和x86指令集是比較類似的。但和x86相比,ARM指令最顯著的特點它們都是32-bit 定長的。另外,由于arm是基于RISC指令集的,所以CPU只處理在寄存器中的數(shù)據(jù)并通過獨立的load-store指令在內(nèi)存和寄存器之間進行數(shù)據(jù)的傳遞。
          在使用方面,ARM指令的格式也要比Intel的復(fù)雜些。一般說來,一條ARM指令有如下的形式:
          {S} [Rd], [Rn], [Rm]
          其中:
          * {S} —— 加上這個后綴的指令會更新cpsr 寄存器
          * [Rd] —— 目的寄存器
          * [Rn]/[Rm] —— 源寄存器
          一般來說,arm 指令有3個操作數(shù),其中Rm寄存器在執(zhí)行指令前可以進入桶形移位器進行移位操作,而Rn則會直接進入ALU 單元。如果一條arm 指令只有2 個操作數(shù),那么源寄存器按照Rm 來處理。例如,一條加法指令:
          add r0, r1, #1
          就會把r1+1的結(jié)果存放到r0中。
          在熟悉了基本的匯編格式后,讀者就可以自行去查詢基本的編指令了,下面,我們找出ARM中比較有特色部分——Load-Store指令結(jié)構(gòu),它是CPU 和內(nèi)存進行通信的一個重要媒介。

          Load-Store 指令體系
          由于ARM CPU并不直接處理內(nèi)存中的數(shù)據(jù),這個指令體系就擔(dān)起了在寄存器和內(nèi)存之間交換數(shù)據(jù)的重要媒介。它要比x86 的內(nèi)存訪問機制復(fù)雜一些。該指令體系分成3 類:
          ◆ 單寄存器傳輸(這是與x86 最為相像的)
          ◆ 多寄存器傳輸
          ◆ 交換指令

          單寄存器傳輸
          先看第一個,很簡單:把單一的數(shù)據(jù)傳入(LDR) 或傳出(STR)寄存器,對內(nèi)存的訪問可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
          DWORD:
          Rd, addressing1
          WORD:
          H Rd, addressing2 無符號版
          SH Rd, addressing2 有符號版
          BYTE:
          B Rd, addressing1 無符號版
          SB Rd, addressing2 有符號版
          addressing1 和addressing2 的分類下面再說,現(xiàn)在理解成某種尋址方式就可以了。
          在單寄存器傳輸方面,還有以下三種變址模式,他們是:
          ◆ preindex
          這種變址方式和x86的尋址機制是很類似的,先對寄存器進行運算,然后尋址,但是在尋之后,基址寄存器的內(nèi)容并不發(fā)生改變,例如:
          ldr r0, [r1, #4]
          的含義就是把r1+4 這個地址處的DOWRD 加載到r0,而尋址后,r1 的內(nèi)容并不改變。
          ◆ preindex with writeback
          這種變址方式有點類似于++i的含義,尋址前先對基地址寄存器進行運算,然后尋址. 其基本的語法是在尋址符[]后面加上一個"!" 來表示.例如:
          ldr r0, [r1, #4]!
          就可以分解成:
          add r1, r1, #4
          ldr r0, [r1, #0]
          ◆ postindex
          自然這種變址方式和i++的方式就很類似了,先利用基址寄存器進行尋址,然后對基址寄存器進行運算,其基本語法是把offset 部分放到[]外面,例如:
          ldr r0, [r1], #4
          就可以分解成:
          ldr r0, [r1, #0]
          add r1, r1, #4
          如果你還記得x86 的SIB 操作的話,那么你一定想ARM是否也有,答案是有也沒有。在ss上面提到的addressing1 和addressing2的區(qū)別就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]來實現(xiàn)SB 的效果,或者通過[base,offset](這里的offset 可以是立即數(shù)或者寄存器)來實現(xiàn)SI 的效果,而addressing2則只能用后者了。于是每一種變址方式最多可以有3 種尋址方式,這樣一來,最多可以有9種用來尋址的指令形式。例如:
          ldr r0, [r1, r2, LSR #0x04]! LSR左移
          ldr r0, [r1, -#0x04]
          ldr r0, [r1], LSR #0x04
          每樣找了一種,大概就是這個意思。到此,單寄存器傳輸就結(jié)束了,掌握這些足夠應(yīng)付差事了。下面來看看多寄存器傳輸吧。

          多寄存器傳輸
          說得很明白,意思就是通過一條指令同時把多個寄存器的內(nèi)容寫到內(nèi)存或者從內(nèi)存把數(shù)據(jù)寫到寄存器中,效率高的代價是會增加系統(tǒng)的延遲,所以armcc 提供了一個編譯器選項來控制寄存器的個數(shù)。指令的格式有些復(fù)雜:
          <尋址模式> Rn{!}, {r^}
          我們先來搞明白尋址模式,多寄存器傳輸模式有4 種:
          也就是說以A開頭的都是在Rn的原地開始操作,而B開頭的都是以Rn的下一個位置開始操作。如果你仍然感到困惑,我們不妨看個例子。
          所有的示例指令執(zhí)行前:
          mem32[0x1000C] = 0x04
          mem32[0x10008] = 0x03
          mem32[0x10004] = 0x02
          mem32[0x10000] = 0x01
          r0 = 0x00010010
          r1 = 0x00000000
          r3 = 0x00000000
          r4 = 0x00000000
          1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
          執(zhí)行后: 執(zhí)行后:
          r0 = 0x0010001C r0 = 0x0010001C
          r1 = 0x01 r1 = 0x02
          r2 = 0x02 r2 = 0x03
          r3 = 0x03 r3 = 0x04
          至于DA 和DB 的模式,和IA / IB 是類似的,不多說了。
          最后要說的是,使用ldm 和stm指令對進行寄存器組的保護是很常見和有效的功能。配對方案:
          stmia / ldmdb
          stmib / ldmda
          stmda / ldmib
          stmdb / ldmia
          繼續(xù)來看兩個例子:
          執(zhí)行前:
          r0 = 0x00001000
          r1 = 0x00000003
          r2 = 0x00000002
          r3 = 0x00000001
          執(zhí)行的指令:
          stmib r0!, {r1-r3}
          mov r1, #1 ; These regs have been modified
          mov r2, #2
          mov r3, #3
          當(dāng)前寄存器狀態(tài):
          r0 = 0x0000100C
          r1 = 0x00000001
          r2 = 0x00000002
          r3 = 0x00000003
          ldmia r0!, {r1-r3}
          最后的結(jié)果:
          r0 = 0x00001000
          r1 = 0x00000003
          r2 = 0x00000002
          r3 = 0x00000001
          另外,我們還可以利用這個指令對完成內(nèi)存塊的高效copy:
          loop
          ldmia r9!, {r0-r7}
          stmia r10!, {r0-r7}
          cmp r9, r11
          bne loop
          說到這里,讀者應(yīng)該對RISC的Load-Store體系結(jié)構(gòu)有一個大概的了解了,能夠正確配對使用指令,是很重要的。

          ARM 異常處理

          如果您閱讀ARM手冊,您會發(fā)現(xiàn),在ARM中,經(jīng)常強調(diào)Exception(異常)這個概念,在ARM里,Interrupt(中斷)也是一種形式的異常。ARM的Exception同其所定義的5種異常模式是密切相關(guān)的,CPU在捕獲到任何一個Exception后,必定會進入某個異常模式,異常類型及捕獲到該異常后CPU所進入的異常模式之間的對應(yīng)關(guān)系是ARM所預(yù)先定義好的。
          如果您對X86比較熟悉,您會發(fā)現(xiàn),不象X86,系統(tǒng)定義了不同的中斷,比如鍵盤中斷,鼠標(biāo)中斷等等,并且系統(tǒng)也定義了這些中斷所對應(yīng)的中斷向量。ARM沒有定義這些,ARM只會告訴你,有外部中斷產(chǎn)生,并切換到IRQ或FIQ模式,然后執(zhí)行IRQ或FIQ所對應(yīng)的中斷向量。至于到底是鍵盤中斷,還是鼠標(biāo)中斷,這得由操作系統(tǒng)提供的中斷函數(shù)自己去判斷,比如通過查詢中斷控制器的某個或某些寄存器。ARM這樣做的原因是:ARM只是一個CORE,它并不定義也不去假想其外部環(huán)境,這樣可以使得ARM CORE更加緊湊和簡潔,同時也給SOC設(shè)計者提供了更多的靈活性和發(fā)揮空間。您一定要相信,ARM被如此廣泛使用不是“蓋”的,從系統(tǒng)開發(fā)者角度看,ARM是一種最簡單、最靈活的CPU,它的優(yōu)雅和簡潔性就像C語言一樣。呵呵,C語言是我最喜歡的語言。
          好了,“臭屁”了這么多,我們言歸正傳。對ARM異常處理的研究務(wù)必要弄清楚以下幾個方面:
          (1) 異常類型
          (2) 異常類型及處理該異常時CPU的執(zhí)行模式
          (3) 異常向量地址
          (4) 異常處理過程

          異常類型
          ARM定義了如下類型的異常(江南七怪,這樣好記):
          (1) RESET異常:由于執(zhí)行RESET指令或外部RESET信號產(chǎn)生的異常
          (2) SWI異常:執(zhí)行SWI指令產(chǎn)生的異常,通常用于提供系統(tǒng)調(diào)用接口
          (3) IRQ異常:ARM的IRQ Signal被觸發(fā)所產(chǎn)生的異常
          (4) FIQ異常:ARM的FIQ Signal被觸發(fā)所產(chǎn)生的異常
          (5) Prefetch Abort異常:預(yù)取指令時產(chǎn)生的異常
          (6) Data Abort異常:存取內(nèi)存數(shù)據(jù)時產(chǎn)生的異常
          (7) Undefined instruction異常:執(zhí)行unknown指令時產(chǎn)生的異常

          執(zhí)行模式
          當(dāng)產(chǎn)生異常后,CPU會進入相應(yīng)的異常模式并處理該異常:
          (1) RESET和SWI異常:CPU進入Supervisor模式
          (2) IRQ異常:CPU進入IRQ模式
          (3) FIQ異常:CPU進入FIQ模式
          (4) Prefetch Abort和Data Abort異常:CPU進入Abort模式
          (5) Undefined instruction異常:CPU進入Undefined模式

          向量地址
          ARM的異常向量地址可以處于4G物理空間的低端(0x00000000起),也可以處于高端(0xffff0000起),具體是哪種情況,根據(jù)具體的CPU及其配置而定。下面是7種異常的向量地址(挎弧內(nèi)為高端情形):
          (1) RESET異常:0x00000000 (0xffff0000)
          (2) Undefined instruction異常: 0x00000004 (0xffff0004)
          (3) SWI異常:0x00000008 (0xffff0008)
          (4) Prefetch Abort異常: 0x0000000c (0xffff000c)
          (5) Data Abort異常: 0x00000010 (0xffff0010)
          (6) IRQ異常: 0x00000018 (0xffff0018)
          (7) FIQ異常: 0x0000001c (0xffff001c)

          每個中斷向量為4字節(jié),一般的操作系統(tǒng)在該地址處放置一條跳轉(zhuǎn)指令“LDR PC,終端處理函數(shù)地址”。另外要注意的是,在IRQ異常和Data Abort異常之間空了4個字節(jié),這4個字節(jié)是保留的。

          處理過程
          處理過程包括兩個部分:
          (1) 進入:這個過程由CPU負(fù)責(zé)
          (2) 退出:這個過程由OS負(fù)責(zé)

          在捕獲到某個異常后,啟動“進入”過程,該過程內(nèi)CPU執(zhí)行如下動作:
          (1)將當(dāng)前PC的值(或PC + 4,或PC + 8)保存到R14的某個影子寄存器中。到底選擇哪個影子寄存器由該異常的執(zhí)行模式而定;另外R14影子寄存器的值同異常類型相關(guān)。比如Data Abort異常,對應(yīng)的影子寄存器就是Abort模式的影子寄存器R14_abt,R14_abt的值為異常產(chǎn)生時PC值 + 8。
          (2)將CPSR保存到CPSR的某個影子寄存器SPSR中,同樣,具體選擇哪個影子寄存器由該異常的執(zhí)行模式而定。
          (3) 執(zhí)行對因的中斷向量

          退出過程由操作系統(tǒng)自己負(fù)責(zé),只要確保退出后的PC和CPSR同進入之前是一樣就可以了。有時候操作系統(tǒng)在處理某種特定情況的異常后會將退出后PC值變?yōu)檫M入前PC值 + 4(即下一條指令地址),這僅僅是一個提醒,其目的是說明退出過程是完全由軟件自己決定的。

          Linux BOOTLOADER全程詳解

          網(wǎng)上關(guān)于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比較龐大的程序,讀起來不太方便,編譯出的文件也比較大,而且更多的是面向開發(fā)用的引導(dǎo)代碼,做成產(chǎn)品時還要裁減,這一定程度影響了開發(fā)速度,對初學(xué)者學(xué)習(xí)開銷也比較大,在此分析一種簡單的BOOTLOADER,是在三星公司提供的2410BOOTLOADER上稍微修改后的結(jié)果,編譯出來的文件大小不超過4k,希望對大家有所幫助.
          1.幾個重要的概念
          COMPRESSED KERNEL and DECOMPRESSED KERNEL
          壓縮后的KERNEL,按照文檔資料,現(xiàn)在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解壓器.因此要在ram分配時給壓縮和解壓的KERNEL提供足夠空間,這樣它們不會相互覆蓋.當(dāng)執(zhí)行指令跳轉(zhuǎn)到 COMPRESSED KERNEL后,解壓器就開始工作,如果解壓器探測到解壓的代碼會覆蓋掉COMPRESSED KERNEL,那它會直接跳到COMPRESSED KERNEL后存放數(shù)據(jù),并且重新定位KERNEL,所以如果沒有足夠空間,就會出錯.

          Jffs2 File System
          可以使armlinux應(yīng)用中產(chǎn)生的數(shù)據(jù)保存在FLASH上,我的板子還沒用到這個.

          RAMDISK
          使用RAMDISK可以使ROOT FILE SYSTEM在沒有其他設(shè)備的情況下啟動.一般有兩種加載方式,我就介紹最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把這個地址通過啟動參數(shù)的方式ATAG_INITRD2傳遞給KERNEL.具體看代碼分析.

          啟動參數(shù)(摘自IBM developer)
          在調(diào)用內(nèi)核之前,應(yīng)該作一步準(zhǔn)備工作,即:設(shè)置 Linux 內(nèi)核的啟動參數(shù)。Linux 2.4.x 以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來傳遞啟動參數(shù)。啟動參數(shù)標(biāo)記列表以標(biāo)記 ATAG_CORE 開始,以標(biāo)記 ATAG_NONE 結(jié)束。每個標(biāo)記由標(biāo)識被傳遞參數(shù)的 tag_header 結(jié)構(gòu)以及隨后的參數(shù)值數(shù)據(jù)結(jié)構(gòu)來組成。數(shù)據(jù)結(jié)構(gòu) tag 和 tag_header 定義在Linux 內(nèi)核源碼的include/asm/setup.h 頭文件中.
          在嵌入式 Linux 系統(tǒng)中,通常需要由 BOOTLOADER 設(shè)置的常見啟動參數(shù)有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
          (注)參數(shù)也可以用COMMANDLINE來設(shè)定,在我的BOOTLOADER里,我兩種都用了.

          2.開發(fā)環(huán)境和開發(fā)板配置:
          CPU:S3C2410,BANK6上有64M的SDRAM(兩塊),BANK0上有32M NOR FLASH,串口當(dāng)然是逃不掉的.這樣,按照數(shù)據(jù)手冊,地址分配如下:
          0x4000_0000開始是4k的片內(nèi)DRAM.
          0x0000_0000開始是32M FLASH 16bit寬度
          0x3000_0000開始是64M SDRAM 32bit寬度
          注意:控制寄存器中的BANK6和BANK7部分必須相同.
          0x4000_0000(片內(nèi)DRAM)存放4k以內(nèi)的BOOTLOADER IMAGE
          0x3000_0100開始存放啟動參數(shù)
          0x3120_0000 存放COMPRESSED KERNEL IMAGE
          0x3200_0000 存放COMPRESSED RAMDISK
          0x3000_8000 指定為DECOMPRESSED KERNEL IMAGE ADDRESS
          0x3040_0000 指定為DECOMPRESSED RAMDISK IMAGE ADDRESS
          開發(fā)環(huán)境:Redhat Linux,armgcc toolchain, armlinux KERNEL
          如何建立armgcc的編譯環(huán)境:建議使用toolchain,而不要自己去編譯armgcc,偶試過好多次,都以失敗告終.
          先下載arm-gcc 3.3.2 toolchain
          將arm-linux-gcc-3.3.2.tar.bz2 解壓到 /toolchain
          # tar jxvf arm-linux-gcc-3.3.2.tar.bz2
          # mv /usr/local/arm/3.3.2 /toolchain
          在makefile 中在把arch=arm CROSS_COMPILE設(shè)置成toolchain的路徑還有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否則庫函數(shù)就不能用了
          3.啟動方式:
          可以放在FLASH里啟動,或者用Jtag仿真器.由于使用NOR FLASH,根據(jù)2410的手冊,片內(nèi)的4K DRAM在不需要設(shè)置便可以直接使用,而其他存儲器必須先初始化,比如告訴memory controller,BANK6里有兩塊SDRAM,數(shù)據(jù)寬度是32bit,= =.否則memory control會按照復(fù)位后的默認(rèn)值來處理存儲器.這樣讀寫就會產(chǎn)生錯誤.
          所以第一步,通過仿真器把執(zhí)行代碼放到0x4000_0000,(在編譯的時候,設(shè)定TEXT_BASE=0x40000000)
          第二步,通過 AxD把linux KERNEL IMAGE放到目標(biāo)地址(SDRAM)中,等待調(diào)用
          第三步,執(zhí)行BOOTLOADER代碼,從串口得到調(diào)試數(shù)據(jù),引導(dǎo)armlinux

          4.代碼分析
          講了那么多執(zhí)行的步驟,是想讓大家對啟動有個大概印象,接著就是BOOTLOADER內(nèi)部的代碼分析了,BOOTLOADER文章內(nèi)容網(wǎng)上很多,我這里精簡了下,刪除了不必要的功能.
          BOOTLOADER一般分為2部分,匯編部分和c語言部分,匯編部分執(zhí)行簡單的硬件初始化,C部分負(fù)責(zé)復(fù)制數(shù)據(jù),設(shè)置啟動參數(shù),串口通信等功能.
          BOOTLOADER的生命周期:
          1. 初始化硬件,比如設(shè)置UART(至少設(shè)置一個),檢測存儲器= =.
          2. 設(shè)置啟動參數(shù),這是為了告訴內(nèi)核硬件的信息,比如用哪個啟動界面,波特率 = =.
          3. 跳轉(zhuǎn)到Linux KERNEL的首地址.
          4. 消亡
          當(dāng)然,在引導(dǎo)階段,象vivi等,都用虛地址,如果你嫌煩的話,就用實地址,都一樣.
          我們來看代碼:
          2410init.s
          .global _start//開始執(zhí)行處
          _start:
          //下面是中斷向量
          b reset @ Supervisor Mode//重新啟動后的跳轉(zhuǎn)
          ??
          ??
          reset:
          ldr r0,=WTCON /WTCON地址為53000000,watchdog的控制寄存器 */
          ldr r1,=0x0
          str r1,[r0]
          ldr r0,=INTMSK
          ldr r1,=0xffffffff
          str r1,[r0]
          ldr r0,=INTSUBMSK
          ldr r1,=0x3ff
          str r1,[r0]

          ldr r0, =GPFCON
          ldr r1, =0x55aa
          str r1, [r0]
          ldr r0, =GPFUP
          ldr r1, =0xff
          str r1, [r0]
          ldr r0,=GPFDAT
          ldr r1,=POWEROFFLED1
          str r1,[r0]

          ldr r0,=CLKDIVN
          ldr r1,=0x3
          str r1,[r0]

          ldr r0,=LOCKTIME
          ldr r1,=0xffffff
          str r1,[r0]

          ldr r0,=MPLLCON
          ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz
          str r1,[r0]
          ldr r1,=GSTATUS2
          ldr r10,[r1]
          tst r10,#OFFRST
          bne 1000f
          //以上這段,我沒動,就用三星寫的了,下面是主要要改的地方

          add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放著MC初始化要用到的數(shù)據(jù)
          ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址
          add r2,r0,#52 // 復(fù)制次數(shù),偏移52字
          1: //按照偏移量進行循環(huán)復(fù)制
          ldr r3,[r0],#4
          str r3,[r1],#4
          cmp r2,r0
          bne 1b
          .align 2
          MCDATA:
          .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
          上面這行就是BWSCON的數(shù)據(jù),具體參數(shù)意義如下:
          需要更改設(shè)置DW6 和DW7都設(shè)置成10,即32bit,DW0 設(shè)置成01,即16bit
          下面都是每個BANK的控制器數(shù)據(jù),大都是時鐘相關(guān),可以用默認(rèn)值,設(shè)置完MC后,就跳到調(diào)用main函數(shù)的部分

          .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0xB2 .word 0x30 .word 0x30 .align 2 .global call_main //調(diào)用main函數(shù),函數(shù)參數(shù)都為0 call_main: ldr sp,STACK_START mov fp,#0 mov a1, #0 mov a2, #0 bl main STACK_START: .word STACK_BASE undefined_instruction: software_interrupt: prefetch_abort: data_abort: not_used: irq: fiq:

          2410init.c file int main(int argc,char **argv) { u32 test = 0; void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL _BASE; //壓縮后的IMAGE地址 int i,k=0; // downPt=(RAM_COMPRESSED_KERNEL_BASE); chkBs=(_RAM_STARTADDRESS);//SDRAM開始的地方 // fromPt=(FLASH_LINUXKERNEL); MMU_EnableICache(); ChangeClockDivider(1,1); // 1:2:4 ChangeMPllValue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz Port_Init();//設(shè)置I/O端口,在使用com口前,必須調(diào)用這個函數(shù),否則通信芯片根本得不到數(shù)據(jù) Uart_Init(PCLK, 115200);//PCLK使用默認(rèn)的200000,撥特率115200 Uart_SendString("ntLinux S3C2410 Nor BOOTLOADERn"); Uart_SendString("ntChecking SDRAM 2410loader.c...n"); for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//


          //根據(jù)我的經(jīng)驗,最好以一個字節(jié)為遞增,我們的板子,在256byte遞增檢測的時候是沒問題的,但是
          //以1byte遞增就出錯了,第13跟數(shù)據(jù)線隨幾的會冒”1”,檢測出來是硬件問題,現(xiàn)象如下
          //用仿真器下代碼測試SDRAM,開始沒貼28F128A3J FLASH片子,測試結(jié)果很好,但在上了FLASH片子//之后,測試數(shù)據(jù)(data)為0x00000400
          連續(xù)成批寫入讀出時,操作大約1k左右內(nèi)存空間就會出錯,//而且隨機。那個出錯數(shù)據(jù)總是變?yōu)?x00002400,數(shù)據(jù)總線10位和13位又沒短路
          發(fā)生。用其他數(shù)據(jù)//測試比如0x00000200;0x00000800沒這問題。dx幫忙。
          //至今沒有解決,所以我用不了Flash.
          {
          chkPt1 = chkBs;
          *(u32 *)chkPt1 = test;//寫數(shù)據(jù)
          if(*(u32 *)chkPt1==1024))//讀數(shù)據(jù)和寫入的是否一樣?
          {
          chkPt1 += 4;
          Led_Display(1);
          Led_Display(2);
          Led_Display(3);
          Led_Display(4);
          }
          else
          goto error;
          }
          Uart_SendString("ntSDRAM Check Successful!ntMemory Maping...");
          get_memory_map();
          //獲得可用memory 信息,做成列表,后面會作為啟動參數(shù)傳給KERNEL
          //所謂內(nèi)存映射就是指在4GB 物理地址空間中有哪些地址范圍被分配用來尋址系統(tǒng)的 RAM 單元。
          Uart_SendString("ntMemory Map Successful!n");
          //我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面這段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.

          Uart_SendString("tLoading KERNEL IMAGE from FLASH... n "); Uart_SendString("tand copy KERNEL IMAGE to SDRAM at 0x31000000n"); Uart_SendString("ttby LEIJUN DONG dongleijun4000@hotmail.com n"); for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString("ttloading COMPRESSED RAMDISK...n"); downPt=(RAM_COMPRESSED_RAMDISK_BASE); fromPt=(FLASH_RAMDISK_BASE); for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString("ttloading jffs2...n"); downPt=(RAM_JFFS2); fromPt=(FLASH_JFFS2); for(k = 0;k < (1024*1024/32);k++,downPt += 1,fromPt += 1) * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString( "Load Success...Run...n "); setup_start_tag();//開始設(shè)置啟動參數(shù) setup_memory_tags();//內(nèi)存印象 setup_commandline_tag("console=ttyS0,115200n8");//啟動命令行 setup_initrd2_tag();//root device setup_RAMDISK_tag();//ramdisk image setup_end_tag(); asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i)); i &= ~0x1000; asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i)); asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));

          //下面這行就跳到了COMPRESSED KERNEL的首地址
          theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));
          //啟動kernel時候,I-cache可以開也可以關(guān),r0必須是0,r1必須是CPU型號
          (可以從linux/arch/arm/tools/mach-types中找到),r2必須是參數(shù)的物理開始地址

          error: Uart_SendString("nnPanic SDRAM check error!n"); return 0; } static void setup_start_tag(void) { params = (struct tag *)RAM_BOOT_PARAMS;//啟動參數(shù)開始的地址 params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size(tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next(params); } static void setup_memory_tags(void) { int i; for(i = 0; i < NUM_MEM_AREAS; i++) { if(memory_map[i].used) { params->hdr.tag = ATAG_MEM; params->hdr.size = tag_size(tag_mem32); params->u.mem.start = memory_map[i].start; params->u.mem.size = memory_map[i].len; params = tag_next(params); } } } static void setup_commandline_tag(char *commandline) { int i = 0; params->hdr.tag = ATAG_CMDLINE; params->hdr.size = 8; //console=ttyS0,115200n8 strcpy(params->u.cmdline.cmdline, p); params = tag_next(params); } static void setup_initrd2_tag(void) { params->hdr.tag = ATAG_INITRD2; params->hdr.size = tag_size(tag_initrd); params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE; params->u.initrd.size = 2047;//k byte params = tag_next(params); } static void setup_ramdisk_tag(void) { params->hdr.tag = ATAG_RAMDISK; params->hdr.size = tag_size(tag_ramdisk); params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE; params->u.ramdisk.size = 7.8*1024; //k byte params->u.ramdisk.flags = 1; // automatically load ramdisk params = tag_next(params); } static void setup_end_tag(void) { params->hdr.tag = ATAG_NONE; params->hdr.size = 0; } void Uart_Init(int pclk,int baud)//串口是很重要的 { int i; if(pclk == 0) pclk = PCLK; rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO dISAble rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC dISAble //UART0 rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits 下面這段samsung好象寫的不太對,但是我按照Normal,No parity,1 stop,8 bits算出來的確是0x245 // [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0] // Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode // 0 1 0 , 0 1 0 0 , 01 01 // PCLK Level Pulse DISAble Generate Normal Normal Interrupt or Polling rUCON0 = 0x245; // Control register rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0 delay(10); }

          經(jīng)過以上的折騰,接下來就是kernel的活了.能不能啟動kernel,得看你編譯kernel的水平了.

          ARM嵌入式入門的建議

          由于很多人總問這個問題,所以這里做一個總結(jié)文檔供大家參考。這里必須先說明,以下的步驟都是針對Linux系統(tǒng)的,并不面向WinCE。也許你會注意到,現(xiàn)在做嵌入式的人中,做linux研究的人遠(yuǎn)比做WinCE的人多,很多產(chǎn)家提供的資料也是以linux為主。我一直很難理解,其實WinCE的界面比linux的界面好看多了,使用起來也很方便,更為重要的是,WinCE的開發(fā)和Windows下的開發(fā)基本一樣,學(xué)起來簡單得多,但是學(xué)linux或者使用linux做嵌入式的人就是遠(yuǎn)比WinCE多。在和很多工作的人交流時我了解到,他們公司從沒考慮使用WinCE,因為成本高,都是使用linux進行開發(fā)。我讀研究生的的實驗室中也沒有使用WinCE的,大都研究linux,也有少部分項目使用vxwork,但是就沒有聽說過使用WinCE的,原因就是開源!當(dāng)然現(xiàn)在WinCE6.0聽說也開源,不過在成本和資源上linux已經(jīng)有了無人能擋的優(yōu)勢。與此相對應(yīng)的是,越來越多的電子廠商已經(jīng)開始使用linux開發(fā)產(chǎn)品。舉個例子,Google近期開發(fā)的智能手機操作系統(tǒng)Android其實就是使用linux-2.6.23內(nèi)核進行改進得到的。

          第一,學(xué)習(xí)基本的裸機編程。
          對于學(xué)硬件的人而言,必須先對硬件的基本使用方法有感性的認(rèn)識,更必須深刻認(rèn)識該硬件的控制方式,如果一開始就學(xué)linux系統(tǒng)、學(xué)移植那么只會馬上就陷入一個很深的漩渦。我在剛剛開始學(xué)ARM的時候是選擇ARM7(主意是當(dāng)時ARM9還很貴),學(xué)ARM7的時候還是保持著學(xué)51單片機的思維,使用ADS去編程,第一個實驗就是控制led。學(xué)過一段時間ARM的人都會笑這樣很笨,實際上也不是,我倒是覺得有這個過程會好很多,因為無論做多復(fù)雜的系統(tǒng)最終都會落實到這些最底層的硬件控制,因此對這些硬件的控制有了感性的認(rèn)識就好很多了
          學(xué)習(xí)裸機的編程的同時要好好理解這個硬件的構(gòu)架、控制原理,這些我稱他為理解硬件。所謂的理解硬件就是說,理解這個硬件是怎么組織這么多資源的,這些資源又是怎么由cpu、由編程進行控制的。比如說,s3c2410中有AD轉(zhuǎn)換器,有GPIO(通用IO口),還有nandflash控制器,這些東西都有一些寄存器來控制,這些寄存器都有一個地址,那么這些地址是什么意思?又怎么通過寄存器來控制這些外圍設(shè)備的運轉(zhuǎn)?還有,norflash內(nèi)部的每一個單元在這個芯片的內(nèi)存中都有一個相應(yīng)的地址單元,那么這些地址與剛剛說的寄存器地址又有什么關(guān)系?他們是一樣的嗎?而與norflash相對應(yīng)的nandflash內(nèi)部的儲存單元并不是線性排放的,那么s3c2410怎么將nandflash的地址映射在內(nèi)存空間上進行使用?或者簡單地說應(yīng)該怎么用nandflash?再有,使用ADS進對ARM9行編程時都需要使用到一個初始化的匯編文件,這個文件究竟有什么用?他里面的代碼是什么意思?不要這個可以嗎?
          諸如此類都是對硬件的理解,理解了這些東西就對硬件有很深的理解了,這對以后更深一步的學(xué)習(xí)將有很大的幫助,如果跳過這一步,我相信越往后學(xué)越會覺得迷茫,越覺得這寫東西深不可測。因為,你的根基沒打好。
          不過先聲明一下,本人并沒有使用ADS對ARM9進行編程,我是學(xué)完ARM7后直接就使用ARM9學(xué)linux系統(tǒng)的,因此涉及使用ADS對ARM9進行編程的問題我很難回答^_^,自己去研究研究吧。
          對于這部分不久將提供一份教程,這個教程中的例程并不是我為我們所代理的板子寫的,是我在我們學(xué)院實驗室拿的,英培特為他們自己的實驗箱寫的,不過很有借鑒意義,可以作為一份有價值的參考。

          第二,使用linux系統(tǒng)進行一些基本的實驗。
          在買一套板子的時候一般會提供一些linux的試驗例程,好好做一段時間這個吧,這個過程也是很有意義的,也是為進一步的學(xué)習(xí)積累感性認(rèn)識,你能想象一個從沒有使用過linux系統(tǒng)的人能學(xué)好linux的編程嗎?好好按照手冊上的例程做一做里面的實驗,雖然有點娃娃學(xué)走路,有點弱智,但是我想很多高手都會經(jīng)歷這個過程。
          在這方面我們深藍(lán)科技目前沒有計劃提供相應(yīng)的例程,主要是開發(fā)板的提供商會提供很豐富的例程,我們不做重復(fù)工作,只提供他們沒有的、最有價值的東西給大家。

          第三,研究完整的linux系統(tǒng)的的運行過程。
          所謂完整的linux系統(tǒng)包括哪些部分呢?
          三部分:bootloader、linux kernel(linux內(nèi)核)、rootfile(根文件系統(tǒng))。
          那么這3部分是怎么相互協(xié)作來構(gòu)成這個系統(tǒng)的呢?各自有什么用呢?三者有什么聯(lián)系?怎么聯(lián)系?系統(tǒng)的執(zhí)行流程又是怎么樣的呢?搞清楚這個問題你對整個系統(tǒng)的運行就很清楚了,對于下一步制作這個linux系統(tǒng)就打下了另一個重要的根基。介紹這方面的資料網(wǎng)上可以挖掘到幾噸,自己好好研究吧。

          第四,開始做系統(tǒng)移植。
          上面說到完整的linux有3部分,而且你也知道了他們之間的關(guān)系和作用,那么現(xiàn)在你要做的便是自己動手學(xué)會制作這些東西。
          當(dāng)然我不可能叫你編寫這些代碼,這不實現(xiàn)。事實上這個3者都能在網(wǎng)下載到相應(yīng)的源代碼,但是這個源代碼不可能下載編譯后就能在你的系統(tǒng)上運行,需要很多的修改,直到他能運行在你的板子上,這個修改的過程就叫移植。在進行移植的過程中你要學(xué)的東西很多,要懂的相關(guān)知識也很多,等你完成了這個過程你會發(fā)現(xiàn)你已經(jīng)算是一個初出茅廬的高手了。
          在這個過程中如果你很有研究精神的話你必然會想到看源代碼。很多書介紹你怎么閱讀linux源代碼,我不提倡無目的地去看linux源代碼,用許三多的話說,這沒有意義。等你在做移植的時候你覺得你必須去看源代碼時再去找基本好書看看,這里我推薦一本好書倪繼利的《linux內(nèi)核的分析與編程》,這是一本針對linux-2.6.11內(nèi)核的書,說得很深,建議先提高自己的C語言編程水平再去看。
          至于每個部分的移植網(wǎng)上也可以找到好多噸的資料,自己研究研究吧,不過要提醒的是,很多介紹自己經(jīng)驗的東西都或多或少有所保留,你按照他說的去做總有一些問題,但是他不會告訴你怎么解決,這時就要靠自己,如果自己都靠不住就找我一起研究研究吧,我也不能保證能解決你的問題,因為我未必遇到過你的問題,不過我相信能給你一點建議,也許有助你解決問題。
          這一步的最終目的是,從源代碼的官方主頁上(都是外國的,悲哀)下載標(biāo)準(zhǔn)的源代碼包,然后進行修改,最終運行在板子上。
          盜用阿基米德的一句話:“給我一根網(wǎng)線,我能將linux搞定”。

          第五,研究linux驅(qū)動程序的編寫。
          移植系統(tǒng)并不是最終的目的,最終的目的是開發(fā)產(chǎn)品,做項目,這些都要進行驅(qū)動程序的開發(fā)。
          Linux的驅(qū)動程序可以說是五花八門,linux2.4和linux2.6的編寫有相當(dāng)大的區(qū)別,就是同為linux2.6但是不同版本間的驅(qū)動程序也有區(qū)別,因此編寫linux的驅(qū)動程序變都不是那么容易的事情,對于最新版本的驅(qū)動程序的編寫甚至還沒有足夠的參考資料。那么我的建議就是使用、移植一個不算很新的版本內(nèi)核,這樣到時學(xué)驅(qū)動的編程就有足夠的資料了。
          這部分的推薦書籍可以參考另一篇文章《推薦幾本學(xué)習(xí)嵌入式linux的書籍》。

          第六,研究應(yīng)用程序的編寫。
          做作品做項目除了編寫驅(qū)動程序,最后還要編寫應(yīng)用程序?,F(xiàn)在的趨勢是圖形應(yīng)用程序的開發(fā),而圖形應(yīng)用程序中用得最多的還是qt/e函數(shù)庫。我一直就使用這個函數(shù)庫來開發(fā)自己的應(yīng)用程序,不過我希望你能使用國產(chǎn)的MiniGUI函數(shù)庫。盜用周杰倫的廣告詞就是“支持國產(chǎn),支持MiniGUI”。MiniGUI的編程比較相似Windows下的VC編程,比較容易上手,效果應(yīng)該說是相當(dāng)不錯的,我曾使用過來開發(fā)ARM7的程序。不過MiniGUI最大的不好就是沒有像qtopia這樣的圖形操作平臺,這大大限制了他的推廣,我曾經(jīng)幻想過與北京飛漫公司(就是MiniGUI的版權(quán)擁有者)合作使用MiniGUI函數(shù)庫開發(fā)像qtopia這樣的圖形操作平臺,不過由于水平有限這只能是幻想了,呵呵。
          完成這一步你基本就學(xué)完了嵌入式linux的全部內(nèi)容了。

          還有一個小小的經(jīng)驗想和大家分享。我在學(xué)習(xí)嵌入式linux的過程中很少問人,客觀原因是身邊的老師、同學(xué)師兄都沒有這方面的高手,主觀原因是我不喜歡問人,喜歡自己研究解決問題。這樣做有個好處,就是可以提高自己解決問題的能力,因為做這些東西總有很多問題你難以理解,別人也沒有這方面的經(jīng)驗,也不是所有問題都有人給你答案,這時必須要自己解決問題,這樣,個人的解決問題能力就顯得非常關(guān)鍵了。因此我的建議就是一般的問題到網(wǎng)上搜索一下,確實找不到答案了就問問高手,還是不行了就自己去研究,不要一味去等別人幫你解決問題。
          記住,問題是學(xué)習(xí)的最好機會。



          關(guān)鍵詞: ARM匯

          評論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();