Vyšlo v týdeníku Computerworld č. 6/94,
Vytištěno z adresy: http://www.earchiv.cz/a94/a406c502.php3

Od von Neumanna ....

Tento článek vyšel v tzv. tématu týdne v CW 6/94, jako druhý ze série článků věnované prvopočátkům výpočetní techniky.

S časovým odstupem téměř padesáti let od vzniku von Neumannovy architektury lze konstatovat, že se udržela až dodnes, a že drtivá většina dnes existujících a vyráběných počítačů vychází právě z této koncepce. Objevily se samozřejmě i jiné koncepce, zásadně odlišné od von Neumannovy, ale žádná z nich se neprosadila takovým způsobem, aby přetrvávající hegemonii von Neumannovy architektury dokázala vůbec ohrozit. Ta sice prošla za dobu své existence určitými změnami, ale ty zůstaly spíše jen na úrovni kvantitativních vylepšení, než zásadních kvalitativních změn.

Následující článek se snaží shrnout ty nejzajímavější momenty v dalším vývoji von Neumannovy architektury, ke kterým došlo nepříliš dlouho po jejím vzniku.

1951 - mikroprogramování

Srdcem každého počítače, vytvořeného podle von Neumannovy koncepce, je tzv. řadič. Ten je možné si představit jako tu část počítače, která všem ostatním částem počítače předepisuje, co přesně mají dělat, a tím vlastně řídí chod celého počítače.

Provedení každé strojové instrukce se vždy skládá z určitého počtu dílčích akcí, které mohou provádět různé části počítače samy, či ve vzájemné spolupráci, vždy ale za potřebné vzájemné součinnosti. Tím, kdo tuto koordinaci zajišťuje, kdo jednotlivým částem počítače vysílá nezbytné pokyny a kdo dohlíží na jejich správnou činnost, je právě řadič. On sám přitom vychází z právě prováděné strojové instrukce, od které pak odvozuje svůj postup.

Po konstrukční stránce byly řadiče nejprve řešeny jako jednoúčelové sekvenční obvody, "šité na míru" konkrétnímu repertoáru strojových instrukcí, kterými byl počítač vybaven. Jinými slovy: všechny strojové instrukce byly v řadiči "pevně zadrátovány".

Již v roce 1951 si ale prof. Maurice V. Wilkes povšiml značné podobnosti mezi činností počítače jako takového, který vykonává určitý program, a činností řadiče, který "vykonává" konkrétní strojovou instrukci. Podobnost spatřoval především v tom, že oba postupují podle předem stanoveného plánu (programu), a podle něj vykonávají určité akce (či zadávají jejich provedení). Při podrobnějším zkoumání nalezl profesor Wilkes další podobnosti - například v potřebě větvení podle výsledku dosavadních činností, opakování stejných činností (podprogramů) apod. Jeho celkový dojem byl takový, jako kdyby řadič byl "počítačem v počítači", nebo jako kdyby šlo o počítač, který je v hierarchii stupňů abstrakce "o jedno patro níže". Na základě toho pak profesor Wilkes přišel s myšlenkou realizovat řadič skutečně jako "počítač v malém" - jednotlivé dílčí akce (mikrooperace, resp. mikroinstrukce), ze kterých se skládá provedení konkrétní strojové instrukce, navrhl sestavovat do větších celků, tzv. mikroprogramů, které by byly umístěny v tzv. řídící paměti (jako analogii operační paměti), odkud by byly jednotlivé mikroinstrukce průběžně načítány a "prováděny". Díky této koncepci tzv. mikroprogramového řadiče mohl Wilkes převést složitost návrhu jednoúčelového obvodu ("zadrátovaného" řadiče) na složitost tvorby mikroprogramu. Podobně jako von Neumann, vycházel i Wilkes z myšlenky, že je výhodnější neměnit vnitřní strukturu (řadiče, resp. počítače), ale přizpůsobovat jeho činnost konkrétním požadavkům programovými prostředky - v případě mikroprogramového řadiče tzv. mikroprogramováním (jako analogií programování u počítače jako takového).

