지난 팀원들간 코어 시간을 보내며 궁금했던 점을 정리해보았다.
'스택 가드'는 이 기술의 초기 구현체 이름이었고, 지금은 '스택 카나리'가 이 기술 자체를 가리키는 일반적인 용어로 더 널리 쓰입니다.
이 기술의 이름은 과거 광부들이 유독가스를 탐지하기 위해 탄광에 카나리아 새를 데리고 들어간 것에서 유래했습니다. 카나리아는 사람보다 유독가스에 민감해서, 만약 새가 죽으면 광부들은 위험을 감지하고 즉시 대피했습니다.
스택 가드는 컴파일러가 프로그램을 만들 때, 함수가 시작되고 끝나는 지점에 특별한 코드를 추가하여 작동합니다.
함수가 호출되면, 지역 변수(버퍼)와 반환 주소(Return Address)라는 매우 중요한 데이터 사이에 '카나리'라고 불리는 무작위 값을 몰래 숨겨둡니다.
스택 메모리 구조
반환 주소 (공격자의 최종 목표)
카나리 값 (비밀 감시병) ⬅️ 여기에 숨겨둠
버퍼 (공격 경로)
해커가 버퍼의 크기를 초과하는 악의적인 데이터를 보내면, 이 데이터는 버퍼를 넘어 주변 메모리를 덮어쓰기 시작합니다. 이때 공격자가 최종 목표인 '반환 주소'에 도달하기 전에 반드시 '카나리' 값을 먼저 덮어쓰게 됩니다.
함수가 종료되어 원래 위치로 돌아가기 직전, 컴파일러가 추가해 둔 코드가 스택에 저장된 카나리 값이 원래의 값과 일치하는지 검사합니다.
스택 오버플로우(Stack Overflow)는 한정된 크기의 접시 보관통(스택 메모리)에 너무 많은 접시(함수 호출 정보)를 쌓아 더 이상 공간이 없을 때 발생하는 에러입니다. 이는 주로 재귀 함수(Recursive function)를 잘못 사용했을 때 발생하며, 프로그램이 비정상적으로 종료되는 원인이 됩니다.
컴퓨터 메모리에는 스택(Stack)이라는 특별한 영역이 있습니다. 이 공간은 함수의 호출을 관리하기 위해 사용됩니다.
스택 메모리의 크기는 한정되어 있습니다. 스택 오버플로우는 이 한정된 공간이 가득 찰 때까지 스택 프레임이 계속 쌓일 때 발생합니다.
가장 흔한 원인은 '무한 재귀' 또는 '너무 깊은 재귀'입니다.
재귀 함수는 자기 자신을 계속해서 호출하는데, 호출이 멈추는 지점(탈출 조건, base case)이 없으면 함수가 끝나지 않고 무한정 스택 프레임을 쌓게 됩니다.
// 무한 재귀를 일으키는 함수 예시
void overflow_function() {
// 탈출 조건 없이 자기 자신을 계속 호출한다.
overflow_function();
}
int main() {
overflow_function(); // 이 함수를 실행하면 스택 오버플로우 발생
return 0;
}
위 코드에서 overflow_function()이 호출될 때마다 스택에 프레임이 쌓이고, 이 함수는 자기 자신을 또 호출하여 프레임을 계속 쌓습니다. 결국 스택에 할당된 메모리 공간을 모두 소진하면, 운영체제는 프로그램을 강제로 종료시키며 "Stack Overflow" 에러를 발생시킵니다.
두 용어는 비슷해 보이지만 완전히 다른 문제입니다.
| 구분 | 버퍼 오버플로우 (Buffer Overflow) | 스택 오버플로우 (Stack Overflow) |
|---|---|---|
| 원인 | 하나의 버퍼에 정해진 크기보다 큰 데이터를 복사 | 너무 많은 함수 호출로 스택 공간 전체를 소진 |
| 결과 | 메모리 변조, 악성 코드 실행 가능 | 프로그램 비정상 종료 (Crash) |
| 종류 | 보안 취약점 | 런타임 에러 |
미리 컴파일된 C언어들이 내부에 존재하며 필요시 해당 부분이 실행된다.
.c: C 소스 코드 파일사람이 C언어 문법에 맞게 작성한 원본 텍스트 파일이에요. 프로그래머가 편집기에서 직접 코드를 작성하고 저장하는 바로 그 파일이죠.
.i: 전처리된 C 소스 코드 파일#include나 #define 같은 전처리 지시문이 모두 처리된 후의 깨끗한 C 소스 코드 파일이에요. 여전히 사람이 읽을 수 있는 텍스트 형태죠. 예를 들어, #include <stdio.h>가 있다면 그 자리에 stdio.h 파일의 내용 전체가 복사되어 들어가요.
.s: 어셈블리어 파일컴파일러가 .i 파일을 번역하여 만든 어셈블리어 코드 파일이에요. 기계어와 일대일로 대응되지만, 아직 사람이 읽을 수 있는 텍스트 형태를 유지하고 있어요. (예: mov, add, jmp)
.o: 오브젝트 파일어셈블러가 어셈블리어 파일(.s)을 실제 컴퓨터가 이해할 수 있는 0과 1의 기계어로 번역한 이진 파일이에요. 이 파일은 완전한 프로그램이 아니라, printf 같은 외부 함수의 연결 정보가 빠져있는 미완성된 코드 조각이죠.
FF D8 FF E0 또는 FF D8 FF DB89 50 4E 47 (.PNG의 ASCII 값)47 49 46 38 (GIF8의 ASCII 값)42 4D (BM의 ASCII 값)25 50 44 46 (%PDF의 ASCII 값)50 4B 03 04 (압축 파일(PKZip) 형식이라서 압축 파일 시그니처와 동일합니다.)D0 CF 11 E0 A1 B1 1A E14D 5A (MZ의 ASCII 값)7F 45 4C 46 (.ELF의 ASCII 값)50 4B 03 0452 61 72 21 (Rar!의 ASCII 값)이 모든 과정은 사람이 인지할 수 없을 정도로 빠르게, 거의 실시간으로 일어납니다. 따라서 컨트롤러의 데이터가 커널로 '가는 시점'은 키를 누르는 바로 그 순간이라고 생각하시면 됩니다.
DMA는 대량의 데이터를 빠르게 전송하여 CPU의 부담을 줄일 때 의미가 있습니다.
키보드 입력은 다음과 같은 특징 때문에 DMA를 사용할 필요가 없습니다.
결론적으로, DMA는 디스크, 네트워크 카드, 그래픽 카드처럼 한 번에 수백 KB 이상의 큰 데이터를 옮기는 작업에 사용되고, 키보드처럼 데이터 양이 적고 빈번한 입력은 CPU가 인터럽트를 통해 직접 처리하는 것이 훨씬 효율적입니다.
hello 프로그램이 실행되고 메시지를 출력할 때, 프로그램이 직접 키보드, 디스플레이, 디스크 같은 하드웨어를 제어한 것이 아니라 운영체제(Operating System, OS)가 제공하는 서비스를 이용했습니다.
운영체제는 응용 프로그램과 하드웨어 사이에 위치하는 소프트웨어 계층으로 생각할 수 있습니다. 응용 프로그램이 하드웨어를 조작하려는 모든 시도는 반드시 운영체제를 거쳐야 합니다.
운영체제의 주요 목적은 두 가지입니다.
운영체제는 이 두 가지 목표를 달성하기 위해 프로세스(Process), 가상 메모리(Virtual Memory), 파일(File)이라는 세 가지 핵심적인 추상화(Abstraction) 개념을 사용합니다.

