Глава 5. Процесор
В тази глава ще разгледаме съвсем отвътре (макар и малко повърхностно) основните въпроси отностно работата на процесора. Какво става "там вътре". Как процесора изпълнява програмите. Какво са всъщност програмите? И въобще "за живота, вселената и всичко останало"...
Изложението ще бъде крайно опростено (не не невярно) с цел да се улесни възприемането на принципните положения от начинаещите. Тук няма да говорим за специфичните хардуерни решения използвани напоследък за увеличаване на производителността на процесорите - конвейерна обработка, кеширане, 32/64 битова памет и т.н. Тези неща не се отразяват на това как изглежда процесорът откъм програмата, така че не са важни за разбирането на основните положения.
Първи принцип: В компютърът няма нищо друго освен: "Памет" и "Процесор". Всичко друго по чисто хардуерен начин се свежда до обекта "Памет". Опитайте се да възприемете тази гледна точка, дори и да ви се вижда странна.
1. Архитектура на процесора
Както казахме, процесорът е устройството, което изпълнява програмите и манипулира с даните от паметта. Тук ще разгледаме вътрешната архитектура на процесор от семеството x86 - това са най-разпространените в момента процесори за персонални компютри, както и за мрежови сървъри.
Процесорът се състои от 3 главни блока:
1. Аритметично логическо устройство.
2. Регистри.
3. Управляващо устройство.
Ще ги разгледаме поотделно:
I. Аритметично логическото устройство е това което извършва всички изчисления в компютъра. То работи под командването на управляващото устройство, което му задава числата с които трябва да се правят изчисленията, задава какво трябва да се прави с тези числа и взима резултата от изчисленията.
II. Регистри: Това е изключително важна част на процесора, поне от програмна гледна точка.
Регистрите са всъщност памет. Няколко клетки памет, която е директно достъпна за процесора, тъй като се намира вътре в него. Достъпа до тази памет е бърз и може да става до няколко регистъра едновременно. Регистрите се използват главно за междинно съхранение на резултатите от изчисленията, тъй като от главната оперативна памет не могат например да се четат повече от едно число едновременно или да се чете и пише едновременно. Например, ако искаме да съберем числата в две клетки от паметта и резултата да го запишем в трета, трябва да направим така:
1. Прочитаме съдържанието на първата клетка и го записваме в регистър.
2. Събираме регистърът с втората клетка от паметта.
3. Полученият резултат записваме в третата клетка от паметта.
Забелязахте ли, че на всяка стъпка, имаме достъп точно до една клетка от оперативната памет.
Главни регистри в x86 процесора:
1. Регистри с общо предназначение - Всички регистри с общо предназначение за
32 битови, тоест състоят се от 4 байта.
Имената на регистрите са следните: eax, ebx, ecx, edx, ebp, esp, esi, edi
.
Първите четири, eax, ebx, ecx и edx
имат особеността, че могат да се използват
и като 16 битови регистри или като два 8 битови. (Това е общо взето по исторически
причини.) Тоест например младшите 16 бита от eax представляват регистъра
"ax", който от своя страна може да се използва и като два 8 битови регистъра
"al" - младшата му част и "ah" - старшата. Страшите 16 бита на eax не могат да се
делят и се използват само в състава на eax.
Същото е и при ebx ( bx, bl, bh ), ecx (cx, cl, ch) и edx (dx, dl, dh)
Останалите регистри (ebp, esp, esi и edi) имат само 16 битова версия (bp, sp, si, di) но не могат да се използват като двойка 8 битови регистри.
Макар в съвременните процесори да няма особенна причина за това, исторически е прието eax, ebx, ecx и edx да се използват повече за аритметични операции а останалите за указатели, тоест да съдържат адреси в паметта.
Едно изключение е esp, който изпълнява изключително важна роля, като указател на върха на стека. Въобще добре е да не използвате esp, ако не знаете какво точно правите.
2. Специални регистри. Тук ще споменем само два: eip
и eflags
.
eip
e т.н. програмен брояч или указател към инструкция (instruction pointer)
В него е записан адреса на инструкцията която се изпълнява от процесора.
След всяка инструкция, този регистър се променя, така че да сочи към следващата инструкция. Когато говорим за разлика - кое е програма и кое данни в паметта, всъщност можем да кажем: За процесора програма е това към което сочи eip, всичко друго са данни.
eflags
- това е т.н. флагов регистър или регистър на състоянието. Всеки
бит от него се установява в 0 или 1 (сваля или вдига) в зависимост от
състоянието на процесора в момента. Всеки бит отговаря за някакво условие.
Най-важните флагове са:
ZF
(zero flag) - установява се в 1 когато последната операция има
за резултат 0 и се установява в 0 в противен случай.
CF
(carry flag) - флаг за пренос. Вдига се в 1 ако предишната операция
за събиране/изваждане води до пренос в най-старшият разряд.
Тъй като броят на цифрите които обработва процесорът е ограничен
при събиране на 2 големи числа (32 битови) е възможно резултатът
да е 33 битов. Тогава, се записват само младшите 32 бита и флага
за пренос се вдига. (реално, именно флага за пренос е 33-тият
бит от резултата)
SF
(sign flag) - флаг за знак. Вдига се в единица, когато резултатът от
последната операция е отрицателно число. Въобще, работата с
отрицателни числа ще разгледаме малко по-нататък в цикъла.
Начинът на изпълнение на някои от инструкциите на процесора зависи от състоянието на флаговете. Например т.н. инструкции за условен преход извършват прехода ако някой флаг е в дадено състояние и не извършват преходът ако флага е в обратното състояние.
Например инструкцията jz
ще извърши "преход при нула" - тоест ако флага
ZF=1 прехода се изпълнява, ако ZF=0, прехода не се изпълнява.
III. Управляващо устройство: Най-общо управляващото устройство отговаря за изпълнението на програмата. То взима поредната инструкция от адреса записан в eip, разкодира смисъла на инструкцията и подава командите към другите части на процесора така че инструкцията да бъде изпълнена. След това взима следващата инструкция и т.н.
2. Основни инструкции, адресация
Тук ще разгледаме няколко базови инструкции, които ще са основата ни за писане на асемблер. Всички инструкции на съвременните процесори са стотици и да се научат всички не е лесна работа. Но всъщност не е и особенно нужно. На 90%, програмите се състоят от набор от 10..20 инструкции.
Всяка инструкция се състои от две главни части: т.н. КОП или код на операцията и един или 2 операнда. КОП определя какво ще прави инструкцията, а операндите определят върху какви данни ще се прави тази операция.
Например:
mov eax, ebx ; тук операндите са "eax" и "ebx" а КОП е "mov".
Тук е мястото да направим един преглед на основният синтаксис на асемблера. Основният принцип е "Един ред - една инструкция". Всеки ред на инструкция може да се състои от следните части:
етикет: КОП операнд2, операнд1 ; коментар
В общи линии никой от елементите не е задължителен с изключение, че не може да има операнди без КОП. (но например има инструкции с един операнд или съвсем без операнди)
Левият операнд е т.н. приемник (target operand) тоест в него се записва резултатът от операцията. Десният операнд е операнд източник (source) - тоест от него се четат данните.
Операндите могат да бъдат константи, регистри или адреси в паметта. Както споменахме малко по-горе, само единият операнд може да бъде адрес в паметта. (но и двата могат да са регистри). Само операндът източник може да е константа (разбира се).
Операндите в паметта се задават с адреса си. Това може да стане по много начини, които определят т.н. начини на адресация на процесора. Всички адреси се затварят в за да се укаже че става въпрос за клетка от паметта.
Има по същество един обобщен начин на адресация, като всички останали са частни случаи на общият. Той може да се дефинира с формулата:
[reg1+N.reg2+const]
- адреса се получава като стойността на reg1 се
прибави към умножената със N (N=1, 2, 4 или 8) стойност на reg2 и се прибави
константата const (кодирана в тялото на инструкцията). reg1 и reg2 са
два произволни регистъра на процесора. Може да са еднакви.
Елементите на тази формула не са задължителни, но поне един то тях трябва да присъства. В зависимост от това кои елементи присъстват имаме по-прости или по-сложни адресации:
С примери това ще стане по-ясно. Валидни адресации са например:
[123456]
- адреса е зададен абсолютно с константа.
[eax]
- проста индексна адресация. Адреса е в регистъра eax
[eax+15]
- индексна адресация с отместване от 15 байта.
[eax+ebx+24]
- адресация с два индекса и отместване.
[2*eax]
- индексна адресация с множител 2.
[2*eax+10]
- индексна адресация с множител 2 и отместване
[eax+4*ebx]
- адресация с два индекса, единият се умножава по 4
[eax+8*ebx+25]
- сложна индексна адресация с умножение на 8 и отместване.
НЕВАЛИДНИ адресации са например:
[16*eax]
- константата за умножение трябва да е 1, 2, 4 или 8
[2*eax+4*ebx]
- само единият регистър може да се умножава по константа.
Базов набор от инструкции:
1. инструкцията mov
- това е може би най-употребяваната инструкция на
процесора. Копира данните от операнда източник в операнда приемник.
Не променя флаговете.
Примери:
mov eax, 1234h ; вкарва константата 1234h в регистъра eax
mov ebx, eax ; записва стойността на eаx в ebx.
Важно: Още веднъж, посоката на движение на данните е отдясно наляво. горната инструкция ще извърши операцията: ebx := eax;
mov eax, [10000h] ; прочита паметта на адрес 100000h и записва стойността и
; в eax. Тъй като eax е 32 битов регистър, ще се прочетат
; 4 последователни байта от паметта.
mov al, [10000h] ; същото но ще прочете само един байт, защото регистърът
; al е 8 битов. (това е най-младшият байт на eax).
2. Инструкцията add оп1, оп2 - събира двата операнда и резултата се записва в оп1. Тоест оп1 = оп1 + оп2
Установява флаговете в зависимост от резултата.
add eax, 21 ; eax = eax + 21
add eax, ebx ; eax = eax + ebx
add eax, [12345h] ; eax = eax + [12345h] ; събиране с операнд от паметта.
add [12345h], eax ; [12345h] = [12345h] + eax ; събиране на клетка от паметта с регистър.
Има цяла група инструкции, които действат по този начин, извършвайки аритметични или логически действия върху два операнда. Ще изредя само няколко от тях, като ще опиша само операцията която извършват, тъй като в останалите неща всичко е еднакво:
sub оп1, оп2
- аритметично изваждане оп1 = оп1 - оп2
and оп1, оп2
- логическа операция И (AND) оп1 = оп1 and оп2
or оп1, оп2
- логическа операция ИЛИ (OR) оп1 = оп1 or оп2
xor оп1, оп2
- логическа операция изключващо ИЛИ (XOR) оп1 = оп1 xor оп2
Тук трябва да споменем и за:
test оп1, оп2
- Тази инструкция извършва логическо И, също като and но резултата
на операцията не се записва в оп1. Служи за установяване на флаговете в
нужните стойност, без в действителност да променяме регистъра.
Двата операнда от горните инструкции могат и да са еднакви, тогава се получават интересни трикове:
sub eax, eax
xor eax, eax - два начина икономично да нулираме някой регистър.
or eax, eax
test eax, eax
and eax, eax - тези инструкции не променят регистъра, но установяват
флаговете в зависимостот стойността на този регистър.
Например ако eax = 0, флагът ZF ще се вдигне.
Следва продължение....
Last modified on: 06.06.2012 21:41:27