A bináris fájlok milyen viszonyban vannak a gépi kóddal?
Ez eléggé elrugaszkodik a valóságtól. Ha csinálok egy fájlt, és teleírom 0-val, az is egy bináris fájl, és teljesen gépfüggetlen.
Ha konkrétan a futtatható állományok szerkezete érdekel, akkor keress rá a PE (windows), ELF (*nix) és hasonlókra, rengeteg nyilvános dokumentáció van róluk, meg hogy hogy működnek és mit tartalmaznak, egyik sem titok.
A "bináris fájl" nem jelent mást, mint a fájl. Hiszen minden fájl bitekből és byte-okból áll. Egy WAV formátumú fájl ugyanúgy néz ki függetlenül attól, hogy milyen gépen futtatod. Akár egy vízhajtású varrógépen is, ha annak a memóriája bináris elemekből áll. Az operációs rendszer (többek között) ahhoz kell, hogy segítse egy olyan program futását, amely ebből a formátumból hangokat tud előállítani. A programnak az operációs rendszerhez és a hardverhez is igazodnia kell, de maga az adatfájl nem köteles bármihez is igazodni. Azok az adatfájlok, amelyeket az általam írt valamelyik program használ az adatok tárolására, teljesen egyedi szerkezetű, az én "szabványom", mégis kiolvasható lenne a tartalma bármilyen számítógépen. Hogy a fájl melyik byte-ja mit jelent, az már külön kérdés, azt én határozom meg.
Ha bináris fájlnak a szövegszerkesztővel olvasható forráskódból a fordítással előállított, egyszerű eszközzel nem olvasható fájlt nevezed, annak a tartalma sok esetben nem gépi kód. Egyrészt vannak azok a nyelvek, amelyek a futtatáskor külső "motort" használnak, az ilyen nyelveken készült programfájl a processzortól független kódrendszerben tárolja a műveletsort, amit a motor tud értelmezni.
Másrészt a fordítóprogram általában egy köztes állapotot készít, amely gépi kódú programrészekből áll, de ezeket még össze kell szerkeszteni végrehajtható programmá, ezt végzi el a linker.
Ami nem oprendszer, azaz kernelfüggő, azt hívjuk baremetalnak: [link]
Egy bootmanager tipikusan mindig baremetal, és a kernel is. (kernel32.dll, vmlinuz).
Az oprendszerfüggő bináris file-ok a memóriakezelés és egyéb erőforrásmegosztás miatt már a kernellel kommunikálnak, ami aztán tényleg a processzor által közvetlen futtatható bináris formába rakja a kéréseket. De ezektől az erőforráskezelés-szükséges műveletektől eltekintve baremetal a bináris kód nagy része. Csak ugye a kernel engedélye nélkül nem tud memóriát foglalni, ezért oprendszerfüggőek a binárisok, hogy melyik kerneltől hogyan kell kunyizni az ilyeneket.
A linker a fordító által előállított gépi kódú programegységekből szervez egy olyan EXE fájlt, amely az oprendszer számára is instrukciókat tartalmaz arról, hogy a program milyen erőforrásokat igényel, és hogy a részeket milyen sorrendben a memória milyen részeire kell betölteni, majd végrehajtatni.
Egy COM fájl azon kívül, hogy az oprendszerre van szükség ahhoz, hogy a fájlból a memóriába kerüljön és a processzor a kijelölt ponton hozzákezdjen a végrehajtásához, közvetlenül a processzorhoz szól, és utána akár el is lehetne tüntetni az oprendszert, maradhatna a nyers hardver.
Ez viszont csak olyan programra lenne igaz, amely nem használ semmit a memórián kívül, hanem a processzorral sorra elvégezteti az előírt számításokat, adatmanipulációkat, az eredményt ott hagyná a memóriában, és kész. Hetven éve még elég volt egy iyen módszer, de még a Commodore 64-esen és már mikrogépeken is volt értelme ilyeneket csinálni.
De ha a program írója nem tudja, hogy az adott gépen melyik memóriacímre (konkrétan az ún. portokba) mit kell írni ahhoz, hogy ezzel a képernyőre, háttértárra, nyomtatóra írjon valamit - kihasználva a hardveregységek erre irányuló készenlétét -, vagy a billentyűzetről kapjon adatot, akkor meg lesz szorulva. Ezért a legegyszerűbb programok is a BIOS előkészített szolgáltatásait hívja meg az ilyen műveletekhez, hiszen épp a BIOS létezésének lényege az, hogy a programok részére a perifériákkal való kommunikációt viszonylag rugalmasan és általánosságban kompatibilis módon lehetővé tegye. Vagyis ha a program meghívja a képernyőre karakert kiíró BIOS funkciót, akkor az alaplapon levő BIOS fogja tudni, hogy ezen az alaplapon milyen portba milyen értéket kell írni a monitorral való kommunikációhoz. De a monitor bonyolult példa, a billentyűzet a legegyszerűbb eset, ha nem USB-s, hanem PS/2 vagy a régi 5 pontos tuchel csatlakozójú billentyűzeteről van szó. Viszont a BIOS még a harverhez tartozik, az alaplaphoz jár, és szigorúan gépi kódú programrészekből áll.
Ennyivel is lehet programot írni, viszont ezzel már csak a legvadabb dzsungelharcos elégszik meg. Így ugyanis magának a programnak kell tartalmaznia az összes részfeladatra vonatkozó programrészt, és egy ablak kirajzolása vagy egy fájl megnyitása kegyetlenül sok munkát kívánna tőle. Ezért a BIOS helyett inkább az oprendszer hasonlóképp előkészített funkcióit szokás a legközvetlenebb gépi kódú programban is meghívogatni, mert az már bonyolultabb feladatokat is el tud végezni. Egész karaktersorozat kiírását, kódrendszerre konvertált billentyűleütések fogadását, fájlok megnyitását és mentését és még hasonlók tucatjait, százait. A DOS kezdetben ennyiből állt, ahogy más oprendszerek is, már az ötvenes években. Az oprendszer egy része az alkalmazásprogramozó elől rejtve dolgozott, és a programozó a kernel nevű interkommunikációs "maggal" vagy interface-szel érintkezett, adva és kapva az információkat.
De ahogy a felhasználó által elvárt szolgáltatások bonyolódtak, az oprendszert egyre több, egyre összetettebb szolgáltatásokra kellett előkészíteni, de ez még mindig gépi kódú program írását jelentette. Az, hogy a programot a programozó már nem gépi kódban - olyan idő is volt ám -, nem is assemblyben írta meg, hanem valami magasabb szintű nyelvben, a programírás eltávolodását hozta a hardvertől. A magasabb szintű forráskódban kiadott utasításokat a fordítóprogram az annak írója által megtervezett gépi kódú utasítássorozatokra cserélte, és így jött létre a gépi kódban írt végeredmény. Mert arra van szükség, a processzor mindmáig ne ért meg mást. A programnyelvek képességei és a hozzájuk tartozó fordítóprogramok egyre összetettebbek lettek, de a lényeg nem változott. Az, hogy a forrásprogram milyen rendszerben legyen megírva, szövegként is elolvasható formájú, tokenizált, kódolt, akármilyen, az a fordító, a nyelv írójának szándékától és az általa lefektetett szabványoktól függ.
Viszont a gépeken megjelent az az igény, hogy egyszerre több program is használható legyen, akkor pedig egy gépi kódú program nem használhatja a memóriát és más hardvert a saját kedve szerint, azzal ugyanis ütközésbe kerülne a gépen futó más programokkal. Ezért a fordítóprogram által készített gépi kódú részeket a linker állította össze úgy, hogy majd az oprendszer az adott helyzethez igazodva oszthassa ki neki a memória szabad területét, és állítsa sorba a perifériák és a processzor elérésében.
Közben megjelentek az interpreterek is, amelyek a fordítóprogram feladatát tették fölöslegessé. Egy interpreter saját forrásszintű nyelvén megírt, és saját hatáskörében futtatott program magas szintú utasításait az interpreter egyenként elolvassa, és rögtön végre is hajtatja az annak megfelelő, az interpreterben előkészített, az operációs rendszer szolgáltatásait is igénybe vevő gépi kódú programegységet. Majd ismét a forrásprogramhoz fordul, elolvassa a következő utasítást, és így tovább. A módszer azért lassú, mert a program végrehajtása közben kell az utasításokat elemeznie, értelmeznie, annyiszor, ahányszor az az utasítássor végrehajtásra kerül. A compilerrel viszont csak egyszer olvassa el és fordítja le azokat, ami miatt a fordítási idő ugyan hosszabb - hajdanában tíz-húsz perces fordítások is megszokottak voltak -, de utána a végrehajtás a lehető leggyorsabb. Az interpreter viszont képes a fordítást kihagyva kisebb programrész független futtatására, ami a tesztelést és javítást teszi gyorsabbá.
Ma is vannak interpreterek, a Python például ilyen, elismert, modern nyelv.
Aztán végül (?) jöttek azok a programkiegészítők, motorok, amelyek olyan bonyolult műveletsorokat képesek egyetlen utasítással való meghívásra végrehajtani, amelyek minden programban való megírása lassú és gazdaságtalan volna, mellesleg a motorok használata formailag és nyelvileg egységessé teszi a programokat, egyformán futtathatóvá téve egy adott forráskódot többféle oprendszer alatt is. (Elméletileg.) Ilyen motor például a Java és a .NET, de ilyen a DirectX, OpenGL, PhysX meg egy csomó más, mindegyik saját feladatkört fed le. Az ezeket hívó utasítások végső soron gépi kódúak, de az oprendszer segítségét veszik igénybe a közvetítéshez ahhoz, hogy a motor valamelyik funkciója elinduljon. Viszont a motor adott funkciója is gépi kódú programegységben van a memóriában elhelyezve, mert a processzornak kell azt végrehajtania, amely végső soron még mindig csakis gépi kódú utasításokat tud megérteni és végrehajtani.
Az adatfájlok teljesen más eset. Vannak szabványok, ezerféle, de a szabványok követése a program írója számára nem tehető kötelezővé. A fájl kiterjesztése csak egy tájékoztatás az oprendszernek (és a felhasználónak) arról, hogy milyen feladatkörű programmal érdemes azt megnyitni, és az a program már érteni fogja a fájlban levő adatok rendszerét, és a kívánt módon hasznosítja azokat. De ha egy JPG fájl kiterjesztését átírjuk WAV-ra, és valahogy elhelyezzük benne a WAV fájlok azonosítására szolgáló jelzéseket - hiszen a zenelejátszó programok is ellenőrzik ezeket, mielőtt belecsapnának a koncertbe -, akkor csak iszonyatos csikorgásokat hallhatnánk, mert a JPG formátum szerinti adattárolás teljesen más, nemcsak tartalmilag, hanem formailag is, mint egy WAV fájl.
Nos, hogy aztán ezek közül mit tekintünk binárisnak és mit nem, az már csak szemlélet dolga. :-)
Kapcsolódó kérdések:
Minden jog fenntartva © 2024, www.gyakorikerdesek.hu
GYIK | Szabályzat | Jogi nyilatkozat | Adatvédelem | Cookie beállítások | WebMinute Kft. | Facebook | Kapcsolat: info(kukac)gyakorikerdesek.hu
Ha kifogással szeretne élni valamely tartalommal kapcsolatban, kérjük jelezze e-mailes elérhetőségünkön!