- Jezici niskog nivoa se približavaju hardver nudeći maksimalnu kontrolu i minimalnu apstrakciju.
- C se smatra programskim jezikom srednjeg nivoa, ali kada se koristi sa sistemskim pristupom, ponaša se kao programski jezik niskog nivoa.
- Binarni kod, mašinski jezik i asemblerski jezik čine osnovu na kojoj se grade C i drugi jezici.
- Ovi jezici su idealni za operativni sistemi, vozači, visokoperformansni ugrađeni firmver i softver.

Kada pričate primjeri niskog nivoa C Ne radi se samo o tome da se vide četiri linije koda, već o razumijevanju kako taj kod komunicira gotovo licem u lice s hardverom. Jezici na mašinskom nivou često izazivaju strahopoštovanje, ali su također osnova operativnih sistema, drajvera i mnogo kritičnog softvera koji svakodnevno koristimo, a da toga nismo ni svjesni.
U narednim redovima naći ćete detaljno objašnjenje o Šta su programski jezici niskog nivoa i kako se C uklapa kao hardverski orijentisani programski jezik srednjeg nivoa?Kakvu ulogu igraju mašinski kod, binarni kod i asembler, i zašto je sve ovo ključno kada razmišljate o programiranju "od temelja"? Ideja je da na kraju dobijete jasnu, praktičnu i utemeljenu viziju, bez nepotrebnih uljepšavanja.
Šta je tačno jezik niskog nivoa?
Jezik niskog nivoa je onaj čiji Instrukcije su veoma slične stvarnim operacijama procesora i nude vrlo malo apstrakcije u vezi sa arhitekturom sistema. Drugim riječima, kod koji pišete je vrlo blizak onome što CPU razumije: jednostavne operacije na registrima, memoriji i skokovima pri izvršavanju.
Ova blizina hardveru implicira da su ovi jezici Nije baš prenosiv i uveliko zavisi od unutrašnjeg dizajna mikroprocesoraIsti programski jezik za određeni CPU neće raditi kao za drugu arhitekturu, jer se mijenjaju skup instrukcija i načini adresiranja.
Termin "niski" ne znači da su to manje moćni jezici; naprotiv, Pružaju maksimalnu kontrolu nad ponašanjem mašineNazivaju se tako jer je udaljenost (apstrakcija) između ljudskog jezika i jezika koji CPU izvršava vrlo mala.
U praksi, kada govorimo o programskim jezicima niskog nivoa, uglavnom mislimo na binarni kod (mašinski jezik) i asemblerUprkos tome, jezici poput C-a se često opisuju kao jezici srednjeg nivoa jer kombinuju vrlo direktan pristup hardveru sa određenom strukturiranom apstrakcijom.
Klasifikacija po nivoima: od mašinskih do srednjih jezika
Jezici programiranje Mogu se naručiti otprilike od od nižeg ka višem nivou apstrakcije u odnosu na hardverOva klasifikacija pomaže u određivanju gdje se C uklapa i zašto se smatra da je na pola puta između čisto niskonivojskih i visokonivoskih programskih jezika.
Mašinski jezik (1GL)
Mašinski jezik, također nazvan jezik prve generacije ili 1GLTo je jedini koji CPU izvorno razumije. Sastavljen je isključivo od bitnih sekvenci (jedinica i nula) koje predstavljaju i instrukcije i podatke, kodirane prema specifičnoj arhitekturi procesora.
Ako otvorite izvršnu datoteku u običnom tekstualnom editoru, vidjet ćete kako izgleda. besmisleni znakovi, mnogi od njih se ne mogu odštampatiIza ovog prividnog haosa kriju se binarni obrasci koje centralna jedinica interpretira kao instrukcije za dodavanje, premještanje podataka, prelazak na drugu memorijsku lokaciju itd.
Instrukcije mašinskog jezika obično zauzimaju jednu ili više memorijskih lokacija: Jedan dio odgovara kodu operacije (opcode), a drugi operandima. (memorijske adrese, registri ili neposredne vrijednosti). Tačan oblik zavisi od načina adresiranja koje je definisao proizvođač CPU-a.
Na primjer, u a Z80 mikroprocesor Možda ćemo vidjeti nešto poput sljedećeg:
| Indeks memorije | Binario | Šesterokutni | Značenje |
|---|---|---|---|
| 0 | 10000111 | 87 | Naredba za dodavanje sadržaja sljedeće pozicije u akumulator |
| 1 | 01110111 | 77 | Podaci: numerička vrijednost 119 u decimalnom sistemu (77 u heksadecimalnom sistemu). |
Rad direktnim pisanjem ovih binarnih obrazaca je vrlo zamorno i sklono ljudskim greškamaZato je rijetkost da neko programira ručno u čistom mašinskom jeziku, osim u obrazovnim ili izuzetno specijalizovanim slučajevima.
Binarni kod i mašinski jezik
Binarni kod se često spominje kao nešto odvojeno od mašinskog jezika, ali u suštini To su dvije strane iste medaljeBinarni sistem je jednostavno bitna reprezentacija instrukcija i podataka koje procesor razumije.
Binarni sistem se sastoji samo od 0 i 1, gdje 1 se obično povezuje s aktivnim stanjem, a 0 s neaktivnim stanjem.Na ovom jednostavnom temelju izgrađene su sve računarske operacije. Svaka kombinacija bitova ima precizno značenje određeno arhitekturom CPU-a.
Iako kao programer obično ne pišete nizove nula i jedinica direktno, Sav softver se na kraju prevodi u ovaj oblik prije izvršenja, bilo putem predkompilacije ili prevođenja u realnom vremenu.
Asemblerski jezik (2GL)
Asemblerski jezik se smatra jezik druge generacije ili 2GLTo predstavlja mali korak u apstrakciji u poređenju sa mašinskim kodom: umjesto pisanja bitova, koristite mnemonike (skraćene riječi) za svaku instrukciju i simboli za predstavljanje adresa, konstanti ili oznaka.
Svaka asemblerska instrukcija obično odgovaraju gotovo jedan na jedan mašinskoj instrukcijiNa primjer, da bi se vrijednost premjestila iz jednog registra u drugi ili iz memorijske lokacije u registar, koriste se mnemoničke tehnike kao što su MOV, i da dodamo, koristi se ADDZa prelazak s jedne tačke u programu na drugu, koriste se instrukcije poput JMP.
Asemblerski program je tekst koji ljudi mogu čitati, ali Nije direktno izvršno od strane CPU-a.Mora proći kroz asembler: alat koji prevodi te mnemoničke znakove i simbole u njihov ekvivalent u binarnom mašinskom jeziku.
Asemblerski programer mora imati vrlo dobro razumijevanje arhitekture CPU-a: dostupni registri, skup instrukcija, načini adresiranja i hardverske karakteristikeOva bliska veza s fizičkim dizajnom čini kod brzim i preciznim, ali ne baš prenosivim.
Nadalje, broj dostupnih instrukcija varira ovisno o vrsti arhitekture. Arhitekture CISC (Complex Instruction Set Computer) obično uključuje prilično bogate skupove instrukcija, dok su arhitekture RISC (Reduced Instruction Set Computer) se odlučuje za manji i homogeniji repertoarfavoriziranje jednostavnih i brzih uputa.
Jezici srednjeg nivoa
Između jezika niskog i visokog nivoa nalaze se jezici srednjeg nivoaOvi programski jezici koriste prednosti oba svijeta: pružaju praktične kontrolne strukture i funkcije, ali i dalje nude prilično direktan pristup memoriji i hardveru.
Ova grupa uključuje jezike kao što su C i Basic, a na nešto višoj ljestvici, ali i dalje blizu Metala, C++, Rust, Fortran, Cobol, Lisp ili GoObično su proceduralno orijentisani (ili objektno-proceduralno orijentisani, u zavisnosti od slučaja), organizujući kod u funkcije ili blokove koji se mogu ponovo koristiti.
Koriste se za razvoj svega, od operativni sistemi i drajveri za korisničke aplikacije kao što su proračunske tablice, upravitelji baze podataka ili naučnih alata. U mnogim slučajevima, oni se oslanjaju na biblioteke napisane na niskom nivou ili direktno u asembleru kako bi maksimizirali performanse.
U ovom kontekstu, C se ističe po tome što omogućava rukovanje pokazivačima i strukturama podataka. izuzetno blizu načinu na koji je memorija organizovana u stvarnoj mašiniOvo olakšava pisanje "sistemskog" koda bez potpunog odustajanja od sintakse koja je donekle prilagođenija korisniku od asemblerskog jezika.
Gdje se C uklapa u jezike niskog nivoa?
C se obično formalno klasifikuje kao jezik srednjeg nivoaMeđutim, u mnogim praktičnim diskusijama svrstava se u jezike niskog nivoa jer nudi vrlo finu kontrolu nad memorijom i hardverom. Razvio ga je Dennis Ritchie u Bell Labsu 70-ih s jasnim ciljem: izgraditi efikasne operativne sisteme i sistemski softver.
Jedna od velikih vrlina jezika C je to što kombinuje efikasnost i prenosivostMožete napisati kod koji je prilično blizak stvarnom radu mašine i istovremeno ga kompajlirati za različite arhitekture sa relativno malim promjenama, sve dok poštujete specifičnosti svakog sistema.
U oblasti "niskog nivoa C", radimo sa konceptima kao što su pokazivači, ručno upravljanje memorijom, strukture koje odražavaju hardverske registre ili baferei minimalna upotreba biblioteka visokog nivoa. Cilj je održavanje kontrole nad performansama i potrošnjom resursa.
Ovaj pristup čini C najboljim jezikom za zadatke poput razvoj jezgara operativnih sistema, drajvera uređaja, firmvera i ugrađenih aplikacija (ugrađeni), gdje je svaki ciklus CPU-a i svaki bajt RAM-a važan.
Iako je C superiorniji od asemblerskog jezika u smislu apstrakcije, moderni kompajleri su sposobni generirati visoko optimizovani mašinski kod iz dobro napisanog C kodau mnogim situacijama postižući performanse praktično ekvivalentne onima kod rukom pisanog asemblera.
/* Primjer "niskog nivoa" C-a sa komentarima red po red. - Nema stdio-a: koristimo write() za direktan izlaz na stdout. - Manipulacija memorijom pomoću pokazivača i bitnih operacija. - Korištenje volatile-a za simulaciju pristupa hardverskim registrima. */ #include // write() #uključi // cjelobrojni tipovi fiksne veličine #include // size_t // Simulira memorijski mapirani hardverski "registar" (pristup nije optimiziran). static volatile uint8_t REG_CONTROL = 0x00; // volatile: uvijek stvarno čitanje/pisanje // Bitmaska za REG_CONTROL #define CTRL_ENABLE (1u << 0) // bit 0: omogućavanje #define CTRL_RESET (1u << 1) // bit 1: resetovati#define CTRL_IRQ_MASK (1u << 2) // bit 2: maska prekida // Jednostavna funkcija kopiranja memorije (slična memcpy), bez optimizacija. static void *mem_copy(void *dst, const void *src, size_t n) { // Pretvaranje pokazivača u bajtove radi kopiranja okteta po oktetu. uint8_t *d = (uint8_t *)dst; // pokazivač odredišta kao bajtovi const uint8_t *s = (const uint8_t *)src; // pokazivač izvora kao bajtovi // Jednostavna petlja kopiranja. for (size_t i = 0; i < n; i++) { // iterira kroz svaki bajt d[i] = s[i]; // kopira bajt } return dst; // vraća pokazivač odredišta } // Funkcija pisanja bez stdio-a: koristite write() direktno. static void putstr(const char *s) { // Ručno izračunava dužinu (do '\0'). size_t len = 0; // početna dužina while (s[len] != '\0') { // prelazi na terminator len++; // povećava brojač } // Zapisuje len bajtova na stdout (fd = 1). (void)write(1, s, len); // write vraća upisane bajtove; ovdje ignorišite rezultat } // Konvertuje bajt u dva heksadecimalna znaka (npr. 0xAF -> "AF") bez dinamičkog dodjeljivanja. static void byte_to_hex(uint8_t b, char out[2]) { // Lokalna tabela za heksadecimalne cifre. statička konstanta char HEX[16] = "0123456789ABCDEF"; // heksadecimalne cifre out[0] = HEX[(b >> 4) & 0x0F]; // visoki nibble out[1] = HEX[b & 0x0F]; // niski nibble } int main(void) { // Izvorni bafer sa binarnim podacima. uint8_t src[8] = {0x10, 0x3F, 0xA5, 0x00, 0x7C, 0xFF, 0x01, 0xB2}; // početni bajtovi // Odredišni bafer u koji ćemo kopirati. uint8_t dst[8] = {0}; // inicijalizirano na nulu // Ručno kopiramo memoriju. mem_copy(dst, src, sizeof(src)); // kopira 8 bajtova iz src u dst // Aktiviramo kontrolne bitove "registra" simulirajući konfiguraciju uređaja. REG_CONTROL |= CTRL_ENABLE; // Omogućava uređaj (postavlja bit 0) REG_CONTROL &= (uint8_t)~CTRL_RESET; // Osigurava da je resetovanje onemogućeno (briše bit 1) REG_CONTROL |= CTRL_IRQ_MASK; // Maskira prekide (postavlja bit 2) // Pišemo zaglavlje. putstr("Status REG_CONTROL i sadržaj bafera:\n"); // početna poruka // Prikaz REG_CONTROL u heksadecimalnom formatu. char hx[2]; // dva znaka za jedan bajt u heksadecimalnom formatu byte_to_hex(REG_CONTROL, hx); // konvertuje vrijednost registra putstr("REG_CONTROL = 0x"); // prefiks (void)write(1, hx, 2); // zapisuje dvije heksadecimalne cifre putstr("\n"); // novi red // Prolazimo kroz odredišni bafer i prikazujemo indekse i vrijednosti heksadecimalno. for (size_t i = 0; i < sizeof(dst); i++) { // iteracija za svaki bajt putstr("dst["); // početak indeksa labele // Pretvaramo indeks u cifru (pod pretpostavkom da je <10 radi jednostavnosti). char idx = (char)('0' + (int)i); // indeksni karakter (void)write(1, &idx, 1); // zapisuje indeks putstr("] = 0x"); // separator i heksadecimalni prefiks byte_to_hex(dst[i], hx); // pretvara bajt u heksadecimalni (void)write(1, hx, 2); // zapisuje heksadecimalnu vrijednost putstr("\n"); // novi red nakon svakog unosa } // Demonstracija pokazivača i aritmetike: inkrementirajte treći bajt pomoću pokazivača. uint8_t *p = &dst[2]; // pokazivač na treći element *p += 1; // povećava njegovu vrijednost za 1 // Prikazuje primijenjenu promjenu. putstr("Modificirano dst[2] + 1 -> 0x"); // poruka byte_to_hex(dst[2], hx); // ponovo izračunava heksadecimalni broj nakon modifikacije (void)write(1, hx, 2); // ispisuje novu vrijednost putstr("\n"); // kraj linije return 0; // završava bez grešaka }
Ključne karakteristike programskih jezika niskog nivoa
Jezici niskog nivoa (i u velikoj mjeri C korišten sa sistemskim načinom razmišljanja) Dijele niz karakteristika koji diktiraju kako se s njima programira. Ovo su snažne prednosti, ali dolaze i s određenim nedostacima koje treba razumjeti.
Maksimalna kompatibilnost hardvera
Ovi jezici omogućavaju postizanje vrlo bliska podudarnost između onoga što programer piše i načina na koji se koriste resursi mašineSvaka instrukcija se prevodi u vrlo specifične operacije na registrima, memoriji ili perifernim uređajima.
Kada programirate na niskom nivou, direktno se oslanjate na skup instrukcija i arhitektonske nacrte: Znate u koji registar ulazi svaki podatak, kako se adresira memorija i koja je latencija svake operacije.Ovo je idealno za maksimalno iskorištavanje određenog procesora, na primjer u ugrađenim sistemima ili komponentama u realnom vremenu.
Brzina i performanse
Eliminisanjem međuslojeva, izvršavanje programa napisanih u mašinskom jeziku, asemblerskom jeziku ili C-u, vrlo blizu hardvera, može biti... izuzetno brzoNema prevodilaca niti virtualne mašine djelujući kao posrednici, a potpuna kontrola resursa omogućava vrlo specifične optimizacije.
To znači da se ove vrste jezika koriste u softver gdje su performanse ključne: motori baza podataka niskog nivoa, dijelovi operativnih sistema, intenzivna obrada podataka, kompresori, komunikacijski sistemi itd.
Vrlo ograničena apstrakcija i prenosivost
Manje ugodna strana je to što Apstrakcija je minimalna, kao i prenosivost.Kod dizajniran za određenu arhitekturu često zahtijeva promjene ako se želi izvršavati na drugoj platformi s drugačijim skupom instrukcija ili modelom memorije.
Nadalje, odsustvo zaštitnih slojeva znači da Svaki propust može dovesti do nevažećeg pristupa memoriji, zaključavanja ili tihih grešaka.Programer je odgovoran za održavanje reda i sigurnosti, uz minimalnu pomoć jezika.
Složenost čitanja i pisanja
Iako su asemblerski jezik i vrlo niskonivojski C čitljiviji od niza bitova, za većinu programera Teško ih je čitati, pisati i održavati u poređenju sa modernim programskim jezikom visokog nivoa.
Uobičajeno je da program niskog nivoa zahtijeva vrlo rigorozna organizacija i pažljiva dokumentacijaupravo zato što radi s tako finim hardverskim detaljima da svaka mala modifikacija može imati složene nuspojave.
Direktna interakcija s hardverom
U jezicima niskog nivoa, interakcija s hardverom se obično odvija bez posrednika: Registri uređaja, ulazno/izlazni portovi i memorijske regije mapirane na periferne uređaje se manipulišu.Ovo vam omogućava da uradite stvari koje bi bilo nemoguće ili vrlo komplikovano postići korištenjem čistih programskih jezika visokog nivoa.
U mnogim slučajevima, jezici visokog nivoa na kraju se oslanjaju na moduli napisani u C-u ili asembleru pristupiti osjetljivim ili kritičnim funkcionalnostima, upravo zbog te mogućnosti direktnog kontakta s hardverom.
Uobičajena upotreba programskih jezika niskog nivoa
Jezici niskog nivoa igraju fundamentalnu ulogu u softverskim slojevima koji možda nisu vidljivi na prvi pogled, ali koji održavaju sve u funkciji. Većina korisnika nikada neće dirati ovaj kod, ali Oni zavise od toga da bi njihov sistem bio stabilan i brz..
Klasičan primjer je onaj od operativni sistemiVećina njegovih kernela i kritičnih komponenti je historijski pisana u C-u, ponekad sa isječcima asemblerskog jezika za vrlo specifične dijelove kao što je boot ili upravljanje prekidima.
Druga tipična upotreba je razvoj upravljački programi uređajaOvi moduli služe kao most između operativnog sistema i stvarnog hardvera (mrežne kartice, grafičke kartice, uređaji). skladištenjeitd.) i zahtijevaju direktan pristup određenim registrima i memorijskim adresama uređaja.
Svijet ugrađeni softver (mikrokontroleri, industrijski kontrolni sistemi, automobilska elektronika, uređaji IoTTakođer se uveliko oslanja na niskonivojski C, a ponekad i na asemblerski jezik, jer su resursi toliko ograničeni da je svaki bajt važan.
Konačno, postoje mnoge komponente aplikacije visokih performansi (mehanizmi baza podataka, biblioteke za šifriranje, kompresija, grafika, itd.) koji su implementirani u C-u s filozofijom vrlo bliskom niskom nivou kako bi se postigle performanse koje bi drugi jezici, sami po sebi, teško dostigli.
Uzeti zajedno, svi ovi slojevi niskog nivoa funkcionišu kao "skelet" na kojem se grade jezici prilagođeniji korisnicima i okviri višeg nivoa, dajući predstavu o strateška važnost savladavanja ove vrste programiranja ako želite raditi blizu srži sistema.
Nakon pregleda svih ovih koncepata, postaje jasnije zašto se toliko priča o Primjeri niskonivojskog C jezika prilikom podučavanja osnova sistema i arhitektureC služi kao praktični most između teorije hardvera (bitovi, registri, instrukcije) i konstrukcije stvarnog softvera koji radi maksimalnom brzinom i sa finom kontrolom nad mašinom.
Strastveni pisac o svijetu bajtova i tehnologije općenito. Volim dijeliti svoje znanje kroz pisanje, a to je ono što ću raditi na ovom blogu, pokazivati vam sve najzanimljivije stvari o gadžetima, softveru, hardveru, tehnološkim trendovima i još mnogo toga. Moj cilj je pomoći vam da se krećete u digitalnom svijetu na jednostavan i zabavan način.