Jestliže první skutečné počítače používaly ještě "zadrátované" (anglicky: hardwired) řadiče, Wilkesovy mikroprogramové řadiče již počátkem šedesátých let zcela převládly. V poslední době jsou ovšem zase na ústupu - jsou totiž přeci jen pomalejší, než původní "zadrátované" řadiče, a v dnešní honbě za co možná nejrychlejšími procesory převážilo hledisko rychlosti nad výhodami snazšího návrhu.

1956 - přerušení a asynchronní V/V operace

Jednou z nejvíce kritizovaných vlastností von Neumannovy architektury je její čistě sekvenční charakter (viz též: ..vložit odkaz na box o von Neumannově architektuře), který nepočítá s tím, že by se provádělo více činností souběžně. To ostatně souvisí i s představou, že procesor (resp. řadič jako část procesoru) bude bezprostředně řídit skutečně všechny prováděné činnosti, včetně vstupně výstupních operací.

Zajímavé je, že toto omezení začalo již velmi brzy vadit. Důvod byl ten, že rychlost procesoru i většiny ostatních částí počítače prakticky od začátku řádově převyšovala rychlost všech vstupně/výstupní zařízení mechanického charakteru (zejména snímačů děrných pásek a štítků, jejich děrovačů, tiskáren). Pokud by ale procesor (resp. řadič) skutečně přímo řídil takováto vstupně/výstupní zařízení, musel by se nutně přizpůsobovat jejich rychlosti. Efekt by pak byl takový, že drtivou většinu času by procesor musel trávit čekáním na připravenost těchto zařízení. A to jistě není rozumné.

Přitom bezprostřední řízení jednotlivých periferií je vesměs natolik jednoduché, že je lze svěřit nepříliš složitému jednoúčelovému obvodu. A tak se stalo, že k jednotlivým periferním zařízením se začaly přidávat samostatné řadiče (nejčastěji označované jako tzv. řídící jednotky), které převzaly na svá bedra úlohu bezprostředního řízení vlastních zařízení. Procesor těmto řídícím jednotkám vždy zašle explicitní pokyn k zahájení určité vstupně/výstupní operace či její dílčí části, a řídící jednotka pak určitou dobu pracuje samostatně (míněno: nezávisle na procesoru), dokud zadaný pokyn nedokončí. Jelikož přitom řídící jednotka pracuje "svým tempem" (tj. nezávisle na rychlosti procesoru), hovoří se v této souvislosti také a asynchronním průběhu vstupně/výstupních operací

Jak ale vyřešit otázku zpětné vazby? Jak se má řídící jednotka zachovat v okamžiku, kdy dokončí dříve zadaný úkol, a chce oznámit, že je schopna převzít nový úkol? A jak řešit situaci, kdy dojde k nějaké nestandardní situaci, a řídící jednotka nemůže pokračovat ve své práci? Nebo co když potřebuje spolupráci procesoru k tomu, aby přenesla nějaká data z/do operační paměti? Problém je o to těžší, že vzhledem k asynchronnímu průběhu vstupně/výstupních operací nemá procesor možnost předem přesně určit okamžik, kdy budou dokončeny.

Jednou z možností by bylo, aby se procesor sám průběžně dotazoval řídící jednotky na její stav - tedy aby iniciativa byla na straně procesoru. To je samozřejmě možné, ale zcela to neguje výsledný efekt samostatné práce řídící jednotky: procesor sice již nemusí bezprostředně řídit průběh vstupně/výstupní operace, ale místo toho se musí neustále ptát řídící jednotky, zda už je hotova. Přitom ovšem již těžko stihne dělat něco užitečného.

Ve snaze odstranit tento začarovaný kruh tvůrci počítačů nakonec dospěli k závěru, že iniciativa by měla přejít na stranu řídící jednotky příslušné periferie. Že procesor může starost o průběh operace "hodit za hlavu" a počítat s tím, že to bude řídící jednotka, která se sama ozve, jakmile bude něco potřebovat.

Aby tak řídící jednotka mohla udělat, potřebuje mít k dispozici vhodný mechanismus. Takový, který jí umožní přihlásit se procesoru a vynutit si jeho pozornost - přinutit jej přerušit provádění právě probíhajícího programu, a místo toho vyvolat jiný program (tzv. obslužný program), který zjistí příčinu, zajistí vše potřebné, a pak zase vrátí řízení zpět původně přerušenému programu.

