1. 인코딩 관련

 

문자열 인코딩과 문자 집합

- 프로세스와 메모리에서는 바이너리로 데이터를 읽고, 쓰므로 모든 것들이 2진수로 처리된다. 문자열 인코딩은 2진수 데이터를 어떤 식으로 읽어서 문자로 표현할지에 대한 방식을 의미한다.

- UTF-16으로 쓴 문자열을, UTF-8로 읽을 경우 문자가 깨지게 된다. 읽고자 하는 텍스트의 인코딩 방식으로 읽어야한다.

- 예시 : 아스키 코드, EUC-KR, UTF-8, UTF-16

- 문자 집합 chracter set : 문자들의 모음으로 ASCII는 영어와 여러 특수 문자의 모음, 유니코드는 전세계 문자들의 모음

- 문자 집합과 문자열 인코딩의 차이 : 문자집합인 유니코드를 인코딩 한 방식이 UTF-8, UTF-16 등이므로 문자 집합(바이너리=수와 문자의 관계 모음)과 인코딩 방식(바이너리를 어떻게 읽을지에 대한 규칙)이란 점에서 다름.


ASCII

유니코드 

 

문자열 인코딩 종류

1. 아스키 코드 : 1바이트로 128개의 문자 표현

2. EUC-KR

 - 옛날에 한글과 영어만을 표현하기 위한 한국만의 인코딩 방식, 2바이트

3. UTF-8

  - 여러 언어를 모은 유니코드의 인코딩 방식 중 하나로 1바이트로 인코딩. 문자에 다라 1 ~ 6바이트까지 사용

  - 가장 많이 쓰는 방식, 아스키 코드와 호환, 윈도우, 자바, 임베디드 외 대부분 환경에서의 표준(+Json)

4. UTF-16

 - 2바이트로 인코딩 -> 유니코드 값이 커 2바이트로 부족하면 4바이트 사용. 

 - 자바와 윈도우에서 주로 사용

 


UTF-8 규칙 
- 1바이트만으로 충분한 경우(영어) : 0xxxxxxx(0으로 시작)
 ex) 영어 A는 유니코드 0x0041 -> 0x41 한바이트로 표현
- 2바이트가 필요한 경우 : (1) 110x xxxx  (2) 10xx xxxx
 ex) 파이는 유니코드 0x3C = 0000 0011(3) 1100 0000(C)
-> 위 규칙으로 표현하면 110/0 1111(0xCF), 1000 0000(0x80)
- 3 바이트가 필요한 경우 한글 : (1) 1110 xxxx (2, 3) 10xx xxxxex) '한'은 유니코드 0xD55C = 1101 0101(D5) 0101 1100(5C)
-> 1100/ 1101(0xED), 10/01 0101(0x95), 10/01 1100(0x9C)


유니코드를 UTF-8로 인코딩 예시
- 좌측에 A, 파이, 한의 유니코드 값에 따라 UTF-8로 3바이트까지 사용해서 인코딩한 예시 내용 정리

 

 

2. 시간, 식별자, 해시

 

컴퓨터 시간 종류

- 타임 스탬프 :  1970년 1월 1일 0시 0분 0초부터 1초 단위(.밀리초)로 증가해서 정수 형태로 나타내는 시간

- 단조 시간 : OS 시작햇을때 기준으로 시간

- 실제 시간 : 시간 서버로 얻어 동기화 한 시간

타임 스탬프 변환 예시

 

범용고유 식별자 UUID Universial Unique IDentifier

- 같은 세션, 같은 사용자를 넘어서 가장 크게 구분하기 위한 식별자

- 형태 : 16진수 32개(8-4-4-4-12)  4 x 32 = 128비트 = 16바이트 크기 -> xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

 

 

해시 함수

1. 특징 : 어떤 종류든 간에 입력된 값을 고정 길이 해시 값으로 변환, 동일한 입력 = 동일한 해시, 해시로 입력 복원 불가

2. 용도 

 - 암호 검증 : 서버에서 암호 대신 해시 저장 -> 입력한 비번의 해시가 저장된 해시와 같으면 ok(비번 저장필요 x)

 - 데이터 변조여부 검증 : 데이터 변조시 해시 값이 변경됨.

  ex) 파일 전송 시 파일과 해시값을 같이 보냄. 해시 함수에 전송된 파일을 넣어 얻은 해시값과, 전달받은 해시값이 다르면 파일 전송 오류/변조

3. 종류

 - MD 5 : 초기 해시 알고리즘, 적은 연산량, 충돌/동일한 해시값이 나오기 쉬움

 - SHA-1 Secured Hash Algorithm-1 : 200TB까지 입력 가능, 20바이트 해시 생성, md5보다 많은 연산량+적은 충돌가능성

 - SHA-2 : SHA-224/256/512 등 통틀어서 부르는 명칭. SHA-1보다 연산량 많으나 안전

 

 

 

 

 

 

3. 데이터 규격

 

JSON

- {}와 키, 값으로 구성된 데이터 구조, UTF-8 문자열 인코딩 사용, 주석 지원 x

- 장점 : 읽기 쉽다.

- 단점 : 텍스트로 이뤄저 크다, JSON 파일의 형태(버전)이 바뀐경우 전체(서버, 클라이언트 등) 바꿔줘야함. 

 

YAML Yet Another Markup Language

- json과 비슷 + UTF-8/16 + 앵커, 별칭 이용

 

XML eXtensible Markup Language

- 여러 목적의 마크업 언어를 만들기 위한 마크업 언어 = 다양한 형태의 데이터 구조를 만드는 마크업 언어

- 일찍만들어짐, 많이 씀,  MS 워드 파일 등은 XML 파일 형태, 다양한 인코딩 지원, 읽기 쉬우나 많은 데이터 필요


xml

json

yaml

 

 

프로토콜 버퍼 Protoco Buffer = Protobuf)

- 구조화된 데이터(구조를 가진 텍스트)를 데이터 직렬화(바이너리)하는 구글이만든 오프소스

- 장단점 : 크기가 줄어 통신, 저장 시 사용 <-> 알아보기 힘듬 = 유지보수 힘들다

- 프로토콜 버퍼 사용법

 1. 스키마 파일(데이터 규격) 정의(.proto) : 직렬화(바이너리) 형태 정의
 2. 컴파일러(protoc)로 컴파일 : 인터페이스 코드 생성
 3. 인터페이스 코드 임포트
 4. 인코더 - 디코드

 


protobuf 사용법


protobuf 스키마 파일, 데이터 예시

 

base64

- 바이너리를 아스키 문자열로 대치한 인코딩

- 방식 : 바이너리를 24비트(3바이트)로 나눔(바이트가 남을때 패딩 '='(=0) 추가)-> 6비트로나눔 -> 0 ~63개(64개)문자 표현

- 장단점 : 바이너리를 문자열로 취급(=이미지, 음성 등 바이너리를 문자로 표현) <-> 데이터가 길어진다


base64 인코딩 테이블

text를 base64인코딩

 

 

 

 

4. 프로세서 관련

 

가상 메모리

- 2차 메모리(HDD, SSD)를 이용하여 각 프로세서가 4GB의 가상의 메모리 공간(주소)을 가지고 프로그램 실행.

- 각 가상메모리는 코드, 데이터(전역 변수), 힙(동적 할당), 스택(함수 호출, 지역 변수) 영역을 가짐.

- 장점  ; 모든 프로세스는 4GB나 되는 공간을 갖는다, 프로세스 간에 침범 방지

- 원리

 1. 가상 주소가 크므로 주소를 4kb의 고정 크기 덩어리 페이지로 분할

 2. 메모리 관리 장치 MMU 혹은 페이지 테이블로 가상 주소를 물리 주소로 변환

  => MMU나 페이지 테이블이 알아서 가상 주소를 물리 주소려 변환해주니 신경 쓸필요 없음

 

명령어 파이프라인

- 비 파이프라인 : 명령어 사이클(읽기, 해석, 실행, 기록)이 다 끝나면 다음 명령 처리

- 명령어 파이프라인 : 작업이 넘어가면 새로운 명령어를 받아 수행하는 방식. 아래 그림의 경우 최대 4명령이 동시 실행

- 슈퍼스칼라 파이프라인 구조 : 파이프라인을 여러개 두어 여러 명령어를 동시에 처리

- 비순차 프로세서 파이프라인 :  파이프라인에서 순차적으로 처리하면 대기해야하는 경우 발생, 스케줄링을 추가하여 비순차적으로 처리하여 고속화. 명령어 인출 -> 해독 -> 리네임(가짜 의존성 제거를 위한 레지스터 이름 변경) -> 이슈(할당) -> 스케쥴링 -> 실행                              

비파이프라인 A와 파이프라인 B

비파이프라인 A, 파이프라인 B

슈퍼스칼라 파이프라인

비순차 프로세서 파이프라인

                                                                                                                                                                                                                             

하이퍼 스레딩

- 하나의 HW가 두개의 컨텍스트 유지(프로세서가 2개처럼 보인다)

 

 

병렬 컴퓨터

- 프로세서를 모아, 협력해서 계산

