정글 40일차

윤종성·2024년 8월 9일
0

CSAPP

목록 보기
2/5
post-thumbnail

CSAPP 정리

6. 메모리 계층구조

6-2. 지역성

잘 작성한 컴퓨터 프로그램은 좋은 지역성 locality을 보여준다.
1. 시간 지역성 temporal locality
한번 참조된 메모리 위치는 가까운 미래에 다시 여러 번 참조될 가능성이 높은 것
2. 공간 지역성 spatial locality
어떤 메모리 위치가 참조되면, 가까운 미래에 근처의 메모리 위치를 참조할 가능성이 높은 것(예를 들어 배열의 경우)

하드웨어는 지역성을 활용하여 캐시메모리 등 작업을 빠르게 하기 위한 설계가 적용되어 있으므로, 지역성을 이해하고 좋은 지역성을 갖도록 프로그램을 작성하는 것은 성능에서 중요하다.

6-2-1. 프로그램 데이터 참조의 지역성

int sumvec(int v[N]) {
    int i, sum = 0;
    
    for (i = 0; i < N; i++) 
        sum += v[i];
    return sum;
}

배열의 합을 계산하는 함수를 생각하자.
위 코드에서 sum은 반복적으로 참조되므로 좋은 시간 지역성을 가지고 있다.
반면 스칼라 변수로서 인접한 메모리 공간과 연관이 없으므로 공간 지역성이 존재하지 않는다.
v의 원소들은 순차적으로 읽히므로 v에 대해서는 좋은 공간 지역성을 가지고 있다.
그러나 각 원소들은 한 번만 접근되므로 나쁜 시간 지역성을 가지고 있다.
함수 sumvec은 좋은 시간 또는 공간 지역성을 지니므로 전체적으로 좋은 지역성을 지녔다고 볼 수 있다.

이렇게 벡터의 매 k번째 원소를 방문하는 것을 stride-k 참조 패턴이라고 부른다.
sumvec함수는 stride-1 참조 패턴을 가진다.
일반적으로 k가 증가하면 공간 지역성은 감소한다.

int sumarrayrows(int a[M][N]) {
    int i, j, sum = 0;

    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            sum += a[i][j];
    return sum
}

이차원 배열의 합을 계산하는 함수를 생각하자.
위 함수는 stride-1 참조 패턴을 갖는다.

int sumarraycols(int a[M][N]) {
    int i, j, sum = 0;

    for (j = 0; j < N; j++)
        for (i = 0; i < M; i++)
            sum += a[i][j];
    return sum
}

똑같은 동작을 하지만 열우선 참조를 하는 다른 함수를 생각하자.
이제 stride-N 참조 패턴을 갖는다.
이렇게 같은 역할을 하더라도 공간 지역성에서 차이가 커질 수 있다.

6-2-2. 인스트럭션 선입의 지역성

프로그램의 인스트럭션 역시 메모리에 저장되고 CPU가 읽어들여야 하므로 인스트럭션 선입(읽기)에 대한 지역성도 평가할 수 있다.
예를 들어 sumvec 함수에서 사용한 for루프 내의 인스트럭션들은 순차적인 메모리 순서대로 반복해 실행되며, 좋은 공간 지역성을 갖게 된다.

8. 예외적인 제어흐름

시스템은 프로그램의 실행과는 관련 없는 시스템 상태 변화에도 반응할 수 있어야 한다.
시스템은 제어흐름의 갑작스런 변화를 만드는 방법(예외적인 제어흐름 exceptional control flow (ECF))으로 이러한 상황에 반응한다.

8-1. 예외상황

예외상황은 하드웨어 또는 운영체제에 의해 구현된 예외적인 제어흐름의 한 형태이다.

예외상황에 대한 처리 방식을 간단히 나타낸 그림이다.
명령어 IcurrI_{curr}를 실행하고 있을 때 프로세서 상태에 중요한 변화(이벤트)가 일어난다면,
상태는 예외처리 핸들러로 보내지고 보내진 상태에 따라 예외를 처리한 뒤 프로그램을 종료시키거나 다시 프로그램의 실행 흐름으로 되돌아 간다.

8-1-1. 예외처리

예외상황은 하드웨어와 소프트웨어가 긴밀하게 협력해야한다.
하드웨어와 소프트웨어 사이에 작업이 분배되는 모습을 자세히 살펴본다.

