전공/시스템 프로그래밍

[시스템 프로그래밍 2-3] CPU 명령 실행주기 프로그램 실행 과정(bottleneck 병목현상,캐쉬의 계층화)

뜨거운 개발자 2023. 3. 19. 23:10

명령 실행 사이클[명령 실행 주기]

CPU는 기계 명령을 실행하기 위해 미리 정해둔 일련의 단계를 거쳐야만 한다.

예시

명령어 포인터 레지스터에 실행하고자 하는 명령의 주소가 저장되어 있다고 가정한 상황.

  1. 첫번째로 CPU는 명령어 대기열 (instruction queue)에서 명령을 가져와야(fetch) 한다. 이 과정에서 어디서 명령어를 가져올지 그 주소를 이미 CPU는 알고 있기에 사실 빼오는게 아니라 요청하는 것과 같다. 이후 명령어 하나의 크기가 4bite라서 4바이트 만큼 명령어 포인터를 증가시켜서 명령 포인터를 업데이트 한다.
  2. 명령어를 가져왔으니 CPU는 명령을 디코딩한다. (사실 해석을 하는게 아니라 회로를 수행하는 것이다.) - 여기서 비트 패턴을 확인하고 만약 덧셈과 같이 피연산자가 필요한 명령이라면 그 사실을 알게 된다.
  3. 피연산자가 필요하기 때문에 이제 데이터가 필요하니까 그 데이터를 가져와야 한다.(fetch). 만약 덧셈처럼 대상이 필요한 연산의 경우 덧셈의 대상이 레지스터에 있을수도 있고 메모리 위에 있을 수도 있기에 레지스터를 탐색하고 메모리를 탐색한다.(주소 계산이 포함된다.)
  4. (Execution) : 덧셈을 진행한다. 가져온 피연산자가 있다면 계산을 진행하고 flag를 세팅해준다. (Carry가 발생했거나 연산결과가 0인경우 오버플로우가난 경우 등의 플래그를 세팅해준다.)
  5. 만약 출력 피연산자가 명령의 일부인 경우에 CPU는 피연산자 즉 가져온 데이터에 다시 그 결과를 저장한다.

CPU입장 필수 행동은 Fetch ,decode, execute 즉 필수는 1,2 4에 해당한다.

방금 든 예시는 Z = X+Y 와 같이 두개의 입력 피 연산자와 하나의 출력 피연산자를 가지는 경우를 적용해서 생각해보자.

명령 실행 주기동안 상호작용하는 컴포넌트간의 관계

  1. 메모리에서 프로그램 명령을 읽기 위해 주소가 주소 버스에 배치된다.
  2. 다음으로 메모리 컨트롤러가 요청된 코드를 데이터 버스에 배치하여 코드 캐시내에서 코드를 사용할 수 있도록한다.즉 쉽게 말하면 명령 요청 받은 건 잠시 캐쉬에다가 넣어둔다. (코드에서 사용된 녀석은 또 사용될 가능성이 크기 때문이다. —반복문을 생각하면 쉽다.) (캐쉬는 CPU안에 있고 훨씬 빠르기 때문에 이득이다.)
  3. 명령 포인터의 값에 따라서 다음에 실행 될 명령이 결정이 된다.
  4. 명령어는 명령어 디코더에 의해 분석되어서 적절한 디지털 신호가 제어장치로 전송되고, 제어장치(control unit)는 ALU와 부동소수점 유닛을 조정한다.

메모리에서 데이터를 읽는 과정

메모리에서 단일 값을 읽어오는 4가지 단계

  1. 읽으려는 주소값을 주소 버스에 배치한다. (즉 CPU가 읽고 싶은 번지를 말해서 address bus한테 빠르게 보내달라고 한다.)
  2. 프로세스가 가지고 있는 RD(읽기)라는 핀을 Assert(값 변경)세팅해주고
  3. 메모리 칩이 응답할 때까지 한 클록 사이클을 기다린다.
  4. 데이터 버스에서 대상 피연산자로 데이터를 복사한다.

이것이 컴퓨터가 내부 레지스터에 엑세스 하는 것보다 훨씬 느리게 메모리를 읽는 이유이다.

각 단계는 일반적으로 단일 클록 사이클이 필요하기 때문에 단 한번의 클록주기로만 엑세스가 가능한 CPU레지스터보다 훨씬 느릴 수 밖에 없는 것이다.

폰노이만 아키텍쳐의 장단점

폰노이만 컴퓨터 구조는 상당히 효과적인건 사실이다. 그러나 여기서 단점이 들어난다.

우선 폰노이만 아키텍쳐를 통해서 하드웨어 조작할 필요가 없어진 엄청난 변화가 일어났고 그 결과 컴퓨터가 상당히 범용적으로 변했다.

한가지 단점 : 폰노이만 컴퓨터 구조의 단점은 메모리에서 cpu로 가져오는 과정이 있어야만한다. 근데 이건 시간이 많이 걸리는 작업이다.

