본문 바로가기
Computer Science/시스템 프로그래밍

SIC/XE 아키텍처: 기본 구조에서 명령어 집합까지 (2)

by 큌 2024. 3. 3.
반응형

Translating and Starting a Program

  • 컴퓨터의 초창기에 프로그래머들은 기계 코드 코딩 (다른 것이 없었기 때문에)
    • 기계 코드는 이진 코드(예: 11100110000001100001100101110000)이며, 사람이 읽을 수 없지만(극도의 어려움이나 훈련 없이), 기계는 매우 읽기 쉬움
  • 이제 다른 프로그램들이 기계 코드로 번역되는 언어로 프로그램을 작성
    • Compiler
    • Assembler
    • Linker
    • Loader

우리는 고급 언어(High Level Language)를 사용


Translation Hierarchy

  • 고급 언어 프로그램을 컴퓨터에서 실행되는 프로그램으로 변환하는 4단계
    • 일부 시스템은 이러한 단계를 결합하거나 건너뛰어 번역 시간을 단축

 


컴파일러 → 어셈블러 → 링커 → 로더

링커는 여러 오브젝트 파일을 통합하여 하나의 실행가능한 파일로 만듬

로더는 운영체제에서에서 제공하며 운영체제의 레이어 중 하나


What is “Assembler”?

  • 어셈블리 언어로 작성된 프로그램을 기계 코드(즉, xxx.o라는 오브젝트 파일)로 변환하기 위한 시스템 S/W
    • 어셈블리 언어는 기계가 이해하는 것의 상징적 형태
    • 오브젝트 파일은 기계어 명령, 데이터 및 정보를 메모리에 올바르게 배치하는 데 필요한 명령의 조합
  • 어셈블러가 반드시 수행해야 하는 몇 가지 기본 기능
    • opcode 테이블을 통해 니모닉 작동 코드를 해당 기계어로 변환
    • 프로그래머가 사용하는 심볼릭 레이블에 컴퓨터 주소 할당
      • 어셈블러는 모든 레이블에 해당하는 주소를 결정해야 함
      • 어셈블러는 심볼 테이블(라벨의 이름을 명령/데이터가 차지하는 메모리 주소와 일치시키는 테이블)을 통해 명령에 사용된 라벨을 추적

opcode 테이블을 통해 매핑하여 니모닉 작동 코드를 기계어로 변환


Assembler & Its Machine Dependency

  • 기본적인 기능만을 고려한다면, 대부분의 어셈블러들은 매우 유사
  • 그러나 어셈블러의 기능과 디자인은 명령 형식과 주소 지정 모드와 같은 기계 아키텍처에 크게 의존
  • → Machine Dependent Features 기계 종속 기능
  • 먼저 SIC(Simplified Instructional Computer)의 표준 버전을 위한 기본 어셈블러 설계를 알아보자
    • 다음 몇 개의 슬라이드의 그림 2.1은 어셈블러 언어 프로그램을 보여줌

SIC Assembler Language Program 1 (Fig. 2.1, as an example)

  • 라인 번호는 참조용일 뿐 프로그램의 일부가 아님
  • 사용된 mnemonic instructions은 1장 및 부록 A에 소개된 지침
  • 피연산자 뒤에 수식자 ",X"를 추가하여 Indexed Addressing를 나타냄(다음 슬라이드의 행 160 참조)
    • STCH BUFFER, X
  • "."로 시작하는 행에는 주석만 포함(110, 115, 120 및 195, 200, 205 참조)

SIC Assembler Language Program 2 (Fig. 2.1, as an example)

  • mnemonic machine instructions 외에도 다음과 같은 assembler directives 사용
    • START: 프로그램의 이름 및 시작 주소 지정
      • COPY START 1000
      • 이름 START 시작 주소
    • END: 소스 프로그램의 끝을 표시하고 (선택 사항) 프로그램의 첫 번째 실행 명령을 지정
      • FIRST STL RETADR .... END FIRST

시작 주소는 Zero Value를 의미

START Directive를 통해서 이 프로그램의 이름과 시작 주소를 정할 수 있음

물론 START Directive를 사용하는 것은 하나의 옵션

사용하지 않을 경우 디폴트 네임 및 시작 주소를 가지게 됨

디폴트 시작 주소는 0

END Directive를 통해서 소스 프로그램이 어디서 끝나는지 명시 가능

물론 END Directive를 사용하는 것은 하나의 옵션

어떤 명령어가 실행되어야할 First instruction임을 명시

이렇게 명시를 해두면 운영체제가 이 프로그램을 시작할때 First instruction를 찾아서 실행


