IT기업이 원하는 백엔드개발자 에서 이어집니다!!
채용 공고를 통해 현장에서 신입 개발자들에게 공통적으로 요구하는 역량이 CS에 대한 기초적 이해라는 것을 파악하였습니다. CS:APP📒 를 개발일지에 정리해가며 공부해보려고 합니다. 알고리즘 CLRS📒 책을 읽을 때도 그러하였지만, 처음 읽을 때 배우게 되는 것과 어느 정도 학습한 이후 다시 읽게 되었을 때 깨닫는 것이 달라 첫 작성 이후에도 다시 찾아와 수정하도록 하겠습니다.
특히 1장의 경우, 책 전체에 대한 개요를 다루고 있기 때문에 자세한 설명은 추후 이어지는 장들에서 만나볼 수 있습니다. 아래에 요약도 있습니다!
프로그래머는 소스파일을 작성하고 텍스트 파일로 저장된다. 소스파일은 0과 1로 이루어진 비트들의 연속이며, 바이트라는 8비트 단위로 구성된다. 각 텍스트는 ASCII 표준을 사용하여 표시한다.
아스키(ASCII) : 각 문자를 바이트 길이의 정수로 표현하는 방식
모든 시스템 내부의 정보는 비트들로 표시되며, 다른 객체들을 구분하는 유일한 방법은 이들을 바라보는 컨텍스트에 의해서다. 이를 달리 말하면, 다른 컨텍스트에서는 동일한 일련의 바이트가 전혀 다른 정수, 문자열 등을 의미할 수 있다는 것이다!!
그렇다면, 우리가 작성한 소스파일이 어떻게 컴퓨터에게 전달되고 저장되어 다시 모니터를 통해 보이는 것일까? 다음 장에서 알아보자.
컴퓨터님..제 말 이해하셨나요..?
컴퓨터는 우리가 작성한 소스파일을 4단계에 걸쳐서 실행한다. 이 네 단계(전처리, 컴파일러, 어셈블러, 링커)를 합쳐서 '컴파일 시스템'이라고 부른다.
우리는 C 언어로 작성된 Moon.c 파일을 실행하여 그 안에 입력된 Hello, Moon!을 출력하고 싶다.
#include <stdio.h>
int main()
{
printf("Hello, Moon!\n");
return 0;
}
먼저, 전처리 단계에서는 전처리기(cpp)를 통해 헤더파일을 인식한 후, 본래의 C프로그램을 #문자로 시작하는 디렉티브에 따라 수정한다. 그 결과, Moon.i라는 새로운 C프로그램이 생성된다.(파이썬의 from, import가 유사한 기능일지도..? 추후 공부 후 보충해보겠습니다)
컴파일러는 들어온 텍스트파일 Moon.i를 Moon.s로 번역하며, 이 파일에는 어셈블리어 프로그램이 저장된다. (참고로, 어셈블리어는 기계어와 일대일 대응이 되는 저급 언어입니다!)
어셈블리 단계에 들어오게 되면, Moon.s를 기계어 인스트럭션으로 번역하고 재배치가능 목적프로그램의 형태로 묶어서 Moon.o라는 목적 파일에 그 결과를 저장한다.
마지막으로 링커는 우리가 작성한 프로그램이 printf라는 함수를 호출하는데, 이미 컴파일된 별도의 목적파일인 printf.o에 들어 있으며 이 파일을 Moon.o파일과 결합시키는 일을 하는 것이 바로 링커다. 링크 단계를 통해 Moon파일은 실행파일로 메모리에 적재되어 실행된다.
프로그래머가 컴파일 시스템이 어떻게 동작하는지 이해해야 하는 중요한 상황 3가지.(각 문제에 대한 자세한 답은 해당 챕터에서)
문제1) switch문은 if-else문을 연속해서 사용하는 것보다 언제나 효율적인가?, while루프는 for루프보다 효율적인가? (이에 대한 답은 3장, 5장, 6장)
문제2) 큰 규모의 소프트웨어 시스템을 빌드하는 경우 발생하는 링커가 어떤 참조를 풀어낼 수 없다고 할 때는 무슨 의미인가. 정적변수와 지역변수의 차이는 무엇인가?(답 7장)
문제3) 보안의 약점의 주요 원인인 버퍼 오버플로우는 프로그래머들이 신뢰할 수 없는 곳에서 획득한 데이터의 양과 형태를 제한하지 않기 때문에 발생한다. 안전한 프로그래밍은 무엇인가?(답 3장)
출처)컴퓨터 시스템(2016, 김형신 옮김)
메인 메모리 : 프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을 모두 저장하는 임시 저장장치.(DRAM으로 구성된다)
프로세서 : 주처리장치(CPU) 또는 간단히 프로세서는 메인메모리에 저장된 인스트럭션들을 해독하는 엔진이다. 프로세서의 중심에는 워드 크기의 저장장치(레지스터)인 프로그램 카운터(PC)가 있고, PC는 메인 메모리의 기계어 인스트럭션을 가리킨다.
우리 인텔 cpu님이 다음 같은 일을 하시는 것이다!!
워드(word) : 컴퓨터 설계 시 정해지는 메모리 기본 단위
적재(Load) : 메인 메모리에서 레지스터에 한 바이트 또는 워드를 이전 값에 덮어쓰는 방식으로 복사
저장(Store) : 레지스터에서 메인 메모리로 한 바이트 또는 워드를 이전 값을 덮어쓰는 방식으로 복사
작업(Operate) : 두 레지스터의 값을 ALU로 복사하고 두개의 워드로 수식연산을 수행한 뒤, 결과를 덮어쓰기 방식으로 레지스터에 저장
점프(Jump) : 인스트럭션 자신으로부터 한 개의 워드를 추출하고, 이것을 PC에 덮어쓰기 방식으로 복사
100층 갈 거야.. 갈거라고..(할아버지 또 이러신다!!)
캐시가 중요한 이유는 '오버헤드'(처리를 위해 들어가는 추가적 신호)때문이다.
위에서 작성한 Hello 프로그램의 기계어 인스트럭션들은 본래 하드디스크에 저장되어 있었고, 프로그램이 로딩되면 메인메모리로 복사된다. 프로세서가 프로그램을 실행할 때 인스트럭션들은 메인메모리에서 프로세서로 복사된다. 프로그래머 관점에서 보면, 이러한 여러 복사과정들이 프로그램의 실제 작업을 느리게 하는 오버헤드다.
캐시는 프로세서-메모리 간 격차에 대응하기 위해 고안된 것으로, 단기간에 필요로할 가능성이 높은 정보를 임시로 저장할 목적으로 사용된다.
작고 빠른 저장장치(캐시메모리), 크고 느린 장치(메인메모리)와 같이 저장장치들은 계층구조를 이룬다. 계층의 꼭대기에서부터 밑으로 갈수록 저장장치들은 느리고, 크고, 바이트 당 가격이 저렴해진다.
레지스터->캐시메모리->메인메모리->로컬디스크
중요)하나의 저장장치는 다음 하위레벨 저장장치의 캐시 역할을 한다!!!!!
응용프로그램이 하드웨어를 제어하려면 언제나 운영체제를 통해야한다.
운영체제는 두 가지 목적이 있다.
- 응용프로그램들이 하드웨어를 잘못 사용하는 것을 막기 위해
- 응용프로그램들이 단순하고 균일한 매커니즘을 사용하여 복잡하고 매우 다른 저수준 하드웨어 장치들을 조작할 수 있도록
프로세스 : 실행 중인 프로그램에 대한 운영체제의 추상화
프로세서가 프로세스들을 바꿔주는 방식으로 한 개의 CPU가 다수의 프로세스를 동시에 실행하는 것처럼 보이게 해준다.
문맥전환(Context switching)이라는 방법을 사용해서 이러한 교차실행을 수행
쓰레드 : 프로세스가 실제로 쓰레드라고 하는 다수의 실행 유닛으로 구성되어 있음. 각각의 쓰레드는 동일한 코드와 전역데이터를 공유함
가상메모리 : 각 프로세스들이 메인 메모리 전체를 독점적으로 사용하고 있는 것 같은 환상을 제공하는 추상화
컴퓨터 시스템은 응용프로그램을 실행하기 위해 동작하는 하드웨어와 시스템 소프트웨어로 구성된다. 컴퓨터 내의 정보는 상황에 따라 다르게 해석되는 비트들의 그룹으로 표시된다.
프로세서는 메인 메모리에 저장된 바이너리 인스트럭션을 읽고 해석한다. 컴퓨터는 대부분의 시간을 메모리, 입출력장치, CPU레지스터 간에 데이터를 복사하는데 시간을 쓰고 있으므로 저장장치들을 계층구조로 형성하고 있다. 계층구조 상부의 저장장치들은 하부 장치들을 위한 캐시 역할을 수행한다. 프로그래머들은 이러한 메모리 계층구조를 이해하고 활용하면 C프로그램의 성능을 최적화 할 수 있다.
운영체제 커널은 응용프로그램과 하드웨어 사이에서 중간자의 역할을 수행한다. 운영체제는 3가지 근본적인 추상화를 제공한다.
(1) 파일은 입출력장치의 추상화
(2) 가상메모리는 메인메모리와 디스크의 추상화
(3) 프로세스는 프로세서, 메인 메모리, 입출력 장치의 추상화.