이번 포스팅은 System Structure & Program Execution에 대해 알아보겠습니다!
컴퓨터 시스템 구조는
Computer(CPU, Memory), I/O Device(Disk, Keyboard, Printer, Monitor)로 구성되어 있다.
메모리라는건 CPU의 작업 공간을 의미한다.
CPU는 기계어를 하나 씩 읽어서 실행한다.
I/O Device는 별 개의 디바이스이다.
예를 들어 키보드를 두들기면 정보가 컴퓨터로 들어간다! 키보드는, Input 디바이스이다.
프린터나 모니터같은건 결과를 내보내기 때문에 Output 디바이스이다.
하드디스크는 보조기억장치이지만, I/O Device라고도 볼 수 있다. Input Device로서의 역할도 하고, Output Device의 역할도 수행하는 장치이다.
I/O Device는 그 디바이스를 전담하는 CPU가 붙어있게 되고 이를 Device Controller가 붙어있다.
디스크에서 헤드가 어떻게 움직이고, 어떤 데이터를 읽을지 디스크의 내부를 통제하는 것은 CPU의 역할이 아니고 디스크에 붙어 있는 Device Controller의 역할이다.
키보드, 프린터, 모니터도 모두 Device Controller가 있다.
Device Controller도 CPU와 마찬가지로 작업공간이 필요하다. 그걸 우리가 Local Buffer라고 부른다.
CPU와 I/O Device는 처리하는 속도차이가 많이난다. CPU에 비해서 Disk는 백만배느리다.
CPU는 매 클럭마다 메모리에서 기계어를 한 줄씩 읽어서 실행하고 그 다음 기계어를 읽어서 실행하고 이런 작업만 계속한다. CPU는 평생 이 작업만 하는 것이다.
그리고 CPU안에는 메모리보다 더 빠르면서 정보를 저장할 수 있는 작은 공간들이 있는데 이걸 레지스터라고 부른다. CPU안에 mode bit이라는 것이 있는데 CPU에서 실행되는 것이 운영체제인지? 아니면 사용자 프로그램인지를 구분해 주는 것이다.
mode bit이 1이면 사용자 프로그램이 CPU를 가지고 있고
mode bit이 0이면 운영체제가 CPU를 가지고 있다는 뜻이다.
또 하나, CPU에 붙어있는 게 Interrupt line인데
키보드에서 어떤 입력이 들어왔다든지, 디스크에서 뭘 읽어와야된다는지, 디스크에서 뭘 읽어오라고 요청을 했는데 이 일을 다 끝냈다든지, 이런 거를 CPU가 어떻게 아느냐면 이를 전달하기 위해서 Interrupt line이 붙어있는 것이다.
CPU는 메모리하고만 일한다!
CPU를 c언어로 만든 프로그램 A가 가지고 있다고 하면, 이 프로그램이 CPU에서 기계어를 실행하다가, 경우에 따라서 scanf같은걸 통해서 키보드 입력을 받을 수 있고 디스크에서 파일을 읽어와서 실행하는 경우가 있을 수 있다.
프로그램을 작성하다보면 변수만 적어서 작업을 하는(메모리만 사용하는 것)게 아니라 키보드에서 뭘 읽어오고, 화면에 뭘 출력하고, 디스크에서 뭘 읽어오거나 처리한 결과를 디스크에 쓰거나.. 이런식으로 프로그램 구조가 되어있다.
이런 I/O Device들을 접근하는 기계어들이 있다. CPU는 I/O Device에 직접 접근하지 않고 메모리에서 읽어서 실행만한다… 그러다가 디스크에서 뭘 읽어오라는 요청은 어떻게 보내냐면, Disk Controller한테 디스크에서 특정 데이터를 어딘가에서 읽어오라고 일을 시킨다. 일을 시키는 기계어가 있는 것이다. 일을 시켜놓으면 Disk는 데이터를 읽어오고 읽어오는 작업은 오래걸린다… 디스크는 시킨일을 하면서 읽어다가 local buffer에 집어넣게 된다. 그러는 동안에 CPU가 놀고 있으면 낭비가 되기 때문에 보통은 프로그램이 메모리 접근만하다가 I/O Device 명령어라면 그건 CPU가 직접하는게 아니라 이 Device Controller한테 시키고 CPU는 놀지 않고 메모리 접근을 하면서 기계어를 실행한다.
프로그램을 실행하다가 scanf처럼 키보드한테 뭘 입력받으라고 하면 Keyboard Controller한테 이야기를 한다. 사용자가 키보드를 두들겨서 인풋이 들어오면 그 때 나한테 알려달라! 오래걸리는 작업이기 때문에 CPU는 입력이 들어오는지 아닌지 개의치 않고 본인이 할 수 있는 일을 계속 실행한다.
그 중에서는 프로그램이 더 이상 I/O가 뭔지는 모르고 실행이 안되겠다.. 라면 CPU가 다른 프로그램한테 넘어가게 된다.
CPU는 빠르면서 쉬지않고 일을한다!
프로그램 여러개가 동시에 실행될 때 CPU는 빠르게 왔다갔다 거리며 처리하는 것이고, 사용자 입장에서 보면 굉장히 Interactive하게 보인다.
만약에 무한루프를 하는 프로그램이여서 계속 CPU만 쓴다면? 프로그램이 종료되지도 않고 I/O를 하지도 않고 계속 CPU만 쓸 수가 있는데 CPU가 다른 프로그램한테 넘어가지 못해서 Time Sharing을 구현할 수가 없다. 그래서.. 컴퓨터안에는 타이머라는 하드웨어를 둔다!!
타이머라는 하드웨어의 역할은 특정 프로그램이 cpu를 독점하기 위한 것을 막는다. 처음에는 운영체제가 cpu를 가지고 있다가 여러 사용자 프로그램이 실행되면 그 프로그램한테 CPU를 넘겨주는데 타이머에 값을 세팅하고 프로그램에 CPU를 넘겨주는 것이다. 독점적으로 CPU를 계속 쓰는게 아니다.
세팅된 시간이 되면 타이머가 CPU한테 인터럽트를 건다. 세팅해놓은 시간이 끝났다고 알려주면 CPU는 매번 기계어를 실행하다가 Interrupt line을 체크한다. 계속 Instruction만 실행하는건 아니고 기계어 실행 → Interrupt line체크 → 기계어 실행 → Interrupt line 체크가 반복된다.
만약 타이머가 인터럽트를 걸어왔으면 cpu는 하던일을 잠시 멈추고 cpu의 제어권이 사용자 프로그램으로부터 운영체제로 자동으로 넘어가게 되어있다. 운영체제가 사용자 프로그램에게 cpu를 줄 때는 자유롭게 주지만 한 번 넘어가면 뺏을수는 없는거다.. 뺏으려면 본인이 cpu를 가지고 있으면서 기계어를 실행해야 뺏든지하는것이기 때문이다.. cpu를 사용자 프로그램이 가지고 있기 때문에 운영체제가 아무리 똑똑하게 짜여져 있다고 하더라도 뺏을 방법이 없고 추가적인 하드웨어를 둬서 타이머 인터럽트를 걸어주면 cpu제어권이 자동으로 사용자 프로그램으로부터 운영체제로 넘어가도록 만든다!!
그래서 운영체제가 cpu를 얻게 되면 다음 프로그램에 넘겨주고 타이머에 그 시간초를 세팅하고 넘겨준다. 그러면 또 그 프로그램이 cpu를 쓰다가 그 시간이 만료되면 타이머 인터럽트가 들어오고 cpu제어권이 프로그램으로부터 os로 넘어간다.
만약 프로그램이 무한루프가 아니라 종료가 되는 로직이 있다면 cpu를 운영체제에 자동으로 반납하게 된다. 본인은 계속 cpu를 쓰고 싶은데 분할해서 써야 되니까(1장에서 배운 CPU의 Time Sharing) 타이머를 두는 것이다.
만약 프로그램에 코드가 디스크에서 파일을 읽는다던지 모니터에 출력한다던지.. I/O를 해야되면 이 프로그램이 자진해서 운영체제에 I/O를 해달라고 cpu를 넘겨주게 된다. 사용자 프로그램은 본인이 직접 I/O장치에 접근할 수가 없다.
I/O장치에 접근하는 모든 기계어는 운영체제를 통해서만 할 수 있도록 막아놨다!!
스스로 운영체제한테 CPU를 넘겨주고 운영체제가 해당하는 작업을.. I/O컨트롤러한테 시키는 것이다! 시키고 나서 이 작업들이 오래걸리기 때문에 운영체제는 요청한 프로그램에 CPU를 넘기는 것이 아니라 다른 프로그램한테 CPU를 넘겨주게 된다.
그러면 요청한 프로그램은 언제 CPU를 얻게 되느냐..??
키보드에서 뭔가 입력이 들어와야지 그 다음 처리를 하기 때문에 언제까지 기다리냐면 I/O컨트롤러가 요청한 작업이 끝나서(키보드 입력된 데이터가 자신의 버퍼에 들어왔다?!) 그러면 키보드 컨트롤러가 CPU한테 인터럽트를 걸고, 어떤 프로그램이 CPU에서 실행이되고 있었겠지만 인터럽트가 들어오면 기본적으로 CPU제어권이 운영체제에게 자동으로 넘어가게 된다. 그러면 운영체제가 인터럽트가 왜 들어왔는지? 아까 요청했던 걸 확인하고 입력된 키보드 값을 아까 키보드 입력을 요청한 메모리 공간에 카피를 해주고 그런 다음 방금전에 누군가가 CPU를 쓰다가 인터럽트를 당했으니까 보통은 아까 받았던 프로그램에게 다시 CPU를 준다.
Mode bit이라는 건 cpu가 운영체제가 가지고 있으냐 사용자 프로그램이 가지고 있는것이냐를 나타낸다!
Mode bit은 사용자 프로그램의 잘못된 수행으로 다른 프로그램 및 운영체제에 피해가 가지 않도록 하기 위한 보호 장치가 필요하기 때문에 나온 것이다.
Mode bit을 통해 하드웨어적으로 두 가지 모드의 operation을 지원한다.
- 사용자 모드: 사용자 프로그램 수행
- 모니터 모드(커널 모드, 시스템 모드): OS 코드 수행
보안을 해칠 수 있는 중요한 명령어는 모니터 모드에서만 수행 가능한 “특권명령”으로 규정한다!
Interrupt나 Exception 발생시 하드웨어가 Mode bit을 0으로 바꾼다.
사용자 프로그램에 CPU를 넘기기 전에 Mode bit을 1로 세팅한다.
Mode bit이 0일 때(운영체제가 CPU에서 실행중 일 때) 무슨일이든 다 할 수 있도록 정의가 되어 있다. 메모리 접근 뿐만 아니라 I/O 디바이스에 접근하는 기계어들.. 이다! Mode bit이 0일 때는 이 기계어들을 실행할 수 있는 것이다!
그런데, Mode bit이 1일 때는 제한된 기계어만 CPU에서 실행할 수 있게 되어있다! 보안상의 목적이 있다. 그래서 만약 사용자 프로그램이 I/O에 접근한다던지 다른 프로그램의 메모리 공간에 접근한다던지의 시도를 하면 Mode bit이 1인 걸 보고 기계어가 실행이 안되게 하드웨어적인 구현을 해놓은 것이다.
Interrupt가 들어오게 되면 CPU제어권이 운영체제로 넘어가면서 Mode bit이 0으로 바뀐다.
타이머 정리!
- 정해진 시간이 흐른 뒤 운영체제에게 제어권이 넘어가도록 인터럽트를 발생시킴
- 타이머는 매 클럭 틱 때마다 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가 끝났을 경우 인터럽트로 CPU에 그 사실을 알림
Device driver(장치구동기)
- OS 코드 중 각 장치별로 처리하기 위해 그거에 맞게 접근하게 해주는 소프트웨어 → Software
Device Controller(장치제어기)
- 각 장치를 통제하는 일종의 작은 CPU → Hardware이다.
제어정보를 가진 레지스터라는 건 CPU가 I/O Device에 일을 시킬 때 그 레지스터를 통해 무슨 일을 해라! 라고 지시하기 위한 것이다.
데이터를 담는 로컬 버퍼는 담은 데이터를 넘겨주거나 화면에 출력하기 위해서는 로컬 버퍼에 담아 화면을 출력하라는 식이다! 실제로 데이터를 담는건 로컬 버퍼에 담는것이고, 실제 지시를 하는 건 제어 레지스터를 통해 하는 것이다.
어떤 데이터를 파일에 저장하고 싶다면 데이터 자체는 로컬 버퍼에 넣고 파일을 저장하라는 명령은 제어 레지스터를 통해서 cpu가 i/o컨트롤러에 전달한다.
메모리를 전담하는 컨트롤러도 있다!
Memory Controller
메인 메모리는 원칙적으로 CPU만 접근할 수 있도록.. 만든다.
I/O Device는 자기 자신들의 로컬 버퍼가 존재하기 때문에 버퍼에 데이터를 받아서 일을 하고 버퍼에 쌓이게 되면 CPU가 거기에 있는 내용을 읽어서 자신의 작업영역인 메모리에 복사를 하고.. CPU는 사실 메모리 접근도 할 수 있고 로컬 버퍼 접근도 할 수 있다.
이러한 작은 cpu들은 자신의 자신의 로컬 버퍼만 접근할 수 있다.
그렇게 하다 보니까 CPU가 너무 인터럽트를 많이 당한다. 너무 많이당하면 빠른 장치인 CPU가 효율적이지 않게 동작을 한다. 그래서.. DMA라는 장치를 하나 더둔다.
DMA(Direct Memory Access)
직접 메모리를 엑세스할 수 있는 컨트롤러이다..
원래는 메모리를 접근할 수 있는 장치는 CPU뿐이지만 DMA를 두게 되면 이 메모리를 CPU도 접근할 수 있고 DMA도 접근할 수 있게 되어 있다.
그래서 만약 CPU도 메모리에 접근하고 DMA도 메모리에 접근하는 경우 문제가 생길 수가 있어서 메모리 컨트롤러는 그런걸 중지하는 역할을 담당한다.
만약, CPU도 메모리에 접근해서 데이터를 쓰고 있는데 DMA도 메모리에 접근해서 데이터를 쓰면 일관성이 깨지는 문제가 생겨서 누가 먼저 접근하게 할지 교통정리하는 역할을 Memory Controller가 해준다..
그럼 DMA는 왜 달아놓은 것인까?
DMA가 아니라 CPU만 메모리를 쓰면 편한데, I/O장치가 너무 자주 인터럽트를 거니까 CPU가 방해를 받는다. 중간중간에 CPU가 로컬버퍼에 있는 내용을 메모리에 카피하는게 너무 오버헤드가 크다!
그래서 그런 식으로 하지 않고 로컬버퍼에 들어오는 내용이 작업이 끝났으면 DMA가 직접 로컬버퍼에 있는 내용을 메모리로 카피하는 작업을 해준다!
그래서 그 작업이 끝났으면 CPU한테 인터럽트를 한 번만 걸어서 그 내용이 메모리에 올라왔다고 보고한다.
그렇게 되면 CPU가 중간에 인터럽트 당하는 빈도가 줄어들어서 이 빠른 CPU장치를 효율적으로 사용할 수 있다.
결국 cpu의 일은 본인이 실행해야 할 기계어의 메모리주소를 레지스터 중에서 pc(program counter)라는 레지스터가 다음번에 어디 있는 기계어를 실행해야 할지 주소를 가지고 있다. 그래서 매번 그 기계어만 실행하는게 CPU의 운명이다!
만약 그 기계어 중에서 I/O장치에 접근해야 하는 그런 상황이면 디바이스 드라이버를 통해서 I/O한테 읽으라던지 쓰라던지 명령을 하게 되는 것이다!
디바이스 드라이버가 디스크에서 실제로 헤드를 움직이고 읽고 쓰는 코드는 아니다. 그 코드는 디스크 컨트롤러가 이 코드의 지시를 받아서 일을한다!
Device Driver도 본인이 일을 하기 위해서 메뉴얼이 있다. 그거를 Disk안에 펌웨어라고 해서 동작하기 위해 무슨일을 해야 하는지..가있다.
Device Driver는 CPU가 실행하기 위한 코드를 담고 있는 것이다. CPU는 직접 일을 하는게 아니라 메뉴얼대로 일을 한다. 메뉴얼에는 메모리 몇번지에 있는 일을 하라고 적혀있고 몇번지에 적혀 있는지 일을 한다. 그 기계어를 실행하고 나면 메뉴얼의 다음 페이지에 있는 기계어를 실행하는 것을 반복한다. 이 전체적인 통제를 운영체제가 한다.
<입출력의 수행>
모든 입출력 명령은 특권 명령이다.
사용자 프로그램은 운영체제에게 I/O를 요청한다.
System Call은 사용자 프로그램이 운영체제의 커널 함수를 호출하는 걸 시스템 콜이라고 부른다!
Device Controller는 CPU한테 인터럽트를 걸수가 있다. 그런 식으로 시스템콜을 하는거다. 내가 내 프로그램을 실행하다가 I/O를 해야 한다고 하면 직접 메모리에서 주소 점프를 못한다.. 왜냐하면 지금 사용자 프로그램이라 Mode bit이 1이고 프로그램 안에서만 점프가 가능하고 함수 호출이 가능한 건데 I/O를 하기 위해서 운영체제에 해당하는 주소로 넘어가야 한다. 하지만 Mode bit이 1이기 때문에 접근이 허용이 되지 않는다.
→ 이를 해결하기 위해 프로그램이 직접 인터럽트 라인을 세팅하는 명령어를 실행한다. CPU는 프로그램을 가지고 있으면 실행할텐데 명령어를 한 줄 실행하고 인터럽트 들어온게 있으면 체크를 한다. 아까처럼 타이머가 인터럽트를 걸거나 Device Controller가 인터럽트를 거는 것이 아니고 프로그램이 운영체제에게 뭔가 요청하기 위해 소프트웨어적으로 인터럽트를 걸 수가 있고 CPU는 다음 기계어를 실행하는 것이 아니고 Mode bit이 0으로 바뀌고 CPU제어권이 운영체제에게 넘어간다. 그러면 디스크에서 뭘 읽어오라는 시스템 콜이면 운영체제가 CPU를 가지고 있어서 Disk Controller에게 그걸 읽어오라고 부탁할 수 있는 것이다!!
그래서 인터럽트는 하드웨어 일꾼들이 CPU에게 뭔가 정보교신을 위해 걸어줄 수도 있고 사용자 프로그램이 돌아가다가 내가 직접 처리못하고 운영체제에게 대신 해달라고 요청할 때 인터럽트 라인을 걸 수 있다.
정리하자면, 인터럽트는 인터럽트 당한 시점의 레지스터와 Program Counter를 Save한 후 CPU의 제어를 인터럽트 처리 루틴에 넘긴다
인터럽트는 하드웨어가 발생시킨 인터럽트인 Interrupt와, 소프트웨어 인터럽트인 Trap으로 나뉜다. Trap에는 프로그램이 오류를 범한 경우의 Exception과 프로그램이 커널 함수를 호출하는 경우인 System call로 나뉜다.
일반적으로 Interrupt를 걸었다고 하면 하드웨어 인터럽트를 말한다. 타이머 인터럽트나 I/O 컨트롤러가 거는 인터럽트..
넓은 의미의 인터럽트는 하드웨어와 소프트웨어 인터럽트를 둘 다 포함한다.
I/O를 요청할 때 발생하는 인터럽트는 소프트웨어 인터럽트이다. OS가 Disk 에게 I/O하라고 시키고 CPU는 다른 프로그램한테 제어권이 넘어가고 시킨일이 다 끝나면 그 때는 하드웨어 인터럽트가 걸린다.. 일을 다 했다고 CPU에게 알려준다
→ I/O 요청을 할 때는 소프트웨어 인터럽트를 통해서 하고 다 끝났으면 하드웨어 인터럽트를 통해서 끝났다고 알려주기 ,때문에 두 가지 다 필요하다!
<인터럽트 사용>
- CPU를 특정 프로그램이 독점하는 것을 막기 위해서 Timer 인터럽트를 사용(하드웨어 인터럽트)
- 요청한 I/O가 다 끝났다는 것을 알려주기 위해서 I/O컨트롤러가 인터럽트를 거는 경우(하드웨어 인터럽트)
- 사용자프로그램에서 I/O요청을 할 때 거는 인터럽트(소프트웨어 인터럽트)
- 사용자 프로그램에서 Exception(소프트웨어 인터럽트)
현대의 운영체제는 인터럽트에 의해 구동된다!
운영체제는 CPU를 사용할 일이 없고 인터럽트가 들어올 때만 CPU가 운영체제에게 넘어가는거지 그렇지 않을 때는 운영체제는 항상 사용자 프로그램이 쓰고 있게 되는 것이다.
사용자 프로그램이 운영체제에게 부탁할 때 시스템 콜을 통해 부탁하는 것이고, 사용자 프로그램이 운영체제 코드를 직접 수행하는 것이 불가능하기 때문에 사용자 프로그램이 인터럽트 라인을 세팅하고 CPU제어권이 운영체제에게 넘어가기 때문에 부탁한 일을 운영체제가 할 수 있게 된다.
인터럽트 관련 용어에는 인터럽트 벡터와 인터럽트 처리 루틴이라는게 있다. 인터럽트가 종류가 굉장히 많다. 타이머가 걸어주는 인터럽트가 있고 키보드가 거는 인터럽트가 있는데 각각의 인터럽트 종류마다 운영체제가 해야할 일이 다르다. 키보드 인터럽트가 걸려왔으면 키보드버퍼에 있는 내용을 메모리로 카피하고.. 키보드I/O를 요청했던 프로세스에게 CPU를 얻을 수 있다라는걸 표시하고 타이머가 인터럽트를 걸었으면 CPU를 뺏어서 다른 프로세스에게 CPU를 주는 등이다!
→ 각각의 인터럽트 종류마다 무슨일을 해야 하는지가 운영체제 코드에 다 정의가 되어 있다. 운영체제를 누군가가 만들면서 어떤 인터럽트가 들어오면 어떤 일을 하고! 그래서 운영체제가 처리하는 실제 코드를 인터럽트 처리 루틴이라고 한다. 1번 인터럽트가 들어오면 어떤 함수로 가야되고, 2번 인터럽트가 들어오면 어떤함수로 가야하는지 표시하는게 인터럽트 벡터이다!
- 인터럽트 벡터: 해당 인터럽트의 처리 루틴 주소를 가지고 있음
- 인터럽트 처리 루틴: 해당 인터럽트를 처리하는 커널 함수
다음 포스팅에서 System Structure & Program Execution에 대해서 더 자세히 알아보겠습니다!

'Dev > OS' 카테고리의 다른 글
[OS] 운영체제 완전타파 - (2-2) (0) | 2022.06.20 |
---|---|
[OS] 운영체제 완전타파 - (1) (0) | 2022.06.17 |