- 구조

 1. SISD(single instruction single data) : 하나의 명령어가 하나의 데이터만 갖고 계산, 스칼라/슈퍼스칼라 프로세서

 2. SIMD(single instruction multiple data) : 하나의 명령어가 여러 데이터갖고 계산(벡터 연산), MMX, SSE

 3. MISD(multiple instruction single data) : 데이터 하나 명령어 여러개

 4. MIMD(multiple instruction multiple data) ; 여러 명령어로 여러 데이터 처리, 일반적인 멀티 프로세서 -공유/분산 메모리


병렬 컴퓨터 구조 비교

병렬 컴퓨터 구조 형태

- GPU : 병렬 프로세서, 비순차 처리가 많지만 ALU가 적은 CPU와 달리 훨씬 많은 ALU와 큰 메모리 대역폭 가짐

 

 

와 난드투 테트리스를 대충 정리하고나서

주말부터 어떤 책 읽어볼까 고르고 이책 저책 읽기 시작했는데,

제대로 봤다고 하긴 힘들지만 생각보다 책을 꽤 많이 봤다.

 

이번에 본 책은 유닉스의 탄생인데,

벨 연구소에서 유닉스와 관련 프로그램들이 어떻게 누가 만들어졌는지에 대한 이야기들이 나온다.

 

내가 이 책을 왜 읽었더라..

예전에 집근처 도서관에서 조금 읽다가 만 적이 있어서인지

리눅스를 목표로 한번에 어려운 시스템 프로그래밍에 뛰어들기보다는 이책 저책을 읽으면서

배경지식을 쌓는 중이기도 해서 눈에 들어왔던거 같다.

 

IT 쪽을 공부하면서 한두번씩은 들어봤을만한 유명한 사람들이 많이 나오기도하고

1960년대쯤부터 은근히 유쾌하기도한 벨 연구소 사람들 이야기들

리눅스에서 쓰이는 grep, awk 같은 명령어들의 개발 비화라던가

make, yacc, lex 같은 컴파일 관련 도구들에 대한 이야기 등

 

CS의 토대가 되는 것들이 어쩌다가 만들어졌는지 알수 있었다.

이 책의 구입 페이지에 들어가보면

예전에 okky에서 종종 만화를 올리시던 빈꿈님의 만화가 있는데,

처음 이 책을 봤을때는 빈꿈님이 쓰신 책인줄 알았다.

근데 아니더라

 

이 책은 제목 그대로 cpu 아키텍처 설계에 관한 이야기라

처음에는 컴퓨터 구조나 기본적인 명령어 처리 과정 설명으로 시작하다가

뒤에는 파이프 라인이니 병렬프로그래밍이니 하는 보통의 응용 어플리케이션 개발자들이 잘 모르는 내용이 많다.

 

하지만 뒤에 나오는 SIMD니 CUDA니 하는 것들을 어디서 봤는지는 잘 기억나지는 않지만

종종 마주친적이 있어서 그런게 있는갑다 하고 넘어간 적이 있어서 한번 뭔지는 알아야겠다 싶기도 했었고,

 

요새 임베디드 내용을 보면서 어셈블리니 프로세서마다 어떻게 컴파일하니 바이너리가 어떻게 나오니 하는걸 다루니까 결국에는 이 책을 한번 봐야겠다 싶었다.

 

이 책 또한 엉터리 번역서와는 다르게 컴공 학부 수준 학생들이 이해할 수 있을만큼 친절하게 설명해준다.

주제가 보통 컴공하는 사람들에게는 관심가지 않고, 친숙하지 않은 것들이라 그렇지 .....

 

그래도 OS, 프로세스, 명령어 등 컴퓨터 구조 관련 개념들을 복습하고, 모르는 부분을 간단하게나마 파악하는 좋은 책이었다.

이 책은 그동안 it 공부하면서 아무 곳에서도 알려주지 않지만

은근히 자주 나와서 블로그에서 찾아보게 되는 내용들을 모은 책이다.

 

저자가 웹쪽에서 일하시는 분이셔서 인코딩, 포멧, 웹관련 내용들 위주긴 하지만

유니코드와 utf-8/16 같은 것의 차이가 뭔지,

(은근히)자주 보는 형식이지만 찾아봐도 의미를 시원하게 이해할수 없는 xml, 프로토버프 같은 내용을

 

이해하기 좋게 간단한 예시와 만들거나 출력하면서 알려주는 친절한 책이다.

 

이 책의 각 주제들은 조금만 구글링해도 쉽게 찾아볼수 있는 것들이지만

그렇게 찾은 정리된 글이라 해도 위키피디아처럼 딱딱하다고 해야할까, 60%도 이해했다고 하기 힘들만큼 막연한 경우가 많았다.

 

하지만 이 책 같은 경우에는 흔히 있는 정리 글이나 안내 글보다는 독자들이 이해하는데 많이 신경을 썻다는걸 느낄수가 있었다.

 

프로그래밍 문법과 프레임워크 사용법만 배운 웹개발자들에게는 인코딩, 포멧, 웹서버 통신 등 학원이나 학교에서는 잘 알려주지는 않지만 꼭 필요한 지식들을 안내해주니 추천하며,

 

다른 분야라 해도 인코딩이나 종종 만나는 포멧들에 대해서 잘 설명되어있으니 한번 보면 좋은책이라 생각한다.

리눅스 운영체제 주요 개념

- 파일 시스템, 프로세스, 스트림

 

운영체제 개요

- 역활 : 하드웨어, SW 자원 관리, 시스템 콜 등

- 운영체제 패키지 : 셸, util-linx, procps, gnu coreutils/grep/diffutils/libc, libs, 개발 환경, X window

- 커널 : 리눅스 핵심, vmlinuz 파일 /boot 

- 디바이스 드라이버 : CPU, ram, 그래픽/네트워크 어뎁터 등 다양한 종류의 특정 디바이스을 제어하기 위한 SW

- 시스템콜 : 응용 어플이 간접적으로 하드웨어 제어를 하도록 제공하는 API로 커널을 불름(call), open, read, write, fork 등

 

파일 시스템

- 파일 종류 : 일반 파일, 디렉터리, 심볼릭 링크(바로가기), 디바이스 파일(디바이스를 파일로 인식), 파이프, 소켓

- 메타 정보 : 파일이 가진 데이터 외 정보 - 종류, 권한, 크기 등

- 파일 시스템 종류 : ext4(리눅스 파일시스템), nfs(네트워크 파일 시스템)

 

프로세스

- 실행중인 프로그램의 동작 단위 

- 프로세스 ID : 프로세스 구분하기 위한 식별자, 프로그램 한개에 여러 프로세스 존재 가능

- 시그널 : 프로세스 제어에 사용되는 신호

 

스트림

- 바이트 스트림 : 바이트들이 흘러가는 통로, 파일을 통해 이뤄짐

- 종류 : file, 스트림 커널

- 용도 : 파일 입출력, 파이프(프로세스 사이의 스트림), 디바이스 스트림(장치 입출력), 네트워크통신->프로세스간 통신IPC

 

 

다중 사용자 시스템

- 윈도우, 리눅스 <-> 안드로이드, IOS(단일 사용자 시스템)

- 다중 사용자 시스템 사용 이유 : 다중 사용자를 고려해서 설계하는 것이 더 안전하므로

 

단말 개요와 종류

- 구성 요소의 가장 끝단 terminal에 위치 한 장치

- 텔레타이프 : 초기 디스플레이가 없는 텔레타이프(teletype)를 이용. 여기서 tty라는 이름이 사용

- thumb terminal : 문자만 표기가능한 디스플레이가 추가됨.

- X termnial : 요즘 모니터(비트맵 디스플레이)를 탑제한 단말. X 윈도우 시스템 클라이언트로 화면 출력

 * X 윈도우 시스템(X11) : 서버-클라이언트 모델의 GUI. 클라이언트가 x 서버에 접속해서 요청하면 결과를 화면에띄워줌

- 단말 에뮬레이터 : 이전에 하드웨어로 만든 단말을 SW로 구현한 것으로 우분투 터미널, iTerm 같은것들

- 단말의 변화 흐름 : 텔레타이프 -> 문자 터미널 -> 비트맵 터미널 -> 단말 에뮬레이터

- 가상 콘솔(=터미널=단말) : 리눅스서 여러 가상콘솔동작 X 윈도우가 tty7 점유, [ctrl] + [alt] + [fn키]로 다른 tty로 변경 가능


텔레 타이프 : 출력이 종이에 적혀 나온다

덤 터미널 thumb terminal : 문자만 출력, VT100

NCD-X terminal : 비트맵 디스플레이를 이용

단말 에뮬레이터 : SW로 가상화한 터미널, 위는 iterm

파일, 디바이스, 단말

- 리눅스에선 모든 디바이스, 단말이 파일로 표현하여, 스트림으로 입출력 가능

- /dev 경로에 /dev/tty0, /dev/tty1로 존재.

+ 셸 : 명령어 해석기

 

 

스트림 시스템 콜

- 파일 디스트립터 : 커널이 만들어준 스트림 번호로 입출력시 사용

#include <unistd.h>

