chihyeonwon / System_Programming_Outline

시스템 프로그래밍 ( 어셈블리, 컴파일러)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

System_Programming_Outline

시스템 프로그래밍 정리( 어셈블리, 컴파일러)

1장 어셈블리 프로그래밍

컴퓨터의 기본 구성 요소

  1. 컴퓨터를 구성하는 기본적인 요소는 CPU와 주기억장치(메모리)이다.
  2. CPU와 메모리는 3개의 버스로 연결되어 있다.

주소 버스 : 메모리에 접근하기 전 접근하고자 하는 주소를 전달
제어 버스 : 메모리에 읽기 쓰기 등 기능 및 통제 신호 전달
데이터 버스 : 읽기 때는 저장값이 버스로 실려 전달 쓰기 때는 버스로 전달되는 값이 메모리에 저장

레지스터

메모리와 같은 대량 저장 장치외에 소량의 저장 장치를 레지스터라고 한다.
PC, MAR, MBR, IR, PSW, AC, IX 등이 레지스터이다.

CPU 제어 회로

CPU 제어 회로는 발진기에서 공급되는 클록에 따라 단계적 처리가 진행되도록 설계되어 있다.
그러므로 발진기의 속도가 곧 CPU의 처리 속도를 결정한다.

1KHz : 1초에 1024번 진동
1MHz : 1초에 10241024번(100만번) 진동
1GHz : 1초에 1024
1024*1024(10억번) 진동

컴퓨터가 연산(계산)을 진행하는 과정

  1. 메모리에서 기계 명령어를 가져온다. 이 때 가져올 기계 명령어의 위치는 주소버스를 통해 메모리에 전달
  2. 기계 명령어에 포함된 피연산자 주소를 참조하여 다시 메모리에 접근한다.
    만약 읽어야 할 명령어라면 CPU내 레지스터로 읽어들이고 써야 할 명령어라면 레지스터에 보관중인 값을 메모리에 쓴다.
  3. 단순히 읽기만 하지않고 읽은 후 연산을 의미한다면 읽어들인 값에 대해 주어진 연산을 처리한다.

연산 레지스터 AC

ADD나 SUB 명령어에서와 연산이 이루어질 수 있는 CPU 내 레지스터를 누산기라고 한다.
레지스터 이름을 A 혹은 AC라고 부른다.

x = y // load 명령어로 메모리의 변수 y 값을 CPU 레지스터에 읽는다. store 명령어로 cpu 레지스터 값을 메모리 변수 x에 저장한다.
z = x + y // load 명령어로 메모리의 변수 y값을 CPU 누산기에 읽는다. add 명령어로 cpu 누산기에 메모리의 변수 y 값을 더한다. store 명령어로 cpu 누산기의 값을 변수 z에 저장한다.

기계 사이클

CPU에 설계된 회로가 기계 명령어를 읽어서 처리하는 일련의 과정을 기계 사이클이라고 한다.
크게 보면 인출, 해독, 피연산자 획득, 실행 등 네 사이클의 과정을 영원히 되풀이한다.

  1. 인출 사이클 : CPU 내 PC 레지스터에 저장된 주소의 메모리에서 기계 명령어 하나를 읽어온다.
  2. 해독 사이클 : 기계 명령어의 연산 코드가 어떤 것인지 식별한다.
  3. 피연산자 사이클 : 해독된 결과에 따라 메모리로부터 피연산자 값을 획득한다.
  4. 실행 사이클 : 획득한 피연산자 값과 누산기 사이에 연산 코드에 대응되는 연산을 처리한다.

PC (Program count) -> MAR(주소 버스)
M -> MBR(데이터버스)
PC + 4 -> PC (다음 명령어 4는 명령어길이)
MBR(2023) -> IR : 어떤 연산인가를 판별
MBR(0
19) -> MAR : MAR에 복사하여 주소 버스에 실어서
M -> MBR : MAR로 복사
AC + MBR -> AC : 연산을 처리하여 그 결과를 AC에 보관

SVM(Simple Virtual Machine) 단순 가상 기계 어셈블리 프로그래밍

svm 기계 구조

  1. 기계 명령어의 길이 모든 기계 명령어는 32비트(4바이트)이다.
  2. 연산 코드의 개수 : 연산 코드 8비트 중 상위 두 비트는 00으로 고정 나머지 6비트에 의해 총 64개의 서로 다른 연산 코드 지원
  3. 피연산자의 개수 : 오직 하나의 주소 피연산자만을 표기 가능
  4. 주소를 표현하는 비트 수 : 23번째 X비트가 뒤에 주어진 주소 값에 X레지스터 값을 더하여 최종 주소로 사용할 것인가를 지시 따라서
    0~2의 23승 -1 이 최종 주소 표현 범위 -> 8M-1