따라서 메모리 병목현상이 항상 버스에서 발생한다. 이건 시스템 전체적인 속도를 저하시키는 원인이 된다.

폰노이만 컴퓨터는 버스때문에 결국 느리다.즉 전기의 속도가 일정한데 거리가 길면 당연히 속도가 느려질 수 밖에 없는것이다.

점점 더 CPU가 작아지는 이유와 작아질 수록 속도가 빨라지는 이유는 전기의 속도는 일정한데 길이가 짧아지기 때문이다. 따라서 프로그래머는 메모리에 많이 Access할 때 속도적 측면에서 조심해야한다. 퍼포먼스를 올리고 싶으면 구조를 모르면 올릴 수가 없다.

우리가 그래픽 카드를 사는이유도 같은 맥락이다. CPU만 연산하기엔 연산이 너무 많아서 맡기는경우, 처리 할 일을 CPU와 그래픽 카드가 왔다갔다 하는게 아니라 그래픽 카드는 버스를 안 타도 돼서 상당히 빠르다.

bottleneck 병목현상

캐쉬를 쓰는 이유는 병목현상을 줄이기 위해서 즉 최근에 사용한 명령을 또 쓰는경우가 많기 때문에 병목현상을 줄여준다.

캐쉬의 계층화

  • 캐시 히트 : 프로세서가 캐시 메모리에서 데이터를 찾을 수 있을 때 캐시 히트라고 합니다.
  • 캐시 미스 : CPU가 캐시에서 뭔갈 찾으려고 하는데 없는경우 캐시 미스라고 부릅니다.

캐쉬의 크기가 크다는건 더 때릴 가능성이 크다는 것을 의미한다.(캐쉬는 클수록 좋은데 돈이 언제나 문제)

X86 제품 군의 캐시메모리 2개의 유형

  1. Level-1 cache (or primary cache) : CPU에 바로 저장된다.
  2. Level-2 cache (or secondary cache) : 조금 더 느리고 고속 데이터 버스를 통해서 CPU에 연결됩니다.

이 두가지 유형의 캐시는 최적의 방식으로 함께 작동한다.

캐쉬가 램보다 빠른이유는 SRAM(정적 램 static ram)을 사용해서이고 비싸다. 메모리는 이렇게 항상 계층구조를 가진다.

일반적인 RAM은 Dynamic RAM을 사용한다.

  • 동적 메모리 특징 - 지속적으로 새로고침해야한다. 훨씬 드리지만 저렴하다.
  • 정적메모리 특징 -비싸지만 컨텐츠 유지를 위해 지속적으로 새로고침 할 필요는 없다.

프로그램 로드 및 실행

  1. 프로그램 로더가 프로그램을 메모리에 올려줘야 한다.
  2. 로딩 후 OS는 프로그램이 실행을 시작할 주소인 프로그램의 진입 지점을 CPU로 지정 해야 한다.
  3. 적재가 되면 바로 실행이 되는게 아니고 운영체제를 거쳐서 CPU에게 명령어가 어디서 처음 시작하는지를 운영체제가 가지고 있고 그걸 CPU에게 알려주는 것이다.
  4. 그리고 나서 CPU가 램에게 요청을 하는 식이다. 운영체제가 메모리를 가상화하기 때문에 이렇게 동작하는 것이다.

세부 동작

  1. 운영체제는 파일이름을 찾아서 위치를 가져온다.
    1. 현재 디렉토리에서 프로그램 파일을 검색하고
    2. 거기서 찾을 수 없으면 미리 정해진 디렉토리 목록(호출경로) 에서 파일이름 검색
    3. 여기서도 찾지 못하면 오류메시지가 표시된다.
  2. 프로그램 파일이 발견되면 OS는 디스크 디렉토리에서 프로그램 파일에 대한 기본 정보를 검색한다.
    1. 파일 크기 및 디스크 드라이브의 실제 위치를 검색
  3. 운영체제는 메모리에 사용가능 위치를 결정하고 프로그램을 메모리에 로드한다.
    1. 파일 크기를 다 알고 있어서 메모리 크기 할당 가능
    2. 프로그램에 메모리 블록을 할당하고 프로그램 크기와 위치에 대한 정보를 태이블에 입력한다.
    3. 운영체제는 프로그램 데이터의 주소를 포함하도록 프로그램 내 포인터 값을 조정할 수있다.
  4. 운영 체제는 프로그램의 첫번 째 명령어를 실행한다.(진입점)
    1. 프로그램은 실행되자마자 프로세스라고 불린다.
    2. 운영채제는 프로세스에 식별번호 (프로세스 ID -pid) 를 할당해서 실행중 프로세스를 추적하는데 사용한다.
  5. 프로세스가 자체적으로 실행된다.
    1. 멀티태스킹 관리 운영체제는 계속 모니터링하고 시스템 리소스(메모리, 디스크 파일, IO장치) 응답을 운영체제가 해준다.
  6. 프로세스가 명령어의 끝에 도달하면 그 사이즈를 다시 회수한다.
728x90
반응형