- ssize_t read(int fd, void *buf, size_t bufsize); : fd 읽어서 buf에 저장, ssize_t는 부호있는 정수(여러 플랫폼에 따라서 정수 크기가 다를 수 있음), size_t 부호 없는 정수, 읽은 크기 반환, 0은 끝 도달, -1은 에러, read()는 \0이 끝에 있는걸 전재 x

#include <unistd.h>

- ssize_t write(int fd, const *buf, size_t bufsize) : bufsize 만큼 읽어 fd에 씀.

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

- int open(const char *path, int flags, (mode_t mode)) : mode는 create 플래그시 권한 지정

#include <unistd.h>

- int close(int fd) : fd 스트림 해제

- off_t lseek(int fd, off_t offset, int whence) : 파일 디스크립터 파일 오프셋 이동, whence 플래그(SEEK_SET : 파일 처음 기준, SEEK_CUR : 현재 기준, SEEK_END 끝 기준), lseek의 l은 Long 타입으로 seek()도 존재

- int dup(int oldfd), int dup2(int oldfd, int newfd) : oldfd를 복제

#include <sys/ioctl.h>

- int ioctl(int fd, unsigned long request, ... ) : 디바이스 처리 관련 시스템 콜 ex : dvd 여닫기, 프린터 구동, 단말 통신속도 조절 등, request는 작업을 의미하는 상수, ...는 가변 인자로 여러개 지정 가능

- int fcntl(int fd, int cmd, ...) : 파일 디스크립터 관련 작업 위주의 시스템 콜(ioctl과 구분) 

 

 

 

cat 명령어 구현하기

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

static void do_cat(const char *path);
static void die(const char *s);

int main(int argc, char *argv[])
{
    int i;
    if (argc < 2)
    {
        fprintf(stderr, "%s : file name not given\n", argv[0]);
        exit(1);
    }
    for (i=1; i< argc; i++)
    {
        do_cat(argv[i]);
    }
    exit(0);
}

#define BUFFER_SIZE 2048

static void do_cat(const char *path)
{
    int fd;
    unsigned char buf[BUFFER_SIZE];
    int n;

    fd = open(path, O_RDONLY);
    if (fd < 0) die(path);
    for (;;)
    {
        n = read(fd, buf, sizeof buf);
        if (n < 0) die(path);
        if (n == 0) break;
        //STDOUT_FILENO : standard output stream = print buf data 
        //STDOUT_FILENO(is linux file descrypter in unistd.h)
        //stdout is c standard io stream(in stdio.h, similar with STDOUT_FILENO)
        if (write(STDOUT_FILENO, buf, n) < 0) die(path);
    }

    if (close(fd) <0) die(path);
}

static void die(const char *s)
{
    perror(s);
    exit(1);
}

* exit(0) : 정상 종료, exit(1) : 오류 종료, return과의 차이 : exit 즉시 프로세스 종료, return은 뒷 내용 마친 후 종료

 

cat 명령어 실행 결과

데이터 교환 방식

- 패킷 교환 방식 : 전송하고자 하는 데이터를 패킷로 분할하여 디지털 신호를 목적지로 전달하는 방식

- 회선 교환 방식 : 아날로그 신호(전화같은거)를 중간에 교환기에서 회선을 선택함으로써 원하는 목적지로 보내는 방식

 

 

패킷 교환 방식
회선 교환 방식

 

 

계층 모델

- 네트워크 내 장비와 프로그램의 역활을 계층 별로 분류한 모델

- OSI 7계층 참조 모델 :  복잡해서 잘 안쓰고 네트워크 구조 설명하는데 이용

- TCP/IP 모델 : 네트워크를 간략하게 4단계로 나타낸 모델로 대표적인 프로토콜이 TCP, IP이다보니 이런 이름 가짐.

 

 

프로토콜

- 어떻게 동작할지 서로 다른 장치/소프트웨어 간에 혼선을 방지하도록 정한 약속

 

TCP/IP 모델

- 어플리 케이션 계층 : 사용자와 만남, HTTP, SMTP, POP 등

- 트랜스 포트 계층 : 데이터 전송 방식과 포트(전달할 프로세스) 결정, TCP - 송신 후 잘 송신되었는지 확인하는 방식으로 조금 늦어지더라도 확실한 전송이 필요한 경우 (파일 전송), UDP - 잘 도착했는지 확인 없이 송신하며 패킷이 다소 손실되어도 괜찬은 경우 사용(전화) 

- 인터넷 계층 : 경로, 통신 제어 역활, IP - 송수신지 정의, ICMP 

- 네트워크 인터페이스 계층 : 데이터를 어떻게 보낼지 무선인지 유선인지 광케이블인지 같은것들을 결정, MAC, ARP, Ehternet(유선), IEEE 802.11(무선)

 

 

어플리케이션 계층

- 사용자에게 서비스를 제공하는 계층

- 대표적으로 HTTP, SMTP, POP, FTP 등

HTTP 프로토콜

트랜스포트 계층

- 어플리케이션 계층으로 전달받은 데이터를 세그먼트로 만들어 인터넷 계층으로, 인터넷 계층에서 전달받은 패킷을 풀어서 어플리케이션 계층으로 전달.  

- TCP 인지 UDP 인지에 따라 전송 방식을 결정하며, 포트 번호로 어느 어플리케이션에 전달할지 판단.

- 데이터에 TCP 헤더가 붙으면 세그먼트, UDP 헤더가 붙으면 UDP 메시지가 된다.

- TCP는 연결을 하기 위해 3방향 핸드셰이크 과정을 진행하며, 최대 세그먼트 크기에 맞춰 송수신

- 한번에 받을 수 있는 데이터 크기(버퍼 크기)에 따라서 빠른 전송을 위해 한번에 보내나 버퍼가 비워지지 않으면 전송멈춤

 

 

 

 

 

 

인터넷 계층

- IP 주소에 따라서 데이터를 전송할 목적지를 정하고, 경로를 설정하며(라우팅) 제어하는 계층

- IP : 송 수신지가 인터넷 서비스 제공자로부터 받은 주소, 생존시간인 TTL을 가짐, IP 해더가 붙은것을 패킷이라 하며 패킷은 다른 패킷들과  섞인채 전달되나 IP 패킷 헤더의 같은 데이터인지를 판단하는 식별자, 원래 데이터에서 위치를 나타내는 프래그먼트 오프셋 등을 이용해서 수신지에서 복원된다.

 

- 라우팅 : 네트워크를 구분하는 라우터들을 거쳐 목적지까지의 최적의 경로를 찾는 과정으로 라우팅 테이블 이용. 정적 라우팅(직접 라우팅 테이블 작성), 동적 라우팅(라우팅 프로콜로 동적으로 라우팅 테이블 작성)

 

- ICMP internet control message protocol : 데이터 전송 중 문제 발생 시 상황 안내를 위한 프로토콜

- NAT network address port translation : 사설 IP를 할당 받은 단말을 공인 IP로 외부와 통신할수 있게 해줌

- DNS domain name service : 도메인 네임 서버에서 도메인 네임을 받으면 해당 도메인의 IP 주소 전달

- DHCP dynamic host configuration protocol : dhcp 서버에서 자동적으로 새 호스트가 연결되면 ip를 할당

 

 

네트워크 인터페이스 계층

- OSI 7계층 참조 모델의 데이터 링크 계층(소프트웨어 관련 프로토콜)과 물리 계층(하드웨어 관련 프로토콜)로 구성 된 계층으로 이더넷 혹은 무선 LAN에 따라 송 수신지 등의 MAC 어드레스를 추가한 이더넷/무선 LAN 프레임을 전송

이더넷 프레임
무선랜 프레임

- 하드웨어 관련 프로토콜 : 전화 회선을 이용한 통신 PPP, 동축 케이블을 이용하는 EDP, 무선 LAN IEEE 802.11

- 소프트웨어 관련 프로토콜 : ip -> mac addr을 구하는 ARP, 1대 1로 연결하는 PPP 등

- 이더넷 : 케이블로 호스트 연결시 따르는 규격, 통신 속도/접속 방식에 따라구분

- 허브 : 네트워크의 중심이란 의미로 수신지에 전달하는 리피터, 통신 충돌을 방지하기 위해 각 호스트 mac addr을 저장했다가 목적지에만 전달하는 L2 스위치(데이터 링크 계층 역활도 수행), LAN을 가상의 VLAN으로 분할 가능한 L3 스위치(인터넷 계층 기능 수행) 등이 있다.



무선랜

l2 스위치

l3 스위치

 

키를 이용한 암호화

- 키를 이용해서 데이터를 암호화하여 통신하는 방식

- 공유 키 암호화 : 공유하는 키로 암 복호화 하는 방식, 공유키가 유출 시 위험

- 공개 키 암호화 : 공개키, 개인키 두 개를 이용. 공개 키로 암호화, 개인키를 가진 사람만 복호화 하여 사용

 

 

공개키 방식 문제점과 보완 방법

- 공격자가 속이고 공개키를 전달할 수 있음. -> 전자 인증서, 전자 서명, 공개키 개인키를 이용한 공개 키 기반구조 이용

