컴퓨터 아키텍처와 운영체제

  • 컴퓨터는 어떻게 프로그램과 메모리를 조직적으로 관리할까

컴퓨터 아키텍처: 컴퓨터의 여러 구성요소를 배치하는 방법

  • 오랫동안 다양한 아키텍처가 시도됐고, 어떤 아키텍처가 잘 작동했고 어떤 구조가 잘 작동하지 못했는지에 대해 읽어보면 흥미진진하다.

아래 요약

  • 주로 메모리와 관련한 구조개선 에 대한 이야기를 할 것임
  • 명령어 집합 설계, 다른 유형의 레지스터들, 전력 관리, 멋진 실행장치 같은 구조
  • 멀티태스킹 ( 여러 프로그램을 동시에 실행하는 기능이거나 최소한 겉으로 보기에는 여러 프로그램이 동시에 실행되는 것처럼 보이게 하는 기능 )
    => 여러 프로그램을 실행한다는 말은, 프로그램들의 실행을 제어하기 위한 감독 프로그램인 운영체제가 있어야 한다는 사실을 암시한다.

기본적인 구조 요소들

흔한 두 가지 컴퓨터 구조 폰 노이만, 하버드 구조

  • 폰 노이만 : 메모리에서 동시에 명령어와 데이터를 가져올 수 없음(느림)
  • 하버드 : 동시에 가져올 수 있음(빠름) 하지만 명령어 메모리 , 데이터 메모리에 각각 버스가 필요함

프로세서 코어

1980 멀티프로세서 시스템은 단일 CPU 보다 훨씬 더 좋은 성능을 얻어내기 위한 방법

  • 하지만 성능을 높이는 것이 그렇게 쉽지는 않았다. #q 여러 CPU를 활용할 수 있도록 프로그램을 병렬화하는 문제는 아주 많은 수학계산이 필요한 몇몇 경우에는 잘 동작하지만 일반적인 경우에는 풀 수 없는 문제다.
  • 반도체 회로 크기가 줄어들면서 비용 낮아짐. 반도체 칩은 실리콘 웨이퍼 위에 만들어지므로 그 위에 얹는 모든 요소를 작게 만들면 한 웨이퍼로 더 많은 칩을 생산할 수 있다.
  • 하지만 기계가 더 빨라지면 전력을 더 많이 소모하고 회로 크기는 줄어드는데 기계가 빨라져서 전력을 더 많이 소모하게 되면서 단위면적당 열 발생은 더 많아졌다. => 2000년경 프로세서는 전력 장벽에 부딪힌다.

작아진 회로를 활용한 새로운 해결책

  • CPU의 정의가 바뀌었다. 예전에는 CPU 라고 부르던 것을 프로세서 코어라고 부른다. 이런 코어가 여럿 들어가는 멀티코어 프로세스가 이제는 일반적.

마이크로프로세서와 마이크로컴퓨터

물리적인 패키징에 따라 구조를 구분(모호함)

  • 마이크로 프로세서: 메모리와 I/O가 프로세서 코어와 같은 패키지에 들어 있지 않음 / 큰 시스템 부품
  • 마이크로 컴퓨터: 모든 요소를 한 칩 안에 패키징 ex.아두이노 / 보통 식기세척기 등 단일칩의 작은 컴퓨터
    (마이크로컴퓨터가 가리키는 대상을 마이크로컨트롤러라는 용어로 부르기도하고, 마이크로 컴퓨터를 마이크로프로세서를 중심으로 만들어진 컴퓨터 시스템을 뜻하는 말로 사용하는 사람도 있다. )

마이크로프로세서가 마이크로컴퓨터(칩에서 메모리가 차지하는 비율이 큼)보다 강력하다.

프로시저, 서브루틴, 함수

함수(또는 프로시저나 서브루틴)는 코드를 재사용하는 주요 수단이다.
함수를 호출하는 부분에서 함수를 실행하고 다시 원래자리로 돌아올 방법 -> 어디서 함수로 들어갔는지 기억해야한다. 이 위치는 바로 프로그램 카운터의 값이다.
![[IMG_9495.jpeg]]
![[IMG_9496.jpeg]]

이 과정은 상당히 많은 작업이 필요하다. 대부분의 기계는 이런 과정을 돕는 명령어를 제공한다. ARM 프로세서는 링크 레지스터를 사용해 분기 명령어가 있다. 함수 호출하는 명령어와 현재 명령어의 다음위치를 저장하는 명령어를 하나로 합친 것이다.