SIC Assembler Language Program 3 (Fig. 2.1, as an example)

  • BYTE: 문자 또는 16진수 상수 생성(상수를 나타내는 데 필요한 바이트 수)
    • EOF BYTE C’EOF’ → prefix C: char, 3Byte
    • INPUT BYTE X’F1’ → prefix X: hex constant, 1Byte
  • WORD: one-word 정수 상수 생성
    • THREE WORD 3
  • RESB: 데이터 영역에 대해 표시된 바이트 수를 예약
    • BUFFER RESB 4096 → 4096Byte
  • RESW: 데이터 영역에 대해 표시된 워드 수를 예약
    • RETADR RESW 1 → 1Word = 3Byte
  • Directives는 machine instructions으로 변환되지 않음
    • 대신 어셈블러 자체에 instructions을 제공

Directives는 명령어로 변환되지 않음

Directives는 프로그램에 메타데이터를 제공하기 위해 상수 값을 정의하거나 메모리 공간을 정의하는 것과 같은 몇 가지 작업을 수행하도록 어셈블러에 명령하는 데 사용


SIC Assembler Language Program 4 (Fig. 2.1, as an example)

  • 이 프로그램에는 입력 장치(장치 코드 F1로 식별됨)에서 레코드를 읽고 출력 장치(장치 코드 05로 식별됨)로 복사하는 메인 루틴이 포함
  • 이 메인 루틴은 두 개의 서브 루틴을 호출
    • RDREC: 레코드를 버퍼로 읽기(버퍼 사이즈 = 4096바이트)
    • WRREC: 버퍼에서 출력 장치로 레코드 쓰기


3개의 루틴 존재, 메인 루틴과 2개의 서브 루틴 RDREC 그리고 WRREC

기본적으로 예시 프로그램은 3가지 코드 루틴으로 실행

메인 루틴이 2개의 서브 루틴을 호출하여 인풋 디바이스로 부터 레코드를 읽어내고, 그 레코드를 아웃풋 디바이스로 쓰기를 함

인풋 디바이스에 여러 파일이 있고 그 파일은 여러 레코드로 구성


SIC Assembler Language Program 5 (Fig. 2.1, as an example)

  • 사용 가능한 I/O 명령은 RD 및 WD 뿐이므로 각 서브 루틴은 한 번에 한 문자씩 레코드를 전송해야 함
    • 각 레코드는 여러 문자로 구성
    • 각 레코드의 끝에는 NULL 문자(16진수 00)가 표시
    • 입력 장치와 출력 장치 간의 I/O 속도가 다르기 때문에 버퍼가 필요

각 레코드는 여러 문자(캐릭터)로 구성, 레코드는 문자열과 유사

레코드를 읽고 쓰기 위한 RDREC, WDREC은 RD 명령어와 WD 명령어를 사용

이 명령어는 한번에 하나의 바이트 = 캐릭터만 이동 가능

프로그램에서 버퍼 영역을 제공하는 이유는 인풋 디바이스와 아웃풋 디바이스의 속도 차이로 인한 문제를 완화하기 위함

아웃풋 디바이스가 1초에 1바이트를 처리하고, 반면 인풋 디바이스는 1초에 10바이트를 내보낼 수 있음

아웃풋 디바이스가 1바이트에 처리될 동안 나머지 9바이트는 아웃풋 디바이스로 전달이 되지만 저장될 장소가 없으면 드랍됨

즉 데이터 전송중에 로스가 발생할 수 있음

이러한 상황을 막기위해 사용하는 것이 버퍼 영역

버퍼에서 일단 인풋 디바이스의 10바이트를 받고, 아웃풋 디바이스에서 1바이트씩 읽을 수 있도록 함

그럼 우리가 데이터 손실 없이 인풋 디바이스에서 아웃풋 디바이스로 데이터 전송 가능

물론 버퍼가 가득 차면 데이터 손실이 발생할 수 있음

시스템 디자인에서 버퍼 크기를 정하는 것도 중요한 이슈

이런 버퍼를 이용하여 양쪽 IO 디바이스의 속도 차이로 인한 문제를 완하하는 기술이 버퍼링

버퍼링은 굉장히 많이 사용


SIC Assembler Language Program 6 (Fig. 2.1, as an example)

  • 파일의 끝이 감지되면 프로그램은 출력 장치에 EOF를 쓰고 RSUB 명령을 실행하여 종료
    • 우리는 프로그램이 JSUB 명령을 사용하여 OS에 의해 호출되었다고 가정
      • JSUB m ? L ← (PC); PC ← m
    • 따라서 RSUB는 OS에 대한 제어권을 반환
      • RSUB ? PC ← (L)

우리가 인풋 디바이스에서 파일을 읽다보면 언젠가 이 파일의 끝 EOF를 만남

