Strojový kód je v informatice posloupnost strojových instrukcí prováděných procesorem počítače, která je zapsána pomocí posloupnosti číselných kódů těchto strojových instrukcí. Hnutí GNU pojem strojový kód chápe obecnějším způsobem.
Instrukce jsou uloženy v paměti jako sekvence bitů, tedy jako čísla. Nejčastěji se instrukce skládá z bitového pole určujícího kód instrukce a bitových polí popisujících operandy. Instrukce může mít několik operandů (třeba i čtyři operandy), existují ovšem i instrukce bez operandů (nebo s operandy implicitními, tedy vždy stejnými, bez zvláštního bitového pole v kódu instrukce).
Pro převod instrukcí ze symbolického zápisu do strojového kódu se používá sada jednoduchých překladových nástrojů, zpravidla jde o assembler – program pro překlad kódu modulů a linker – program pro spojování jednotlivých modulů.
Instrukční pole
Instrukční pole je v podstatě bitové pole tj. sekvence bitů, do které je zakódována instrukce procesoru. Instrukční pole lze v podstatě rozdělit na bitové pole obsahující operační kód a bitová pole, ve kterých jsou zakódovány operandy instrukce.
Operační kód
Podrobnější informace naleznete v článku
Opkód.
Operační kód neboli opkód je ta část instrukčního pole, která určuje typ instrukce a jejích operandů, tedy zda půjde například o sčítání, odečítání, skok, atp. U delších instrukcí bývá zpravidla umístěn na začátku.
Operandy
Operand instrukce je jejím parametrem. Jestliže operační kód říká co se bude provádět, operandy definují s čím, s jakými daty. V bitových polích vyhrazených pro operandy (neboli parametry) instrukce může být zakódována:
- konstanta – jako operand se použije přímo hodnota bitového pole
- registr – číslo v bitovém poli určuje registr
- adresa určená konstantou – operandem je hodnota na adrese určené bitovým polem
- adresa určená registrem – operandem je hodnota na adrese obsažené v registru určeném bitovým polem
- adresa určená kombinací registrů a konstant – v bitovém poli může být dohodnutým způsobem zakódován i složitější adresový výraz V instrukční sadě i386 lze například adresu zadat i jako součet bázového registru, indexovaného registru vynásobeného jednou z konstant 1,2,4 a konstanty.
Šířka instrukčního pole
Počet bitů kódu instrukce, tj. základní šířka instrukčního slova je většinou násobkem osmi, takže instrukce zaujímá v paměti celý počet bytů. Výjimkou jsou např. některé řady mikrokontrolérů PIC, které používají dvanáctibitové nebo čtrnáctibitové instrukční slovo.
Čím výkonnější má být určitý procesor, tím větší šířku instrukčního slova používá. Do širšího instrukčního slova je možné zakódovat více informací, takže instrukce pak mohou být výkonnější. Nevýhodou širšího instrukčního slova je nižší hustota kódu. Široké instrukční slovo sice dovoluje udělat více věcí najednou. Například DSP mohou mít instrukci, která vynásobí dvě buňky z kruhových bufferů, výsledek násobení posune o daný počet bitů doprava a přičte do akumulátoru, přičemž současně posune indexy kruhových bufferů na následující prvek. V případě že potřebujeme jednoduchou akci (přičíst do registru jedničku), bude pravděpodobně kód schopný vyjádřit složité operace zbytečně dlouhý.
Kompatibilita strojového kódu
Určitá rodina procesorů má zpravidla shodný strojový kód, jednotlivé podtypy dané rodiny se mohou lišit například rychlostí, velikostí paměti cache, vybavením periferiemi, nebo napájecím napětím.
U úspěšných procesorů často dochází ke vzniku nových typů s rozšířenou instrukční sadou. Takto dochází k tomu, že instrukční sada dvou procesorů se může částečně překrývat. Pokud instrukční sada procesoru B obsahuje celou instrukční sadu jiného procesoru A, můžeme prohlásit že procesor B je zpětně kompatibilní s procesorem A.
Výhodou zpětné kompatibility je, že pro procesor B můžeme přímo použít známé překladové nástroje, které již máme zprovozněné a ověřené pro procesor A, což zvláště u nového typu procesoru znamená zásadní urychlení vývoje. V ideálním případě dokonce můžeme přímo použít pro procesor B kód, který byl vytvořen pro procesor A.
Typy instrukčních sad
Instrukční sada by měla odpovídat typu úloh, na který je daný procesor zaměřen. Například procesory určené pro PC mají komplikovanou ale univerzální a výkonnou instrukční sadu, zatímco na druhé straně spektra jsou mikrokontroléry pro jednoduché jednoúčelové aplikace (např. klávesnice, nebo dálkové ovládání) zaměřené na co nejnižší cenu a spotřebu, s velmi jednoduchou a nepříliš výkonnou sadou instrukcí.
Instrukční sady typu CISC
Zkratka CISC – complex instruction set computer – znamená v doslovném překladu „počítač s komplexní instrukční sadou“. Instrukční sada typu CISC má mnoho typů instrukcí, délka instrukčního slova může být proměnná. Nejstarší architektury mikroprocesorů, jako například Intel 8080, Zilog Z80, Intel 8086 nebo Motorola 68000 jsou založeny na tzv. mikrokódu. To znamená, že každá instrukce se skládala z několika kroků tzv. mikroprogramu.
Instrukční sady typu RISC
Zkratka RISC – reduced instruction set computer znamená „počítač s redukovanou instrukční sadou“. Základní myšlenkou je snížení počtu instrukcí, aby mohla být každá instrukce provedena pokud možno v jediném taktu procesoru. Díky zjednodušení instrukčního dekodéru je pak možné rozšířit funkčnost instrukcí a zmenšit počet tranzistorů v procesoru. Typů instrukcí je relativně málo a jsou jednoduché na dekódování. Specializované instrukce běžné v sadách typu CISC je možné nahradit jednou nebo několika univerzálními instrukcemi sady RISC, i tak je díky nepřítomnosti mikrokódu rychlost procesoru vyšší. Nevýhodou proti sadám CISC je nižší hustota kódu, a tedy vyšší spotřeba paměti programu pro dosažení stejné funkčnosti.
Procesory s RISCovým jádrem
Strojový kód typu CISC je možné překódovat do interního strojového kódu typu RISC.
To je příklad procesorů Intel Pentium Pro a vyšších a AMD K5 a vyšších, které se kombinací CISCové externí a RISCové interní sady vyrovnávají s nutností zvýšit rychlost a přitom udržet kompatibilitu s instrukční sadou i386.
Virtuální strojový kód
Tvrdá vazba aplikací na konkrétní strojový kód společně s dírami v definicích standardů a s diverzitou používaných procesorů vede k velkým problémům s kompatibilitou programů. I když máme zdrojový kód napsaný například ve vyšším programovacím jazyce (C, C++, ...), tak vzhledem k dírám v definici jazyka a k rozdílům v adresování paměti může (a často také bude) při překladu pro různé procesory a operační systémy fungovat různě. Odstraňování zjevných i skrytých chyb při přechodu do jiného operačního prostředí může být velmi náročné.
To nenastává u interpretovaných programovacích jazyků, nastávají však jiné problémy. Hlavním problémem je, že interpretace dlouhého a složitého kódu je časově velmi náročná a rovněž interprety mohou být komplikované (což znemožňuje jejich chod na jednoduchých zařízeních). Rovněž distribuce aplikací ve formě zdrojového kódu by byla v mnoha případech komerčně neúnosná a zabírala by mnohem více místa. Připočteme-li fakt, že aplikace mohou být znehodnocovány i samotným vývojem poměrně snadno modifikovatelného interpretovaného jazyka, je zřejmé jak snadné je s tímto přístupem skončit ve slepé uličce.
Kompromisem může být virtuální strojový kód, který není interpretován přímo procesorem, ale prostřednictvím specializovaného programu. Výhodou zde je, že již při jeho návrhu bylo myšleno na to, aby byl dobře interpretovatelný na různých architekturách. Výsledná aplikace sice běží výrazně pomaleji než aplikace kompilovaná přímo do strojového kódu, ale zato je její kód (alespoň teoreticky) bez větších problémů použitelný na různých typech procesorů a operačních systémů, což může být zvláště při existenci nejrůznějších zařízení připojovaných na internet velkou výhodou. Interpret virtuálního kódu by měl být podstatně jednodušší, než interpret vyššího programovacího jazyka, což umožňuje jeho chod i na relativně jednoduchých zařízeních (třeba v mobilu).
Příkladem úspěšného virtuálního strojového kódu je "bytekód" vytvářený programovacím jazykem Java. Různé bytekódy mohou produkovat také jazyky Smalltalk, Lisp a jiné. Krokem dál by mohl být kód ".net" od firmy Microsoft, jehož výhodou je dostupnost kompilátorů pro více jazyků (C++, C#, Delphi, ...). Primární určení frameworku ".net" pro operační systémy firmy Microsoft jeho základní myšlenku znehodnocuje. Tento nedostatek se snaží řešit opensource alternativa Mono, obsahující také překladač jazyka C#, který by byl jinak pro ne-windowsovské operační systémy většinou nedostupný.
U úspěšných virtuálních kódů můžeme očekávat, že dojde k jejich přelévání zpět do hardware, k čemuž dochází například u bytekódu jazyka Java, jehož interpretaci již podporují některá jádra ARM. S rozvojem technologie hradlových polí může dokonce dojít k situaci, kdy bude pro interpretaci určitého kódu dynamicky vytvářen jeho částečný nebo i úplný hardwarový interpret.
Vytváření strojového kódu
Programování přímo ve strojovém kódu je značně pracné, vzhledem k tomu, že je snadnější napsat jednoduchý assembler, se prakticky vůbec nepoužívá. Nevýhodou je nejen používání nepřehledných číselných kódů, ale především nutnost přepisovat instrukce obsahující adresy skoků a adresy proměnných po každém vložení nebo ubrání instrukce, nebo proměnné.
Strojový kód vzniká většinou překladem zdrojového kódu psaného ve vyšších programovacích jazycích, nebo alespoň v jazyku symbolických adres (assembler), které jsou specializovanými programy (překladači) překládány do strojového kódu příslušného procesoru. Proces překladu se tradičně dělí na několik kroků. Zdrojový kód psaný ve vyšším programovacím jazyce se překládá do assembleru (tento krok bývá volitelný, může být nutný při podezření na nesprávnou funkci překladače). Dalším krokem je překlad pomocí assembleru do relativního modulu (jeho soubor má obvykle příponu .obj). Každý relativní modul obsahuje skupinu funkcí a proměnných softwarového projektu. Spojením relativních modulů pomocí linkeru vzniká výsledný kód použitelný pro určitý operační systém, nebo jako obsah paměti, který lze nahrát do embedded systému. Aby nemusely být do každého projektu vkládány desítky či stovky modulů obsahujících běžně používané funkce, bývají jejich přeložené relativní moduly seskupeny v knihovnách (.lib). Linker při spojování projektu automaticky vytáhne z knihovny pouze ty moduly, na které se ostatní relativní moduly odkazují.
Překladu do strojového kódu se obecně vzato nelze vyhnout, každá aplikace nějakým způsobem musí běžet ve strojovém kódu, protože procesor žádný jiný kód z principu své funkce nemůže přímo interpretovat. I interprety jazyků jako je BASIC, různé "frameworky" nebo interprety virtuálních strojových kódů, mohou běžet jedině v nativním strojovém kódu procesoru. Některé pokročilé interprety používají techniku JIT kompilace (tj. kompilace "právě včas"), což znamená že místo přímé interpretace mohou průběžně vytvářet kousky nativního strojového kódu, který následně spouštějí, což vede k rychlejšímu chodu programu.
Příklady
Příkladem instrukce ze sady i386 může být např. instrukce int 21h
. Int znamená interrupt a 21h (číslo 21 v šestnáctkové soustavě) je operand zadaný konstantou. Strojový kód této instrukce v paměti je 11001101 00100001 (posloupnost dvou bytů, které můžeme zapsat šestnáctkově jako 0CDh 21h).
Příkladem nejdelší instrukce i386 (ve skutečnosti tak dlouhé, že nefunguje – prefix F0 (lock) musíte odstranit) je
F0 6626 67C7 845E 7856 3412 F0DE BC9A – lock mov dword [es:esi+ebx*2+0x12345678],0x9abcdef0.
Nejkratší instrukcí je třeba nop
(nic nedělej) s kódem 0x90. Ve skutečnosti se ale jedná o instrukci xchg ax,ax
(vyměň registr ax s registrem ax, tedy skutečně instrukce bez efektu), rozdělitelnou na kód (10010) a registrový operand (000 znamená ax, resp. eax v 32bitovém módu).
Definice GNU
Podle GNU všechny reprezentace autorského díla, které nejsou zdrojovým kódem, jsou strojovým kódem. Například třetí verze licence GPL[1] definuje zdrojový kód a strojový kód takto:
- „Zdrojový kód“ označuje preferovanou formu díla určenou na jeho úpravy. „Strojovým kódem“ se označuje jakákoli nezdrojová forma díla.
Ve smyslu licencí GNU je strojovým kódem například obrázek vzniklý vyrenderováním 3D modelů. Použité 3D modely jsou zdrojovým kódem.
Reference