레지스터 용도

U : 점프 시 돌아올 주소의 번지를 보관, 리턴 명령어에서 그 주소를 참조
X : 배열 인덱스를 구현하기 위해 사용 X 레지스터 값을 0,1,2와 같이 증가시키면 첨자기반 배열 접근 가능
A : SVM 모든 연산은 A레지스터에서 이루어지고 결과가 A레지스터에 누적된다.

적재, 저장 연산 정의

LDA m // m부터 시작하는 4바이트 내용을 A레지스터에 적재
LDAW m // m부터 시작하는 2바이트 내용을 A레지스터에 적재
LDAB m // m부터 시작하는 1바이트 내용을 A레지스터에 적재

LDX m // m부터 시작하는 4바이트 내용을 X레지스터에 적재
LDU m // m부터 시작하는 4바이트 내용을 U레지스터에 적재

STA m // A레지스터 내용을 m부터 시작하는 메모리 4바이트에 적재
STAW m // A레지스터 내용을 m부터 시작하는 메모리 2바이트에 적재
STAB m // A레지스터 내용을 m부터 시작하는 메모리 1바이트에 적재

STX m // X레지스터의 내용을 m부터 시작하는 메모리 4바이트에 적재
STU m // U레지스터의 내용을 m부터 시작하는 메모리 4바이트에 적재

수리 연산 코드 정의

ADD m // m부터 시작하는 메모리 4바이트의 내용을 A 레지스터에 가산
ADDW m // m부터 시작하는 메모리 2바이트의 내용을 A 레지스터에 가산
ADDB m // m부터 시작하는 메모리 1바이트의 내용을 A 레지스터에 가산

SUB m // m부터 시작하는 메모리 4바이트의 내용을 A 레지스터에 감산
SUBW m // m부터 시작하는 메모리 2바이트의 내용을 A 레지스터에 감산
SUBB m // m부터 시작하는 메모리 1바이트의 내용을 A 레지스터에 감산

MOD m // m부터 시작하는 메모리 4바이트 내용으로 A레지스터 4바이트에 mod 연산

논리(비트) 연산

AND m // m부터 시작하는 메모리 4바이트의 내용을 A 레지스터에 AND 연산
OR m // m부터 시작하는 메모리 4바이트의 내용을 A 레지스터에 OR 연산
XOR m // m부터 시작하는 메모리 4바이트의 내용을 A 레지스터에 XOR 연산

SHL m // m부터 시작하는 메모리 4바이트의 내용만큼 A 레지스터 4바이트를 왼쪽으로 시프트
SHR m // m부터 시작하는 메모리 4바이트의 내용만큼 A 레지스터 4바이트를 오른쪽으로 시프트

비교 연산

CMP m, (CMPW m, CMPB m) // A 레지스터 4(2, 1)바이트와 주소 m부터 시작하는 내용을 뺄셈으로 비교
IXC m // X레지스터 4바이트를 4 증가시키고 그 결과와 주소 m부터 시작하는 메모리 4바이트를 뺄셈으로 비교
IXWC m // X레지스터 4바이트를 2 증가시키고 그 결과와 주소 m부터 시작하는 메모리 4바이트를 뺄셈으로 비교
IXBC m // X레지스터 4바이트를 1 증가시키고 그 결과와 주소 m부터 시작하는 메모리 4바이트를 뺄셈으로 비교

분기 연산 정의

JZ m // PSW 레지스터의 Z 플래그가 1이면 m 번지로 점프
JNZ m // PSW 레지스터의 NZ 플래그가 1이면 m 번지로 점프
JC m // PSW 레지스터의 C플래그가 1이면 m 번지로 점프
JMP m // 무조건 주소 m번지로 점프

GSUB m // 서브루틴으로 점프
RSUB // 서브루틴에서 주프로그램으로 점프

입,출력 연산 정의

CID m // m 부터 시작하는 메모리에 주어진 입력 장치 포트의 상태를 체크
COD m // m 부터 시작하는 메모리에 주어진 출력 장치 포트의 상태를 체크
IN m // m부터 시작하는 메모리 4바이트에 주어진 입력장치포트에서 1바이트를 읽어 A레지스터 하위 1바이트에 적재
OUT m // A레지스터 하위 1바이트 내용을 m부터 시작하는 메모리 4바이트에 주어진 출력 장치 포트로 출력

어셈블리 프로그램에서 상수 및 변수 공간 확보

기계명령어와는 달리 주어진 위치에 주어진 형태의 공간 확보를 지시한다는 의미로 지시자가 존재한다.

어셈블러 지시자 정의