스택

함수는 다른 함수를 호출하기도하고 자기 자신을 호출(재귀, recursion)하기도 한다.

  • 재귀함수가 제대로 작동하려면 반환 주소를 여럿 저장할 수 있어야한다. 그리고 함수 호출 지점으로 반활할 때 저장된 주소 중 어떤 주소를 사용할지 결정할 수 있어야한다.
  • 깊이 우선 순위의 경우, 트리에서 한 수준을 내려갈 때마다 돌아올 위치를 기억해야한다. 원래 위치로 돌아오고 나면 저장했던 위치는 더 이상 필요하지 않다.
    -> 접시더미 같은 역할을 수행하는 장치가 필요하다. 함수를 호출할 때 반환주소를 접시에 넣어 맨 위에 놓고 함수에서 돌아올 때는 맨 위 접시의 반환주소로 반환한 후 접시를 제거한다.

접시를 쌓는 곳 = 스택

  • 스택은 단지 반환 주소만 저장하기 위한 장소는 아니다. 함수는 프로그램의 속도를 빠르게 하기 위해 미리 계산한 값을 지역변수에 넣고 지역변수에 들어있는 값을 여러번 사용한다. 함수를 호출할 때 이 지역 변수 값을 그냥 덮어쓰면 안된다. 지역 변수도 스택에 저장해야한다. -> 함수의 호출이 독립적이게 된다.
  • 함수가 호출될 때마다 스택에 저장되는 데이터의 모음을 스택프레임이라고 부른다.

인터럽트

어떤 작업(1)을 하면서 응답해야할 다른 작업(2)이 도착했는지 확인하는 방법 중, 세부작업(1-1, 1-2, … )이 끝날 때 마다 다른 작업이 왔는지 확인하는 방법을 폴링이라고 한다. 폴링은 다른 작업이 도착했는지 검사하는데 많은 시간을 소비한다. (이런 문제를 소프트웨어적으로 해결할 수 없다.)
-> 실행 중인 프로그램을 잠깐 중단시켜서 외부 요소에 대응할 수 있게 만드는 방법이 필요하다.(하드웨어 기능을 추가하겠다!!) 인터럽트 시스템

인터럽트 시스템

  • 적절한 신호가 들어오면 CPU실행을 잠깐 중단시킬 수 있는 핀이나 전기 연결을 포함한다.
  • 동작 방식
    • CPU가 주의를 기울여야하는 주변장치는 인터럽트 요청을 생성한다.
    • 프로세서는 (보통은) 현재 실행 중인 명령어를 끝까지 실행한다.
    • 그 후 현재 실행 중인 프로그램을 잠시 중단시키고 인터럽트 핸들러라는 프로그램을 실행하기 시작한다.
    • 인터럽트 핸들러가 필요한 작업을 다 마치고 나면 원래 실행 중이던 프로그램이 중단 된 위치부터 다시 실행을 계속한다.
    • 인터럽트 핸들러는 함수다.
  • 고려해야할 것
    • 인터럽트에 대한 응답시간. 인터럽트 처리를 정해진 시간 안에 끝내야한다.
    • 인터럽트를 서비스하고 나중에 다시 원래대로 돌아오기 위해 현재 상태를 저장할 방법이 필요하다.
  • 인터럽트 시스템은 돌아올 프로그램 위치를 스택에 저장한다.
  • 인터럽트 핸들러는 자신이 덮어쓸 레지스터를 모두 저장해야 할 책임이 있다.
  • 인터럽트 핸들러 주소를 저장하기로 약속한 메모리 주소가 존재한다. 이 주소에는 여러 인터럽트 벡터가 들어있고 각 인터럽트 벡터는 CPU가 지원하는 각 인터럽트에 대한 핸들러 주소를 지정한다.
  • 인터럽트 제어
    • 어떤 인터럽트를 켜거나 끌 수 있다.
    • 인터럽트 간의 우선순위가 있다.
    • (대부분의 기계에) 일정시간이 지나면 인터럽트를 발생시킬 수 있는 내장 타이머도 있다.
    • 운영체제는 가상 인터럽트나 소프트웨어 인터럽트 시스템을 제공하기도한다. “이벤트”라고 부르는 추세이다. (유닉스의 시그널 메커니즘)