시스템은 가능한 예외상황마다 예외번호를 할당하고 있다.
일부는 프로세서(하드웨어) 설계자가, 나머지는 운영체제 커널(소프트웨어) 설계자가 할당한다.

프로세서 설계가 할당한 예외번호의 예: divide by zero, 페이지 오류, 메모리 접근 위반, breakpoint, 산술연산 오버플로우
커널 설계자가 할당한 예외번호의 예: 시스템 콜, 외부 I/O 디바이스로부터의 시그널

시스템 부팅 시 운영체제는 예외 테이블[^8-1]을 할당하고 예외번호별 처리 핸들러로 할당한다.
즉, 예외 테이블의 엔트리(배열 인덱스) k가 예외상황 k에 대한 핸들러의 주소를 갖는다.
프로세서가 이벤트 발생을 감지하면 해당되는 예외번호 k를 결정하고, 예외 테이블의 k를 참조해 핸들러를 호출한다.

[^8-1]: 예외 테이블의 주소는 예외 테이블 베이스 레지스터라는 특별한 레지스터에 저장한다.

예외상황은 프로시저 콜과 유사하지만 중요한 차이점이 있다:

  1. 핸들러를 호출하기 전에 스택에 리턴주소를 푸시하는 것은 같지만, 예외의 종류에 따라 현재(이벤트가 발생했을 때 실행 중이던) 인스트럭션 또는 다음 인스트럭션을 푸시한다.
  2. 중단됐던 프로그램으로 돌아가기 위해 필요한 프로세서 상태를 푸시한다.
  3. 이것들은 사용자 스택이 아니라 커널 스택 상에 푸시된다.
  4. 예외 핸들러는 커널 모드에서 돌아가므로 모든 시스템 자원에 완전히 접근할 수 있다.
    핸들러가 이벤트를 처리한 후 다시 사용자 프로그램으로 돌아갈 때에는 원래 프로세서의 제어상태와 레지스터 상태를 돌려놓는다.

8-1-2. 예외의 종류 Exception Class

8-1-2-1. 인터럽트 Interrupt

입출력 디바이스로부터 신호를 받아 발생하는 예외이다.
특정 인스트럭션의 실행 여부와 관련이 없기 때문에 비동기적(Async)이다.[^8-2]
프로세서가 인스트럭션 실행을 완료하고 인터럽트 시그널을 감지하게 되면, 시스템 버스에서 예외번호를 읽어 해당 인터럽트 핸들러를 호출한다.
핸들러가 리턴할 때엔 항상 제어를 다음 인스트럭션으로 돌려준다. 즉, 프로그램은 인터럽트가 발생하지 않은 상황과 같이 계속 실행된다.
[^8-2]: 다른 예외의 종류들은 오류 인스트럭션 faulting instruction의 실행에 의해 동기적으로 일어난다. 비동기적 예외를 외부 인터럽트 External Interrupt 또는 하드웨어 인터럽트 Hardware Interrupt, 동기적 예외를 내부 인터럽트 Internal Interrupt 또는 소프트웨어 인터럽트 Software Interrupt라고 부르기도 한다.

8-1-2-2. 트랩 Trap과 시스템 콜 System Call

트랩은 의도적인 예외상황이며 어떤 인스트럭션을 실행한 결과로 발생한다(Sync).
프로그램이 시스템 콜을 호출하였을 때나 예외 상황이 발생하여 시스템으로 제어를 넘기기 위해 발생시키는 예외상황이다.
시스템 콜은 사용자 프로그램에서 커널의 동작을 요청할 때 사용하는 프로시저와 유사한 인터페이스이다.
시스템 콜은 커널 모드에서 돌아가며, 이로 인해 커널 내에서 정의된 스택에 접근하며, 시스템을 제어하는 모든 인스트럭션을 실행할 수 있다.

x86-64에서 시스템 콜은 syscall이라는 트랩 인스트럭션을 통해서 제공된다.
리눅스 시스템 콜에 전달되는 모든 인자들은 범용 레지스터를 통해서 이루어진다.
%rax 레지스터에 시스템 콜 번호를 보관하고, argument용 레지스터에 최대 여섯 개의 인자들을 보관한 후 호출하게 된다.

8-1-2-3. 오류 Fault

핸들러가 정정할 수 있을 가능성이 있는 에러 조건일 때 발생한다.
오류가 발생하면 프로세서는 오류 핸들러로 제어를 이동한다.
핸들러가 에러 조건을 정정할 수 있다면, 오류를 발생시킨 인스트럭션(현재 인스트럭션)으로 제어를 돌려주어 프로그램 실행을 계속한다.
정정할 수 없다면, 핸들러는 커널 내부의 abort 루틴으로 리턴하여 프로그램을 종료한다.