- 전자 인증서 : 전달 받은 공개키가 공격자의 것이 아닌 것을 인증함(신분 사칭 방지).

- 전자 서명 : 전달 받은 데이터가 위변조 되었는지 확인하기 위한 방식. 개인 키로 전달 데이터 해시 값을 암호화 하여 데이터와 같이 전달 해, 전달 받은 데이터의 해시값과 공개 키로 복호화 한 해시값이 일치하는지 여부로 판단.

* 공개 키로 암호화 한것은 개인 키로 복호화, 개인 키로 암호화 한 것은 공개 키로 복호화 가능

 

공개 키 기반 구조

 

 

 

SSL

- 인증 기관에서 받은 서버 인증서로 올바른 서버임을 인증, 통신 암호화하는 방식으로 HTTP에 적용한 것이 HTTPS

 

대충 난드투 테트리스를 끝내고

잠시 쉬면서 다음에 뭘 해야할까 고민하다가 

 

이제 컴퓨터 구조에 대해서 약간은 감 잡았다! 싶어서

한 책만 가지고 2주동안 해맸으니 이번에는 다양한 책을 빠르게 독파해보자 싶어서 

 

이것 저것 찾아보면서

이만우 님의 임베디드 os 개발 프로젝트인가? 하는 책을 보기로 시작했다.

 

확실해 메모리 맵드 아이오니 스택, 가상 머신이 뭔지 같은거를 난드 투 태트리스하면서

이게 뭔지 조금은 더 감잡고 나서 보니까 전보다는 이해가 더 잘되긴 했었다.

 

하지만 이 책을 보는데 여전히 부족하다고 느꼈던데

책 자체는 아주 어렵지는 않았고, 저자 분이 친절하게 옆에 앉아서 설명해주는 식이여서

그냥 책 내용데로 따라간다고 하면 따라가지 못할것도 없었다.

 

그런데 항상 공부하면서 생각하는 거지만 나한테 맞는 순서가 중요했구나를 느꼇었는데,

이 책이 내가 어거지로 따라갈 수는 있어도

 

리눅스 시스템이나 빌드 도구, c 어셈블리 같은 내용들에 대해서 이해 없이 보는건

뭔가 순서가 안맞다는 느낌이 들었었다.

 

그래서 다른 책을 찾아 보게 되었는데

리눅스 책을 볼까 아니면 컴퓨터 안에서 데이터가 어떻게 돌아가는지 알아 볼수 있도록 네트워크 관력 책을 할까 고민하다가 두 분야의 책을 몇개 골라서 완독 까지는 아니더라도 앞의 2-3장 정도는 읽어 보면서 가장 쉽게 읽힐만한 네트워크 분야 책인 '명쾌한 설명과 풍부한 그림으로 배우는 TCP/IP 쉽게, 더 쉽게'를 우선 완독했다.

 

너무 고맙게도 이런 책들이 서울시 전자 도서관에서 제공하고 있다 보니까 바로 빌려서 봤는데, 서울 도서관 앱이 교보문고나 yes24 전자책 앱보다는 훨씬 별로긴 했다.

 

 

아무튼 이 책을 보게 된 경위를 쭉 적어버렸는데,]

책 내용은 정말 네트워크 처음 공부하는 사람들을 위해서 진짜 완전 친절하게 정리되어있다.

그래도 아무런 배경없이 보는건 힘들겠지만 다른 네트워크 서적 중에서 이렇게 까지

귀여운 그림이랑 독자들을 혼란스럽게 하지 않게 어렵지도 않으면서 간결한 깔끔한 설명으로

 

각 계층이 뭐하는거다, 보안에서 어떤게 쓰인다는걸 정리하기가 쉽게 되어있었다.

이 책 만큼 독자가 이해하기 좋게 그림으로 네트워크의 동작을 그려낸 책을 본적이 없었다.

물론 네트워크나 보안 분야에서 일하는데는 책 내용이 많이 부족하다고 생각할 수 있긴하지만

 

불친절한 네트워크 책을 처음 보고 몇일을 머리 싸메다가 손을 놓는 것 보다는

네트워크 분야 전체적인 틀을 훑어보고, 다음 단계로 넘어가는데는 크게 도움되는 책이라 생각된다.

잠깐 쉬고나서 10장 컴파일러를 시작한다는게

너무 놀다가 11시가 되버렸다.

오늘 좀 늦게 자려고 중간에 잠깐 자기도 했지만 너무 늦어버리긴 했지만

일단 하는데까지는 해봐야지

 

 

이번 글 제목을 적는데서 부터 조금 불편한게

10장이 신텍스 에날리시스인데 아날리시스랑 파서랑의 차이가 뭔가 싶었다.

 

지난 번에 vm 부분이었었나 어셈블리어였엇나. 아 어셈블리어 파트구나 파서를 구현하라고 하는데

파서의 뜻이 그냥 파서라고 봐왓다보니까 그냥 파서라 생각하고 대충 넘겨왔지만

잠깐 찾아보니 분석하다라는 의미였었다.

 

이번 장 제목이 신텍스 에날리시스인데, 

아날리시스도 결국에는 분석하다는 의미였지 않았던가.

 

아날리시스와 파서가 한국어로는 동일한거 같아도 영어에서의 의미는 조금 다를거란 생각이 들었다.

 

 

 

 영영 사전을 보니 아날라이즈가 이해하도록 조사 연구하는 식으로 분석한다면, 파스는 문장을 분할한다는 의미더라. 결국에 이번 장 제목인 신텍스 에날리시스는 문법 분석이고, (파싱의 의미) 파싱은 주어진 문법에 따라서 토큰들을 분석하여 나누는 것/분류하는 것 정도로 생각하면 되는거같다. 다른 개발 블로그에서도 파싱, 파서를 그런식으로 설명하고.

 

 

 

신텍스/문법 분석기 개요

 자. 이제 시작하면 컴파일이야 고급 언어를 중간 언어로 생성하는 걸 말하고,  크게 문법 분석과 코드 생성 작업으로 구분된다. 이번 장에서는 문법 분석을 다루며 문법 분석기는 토크나이저와 파서, 그러니까 형태소 분리기?와 토큰을 문법에 따라 분류하고 xml로 생성하는 파서로 구성된다.

 

 그런데 컴파일러 만드는법을 배우는게 왜 필요할까? 저자분은 컴파일러를 만들면서 프로그래밍 언어를 분석하는데 쓰는 규칙과 문법/방식들이 컴퓨터 그래픽스나, 통신, 네트워크, 머신러닝 등 다양한 분야에서도 사용된다고 한다.  그런 분야 중 하나로 자연어 처리, 언어 번역 같은 곳에서도 텍스트를 분석하고 의미를 가진 단어들을 합성을 하기 때문이란다. 

 

 확실히 전에 자연어 처리 잠깐 볼때 토크나이저가지고 단어 나누고 어째저쨰하면 자연어 처리 모델이 문장을 만드는걸 보긴 했었다. 이런데는 아니더라도 데이터, 텍스트같은걸 다룰대 비슷한 내용들을 마주치긴한다.

 

 아까 잭 컴파일러가 문법 분서기와 코드 생성기로 구분할 수 있다고 했는데, 문법 분석기는 토크나이징/형태소화와 파싱/형태소 구조화이 두가지 작업으로 나뉜다고 한다. 문법 분석기의 출력으로는 XML 파일인데 이 XML로 작업이 잘 됬는지를 쉽게 확인할수 있고, 완전한 코드를 생성하는데 사용할 수가 있다.

 

 여기서는 문법 분석기가 어떻게 사람처럼 프로그램 구조가 어떤지, 문법이 틀렸는지, 어디서 클래스/메소드가 시작하고 끝나는지 등을 분석할수 있도록 만드는지를 다룰건데, 이것들을 하려면 가장 먼저 프로그램을 토큰화(형태소화)를 하는것에서 부터 시작한다.

 

 이 신텍스/문법 분석기에서 사용되는 개념들로 어휘 분석 lexical analysis, 문맥에 얽히지 않은 문법 context free grammars, 파스 트리 parse tree, 재귀 하강 파싱 알고리즘 recursive descent parsing 등을 살펴보자.

 

 

 