DD vd0 // 4바이트의 공간을 확보하고 특정 값을 채움
DW vw0 // 2바이트의 공간을 확보하고 특정 값을 채움
DB vb0 // 1바이트의 공간을 확보하고 특정 값을 채움
RD nd // 4바이트 단위의 공간을 하나 이상 확보
RW nw // 2바이트 단위의 공간을 하나 이상 확보
RB nb // 1바이트 단위의 공간을 하나 이상 확보

변수 주소의 기호화

변수 공간 확보 명령어 앞에 기호를 두고 이들 기호가 주소를 의미하도록 코딩할 수 있다.

DMP 니모닉 코드(연산 명령어)

디버깅을 위해 레지스터나 메모리에 저장된 값을 출력하는 DMP 명령어를 정의한다.

DMPR r // PC(:0), U(:1), X(:2), A(:3) 레지스터 덤프
DMP m // 주소에 해당하는 메모리 내용 덤프
DMP m[X] // 주소에 해당되는 메모리 내용 덤프
DMPS m // 주소에서 시작하는 문자열 덤프

HALT 니모닉 코드

START로 시작하고 END로 끝을 맺는다. 실행도중 프로그램을 종료시킬 수 있는 명령어가 필요하다.
길이가 4바이트고 피연산자가 필요없으며 연산 코드는 3Fh이다.

FILE 지시 명령어

SVM의 IN, OUT 기계 명령어 지원을 위해 입출력 장치포트 번호를 디스크 파일과 매핑하는 FILE 지시 명령어를 제공한다.

FILE 124, I, DATA.TXT

난수 생성 내장 변수

LDA RND 혹은 LDA 7FFFFFh를 사용하면 4바이트 난수가 얻어진다.
반대로 STA RND 혹은 STA 7FFFFh를 사용하면 난수 씨앗을 새롭게 설정한다.

2장 SVM 어셈블러

프로그램의 총 크기와 기호(Label)과 대응되는 주소값을 작성하는 기호 테이블로 관리한다.

프로그램의 총 크기는 END 기호 바로 위의 주소(128h)에서 메모리 차지크기(4x100=190h)를 더한 2B8h 가된다.
여기서 프로그램의 크기는 2B8h-100h(시작주소)=1B8h가 된다.

목적 프로그램 구성안 1

목적 프로그램 구성안1

H:이름:적재시작주소:프로그램크기:실행시작주소
연산코드피연산코드 -> 32비트

문제점 : 공간만확보하는 부분(RD)가 포함되는 목적 코드가 없는 빈틈이 발생한다.

목적 프로그램 구성안 2

위의 문제점을 해결하기 위한 방법으로 아래의 방법을 도입한다.

H:이름:적재주소:프로그램크기
T:시작주소:0C(12바이트/4=명령어3개 작성)  //256바이트에 따라 T를 여러개로 분리가능
T:시작주소:18(16+8=24바이트/4= 명령어 6개 작성)
D:
E:

T는 텍스트(기계명령어), D는 데이터(변수), E는 시작주소 ^는 구분자(실제로는 없음)

1-PASS Assembler 의 설계와 구현

수동조립과정(2-pass)을 소프트웨어로 해결한다. 어셈블러를 C언어로 구현한다(1-pass)

어셈블러를 위한 자료구조

  1. LOC (Location Counter)
int LOC = 0;
  1. LDaddr(Load address), GOaddr(Execution start address)
소스 코드의 첫줄(START) 기술된 프로그램 적재주소(LDaddr) 마지막 (END) 기술된 실행시작 주소(GOaddr) 관리한다.
  1. SYMTAB(Symbol Table), SYMCNT(Symbol Counter)
typedef struct symtab {
   char symbol[MAX_SLEN];
   int value; // 몇 번지 주소
} Symtab;

현재 처리중인 줄에 기호가 존재할 경우 <기호, 현재 LOC 값> 쌍을 기호테이블에 삽입하고
기호 개수(SYMCNT)를 1 증가시킨다.

int SYMCNT = 0;
Symtab SYMTAB[MAX_SLEN];
  1. LNO, LABEL, OPcode, OPerand

어셈블러가 어셈블리 원천 코드의 한줄을 읽었을 때 해당 줄에 존재하는 라인번호, 기호, 명령어, 피연산자를
토큰으로 분리하여 LNO, LABEL, OPcode, OPerand에 각각 보관한다. 해당 필드가 존재x -> NULL 값을 갖는다.

char *LNO, *LABEL, *OPcode, *OPerand;
  1. OPTAB(Operation Code Table)

어셈블리 원천 코드에 기입된 명령어가 니모닉 코드인지를 검색하기 위해 svm이 제공하는 모든 연산 명령어에 대해 <니모닉 코드, 연산코드, 피연산자 개수> 쌍을 테이블로 관리한다.

