1) derlenmiş ikili balo / flash evet yazılır. USB, seri, i2c, jtag, vb. Önyükleme işlemini anlamak için aygıta o aygıt tarafından neyin desteklendiğine bağlıdır.
2) Bu genellikle bir mikrodenetleyici için geçerli değildir, birincil kullanım durumu rom / flash ve ram içindeki verilere sahip olmaktır. Ne mimarisi olursa olsun. mikrodenetleyici olmayan bir bilgisayar için, bilgisayarınız, dizüstü bilgisayarınız, sunucunuz, program kalıcı (disk) 'den ram'a kopyalanır ve oradan çalıştırılır. Bazı mikrodenetleyiciler, tanımı ihlal etmiş gibi görünse de harvard iddiasında olanlar bile koç kullanmanıza izin verir. Harvard hakkında, koçu talimat tarafına eşleştirmenizi engelleyen hiçbir şey yoktur, sadece güç arttıktan sonra talimatları almak için bir mekanizmaya sahip olmanız gerekir (bu, tanımı ihlal eder, ancak harvard sistemleri bunu yararlı olmak için diğer mikrodenetleyicilerden daha fazla).
3) bir çeşit.
Her işlemci, tasarlandığı gibi deterministik bir şekilde "çizer". En yaygın yol, açıldıktan sonra çalıştırılacak ilk talimatların adresinin sıfırlama vektöründe olduğu, donanımın okuduğu ve daha sonra çalışmaya başlamak için bu adresi kullandığı bir vektör tablosudur. Diğer genel yol, işlemcinin iyi bilinen bir adreste bir vektör tablosu olmadan yürütmeye başlamasını sağlamaktır. Bazen çipin "kayışları", sıfırlamayı bırakmadan önce yüksek veya düşük bağlayabileceğiniz, mantığın farklı yollarla önyükleme yapmak için kullandığı bazı pimler olacaktır. CPU'nun kendisini, işlemci çekirdeğini sistemin geri kalanından ayırmanız gerekir. İşlemcinin nasıl çalıştığını anlayın ve yonga / sistem tasarımcılarının işlemcinin dışında çevresinde adres kod çözücüleri olduğunu anlayın, böylece cpus adres alanının bir kısmı bir flaşla iletişim kurar, ve bazıları ram ve bazıları çevre birimleri ile (uart, i2c, spi, gpio, vb.). İsterseniz aynı işlemci çekirdeğini alabilir ve farklı şekilde sarabilirsiniz. Kol veya mips bazlı bir şey satın aldığınızda elde ettiğiniz şey budur. kol ve mips, çip insanların kendi eşyalarını satın aldıkları ve etrafına saladıkları cpu çekirdekleri yaparlar, çeşitli nedenlerle bu şeyleri markadan markaya uyumlu hale getirmezler. Bu nedenle, çekirdek dışındaki herhangi bir şey söz konusu olduğunda nadiren genel bir kol sorusu sorabilir.
Bir mikrodenetleyici bir çip üzerinde bir sistem olmaya çalışır, bu yüzden onun uçucu olmayan belleği (flash / rom), uçucu (sram) ve cpu, çevre birimlerinin bir karışımı ile birlikte aynı çip üzerindedir. Ancak çip dahili olarak, flaşın cpu'nun önyükleme özelliklerine uyan adres alanına eşleneceği şekilde tasarlanmıştır. Örneğin cpu'nun 0xFFFC adresinde bir sıfırlama vektörü varsa, o zaman 1) aracılığıyla programlayabileceğimiz bu adrese yanıt veren flash / rom olması ve yararlı programlar için adres alanında yeterli flash / rom olması gerekir. Bir çip tasarımcısı, bu gereksinimleri karşılamak için 0xF000'den başlayarak 0x1000 bayt flaş kullanmayı seçebilir. Ve belki de daha düşük bir adrese ya da belki 0x0000'e ve ortada bir yerde çevre birimlerine bir miktar koç koydular.
Başka bir cpu mimarisi sıfır adresinde çalışmaya başlayabilir, bu yüzden tersini yapmaları gerekir, flaşı sıfır civarındaki bir adres aralığına cevap verecek şekilde yerleştirin. örneğin 0x0000 ila 0x0FFF diyelim. ve sonra başka bir koç koydum.
Çip tasarımcıları cpu'nun nasıl önyüklendiğini biliyor ve oraya kalıcı depolama (flash / rom) yerleştirdiler. Daha sonra, bu cpu'nun iyi bilinen davranışıyla eşleşmesi için önyükleme kodunu yazmak yazılım arkadaşlarına kalmıştır. Sıfırlama vektör adresini sıfırlama vektörüne ve önyükleme kodunuzu sıfırlama vektöründe tanımladığınız adrese yerleştirmelisiniz. Alet zinciri burada size çok yardımcı olabilir. bazen, esp ile nokta ve tıklama ides veya diğer sandboxes onlar sizin için işin çoğunu yapabilir tek yaptığınız apis üst düzey bir dilde (C) çağırmaktır.
Ancak, flash / rom'a yüklenen programın cpu'nun kablolu kablolu önyükleme davranışıyla eşleşmesi gerekir. Main () programının C kısmından önce ve eğer giriş noktanız olarak main kullanıyorsanız bazı şeylerin yapılması gerekir. AC programcısı, başlangıç değeri olan bir değişkeni bildirirken, bunun gerçekten çalışmasını beklediğini varsayar. Sabit değişkenler dışındaki değişkenler koç halindedir, ancak başlangıç değerine sahip bir değişkeniniz varsa, bu ilk değerin uçucu olmayan koçta olması gerekir. Yani bu .data segmentidir ve C önyüklemesinin .data öğelerini flash'tan ram'a kopyalaması gerekir (genellikle sizin için araç zinciri tarafından belirlenir). Başlangıç değeri olmadan bildirdiğiniz genel değişkenlerin, programınız başlamadan önce sıfır olduğu varsayılır, ancak gerçekten varsaymamanız gerekir ve neyse ki bazı derleyiciler başlatılmamış değişkenler hakkında uyarmaya başlar. Bu .bss segmentidir ve koçta bulunan C bootstrap sıfırları, içerik, sıfırlar, kalıcı bellekte depolanmak zorunda değildir, ancak başlangıç adresi ve ne kadar yapar. Alet zinciri yine burada size çok yardımcı oluyor. Ve son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz. kalıcı bellekte depolanmak zorunda değil, başlangıç adresi ve ne kadar saklandığı. Alet zinciri yine burada size çok yardımcı oluyor. Ve son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz. kalıcı bellekte depolanmak zorunda değil, başlangıç adresi ve ne kadar saklandığı. Alet zinciri yine burada size çok yardımcı oluyor. Son olarak, C programları yerel değişkenlere sahip olmayı ve diğer işlevleri çağırabileceğini düşündüğü için minimum bir yığın işaretçisini ayarlamanız gerekir. Sonra belki çiplere özgü başka şeyler yapılır, ya da çipin geri kalan kısmının C'de olmasına izin veririz.
Koldaki cortex-m serisi çekirdekler sizin için bazılarını yapacak, yığın işaretçisi vektör tablosunda, sıfırlamadan sonra çalıştırılacak koda işaret edecek bir sıfırlama vektörü var, böylece ne yapmanız gerekiyorsa vektör tablosunu oluşturmak için (genellikle yine de asm kullanırsınız) asm olmadan saf C'ye gidebilirsiniz. Şimdi .data'nızın kopyalanmasını veya .bss'inizi sıfırlamayın, böylece korteks-m tabanlı bir şeye asm olmadan gitmek istiyorsanız bunu kendiniz yapmak zorundasınız. Daha büyük özellik sıfırlama vektörü değil, donanımın önerilen silahları takip eden C çağrı kuralını izlediği ve sizin için kayıtları koruduğu kesinti vektörleridir ve bu vektör için doğru dönüşü kullanır, böylece her işleyicinin etrafına doğru asm sarmanız gerekmez ( veya araç zincirinin sizin için sarmasını sağlamak için hedefiniz için araç zincirine özgü direktiflere sahip olabilirsiniz).
Talaşa özgü şeyler, örneğin, mikrodenetleyiciler genellikle pil tabanlı sistemlerde kullanılır, bu nedenle düşük güç, bu nedenle bazıları çevre birimlerinin çoğu kapalıyken sıfırlanır ve bunları kullanmak için bu alt sistemleri açmanız gerekir. . Genellikle bir kristal veya dahili osilatörden düşük-ish bir saat hızı kullanılır. Ve sistem tasarımınız daha hızlı bir saate ihtiyacınız olduğunu gösterebilir, böylece bunu başlatırsınız. saatiniz flaş veya ram için çok hızlı olabilir, bu nedenle saati yükseltmeden önce bekleme durumlarını değiştirmeniz gerekebilir. Uart veya usb veya diğer arayüzleri kurmanız gerekebilir. o zaman uygulamanız kendi işini yapabilir.
Bilgisayar masaüstü, dizüstü bilgisayar, sunucu ve mikrodenetleyici, önyükleme / çalışma yöntemlerinden farklı değildir. Dışında çoğunlukla bir çip üzerinde değiller. Bios programı genellikle cpu'dan ayrı bir yonga flash / rom'undadır. Son zamanlarda x86 cpus, aynı pakete (pcie kontrolörleri, vb.) Destek yongaları olan şeyleri gittikçe daha fazla çekiyor olsa da, hala ram ve rom off çipinizin çoğuna sahipsiniz, ancak yine de bir sistem ve hala tam olarak çalışıyor aynı yüksek seviyede. İşlemci önyükleme işlemi iyi bilinir, kart tasarımcıları flash / rom'u işlemcinin önyüklendiği adres alanına yerleştirir. bu program (bir x86 bilgisayardaki BIOS'un bir kısmı) yukarıda belirtilen her şeyi yapar, çeşitli çevre birimleri başlatır, dram başlatır, pcie otobüslerini numaralandırır vb. Çoğu zaman kullanıcı tarafından bios ayarlarına veya cmos ayarlarını çağırmak için kullandığımıza göre oldukça yapılandırılabilir, çünkü o zaman teknoloji kullanılıyordu. Önemli değil, gidip değiştirebileceğiniz kullanıcı ayarları vardır, bios önyükleme koduna ne yaptığını nasıl değiştireceğini söylemek için.
farklı insanlar farklı terminoloji kullanır. bir çip önyükleme yapar, çalışan ilk kod budur. bazen bootstrap denir. kelime yükleyicili bir bootloader genellikle müdahale etmek için bir şey yapmazsanız, genel önyüklemeden daha büyük bir şeye, uygulamanıza veya işletim sisteminize götüren bir bootstrap olduğu anlamına gelir. ancak yükleyici kısmı, önyükleme işlemini yarıda kesebileceğiniz ve daha sonra diğer test programlarını yükleyebileceğiniz anlamına gelir. Örneğin gömülü bir linux sisteminde uboot kullandıysanız, bir tuşa basabilir ve normal önyüklemeyi durdurabilir, ardından bir test çekirdeğini koç içine indirebilir ve flash olanın yerine önyükleyebilirsiniz veya veya yeni çekirdeği indirebilir ve daha sonra önyükleyicinin yanıp sönmesini yazmasını sağlayın, böylece bir daha önyükleme yaptığınızda yeni şeyler çalıştırır.
İşlemcinin kendisi kadar, çevre birimlerinden gelen flaştan gelen koçu bilmeyen çekirdek işlemci. Önyükleyici, işletim sistemi, uygulama kavramı yoktur. Sadece çalıştırılacak CPU'ya beslenen bir talimatlar dizisidir. Bunlar, farklı programlama görevlerini birbirinden ayıran yazılım terimleridir. Yazılım kavramları birbirinden.
Bazı mikrodenetleyicilerde, çip satıcısı tarafından değiştirilemeyebilecek ayrı bir flaş veya ayrı bir flaş alanında sağlanan ayrı bir önyükleyici vardır. Bu durumda, sıfırlama serbest bırakılmadan önce onları yüksek veya düşük bağlarsanız, mantığa ve / veya bu önyükleyiciye ne yapması gerektiğini söyleyen bir pim veya pim (bunlara kayışlar diyorum) vardır, örneğin bir kayış kombinasyonu çipe o önyükleyiciyi çalıştırmasını söyleyin ve verilerin flaşa programlanması için uart'ta bekleyin. Kayışları başka bir şekilde ayarlayın ve programınız çip satıcılarının önyükleyicisini değil, çipin saha programlamasına veya programınızın çökmesinden kurtarılmasına izin verir. Bazen flaşı programlamanıza izin veren saf bir mantıktır. Bu bugünlerde oldukça yaygın,
Çoğu mikrodenetleyicinin koçtan çok daha fazla flaşa sahip olmasının nedeni, birincil kullanım durumunun programı doğrudan flaştan çalıştırmak ve yalnızca yığın ve değişkenleri kapsayacak kadar koç olmasıdır. Her ne kadar bazı durumlarda, doğru derlemek ve flash saklamak zorunda sonra aramadan önce kopyalamak zorunda ram programları çalıştırabilirsiniz.
DÜZENLE
flash.s
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
Yani bu bir korteks-m0 için bir örnektir, korteks-ms'nin hepsi bu örnekte olduğu gibi çalışır. Bu çip için, örneğin çip adres alanında 0x00000000 adresinde uygulama flaşı ve 0x20000000'de ram vardır.
Bir korteks-m önyükleme yolu, 0x0000 adresindeki 32 bit sözcüktür, yığın işaretçisini başlatmak için kullanılan adrestir. Bu örnek için çok fazla desteğe ihtiyacım yok, bu yüzden 0x20001000 yeterli olacak, açıkçası bu adresin altında koç olmalı (kol itme şekli, önce çıkarır sonra 0x20001000'i ayarlarsanız, yığıntaki ilk öğe 0x2000FFFC adresindeyse 0x2000FFFC kullanmak zorunda değilsiniz). 0x0004 adresindeki 32 bit sözcük, sıfırlama işleyicisinin adresidir, temel olarak sıfırlamadan sonra çalışan ilk koddur. Daha sonra, o korteks m çekirdeğine ve çipine özgü daha fazla kesme ve olay işleyicisi var, muhtemelen 128 veya 256 kadar, eğer bunları kullanmazsanız, onlar için tabloyu ayarlamanız gerekmez, gösteri için birkaç tane attım amaçlar.
Bu örnekte .data veya .bss ile uğraşmak zorunda değilim çünkü koda bakarak bu segmentlerde hiçbir şey olmadığını biliyorum. Eğer orada olsaydım, bir saniye içinde hallederdim.
Yığın kurulum, kontrol, .data halledilir, kontrol, .bss, kontrol, böylece C bootstrap şeyler yapılır, C için giriş işlevine dallayabilir, çünkü bazı derleyiciler işlevi görürlerse ekstra önemsiz ekler main () ve ana yola giderken, bu tam adı kullanmıyorum, burada C giriş noktası olarak notmain () kullandım. Bu nedenle sıfırlama işleyicisi notmain () öğesini çağırır, sonra notmain () döndürürse, yalnızca kötü adlandırılmış sonsuz bir döngü olan askıda kalmaya gider.
Araçlara hakim olmaya inanıyorum, pek çok kişi yok, ancak bulacağınız her çıplak metal geliştiricisinin, kendi uygulamalarını veya web sayfalarını yapacağınız kadar uzaktan değil, neredeyse tamamen özgür olması nedeniyle kendi işini yapması. . Yine kendi şeylerini yaparlar. Kendi bootstrap kodumu ve link komut dosyasını kullanmayı tercih ederim. Diğerleri araç zincirine güvenir veya işin çoğunun başka biri tarafından yapıldığı satıcıların sanal alanında oynarlar (ve bir şey kırılırsa, incinme dünyasındasınız ve çıplak metal şeyler sık sık ve dramatik yollarla kırılır).
Yani gnu araçları ile montaj, derleme ve bağlantı:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
Önyükleyici nasıl bir şey olduğunu nereden biliyor? Çünkü derleyici işi yaptı. İlk durumda, derleyici flash.s için kod üretti ve bunu yaparak etiketlerin nerede olduğunu bilir (etiketler sadece işlev adları veya değişken adları gibi adreslerdir), böylece bayt saymak ve vektörü doldurmak zorunda kalmadım Tabloda manuel olarak bir etiket adı kullandım ve montajcı bunu benim için yaptı. Şimdi, sıfırlama 0x14 adresi ise, derleyici neden vektör tablosuna 0x15 koyduğunu sorarsınız. Bu bir korteks-m ve önyükleme yapıyor ve sadece başparmak modunda çalışıyor. ARM ile başparmak moduna dallanıyorsa bir adrese dalladığınızda, kol modu sıfırlanırsa lsbit'in ayarlanması gerekir. Yani her zaman bu bit setine ihtiyacınız var. Araçları biliyorum ve bir etiket önce .thumb_func koyarak, bu etiket vektör tablosunda olduğu gibi veya dallanma için veya herhangi bir şekilde kullanılıyorsa. Alet zinciri lsbit ayarını biliyor. Yani burada 0x14 | 1 = 0x15 var. Aynı şekilde asmak için. Şimdi disassembler notmain () çağrısı için 0x1D göstermiyor, ancak araçların talimatı doğru şekilde oluşturduğundan endişelenmeyin.
Notmain'deki kod şimdi, bu yerel değişkenler kullanılmıyor, ölü kod. Derleyici, y'nin ayarlandığını ancak kullanılmadığını söyleyerek bu gerçeği yorumlar.
Adres alanını not edin, tüm şeyler 0x0000 adresinde başlar ve oradan gider, böylece vektör tablosu düzgün bir şekilde yerleştirilir, .text veya program alanı da düzgün yerleştirilir, nasıl flash.s var notmain.c kodunun önünde araçları bilerek, yaygın bir hata, bunu doğru yapmamak ve çökmek ve sert yanmaktır. IMO ilk kez önyükleme yapmadan hemen önce şeylerin yerleştirildiğinden emin olmak için sökmeniz gerekir, bir kez doğru yerde bir şeyler her zaman kontrol etmek zorunda değilsiniz. Sadece yeni projeler için ya da asılırlarsa.
Şimdi bazı insanları şaşırtan bir şey, herhangi bir iki derleyicinin aynı girdiden aynı çıktıyı üretmesini beklemek için hiçbir sebep olmamasıdır. Hatta farklı ayarlarla aynı derleyici. Clang kullanarak, llvm derleyici optimizasyonu olan ve olmayan bu iki çıkışı alıyorum
llvm / clang optimize
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
optimize edilmedi
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
bu derleyicinin eklemeyi optimize ettiği bir yalandır, ancak değişkenler için yığına iki öğe tahsis etti, çünkü bunlar koçta oldukları, ancak sabit adreslerde olmayan yığınlardaki yerel değişkenler değiştirir. Ancak derleyici, derleme zamanında y'yi hesaplayabildiğini ve çalışma zamanında hesaplamak için bir neden olmadığını fark etti, bu nedenle x için ayrılmış yığın alanına 1 ve y için ayrılmış yığın alanı için 2 yerleştirdi. derleyici bu alanı "değişken" için yığın artı 0 ve değişken x için yığın 4 bildiren dahili tablolarla "ayırır". derleyici uyguladığı kod C standardına veya bir C programcısının beklentilerine uyduğu sürece istediği her şeyi yapabilir. Derleyicinin işlev süresi boyunca x'i yığın + 4'te bırakması için hiçbir neden yoktur,
Montajcıda bir işlev kukla eklersem
.thumb_func
.globl dummy
dummy:
bx lr
ve sonra ara
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
çıktı değişir
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
iç içe geçmiş fonksiyonlara sahip olduğumuza göre, notmain işlevinin dönüş adresini koruması gerekir, böylece iç içe çağrı için dönüş adresini tıkayabilir. bunun nedeni kolun geri dönüşler için bir kayıt kullanmasıdır, eğer yığını x86 veya bazılarını iyi kullanırsa kullanırız ... yine de yığını kullanır ama farklı şekilde. Şimdi neden r4'ü ittiğini soruyorsunuz? Uzun zaman önce çağrılma kuralı, yığını 32 bit yerine bir bitlik 64 bit (iki sözcük) hizasında tutmak için değişti. Bu yüzden yığını hizalamak için bir şey itmeleri gerekiyor, bu yüzden derleyici keyfi olarak r4'ü seçti, neden önemli değil. Bu hedef için çağrı kuralına göre, r4 içine haşhaş bir hata olurdu, biz bir fonksiyon çağrısı üzerinde clobber r4 yok, biz r0 ila r3 clobber olabilir. r0, dönüş değeridir. Belki bir kuyruk optimizasyonu yapıyor gibi görünüyor,
Ancak x ve y matematiğinin kukla fonksiyona geçirilen 2 sabit kodlu bir değere optimize edildiğini görüyoruz (kukla özellikle ayrı bir dosyada kodlanmıştır, bu durumda asm, böylece derleyici fonksiyon çağrısını tamamen optimize etmeyecektir, Ben sadece notmain.c içinde C döndürülen bir kukla işlevi olsaydı, optimizer x, y ve kukla işlev çağrısı kaldırıldı çünkü hepsi ölü / yararsız kod).
Ayrıca flash.s kodu büyüdükçe notmainhiçbiriydi ve toolchain bizim için tüm adresleri yamalamaya özen gösterdi, bu yüzden bunu manuel olarak yapmak zorunda değiliz.
referans için optimize edilmemiş clang
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
optimize edilmiş clang
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
derleyici yazarı yığını hizalamak için kukla değişken olarak r7 kullanmayı seçti, ayrıca yığın çerçevesinde hiçbir şey olmamasına rağmen r7 kullanarak bir çerçeve işaretçisi oluşturuyor. temel olarak talimat optimize edilmiş olabilir. ama üç talimat geri dönmek için pop kullanılır, bu muhtemelen bana oldu Ben doğru komut satırı seçenekleri (işlemci belirterek) ile bunu yapmak için gcc alabilirim bahse girerim.
bu çoğunlukla diğer sorularınıza cevap vermelidir
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Şimdi küresellerim var. optimize edilmezlerse .data veya .bss içine girerler.
Nihai çıktıya bakmadan önce itermediate nesnesine bakalım
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
şimdi bundan eksik bilgi var ama neler olup bittiğine dair bir fikir veriyor, linker nesneleri alan ve onları .text ve. veri ve benzeri gider. derleyici bu tür şeyleri bilmiyor, sadece sunulan koda odaklanabilir, herhangi bir harici, bağlayıcıyı bağlantıyı doldurması için bir delik bırakmalıdır. Bu şeyleri birbirine bağlamak için bir yol bırakması gereken herhangi bir veri, bu yüzden her şey için adresler sıfırdır, çünkü derleyici ve bu dağıtıcı bilmiyor. Burada gösterilmeyen, bağlayıcının bir şeyleri yerleştirmek için kullandığı başka bilgiler de vardır. Buradaki kod, bağlayıcının işini yapabilmesi için konumdan yeterince bağımsızdır.
daha sonra bağlantılı çıkışın en azından bir sökülmesini görüyoruz
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
derleyici temelde ram cinsinden iki adet 32 bit değişken istedi. Bir .bss içinde çünkü ben sıfırlama olduğu varsayılır bu yüzden başlatmadı. Diğeri .data çünkü beyanda başlattım.
Şimdi bunlar küresel değişkenler olduğu için diğer fonksiyonların bunları değiştirebileceği varsayılmaktadır. derleyici, notmain'in ne zaman çağrılabileceğine dair hiçbir varsayımda bulunmaz, böylece görebileceği şeyle optimizasyon yapamaz, y = x + 1 matematiği, bu çalışma zamanını yapmak zorundadır. Koçtan iki değişkeni okumak ve geri kaydetmek zorunda.
Şimdi açıkça bu kod çalışmaz. Neden? çünkü burada gösterildiği gibi benim bootstrap notmain çağırmadan önce ram hazırlamaz, bu nedenle çip uyandığında ne olursa olsun çöp y ve x için kullanılacak olan çöptür.
Bunu burada göstermeyeceğim. .data ve .bss üzerinde daha da uzun sarmal başıboş dolaşımı ve neden çıplak metal kodumda bunlara hiç ihtiyaç duymadığımı okuyabilirsiniz, ancak başkalarının doğru yapmasını ummak yerine araçlara hakim olmanız ve ustalaşmak istediğinizi düşünüyorsanız. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
linker komut dosyaları ve bootstraps biraz derleyiciye özgüdür, bu nedenle bir derleyicinin bir sürümü hakkında öğrendiğiniz her şey bir sonraki sürümde veya başka bir derleyicide fırlatılabilir, ancak .data ve .bss hazırlığına ton çaba harcamamın başka bir nedeni sadece bu tembel olmak için:
unsigned int x=1;
Bunu çok yapmayı tercih ederim
unsigned int x;
...
x = 1;
ve derleyicinin benim için .text yazmasına izin ver. Bazen flaşı bu şekilde kaydeder, bazen daha fazla yanar. Toolchain sürümünden veya bir derleyiciden diğerine programlamak ve bağlantı kurmak kesinlikle daha kolaydır. Çok daha güvenilir, daha az hata eğilimli. Evet, C standardına uymuyor.
Şimdi ya bu statik küreselleri yaparsak?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
iyi
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
açıkçası bu değişkenler diğer kodlarla değiştirilemez, bu yüzden derleyici derleme sırasında ölü kodu daha önce olduğu gibi optimize edebilir.
Optimize edilmemiş
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
yerliler için yığını kullanan bu derleyici, şimdi küreseller için ram kullanır ve ben .data ne de .bss düzgün işlemedi çünkü yazılı olarak bu kod bozuk.
ve sökme işleminde göremediğimiz son bir şey.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
Ben x 0x12345678 ile ön-init olarak değiştirildi. Benim linker betiğim (bu gnu ld için) bob şey bu ted var. Bu, bağlayıcıya son yerin ted adres alanında olmasını istediğimi söyler, ancak ted adres alanındaki ikili dosyada saklar ve birisi sizin için hareket ettirir. Ve bunun olduğunu görebiliriz. Bu intel hex biçimidir. ve 0x12345678'i görebiliriz
:0400480078563412A0
ikilinin flash adres alanındadır.
readelf bunu da gösterir
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
sanal adresin 0x20000004 ve fizikselin 0x48 olduğu LOAD satırı