1장 프로그래밍 언어부터 프로그램 실행까지, 이렇게 진행된다
1.1 여러분이 프로그래밍 언어를 발명한다면?
__1.1.1 창세기: CPU는 똑똑한 바보
__1.1.2 어셈블리어 등장
__1.1.3 저수준 계층의 세부 사항 대 고수준 계층의 추상화
__1.1.4 가득한 규칙: 고급 프로그래밍 언어의 시작
__1.1.5 〈인셉션〉과 재귀: 코드 본질
__1.1.6 컴퓨터가 재귀를 이해하도록 만들기
__1.1.7 우수한 번역가: 컴파일러
__1.1.8 해석형 언어의 탄생
1.2 컴파일러는 어떻게 작동하는 것일까?
__1.2.1 컴파일러는 그저 일반적인 프로그램일 뿐, 대단하지 않다
__1.2.2 각각의 토큰 추출하기
__1.2.3 토큰이 표현하고자 하는 의미
__1.2.4 생성된 구문 트리에 이상은 없을까?
__1.2.5 구문 트리를 기반으로 중간 코드 생성하기
__1.2.6 코드 생성
1.3 링커의 말할 수 없는 비밀
__1.3.1 링커는 이렇게 일한다
__1.3.2 심벌 해석: 수요와 공급
__1.3.3 정적 라이브러리, 동적 라이브러리, 실행 파일
__1.3.4 동적 라이브러리의 장단점
__1.3.5 재배치: 심벌의 실행 시 주소 결정하기
__1.3.6 가상 메모리와 프로그램 메모리 구조
1.4 컴퓨터 과학에서 추상화가 중요한 이유
__1.4.1 프로그래밍과 추상화
__1.4.2 시스템 설계와 추상화
1.5 요약
2장 프로그램이 실행되었지만, 뭐가 뭔지 하나도 모르겠다
2.1 운영 체제, 프로세스, 스레드의 근본 이해하기
__2.1.1 모든 것은 CPU에서 시작된다
__2.1.2 CPU에서 운영 체제까지
__2.1.3 프로세스는 매우 훌륭하지만, 아직 불편하다
__2.1.4 프로세스에서 스레드로 진화
__2.1.5 다중 스레드와 메모리 구조
__2.1.6 스레드 활용 예
__2.1.7 스레드 풀의 동작 방식
__2.1.8 스레드 풀의 스레드 수
2.2 스레드 간 공유되는 프로세스 리소스
__2.2.1 스레드 전용 리소스
__2.2.2 코드 영역: 모든 함수를 스레드에 배치하여 실행할 수 있다
__2.2.3 데이터 영역: 모든 스레드가 데이터 영역의 변수에 접근할 수 있다
__2.2.4 힙 영역: 포인터가 핵심이다
__2.2.5 스택 영역: 공유 공간 내 전용 데이터
__2.2.6 동적 링크 라이브러리와 파일
__2.2.7 스레드 전용 저장소
2.3 스레드 안전 코드는 도대체 어떻게 작성해야 할까?
__2.3.1 자유와 제약
__2.3.2 스레드 안전이란 무엇일까?
__2.3.3 스레드 전용 리소스와 공유 리소스
__2.3.4 스레드 전용 리소스만 사용하기
__2.3.5 스레드 전용 리소스와 함수 매개변수
__2.3.6 전역 변수 사용
__2.3.7 스레드 전용 저장소
__2.3.8 함수 반환값
__2.3.9 스레드 안전이 아닌 코드 호출하기
__2.3.10 스레드 안전 코드는 어떻게 구현할까?
2.4 프로그래머는 코루틴을 어떻게 이해해야 할까?
__2.4.1 일반 함수
__2.4.2 일반 함수에서 코루틴으로
__2.4.3 직관적인 코루틴 설명
__2.4.4 함수는 그저 코루틴의 특별한 예에 불과하다
__2.4.5 코루틴의 역사
__2.4.6 코루틴은 어떻게 구현될까?
2.5 콜백 함수를 철저하게 이해한다
__2.5.1 모든 것은 다음 요구에서 시작된다
__2.5.2 콜백이 필요한 이유
__2.5.3 비동기 콜백
__2.5.4 비동기 콜백은 새로운 프로그래밍 사고방식으로 이어진다
__2.5.5 콜백 함수의 정의
__2.5.6 두 가지 콜백 유형
__2.5.7 비동기 콜백의 문제: 콜백 지옥
2.6 동기와 비동기를 철저하게 이해한다
__2.6.1 고된 프로그래머
__2.6.2 전화 통화와 이메일 보내기
__2.6.3 동기 호출
__2.6.4 비동기 호출
__2.6.5 웹 서버에서 동기와 비동기 작업
2.7 아 맞다! 블로킹과 논블로킹도 있다
__2.7.1 블로킹과 논블로킹
__2.7.2 블로킹의 핵심 문제: 입출력
__2.7.3 논블로킹과 비동기 입출력
__2.7.4 피자 주문에 비유하기
__2.7.5 동기와 블로킹
__2.7.6 비동기와 논블로킹
2.8 높은 동시성과 고성능을 갖춘 서버 구현
__2.8.1 다중 프로세스
__2.8.2 다중 스레드
__2.8.3 이벤트 순환과 이벤트 구동
__2.8.4 첫 번째 문제: 이벤트 소스와 입출력 다중화
__2.8.5 두 번째 문제: 이벤트 순환과 다중 스레드
__2.8.6 카페는 어떻게 운영되는가: 반응자 패턴
__2.8.7 이벤트 순환과 입출력
__2.8.8 비동기와 콜백 함수
__2.8.9 코루틴: 동기 방식의 비동기 프로그래밍
__2.8.10 CPU, 스레드, 코루틴
2.9 컴퓨터 시스템 여행: 데이터, 코드, 콜백, 클로저에서 컨테이너, 가상 머신까지
__2.9.1 코드, 데이터, 변수, 포인터
__2.9.2 콜백 함수와 클로저
__2.9.3 컨테이너와 가상 머신 기술
2.10 요약
3장 저수준 계층? 메모리라는 사물함에서부터 시작해 보자
3.1 메모리의 본질, 포인터와 참조
__3.1.1 메모리의 본질은 무엇일까? 사물함, 비트, 바이트, 객체
__3.1.2 메모리에서 변수로: 변수의 의미
__3.1.3 변수에서 포인터로: 포인터 이해하기
__3.1.4 포인터의 힘과 파괴력: 능력과 책임
__3.1.5 포인터에서 참조로: 메모리 주소 감추기
3.2 프로세스는 메모리 안에서 어떤 모습을 하고 있을까?
__3.2.1 가상 메모리: 눈에 보이는 것이 항상 실제와 같지는 않다
__3.2.2 페이지와 페이지 테이블: 가상에서 현실로
3.3 스택 영역: 함수 호출은 어떻게 구현될까?
__3.3.1 프로그래머를 위한 도우미: 함수
__3.3.2 함수 호출 활동 추적하기: 스택
__3.3.3 스택 프레임 및 스택 영역: 거시적 관점
__3.3.4 함수 점프와 반환은 어떻게 구현될까?
__3.3.5 매개변수 전달과 반환값은 어떻게 구현될까?
__3.3.6 지역 변수는 어디에 있을까?
__3.3.7 레지스터의 저장과 복원
__3.3.8 큰 그림을 그려 보자, 우리는 지금 어디에 있을까?
3.4 힙 영역: 메모리의 동적 할당은 어떻게 구현될까?
__3.4.1 힙 영역이 필요한 이유
__3.4.2 malloc 메모리 할당자 직접 구현하기
__3.4.3 주차장에서 메모리 관리까지
__3.4.4 여유 메모리 조각 관리하기
__3.4.5 메모리 할당 상태 추적하기
__3.4.6 어떻게 여유 메모리 조각을 선택할 것인가: 할당 전략
__3.4.7 메모리 할당하기
__3.4.8 메모리 해제하기
__3.4.9 여유 메모리 조각을 효율적으로 병합하기
3.5 메모리를 할당할 때 저수준 계층에서 일어나는 일
__3.5.1 천지인과 CPU 실행 상태
__3.5.2 커널 상태와 사용자 상태
__3.5.3 포털: 시스템 호출
__3.5.4 표준 라이브러리: 시스템의 차이를 감춘다
__3.5.5 힙 영역의 메모리가 부족할 때
__3.5.6 운영 체제에 메모리 요청하기: brk
__3.5.7 빙산의 아래: 가상 메모리가 최종 보스다
__3.5.8 메모리 할당의 전체 이야기
3.6 고성능 서버의 메모리 풀은 어떻게 구현될까?
__3.6.1 메모리 풀 대 범용 메모리 할당자
__3.6.2 메모리 풀 기술의 원리
__3.6.3 초간단 메모리 풀 구현하기
__3.6.4 약간 더 복잡한 메모리 풀 구현하기
__3.6.5 메모리 풀의 스레드 안전 문제
3.7 대표적인 메모리 관련 버그
__3.7.1 지역 변수의 포인터 반환하기
__3.7.2 포인터 연산의 잘못된 이해
__3.7.3 문제 있는 포인터 역참조하기
__3.7.4 초기화되지 않은 메모리 읽기
__3.7.5 이미 해제된 메모리 참조하기
__3.7.6 배열 첨자는 0부터 시작한다
__3.7.7 스택 넘침
__3.7.8 메모리 누수
3.8 왜 SSD는 메모리로 사용할 수 없을까?
__3.8.1 메모리 읽기/쓰기와 디스크 읽기/쓰기의 차이
__3.8.2 가상 메모리의 제한
__3.8.3 SSD 사용 수명 문제
3.9 요약
4장 트랜지스터에서 CPU로, 이보다 더 중요한 것은 없다
4.1 이 작은 장난감을 CPU라고 부른다
__4.1.1 위대한 발명
__4.1.2 논리곱, 논리합, 논리부정
__4.1.3 도는 하나를 낳고, 하나는 둘을 낳고, 둘은 셋을 낳으며, 셋은 만물을 낳는다
__4.1.4 연산 능력은 어디에서 나올까?
__4.1.5 신기한 기억 능력
__4.1.6 레지스터와 메모리의 탄생
__4.1.7 하드웨어 아니면 소프트웨어? 범용 장치
__4.1.8 하드웨어의 기본 기술: 기계 명령
__4.1.9 소프트웨어와 하드웨어 간 인터페이스: 명령어 집합
__4.1.10 회로에는 지휘자가 필요하다
__4.1.11 큰일을 해냈다, CPU가 탄생했다!
4.2 CPU는 유휴 상태일 때 무엇을 할까?
__4.2.1 컴퓨터의 CPU 사용률은 얼마인가?
__4.2.2 프로세스 관리와 스케줄링
__4.2.3 대기열 상태 확인: 더 나은 설계
__4.2.4 모든 것은 CPU로 돌아온다
__4.2.5 유휴 프로세스와 CPU의 저전력 상태
__4.2.6 무한 순환 탈출: 인터럽트
4.3 CPU는 숫자를 어떻게 인식할까?
__4.3.1 숫자 0과 양의 정수
__4.3.2 부호 있는 정수
__4.3.3 양수에 음수 기호를 붙이면 바로 대응하는 음수: 부호-크기 표현
__4.3.4 부호-크기 표현의 반전: 1의 보수
__4.3.5 간단하지 않은 두 수 더하기
__4.3.6 컴퓨터 친화적 표현 방식: 2의 보수
__4.3.7 CPU는 정말 숫자를 알고 있을까?
4.4 CPU가 if 문을 만났을 때
__4.4.1 파이프라인 기술의 탄생
__4.4.2 CPU: 메가팩토리와 파이프라인
__4.4.3 if가 파이프라인을 만나면
__4.4.4 분기 예측: 가능한 한 CPU가 올바르게 추측하도록
4.5 CPU 코어 수와 스레드 수 사이의 관계는 무엇일까?
__4.5.1 레시피와 코드, 볶음 요리와 스레드
__4.5.2 작업 분할과 블로킹 입출력
__4.5.3 다중 코어와 다중 스레드
4.6 CPU 진화론(상): 복잡 명령어 집합의 탄생
__4.6.1 프로그래머의 눈에 보이는 CPU
__4.6.2 CPU의 능력 범위: 명령어 집합
__4.6.3 추상화: 적을수록 좋다
__4.6.4 코드도 저장 공간을 차지한다
__4.6.5 필연적인 복잡 명령어 집합의 탄생
__4.6.6 마이크로코드 설계의 문제점
4.7 CPU 진화론(중): 축소 명령어 집합의 탄생
__4.7.1 복잡함을 단순함으로
__4.7.2 축소 명령어 집합의 철학
__4.7.3 복잡 명령어 집합과 축소 명령어 집합의 차이
__4.7.4 명령어 파이프라인
__4.7.5 천하에 명성을 떨치다
4.8 CPU 진화론(하): 절체절명의 위기에서 반격
__4.8.1 이길 수 없다면 함께하라: RISC와 동일한 CISC
__4.8.2 하이퍼스레딩이라는 필살기
__4.8.3 장점은 취하고 약점은 보완하다: CISC와 RISC의 통합
__4.8.4 기술이 전부는 아니다: CISC와 RISC 간 상업적 전쟁
4.9 CPU, 스택과 함수 호출, 시스템 호출, 스레드 전환, 인터럽트 처리 통달하기
__4.9.1 레지스터
__4.9.2 스택 포인터
__4.9.3 명령어 주소 레지스터
__4.9.4 상태 레지스터
__4.9.5 상황 정보
__4.9.6 중첩과 스택
__4.9.7 함수 호출과 실행 시간 스택
__4.9.8 시스템 호출과 커널 상태 스택
__4.9.9 인터럽트와 인터럽트 함수 스택
__4.9.10 스레드 전환과 커널 상태 스택
4.10 요약
5장 작은 것으로 큰 성과 이루기, 캐시
5.1 캐시, 어디에나 존재하는 것
__5.1.1 CPU와 메모리의 속도 차이
__5.1.2 도서관, 책상, 캐시
__5.1.3 공짜 점심은 없다: 캐시 갱신
__5.1.4 세상에 공짜 저녁은 없다: 다중 코어 캐시의 일관성
__5.1.5 메모리를 디스크의 캐시로 활용하기
__5.1.6 가상 메모리와 디스크
__5.1.7 CPU는 어떻게 메모리를 읽을까?
__5.1.8 분산 저장 지원
5.2 어떻게 캐시 친화적인 프로그램을 작성할까?
__5.2.1 프로그램 지역성의 원칙
__5.2.2 메모리 풀 사용
__5.2.3 struct 구조체 재배치
__5.2.4 핫 데이터와 콜드 데이터의 분리
__5.2.5 캐시 친화적인 데이터 구조
__5.2.6 다차원 배열 순회
5.3 다중 스레드 성능 방해자
__5.3.1 캐시와 메모리 상호 작용의 기본 단위: 캐시 라인
__5.3.2 첫 번째 성능 방해자: 캐시 튕김 문제
__5.3.3 두 번째 성능 방해자: 거짓 공유 문제
5.4 봉화희제후와 메모리 장벽
__5.4.1 명령어의 비순차적 실행: 컴파일러와 OoOE
__5.4.2 캐시도 고려해야 한다
__5.4.3 네 가지 메모리 장벽 유형
__5.4.4 획득-해제 의미론
__5.4.5 C++에서 제공하는 인터페이스
__5.4.6 다른 CPU, 다른 천성
__5.4.7 누가 명령어 재정렬에 관심을 가져야 하는가: 잠금 없는 프로그래밍
__5.4.8 잠금 프로그래밍과 잠금 없는 프로그래밍
__5.4.9 명령어 재정렬에 대한 논쟁
5.5 요약
6장 입출력이 없는 컴퓨터가 있을까?
6.1 CPU는 어떻게 입출력 작업을 처리할까?
__6.1.1 전문적으로 처리하기: 입출력 기계 명령어
__6.1.2 메모리 사상 입출력
__6.1.3 CPU가 키보드를 읽고 쓰는 것의 본질
__6.1.4 폴링: 계속 검사하기
__6.1.5 배달 음식 주문과 중단 처리
__6.1.6 인터럽트 구동식 입출력
__6.1.7 CPU는 어떻게 인터럽트 신호를 감지할까?
__6.1.8 인터럽트 처리와 함수 호출의 차이
__6.1.9 중단된 프로그램의 실행 상태 저장과 복원
6.2 디스크가 입출력을 처리할 때 CPU가 하는 일은 무엇일까?
__6.2.1 장치 제어기
__6.2.2 CPU가 직접 데이터를 복사해야 할까?
__6.2.3 직접 메모리 접근
__6.2.4 전 과정 정리
__6.2.5 프로그래머에게 시사하는 것
6.3 파일을 읽을 때 프로그램에는 어떤 일이 발생할까?
__6.3.1 메모리 관점에서 입출력
__6.3.2 read 함수는 어떻게 파일을 읽는 것일까?
6.4 높은 동시성의 비결: 입출력 다중화
__6.4.1 파일 서술자
__6.4.2 다중 입출력을 어떻게 효율적으로 처리하는 것일까?
__6.4.3 상대방이 아닌 내가 전화하게 만들기
__6.4.4 입출력 다중화
__6.4.5 삼총사: select, poll, epoll
6.5 mmap: 메모리 읽기와 쓰기 방식으로 파일 처리하기
__6.5.1 파일과 가상 메모리
__6.5.2 마술사 운영 체제
__6.5.3 mmap 대 전통적인 read/write 함수
__6.5.4 큰 파일 처리
__6.5.5 동적 링크 라이브러리와 공유 메모리
__6.5.6 mmap 직접 조작하기
6.6 컴퓨터 시스템의 각 부분에서 얼마큼 지연이 일어날까?
__6.6.1 시간 지표로 환산
__6.6.2 거리 지표로 환산
6.7 요약