현대 시스템에서 hello 같은 프로그램이 실행될 때, 운영체제는 마치 시스템에 이 프로그램 하나만 실행되고 있는 것 같은 착각을 제공합니다. 프로그램은 프로세서, 주기억장치, 입출력 장치를 독점적으로 사용하는 것처럼 보입니다.
이러한 환상은 컴퓨터 과학의 가장 중요한 개념 중 하나인 프로세스를 통해 제공됩니다.

.text): 읽기 전용으로 '공유'프로그램의 실행 명령어들이 담긴 코드 영역은 실행 중에 내용이 바뀌지 않는 읽기 전용(Read-only)입니다. 따라서 같은 프로그램을 실행하는 여러 프로세스가 이 영역을 각자 메모리에 둘 필요가 없습니다.
.data, .bss): '쓰기 시 복사(Copy-on-Write)'전역 변수나 정적 변수 등이 담긴 데이터 영역은 프로세스마다 고유한 값을 가질 수 있으므로 독립적으로 관리되어야 합니다. 하지만 프로세스를 생성할 때마다 모든 데이터 영역을 무조건 복사하는 것은 비효율적일 수 있습니다.
fork() 시스템 콜)하면, 운영체제는 일단 데이터 영역을 복사하지 않고 부모 프로세스의 데이터 영역을 함께 가리키도록 합니다. 단, 이 공유된 페이지를 '읽기 전용'으로 표시해 둡니다.| 구분 | 코드 영역 (.text) | 데이터 영역 (.data) |
|---|---|---|
| 관리 방식 | 공유 (Sharing) | 쓰기 시 복사 (Copy-on-Write) |
| 이유 | 내용이 변하지 않으므로, 메모리 절약을 위해 | 프로세스별로 내용이 바뀔 수 있으므로, 독립성을 보장하기 위해 |
| 비유 | 도서관 참고서적 (모두가 함께 봄) | 공용 양식 파일 (수정 시 사본 생성) |
페이지 폴트(Page Fault)란 프로그램이 사용하려는 데이터가 물리 메모리(RAM)에 없어 CPU가 실행을 중단하고 운영체제에 도움을 요청하는 일종의 인터럽트입니다. 이는 오류가 아니라, 가상 메모리 시스템이 작동하기 위한 정상적인 과정 중 하나입니다.
가상 메모리 시스템에서 모든 데이터가 항상 RAM에 올라와 있는 것은 비효율적입니다. 따라서 운영체제는 당장 필요하지 않은 데이터는 디스크에 내려놓고, RAM에는 자주 쓰는 데이터만 올려놓습니다.
비유: 도서관에서 공부하기 📖
페이지 폴트가 발생하는 과정은 다음과 같습니다.
하나의 프로세스는 전통적으로 하나의 실행 흐름을 갖지만, 현대 시스템에서는 스레드(Thread)라는 여러 개의 실행 단위로 구성될 수 있습니다. 한 프로세스 내의 스레드들은 코드와 전역 데이터를 공유하며, 프로세스보다 더 효율적으로 동작하기 때문에 동시성 프로그래밍에서 매우 중요합니다.
가상 메모리는 각 프로세스가 주기억장치(RAM) 전체를 독점적으로 사용하는 것 같은 환상을 제공하는 추상화 개념입니다. 각 프로세스는 가상 주소 공간(Virtual Address Space)이라는 동일하고 독립적인 메모리 구조를 갖게 됩니다.
실제로는 디스크에 각 프로세스의 메모리 내용을 저장해두고, 주기억장치를 디스크의 캐시처럼 사용하여 이런 환상을 만들어냅니다. 가상 주소 공간은 다음과 같이 여러 영역으로 나뉩니다.
malloc이나 free 함수 호출을 통해 동적으로 크기가 변하는 메모리 영역입니다.
파일은 단순히 바이트(byte)의 연속입니다. 디스크, 키보드, 디스플레이, 네트워크 등 모든 입출력(I/O) 장치는 파일이라는 개념으로 모델링됩니다. 시스템의 모든 입출력은 파일을 읽고 쓰는 방식으로 수행됩니다.
이 단순한 '파일'이라는 개념은 응용 프로그램에게 모든 종류의 입출력 장치를 통일된 방식으로 바라볼 수 있게 해주는 매우 강력한 추상화입니다. 예를 들어, 프로그래머는 디스크 기술의 종류와 상관없이 동일한 방식으로 파일에 접근하는 코드를 작성할 수 있습니다.
지금까지 시스템을 독립된 하드웨어와 소프트웨어의 집합으로 다루었지만, 실제 현대 시스템들은 네트워크를 통해 다른 시스템들과 연결되어 있습니다.
개별 시스템의 관점에서 보면 네트워크는 또 하나의 입출력(I/O) 장치로 볼 수 있습니다. 시스템이 주기억장치에서 네트워크 어댑터로 데이터를 복사하면, 그 데이터는 로컬 디스크가 아닌 네트워크를 통해 다른 컴퓨터로 흘러갑니다. 반대로 다른 컴퓨터에서 보낸 데이터를 읽어 자신의 주기억장치로 복사할 수도 있습니다.
인터넷과 같은 글로벌 네트워크의 출현으로, 컴퓨터 간에 정보를 복사하는 것은 시스템의 가장 중요한 용도 중 하나가 되었습니다. 이메일, 웹, FTP 등 우리에게 익숙한 응용 프로그램들은 모두 네트워크를 통해 정보를 복사하는 능력에 기반하고 있습니다.
hello 프로그램 실행하기우리가 사용했던 hello 예제를 원격 컴퓨터에서 실행하기 위해 텔넷(telnet)과 같은 네트워크 응용 프로그램을 사용할 수 있습니다.
로컬 컴퓨터의 텔넷 클라이언트(client)를 사용하여 원격 컴퓨터의 텔넷 서버(server)에 접속했다고 가정해 봅시다. 원격 컴퓨터에 로그인하고 셸을 실행하면, 원격 셸은 명령어 입력을 기다립니다. 여기서부터 hello 프로그램을 원격으로 실행하는 과정은 다음 5단계로 이루어집니다.
hello라는 문자열을 입력하고 엔터 키를 누릅니다.hello 프로그램을 실행하고, 그 결과물(hello, world\n)을 다시 텔넷 서버로 넘겨줍니다.이러한 클라이언트와 서버 간의 데이터 교환은 모든 네트워크 응용 프로그램의 전형적인 동작 방식입니다.
시스템은 응용 프로그램을 실행하는 궁극적인 목표를 달성하기 위해 하드웨어와 시스템 소프트웨어가 서로 얽혀 협력하는 집합체입니다.
암달의 법칙은 시스템의 한 부분의 성능을 개선했을 때 전체 시스템 성능에 미치는 영향에 대한 단순하지만 통찰력 있는 관찰입니다. 핵심 아이디어는 특정 부분의 개선 효과가 그 부분이 원래 얼마나 중요했는지(비중)와 얼마나 빨라졌는지(개선율)에 따라 결정된다는 것입니다.