Takto koncipovaný mechanismus přerušení, bez kterého by se dnešní počítače už vůbec neobešly, byl poprvé použit v roce 1956 v počítačích UNIVAC firmy Remington Rand.

1956 - kanály a přímý přístup do paměti

Mechanismus přerušení spolu s asynchronními vstupně/výstupními operacemi značně odlehčil procesoru, a umožnil mu věnovat se jiným, užitečným věcem (prováděním jiných programů). Stále však procesoru zbývalo mnoho povinností, spojených s průběhem vstupně/výstupních operací. Například veškerý přesun dat mezi pamětí a vstupně/výstupním zařízením musel zajišťovat právě procesor, který měl jako jediný přístup k operační paměti. Činil tak přitom na explicitní popud periferie, resp. její řídící jednotky, v rámci obslužných programů přerušení.

Pokud byly obsluhované periferie relativně pomalé, a přenášených dat poměrně málo - jako například při tisku na tiskárně - procesor to nijak zvlášť nezatěžovalo. Ovšem v případě rychlých periferií, které vyžadují přenosy relativně velkých objemů dat (jako například disky) došlo k zajímavé situaci: procesor byl velmi často přerušován (za účelem přenosu dat z/do periferie), a míra jeho schopnosti vykonávat vedle toho ještě i jiný program byla tudíž značně omezená. Dokonce se mohlo stát i to, že procesor jednoduše "nestíhal" přenášet data z/do periferie potřebnou rychlostí.

Ve snaze ještě více odlehčit procesoru se přišlo na zajímavé řešení: zabudovat do počítače specializovaný subsystém, tzv. kanál, určený výhradně k zajišťování přenosů dat mezi operační pamětí a periferními zařízeními (resp. jejich řídícími jednotkami). Periferie se pak se svými žádostmi o jednotlivé přenosy dat nemusí obracet přímo na procesor (prostřednictvím přerušení), ale místo toho se obrací na kanál.

Aby ovšem kanál mohl plnit svou funkci a přenášet data mezi pamětí a periferními zařízeními, musí se umět nejprve "poprat" s procesorem o právo přístupu k operační paměti. Do té doby byl procesor "jediným pánem" v celém počítači, a nikdo jiný než on neusiloval o přístup k paměti. S příchodem kanálu však tuto svou výlučnost ztratil, protože i kanál ke své funkci vyžaduje schopnost toho, čemu se dnes říká přímý přístup do paměti (Direct Memory Access).

Kanál tedy musí být relativně inteligentním zařízením, které dokáže korektně soutěžit s procesorem o získání přístupu k paměti. V zájmu dosažení maximální autonomie kanálů jsou tyto vybavovány vlastním programem (tzv. kanálovým programem), na jehož základě jim mohly být předepsány rozsáhlejší vstupně/výstupní operace, sekvence takovýchto operací apod. Na procesor se pak kanál obrací jen v okamžiku, kdy dokončil provádění celého kanálového programu, nebo v okamžiku výskytu nějaké nestandardní situace (chyby a pod.).

První kanál ve výše naznačeném smyslu byl použit v roce 1956, opět v počítačích UNIVAC firmy Remington Rand. Koncepce kanálů pak dlouhou dobu dominovala především v oblasti tzv. střediskových počítačů (mainframes). U "menší" výpočetní techniky se prosadilo jiné řešení: u minipočítačů se schopností přímého přístupu do paměti vybavily přímo řídící jednotky jednotlivých periferních zařízení, zatímco u současné mikroprocesorové techniky se začaly používat samostatné speciální obvody, vybavené schopností přímého přístupu do paměti, ale bez možnosti používání kanálových programů (jsou řízeny údaji, zapisovanými přímo do jejich interních registrů). Tyto tzv. DMA řadiče pak mohou zajišťovat potřebné přenosy dat z/do paměti pro více jednotlivých periferií.

1957 - programovací jazyk vyšší úrovně