그러면 이 프로그램은 아웃풋 디바이스에 EOF 캐릭터를 써야함

지금 인풋 디바이스 파일에 읽어낼 데이터가 더 없다~

그후 프로그램이 종료될때 RSUB 명령어를 실행하여 종료

기본적으로 프로그램이 OS에 실행된다고 가정, 이것은 굉장히 일반적인 가정

우리가 어떤 프로그램을 실행할때 터미널에서 커멘드를 통해 운영체제에 명령 전달

OS는 먼저 그 타겟 프로그램 파일의 위치를 찾아내고 그 파일에 있는 코드와 데이터를 로더를 통해 메모리로 올림

그 타겟 프로그램의 코드를 트리거, 실행, 이때 사용하는 명령어가 JSUB 명령어

JSUB 명령어는 OS에서 다른 타겟 프로그램을 호출할때도 사용

PC에 있는 값을 먼저 레지스터 L에 저장, 이것은 곧 리턴 어드레스, 돌아올 메모리 주소

그후 우리가 실행할 명령어 주소 즉, 타겟 프로그램의 첫번째 명령어는 PC로 저장

JSUB을 통해 타겟 프로그램이 실행, 그 코드를 쭉 실행하다 어떤 시점에 프로그램 종료

이때 RSUB 명령어를 사용해서 레지스터 L에 저장된 리턴 어드레스를 PC로 저장하여 코드 플로우가 다시 OS로 넘어감

이것이 프로그램 실행시 전체적인 워크 플로우

C프로그램의 리턴과 유사


Main Routine of Fig. 2.1 (1)

레지스터 L에는 리턴 어드레스가 존재

STL RETADR을 통해 메모리에 레지스터 L의 리턴 어드레스 저장

레지스터 L이 이후에 다른 용도로 사용되기 때문, RDREC나 다른 서브 루틴 호출시 사용

서브 루틴 호출시 레지스터 L이 다른 데이터로 덮어 씌워짐

그렇기 때문에 첫번째 명령어를 통해 다른 영역에 잠깐 보관


Sub Routine RDREC of Fig. 2.1

Main Routine에서 레지스터 L에 존재하는 리턴 어드레스를 다른 메모리에 저장

그리고 RDREC을 호출

레지스터 X와 레지스터 A를 0으로 초기화

TD INPUT

JEQ RLOOP

명령어에서 인풋 디바이스의 준비 상태를 확인

인풋 디바이스가 준비가 되면 인풋 디바이스로 부터 1바이트 데이터를 읽어드리기 시작

1바이트 데이터는 레지스터 A의 가장 오른쪽에 저장

COMP ZERO

명령어를 통해 비교, 이때 만약 읽어드린 데이터가 일반적인 데이터일수도 있지만 레코드의 끝자락을 읽는 경우 데이터가 널 캐릭터일 수 있음

즉 COMP를 통해 레코드의 끝인지 아닌지 판단

만약 이 값이 널 캐릭터인 경우 즉, 레코드의 끝인 경우

JEQ EXIT

명령어를 통해

STX LENGTH

명령어로 점프 후 최종 적으로 종료

만약 이 값이 널 캐릭터가 아닌 경우, 즉 레코드의 끝이 아닌 경우

이 값을 버퍼에 써줌

STCH BUFFER, X

명령어를 통해 버퍼에 값을 저장, Indexed Addressing을 사용

그후

TIX MAXLEN

명령어를 통해 X 레지스터 값을 1 증가시킨 후 MAXLEN과 비교

즉 이 버퍼가 가득 찼는지 아닌지를 검사

버퍼가 가득 찼을 경우 이 서브 루틴을 종료하고 메인으로 돌아감

버퍼가 가득 차지 않았을 경우 데이터를 더 저장할 수 있으므로 RLOOP 코드 루틴으로 돌아감

이후 1바이트를 읽어드리는 위의 과정을 반복

RDREC 서브루틴에서는 2가지의 조건을 확인

첫번째

COMP ZEPO

명령어에서 지금 읽어들인 데이터가 레코드의 끝인지, END OF RECORD인지 검사

레코드의 끝이면 EXIT으로 표기된 명령어 부분을 실행

두번째

TIX MAXLEN

지금 버퍼가 가득 찼는지 검사

버퍼가 가득 찼으면 동일하게 RSUB로 빠져 나감

만약 이 두가지 조건에 안걸리면

계속해서 인풋 디바이스를 통해 1바이트 데이터를 읽어드림


Main Routine of Fig. 2.1 (2)

RSUB 명령어를 통해 RDREC 서브 루틴을 빠져 나간 이후

LDA LENGTH