어휘 분석 lexical analysis

 모든 프로그래밍 언어는 각 언어마다 고유의 토큰 종류들을 가지고 있다. JACK 언어의 경우 토큰들을 5가지 키워드, 심볼, 정수 상수, 문자열 상수, 식별자로 분류한다. 토큰화기/형태소화기는 주어진 프로그램을 형태소/의미를 가진 최소의 낱말 단위로 분할하여 우측의 xml 형태의 파일로 만들어낸다.

 

 

 

 문법

 문법 컴파일에서 당 연하게 중요해 보이는데, 프로그래밍 언어의 문법은 토크나이저로 분리한 토큰들의 흐름/모음을 보고 이게 어떤 종류인지 구문인지, 표현식인지, 구문이면 if구문인지 while구문인지, let구문인지, 표현식이라면 용어 하나만 있는지 연산자와 따른 용어가 또 있는지 등으로 주어진 토큰들의 종류를 판단하는데 사용한다. 또, 토큰 뭉치가 그 종류의 구문/표현식의 문법을 따르는지 보고 올바른 문장인지 아닌지를 확인하는데 쓸수 있다. 

 

 문법 왼편과 오른편으로 나눠보자 : 저자 분은 문법을 메타 언어라고 하는데, 언어가 어떻게 되어있는지 표현하는 언어라고 한다. 이 경우에는 JACK이란 프로그래밍 언어가 어떤 형식과 규칙을 가지고 있는지를 영어로 표현한게 문법이 되는거같다. 컴파일 이론에서 언어, 메타언어, 문법, 추론 등의 내용이 있지만 여기서는 단순하게 왼편 오른쪽편만 나눠서 보자.

 

 문법 규칙 왼편의 예시 : 위 그림의 왼편에는 규칙의 이름으로 실제로 프로그래밍 언어에 쓰이는건 아니지만 규칙끼리 구분하고, letStatement: 'let' varName '=' expression';' 처럼 letStatement라는 규칙 안에 varName이라는 규칙과 expression이라는 규칙이 들어가는데 이런식으로 다른 규칙 내용안에 포함되기도 한다.

 

 문법 규칙 우측의 형태 : 규칙의 오른쪽 편에는 이 규칙이 어떻게 생겻는지 언어적으로 패턴이 나와있다. 이 언어 패턴은 3가지 블록 터미널, 비터미널 nonterminal, 수식어 qualifier로 구성된다. 여기서 말하는 터미널은 토큰(형태소)이고, 비터미널은 다른 규칙의 이름, 수식어 qualifier는 |, *, ?, (, ) 이 5가지 심볼로 구성된다.

 

 토큰을 터미널이라고 하는건 터미널이 끝단이란 의미니까 의미를 가진 단어의 최소 단위/끝이므로 이걸 토큰을 터미널이라 하고, 터미널 외에 단어는 비터미널이라고 하는거같다. 위 문법 내용에서는 터미널 요소인 'if'처럼 볼드체에다가 따옴표가 붙어있고, expression같은 비터미널 요소에는 이탤릭체를, *나 ?처럼 수식어는 그냥 일반 폰트로 나타낸다.

 

 문법 규칙 우측의 예시 : 예시로 하나를 보면 언어 규칙 ifStatement: 'if' '(' expression ')' '{' statements '}' 라고 되어있는데 이 의미는 모든 if문은 'if'라는 토큰으로 시작하고, 그다음에는 '('라는 토큰이, 그다음에는 expression이라는 비터미널이, 그다음에는 토큰), 또 토큰 '{', 뒤엔 비터미널 statements, 마지막으로 '}'토큰이 붙어 있다면 이게 유효한 ifStatement가 된다!

 

 수식어의 역활 : 수식어 *는 0, 1 혹은 그 이상을 의미하는데 statements: statement*는 구문이 여러개 올수 있다는 의미가 된다. ?는 0, 1개만 온다는 의미로 expression: term (op term)? 이란 규칙에서 (op term)이 없거나 1개만 추가 될수 있다고 보면 될거같다. 수식어 (, )는 그룹핑으로 바로 윗줄의 (op term)?의 경우 op가 먼저 오고 term이 붙어서 한덩어리가 된다는 의미다.

 

 

 

 

파스 트리 : 토큰들을 문법에 따라 분류한 트리

 앞에서 프로그램을 토큰화기를 통해 토큰들과 각 토큰들의 종류를 xml로 정리했었다. 또, JACK의 문법 규칙들이 어떤 형태로 되어있는지도 봤다. 문법과 토큰, 토큰의 종류를 봤으니 이제 토큰들을 문법에 따라서 분류를 파스 트리를 만들 차례이다.

 

 위 문법을 보고 코딩하던걸 생각해보면 문법을 재귀적으로 따르던걸 알수 있다.

void main()
{
    int num = 0;
    while (1)
    {
       if(num == 10)
       {
           printf("out");
           break;
       }
       num = num + 1;
    }
}

 재귀적으로 반복되는 문법 규칙들 : 이 코드는 Jack은 아니지만 간단한 c언어를 작성했는데, jack 언어의 문법 규칙을 참고해서 보면 while (1)은 while구문 바로 뒤에 수식어 ()와 수식어 안에는 1이라는 expression이 존재한다. 근데 이 표현식은 용어 term이 1개인 표현식이며, 이 용어는 변수 용어가 아닌 상수 용어이다.

 

 수식어 )뒤에는 수식어 {}가 오며 이 안에는 구문들 statements이 들어가 있는데, 이 구문들은 if 구문과 num = num +1이니 let 구문 2개로 구성되어 있다고 볼수 있을거 같다. 또 이 구문안에는 또 다른 구문이 들어가 있는 식으로 문법 규칙들이 재귀적으로 계속 반복되고 있다.

 

 

 파스 트리 : 이와 같은 식으로 문법 규칙을 기준으로 구문들을 구문으로 구문의 종류에 따라서 또 분리하고, 들어가서 또 분리하기를 반복하며 모든 토큰들을 분류해낸 결과물이 파싱 트리이다. 위 그림은 while 구문을 규칙에 따라서 분류해 만든 결과물이다.

 

 파스 트리 XML : 파스 트리를 만들면 이걸 어떻게 표현할까? nand2tetris에서는 XML형태 파일로 만드는데, 바로 위 while구문의 파스트리를 XML 파일로 만들면 이런 형태가 된다. 

 

 

 

