[운영체제] 시스템 구조와 프로그램 실행
System Structure & Program Execution (1)
🔊 이화여자대학교 반효경 교수님의 KOCW 2014년 1학기 운영체제 강의를 들으며 정리한 노트입니다.
캡쳐한 이미지 중 따로 출처 명시를 하지 않은 이미지 또한 반효경 교수님 강의 자료에 있음을 밝힙니다.
컴퓨터 시스템 구조
컴퓨터 시스템의 하드웨어 구조를 간단히 요약한 그림 |
- 컴퓨터 내부장치 : CPU, 메모리
- 컴퓨터 외부장치 : 그 외 나머지
-
I/O는 Input, Output을 말한다.
- Input(입력) : I/O 디바이스의 데이터가 컴퓨터 안으로 들어가는 것
- Output(출력) : 데이터를 받아서 컴퓨터에서 처리를 한 결과를 다시 I/O 디바이스로 보내는 것
- CPU의 작업 공간이 메모리이기 때문에 CPU는 매 순간 정확하게는 매 클럭 싸이클마다 메모리에서 기계어를 하나씩 읽어서 실행을 하게 된다.
-
그리고 이러한 각각의 I/O 디바이스들은 그 디바이스를 전담하는 작은 CPU 같은 것들이 붙어있게 된다. 그것을 우리는 device controller라고 한다.
디스크에서 헤드가 어떻게 움직이고 어떤 데이터를 읽을지 디스크의 내부를 통제하는 것은 CPU의 역할이 아니고 디스크에 붙어있는 디스크 컨트롤러가 그런 작업을 하게 된다.
키보드 컨트롤러 등도 마찬가지… 키보드에 붙어있는건 키보드 컨트롤러 이런식으로 컨트롤러들이 각 I/O 디바이스마다 붙어있어서 그 디바이스를 전담하는 작은 CPU 역할을 한다고 보면 된다.
메인 CPU의 작업공간인 메인 메모리가 있듯이 이러한 디바이스 컨트롤러들도 그들의 작업공간이 필요하다. 그들의 작업공간이 위와 같이 각각 존재하는데 그것을 우리는 local buffer라고 부른다.
CPU하고 I/O 디바이스는 처리하는 속도 차이가 굉장히 많이 난다. 그래서, 디스크를 CPU가 직접 관장하지 않고, device controller가 담당하고 있다.
CPU의 운명이자 역할은 평생 메모리에서 Instruction을 하나씩 읽어서 실행하고, 그러고 또 그 다음 거 읽어서 실행하는 것이다.
CPU안에는…
-
registers
- 메모리보다 더 빠르면서 정보를 저장할 수 있는 작은 공간들
-
mode bit
- CPU에서 실행되는 것이 운영체제인지, 사용자 프로그램인지 구분해준다.
-
Interrupt line
- CPU는 항상 메모리에 있는 Instruction만 계속 실행을 하는데 키보드에서 어떤 입력이 들어오거나, 또는 디스크에서 뭘 읽어온다던지 디스크에 뭔가를 읽어오라고 요청을 했는데 디스크가 그 일을 다 끝냈다던지… 이런 것을 CPU가 어떻게 알까?
- 그걸 전달하기 위해서 Interrupt line이 CPU에 붙어있는 것
CPU는 항상 메모리하고만 일한다. 그러나, 경우에 따라서는 키보드 입력을 받을 수도 있고, 프로그램이 실행되다가 디스크에서 어떤 파일을 읽어와서 다음 Intruction을 실행하는 그런 경우도 있을 것이다. (프로그램을 작성하다보면 항상 메모리만 가지고 작업을 하는 것이 아니라, 변수만 정의해서 작업을 하는게 아니라 키보드에서 뭘 읽어오고 화면에 뭔가를 출력하고 또, 디스크에서 뭔가를 읽어오거나 프로그램이 처리한 결과를 디스크에 쓰거나 이런식의 프로그램 구조가 되어 있기 때문에)
키보드 입력이나 화면 출력, 디스크 입출력 같은게 I/O 디바이스를 접근하는 Instruction들이 되겠다.근데, CPU는 I/O 디바이스를 직접 접근을 하지 않고 계속 메모리에서 읽어서 즉, 메모리를 접근하는 Instruction만 실행하게 되어있다. 그러다가 디스크에서 뭘 읽어오라는 요청은 어떻게 보낼까?
만약에 Instruction을 쭉 실행하다가 프로그램 A가 디스크에서 뭘 읽어와야된다 그러면 CPU가 직접 디스크를 접근하는게 아니라 CPU가 Disk controller한테 특정 데이터를 어딘가에서 읽어오라고 일을 시킨다. (일을 시키는 Instruction이 있다)
일을 시켜놓으면 디스크는 그 요청한 데이터를 Disk controller의 지시를 받아서 읽어온다. (읽어오는 작업은 굉장히 오래 걸릴 것이다) 그래서 디스크는 시킨 일을 하면서 읽어다가 자신의 local buffer에 집어넣게 된다.
그러는 동안 굉장히 빠른 CPU가 놀고 있으면 낭비가 될 것이다. 그래서 보통은 프로그램이 쭉 메모리 접근만 하면서 실행이 되다가, I/O를 하게 되면 그 I/O는 CPU가 직접하는게 아니라 I/O 컨트롤러한테 시키고 그런 다음에 CPU는 놀지 않고 또 메모리 접근을 계속 하면서 Instruction을 실행하는 것이다.근데, 이 프로그램을 실행하다가 디스크에서 뭘 읽어오라고 했으면 읽어온 결과물을 보고 그 다음 Instruction을 실행하는 게 보통 프로그램의 구조…
예를 들면, CPU가 키보드에서 뭘 읽어오는 Instruction을 만나게 되면 Keyboard controller한테 사용자가 키보드를 두드려서 input이 들어오면 나한테 알려달라고 얘기해놓고, CPU는 키보드에 입력이 들어오는지 안들어오는지는 (오래 걸리는 작업이기 때문에) 개의치 않고, 본인이 할 수 있는 일을 계속 실행하는 것이다.
그러다가 프로그램이 더이상 I/O 들어오는 데이터가 뭔지를 모르고서는 더 이상 실행이 안되겠다라고 하면 CPU가 (당장 CPU만 주면 나는 메모리하고만 접근하면서 Instruction을 실행할 수 있다고 하는) 다른 프로그램한테 넘어간다.즉, CPU는 되게 빠른 일꾼인데 그 빠른 속도를 이용해서 계속 쉬지않고 일만 하는 것…
그래서 프로그램 여러개가 동시에 실행될 때 CPU는 아주 짧은 시간 간격으로 왔다갔다하면서 처리하기 때문에 실제 사용자 입장에서 보면 굉장히 Interactive하게, 빠르게 CPU가 응답해주는 것으로 보이는 것이다.여기서 한 가지 문제는, 만약에 for나 while문을 돌리면서 무한 루프를 도는 프로그램을 만들었는데 이 프로그램한테 CPU가 한번 넘어가면 프로그램이 종료되지도 않고, I/O를 하지도 않고 계속 CPU만 쓸 것이다. 그렇게 되면 CPU가 다른 프로그램한테 넘어가지 못해서 Time Sharing을 구현할 수 없을 것이다. 그래서, 컴퓨터 안에는 timer라는 하드웨어를 두고 있다.
-
timer
- 역할: 특정 프로그램이 CPU를 독점하는 것을 막기 위한 것
컴퓨터 켰을 때 처음에는 운영체제가 CPU를 가지고 있다가, 여러 사용자 프로그램이 실행이 되면 Timer에다가 어떤 값을 셋팅(보통 1초보다 짧게, 수십 ms 정도) 한 다음에 사용자 프로그램에게 CPU를 넘겨준다.
그럼 사용자 프로그램은 독점적으로 CPU를 계속 쓸 수 있는게 아니라 timer에 할당된 시간 만큼 CPU를 사용
CPU는 그 시간 동안 자기 Instruction을 실행하고, 실행하다가 셋팅 된 시간이 다 되면 timer가 CPU한테 Interrupt를 건다.(시간이 끝났다고 알려준다)
CPU는 매번 Instruction을 하나씩 실행하다가 하나의 Instruction이 끝나고 나면, Interrupt line을 체크한다.
즉, CPU는 계속 Instruction만 실행하는 것은 아니고 Instruction 실행하고, Interrupt line 체크. Interrupt 들어온게 없으면 다음 Instruction 실행하고, 끝났으면 Interrupt 들어온게 있는지 Interrupt line 체크… 이 작업이 CPU가 반복하는 작업
그래서 만약에 timer가 Interrupt를 걸어왔으면 CPU는 하던 일을 잠시 멈추고, CPU의 제어권이 사용자 프로그램으로부터 운영체제한테 자동으로 넘어가게 되어있다.
즉, 운영체제가 사용자 프로그램한테 CPU를 줄 때는 자유롭게 주지만 한번 넘어가면 뺏을 수는 없는 것
왜 못뺏느냐? 운영체제가 CPU를 뺏으려면 본인이 CPU를 가지고 있으면서 뭔가 Instruction을 실행을 해야 뺏든지 말든지 하니까(운영체제 본인이 사용자 프로그램한테 CPU를 넘겨준 다음에는 사용자 프로그램이 CPU를 가지고 마음껏 쓰는 것임. 그러면, 운영체제가 아무리 똑똑하게 짜여져 있다고 하더라도 뺏을 방법이 없다.)그래서, 추가적인 timer라는 하드웨어를 둬가지고 이 친구가 timer Interrupt를 걸어주면 CPU제어권이 자동으로 사용자 프로그램으로부터 운영체제로 넘어오도록 그렇게 만든 것.
그렇게 운영체제가 CPU를 얻게 되면? 다음 프로그램한테 timer에다가 값을 셋팅해서 CPU를 넘겨준다. 그리고 만료가 되면 timer interrupt가 들어오고 CPU제어권이 OS로 넘어간다.
위 이미지에선 프로그램이 A, B 두 개 밖에 없으나 프로그램이 N개가 있다고 하면 CPU가 N개의 프로그램한테 하나씩 넘어갔다가 제일 앞 프로그램으로 되돌아오고 이런식으로 CPU가 넘어감…프로그램 본인은 계속 CPU를 쓰고 싶은데 여럿이서 분할해서 써야 되니까 CPU의 time sharing… 나눠서 쓰는 것을 구현하기 위해서 timer라는 것을 두고 있는 것.
그런데, 사실 본인이 계속 CPU를 쓰는게 아니다. 제대로 된 프로그램이라면 메모리하고만 작업을 하는게 아니라 가끔씩 I/O 디바이스에서 읽어오고 출력하는 경우가 반드시 있을 것이다.사용자 프로그램은 본인이 직접 I/O 장치를 접근할 수 없다. I/O 장치를 접근하는 모든 Instruction은 운영체제를 통해서만 할 수 있도록 막아놨다. (이유: 보안)
그렇게 때문에, 프로그램이 디스크나 키보드에서 뭘 읽어와야 된다거나, 화면에 출력하는 등의 I/O 작업을 해야되면 스스로 프로그램이 운영체제한테 CPU를 넘겨준다.
그러면, 운영체제가 해당하는 작업을 I/O Controller한테 시킨다.
시키고 나서 I/O 작업이 오래 걸리니까 운영체제는 다른 프로그램한테 CPU를 넘겨주게 된다.I/O 작업을 요청했던 프로그램은 언제 다시 CPU를 얻게 되느냐?
I/O Controller가 요청한 작업이 끝나서 (예를 들어, 사용자가 키보드 입력을 두드려서 키보드 입력된 데이터가 자신의 buffer에 들어오면 Keyboard Controller가) CPU한테 Interrupt를 걸면 (어떤 다른 프로그램에서 CPU가 실행이 되고 있었겠지만 Interrupt가 들어오면) 기본적으로 CPU제어권은 운영체제한테 자동으로 넘어가게 된다.
그러면 운영체제가 지금 Interrupt가 왜 들어왔는지 살펴본 후, (아까 어떤 프로그램이 요청했던 키보드 입력이 들어온거라면) 입력된 키보드 값을 아까 키보드 입력 요청한 프로그램의 메모리 공간에다가 copy를 해주고, 그런 다음에 보통은 방금 전에 CPU를 쓰다가 Interrupt를 당한 다른 프로그램에 (timer에서 한정된 시간이 아직 남아있다고 하면) CPU를 주어 CPU를 더 쓰게 하고, 할당된 시간이 끝나고 언젠가는 키보드 입력이 들어와서 준비가 완료된 프로그램한테 CPU를 주게 된다.이런식으로 돌아간다.
Mode bit
- 사용자 프로그램의 잘못된 수행으로 다른 프로그램 및 운영체제에 피해가 가지 않도록 하기 위한 보호 장치 필요
-
Mode bit을 통해 하드웨어적으로 두 가지 모드의 operation 지원
- Mode bit
- 1 사용자 모드: 사용자 프로그램 수행
- 0 모니터 모드(= 커널 모드, 시스템 모드): OS 코드 수행, (운영체제가 CPU에서 실행중인 것)
- 보안을 해칠 수 있는 중요한 명령어는 모니터 모드에서만 수행 가능한 ‘특권명령’으로 규정
- Interrupt나 Exception 발생시 하드웨어가 mode bit을 0으로 바꿈
- 사용자 프로그램에게 CPU를 넘기기 전에 mode bit을 1로 셋팅
- Mode bit
Timer(타이머)
- 정해진 시간이 흐른 뒤 운영체제에게 제어권이 넘어가도록 인터럽트를 발생시킴
- 타이머는 매 클럭 틱 때마다 1씩 감소
- 타이머 값이 0이 되면 타이머 인터럽트 발생
- CPU를 특정 프로그램이 독점하는 것으로부터 보호
- 타이머는 time sharing을 구현하기 위해 널리 이용됨
- 타이머는 현재 시간을 계산하기 위해서도 사용
Device Controller
- I/O device controller
- 해당 I/O 장치유형을 관리하는 일종의 작은 CPU
- 제어 정보를 위해 control register, status register를 가짐
- local buffer를 가짐 (일종의 data register, 데이터 저장하는…)
- I/O는 실제 device와 local buffer 사이에서 일어남
- Device controller는 I/O가 끝났을 경우 interrupt로 CPU에 그 사실을 알림
- device driver(장치구동기)
- OS 코드 중 각 장치별 처리 루틴 → software
- device controller(장치제어기)
- 각 장치를 통제하는 일종의 작은 CPU → hardware
사용자 프로그램의 결과를 화면에 출력하기 위해서는,
실제 프로그램에 있는 데이터는 모니터의 local buffer에 담는거고, 화면에 출력을 하라는 지시는 control register(제어 레지스터)를 통해서 한다는 얘기이다.마찬가지로 사용자 프로그램에 있는 어떤 데이터를 파일에다 저장하고 싶다면,
그 데이터 자체는 디스크 쪽 local buffer에 넣고, 파일에 저장하라는 명령은 control register(제어 레지스터)를 통해서 CPU가 I/O Controller에게 전달을 하게 되는 것이다.
위 이미지를 보면 메모리도 하나의 디바이스이기 때문에 메모리를 전담하는 컨트롤러도 있다. (memory controller) 다른 디바이스와 마찬가지로 붙어있고…
메인 메모리는 CPU만 접근하고, I/O 디바이스들은 자기 자신들의 local buffer가 존재하기 때문에 buffer에다가 데이터를 받아서 일을 하고 buffer에 쌓이게 되면 CPU가 buffer에 있는 내용을 읽어서 자신의 작업영역인 메모리에 복사를 한다.
CPU는 메모리 접근도 할 수 있고 local buffer접근도 할 수 있게 되어있고, 작은 CPU(device controller)들은 자기 자신의 local buffer만 접근할 수 있게 이렇게 정의가 되어 있는 것이다.근데 그렇게 하다보니까 CPU가 너무 Interrupt를 많이 당함…
그 얘기는 처리 속도가 굉장히 빠른 CPU라는 장치가 그다지 효율적이지 않게 동작을 한다는 것이다. 그래서 DMA라는 컨트롤러를 하나 더 두고 있다.
DMA(Direct Memory Access) Controller
-
Direct Memory Access Controller, 직접 메모리를 접근할 수 있는 컨트롤러
-
원래는 메모리를 접근할 수 있는 장치는 CPU뿐이었는데, DMA Controller를 두게 되면 CPU도, DMA도 메모리를 접근할 수 있게 되어있다.
-
CPU와 DMA 둘이서 만약에 특정 메모리 영역을 동시에 접근하거나 이럴 때 문제가 생길 수 있어서 memory controller가 그런 동시 접근하는 것을 중재, 조율, 교통정리하는 역할을 해준다.
그럼 DMA는 왜 달아놨는가?
I/O 디바이스들이 너무 자주 Interrupt를 거니까 CPU가 방해를 너무 많이 받는다. 즉, 중간중간에 I/O 디바이스들이 뭔가 작업이 들어왔을 때 그거를 CPU한테 Interrupt를 걸어서 CPU가 local buffer에 있는 내용을 메인 메모리에 copy하게 하는 것이 너무 오버헤드가 크다는 것이다.
그 문제를 해결 하기 위해서 CPU는 계속 자기 일을 하고 있고, 중간중간에 local buffer에 들어오는 내용이 작업이 끝났으면 DMA가 직접 local buffer에 있는 내용을 메모리로 복사하는 일을 하고, 그 작업이 다 끝났으면 CPU한테 Interrupt를 한번만 걸어서 작업이 완료되었음을 알린다.이와 같이, CPU는 중간에 Interrupt 당하는 빈도가 줄어들어서 자신의 빠른 장치를 좀 더 효율적으로 쓸 수 있게 DMA가 역할해주는 것이다.
출처 : https://www.quora.com/What-is-the-function-of-DMA-in-a-computer |
간단하게…
-
DMA (Direct Memory Access)
- 빠른 입출력 장치를 메모리에 가까운 속도로 처리하기 위해 사용
-
CPU의 중재없이 device controller가 device의 buffer storage의 내용을 메모리에 block 단위로 직접 전송
- 바이트 단위가 아니라 block 단위로 인터럽트를 발생시킴
입출력(I/O)의 수행
- 모든 입출력 명령은 특권 명령
- 사용자 프로그램은 어떻게 I/O 하는가?
- 시스템콜(System call)
- 사용자 프로그램은 운영체제에게 I/O 요청
- trap을 사용하여 인터럽트 벡터의 특정 위치로 이동
- 제어권이 인터럽트 벡터가 가리키는 인터럽트 서비스 루틴으로 이동
- 올바른 I/O 요청인지 확인 후 I/O 수행
- I/O 완료 시 제어권을 시스템콜 다음 명령으로 옮김
- 시스템콜(System call)
인터럽트 (Interrupt)
-
인터럽트 당한 시점의 레지스터와 program counter를 save한 후 CPU의 제어를 인터럽트 처리 루틴에 넘긴다
- Interrupt (넓은 의미)
- Interrupt (하드웨어 인터럽트): 하드웨어가 발생시킨 인터럽트
- Trap (소프트웨어 인터럽트)
- Exception: 프로그램이 오류를 범한 경우
- System call: 프로그램이 커널 함수를 호출하는 경우
- 인터럽트 관련 용어
- 인터럽트 벡터
-
해당 인터럽트의 처리 루틴 주소를 가지고 있음
(인터럽트의 종류가 여러가지 있고, 그 인터럽트 종류마다 해야될 일들이 다르다. 그러면 각각의 인터럽트 종류마다 무슨 일을 해야되는지가 운영체제 코드에 다 정의가 되어있다.)
(인터럽트가 들어왔을 때 예를 들어, 1번 인터럽트가 들어왔을 때는 어떤 함수로 가야되고, 2번 인터럽트가 들어왔을 때는 어떤 함수로 들어가야되는지 그런거를 표시를 해줘야 될 것이다.)
그런 표시를 인터럽트 벡터에다가 표시해주고 있는 것이다.
(각 인터럽트 종류마다 그 인터럽트가 생겼을 때 어디 있는 함수를 실행해야되는지 그 함수의 주소들을 쭉 정의해놓은 일종의 테이블 같은 것)(그렇게 각 인터럽트마다 처리해야될 실제 코드를 인터럽트 처리 루틴이라고 부른다.)
-
- 인터럽트 처리 루틴
(= Interrupt Service Routine, 인터럽트 핸들러)- 해당 인터럽트를 처리하는 커널 함수
- 실제로 인터럽트를 처리하는 부분
- 인터럽트 벡터
I/O 요청을 할 때는 소프트웨어 인터럽트를 통해서 요청, I/O가 다 끝났으면 하드웨어 인터럽트를 통해서 끝났다는 것을 알려줌… 그래서 두 가지 다 필요한 것이다!
아까 말했듯이 CPU를 특정 프로그램이 독점하는 것을 막기 위해서 timer Interrupt 라는 것을 사용하고 있고,
그 다음에 요청한 I/O가 다 끝났다는 것을 알려주기 위해서(또는 예를 들어, 키보드에 어떤 입력이 들어왔다는 것을 알려주기 위해서) 그런 것을 전달하는 것은 I/O Controller가 Interrupt를 걸어서 알려주게 되는 것이다.
그래서, “현대의 운영체제는 인터럽트에 의해서 구동된다.” 이런 말을 쓴다.
즉, 운영체제는 CPU를 사용할 일이 없고, 인터럽트가 들어올 때만 CPU가 운영체제한테 넘어가는 것이지 그렇지 않으면 운영체제는 항상 사용자 프로그램이 쓰고 있게 되는 것이다.
시스템콜 (System Call)
- 사용자 프로그램이 운영체제의 서비스를 받기 위해 커널 함수를 호출하는 것
사용자 프로그램이 운영체제한테 뭔가 부탁할 때 시스템콜(System Call)을 통해서 부탁을 하게 되는 것이다. (꼭 I/O만 시스템콜에 의해서 되는건 아님)
즉, 사용자 프로그램이 운영체제 코드를 직접 수행하는 것이 불가능하기 때문에 운영체제에 있는 함수 호출을 할 때는 사용자 프로그램이 Interrupt line을 셋팅하고, 그러면 CPU 제어권이 운영체제한테 넘어가기 때문에 부탁한 일을 운영체제가 할 수 있게 되는 것이다.
System Structure & Program Execution (2)
동기식 입출력과 비동기식 입출력
- 동기식 입출력 (synchronous I/O)
- I/O 요청 후 입출력 작업이 완료된 후에야 제어가 사용자 프로그램에 넘어감
- 구현 방법 1
- I/O가 끝날 때까지 CPU를 낭비시킴
- 매 시점 하나의 I/O만 일어날 수 있음
- 구현 방법 2
- I/O가 완료될 때까지 해당 프로그램에게서 CPU를 빼앗음
- I/O 처리를 기다리는 줄에 그 프로그램을 줄 세움
- 다른 프로그램에게 CPU를 줌
- (보통 여러 I/O 장치로 동시에 실행하기 때문에 CPU의 효율적 운영을 위해서 동기식 입출력을 할 때는 보통 이런식으로 구현을 한다.)
- 비동기식 입출력 (asynchronous I/O)
- I/O가 시작된 후 입출력 작업이 끝나기를 기다리지 않고 제어가 사용자 프로그램에 즉시 넘어감
★ 두 경우 모두 I/O의 완료는 인터럽트로 알려줌
I/O는 커널을 통해서만 할 수 있다고 했었다.
사용자 프로그램이 운영체제 커널한테 I/O 요청을 하게 되면 그 I/O 장치에 맞는 device driver를 거치고 실제 하드웨어를 통해서 I/O를 읽거나 쓰는 작업을 할 것이다. (근데 이 I/O 작업을 하는 것은 시간이 좀 걸리는 작업) 시간이 좀 흐른 뒤에 I/O가 끝났다고 인터럽트를 통해 알리고, I/O 완료된 것이 도착을 하고, 이걸 보고서 사용자가 다음 작업을 한다. 그러면 이것은 Synchronous I/O다.
Asynchronous I/O는 사용자 프로그램이 I/O 요청을 운영체제 커널한테 해서 실제로 I/O 작업이 진행될 것이다. 그러나, 그 진행되는 것을 기다리지 않고 I/O 작업 요청만 해놓고 바로 CPU제어권을 얻어서 다른 작업들을 하는 것.
그렇지만 I/O가 시간이 걸려 다 끝났으면 끝났다고 인터럽트를 통해 알려주게 된다.
I/O를 할 수 있는 방법이 서로 다른 2가지(입출력 명령어)
- I/O를 수행하는 special instruction에 의해
- 좌측에 있는 그림… 일반적인 I/O 방식
- 메모리를 접근하는 Instruction과 별개의 I/O를 하는 special instruction이 따로 존재
- Memory Mapped I/O에 의해
- 우측에 있는 그림
- I/O 장치도 메모리 주소에 연장 주소를 붙인 다음 그 주소에 접근해서 사용
저장장치 계층 구조
그림에는 없지만 맨 위에는 CPU가 있을 것이다.
CPU안에 Registers, Cache Memory가 있다.
그리고, 따로 DRAM으로 구성된 Main Memory가 있다.Secondary에는 하드 디스크부터 마그네틱 테이프까지…
(지금은 하드 디스크 대신에 플래시 메모리 같은게 사용되기도 하는 등… 이러한 스토리지 계층 구조도 약간씩 바뀌고 있다.)
- 위로 갈수록 속도가 빠른 매체를 사용하고 있다.
- 단위 공간당 가격이 비싸기 때문에 위로 갈수록 용량이 적다.
- Volatility… 휘발성 매체냐 아니냐를 말함
Secondary 부분은 컴퓨터 전원이 나가도 사라지지 않는다. ※ 비휘발성 매체
반면 DRAM이나 Cache Memory로 사용되는 SRAM이나 CPU안의 Register는 전원이 나가면 내용이 사라진다. ※ 휘발성 매체 - 보통 (위 이미지) 연두색으로 표시된 것들이 휘발성 매체로 구성이 되고, 아래 분홍색으로 표시된 부분이 비휘발성 매체로 구성이 되는 것이다. (바뀌고 있는 추세긴 함…)
- CPU에서 직접 접근할 수 있는 메모리 스토리지 매체를 Primary라고 하고, 실행가능하다라고 한다.
- CPU가 직접 접근해서 처리 못하는 것을 Secondary라고 부른다.
Caching(캐싱)
CPU가 Instruction을 처리하는데 있어서 DRAM 메모리에 접근하려면 오래걸린다. 그러한 속도차이를 완충하기 위해 중간에 캐시 메모리를 두고 레지스터를 읽어들이는 작업을 하는 것
그러나, 캐시 메모리는 메인 메모리보다 용량이 적기 때문에 모든 걸 담아놓지는 못한다.
따라서, 당장 필요한 것만 밑에서 위로 올려서 쓰는데 그것을 캐싱이라고 한다.
즉, 빠른 매체로 정보를 읽어들여서 쓰는 것, 캐싱은 보통 재사용을 목적으로 한다.
캐싱 기법의 효과
처음 요청 됐을 때는 어쩔수없이 밑에서 위로 읽어 올라가야하지만, 일단 한 번 위로 읽어들여 놓으면 같은 것을 두번째 요청하게 될 때는 밑에까지 가지 않고 (이미 위에 읽어온 데이터가 있으면) 위에서 바로 읽어갈 수가 있다.
그러나 용량이 적기 때문에 밑에서 모든 것을 다 읽어들이지는 못하고, 새로운게 들어오면 기존에 있던 것은 쫓아내야 함 (어떤걸 쫓아내느냐? 그 내용은 메모리 관리에 대한 내용을 다룰 때 자세히 설명될 것이다.)
프로그램의 실행 (메모리 load)
보통 프로그램은 실행파일의 형태로 하드 디스크에 저장되있다.
그 파일을 실행시키게 되면 그게 메모리로 올라가서 프로세스가 된다. 그래서 실행이 되는 것임정확히는 물리적인 메모리에 바로 올라가는 것이 아니라 중간에 한 단계를 더 거치게 되는데, 그게 바로 Virtual memory(가상 메모리)라는 단계가 되겠다.
(아래 이미지)
어떤 프로그램을 실행시키게 되면 그 프로그램에 주소 공간(메모리 주소 공간)이 형성이 된다.
그 프로그램 자기 자신만의 독자적인 주소 공간이 생기게 되는데…
이러한 주소 공간은 코드(code), 데이터(data), 스택(stack) 이런 영역으로 구성된다.
코드는 CPU에서 실행할 (프로그램의) 기계어 코드를 담고 있다.
데이터는 변수 같은 그 프로그램이 사용하는 자료구조를 담고 있다.
스택은 코드가 함수 구조로 되어있기 때문에 함수를 호출하거나 리턴할 때 어떤 데이터를 쌓았다가 꺼내가는 용도로 사용하는 곳모든 프로그램이 이러한 독자적인 주소 공간을 가지고 있는데, 이거를 물리적인 메모리에 올려서 실행을 시키는 것
근데, 물리적인 메모리의 커널 부분은 (컴퓨터를 켜서 부팅하고나면) 메모리에 항상 상주해서 올라가 있지만, 사용자 프로그램들은 실행을 시키면 주소 공간이 생겼다가 프로그램을 종료시키면 사라진다.프로그램을 실행시켰을 때 만들어진 주소 공간을 물리적인 메모리에 통째로 다 올려놓는 것이 아니다. (메모리가 낭비되기 때문에) 당장 필요한 부분… 예를 들어, a라는 함수를 실행하고 있다고 하면 거기에 해당하는 코드만 물리적 메모리에 올려놓고, 그렇지 않은 것은 올리지 않는다.
그리고, 사용이 안되면 물리적 메모리에서 쫓아낸다.
(경우에 따라서는 프로그램이 종료되기 전까지 메모리에 올라와서 보관하고 있어야 되는게 있는 경우도 있다.)
당장 필요한 것은 물리적인 메모리에 올려 놓고, 그렇지 않은 부분은 하드 디스크의 Swap area라는 곳에 내려놓는다.
(Swap area로 사용한 하드 디스크 부분은 전원이 나가면 의미 없는 데이터이다. 전원이 나가면 프로세스가 종료되면서 메모리에 있는 내용이 사라지기 때문에, Swap area에 있는 내용도 의미가 없는 정보가 되는 것이다. 그래서 Swap area는 메모리 용량의 한계로 메모리 연장공간으로써 사용하는 것, 하드디스크의 File system 부분은 내용이 유지되는 비휘발성 용도로 사용된다.)즉, 프로그램마다 만들어진 이러한 독자적인 주소 공간은 사실 머릿속에만 있는 주소 공간이지 실제로 연속적으로 어디 할당되는 것이 아니라 쪼개져서 어떤 부분은 물리적 메모리에 와있고, 어떤 부분은 Swap area에 내려와있고 이렇게 되는 것이다. 그래서 Virtual Memory(가상 메모리)라고 부르는 것이다.
물리적인 메모리에 올라갈 때는 메모리 주소가 변환되어 올라간다.
커널 주소 공간의 내용
프로그램의 실행 2번째 이미지에 있는 커널(운영체제 커널) 주소 공간을 옆으로 그려놓은 이미지 |
커널의 코드(code) 영역에는 위와 같은 내용들의 코드들이 들어있다.
커널의 데이터(data) 영역에는 운영체제가 사용하는 여러 자료구조들이 정의되어있다.
운영체제는 CPU나 메모리나 디스크 같은 하드웨어들을 직접 관리하고 통제함, 하드웨어들을 직접 관리하기 위해서 하드웨어 종류마다 자료구조를 하나씩 만들어서 관리를 하고 있을 것. 그런 모습을 추상적으로 그려놓은 것임.
(육면체는 실제 CPU, 메모리, 디스크 등의 하드웨어들을 뜻하고, 커널 주소 영역에 있는 사각형은 그러한 하드웨어를 관리하기 위한 자료구조를 그려 놓은 것)
또, 운영체제는 프로세스들(현재 실행중인 프로그램들)을 관리 해야함.
각 프로그램들이 독자적인 주소 공간을 가지고 있지만 이러한 것들을 관리하기 위해서는 각 프로그램마다 운영체제가 관리하고 있는 자료구조가 필요하다. 그것을 PCB(Process Control Block)라고 부른다. 프로세스마다 PCB가 하나씩 만들어져서 관리하고 있다.운영체제도 함수 구조로 코드가 짜여져 있기 때문에, 함수를 호출하거나 리턴할 때 스택 영역을 사용해야된다. 그래서 커널 스택(Stack)이 있다.
운영체제 코드는 여러 사용자 프로그램들이 요청에 따라서 불러서 쓸 수가 있다.
그래서, 사용자 프로그램들이 운영체제 커널의 코드를 불러서 실행을 하기 때문에 커널의 코드에서 함수 호출을 하게 되면 커널 스택을 쓸 때 어떤 사용자 프로그램이 커널의 코드를 실행 중인가에 따라서…
즉, 사용자 프로그램마다 커널 스택을 따로 두고 있다. 그게 프로세스마다 커널 스택을 별도로 그려 놓은 이유가 되겠다.
사용자 프로그램이 사용하는 함수
- 함수 (function)
- 사용자 정의 함수
- 자신의 프로그램에서 정의한 함수
- 라이브러리 함수
- 자신의 프로그램에서 정의하지 않고 갖다 쓴 함수
- 자신의 프로그램의 실행 파일에 포함되어 있다
- 커널 함수
- 운영체제 프로그램의 함수
- 커널 함수의 호출 = 시스템 콜
- 사용자 정의 함수
이미지 : 그냥 참고… |
사용자 정의 함수든, 라이브러리 함수든 어쨋든 컴파일해서 실행 파일을 만들게 되면, 이 함수들과 코드 자체도 내 프로그램 안에 포함이 되어 있는 것.
반면, 커널 함수는 운영체제 안에서 정의된 함수고, 내가 프로그램 안에서 시스템 콜을 통해서 가져다 쓸 수 있지만, 내 프로그램 안에 들어있는 함수가 아니라 커널 코드 안에 들어있는 함수
따라서,내 프로그램 안에는 커널 함수의 정의가 없고, 호출만 하게 됨.
시스템 콜을 통해 커널 주소 공간의 코드 영역으로 넘어가서 커널에 있는 함수를 호출함.
(커널의 함수를 호출할 때는 시스템 콜을 통해서 인터럽트 라인을 셋팅해서 CPU제어권이 OS쪽으로 넘어가게 해서 커널 함수를 실행)
프로그램의 실행
A라는 프로그램이 시작이 되서 종료될 때까지의 모습 (A라는 프로그램 본인이 CPU를 가지고 있는 동안만 간단히 표현되있는 그림) |
프로그램이 직접 CPU를 잡고 있으면 user mode에 있다고 한다.
프로그램이 user mode 안에서 자기가 정의한 사용자 정의 함수를 호출해서 쓸 수도 있다. 그래도 여전히 user mode에서 실행이 되는 것이다.그러다가 만약 시스템 콜을 하게 되면 그 프로그램의 주소 공간에 있는 코드가 아니라 커널의 커널 주소 공간에 있는 코드가 실행된다. 그때는 kernel mode에서 CPU가 동작 중인 것이다.
그러다가 시스템 콜이 끝나게 되면 다시 A라는 프로그램한테 CPU제어권이 넘어오고 본인의 주소 공간에 있는 코드를 실행을 할 것이다.
그러다 경우에 따라서는 라이브러리 함수 같은 것을 호출하지만, 여전히 자기 주소 공간에 있는 코드를 실행하는 것그러다가 또 시스템 콜을 하면 CPU제어권이 A로부터 커널로 넘어가고, 커널이 커널 주소 공간에 있는 코드를 실행한다.
이런식으로 프로그램이 진행되기 때문에 프로그램은 태어나서 죽을 때까지 user mode, kernel mode, user mode, kernel mode를 반복하면서 실행을 하다가 언젠가는 (만약, C언어로 프로그램을 짰다면 메인 함수에서 중괄호를 닫는 지점에 다다르면) 프로그램이 종료가 되는 것이다.
Leave a comment