typedef struct optab {
   char mnemonic[MAX_OLEN];
   unsigned char opcode;
   char n_opernad;
} Optab;
Optab OPTAB[MAX_OP] = {
   "LDA", 0x00, 1,
   "RSUB", 0X20, 0,
}
  1. DCTAB(Directive Command Table)

명령어가 OPTAB에서 검색되지 않을 경우 이번에는 DD 등 변수 공간 확보 명령어인가를 검색하기 위하여
<지시 명령어, 타입(상수 혹은 공간), 크기 단위> 쌍을 테이블로 관리한다.

typedef struct dctab {
   char directive[MAX_DLEN]; //지시명령어
   char type; // CNST or RESV // 타입
   char unit; // 4 or 2 or 1 // 단위
} Dctab;
Dctab DCTAB[MAX_DC] = {
   "DD", CNST, 4,
   "DW", CNST, 2,
   "RD", RESV, 4,
}

1-패스 어셈블러 알고리즘

  1. 1pass 어셈블러 전제조건 : 전방창조가 아니여야한다(미리 앞에 나와있어야함)
  2. 어셈블러를 구현하려면 소스 파일을 읽고 읽은 내용에서 공백으로 분리된 단어를 끊어 내고
    추출한 단어가 특정 문자열인지를 비교하거나 단어나 문자열을 복사하는 등의 작업이 필요하다.

문자열 채우기

문자열을 채우는 방법은 주로 fgets(buf, sizeof(buf), fp) 함수를 사용한다.

문자열 비교하기

문자열을 비교하는 방법으로는 strcmp(buf1, buf2) 형태를 사용한다.

문자열 분리해 내기

문자열을 분리하는 방법으로는 strtok(char *buf, char *delimiter)를 사용한다.
처음에 분리할때는 wp1=strtok(buf, " \t\n\r");을 넣어주고
이후 이어서 분리할때는 wp2=strtok(NULL, " \t\n\r") 첫번째 매개변수에 NULL을 넘겨준다.

어셈블러 구현 => 함수 5개를 엮어서 xsms 명령어를 만드는 것이 최종 목표이다.

하나의 파일로 작성된 거대 프로그램의 문제점이 발생

컴파일러를 구현하기 위해 5개의 함수가 필요한데 하나의 파일로 작성하면
여러가지 문제점이 발생하는데 이 문제점을 해결하려고 C언어의 파일분리를 사용한다.

C 프로그램의 구성 요소

크게 세가지로 나뉜다.

  1. 사용자 정의 타입 : typedef, #define, enum, struct, union 정의가 포함
  2. 전역 변수(함수 밖 정의) : 단위 변수, 구조형 변수
  3. 함수 : main() 함수부터 시작해서 호출 복귀에 의해 주 종 관계를 이루는 모든 함수

C 프로그램의 파일 나누기

1단계 : 함수를 중심으로 단순 분리

함수와 관계되는 사용자 정의 타입과 전역 변수를 함수와 동일한 파일에 배치
파일이름은 포함 함수를 연상하도록 명명

책에서는 main, 레이블 테이블 구성하는 symtab, 기계명령어 테이블 구성하는 optab, 지시명령어 테이블 구성하는 dctab으로
크게 4개의 함수 파일로 분리

2단계 : 헤더 파일 작성 및 include

분리된 파일 각각에 대해 사용자 정의타입, 전역 변수 선언(extern 필수), 함수 선언(extern 선택)을 헤더파일로 작성하고
정의 부분을 본체 파일에 둔다.
헤더 파일의 이름은 본체 파일과 동일하게 한다.
본체 파일에서 헤더 파일을 include 해준다. main 함수는 사용자정의타입이나 변수가 없으므로 main.c 자체 헤더파일이 필요없고 3개만 만들면됨

여기서 주의할점 헤더파일은 컴파일 안한다. 본체파일이 컴파일될때 헤더파일을 껴서 컴파일 처리하기 때문

main() 함수에서는 정의된 함수들을 사용하므로 선언이 정의되어있는 헤더파일을 모두 include 해준다.

Makefile 작성

컴파일 명령어 즉 목적 파일을 만드는 명령어는 다음과 같다.

cc -o 생성할파일이름 .c파일 모두 나열
cc 목적코드 나열

변경된 파일만 다시 컴파일 하는 방법은 다음과 같다.

cc main.o symtab.o optab.c dctab.o

변경없는 파일은 .o 목적코드파일 지정 변경있는 파일만 본체 코드 .c를 지정

파일 변경의 의미

  1. 말 그대로 본체 파일이 변경된 경우
  2. 본체 파일 변경 x -> include 된 헤더 파일이 변경된 경우

About

시스템 프로그래밍 ( 어셈블리, 컴파일러)