2023-10-19-TIL
2023-10-19-TIL
Today I Studied
운영체제와 컴퓨터 구조의 기본 개념을 한꺼번에 훑었다. 함수 호출이 스택을 어떻게 쓰는지에서 시작해, 가상 메모리와 캐시, 하드웨어와 CPU의 상호작용 순서로 정리한다.
X Windows System
X Window System은 유닉스 계열 운영체제에서 흔히 쓰는 윈도 시스템이다. 1984년 MIT의 Project Athena에서 시작됐다. 특징은 처음부터 네트워크로 쓰도록 설계됐다는 점이다. 화면과 키보드, 마우스를 가진 쪽이 X 서버이고, 그 화면에 창을 그려 달라고 요청하는 애플리케이션이 X 클라이언트다. 그래서 원격 서버에서 실행한 프로그램의 창을 내 컴퓨터 화면에 띄울 수 있다. 보통 생각하는 클라이언트·서버와 방향이 반대라서 헷갈리기 쉽다.
- https://en.wikipedia.org/wiki/X_Window_System
Calling Convention
호출 규약은 함수가 인자를 어디로 받고 결과를 어디로 돌려주는지 정한 구현 수준의 약속이다. 인자를 레지스터로 넘길지 스택으로 넘길지, 스택은 호출한 쪽과 호출된 쪽 중 누가 정리할지 등을 정한다. 호출 규약은 ABI(Application Binary Interface)의 일부로, 호출하는 쪽과 호출되는 쪽 사이의 계약이다. 예를 들어 x86-64에서는 Windows가 쓰는 Microsoft 규약과 유닉스 계열이 쓰는 System V ABI 규약이 서로 다르다. 서로 다른 언어나 컴파일러로 만든 코드를 링크할 때 규약이 맞지 않으면 인자가 엉뚱한 값으로 읽힌다.
- https://en.wikipedia.org/wiki/Calling_convention
Function Prologue and Epilogue
위키백과에 따르면 프롤로그는 함수 시작 부분에서 스택과 레지스터를 함수가 쓸 수 있게 준비하는 몇 줄의 코드이고, 에필로그는 함수 끝에서 그 상태를 호출 전으로 되돌리는 코드다. 어셈블리 언어 자체의 기능이 아니라 컴파일러가 따르는 관례다. x86의 전형적인 형태는 이렇다.
1
2
3
4
5
6
7
push ebp ; 호출한 함수의 프레임 포인터 저장
mov ebp, esp ; 현재 스택 꼭대기를 새 프레임의 기준으로 삼음
sub esp, N ; 지역 변수 공간 확보
...
mov esp, ebp ; 지역 변수 공간 해제
pop ebp ; 이전 프레임 포인터 복원
ret ; 복귀 주소로 돌아감
- https://en.wikipedia.org/wiki/Function_prologue_and_epilogue
Stack Frame
스택 프레임은 함수 호출 하나가 콜 스택에 차지하는 영역이다. 복귀 주소, 이전 프레임 포인터, 인자 일부, 지역 변수가 들어간다. 콜 스택 문서는 콜 스택의 주된 목적을 각 함수가 끝났을 때 돌아갈 위치를 기억하는 것이라고 설명한다. 함수를 호출할 때마다 프레임이 쌓이고 반환할 때 사라지므로, 재귀가 너무 깊어지면 스택 공간이 바닥나 stack overflow가 난다.
- https://www.techopedia.com/definition/22304/stack-frame#:~:text=A%20stack%20frame%20is%20a,generating%20and%20eliminating%20temporary%20variables.
Interrupt
인터럽트는 실행 중인 코드를 잠시 멈추고 어떤 사건을 제때 처리해 달라는 요청이다. 요청이 받아들여지면 프로세서는 현재 상태를 저장하고 인터럽트 핸들러를 실행한 뒤 원래 코드로 돌아간다. 장치가 보내는 하드웨어 인터럽트와 명령어로 일으키는 소프트웨어 인터럽트(시스템 콜 등)로 나뉜다. CPU가 장치 상태를 계속 확인하는 폴링(polling)의 낭비를 없애려고 도입됐다.
『Linux Kernel Development』의 인터럽트 처리 구현 절은 리눅스에서의 경로를 따라간다. 장치가 버스로 인터럽트 컨트롤러에 신호를 보내고, 해당 라인이 마스킹되지 않았다면 컨트롤러가 프로세서에 전달한다. 프로세서는 인터럽트 라인마다 정해진 진입점으로 점프하고, 커널의 do_IRQ()가 인터럽트 번호를 확인해 등록된 핸들러를 실행한다.
- http://books.gigatux.nl/mirror/kerneldevelopment/0672327201/ch06lev1sec6.html
- https://www.elprocus.com/basics-of-interrupt-types-and-its-applications/
- https://medium.com/@mekaladahanayaka80/create-your-own-operating-system-9f102c14198b
Virtual Memory
가상 메모리는 프로그램이 쓰는 가상 주소를 운영체제와 하드웨어가 물리 주소로 대응시켜 주는 기법이다. 각 프로세스는 자기만의 연속된 주소 공간을 가진 것처럼 동작한다. 문서가 꼽는 이점은 애플리케이션이 공유 메모리를 직접 관리하지 않아도 되는 것, 라이브러리 메모리를 프로세스 간에 공유할 수 있는 것, 메모리 격리로 보안이 높아지는 것, 물리 메모리보다 큰 메모리를 쓰는 것처럼 보이게 하는 것이다.
- https://en.wikipedia.org/wiki/Virtual_memory
- https://www.geeksforgeeks.org/virtual-memory-in-operating-system/
- https://www.techtarget.com/searchstorage/definition/virtual-memory#:~:text=Virtual%20memory%20is%20a%20common,(RAM)%20to%20disk%20storage.
Paging in Operating System
페이징은 가상 주소 공간과 물리 메모리를 같은 크기의 블록(페이지와 페이지 프레임)으로 나누고, 페이지 테이블로 둘을 대응시키는 방식이다. 메모리 페이징 문서에 따르면 페이징 덕분에 프로그램이 쓰는 물리 메모리가 연속일 필요가 없어서 외부 단편화 문제를 피할 수 있다. 접근한 페이지가 물리 메모리에 없으면 페이지 폴트가 나고, 운영체제가 디스크에서 페이지를 읽어 와 프레임에 올린 뒤 실행을 이어 간다.
- https://www.geeksforgeeks.org/paging-in-operating-system/
- https://byjus.com/gate/paging-in-operating-system-notes/
Translation Lookaside Buffer (TLB)
페이징을 하면 메모리 접근마다 페이지 테이블을 먼저 읽어야 하므로 접근 횟수가 늘어난다. TLB는 최근의 가상 주소에서 물리 주소로의 변환 결과를 저장하는 캐시로, MMU의 일부다. 변환 결과가 TLB에 있으면(TLB hit) 바로 물리 주소를 얻고, 없으면(TLB miss) 페이지 테이블을 탐색한다.
- https://en.wikipedia.org/wiki/Translation_lookaside_buffer
AMD’s TLB Bug
TLB가 하드웨어 버그로 문제가 된 유명한 사례가 AMD Phenom이다. 위키백과의 AMD Phenom 문서에 따르면 출시 전에 드물게 시스템을 멈추게 하는 TLB 결함이 발견됐고, B2와 BA 스테핑까지의 프로세서가 영향을 받았다. BIOS와 소프트웨어 우회책은 TLB 기능을 꺼서 성능을 떨어뜨렸다. Super User 질문은 이 패치를 끄면 성능은 돌아오지만 시스템이 멈출 위험을 감수해야 하는 상황을 다룬다. TLB가 성능에 얼마나 중요한지 거꾸로 보여 주는 사례다.
- https://superuser.com/questions/231299/implications-of-disabling-the-amd-phenoms-tlb-patch
- https://www.anandtech.com/show/2477
Von Neumann vs Harvard Architecture
폰 노이만 구조는 명령어와 데이터를 같은 메모리에 두고 같은 버스로 가져온다. 그래서 명령어 인출과 데이터 접근을 동시에 할 수 없고, 이 병목을 폰 노이만 병목이라 부른다. 하버드 구조는 명령어 메모리와 데이터 메모리를 완전히 분리해 둘을 동시에 접근한다. 수정 하버드 구조 문서에 따르면 오늘날 하버드 구조라고 불리는 컴퓨터는 대부분 수정 하버드 구조다. 주소 공간은 하나로 두되 CPU 가까이의 캐시를 명령어 캐시와 데이터 캐시로 나누는 방식이 가장 흔하다.
- https://www.geeksforgeeks.org/difference-between-von-neumann-and-harvard-architecture/
- https://en.wikipedia.org/wiki/Modified_Harvard_architecture
Out-of-order Execution
비순차 실행은 프로세서가 명령어를 프로그램에 적힌 순서가 아니라 입력 데이터와 실행 유닛이 준비된 순서로 실행하는 방식이다. 앞 명령어가 메모리에서 데이터를 기다리는 동안 그와 무관한 뒤 명령어를 먼저 실행해 낭비되는 사이클을 줄인다. 결과는 원래 순서대로 반영하므로 프로그램 입장에서는 순서대로 실행된 것처럼 보인다.
- https://www.linkedin.com/advice/1/how-do-you-design-implement-efficient-out-of-order-execution#:~:text=Out%2Dof%2Dorder%20execution%20is,avoiding%20stalls%2C%20and%20hiding%20latency.
Write-back vs Write-through
캐시에 쓰기가 일어났을 때 원본 저장소에 언제 반영하느냐의 차이다. 캐시 문서의 정의는 이렇다.
- Write-through: 캐시와 원본 저장소에 동시에 쓴다. 단순하고 일관성이 좋지만 쓰기마다 느린 저장소에 접근한다.
- Write-back: 처음에는 캐시에만 쓰고, 그 블록이 다른 블록으로 교체되기 직전에 원본에 쓴다. 쓰기가 빠르지만 아직 반영되지 않은 블록(dirty)을 추적해야 하고, 그사이 장애가 나면 데이터를 잃을 수 있다.
CPU 캐시뿐 아니라 애플리케이션의 DB 앞 캐시 설계에서도 같은 선택지가 나온다.
- https://stackoverflow.com/questions/27087912/write-back-vs-write-through-caching#:~:text=Write%2Dthrough%3A%20Write%20is%20done,just%20before%20it%20is%20replaced.
Async Processing and DMA
DMA(Direct Memory Access)는 디스크 컨트롤러나 네트워크 카드 같은 장치가 CPU를 거치지 않고 메인 메모리에 직접 접근하는 기능이다. DMA가 없으면 CPU가 데이터 전송 내내 붙잡혀 있어야 한다. DMA가 있으면 CPU는 전송을 시작시킨 뒤 다른 일을 하고, 전송이 끝나면 DMA 컨트롤러가 인터럽트로 알린다. 하드웨어 수준의 비동기 처리이고, 위의 인터럽트와 짝을 이뤄 동작한다.
- https://www.sciencedirect.com/science/article/abs/pii/S0743731516302040
Redis Swap
“swap”이라는 같은 단어가 Redis와 리눅스에서 다른 뜻으로 쓰인다. Redis의 SWAPDB는 두 논리 데이터베이스를 맞바꾸는 명령이다. 문서에 따르면 실행 즉시 한 DB에 연결된 클라이언트는 다른 DB의 데이터를 보게 된다. 새 데이터를 다른 DB에 미리 채워 두고 한 번에 바꿔치기하는 용도로 쓸 수 있다. FLUSHALL은 모든 DB의 키를 지우는 명령이고, ASYNC 옵션을 주면 백그라운드에서 지운다. 반면 Red Hat 글이 다루는 swap은 아래의 운영체제 스왑 메모리다.
- https://redis.io/commands/swapdb/
- https://velog.io/@jinse/redis-key-%EB%AA%A8%EB%91%90-%EC%A7%80%EC%9A%B0%EA%B8%B0-flush-all
- https://redis.io/commands/flushall/
- https://www.redhat.com/sysadmin/clear-swap-linux
Swap Memory
스왑 메모리는 물리 메모리가 부족할 때 당장 쓰지 않는 페이지를 디스크의 스왑 영역으로 내보내는 것이다. 메모리 부족으로 프로세스가 죽는 것은 막지만, 디스크는 메모리보다 훨씬 느리므로 스왑이 잦으면 시스템 전체가 느려진다. Red Hat 글에 따르면 리눅스는 vm.swappiness 값(0에서 100, 기본 60)으로 얼마나 적극적으로 스왑할지 정한다. 값이 클수록 적극적으로 스왑한다. Redis처럼 모든 데이터를 메모리에 두는 프로그램은 페이지가 스왑으로 밀려나면 응답 시간이 크게 늘어나므로 스왑 사용량을 따로 지켜봐야 한다.
- https://ioflood.com/blog/what-is-swap-memory-exactly-swap-and-ram-explained/#:~:text=%E2%80%9CSwap%20memory%E2%80%9D%20in%20linux%20is,than%20your%20computer%20has%20available.
- https://ssue-dev.tistory.com/entry/Linux-Swap-Memory-%EC%8A%A4%EC%99%91-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%84%A4%EC%A0%95-%EB%B0%A9%EB%B2%95
DRAM Row Memory Addressing
별찍기도 특정 모양으로 찍을때 일정버전 이하의 CPU에서는 느리다고 함.
같은 양의 데이터를 읽어도 순서에 따라 속도가 다를 수 있다는 이야기다. DRAM 문서에 따르면 DRAM은 행(row)을 먼저 열어 그 행 전체를 감지 증폭기에 읽어 둔다. 그 뒤로는 같은 행의 다른 열을 행을 다시 여는 지연 없이 읽을 수 있다. 그래서 메모리를 연속으로 읽으면 빠르고, 여기저기 건너뛰면 행을 자주 새로 열어야 해서 느려진다. CPU 캐시도 연속된 데이터를 한 줄(cache line) 단위로 가져오므로 같은 방향으로 작용한다.
행 우선·열 우선 순서 문서는 C처럼 행 우선으로 저장하는 언어에서 배열을 열 방향으로 순회하면, 현대 CPU가 연속 데이터를 더 효율적으로 처리하기 때문에 느려진다고 설명한다. 별찍기처럼 2차원 출력을 만들 때도 버퍼를 어떤 순서로 채우느냐에 따라 같은 원리가 적용된다.
- https://www.wrapuppro.com/programing/view/iaU1cesj6Uynm3c
- https://www.educba.com/star-patterns-in-python/
- https://www.geeksforgeeks.org/printing-triangle-star-pattern-using-a-single-loop/
댓글
아직 댓글이 없습니다