對(duì)大多數(shù)童鞋來(lái)說(shuō)理解編譯器將.c文件編譯為.o文件并不大困難,但是卻難以明白最后鏈接的過(guò)程是什么作用和為什么要這樣做? 還有就是我們?cè)跇永こ讨袉?dòng)的文件為什么是自己編寫(xiě)的,它又怎樣做到將程序入口引導(dǎo)到main函數(shù)上,那么在這篇中我們就來(lái)深入的討論下這兩個(gè)話題。
鏈接器
鏈接的過(guò)程
首先,想要明白鏈接器的工作原理我們還是要來(lái)深入的看看整個(gè)編譯過(guò)程中具體的方式和原理。 我想大家都知道高級(jí)語(yǔ)言出現(xiàn)之前我們所用的匯編語(yǔ)言是除機(jī)器碼外最接近硬件的語(yǔ)言。使用匯編的代碼甚至可以很容易的手動(dòng)轉(zhuǎn)換為機(jī)器代碼。那么接下來(lái)的介紹就需要童鞋們多少了解一點(diǎn)匯編程序了(如8051的匯編)。 在單片機(jī)執(zhí)行的過(guò)程中命令被執(zhí)行的順序只有兩種:順序執(zhí)行和根據(jù)指令跳轉(zhuǎn)執(zhí)行位置。在匯編的代碼中,良好的寫(xiě)法是把各個(gè)函數(shù)分塊放在儲(chǔ)存的不同位置上,并在前面寫(xiě)上程序的標(biāo)號(hào) (如:“START:”),最后由編譯器將START程序處的地址裝入寫(xiě)有 START標(biāo)號(hào)跳轉(zhuǎn)指令的地方。 由此,我們就可以理解C語(yǔ)言被編譯為二進(jìn)制執(zhí)行文件的過(guò)程了,首先每個(gè)C文件都被編譯為了.o的,帶有未解析地址的中間文件,而后工具鏈的鏈接器將所有C文件的.o文件鏈接將他們有序的排列到儲(chǔ)存中,并將他們個(gè)個(gè)函數(shù)處的地址解析使得其他不同地方的函數(shù)能夠跳轉(zhuǎn)到該函數(shù)的入口地址,由此一個(gè)有序排列的可被單片機(jī)執(zhí)行的文件便生成了。 至于其中各個(gè).c文件產(chǎn)生的功能在單片機(jī)儲(chǔ)存中的排列順序和地址位置,在最后我們鏈接器工作產(chǎn)生的.map文件中是有顯示的,如下面從樣例工程中.map文件中復(fù)制的片段:.isr_vector0x080000000x134 0x08000000.=ALIGN(0x4) *(.isr_vector) .isr_vector0x080000000x134./USER/CoIDE_startup.o 0x08000000g_pfnVectors 0x08000134.=ALIGN(0x4) .text0x080001340x1464 0x08000134.=ALIGN(0x4) *(.text) .text0x080001340x5c/home/yangliu/Library/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/armv7-m/crtbegin.o .text0x080001900x80./USER/main.o 0x08000190main .text0x080002100x68./USER/CoIDE_startup.o 0x08000210Reset_Handler 0x08000210Default_Reset_Handler 0x08000268EXTI2_IRQHandler 0x08000268TIM8_TRG_COM_IRQHandler 0x08000268TIM8_CC_IRQHandler 0x08000268TIM1_CC_IRQHandler 0x08000268TIM6_IRQHandler 0x08000268PVD_IRQHandler 0x08000268SDIO_IRQHandler 0x08000268EXTI3_IRQHandler 0x08000268EXTI0_IRQHandler 0x08000268I2C2_EV_IRQHandler 0x08000268ADC1_2_IRQHandler123456789101112131415161718192021222324252627 所以我們的gcc鏈接器就是用來(lái)做這個(gè)工作的,當(dāng)然不只是gcc的鏈接器,世上所有c程序的編譯工具鏈應(yīng)該都是以這種理念設(shè)計(jì)的。。當(dāng)然不排除我見(jiàn)識(shí)少,沒(méi)見(jiàn)過(guò)特殊的。
工具鏈中鏈接器的用法
在實(shí)際中,鏈接器的執(zhí)行程序?qū)嶋H上是arm-none-eabi-ld這個(gè)文件,但是我再實(shí)際的編寫(xiě)過(guò)程中在遇到.c和.cpp文件混合的工程中,ld會(huì)在鏈接過(guò)程中報(bào)錯(cuò)。而對(duì)此官方的說(shuō)明是推薦使用arm-none-eabi-gcc指令來(lái)鏈接工程,它會(huì)自動(dòng)的調(diào)用ld程序且不會(huì)出現(xiàn)上面這種情況,所以接下來(lái)我們都是以arm-none-eabi-gcc指令來(lái)介紹鏈接器工作的。$(CC)$(C_OBJ)-Tstm32_f103ze_gcc.ld-o$(TARGET).elf-mthumb-mcpu=cortex-m3-Wl,--start-group-lc-lm-Wl,--end-group-specs=nano.specs-specs=nosys.specs-static-Wl,-cref,-u,Reset_Handler-Wl,-Map=Project.map-Wl,--gc-sections-Wl,--defsym=malloc_getpagesize_P=0x80 1 在上面這段截取自樣例工程makefile的代碼片中,我們可以看到在最后生成.elf文件時(shí)的指令。變量CC為arm-none-eabi-gcc,變量OBJ為所有.o文件。**-o xx.elf**為鏈接.o文件生成.elf文件。
ld文件
在鏈接的過(guò)過(guò)程中與編譯過(guò)程相比其中顯著的與編譯指令不同的便是 -T xx.ld。 在這里 -T xx.ld實(shí)際上是調(diào)用了一個(gè).ld的文件,那么.ld文件是做什么的呢? 這里就比較高深了,在51單片機(jī)中我們知道最后在生成代碼后51單片機(jī)內(nèi)存中會(huì)有如 code、xdata、data的區(qū)段,來(lái)講代碼中執(zhí)行部分、變量部分等分區(qū)塊放置,而.ld就是一種鏈接器使用的規(guī)則性文件,他告訴鏈接器單片機(jī)系統(tǒng)的ROM、RAM的地址和他們的大小等信息,并指示鏈接器將什么代碼保存在什么位置。 對(duì)于.ld文件它是有一套自己的語(yǔ)法及設(shè)置參數(shù)的規(guī)則的,大家可以不具體作了解,但求看懂其中一部分的信息。/*EntryPoint*/ ENTRY(Reset_Handler) /*Highestaddressoftheusermodestack*/ _estack=0x20010000;/*endof64KRAM*/ /*Generatealinkerrorifheapandstackdon'tfitintoRAM*/ _Min_Heap_Size=0;/*requiredamountofheap*/ _Min_Stack_Size=0x200;/*requiredamountofstack*/ /*Specifythememoryareas*/ MEMORY { FLASH(rx):ORIGIN=0x08000000,LENGTH=512K RAM(xrw):ORIGIN=0x20000000,LENGTH=64K MEMORY_B1(rx):ORIGIN=0x60000000,LENGTH=0K } SECTIONS { /*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH /*TheprogramcodeandotherdatagoesintoFLASH*/ .text: { .=ALIGN(4); *(.text)/*.textsections(code)*/ *(.text*)/*.text*sections(code)*/ *(.glue_7)/*gluearmtothumbcode*/ *(.glue_7t)/*gluethumbtoarmcode*/ *(.eh_frame) KEEP(*(.init)) KEEP(*(.fini)) .=ALIGN(4); _etext=.;/*defineaglobalsymbolsatendofcode*/ }>FLASH /*ConstantdatagoesintoFLASH*/ .rodata: { .=ALIGN(4); *(.rodata)/*.rodatasections(constants,strings,etc.)*/ *(.rodata*)/*.rodata*sections(constants,strings,etc.)*/ .=ALIGN(4); }>FLASH .ARM.extab:{*(.ARM.extab*.gnu.linkonce.armextab.*)}>FLASH .ARM:{ __exidx_start=.; *(.ARM.exidx*) __exidx_end=.; }>FLASH .preinit_array: { PROVIDE_HIDDEN(__preinit_array_start=.); KEEP(*(.preinit_array*)) PROVIDE_HIDDEN(__preinit_array_end=.); }>FLASH .init_array: { PROVIDE_HIDDEN(__init_array_start=.); KEEP(*(SORT(.init_array.*))) KEEP(*(.init_array*)) PROVIDE_HIDDEN(__init_array_end=.); }>FLASH .fini_array: { PROVIDE_HIDDEN(__fini_array_start=.); KEEP(*(SORT(.fini_array.*))) KEEP(*(.fini_array*)) PROVIDE_HIDDEN(__fini_array_end=.); }>FLASH /*usedbythestartuptoinitializedata*/ _sidata=LOADADDR(.data); /*InitializeddatasectionsgoesintoRAM,loadLMAcopyaftercode*/ .data: { .=ALIGN(4); _sdata=.;/*createaglobalsymbolatdatastart*/ *(.data)/*.datasections*/ *(.data*)/*.data*sections*/ .=ALIGN(4); _edata=.;/*defineaglobalsymbolatdataend*/ }>RAMAT>FLASH /*Uninitializeddatasection*/ .=ALIGN(4); .bss: { /*Thisisusedbythestartupinordertoinitializethe.bsssecion*/ _sbss=.;/*defineaglobalsymbolatbssstart*/ __bss_start__=_sbss; *(.bss) *(.bss*) *(COMMON) .=ALIGN(4); _ebss=.;/*defineaglobalsymbolatbssend*/ __bss_end__=_ebss; }>RAM /*User_heap_stacksection,usedtocheckthatthereisenoughRAMleft*/ ._user_heap_stack: { .=ALIGN(4); PROVIDE(end=.); PROVIDE(_end=.); .=.+_Min_Heap_Size; .=.+_Min_Stack_Size; .=ALIGN(4); }>RAM /*MEMORY_bank1section,codemustbelocatedhereexplicitly*/ /*Example:externintfoo(void)__attribute__((section(".mb1text")));*/ .memory_b1_text: { *(.mb1text)/*.mb1textsections(code)*/ *(.mb1text*)/*.mb1text*sections(code)*/ *(.mb1rodata)/*read-onlydata(constants)*/ *(.mb1rodata*) }>MEMORY_B1 /*Removeinformationfromthestandardlibraries*/ /DISCARD/: { libc.a(*) libm.a(*) libgcc.a(*) } .ARM.attributes0:{*(.ARM.attributes)} }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 至于鏈接時(shí)其他的鏈接參數(shù)大部分和編譯參數(shù)相同,不同的也就是:
--start-group-lc-lm-Wl,--end-group-specs=nano.specs-specs=nosys.specs-static-Wl,-cref,-u,Reset_Handler-Wl,-Map=Project.map-Wl,--gc-sections-Wl,--defsym=malloc_getpagesize_P=0x80 1 對(duì)于這些指令我只是大致的清楚是什么,但具體的一些參數(shù)我也不大了解,如果大家有興趣可以自己檢索一下,或者最好的辦法就是到工具鏈中的說(shuō)明文檔尋找說(shuō)明。 在我們實(shí)際的工程建立及編寫(xiě)中,我們使用的都是從別處找來(lái)的ld文件,在樣例工程中的.ld文件只要在內(nèi)存大小堆棧等位置上根據(jù)stm32具體的型號(hào)稍作修改就可以使用了。 或者在之后我們介紹libopencm3的驅(qū)動(dòng)庫(kù)中,其作者就有寫(xiě)好的所有芯片型號(hào)的ld文件,我們也可以從那里復(fù)制并修改以用于我們自己的工程。其中l(wèi)d文件中一些變量如堆棧大小等我們會(huì)在講解啟動(dòng)文件的過(guò)程中來(lái)解析,因?yàn)閱?dòng)文件和ld文件中的東西息息相關(guān)。
啟動(dòng)文件
很多剛接觸stm32不久的童鞋對(duì)stm32的啟動(dòng)文件的印象大多就是教程里的一句話:?jiǎn)?dòng)文件就是stm32在執(zhí)行main函數(shù)前將系統(tǒng)初始化并把PC(即程序計(jì)數(shù)器,也就是當(dāng)前執(zhí)行代碼位置的指針)設(shè)置到main函數(shù)的文件。確實(shí)在KEIL或IAR之類(lèi)的集成開(kāi)發(fā)環(huán)境中我們不必關(guān)心啟動(dòng)文件的存在,但是在我們的gcc的使用中,我們就需要去理解這個(gè)文件了。 在樣例工程中,我放置的是一個(gè)從CooCox開(kāi)源集成開(kāi)發(fā)環(huán)境中拷貝修改的啟動(dòng)文件,在USER目錄下的CoIDE_startup.c,這里我就不放文件的內(nèi)容了,我們只去其中一部分來(lái)講。 想要理解啟動(dòng)代碼,首先我們需要看看GNU編譯器的與其他編譯器不同的新特性之一:*_attribute*((xxx)),在gcc中attribute關(guān)鍵詞用于為函數(shù)或變量等賦予特性,就像MDK中的weak 說(shuō)明符類(lèi)似,只不過(guò)attribute的使用更具多樣性且靈活。 其次我們要知道,在我們使用的Cortex-M3內(nèi)核中,程序執(zhí)行的最開(kāi)始會(huì)從ROM首地址的第一位取出MSP的數(shù)值(即棧頂?shù)刂分羔?a href="http://hljzzgx.com/tags/寄存器/" target="_blank">寄存器),然后會(huì)在第二位取出復(fù)位中斷函數(shù)的地址,并跳轉(zhuǎn)過(guò)去。且在一般來(lái)說(shuō),單片機(jī)系統(tǒng)的所有中斷向量表初始時(shí)會(huì)放在ROM的最前段,所以我們定義了一個(gè)函數(shù)指針數(shù)組在堆棧初始值的后方,構(gòu)成了這樣一個(gè)被裝入ROM首段地址的數(shù)據(jù):__attribute__((used,section(".isr_vector"))) void(*constg_pfnVectors[])(void)= { /*----------CoreExceptions-------------------------------------------------*/ (void*)&pulStack[STACK_SIZE],/*! Reset_Handler,/*! NMI_Handler,/*! HardFault_Handler,/*! MemManage_Handler,/*! BusFault_Handler,/*! UsageFault_Handler,/*! 0,0,0,0,/*! SVC_Handler,/*! DebugMon_Handler,/*! 0,/*! PendSV_Handler,/*! SysTick_Handler,/*! /*----------ExternalExceptions---------------------------------------------*/ WWDG_IRQHandler,/*!?0:?Window?Watchdog??????????????????????*/ PVD_IRQHandler,/*!?1:?PVD?through?EXTI?Line?detect?????????*/ TAMPER_IRQHandler,/*!?2:?Tamper???????????????????????????????*/ RTC_IRQHandler,/*!?3:?RTC??????????????????????????????????*/ FLASH_IRQHandler,/*!?4:?Flash????????????????????????????????*/ RCC_IRQHandler,/*!?5:?RCC??????????????????????????????????*/ EXTI0_IRQHandler,/*!?6:?EXTI?Line?0??????????????????????????*/ EXTI1_IRQHandler,/*!?7:?EXTI?Line?1??????????????????????????*/ EXTI2_IRQHandler,/*!?8:?EXTI?Line?2??????????????????????????*/ EXTI3_IRQHandler,/*!?9:?EXTI?Line?3??????????????????????????*/ EXTI4_IRQHandler,/*!10:?EXTI?Line?4??????????????????????????*/ DMA1_Channel1_IRQHandler,/*!11:?DMA1?Channel?1???????????????????????*/ DMA1_Channel2_IRQHandler,/*!12:?DMA1?Channel?2???????????????????????*/ DMA1_Channel3_IRQHandler,/*!13:?DMA1?Channel?3???????????????????????*/ DMA1_Channel4_IRQHandler,/*!14:?DMA1?Channel?4???????????????????????*/ DMA1_Channel5_IRQHandler,/*!15:?DMA1?Channel?5???????????????????????*/ DMA1_Channel6_IRQHandler,/*!16:?DMA1?Channel?6???????????????????????*/ DMA1_Channel7_IRQHandler,/*!17:?DMA1?Channel?7???????????????????????*/ ADC1_2_IRQHandler,/*!18:?ADC1?&?ADC2??????????????????????????*/ USB_HP_CAN1_TX_IRQHandler,/*!19:?USB?High?Priority?or?CAN1?TX?????????*/ USB_LP_CAN1_RX0_IRQHandler,/*!20:?USB?Low??Priority?or?CAN1?RX0????????*/ CAN1_RX1_IRQHandler,/*!21:?CAN1?RX1?????????????????????????????*/ CAN1_SCE_IRQHandler,/*!22:?CAN1?SCE?????????????????????????????*/ EXTI9_5_IRQHandler,/*!23:?EXTI?Line?9..5???????????????????????*/ TIM1_BRK_IRQHandler,/*!24:?TIM1?Break???????????????????????????*/ TIM1_UP_IRQHandler,/*!25:?TIM1?Update??????????????????????????*/ TIM1_TRG_COM_IRQHandler,/*!26:?TIM1?Trigger?and?Commutation?????????*/ TIM1_CC_IRQHandler,/*!27:?TIM1?Capture?Compare?????????????????*/ TIM2_IRQHandler,/*!28:?TIM2?????????????????????????????????*/ TIM3_IRQHandler,/*!29:?TIM3?????????????????????????????????*/ TIM4_IRQHandler,/*!30:?TIM4?????????????????????????????????*/ I2C1_EV_IRQHandler,/*!31:?I2C1?Event???????????????????????????*/ I2C1_ER_IRQHandler,/*!32:?I2C1?Error???????????????????????????*/ I2C2_EV_IRQHandler,/*!33:?I2C2?Event???????????????????????????*/ I2C2_ER_IRQHandler,/*!34:?I2C2?Error???????????????????????????*/ SPI1_IRQHandler,/*!35:?SPI1?????????????????????????????????*/ SPI2_IRQHandler,/*!36:?SPI2?????????????????????????????????*/ USART1_IRQHandler,/*!37:?USART1???????????????????????????????*/ USART2_IRQHandler,/*!38:?USART2???????????????????????????????*/ USART3_IRQHandler,/*!39:?USART3???????????????????????????????*/ EXTI15_10_IRQHandler,/*!40:?EXTI?Line?15..10?????????????????????*/ RTCAlarm_IRQHandler,/*!41:?RTC?Alarm?through?EXTI?Line??????????*/ USBWakeUp_IRQHandler,/*!42:?USB?Wakeup?from?suspend??????????????*/ TIM8_BRK_IRQHandler,/*!43:?TIM8?Break???????????????????????????*/ TIM8_UP_IRQHandler,/*!44:?TIM8?Update??????????????????????????*/ TIM8_TRG_COM_IRQHandler,/*!45:?TIM8?Trigger?and?Commutation?????????*/ TIM8_CC_IRQHandler,/*!46:?TIM8?Capture?Compare?????????????????*/ ADC3_IRQHandler,/*!47:?ADC3?????????????????????????????????*/ FSMC_IRQHandler,/*!48:?FSMC?????????????????????????????????*/ SDIO_IRQHandler,/*!49:?SDIO?????????????????????????????????*/ TIM5_IRQHandler,/*!50:?TIM5?????????????????????????????????*/ SPI3_IRQHandler,/*!51:?SPI3?????????????????????????????????*/ UART4_IRQHandler,/*!52:?UART4????????????????????????????????*/ UART5_IRQHandler,/*!52:?UART5????????????????????????????????*/ TIM6_IRQHandler,/*!53:?TIM6?????????????????????????????????*/ TIM7_IRQHandler,/*!54:?TIM7?????????????????????????????????*/ DMA2_Channel1_IRQHandler,/*!55:?DMA2?Channel1????????????????????????*/ DMA2_Channel2_IRQHandler,/*!56:?DMA2?Channel2????????????????????????*/ DMA2_Channel3_IRQHandler,/*!57:?DMA2?Channel3????????????????????????*/ DMA2_Channel4_5_IRQHandler,/*!58:?DMA2?Channel4?&?Channel5?????????????*/ (void*)0xF108F85F/*! }; 注意在數(shù)組的attribute的修飾中,它將函數(shù)的位置規(guī)定在了[section(“.isr_vector”)]的位置,而[.isr_vector]則在ld文件中定義在FLASH開(kāi)始的地方:
/*ThestartupcodegoesfirstintoFLASH*/ .isr_vector: { .=ALIGN(4); KEEP(*(.isr_vector))/*Startupcode*/ .=ALIGN(4); }>FLASH 所以顯而易見(jiàn)的,在啟動(dòng)后第二個(gè)周期里內(nèi)核讀取了復(fù)位向量表的地址并跳轉(zhuǎn)了過(guò)去,所以單片機(jī)的啟動(dòng)代碼必然存放于rest vector中,我們?cè)趩?dòng)文件中找到復(fù)位函數(shù):
#pragmaweakReset_Handler=Default_Reset_Handler voidDefault_Reset_Handler(void) { /*Initializedataandbss*/ unsignedlong*pulSrc,*pulDest; /*CopythedatasegmentinitializersfromflashtoSRAM*/ pulSrc=&_sidata; for(pulDest=&_sdata;pulDest&_edata;?) ??{ ????*(pulDest++)?=?*(pulSrc++); ??} ??/*Zerofillthebsssegment.Thisisdonewithinlineassemblysincethis willclearthevalueofpulDestifitisnotkeptinaregister.*/ __asm("ldrr0,=_sbss " "ldrr1,=_ebss " "movr2,#0 " ".thumb_func " "zero_loop: " "cmpr0,r1 " "itlt " "strltr2,[r0],#4 " "bltzero_loop"); /*Setupthemicrocontrollersystem.*/ SystemInit(); /*Calltheapplication'sentrypoint.*/ main(); } 在啟動(dòng)函數(shù)中我們可以清晰地看到,在最后一步中,單片機(jī)的程序被轉(zhuǎn)入到了main函數(shù)的入口,那么在執(zhí)行main函數(shù)之前,C語(yǔ)言,和內(nèi)聯(lián)匯編程序干了什么呢?首先頭位置的C語(yǔ)言將終端向量表從ROM頭位置,復(fù)制到了RAM頭位置(即:0x20000000),這里在RAM中的終端向量表時(shí)間上沒(méi)有沒(méi)我們用到,當(dāng)然這是因?yàn)樵贛3的內(nèi)核中,它允許用戶(hù)在NIVC的寄存器中重新定義終端向量表的位置,我們可以使用
NVIC_SetVectorTable(NVIC_VectTab_FLASH,0); 這個(gè)函數(shù)來(lái)將終端向量表設(shè)置到到0x20000000位置。該功能實(shí)際上是用于方便裝有系統(tǒng)的環(huán)境中使用,可以加快終端響應(yīng)的速度,同時(shí)可以快速的動(dòng)態(tài)的更改終端處理的程序。 當(dāng)然在我們的應(yīng)用中并未使用到這一特性,所以此處的復(fù)制中斷向量表的操作是可以刪除的,它在此的作用只是為了防止用戶(hù)在程序中使用了重定向向量表語(yǔ)句而使得程序跑飛所添加的。因?yàn)榻K端向量是系統(tǒng)最基礎(chǔ)穩(wěn)定性的保證,如果在硬件錯(cuò)誤發(fā)生等中斷發(fā)生的情況下單片機(jī)無(wú)法正確的跳轉(zhuǎn),會(huì)對(duì)代碼調(diào)試和系統(tǒng)穩(wěn)定運(yùn)行帶來(lái)嚴(yán)重的影響。 之后緊跟的這幾條匯編代碼實(shí)現(xiàn)的是:全局變量與靜態(tài)變量的初始化并將其從flash中調(diào)入內(nèi)存,即在C語(yǔ)言運(yùn)行全局變量與靜態(tài)變量的初始化操作。在此之后, SystemInit();函數(shù)被調(diào)用,配置好時(shí)鐘等參數(shù)。最后我們的main函數(shù)就可以執(zhí)行啦~。 這便是是我們?cè)谶@個(gè)例程中使用的啟動(dòng)文件,而在keil工程中,這個(gè)文件是用匯編代碼寫(xiě)成的,但這些文件功能都是一樣的,設(shè)置終端向量表,初始化全局與靜態(tài)變量,進(jìn)入main函數(shù),都是這樣的流程。 在gcc的環(huán)境中我們也可以是用匯編編寫(xiě)這樣的文件,我們面前的選擇有很多,當(dāng)然我們沒(méi)必要自己編寫(xiě)這些鏈接文件和啟動(dòng)代碼,在之后的實(shí)際的工程建立中我會(huì)告訴大家實(shí)際的方法。不過(guò)在此之前我們還是要先把基礎(chǔ)的內(nèi)容學(xué)好再說(shuō)。
其他的說(shuō)明
在文件中我們看到了**_sidata、_sdata**等變量,這些變量在文件的前面部分被定義為外部:externunsignedlong_sidata;/*! externunsignedlong_sdata;/*! externunsignedlong_edata;/*! externunsignedlong_sbss;/*! externunsignedlong_ebss;/*! 而該文件卻并未包含任何.h文件,那么他們從哪來(lái)的呢?細(xì)心的同學(xué)可能已經(jīng)注意到了,我們之前提到過(guò),這些變量的定義實(shí)際上都來(lái)自于ld文件中,他們?cè)趌d文件中被定義,最后鏈接器會(huì)將他們轉(zhuǎn)換為實(shí)際的地址給我們的程序所使用的。 最后再說(shuō)一下 attribute ((weak))屬性,該屬性表面其后的變量或是函數(shù)為弱申明,即在沒(méi)有其他申明情況下調(diào)用改函數(shù),而如果其他地方申明了,則會(huì)頂替該函數(shù)。所以在啟動(dòng)文件中,他們被用來(lái)修飾中斷處理函為中斷向量表提供一個(gè)默認(rèn)的地址,而當(dāng)用戶(hù)定義后,就將地址轉(zhuǎn)為用戶(hù)定義的位置。
總結(jié)
說(shuō)了這么多,這也是我們?cè)谶@個(gè)系列中比較難以理解的部分,因?yàn)樯婕暗搅薌NU C的特性和計(jì)算機(jī)編譯鏈接的最基礎(chǔ)的部分,還有Cortex-M3內(nèi)核工作的方式,但是請(qǐng)大家仔細(xì)的去理解學(xué)習(xí),如果看了這篇文章還不懂那就多查查相關(guān)的資料,當(dāng)你理解并貫通 這些知識(shí)時(shí),你會(huì)發(fā)現(xiàn)原來(lái)在單片機(jī)上c語(yǔ)言是這樣工作的,原來(lái)中斷系統(tǒng)是這么的重要,你會(huì)發(fā)現(xiàn)單片機(jī)在你的眼前是如此的透徹。 在最后,我們還要說(shuō)說(shuō),其實(shí)很多同學(xué)目前掌握的都是一個(gè)很簡(jiǎn)單的單片機(jī)應(yīng)用方式,這都是被keil、IAR之流慣壞的,實(shí)際上在單片機(jī)背后,其實(shí)際的工作復(fù)雜而又充滿(mǎn)著精致的設(shè)計(jì),這點(diǎn)我們會(huì)在之后的nuttx系統(tǒng)使用中見(jiàn)到。 那時(shí)你會(huì)發(fā)現(xiàn)原來(lái)我們使用的M3單片機(jī)還有這么多的我們之前沒(méi)用過(guò)的中斷,原來(lái)m3的內(nèi)核如此強(qiáng)大。對(duì)此我推薦大家還是學(xué)一遍51單片機(jī)的匯編教程,當(dāng)你理解和使用過(guò)匯編后,你會(huì)更容易理解未來(lái)的講解內(nèi)容,同時(shí)也更容易理解此篇的內(nèi)容。當(dāng)然如果大家有興趣可以先自己看看由宋巖前輩翻譯的Cortex-M3 權(quán)威指南,來(lái)提前感受一下Cortex-M3內(nèi)核的魅力。
審核編輯:湯梓紅
-
單片機(jī)
+關(guān)注
關(guān)注
6035文章
44554瀏覽量
634632 -
STM32
+關(guān)注
關(guān)注
2270文章
10895瀏覽量
355729 -
編譯器
+關(guān)注
關(guān)注
1文章
1623瀏覽量
49108
原文標(biāo)題:STM32高級(jí)開(kāi)發(fā)——鏈接器與啟動(dòng)文件
文章出處:【微信號(hào):技術(shù)讓夢(mèng)想更偉大,微信公眾號(hào):技術(shù)讓夢(mèng)想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論