컴퓨터의 발전은 '더 많은 일을' 그리고 '더 빠르게'라는 두 가지 요구에 의해 주도되어 왔습니다. 이 두 가지는 프로세서가 한 번에 더 많은 일을 처리할 때 향상됩니다.
병렬성은 시스템의 여러 수준에서 활용될 수 있습니다.
여러 프로그램이나 한 프로그램 내의 여러 스레드가 동시에 실행되는 것을 의미합니다.
하이퍼스레딩은 인텔(Intel) CPU에 적용된 하드웨어 스레딩 기술의 상표명입니다.
하나의 코어 안에는 연산 장치, 레지스터, 캐시 등 여러 구성 요소가 있습니다. 하이퍼스레딩은 이 중에서 레지스터와 프로그램 카운터(PC)처럼 각 스레드가 독립적으로 사용해야 하는 일부 요소만 복제하고, 산술논리연산장치(ALU)처럼 공유해서 쓸 수 있는 핵심 자원은 하나만 둡니다.
이를 통해 하나의 스레드가 데이터를 기다리는 등 잠시 멈추는 순간에, 다른 스레드가 그 자원을 즉시 사용하여 코어의 자원 활용률을 극대화하고 전체적인 성능을 높이는 원리입니다.
결론적으로, 하이퍼스레딩은 하드웨어 스레딩 기술의 한 종류이며, 가장 널리 알려진 상표명이라고 생각하시면 됩니다.

현대 프로세서는 한 번에 여러 개의 명령어를 실행할 수 있습니다.
하나의 명령어로 여러 개의 데이터에 대해 동일한 연산을 병렬로 수행하는 방식입니다. 주로 이미지, 사운드, 비디오 데이터 처리 속도를 높이는 데 사용됩니다. (예: 8쌍의 실수를 한 번에 더하는 명령어)