파서와 재귀 하강을 통한 파싱 처리

  앞에서 문법 규칙에 따라서 파스 트리를 만들었는데, 파서는 프로그램을 입력받아서 파스 트리를 xml로 만들어내는 프로그램이 된다. 여기서 파스 트리를 만들기 위해 사용하는 방법은 재귀 하강 파싱이다. 

 

 파서의 구현 : 파스 트리는 문법 규칙에 따라서 재귀적으로 구문을 내려가며, 끝에는 토큰단위로 분해해내었는데, 이 동작을 재귀적으로 호출해서 처리 할 수 있도록 각 문법 규칙 때마다 컴파일 하도록 구문 종류별 컴파일 함수 compileStatements(), compileIfstatement() 들을 구현해내면 된다.

 compileWhile() 슈도 코드 : 위 그림은 while 구문의 컴파일 구현 슈도 코드로 어떻게 while 구문을 파싱 트리로서 xml 출력을 낼지를 보여주는데, 가장 먼저 <whileStatement>를 출력한 후, process("while")을 통해 이 문자열을 XML 토큰으로 출력한다.

 그 다음 수식어 (를 처리하고, while 구문 규칙상 수식어 ( 뒤에는 표현식 Expression이 오므로 compileExpression()를 호출한다.

 이와 같은 식으로 내부에 존재하는 수식어를 출력하고, 구문들을 컴파일 하면서 필요한 경우 재귀적으로 더 내려가면서 반복하게 되고 모든 재귀 연산을 마치고 나머지 수식어 출력도 마치면 </whileStatement>로 이 while 구문의 xml 부분 출력을 마무리한다.

 

 이쯤 되면 대강 파싱 XML 만드는 과정이 감이 잡힐탠대 PPT 내용을 일일히 캡처해서 while 구문을 xml로 만드는 과정을 gif로 찍어왔다..

 

 방금 본 문법 규칙들 외에도 실제 객체 지향 언어의 문법 규칙은 더 복잡한데 jack에서 클래스 레벨의 static이나 인스턴스 레벨의 field 변수의 경우 이런 규칙을 쓴다.

 

 LL 문법 (var, let, while 토큰의 역활) : 이런식으로 재귀를 이용하여 쉽게 파싱 동작을 수행할수 있게 만들었다. 이쯤되면 왜 jack에서 let이니 var이니 같은걸 쓰는걸 궁금했을텐데, 앞에다가 let이나 var 같은 토큰을 두면서 이게 무슨 동작을 하는건지 let 구문 규칙을 비교해야할지, var 구문 규칙이랑 비교해야할지 구분할 수 있게 해준다. 위 gif 예시에서도 while이라는 토큰이 맨 앞에 있어서 while 구문인걸 알고 compilewhile()을 호출해서 썻다.

 

 위 문법 표처럼 첫번째 토큰으로 어느 문법 규칙을 봐야할지 판단할수 있으면 LL(1), 이걸로는 부족해서 두번째 토큰도 봐야한다면 LL(2)를 이와 같은 방식을 LL 문법이라 하며 이 덕분에 백트래킹 없이도 효율적인 재귀 파싱 알고리즘을 구현해냈다.

 

 

 

 

JACK 문법 정리

 이제 앞서 본 문법 표기법에 따라서 JACK의 문법을 정리한 내용은 우측의 그림과 같다. 

 

 

 

첫번째 토큰만으로는 애매할 때

 위 그림처럼 foo라는 토큰 뒤에 (, . , [ 같은 심볼이 올때가 있다. 그러면 foo[인 경우, foo. 인 경우, foo(인 경우 등 경우에 따라서 다른 형태의 문법 규칙을 사용해서 컴파일을 해야하는데 첫째 토큰인 foo 만으로는 어느 컴파일 함수를 해야할지 판단할 수 없다. 그러므로 LL 문법에 따라서 2번째 토큰이나 필요한 경우 3번째 토큰 혹은 그 이상을 확인하는 식으로 판단하면 된다.

 

 

 

마무리

 

 모든 파트가 그렇듯이 구현하는 부분도 있지만 넘어가고, jack으로 짜여진 프로그램에서 vm 코드를 생성하기 위해 이번 장에서는 토큰화기와 파서로 구성된 신텍스 분석기로 파싱 트리 XML을 만드는 과정 전반을 살펴봤다.

 

 와 하루만에 챕터 2장 봤어. 지난 주에는 삽질도 제대로 하면서 구현까지 하느라 기본 1장에 이틀은 걸렸는데 진짜 금요일에 OS까지 훑어보고 이젠 nand2tetris 좀 그만 끝내고싶다 ㅋㅋㅋㅋㅋㅋ

 

 실제 구현도 하면 더 기억에 오래남겠지만 가산기도 제대로 못만들어서 끄적대다가 원서도 이만큼 읽고 ppt의 도움으로 여기까지 왔으니 나중에 하고싶어지면 구현해야지 싶다 지금은 이렇게 CS 지식을 좀 더 쌓은것만으로도 충분히 잘한거라 생각한다.

----

05.26

어제 하루만에 2장을 급히 훑어보고

11장, 12장만 하면 되는데 오늘 급 의욕이 떨어졌다.

처음에 시작할때는 괜찬았는데 대강 지금까지 한 내용들만해도 내가 궁금했던거 거의 다 보기도 했고

OS 파트를 좀 보긴 해야되는데 나중에 하고싶어지면 보고 일단 nand2tetris는 여기까지 마무리하는걸로?

 

이 다음으로는 임베디드나 이용성 교수님이 알려주신 프로테우스로 회로 만들고 시뮬레이션 하는걸 해볼까 싶다.

 

이제 가상 머신 파트를 끝나고 컴파일러와 운영체제에 다와간다.

이번에 봐야하는 장은 고급 언어로 HACK에서 사용하는 고급언어인 JACK에 대해서 보면된다.

JACK은 JAVA나 C++ 같은 객체지향 언어 비슷한거라 저자분 말로는 배우는데 1시간이면 충분하다고 한다.

이런 고급언어 처럼 상속같은건 안되지만 왠만한 건 다 가능해서 테트리스나 스네이크, 퐁, 스페이스 인베이더 등 게임도 만들수가 있다.

 

 

이번 장 학습 개요와 절차 지행 프로그래밍

 

이번 장에서 다루는 내용은 아래와 같다.

1. JACK을 이용한 프로그래밍 예제,  절차지향, 객체지향, 리스트 처리 등

2. 문법 관련 예시들

3. 응용 어플리케이션 개발 관련 내용

 

 

 모든 언어를 공부할때 처음 만나는 hello world 출력하기 예제, 자바랑 거의 비슷하다. 우측에 주석문에 대한 내용이 있는데 /**    */ 가 API 설명 블록인지는 처음알았다.

 

 이번에는 절차와 배열을 이용한 프로그래밍 예시인데 키보드 입력을 받아서 배열에다가 담아내는 내용이네. 이 예제 코드로 JACK 언어에 대해서 좀 보면

 

1. JACK 프로그램은 여러 개의 .jack 클래스 파일을 모아서 Main.jack의 main 함수서 시작한다.(Main.jack은 꼭있어야함)

2. 흐름 제어도 제공하고

3. 기본 자료형으로 int, char, boolean, 클래스 정의한 타입인 OS에서 제공하는 Array와 String, 그리고 추상 자료형을 구현한 사용자 정의 클래스도 사용할수 있다.

4. 제공되는 OS 서비스로 Keyboard.readInt, Output.printString 등이 있다.

 5. OS에서 배열 자료형을 제공하는데, jack arrays are not typed 이 부분은 뭔 의미인지 잘모르겠다.

 

 

객체 지향 프로그래밍

 다른 언어처럼 JACK도 필요한 클래스를 추상 데이터 타입 ADT로 표현할수 있으니 구현해놓고 이걸 보고 쓰면된다.

 

 JACK에서는 기본 자료형으로 int, char, boolean만 제공하고 있다. 소수점 자리 날라가는거 없이 분수 표현을 하기 위해서 만들어진 Fraction 클래스가 있는데, field는 맴버 변수가 된다.

 

 Jack의 서브 루틴으로는 메서드와 생성자 그리고 함수가 있는데 메소드는 이 클래스 외부서 접근해서 사용할 수 있지만 function의 경우 이 함수 내부에서만 사용된다. 생성자는 생성자고, JACK에서는 가비지 컬랙터가 제공되지 않으니 사용하지 않는 객체는 명시적으로 해제해야되며 JACK의 모든 서브루틴은 return문으로 종료되어야한다.

 

서브루틴 전반에 대해서 봤는데, 그러면 JACK에서 클래스 객체는 어떻게 만들어지는지를 보자. 생성자는 기본적으로 OS와 컴파일러에서 어느 RAM 공간에다가 객체를 저장할지 다뤄준다. 또, 객체를 생성하면 스택에는 객체의 주소(객체의 값들이 있는 곳의 주소)가 올라가고, 힙 영역에는 그 객체의 값들이 보관된다.

 좌측 클라이언트 코드에서 a = Fraction.new(2, 3)한 결과 지역 변수 a는 스택에 올라갔고 이 스택의 값은 이 변수의 값들의 주소(heap에 위치)를 나타낸다. 이 지역 변수이자 분수인 a의 값들은 힙 영역 RAM[15087], RAM[15088] 에 저장 되어있다.

 빨리 넘어가기위해 생략하지면 연결 리스트를 제공하고 있어 리스트 처리도 가능하다.

 

 

OS를 구성하는 클래스는 위 8개고, 우측의 기능들을 제공한다.

 

 서브루틴 작성할 때 다른 클래스에 있는 내용을 가져와서 쓰면되지, 또 작성하지 말아야 한다.

 

 

 

 

 

JACK 언어 특징

 이제 JACK 언어를 이용한 고급 언어 프로그래밍을 마치고 JACK 언어의 문법, 데이터 타입, 클래스 등 언어적 특징을 살펴보자

 

 JACK은 토큰, 우리나라 말로 하면 형태소랑 비슷할거 같은데, 의미를 가진 최소 단위인 토큰들로 이뤄져있는데 이 토큰들은 신텍스 요소 그러니까 문법적 요소로 분류하면 위의 표와 같다. 이 부분들은 뒤의 컴파일러 파트에서 필요해서 간단하게 짚고 넘어간다. 대충 코딩하면 몰라도 되긴한데 컴파일러를 만들어야되니

 

 공백이나 주석문은 //, /*, /** 같은 식이고,   (), [], {}, ,, ;, +, - 같은 것들을 심볼, int, boolean, class, true 같은 것들은 예약어, 이도 저도 아닌 숫자는 상수, 그 외의 숫자 이외로 시작하는 문자들의 모음을 식별자라고 부른다.

 

 

 클래스의 맴버 변수, 정적 변수는 맨 앞에다가 선언해줘야 되고, 그 뒤에는 서브루틴들을 선언한다.

 

 서브루틴 그러니까 생성자, 메소드, 함수 같은 것들은 위의 형태로 선언과 구현을 한다. 함수 function은 외부에서 사용 불가능한 static 메소드이고, 그냥 메소드는 이 객체를 생성해서 외부에서 쓸수있다. 

 

 

 이제 데이터 타입을보면, 아까 어떤 기본타입이 있는지 말했으니 넘어가고 

 

객체는 위와 같이 선언하고 할당할 수 있으며, let 객체명 = 다른 객체; 시에는 얕은 복사가 이뤄진다.

객체의 선언 : var 클래스명 객체이름;

객체 할당 : let 객체명 = 객체 인스턴스;

 문자열 String은 OS에서 제공하는 String 클래스의 인스턴스며, 특이한점이라면 charAt(index) 라는 함수로 특정 문자 갑을 가져올수 있다.

 

 JACK 언어의 타입은 약한 타입, 약한 형태를 가지고 있는데, 이 말은 한 타입의 값을 다른 타입에다가 어떻게 할당할지 형 변환이 이뤄질지를 정의하지 않았다는 말을 의미한다. 왜냐면 다른 타입 끼리 할당하거나 어떻게 처리할지까지 정의하면 컴파일러가 너무 복잡해지다보니 간단한 컴파일러를 만들다보니 이렇게 됬다.

 

 변수의 경우 1) 해당 클래스와 모든 클래스의 서브루틴에서 접근가능한 정적 변수(같은 클래스 인스턴스끼리 공유하는 변수라 공유 변수라고도 함), 2) 클래스 단위에서 정의되어 객체의 속성을 나타내는 필드 변수, 3) 해당 서브루틴 안에서만 사용되는 로컬 변수와 4) 콜러가 서브루틴으로 전달하는 파라미터 변수 4 종류로 나눌수 있다.

  클래스와 필드 변수는 그 클래스(정적 변수, 해당 클래스의 모든 인스턴스), 그 클래스 인스턴스(필드 변수)에서만 접근이 가능하여 외부에서는 사용이 불가하고, 개발자가 작성한 경우 접근자 accessor(자바로 따지면 게터, 세터같은거)와 메소드로 접근이 가능하다.

 구문 statement는 변수에 값을 할당할때 쓰는 let, 조건문 if, 반복문 while, 함수 호출하는 do, 값을 반환하는 return 정도가 있다.

 

 각 구문들은 변수 명과 또 다른 구문 그리고 표현식 expression으로 이뤄져 있는데, 다음과 같은 표현식들이 존재한다.

1. 상수

2. 변수명 : 변수는 static( 그 클래스와 인스턴스 전체), field(그 클래스 인스턴스 내), local(그 서브루틴 안), parameter(그 서브루틴 안) 중 하나에 속한다.

3. this : 현재 객체의 포인터를 의미한다.

4. arr[expression] : arr은 Array 클래스의 한 타입으로 arr의 요소를 이와 같이 접근한다.

5. 서브루틴 : non-void type을 반환한다는데, return;으로 끝나는 메인함수는 뭔가 싶은데 아직은 잘모르겠다.

6. -expression, ~expression : 이 경우 -는 산술 부정 연산, ~는 논리 부정 연산을 한다.

 

 

 

 서브루틴 호출 : 서브루틴에 함수, 메소드, 생성자가 있는데 매번 호출 가능 범위가 햇갈린다. 이걸 보면서야 정리가 됬는데, 함수는 해당 클래스 안에서만, 메소드는 해당 클래스 그러니까 인스턴스 밖에서, 생성자/소멸자도 밖에서 사용가능하다.

 

 

JACK 프로그램

 이제 JACK 언어에 대해서 대부분을 살펴봤고, 이제 JACK으로 구현한 어플리케이션, OS 사용 등 예시를 둘러보고 이번 장을 마치자.

 

 JACK으로 개발한 이런 프로그램들이 있고

 

 잭 응용 프로그램을 만들려면 하나의 폴더에다가 구현한 .jack 클래스 파일들과 컴파일러를 놓고 컴파일을 하자. 생성된 vm 파일들을 가상 머신으로 로드해서 실행하면 된다.

 

 

 JACK OS 클래스로 이런것들이 있다.

1. Output : 화면에 문자 출력

2. graphics : 화면에 선, 원, 픽셀, 사각형 등 출력

3. inputs : 키보드 입력 읽기

4. Math, String, Array : 수학, 문자열, 배열 처리

5. Memory : 메모리 읽기, 쓰기, 할당, 해제

6. Sys : 중지, 에러 등, 가상머신 파트 마지막 부팅 내용에서 sp=256   sys.init 인가로 시작한다 했던거 같은데 init 이 왜없는지는 아직 모르겠다.

 

 

 

 

 와 대강 2 ~ 3시간만에 9장 내용을 대강 정리했다. 이번 금요일에 OS까지 하는걸 목표로 하다보니까. 이번장이야 이미프로그래밍은 익숙하니 금방 끝내겠지 하고 9, 10장을 한번에 끝낼 생각으로 시작했었다.

 

 그런데 오늘 수업 중에 여유있을때 컴파일러 파트 PPT 자료를 보는데 문법, 구문, 표현식, 파싱 같은 내용들이 수두룩한걸 보고나서 정리를 시작하려고 보니까 대충 넘겨서는 안될 부분들이 좀 있더라.

 

 원래 생각햇던것 보다는 이번장 양이 많아지기는 했는데, 책만 봤을때에 비하면 PPT 자료가 이해하기가 너무 좋게 잘 정리되어있어서 이덕분에 빨리 마무리 할 수 있었다.

 

 다음 장은 컴파일러 1 : 문법 분석 파트다. 좀 쉬고 10장 시작해야지.

이전 글에서 가상 머신이 런타임에서 어떻게 동작하는지를 대강 봤었고

이제 구현하기에 앞서 잠깐 VM 프로그램에 대해서 짚고 넘어가야할 부분이 있다.

 

 

 

VM 프로그램 구현 시 유의사항

책에서 이미 나온 내용들은 대충 패스하고

아 JACK 언어로 만든 프로그램들을 컴파일하면 vm 파일이 나온다했는데

FileName.jack -> FileName.vm 같은 식을 된다.

 

 VM 프로그램에서의 함수명 : 그런데 FileName.jack 에 bar라고 하는 생성자든, 함수가 있다고 하자

VM 함수 명은 전역적이다 보니까 그러면 이런 함수는 여러 vm 파일들을 합쳐서 어샘블리어로 만들 때 동일한 함수명이 존재하는 경우 충돌나게 되므로 서로 다른 vm 파일에 있는 동일한 이름의 함수를 구분해주기 위해서

VM 프로그램의 함수는 FileName.functionName과 같은 식으로 되어야 한다.

foo.jack에 mult라는 함수가 있는 경우 컴파일 하면 foo.vm에는 foo.mult 같은 식으로 함수 이름이 만들어진다.

 

 

 프로그램 시작점은 Main.jack 파일의 main 함수가 된다 그러므로 VM 프로그램 상에서는 Main.vm에 위치한 Main.main이란 함수가 프로그램의 시작하는 위치가 되겠다. 그리고 이 함수에서는 이제 프로그램이 시작되면서 운영체제의 초기화를 위한 함수인 Sys.init 을 호출하면서 시작된다. 다시 말하면 Main.vm 에 있는 Main.main 함수에서 프로그램이 시작하며 가장 먼저 초기화 함수인 Sys.init을 실행한다.

 

 프로그램 실행 과정 :제공되는 VM emulator로 돌리려면 프로그램 폴더에다가 하나든 여러개든 .vm 파일을 넣고 에뮬레이터로 로드하면된다.

 

 

 

VM 명령어와 VM 번역기로 번역한 어셈블리어 (슈도) 코드 

 VM 명령어로 함수를 호출하고 반환하는걸 어떻게 어셈블리어로 쓰나 싶었는데 이렇게 슈도 코드로 알려준다.

 

 

HACK 컴퓨터 가상 머신 표준

1. 스택

 앞에서 여러번 말한거지만 RAM 0 ~ 15번지까지는 가상 레지스터/포인터들이 차지하고 있고, 16 ~ 255번지까지는 정적 변수, 그리고 256번지 부터는 스택 영역으로 차지하고 있다. 그래서 일단 VM 번역기는 0 ~ 256까지 초기화 하는 내용으로 시작해야 하고, 값이 스택에 값이 추가되거나 뺄때 SP가 변경되도록 해야한다.

 

2. 특별한 심볼들

  어셈블리어와 마찬가지로 가상 머신이 다뤄야하는 특별한 심볼 두 가지가 있는데, 하나는 어셈블리어에서 봤던 선정의된 심볼들. 두번째는 return address와 함수 시작점을 표기하기 위한 심볼릭 라벨이다.

 

 내가 앞에서 생략한건데 소프트웨어 파트 시작할때 PointDemo.jack이라는 프로그램을 잠깐 소개했었다. 이 프로그램은 Main.jack과 Point.jack이라는 파일 두개가 PointDemo라는 폴더에 들어있었는데, 컴파일러를 돌리면 파일이 두개니까 두 vm 파일이 나온다. 컴파일 하면 서로 다른 파일의 함수를 구분 시켜주기 위해 기존의 jack 함수 명앞에 파일 명을 붙인다고 했었으므로, Main.vm에는 Main.main이, Point.vm에는 Point.new, Point.get 등으로 바뀐다.

 

 이 폴더를 VM 번역기로 돌리면 같은 폴더에 있는 vm 프로그램들을 읽어서 하나의 어셈블리어 파일인 PointDemo.asm을 만들어내며 이 asm 까지오면 더이상 추상화 된것 없이 심볼릭으로 전체가 구현된다. 

 

 

 

가상 머신 번역기의 함수 호출과 반환 어셈블리어 생성

 그러면 어셈블리어에서 함수를 호출하면서 점프하려면 함수 시작점을 어떻게 표시하고, 함수의 결과를 반환하는건 어떻게 표현할까? 아까 위에서 함수 호출, 결과 반환시에 대한 VM 명령어와 VM 번역기가 생성한 어셈블리 코드 표를 붙여넣기는 했었지만 이 PointDemo.asm에서는 아래와 같은 식으로 어셈블리어가 만들어진다.

 

 VM 코드의 Main.main에서 call Point.new 를 호출한게 어셈블리어 상에서 점프문으로 'goto 함수명', 점프할 위치는 심볼릭 라벨인 (함수명)의 형태하여 goto Point.new와 (Point.new)로 함수의 호출과 함수의 시작점을 표현하였고, (Point.new)아래 부분에 이 함수에서 어셈블리어로 구현된 연산 명령어들이 위치한다.

 

 이번에 리턴의 경우 vmcode function Point.new 의 맨 끝에 return이 위치하고 있다. 이 리턴문에는 반환 값은 없어 보이지만 goto Main.main$ret0라고, (Main.main$ret0)의 위치로 점프하라고 적혀있으며 이 심볼릭 라벨은 goto Point.new 바로 다음 줄에 위치하므로 함수를 실행 한 후에 마치면 메인 함수의 함수 호출문 바로 다음줄로 돌아와서 나머지 메인 함수의 명령어를 실행하게 되었다!

 

 

 

가상머신 번역기가 생성하는 특별한 심볼들

 앞에서 나온 .vm 파일의 명령어가 .asm 파일에서 어떻게 가상머신 번역기를 통해 생성되는지는 위 표를 참고하면 될거같다.

 

- SP, LCL, ARG, THIS, THAT이야 스택 포인터, 로컬 변수 메모리 세그먼트 베이스 주소, 매개변수 메모리 세그먼스 베이스 주소, 포인터 2개인걸 앞에서 여러번 봤었다.

- Xxx.i : Xxx.vm 이란 vm 파일에 존재하는 i번째 정적변수는 어셈블리어 상에서 Xxx.i로 생성된다.

- functionName$label : 이건 내가 놓쳣는지 앞에서 본적이 없었는데, 달라 앞부분만 보면 Xxx.vm 파일에 foo 라는 함수가 있는 경우 어셈블리어 상에서 Xxx.foo라는 심볼로 나오는건 알지만 이 foo 함수 안에 여기선 라벨이라고 하는데 변수라고 이해하면 될지 좀 햇갈리지만 ... 아 조건문일때 내용이구나 조건문을 만족하면 함수 내의 목적지를 Xxx.foo$bar라는 식으로 표기하는거 같다.

- functionName : 이거야 앞에서 봤지만 Xxx.vm 파일의 Xxx.foo 함수는 어셈블리어 상에서 함수의 시작점으로 (Xxx.foo)형태의 심볼릭 라벨로 표기된다.

- functionName$ret.i : 이것도 앞의 예시에서 본거지만 functionName 이라는 함수에서 i 번째 리턴후 시작하는 지점을 나타내며 (functionName$ret.i) 의 심볼릭 라벨로 어셈블리어로 생성된다. 아까의 경우 Main.main 함수의 첫번째 함수 호출 바로 다음줄에 위치하여, 첫번째 함수를 다 실행하면 이 심볼릭 라벨로 점프하여 다시 메인 함수 내용을 실행하도록 되어있었다.

- R13-R15 : 이거는 필요할때 쓰라고 남겨둔거라고 한다.

 

 

 

 와!! 아직 가상 머신 파트 조금 남기는 했지만 거의 다와갔다. ppt 자료는 장난아니게 많아서 걱정좀 했었는데 보다 보니까 전역 스택에서 어떻게 상태를 저장하고 왔다갔다 하는지를 한페이지씩 보여주다 보니까 이런식으로 페이지 양이 장난아니게 많았더라. 조금만 더하면 실제 VM 구현을 제외한 내용이 끝난다.

 

 잠깐 쉬었더니 마무리하기 진짜 귀찬네 ㅋㅋ

일단 다시 돌아와서 VM 번역기가 하는 일을 이제 제대로 정리하자

 VM 번역기는 hack 플랫폼의 가상 머신으로 컴파일러가 번역하여 생성한 VM 코드를 hack에서 실행가능한 어셈블리어로 번역해주는 역활을 한다. 이번 장에서 배운 VM 명령어는 산술논리연산 명령어, 메모리 제어 명령어, 분기 명령, 함수 명령어로 이뤄져있었으며, 이게 우측 하단의 타겟 언어의 내용처럼 되어있는 hack 어셈블리어로 번역된다.

 

 

 조금 더 자세히 보자

 

VM 번역기를 이용한 어셈블리어 생성

1. 호출 제어 어셈블리어 생성

 맨 좌측의 VM 코드를 보면 먼저 call Bar.mult 2라는 명령어가 있는데, 이 명령어는 위 상수 19와 지역변수 3을 매개변수을 가지고 Bar.mult 라는 함수를 호출하게 된다. 우측의 어셈블리어 코드를 보면 메인 함수 프레임을 저장하는거나 다른 해야되는 연산 내용은 없지만 goto Bar.mult로 호출한 함수로 이동하는거랑 콜리의 연산을 마치고 돌아올 수 있도록 Foo 함수 내부의 함수 호출 후 1번째 반윈 위치를 나타내는 (Foo$ret.1)이 있다.

 

 이 과정은 우측의 어셈블리 코드와 전역 스택을 보면 더 자세히 나와있는데, 함수를 호출하는 vm 코드는 슈도 어셈블리어로 맨 먼저 리턴주소와 다른 정보들을 스택에다가 넣어두고 foo,main 함수 프레임 저장을 완료하면, 호출된 함수의 매개변수 세그먼트 베이스 어드레스를 ARG = SP-5-nArgs로 지정해준다. 그 다음에는 현재 스택 포인터를 로컬 메모리 세그먼트 베이스 주소로 잡아주고

 

 이 그림과 같이 Bar.mult 함수 어셈블리어 시작점으로 넘어와 이 함수에 관한 명령어들이 실행된다.

 

 

 

 

2. 함수 제어 어셈블리어 생성

 함수 제어 어셈블리어의 경우에는 함수 내용에 따라서 만들어야 하는 어셈블리어는 다르지만 일단 공통적으로는 이 함수의 지역변수 만큼 공간을 잡고 0으로 초기화 시켜준 다음, 필요한 연산을 수행하게 되고 리턴문 그러니까 goto Foo$ret.1까지 진행하게 된다. 연산 결과를 어떻게 foo.main 함수로 넘어갈지는 다음의 반환 제어 어셈블리어 파트를 보자

 

3. 반환 제어 어셈블리어 생성

 이제 진짜 VM 번역기 동작 끝에 거의 다왔다. 이제 반환 연산을 어셈블리어로 나타낼 차례인데 좌측의 코드를 보면 LCL-5로 리턴 어드래스를 구하는데, 이 리턴 어드레스가 (Foo$ret.1) 심볼릭 라벨의 명령어 메모리상의 주소를 의미하는거 같다.

 

 아무튼 이렇게 돌아가야할 명령어 주소를 구해낸뒤, *ARG = pop()을 통해 글로벌 스택 최상단에 위치한 연산 결과를 매개변수 포인터 위치 즉, 콜러의 워킹 스택 최상단에다가 넣음으로서 콜리의 연산 결과를 콜러의 스택에다가 옮겼다! 그리고 연산 결과를 담았으니 스택 포인터도 그 위로 옮겨주고, 스택 포인터 뒤는 메인 함수 프레임을 가져와 저장하고 나서는 이재 재활용해서 쓴다.

 

 이제 콜러의 프레임들얼 다시 되찾아올수 있도록 endFrame(=LCL) - 1, 2, 3, 4를 하여 나머지 포인터 값들도 다시 읽어들인 뒤에 goto retAddr 으로 (Foo$ret.1)일로 점프해서 함수 호출 후 남은 main 함수의 명령어들을 수행하는게 VM 번역기가 하는 호출, 함수 처리, 반환하면서 어셈블리어 생성하는 과정이 되겠다.

 

 

프로그램 컴파일과 부팅 과정

 

 프로그램 컴파일 및 번역 과정을 정리하자면 한 폴더 myProg에 있는 모든 jack 파일들을 컴파일러가 vm 파일로 바꾸고, vm 파일의 함수 명은 앞에 파일명.함수로 바뀌어서 만들어 진다. 그리고, VM 번역기에 의해 vm code 함수명은 심볼릭 라벨로 생성되어 함수의 시작할 주소를 의미한다.

 

 

 VM 프로그램이 부팅하기 위해서는 VM 프로그램 중에 하나는 프로그램 시작점을 나타낼 수 있도록 Main.vm과 이 파일 안에 Main.main이라는 함수가 존재해야한다.

 

 그리고 hack 표준 맵핑에 나왔듯이 스택은 256번지부터 시작했었는데, 여기서 Sys.init 그러니까 Sys.vm의 Sys.init 함수를 호출하면 초기화 동작과 Main.main 함수를 호출에 무한 루프를 돌며 동작하게 된다!

 

 

 

 

8장 정리

 이번 장에서는 추상적인 분기화 함수 명령어를 hack 플랫폼의 어셈블리어로 구현하는 과정을 거치면서 VM 번역기가 어떻게 어셈블리어를 생성하는지 이해할 수 있었다. 이 내용은 2단계 컴파일 모델의 백앤드 파트로 이제 다음장에서 JACK을 배운 후 10, 11장에서 프론트 엔드 파트인 컴파일러에 대해서 정리하자!

 

 

 wa 드디여 8장을 마무리했다. 코드 구현하지 못한건 아쉽긴한데 그래도 이걸 보면서 가상 머신이 어떻게 된거고 중간 언어를 가지고 어셈블리어를 생성하고, 상세한 함수 호출, 동작, 반환 루틴을 알게 되니까 참 멀리왔다 ㅠㅜ

이번주 금요일까지 코드 구현까지는 아니더라도 OS 내용까지 마무리하면 진짜 뿌듯할거같다 

 

 

 

 

 

 

+ Recent posts