컴퓨터 밑바닥의 비밀 루 샤오펑 pdf 다운로드를 무료로 제공합니다 “어? 이 코드가 왜 돌아가지?”라고 생각해 본 경험이 다들 있을 것이다. 코드가 잘 실행되더라도 이 코드가 어떻게 작동하는지, 컴퓨터가 코드를 실행할 때 내부에서 무슨 일이 벌어지는지
관련 교재 pdf 모음
책 소개
이 책에서는 계층별로 추상화되어 있는 컴퓨터 시스템의 구조를 하나씩 뜯어본다. 프로그래밍 언어와 컴파일러로 시작하여 운영 체제와 프로세스, 스레드와 코루틴, 동기화, 메모리에 대해 살펴보고, CPU와 캐시, 입출력을 구현하는 원리에 대해서도 설명한다. 어렵게 느껴질 수도 있는 컴퓨터 시스템 내부를 일상 속 다양한 비유와 300개 이상의 그림으로 설명하여 비교적 쉽게 접근할 수 있게 구성했다. 초보 개발자라면 컴퓨터 기본 지식을 체계적이고 쉽게 배울 수 있으며, 숙련된 개발자라면 이미 알고 있는 내용을 정리하면서 새로운 아이디어를 얻을 수 있을 것이다. 컴퓨터 시스템의 원리와 메커니즘에 대해 심층적으로 이해하고 있다면 작성하는 코드의 가독성 및 유지 관리 가능성을 개선할 수 있다. 이뿐만 아니라 프로그램 성능을 효과적으로 최적화할 수 있으며, 코드 실행 오류나 비정상적인 시스템 동작을 신속히 찾아내 해결할 수도 있다. 계층별로 추상화되어 있는 컴퓨터 시스템의 구조를 하나씩 뜯어보고, 프로그래밍 언어와 컴파일러로 시작하여 운영 체제와 프로세스, 스레드와 코루틴, 동기화, 메모리에 대해 알아본다. 더불어 CPU와 캐시, 입출력을 구현하는 원리와 파일 시스템, 네트워크와 통신, 보안과 암호화, 병렬 컴퓨팅에 대해서도 함께 배운다.
컴퓨터 밑바닥의 비밀 루 샤오펑
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 요약