상대 주소 지정

여러 프로그램을 동시에 실행시키려면, 각 프로그램을 서로 전환시켜줄 수 있는 관리자 프로그램이 필요하다. 이런 프로그램을 운영체제 또는 운영체제의 커널 이라고 부른다.

  • OS를 시스템 프로그램이라고 부르고, 다른 모든 프로그램을 사용자 프로그램 또는 프로세스라고 부르겠다.

OS는 타이머를 사용해 사용자 프로그램을 전환시켜줄 때가 됐는지 판단한다.

  • 사용자 프로그램 실행시간을 조절하는 스케쥴링 기법을 시분할 이라고 한다. 시간을 정해진 간격으로 나누고 정해진 시간 간격 동안 사용자 프로그램 실행.
  • 사용자 프로그램 상태 또는 문맥은 레지스터의 상태와 프로그램이 사용중인 메모리의 상태를 뜻한다.(메모리에는 스택도 포함)
  • #q 시분할 실행은 느리다. 프로그램을 메모리로 불러들이려면 시간이 걸린다. 프로그램을 메모리로 불러오되 프로그램에게 각기 다른 공간을 허용할 수 있으면 훨씬 빠르게 시분할 실행이 가능하다.

어떻게 프로그램이 하나씩 차례대로 메모리에 올라갈까?

  • 인덱스 레지스터 : 인덱스 레지서트의 값을 명령어에 들어있는 주소와 더해서 유효주소를 계산
  • 상대 주소 지정 : 명령어에 들어있는 주소를 0부터 시작하는 위치로 해석하지 않고 명령어 주소를 기준으로 하는 상대적인 주소로 해석
    • 상대주소를 이용하면 프로그램을 메모리의 원하는 위치로 자유롭게 재배치 할 수 있다.

메모리 관리 장치(MMU)

메모리 관리 장치(MMU: memory management system)는 서로다른 사용자 프로그램이 서로의 메모리를 침범하지 않도록 관리한다.

  • 가상 주소와 물리 주소를 구분한다.
  • 프로그램은 가상 주소를 사용해 작성되고 MMU는 가상주소를 물리 주소로 변환해준다.

MMU와 인덱스 레지스터의 차이

  • MMU의 가상 주소 범위는 물리 메모리 주소보다 클 수 있다.
    • MMU는 가상 메모리 주소를 두 부분으로 나눈다.
    • 주소의 하위(LSB쪽) 부분은 물리적 주소 범위와 같다.
    • 상위(MSB쪽) 부분은 페이지 테이블이라는 RAM 영역을 통해 주소를 변환한다.

메모리는 페이지로 분할된다. 페이지 테이블에는 각 페이지가 물리 메모리상에서 차지하는 실제 위치 정보가 들어있다. 페이지 크기를 줄이면 페이지 테이블 크기가 늘어난다.
-> MMU는 페이지 테이블 항목 중 일부를 필요할 때만 자신의 페이지 테이블로 읽어들인다.

가상 메모리

OS는 희소한 하드웨어 자원을 사용하려고 경합하는 프로그램들 사이의 자원 분배를 관리한다. OS는 MMU를 사용해 사용자 프로그램에게 가상메모리를 제공한다.

페이지 폴트 메커니즘으로 인해 프로그램은 필요한 만큼 많은 메모리가 있다고 생각할 수 있게 됐다.
디멘드 페이징

  • OS는 현재 필요하지 않은 메모리 페이지를 더 느리지만 더 용량이 큰 대용량 저장장치인 디스크로 옮긴다(스왑아웃)
  • 사왑 아웃한 페이지에 프로그램이 접근하면 운영체제는 필요한 메모리 공간을 확보하고 요청받은 페이지를 다시 메모리로 불러들인다.(스왑인)
  • 스와핑이 일어나면 시스템 성능이 크게 저하된다. 시스템 성능 저하를 막기 위해 스왑 아웃할 페이지를 결정하는 LRU(Least recently used) 알고리즘 같은 것들을 이용한다.

시스템 공간과 사용자 공간