Každý počítač má vždy definován určitý repertoár strojových instrukcí, které je schopen provádět (neboli tzv. instrukční soubor). Programy, které je takovýto počítač schopen přímo vykonávat, pak musí být sestaveny právě a pouze z těchto strojových instrukcí. Přitom každý počítač má obecně jiný instrukční soubor, jiný repertoár registrů, se kterými tyto instrukce pracují, jiné způsoby adresování atd.

Programátor, který svůj program, sestavuje přímo z jednotlivých strojových instrukcí (zapisovaných symbolicky v tzv. jazyku symbolických adres neboli asembleru, nedej bože přímo v číselném tvaru), samozřejmě musí tyto strojové instrukce znát, a s nimi musí dosti podrobně znát i další více či méně detaily, jako skladbu registrů, způsoby adresování, významy příznaků apod. Programátor, který programuje na úrovni strojového kódu, si tedy musí být vědom, že pracuje na určitém konkrétním počítači, a musí respektovat jeho specifika.

Dnes zřejmě již nelze zjistit, koho poprvé napadla myšlenka tuto nepříjemnou situaci změnit. Koho napadlo vytvořit takový programovací jazyk, který bude nezávislý na konkrétním instrukčním souboru, a nebude od programátora očekávat znalost konkrétního počítače, pro který bude program psát. Dnes také již těžko zjistíme, kdy a v jaké fázi úvah o strojově nezávislém programovacím jazyku se přišlo na to, že elementární operace (instrukce), které se v takovémto jazyku budou používat, mohou být mnohem obecnější, než jednotlivé strojové instrukce - že jedna instrukce strojově nezávislého jazyka bude odpovídat takové činnosti, kterou dokáže zajistit například jen celá posloupnost strojových instrukcí. Z tohoto pohledu tedy strojově nezávislý jazyk může být jazykem vyšší úrovně, než strojově závislý jazyk. Dokonce se zjistilo, že tomu tak být musí, protože příkazy strojově nezávislého jazyka dost dobře nemohou odpovídat jednotlivým strojovým instrukcím konkrétních počítačů (jako je tomu u asembleru jako strojově závislého jazyka). Strojově nezávislý jazyk pak ovšem bude vyžadovat buď existenci kompilátoru, který v něm zapsané programy "přeloží" do proveditelného tvaru (tedy do tvaru programu, sestaveného ze strojových instrukcí konkrétního počítače), nebo bude vyžadovat alespoň tzv. interpret, který bude postupně brát jednotlivé příkazy strojově nezávislého jazyka, a bude průběžně zajišťovat jejich provádění (bude interpretovat, co jednotlivé příkazy požadují, a příslušné činnosti provede sám).

Jisté je pouze to, že úkol vytvořit strojově nezávislý jazyk si poprvé předsevzala v roce 1954 skupina vědců od firmy IBM, vedená Johnem Backusem. Jejich cílem bylo vytvořit strojově-nezávislý programovací jazyk vysoké úrovně, určený především pro vědeckotechnické výpočty a řešení nejrůznější matematických a inženýrských problémů.