대표적인 예시로 페이지 오류 예외가 있다.
인스트럭션이 참조하는 메모리가 물리 메모리에 페이지되어 있지 않은 상황에 발생한다.
핸들러는 디스크에 있는 페이지를 물리 메모리로 로드하고 다시 오류를 일으킨 인스트럭션으로 돌아가게 해준다.

8-1-2-4. 중단 Abort

하듸웨어 같은 치명적인 에러에서 발생한다. 중단 핸들러는 무조건 응용프로그램을 중단하는 abort 루틴으로 제어를 넘겨준다.

오늘 배운 것들

1. DMA

직접 메모리 접근(Direct Memory Access, DMA)은 주변기기에서 CPU의 처리를 거치지 않고 직접 메인 메모리에 접근해서 데이터를 가져오는 기능이다.

PIO는 DMA의 반대개념으로써, 장치들 사이에 전송되는 모든 데이터가 CPU를 거쳐가는 방식이다.

PIO는 주변기기가 CPU에 필요한 메모리에 대한 정보를 주고 CPU가 메인 메모리에서 데이터를 받아 요청한 기기에 전송한다.
DMA는 CPU를 거치지 않고 직접 해당 기기의 메모리에 접근해서 필요한 정보를 가져온다.

  • 장점
    CPU자원 절약: CPU가 직접 메모리를 읽어오고 전송하지 않으므로 전송 중 다른 작업을 할 수 있다.
    빠른 속도: 데이터를 직접 전송하므로 전송속도가 빠르다.
  • 단점: 보안에 취약할 수 있다.
  • 사례: 그래픽 카드, 사운드 카드, 네트워크 카드 등 많은 하드웨어 시스템, 메모리 간 대용량 데이터 전송

2. Call by value, Call by reference

  • Call by value
    함수 호출 시 매개변수가 값을 가져오는 것.
    호출된 함수 내에서 인자는 호출 시 전달한 변수의 값만을 다루며 인자를 직접 변경할 수 없다.
  • Call by reference
    함수 호출 시 매개변수가 인자를 직접 참조하는 것.
    호출된 함수 내에서 인자의 값을 직접 변경할 수 있다.
    C언어에서는 값만을 전달하기 때문에(레지스터 또는 스택 공간을 이용해서. Call by value) 호출자 변수를 참조하려면 포인터를 사용(굳이 따지자면 Call by address)해야만 한다.
    파이썬에서 리스트나 딕셔너리같은 mutable 객체를 사용하는 경우를 떠올릴 수 있다.(단, 파이썬은 모든 자료형이 객체이기 때문에 객체를 전달할 뿐이지 Call by reference라고 보기는 힘들다.)

3. 취업 설명회

설명회 들으면서 메모한 내용.
전부 쓸 수는 없어서 민감한 부분은 많이 잘라냈다.

회사 분위기

  • 최고의 기술보다 당장 가능한 기술 선호
  • 많이 생각하기보다 바로 시작하고 수정해나가는 방식
  • 계속 공부하여 생산성 기여하기를 기대
  • 회사가 시니어가 필요해지는 시점에 노드, 파이썬 시니어를 찾기 어렵기 때문에 회사가 커지게 되면 자연스럽게 메이저 기술로 이전하게 되고 그래서 공급 역시 쏠리는 것이 자연스럽다..

채용

  • 라이브러리가 해주니까 생각없이 짜는 사람들이 많다..
    내부 작동 방식을 모르는 경우가 많다.
    객체지향이라고 해놓고 물어보면 객체 지향 별거 없는 사람들이 많다(프레임워크 덕지덕지..)
  • 교육수준이 높아지는 만큼 허들이 높아지고 있는 것은 사실이다.

+

순간 효율만 추구하면 잘하는 걸 찾을 수 없다(오버피팅)

급한 일은 언제나 있다.
중요한 일에 쓸 시간을 고정시켜둬라
나머지 시간에 일이 생겨도 하루를 망친 느낌이 안 든다.

정글이 끝나도 실무 관련된 경험을 쌓지 않는 한 취업 가능성은 0일 것이란 생각이 들었다.
다른 분야도 마찬가지겠지만, 기본기를 쌓을 수 있는 직장은 거의 없는 것 같다.

profile
알을 깬 개발자

0개의 댓글