CPU에는 컴퓨터가 시스템 모드에 있는지 사용자 모드에 있는지 결정하는 비트가 어떤 레지스터 안에 들어있다. I/O를 처리하는 명령어 등 일부 명령어는 privileged 명령어라서 시스템 모드에서만 실행할 수 있다. 트랩 혹은 시스템 콜 이라고 부르는 특별한 명령어를 통해 사용자 모드에서 실행중인 프로그램이 시스템 모드 프로그램(OS)에게 요청을 보낼 수 있다.

메모리 계층과 성능

프로세서는 아주 빠르다. 1초에 수십억 개의 명령어를 실행할 수 있다. 하지만 메모리에 데이터를 읽거나 쓸 때까지 기다린다면 CPU는 제 속도를 낼 수 없다. CPU와 주로 소통하는 RAM은 CPU 속도의 1/10 정도 밖에 되지 않는다.

  • 이를 보완하기 위해 캐시(하드웨어)를 CPU에 추가한다. 캐시는 램보다는 훨씬 작지만 훨씬 빠르다.
  • CPU 메모리 컨트롤러(하드웨어)는 메모리에서 연속된 열에 있는 데이터를 한번에 가져온다.
    • 프로그램 메모리는 순서대로 읽는 경우가 많고, 프로그램이 사용하는 데이터는 모여있는 경우가 많다.

캐시 메모리 계층

  • 모든 캐시가 같은 칩 안에 존재하는 경우에도 CPU에서 멀어질 수록 캐시는 더 느려지고 더 커진다. 이들을 L1, L2, L3 캐시라고 부른다.

캐시 일관성
![[스크린샷 2023-09-27 오후 3.02.50.png]]
AMD phenom quad-core processor

프로세서 칩이 2개인 컴퓨터를 생각해보자. 각 칩에는 코어가 4개 있다. 코어 중 하나는 데이터를 어떤 메모리 위치에 기록한다. 실제로는 캐시에 데이터가 먼저 기록되며 결국 메모리에도 기록된다. 이 데이터를 사용해야 하는 다른 코어나 프로세서가 해당 메모리 위치의 데이터가 변경됐음을 어떻게 알 수 있을까?

  • 가장 간단한 방법은 라이트 스루 , 하지만 이 방법은 캐시의 장점을 대부분 없앤다.
    -> 그렇다면? #Q

코프로세서

  • 몇 가지 연산을 위임받아 수행하는 단순한 회로이다.
  • 과거에는 한 칩 안에 모든 연산 회로를 넣기 어려워서 쓰였다. (부동소수점 수 연산 등)
  • 요즘은 그래픽 처리 등 여러가지 기능을 담당하는 코 프로세서가 한 칩에 같이 들어가 있다.
  • 일부 코프로세서는 주메모리와 디스크 사이의 데이터 복사만 담당한다. 이런 방식을 직접 메모리 접근 DMA 라고한다.
    -> CPU는 유용한 연산을 더 많이 처리할 수 있다.

메모리상의 데이터 배치

메모리에는 명령어 뿐 아니라 데이터도 담는다.

  • 이 중 동적 데이터는 프로그램을 실행하기 전에는 크기를 알 수 없는 데이터를 말한다. 이런 데이터는 정적 데이터 영역(스택) 바로 위에 heap 영역에 쌓이며, 더 많은 데이터를 저장해야하는 경우 스택은 아래로 자라고 힙은 위로 자라 서로 충돌하지 않게 한다.
    MMU가 쓰이는 경우 명령, 데이터, 스택이 각기 다른 물리적 메모리 페이지에 매팽되고 필요에 따라 할당된 크기를 변경할 수 있다.

    프로그램 실행

    유용한 함수들을 한데 모아 라이브러리로 만들기도한다. 프로그램은 라이브러리 뿐 아니라 여러 조각으로 이뤄진다. 프로그램을 여러 조각으로 나누면 모든 조각을 하나로 엮거나 연결(링크) 하는 방법이 필요하다. 각 프로그램을 링크하기 편한 형식의 매개 파일로 나누고 링커라는 특별한 프로그램을 사용해 여러 조각을 하나로 연결해 실행한다.

과거에는 라이브러리를 하나의 파일로 간주해 프로그램의 나머지 부분과 직접 연결해 실행파일을 만들었다. 이런 방식을 정적 링크
-> 같은 라이브러리가 여러 프로그램에 쓰이는 것을 발견하고, 공유 라이브러리를 사용하는 동적 링크를 발명했다. MMU가 여러 프로그램이 같은 라이브러리를 공유할 수 있게 해준다.