일단 저번 글에서 알아봤던 컴퓨터 구조에서 중요한 키워드들이 몇개 있어서 그것부터 한번 살펴봐야겠다
- 일단 명령어의 종류들이 있는데 종류가 많은것을 CISC, 줄인것을 RISC라고 한다.
최근에는 RISC가 많다
- Linking은 여러개의 코드와 데이터를 모아 연결하고 메모리에 로드될 수 있게 한다
- MMU는 CPU코어 안에 탑재되어 가상 주소를 실제 메모리 주소로 변환해주는 장치
OS
먼저 OS는 가장 많이 들었던 것중 하나로 Window, Mac OS, 리눅스등 운영체제이다. 프로그램을 다운로드 받을 때도 흔하게 Mac과 Window 버전을 다르게 다운받기도 한다.
간단히 계산기 프로그램을 다운 받았다고 했을 때 계산기는 내부적으로는 더하기 빼기 같은 계산을 할 줄 알아야 한다.
또 화면에 어떤 숫자와 기호를 눌렀는지 등도 입력을 인식할 줄 알아야 하고 계산 후 답을 출력할 줄도 알아야한다.
계산기를 실행하는 컴퓨터의 입장에서 보면 사용자마다 가지고 있는 컴퓨터의 종류도 다를 수 있고 키보드 마우스도 다 다를 수 있다. 이런 하드웨어 장치들은 각자 제어하는 방법도 다를 수 있는데 프로그램은 사용자가 키보드로 입력한 정보를 입력받아와야 하는데, 이렇게 모든 하드웨어들의 각자 다른 제어법들을 모두 고려해 프로그램을 만들려면 고려할게 굉장히 많아진다
OS는 이런 걸 알아서 해준다. 프로그램은 그냥 운영체제에 systme call이라는 요청만 보내면 운영체제가 알아서 모든 하드웨어 들과 대응할 수 있게 알아서 처리해준다.
다만 운영체제마다 프로그램이 보내야하는 시스템 콜의 종류가 다르기에 프로그램을 만들 땐 하드웨어까지 고려하지는 않더라도 운영체제의 종류는 고려해 만들어야 한다.
(이때 우리가 하드웨어에 따라 드라이버를 설치하여 운영체제에서 사용가능하게 만들기도 한다)
OS가 실행되는 과정
- 바이오스 - BIOS(Basic Input Output System)
- 메모리와 CPU 레지스터를 초기화 시킨다
- 디스크로부터 부트 로더를 불러온다
- 부트 로더는 디스크에서 os커널의 이미지를 찾아서 메모리로 불러오고 실행 시키는 역할을 한다
2.부팅을 위해 컴퓨터 전원이 켜지면 제일 먼저 롬 바이오스라고 불리는 기본적인 프로그램이 동작한다
- 이 롬은 전원이 꺼지더라도 기억할 수 있도록 ROM이라는 메모리에 기록된다.
- ROM은 Read Only Memory로 비휘발성 메모리이다.
- 칩에 영구 저장되는데 바이너리 코드를 사용해 개별 셀에 쓰인다
- 롬 바이오스에 있는 프로그램이 동작하며 뭐 바이오스 제작 회사나 일자 셋업 들어가는 f1키 이런게 뜨고 정보를 테스트 해 나가며 부팅이 진행된다.
- 위 단계가 끝나면 시스템의 운영체제(OS)를 읽어 메모리에 상주시키는 작업을 하는데 이때 어느 드라이브에서 읽어올것인지 롬 바이오스로부터 정보를 참조한다.
이런 일련의 과정이 부팅이라고 한다(좀 더 세밀하게 따지면 콜드 부팅이라고 한다)
컴파일러
크게 신경쓰지 않던 부분이기도 하고 자동으로 컴파일 해주니 그 속을 제대로 들여다 보지 않았던 것 같다.
컴파일은 우리가 알고 있는 그대로 우리 인간이 이해할 수 있는 소스 코드들을 CPU가 이해할 수 있는 언어(기계어) 로 변환하는 작업이다
성호님 슬라이드에 있는 5단계의 컴파일러 과정이다. 구글에 검색해보면 4단계도 있고 6단계도 있고 하지만 결론적으로 흐름과 과정은 똑같지만 어느 단계를 통합해서 보거나의 차이가 있는 것 같다.
일단 수업상으로 기억하기론 위에 나타난 소스코드가 컴파일 되는 과정은
- Lexical Analyzer
- 우리가 책을 읽고 말하듯이 맨 첫줄 부터 하나하나 읽어 나가며 어휘를 분석한다.
- 이때 Tokenizer 즉, 완전 글자 하나하나씩 분석하며 스페이스바 까지 포함하여 분석한다.
- 분석된 결과물(예시로 var)를 Lexer에게 주고 Lexer는 이게 어떠한 역할인지 판단한다.
- 판단된 결과물들 심볼테이블에 올린다
- Syntax Analyzer(Parser)
- AST(Abstract Syntax Tree) cf.Annotated AST 로 말그대로 트리화 하는 것이다.
var a = 1
이다 했을 때 var 라는 곳에서 식별자 a와 리터럴 1로 구분지어지고 var c = a+b;
일때 var는 c와 add 로 나눠지고 add는 a와 b로 나눠진다
- 이렇게 트리화되는 것이 2단계이며 이때 타입스크립트처럼 타이비 정해지면 따로 타입까지 트리에 나타나는데 이게 Annotated AST 이다
- Intermediate Code Generator(Semantic Analyzer)
- Type Checking => Byte Code(object file)
- 이 과정에서 중간 코드로 바뀌게 되는데 이때 꼭 어셈블리어로 바뀌는 것은 아니다
- 각 어셈블리어는 cpu제조사마다 다른데 컴퓨터 환경이 달라지면 다른 제조사의 어셈블리어로 컴파일된 코드를 사용할 수 없으니 새롭게 컴파일을 다시 해야한다.
- 이때 등장한게 JVM으로 중간 코드가 하나로 모아질 수 있게끔 했다
- 그 과정은 간단한게 각 cpu마다 알맞는 어셈블리어로 변환해주는 JVM을 깔아두고 우리가 작성한 소스코드는 JVM이 이해할 수 있는 코드로 컴파일 시키면 된다.
- 그럼 다른 컴퓨터 환경에 코드를 전달할 때 소스코드 그대로 전달하여 컴파일 과정을 다시 할 필요없이 JVM이 이해할 코드로 컴파일된 .class 파일만 전달해주면 된다(획기적이긴하다)
- Code Optimizer (Local & Global Optimizer)
- 최적화 과정으로 Scheduling(스케줄링) 어떤 코드가 먼저 실행되고 어떤 방식으로 가야하고 이런 모든 최적화 과정을 이 단계에서 실행한다.
- 이때 각 코드들이 어떤 메모리 크기를 가지고 어디에 할당할 것이지 스케줄링과 함께 동작하고 또한 쓰이지 않는 코드들 또한 삭제된다
- Target Code Generator(exe file)
- 마지막에 타겟 코드 실행할 코드가 담긴 EXE파일이 만들어지며 컴파일 과정은 끝이난다
- 해당 exe 파일을 실행하면 이제 메모리에 코드들이 로드되고 cpu 자원을 할당받아서 우리가 작성한 코드들이 실행되는 것이다.
여기까지가 수업중에 내가 이해한 전체적인 컴파일 과정인데 혹시 추가적인게 있으니 구글링을 통해 또 찾아봤다
컴파일러와 인터프리터
일단 컴파일러는 모든 코드들이 작성되고 컴파일 과정을 거치는 것이고 한줄씩 해석해서 바로 명령어를 실행하는 것이 인터프리터이다
컴파일러: 번역기
- 고급 언어를 바로 기계어로 변환
- 실행속도가 빠르다
- 전체 소스코드를 보고 명령어를 수집 재구성
인터프리터 : 통역기
- 고급언어를 기계어로 변환하는 과정 없이 한줄씩 해석하여 바로 명령어를 실행하는 것(번역보단 실행에 목적이 있다
- 번역 속도는 빠르지만 프로그램 실행 시 매번 변역해야하기 때문에 실행속도는 느리다
- CPU의 사용시간 낭비가 크다
- 소스코드의 각 행을 연속적으로 분석하여 실행한다
컴파일의 모든 과정은 다른 곳에서도 마찬가지로 설명한다
어휘분석 - 구문분석 - 의미분석 및 중간코드 생성 - 최적화 단계 - 코드 생성단계
- 어휘분석(Lexical Analysis)
위에서 설명한 거와 똑같이 모든 텍스트를 토큰 단위로 자르고 렉서가 해석한 후 심볼화 한다.
- 구문 분석(Syntax Analysis)
문법적으로 옳은지 검사하고 파스트리라는 임베딩 구조를 생성한다
이때 파스트리를 이용해서 현재 토큰들이 문법적으로 올바르게 구성되어 있는지 평가 할 수 있다(BNF)
3.의미분석 및 중간코드 생성(Semantic Analysis & Intermediate code)
중간 코드를 생성하고 이 과정에서 의미적으로 옳은지 검사한다(문법 오류는 구문분석, 의미 오류는 의미 분석)
여기서 의미오류란 문법적으론 정상적이지만 실행의 결과가 원하는대로 나오지 않는 오류를 말한다
(예시로 변수 x를 사용해야 하는 곳에서 변수 y를 사용해서 개발자가 의도한 결과가 나오지 않는 것)
- 최적화 단계 (Optimization)
불필요한 코드를 제거하고 효율적인 코드로 개선 -> 크기가 줄어들고 실행속도가 빨라진다
- 코드 생성 단계
최적화된 중간코드를 컴퓨터가 이해할 수 있는 기계어로 만든다. 이를 목적 프로그램(Object Program)이라고 한다
인터프리터와 차이
인터프리터의 경우 코드가 변역된 후 컴퓨터로 전달되어 바로 실행되는데 컴파일러는 위에서 설명한 것과 같이 디스크에 번역이 완료된 코드를 저장하고 이 코드는 언제든 실행할 수 있다
이러한 차이점으로 인터프리터는 프로그램을 실행할 때 필요하므로, 프로그램을 실행시키기 위해서는 반드시 인터프리터가 설치되어 있어야 한다
컴파일러는 한번 컴파일 되었다면 컴파일 된 프로그램만 있으면 실행이 가능하니 따로 컴파일러나 원본 소스코드가 필요하지 않다는 점이 있다
인터프리터는 이식성에 장점이 있고 컴파일러는 성능에 장점이 있다
Reference
(시니어 코딩)[https://www.youtube.com/@SeniorCoding] - 풀스택 실무 프로젝트(5기)
(프로젝트 개요-blog)[https://gusdnd852.tistory.com/205]