명령어를 통해 LENGTH 값을 레지스터 A로 이동

이후

COMP ZERO

명령어를 통해 LENGTH의 값이 0보다 작거나 같은지 검사

만약 작거나 같다면 RDREC 서브루틴을 통해 인풋 디바이스에서 버퍼로 읽어드린 데이터가 없는 것

ENDFILL로 점프하여

LDA EOF 명령어를 실행하여 종료

만약 LENGTH의 값이 0보다 크다면 RDREC 서브루틴을 통해 인풋 디바이스에서 버퍼로 읽은 데이터가 있는 것

이후

JSUB WRREC

명령어를 실행하여 WRREC 서브루틴으로 점프


Sub Routine WEREC of Fig. 2.1

기본적으로 WEREC은 RDREC과 굉장히 유사한 로직을 가짐

다만 데이터의 방향이 버퍼에서 아웃풋 디바이스인 것만 다름

레지스터 X를 0으로 초기화

TD OUTPUT

JEQ WLOOP

명령어에서 아웃풋 디바이스의 준비 상태를 확인

아웃풋 디바이스가 준비 상태면, 버퍼 데이터를 아웃풋 디바이스로 써야함

그러나 바로 쓰지 못하고 레지스터 A에 버퍼 데이터를 옮겨야함

LDCH BUFFER, X

명령어를 사용하여 Indexed Addressing 사용

첫 반복에서는 버퍼에서의 첫번째 데이터가 레지스터 A로 로드

그렇게 로드된 데이터가

WD OUTPUT

명령어를 통해 데이터를 아웃풋 디바이스로 씀

TIX LENGTH

명령어를 통해 레지스터 X의 데이터를 1 증가시켜 조건을 체크

레지스터 X가 LENGTH보다 큰지 작은지 확인, LENGTH는 버퍼에 저장된 인풋 데이터의 크기

레지스터 X 데이터의 의미는 지금까지 쓰기한 데이터의 크기

즉 버퍼에서 쓰기할 데이터가 남아있는지 확인

데이터가 남아있으면 레지스터 X의 값은 LENGTH 보다 작을 것이고 WLOOP로 돌아가 상위 작업 실행

이러한 작업을 반복하면 언젠가 레지스터 X의 값이 LENGTH와 동일

즉 버퍼에 입력 데이터가 더이상 남아있지 않은 순간 발생

이때

RSUB

명령어를 호출에 메인 루틴으로 복귀


Main Routine of Fig. 2.1 (3)

RSUB 명령어를 통해 WRREC 서브 루틴을 빠져 나간 이후

J CLOOP

명령어를 실행하며 RDREC을 실행하여 읽고 WRREC를 실행하여 씀

이러한 과정을 반복하다보면 어느순간 EOF를 마주치는 일이 발생

즉 RDREC을 통해 읽어드린 데이터가 없으므로 LENGTH가 0이 됨

이후

COMP ZERO

명령어를 실행하여 비교 결과가 Equal이 될 것임

이후

JEQ ENDFILL

명령이 실행되어 점프

EOF를 만나게 되면 EOF 캐릭터를 아웃풋 디바이스에 쓰게 되고 RSUB를 실행하여 종료, 이것이 나머지 로직

LDA EOF

STA BUFFER

명령어를 통해 레지스터 A에 EOF를 쓰고 BUFFER에 EOF를 이동

이후

LDA THREE

명령어 실행

THREE의 값인 3을 레지스터 A에 저장

THREE의 의미는 EOF 캐릭터의 크기 3Byte

이후

STA LENGTH

명령어를 실행하여 레지스터 A에 저장된 3을 LENGH에 저장

버퍼는 EOF 캐릭터를 가지고 있는 것

LENGTH는 EOF 캐릭터의 크기를 가지고 있는 것

우리는 아까 RDREC에서 버퍼에 데이터를 채워넣었고, LENGTH 값을 채워 넣었음

우리가 읽어드린 데이터를 쓰기 위해서

이 동작을

LDA EOF

STA BUFFER

LDA THREE

STA LENGTH

메인루틴의 4개의 명령어에서 해주는 것

즉 EOF 캐릭터를 쓸 준비를 해주는 것

이후

JSUB WRREC

서브루틴 WRREC 호출하면 WRREC은 버퍼에 있는 3바이트의 데이터를 아웃풋 디바이스로 쓰기함

아웃풋 디바이스에서 EOF 캐릭터가 쓰기되면

LDL RETADR

명령을 실행하여 RETADR 메모리 공간에 저장된 리턴 어드레스를 레지스터 L로 복원

RSUB

명령을 실행하여 운영체제로 코드플로우가 넘어가 프로그램이 종료

반응형