Výsledek své práce mohla skupina v čele s Johnem Backusem představit světu v roce 1957. Byl jím programovací jazyk, který nazvali FORTRAN (od: FORmula TRANslation). V poměrně rychlém sledu za ním pak následovaly další strojově-nezávislé programovací jazyky: v roce 1958 jazyk LIPS (LISt Processor) pro umělou inteligenci, v roce 1960 jazyk COBOL (COmmon Business Oriented Language), a v témže roce univerzální programovací jazyk ALGOL (Algorithmic Language, jehož první verze pochází z roku 1958, ale praktického využití se dočkala až verze, pocházející z roku 1960). Z roku 1961 pochází první programovací jazyk, určený pro počítačové simulace (jazyk GPSS, neboli General Purpose Systems Simulator). Pedagogy tolik zatracovaný jazyk BASIC (Beginner's All-purpose Symbolic Instruction Code) pochází z roku 1964, zatímco tolik vychvalovaný jazyk Pascal pochází až z roku 1971. V roce 1974 pak vylepšením nepříliš známého programovacího jazyka B vzniká mnohem známější jazyk C, a v roce 1979 se objevuje programovací jazyk Ada. Tím ovšem není vývoj programovacích jazyků zdaleka vyčerpán.

1958 - virtuální paměť

Rychlé pokroky, ke kterým docházelo v nejrůznějších technologiích, umožňovaly konstruovat stále dokonalejší a rychlejší počítače, s větším objemem paměti, a se stále většími schopnostmi. Ovšem požadavky uživatelů rostly ještě rychleji, než technologické možnosti výrobců.

Nejmarkantnější byl rozpor mezi požadavky uživatelů a aktuálními možnostmi výrobních technologií v oblasti pamětí: uživatelé, zlákáni možnostmi počítačů, požadovali stále větší a větší objemy operačních pamětí. To bylo v principu možné řešit přidáváním dalších a dalších paměťových modulů (dokud se nenarazilo na případná konstrukční omezení, daná volným místem pro jejich umístění, kapacitou napájení, možnostmi chlazení atd.). No ale pak se ukázalo, že uživatelé sice vehementně uplatňují své požadavky, ale na druhé straně nejsou ochotni za větší paměť adekvátně zaplatit (zde si ovšem musíme uvědomit, že to bylo v době, kdy počítače i jejich paměti byly nepoměrně dražší, než jsou dnes). I přesto se ale výrobci velmi snažili vyjít uživatelům vstříc.

Se zajímavým nápadem přišla kolem roku 1958 vědců z univerzity v anglickém Manchesteru (při práci na počítači Atlas). Výsledný efekt jejich nápadu by snad nejlépe vystihlo staré přísloví o nasycení vlka a zachování celistvosti kozy: přišli na způsob, jak uživatelům pouze předstírat, že mají k dispozici větší objem operační paměti, zatímco skutečný objem této paměti větší není.

Podstata řešení spočívá v oddělení toho, co uživatelé a jejich programy "vnímají" (tj. toho, co si uvědomují, resp. s čím si myslí, že pracují), od toho, s čím pracují doopravdy. Vedle skutečné a reálně existující paměti se tedy zavedla ještě další paměť, která je pouze iluzí, předkládanou uživatelům a jejich programům (a která reálně neexistuje, a je pouze předstírána - proto se jí také říká virtuální). Pokud se vše šikovně zařídí, může být virtuální paměť výrazně větší, než reálně existující paměť. Uživatel, jehož programy "vnímají" pouze tuto virtuální paměť, si pak může myslet, že pracuje s velmi velkou pamětí, zatímco ve skutečnosti pracuje s pamětí mnohem menší.

Ptáte se, jak je to možné? Vše vychází z pozorování, že "slušné programy se chovají slušně" - statistickým sledováním mnoha programů se zjistilo, že tyto "nesahají" do operační paměti zcela "na přeskáčku" (v tom smyslu, že by jejich přístupy do paměti i v malém časovém intervalu rovnoměrně pokrývaly celý rozsah dostupné paměti). Naopak se zjistilo, že rozumně napsaný program vždy po jistou dobu vystačí s poměrně malou částí operační paměti (nebo s několika málo takovýmito částmi, ze kterých čte a do kterých zapisuje). No a celý "fígl" je pak v tom, že do skutečně existující paměti se průběžně přesouvají právě jen tyto menší části, se kterými program v danou chvíli pracuje, zatímco zbývající části mohou být uloženy například na disku. Rozsah virtuální paměti (se kterou si uživatel myslí, že pracuje), je pak dán kapacitními možnostmi disku, a nikoli skutečným objemem reálně existující paměti.

Vše samozřejmě musí být zařízeno tak, aby veškeré přesouvání dat a programů mezi operační pamětí a diskem bylo pro uživatelské programy plně transparentní (tj. neviditelné) - což ale není principiální problém zajistit.

Existuje dokonce více různých technik pro realizaci virtuální paměti: nejpoužívanější z nich je tzv. stránkování (paging), které mezi operační pamětí a diskem přesouvá celé tzv. stránky (vždy stejně velké, např. 1 kB, 2 kB, 4 kB apod.). Další používanou technikou pro realizaci virtuální paměti je tzv. segmentace (segmentation).