88.10. 36p 멀티태스킹의 세계 글/ 편집부 PC의 능력이 점점 거대화되면서 지금까지의 PC와는 다른 모습으로 그 세계가 점점 넓어지고 있다. 경이적인 32비트 마이크로프로세서를 채택한 몇몇 PC들은 이제 처리속도 면에서나 메모리,또는 디스크 용량 등에서 워크스테이션이나 미니의 영역까지 침범해 들어가고 있는 실정이다. 이렇듯 PC의 하드웨어 분야는 거대한 발전을 거듭하고 있다. 하지만 PC의 운영체제는 아직 이러한 하드웨어의 발전을 뒷받침해주지 못하고 있는 형편이다. PC상위기종에서는 오래 전부터 멀티 태스킹-멀티 유저 기능을 갖춘 강력한 운영체제들이 사용되어져 왔다. 그러나 PC시장에서 가장 우위를 자랑하고 있는 IBM PC의 운영체제인 MS-DOS는 아직도 단일 사용자용으로서 한 번에 한 가지 일밖에 처리할 수 없도록 설계되어 있다. PC의 능력이 한 사람의 업무를 처리하기에도 모자랐던 시절에는 멀티 태스킹이고 뭐고 생각할 여유가 없었으나 이제는 PC의 하드웨어는 멀티 태스킹을 지원하기에 충분하고도 남을만큼 발전하였다. 이렇게 발전한 하드웨어에 지금 쓰고 있는 MS-DOS와 같은 운영체제를 사용한다는 것은 다 자란 어린이에게 아기적에 입던 옷을 입히려는 것이나 다름없는 것이다. 바야흐로 이제 PC는 자기 몸에 맞는 옷을 강력히 요구하고 있다. 이러한 시대적인 요청에 부응하여 PC에서의 멀티 태스킹을 위한 환경들이 쏟아져 나오고 있다. 국내에서는 아직까지 별다른 움직임이 보이지 않으나 PC의 본고장인 미국에서는 IBM PS/2를 위한 새로운 운영체제인 OS/2가 컴퓨터 사용자들의 화제가 되고 있으며, 마이크로소프트사의 윈도우즈나 제닉스 또는 유닉스 등 멀티 태스킹을 위한 환경에 점차 관심이 집중되고 있다. 본고에서는 우선 멀티 태스킹이란 무엇이며 왜 필요한지,그리고 멀티 태스킹을 위해서는 어떤 요소들이 요구되는지를 알아보고, 멀티 태스킹을 위한 움직임을 살펴 보기로 하겠다. 멀티 태스킹이란? 멀티 태스킹과 멀티 프로그래밍의 관계 일반적으로 많은 사람들이 다음의 네 가지 용어를 잘 구별하지 못하고 있다. (1) 멀티 태스킹(multi-tasking) : 다중 작업,다중 처리 (2) 멀티 유저(multi-user) : 다중 사용자 (3) 멀티 프로그래밍(multi-programming) : 다중 프로그래밍 (4) 멀티 프로세싱(multi-processing) : 다중 프로세서 여기서 멀티 프로그래밍은 '메모리내에 한 가지 이상의 프로그램이 존재하여 이들이 CPU를 분배하여 사용하는 것'이라고 정의할 수 있다. 이때 CPU는 이들 프로그램 사이를 빠르게 전환해 가면서 처리하므로 겉으로는 이들 프로그램이 동시에 수행되는 것처럼, 즉 각각이 CPU를 독점하고 있는 것처럼 보이게 된다. 멀티 프로세싱이란 '한 컴퓨터 시스템내에 여러 개의 프로세서(CPU)가 존재하여 이들이 각각 작업을 분담하는 것'이라고 말할 수 있다. 비근한 예로 IBM PC에 꽂아 사용하는 수치 연산보조 프로세서인 8087시리즈 등이 그 경우이다. 사실 본격적인 멀티 프로세싱 시스템은 여러 프로세서가 메모리를 공유하고 서로간에 통신을 하면서 작업을 진행함으로써 더욱 높은 성능을 발휘하도록 되어 있다. 멀티 프로세싱 시스템은 대개 멀티 프로그래밍 시스템이 되기 마련이다. 이 글에서는 멀티 프로세싱 시스템에 대해서는 다루지 않을 것이므로 앞으로 나오는 논의는 모두 하나의 CPU를 가진 시스템을 가정하는 것으로 한다. 멀티 태스킹은 멀티 프로그래밍과 비슷한 의미로 '한 컴퓨터 시스템이 여러 개의 응용 프로그램을 동시에 수행하는 것'이라고 일단 정의 하기로 하자. 여기서 중요한 것은 원래 멀티 태스킹은 사용자들의 입장에서 나온 개념이라는 점이다. 예를 들어 IBM PC에서 키보드 제어를 하는 램상주 프로그램(수퍼키 같은 것)을 띄우고 워드프로세서를 사용한다고 했을 때 메모리에는 분명히 도스, 램상주 프로그램, 그리고 워드프로세서 프로그램이 계속 실행되고 있다. 그렇다고 이러한 체제를 멀티 태스킹이라고 하지는 않는다. 그 이유를 간략히 말하면 겉으로 보기에는 워드프로세서만 실행되고 있기 때문이다. 즉, 사용자가 원하는 응용 프로그램들을 동시에 돌릴 수 없다면 멀티 태스킹 시스템이라고 말할 수 없는 것이다. 그러므로 멀티 태스킹은 멀티 프로그래밍을 포함하는 개념이라고 단정할 수 있다. 멀티 유저라는 말은 쉽게 이해가 갈 것이다. '한 컴퓨터 시스템을 두명 이상의 사용자가 동시에 쓸 수 있으면' 그것이 바로 멀티 유저 시스템이다. 물론 멀티 유저 시스템은 멀티태스킹 시스템도 된다. 그러나 멀티 태스킹 시스템이 반드시 멀티 유저 시스템일 필요는 없다. 또 어떤 의미에서 보면 멀티 유저 시스템은 시분할 시스템(TSS ' Time Sharing System')이라고 볼 수도 있다. TSS란 바로 여러명의 사용자들이 CPU를 분할하여 사용하는 것이기 때문이다. 따라서 당연히 멀티 유저 시스템은 한 대의 메인 콘솔과 여러대의 터미널로 이루어질 것이다. 요약하면 멀티 태스킹이란 컴퓨터 시스템으로 하여금 하나 이상의 응용 프로그램을 동시에 실행할 수 있게 해 주는 방법이라고 말할 수 있다. 왜 멀티 태스킹이 필요한가? 멀티 태스킹을 해야 하는 이유는 바로 컴퓨터 시스템의 자원을 좀 더 효율적으로 사용 하자는 것이다. 간단한 예로 워드프로세싱 프로그램을 생각해 보자. 적당한 분량을 타이핑해 넣은 다음 그 내용을 프린트한다. 다시 그 다음 부분을 쳐 넣고 프린트한다.이렇듯 타이핑을 할 때는 프린터가, 프린팅을 할 때는 키보드가 각각 쉬게 된다. 만일 프린터 스풀러가 중간에서 작동하고 있다면 프린터가 열심히 프린팅을 하는 동안 키보드상에서 다음 부분을 입력할 수 있게 되고 그로 인해 작업의 효율은 두배로 올라갈 것이다. 다른 예로 통신망을 쓰는 경우를 생각해 보자. 모뎀으로 데이터를 주고 받는 것은 시간이 매우 많이 걸리는 작업이다. 이때 컴퓨터가 한없이 느린 통신 포트를 쓰고 있다면 다른 주변 장치들은 낮잠만 자게 된다. 멀티 태스킹 환경에서라면 통신프로그램을 백그라운드로 돌려 파일을 백그라운드로 돌려 파일을 받는 동안 다른 일에 컴퓨터를 이용할 수 있을 것이다. 멀티 태스킹의 필요성은 응용 프로그램 사이를 왔다갔다 하면서 복잡한 일을 처리할 때도 그 필요성이 요구된다. 가령 데이터베이스를 쓰다가 급히 파일 하나를 편집해야 할 경우가 생긴다면 어떻게 할 것인가. 우선 데이터베이스 프로그램을 끝낼 것이다. 지금까지 작업했던 내용 또한 저장해야 한다. 그런 후에 워드프로세서 프로그램을 실행시킨다. 일단 머리에 떠오른 것을 타이핑해넣고 내용을 저장한 다음 워드프로세서 프로그램을 끝낸다. 그런 다음 다시 데이터베이스를 올리고, 전에 저장했던 내용을 불러 온다. 이러한 작업을 반복하다 보면 대충 어림잡아도 디스크가 도는 시간만 3, 4분이 걸릴 것이다. 플로피 디스크 사용자라면 디스크 통을 뒤져서 데이터 베이스와 워드프로세서 프로그램을 찾고 드라이브에 갈아 끼우는 시간을 고려해야 하므로 시간은 더욱 지연될 것이다. 이렇게 귀찮은 작업을 해결할 수 있는 방법 중의 하나는 램상주 프로그램을 쓰는 것이다. 앞의 예와 같은 경우 사이드킥을 미리 메모리에 상주시켜 놓았다면 단지 <Ctrl-Alt>키를 누르는 동작만으로도 충분히 작업했을 것이다. 또 다른 방법은 워드프로세서와 데이터베이스가 함께 들어 있는 통합 소프트웨어를 사용하는 것이다. 심포니와 같은 유명한 통합 소프트웨어는 업무를 처리하는 도중에 그 상태를 떠나지 않고도 많은 일을 처리해주는 기능을 갖고 있다. 그러나 이 두 가지에 방법 모두 완벽한 해결책은 아니다. 그 이유는 우리가 동시에 처리하고자 하는 작업을 모두 램상주 프로그램으로 만들 수는 없는 일이기 때문이다. 또 통합 소프트웨어가 아무리 기능이 다양하다고 할지라도 우리가 원하는 기능을 모두 갖추고 있을 수는 없다. 진정한 멀티 태스킹 프로그램은 램상주 프로그램과 같이 비정상적인 방법을 쓰지 않고서도 응용 프로그램을 손쉽게 바꿔 가면서 사용할 수 있어야만 한다. 멀티 태스킹이 필요한 이유중 또 하나의 요인은 멀티 유저 환경이다. IBM PC에서 사용되는 멀티 태스킹 환경에서 멀티 유저 기능까지 제공하는 시스템은 많지 않지만 유닉스나 그의 변형 제품을 쓰면 손쉽게 멀티 유저 환경을 구축할 수 있다. 예를 들어 IBM PC/AT 나 386PC를 멀티 태스킹/멀티유저 환경으로 구성하여 10명이 동시에 이용하는 것과 IBM PC/XT를 10대 사는 방법중 어느 것이 더 싸게 먹히겠는가? 가격을 따지지 않고서라도 멀티 태스킹 환경에서는 하드 디스크,프린터 등을 공유할 수 있다. 멀티 태스킹이 아닌 시스템이라면 프린터에 바퀴를 달아서 이리저리 끌고 다녀야 할 것이다. 더욱이 각 사용자의 입장에서는 한꺼번에 여러 작업을 처리할 수 있다는 것도 빼놓을 수 없는 장점이 된다. 멀티 태스킹을 하면 속도가 엄청나게 떨어질 것이라고 생각하는 사람들도 있다. 물론 하나의 프로그램을 돌리는 것 보다는 늦다. 그렇지만 대부분의 경우 업무 속도를 결정하는 요인은 컴퓨터의 처리 능력이라기보다 데이터를 타이핑해 넣는 사람의 손이다. 데이터베이스가 수만개의 데이터를 소트하려면 몇 시간이 걸리겠지만 사람이 그것을 타이핑해 넣는데는 몇 달이 걸릴지 모른다. 사용자가 키를 누르기 기다리는 그 시간을 쪼개어 프린터를 쓰고 모뎀으로 파일을 전송할 수 있다면 오히려 각 작업을 하나씩 처리하는 것 보다 훨씬 효율적일 것이다. 요약하면, 멀티 태스킹의 필요성은 '(1)주변장치들을 보다 효율적으로 사용하기 위해서 (2)백그라운드 작업을 통해 같은 시간내에 더욱 많은 작업을 수행하기 위해서 (3)여러 개의 응용 프로그램을 동시에 실행하고 그들 사이를 쉽게 오갈 수 있도록 하기 위해서 (4)멀티 유저 환경으로 다수의 사용자들이 컴퓨터 시스템을 사용할 수 있도록 하기 위해서'라고 간단히 정의할 수 있겠다. 멀티 태스킹의 원리 멀티 태스킹의 원리라고 했지만 여기서 설명하는 것들은 실제로 대부분의 운영체제에서 찾아 볼 수 있는 공통적인 기능들이다. 컴퓨터 시스템은 CPU,메모리,디스크나 프린터와 같은 여러 입출력 장치들로 구성되어 있는데 이들을 통칭하여 자원(resources)이라고 부른다. 멀티 태스킹 운영체제의 가장 핵심적인 기능은 이들 자원을 각 응용 프로그램에게 공정히 분배하는 것이다. 하지만 하나의 프로그램이 시스템의 모든 자원을 독점하여 사용할때와는 달리 여러 개의 프로그램을 동시에 돌리게 되면 많은 문제점이 생겨난다. PC 사용자들은 대부분 한 사람이 컴퓨터를 독점해서 사용하는데 익숙하기 때문에 이러한 문제들을 잘 이해하지 못할 것이다. 여기서는 멀티 태스킹을 위해 가장 기본적으로 갖추어져야 할 요소들에 대해 알아보고,다음 장에서는 어떤 문제점들이 발생하는지,그리고 그 해결책은 무엇인지 살펴 보겠다. 프로세스 개념 멀티 태스킹에서는 여러 개의 프로그램이 동시에 실행된다고 하였다. 이러한 상황을 좀 더 자세히 설명하기 위해서 우선 하나의 프로그램만이 돌아가는 환경, 즉 현재의 MS-DOS와 같은 환경에서 프로그램이 실행될 때 어떤 일이 일어나는가를 보자. MS-DOS의 'A>'프롬프트는 실제로 MS-DOS의 사용자 인터페이스인 COMMAND.COM이 출력하는 것이다. 우리가 어떤 프로그램을 실행하기 위해 그 프로그램 파일의 이름을 입력하고 리턴키를 누르면 COMMAND.COM이 프로그램을 수행하기 위해 운영체제의 EXEC루틴을 수행한다. EXEC루틴은 그 프로그램을 디스크에서 찾아서 메모리에 로드하고, 그 프로그램이 수행될 수 있도록 몇 가지 환경들을 조정한 다음 그 프로그램의 시작 번지로 점프할 것이다. 이때 현재 메모리에 들어와 있는 프로그램의 정보를 프로세스(Process) 또는 태스크(task)라고 부른다. 프로세스를 정확히 정의하기는 어렵지만 대충 '실행중인 프로그램과 그에 관계된 정보'라고 말할 수 있다. 멀티 태스킹이란 바로 이러한 프로세스, 즉 태스크가 동시에 여러 개 존재할 수 있다는 것을 뜻한다. 멀티 태스킹 환경에서는 프로그램을 하나씩 실행할 때마다 그에 해당하는 프로세스가 하나씩 만들어져서 수행된다. 또 프로세스가 다른 프로세스를 만들어낼 수 있는데 이것을 포크(fork) 또는 스폰(spawn)이라고 부른다. 프로세스는 그에 관계된 많은 정보를 가지고 있다. 대개 여기서는 프로세스 번호나 이름, 그 프로세스의 주인(프로세스를 만든 사용자), 시작된 시간,우선 순위, 그리고 그 프로세스에 할당된 메모리 등 자원에 대한 정보들이 포함된다. 운영체제는 프로세스들에 대한 정보를 메모리내의 특별한 영역에 유지하게 된다. 사람에게 일생이 있는 것과 마찬가지로 프로세스에게도 일생이 있다. 프로세스는 그가 태어나서 없어질 때까지 <그림 1>과 같은 다섯 가지 상태 중 하나에 놓이게 된다. 프로세스의 일생중 시작(start)은 프로세스가 막 생겨난 상태이다. 멀티 태스킹 시스템에서는 메모리내에 여러 개의 프로세스가 존재하므로 CPU는 이들 사이를 번갈아가면서 처리해 주어야 한다. 준비(ready)는 프로세스가 실행할 준비를 모두 끝내고 자기에서 CPU가 돌아오기만을 기다리고 있는 상태이다. 실행(running)은 프로세스가 CPU를 받아서 실행되고 있는 상태이다. <그림 1>프로세스의 일생 한 프로세스가 너무 오랜 시간 CPU를 독점하는 것을 방지하기 위해서 일정시간이 지나면 CPU를 뺏는 경우가 있다. 이것을 시간제한(timeout)이라고 하는데, 이런 경우에는 프로세스가 실행 상태에서 다시 준비 상태가 된다. 또 프로세스가 수행되다가 어떠한 이유로 인해 작업을 더 이상 계속할 수 없게 되면 그 프로세스는 CPU를 뺏기고 대기(waiting)상태로 가게 된다. 이렇게 되는 원인은 입출력 요청, 프로그램 에러 등 여러 가지가 있다. 프로세스가 다시 실행될 수 있게 되면 그 프로세스는 준비상태로 간다. 그 프로세스가 할 일을 모두 끝내면, 즉 프로그램이 끝나면 그 프로세스는 없어진다. 스케줄러 이해를 돕기 위해 <그림 2>의 두프로그램이 멀티 태스킹 시스템에서 실행된다고 가정해 보자. 여기서 점선으로 표시된 부분은 입출력을 전혀 하지 않고 CPU만 사용하는 부분이다. <그림 2>멀티 시스템 환경에서 실행되는 프로그램의 예 <프로그램 1> <프로그램 2> start start . . . . read a char from keyboard (1) . . print string (1') . . open file 'A.DAT' (2) . 먼저 <프로그램 1>이 실행되어, 프로세스 1을 만든다고 하자. 프로세스 1이 (1)까지 수행됐을 때 입출력 명령을 만났으므로 CPU를 포기하고 대기 상태로 간다. 이때 <프로그램 2>가 프로세스 2로 메모리에 올라와 있었다면 프로세스 2가 CPU를 받게 되고 수행을 시작한다. 프로세스 1이 키보드에서 한 글자를 입력받고 돌아 왔다고 해도 그것은 곧바로 수행을 계속할 수는 없다. 프로세스 1은 프로세스 2가 끝나거나 입출력 명령을 만나 대기 상태로 돌아 갈 때까지 준비 상태로 기다려야 한다. 시스템내에 두 개 이상의 프로세스가 있을 때에는 이러한 준비 상태의 프로세스가 여러 개가 있어 CPU를 기다리고 있게 된다. 이 프로세스들이 대기하는 장소는 바로 <그림 3>에 표시된 준비 큐(ready queue)이다. <그림 3>준비큐 프로세스 2가 (1)까지 수행되고나서 CPU를 내놓으면 준비 큐에있던 프로세스들 중 하나가 CPU를 차지하는 행운(?)을 누리게 된다. 여기서는 두 개의 프로세스만 가정하므로 당연히 프로세스 1이 다시 들어가서 (2)의 과정까지 작업을 수행하고, 다시 프로세스2에게 CPU를 넘겨 주는 일을 되풀이 할 것이다. 그러나 준비 큐내에 여러 개의 프로세스들이 있다면 '그들 중에 어느 것을 고를지 결정해야'한다. 자, 이제 독자 여러분들은 약간 이상하다는 감을 느낄 수 있을 것이다. 도대체 '누가' 프로세스를 고른다는 말인가? 그 답은 바로 운영체제이다. 정확히 말하자면 운영체제중에서도 프로세스 스케줄러(process scheduler) 또는 잡 스케줄러(job scheduler)이다 (보통 스케줄러의 입장에서 이야기할 때는 프로세스를 잡(job)이라고 부르는 경우가 많다). 스케줄러는 이름 그대로 CPU의 일정표를 짜는 도구라고 말할 수 있다. 즉 언제부터 언제까지는 어느 프로세스, 언제부터 언제까지는 또 어느 프로세스가 CPU를 점유해야 하는지 프로세스들에게 CPU를 배당해 주는 역할을 하는 것이다. 프로세스 이미지와 프로세스 교환 어떠한 프로세스가 시간제한이나 입출력 명령 등으로 수행을 계속할 수 없으면 바로 스케줄러에 제어가 넘어간다. 시간제한을 체크하기 위해서는 일정한 시간마다 CPU에 인터럽트를 걸어 스케줄러를 수행하도록 하면 된다. 따라서 멀티 태스킹 시스템에서는 클럭 인터럽트 발생기가 필수적이다. 어쨌든 스케줄러는 시간제한에 걸린 프로세스를 준비상태나 대기 상태로 보내고 CPU를 다른 프로세스에게 준다. 이렇게 프로세스를 바꾸는 작업을 문맥 교환(context switching)이라 하는데, 이를 위해서는 각 프로세스에 대한 정보가 필요하다. 즉,현재 그 프로세스가 어디까지 실행되었는지(프로그램 카운터의 값), 프로세스가 CPU를 빼앗길 당시의 각종 레지스터들의 값, 스택의 위치 등을 모두 기억하고 있어야 한다. 이러한 정보들을 그 프로세스의 이미지(image)라고 한다. 문맥 교환이 일어날 때는 현재 수행되고 있는 프로세스의 이미지를 어디엔가에 보존하고 다음에 실행시킬 프로세스의 이미지를 가져와서 그 프로세스가 CPU를 뺏기기직전의 상태로 만들어 놓는다. 그 다음에 그 프로세스에게 제어를 넘기면(즉,그프로세스 이미지의 프로그램 카운터가 가리키는 곳으로 점프하면) 순진한(?) 프로세스는 그동안 어떤일이 벌어졌는지 알지 못하고 언제 그랬느냐는 듯이 작업을 계속해 나간다. 보통 시스템내에는 프로세스들의 일람표가 있어서 그곳에 프로세스 이미지를 저장하게 되는데 이 일람표를 PCB(Process Control Block)라고 부르기도 한다. 입출력의 제약과 시스템 모드 어떤 프로세스가 입출력 명령을 수행하게 되면 그 프로세스는 CPU를 빼앗기고 입출력이 끝날 때까지 대기 상태에 있게 된다. 그 이유는 입출력 장치와 CPU사이의 메꿀수 없는 속도 차이 때문이다. 예를 들어 초당 160문자를 찍는 프린터라면 1문자를 처리하는데 걸리는 시간이 0.00625초이다. 이 시간에 8MHz의 PC/AT는 5만회의 클럭 펄스를 생성하고 최소한 3000~4000개의 기계어 명령을 처리할 수 있다. 그러나 프린터에 한 문자를 보내는 데 필요한 기계어 명령의 수는 기껏해야 100개 안팎일 것이다 따라서 프린터를 쓰고 있는 동안 CPU의 95%는 놀고 있는 셈이다. 멀티 태스킹 시스템에서라면 이러한 불합리는 개선될 수 있다.모든 입출력 장치는 스풀링(spooling)으로 운영된다. 예를 들어 프로세스가 실행중에 키보드에서 한 문자를 읽어와야 한다면 그것을 운영체제의 입출력 처리 루틴에 맡기고 자신은 대기 상태에 들어 간다. 따라서 키보드에서 글자가 입력되기를 기다리는 동안 다른 프로세스가 CPU를 이용할 수 있다. 나중에 운영체제가 키보드를 읽고 제어를 넘겨 주면 그 프로세서는 깨어나서 준비상태로 들어가는 것이다. 이러한 방식에서는 모든 입출력을 운영체제가 관리해야 한다. 우리가 보는 모든 운영체제는 이러한 입출력 루틴을 갖고 있다. CP/M의 BDOS 호출, MS-DOS의 도스 펑션 호출, 유닉스의 시스템 콜 등이 모두 이러한 맥락에 속한다. 심지어 IBM 360의 경우는 아예 입출력을 하려면 SVC(Super Visor Call)라는 특별한 기계어 명령을 쓰도록 되어 있다. 멀티 태스킹이 제대로 되려면 cpu가 최소한 두 가지의 작동 상태를 가질 수 있어야 한다. 즉 사용자 모드(user mode)와, 시스템 모드 혹은 관리자 모드(supervisor mode)이다. 일반 프로세스는 사용자 모드에서 돌아 가고, 운영체제는 항상 시스템 모드에서 작동한다. 사용자 모드에서는 프로그램이 컴퓨터 시스템의 여러 자원(resource),즉 CPU, 메모리, 입출력 장치 등을 마음대로 사용할 수 없다. 여기서 입출력은 항상 운영체제의 호출에 의해서만 이루어진다. 그러나 독자 여러분들이 쓰고 있는 프로그램들, 특히 MS-DOS용 프로그램들은 유감스럽게도 이 원칙을 지키는 것들이 거의 없다. 제법 쓸만한 프로그램이다 싶으면 백이면 백 모두 비디오 램을 곧바로 액세스하거나 I/O포트를 직접 읽어들인다. IBM PC에서 멀티 태스킹이 제대로 안되었던 이유중 하나도 바로 이 점 때문이다. 예를 들어 똑같이 비디오 램을 액세스하는 로터스 1-2-3와 터보 파스칼을 동시에 실행시키면 화면은 두 개의 프로그램이 엉켜서 알아볼 수 없게 될 것이다. 따라서 멀티 태스킹을 제대로 하려면 지금까지 사용하던 거의 모든 MS-DOS 프로그램을 수정해야 한다. 메모리 관리 컴퓨터 시스템에서 메모리는 CPU 다음으로 중요한 자원이다. 어떤 프로세스가 수행되면 그 프로세스는 자신의 프로그램과 데이터가 들어갈 메모리 공간을 할당받아야 한다. 이것은 운영체제의 메모리 관리 루틴에 의해서 이루어지는데 메모리 관리(memory management)는 기본적으로 다음의 3가지 일을 처리할 수 있다. (1) 할당(allocation) : 프로세스에 메모리를 준다. (2) 회수(deallocation) : 프로세스에 주었던 메모리를 뺏는다. (3) 자유 메모리(free memory) 관리 : 현재 프로세스에 할당되지 않은 자유 영역을 관리한다. 최초에는 모든 메모리가 자유영역이다. 이 자유영역은 프로세스가 하나씩 만들어질 때마다 프로세스에 할당되어야 한다. 운영체제의 메모리 관리 루틴은 메모리 할당 요구가 있으면 자유 메모리에서 얼마만큼을 떼어 주고, 프로세스가 끝나서 더 이상 필요없게 된 메모리는 회수하여 다시 자유영역으로 돌아가도록 설계되어 있다. 멀티 태스킹을 위해서는 메모리가 많을수록 좋다. PC의 경우에는 대부분의 시스템이 640KB, 심지어는 2MB이상의 메모리를 요구한다. 그러나 우리가 사용하고자 하는 응용프로그램의 크기 또한 만만치 않으므로 웬만한 프로그램 몇 개만 올리면 메모리는 꽉 차버릴 것이다. 따라서 실제로 메모리가 수용할 수 있는 것보다 더 많은 수의 프로세스를 실행하기 위해서 오버레이(overlay), 스와핑(swapping), 가상 메모리(virtual memory) 등의 기법이 고안되었다. 오버레이는 응용 프로그램을 한꺼번에 로드하지 않고 실행에 필요한 일부분씩만 차례로 로드하여 수행하는 방법이다. 스와핑은 다른 프로세스를 실행시키기 위해 현재 메모리에 있는 프로세스를 디스크로 쫓아 보내는(스왑 아웃 : swap-out)방법이다. 나중에 이 프로세스가 다시 수행될 때 그것은 다시 메모리로 로드된다(스왑인 : swap-in). 가상 메모리는 매우 복잡한 방법인데, 메모리를 여러 개의 페이지(page)로 나누고 가상 주소를 실제 주소로 변환하기 위해 페이지 맵핑 테이블을 참조하는 방법이다. 가상 메모리의 페이지 수는 실제의 페이지 수보다 더 많으므로 페이지들 중 일부는 디스크에 저장된다. CPU가 메모리 액세스를 하면 그 주소는 하드웨어인 메모리 관리 유니트(MMU : Memory Management Unit)에 의해 실제 주소로 변환된다. 만일 현재 메모리에 없는 페이지가 액세스되면 메모리에 있는 페이지 중 하나를 디스크에 저장하고 그 자리에 요구된 페이지를 로드하는 것이다. 이 과정은 하드웨어에 의해 자동으로 처리되므로 사용자는 신경쓸 필요가 없다. 이들에 대해 자세히 다루려면 무리이므로 관심있는 독자는 운영체제에 관한 전문서적을 참고하기 바란다. 프로텍션(protection) 멀티 태스킹이 아닌 지금의 IBM PC를 생각해 보면 사용자 프로그램으로 처리할 수 없는 일이 거의 없다는 것을 알 것이다. 비디오 램을 액세스하건 말건, 도스의 인터럽트 벡터를 뜯어 고치건 말건, 디스크를 엉망으로 만들건 말건 도스는 아무런 간섭도 할 수 없다. 이것은 분명히 상하가 뒤바뀐 격이다. 원래 운영체제란 컴퓨터 시스템의 관리자인 동시에 감독자이다. 그런데 운영체제의 통제를 받아야 할 응용 프로그램들이 운영체제를 무시하고 오히려 제멋대로 운영체제를 뜯어 고치고 있으니 그야말로 굴러온 돌이 박힌 돌을 빼내는 격이다. 제대로 된 멀티 태스킹 운영체제라면 각종 자원의 보호(protection)에 신경을 써야 한다. 가령 메모리내에 10개의 프로세스가 있다고 했을 때 각각의 프로세스 사이를 철저히 격리하여 상호 침범을 막아야 한다. 한 프로세스가 다른 프로세스의 코드 부분에다 엉뚱한 내용을 써넣는다면 어떻게 되겠는가? 무한 루프, 아니면 폭주일 것이다. 이를 막기 위해서는 각종 자원의 관리를 운영체제가 독점하여야 한다. 특히 메모리는 엄격한 관리하에 놓여져야 한다. 메모리의 보호를 위해서는 각 프로세스가 액세스할 수 있는 메모리의 범위를 제한할 필요가 있는데 이것은 하드웨어의 도움없이는 불가능하다. 간단한 방법은 <그림 4>와 같이 베이스 레지스터(base register)와 한계 레지스터(bound register)를 쓰는 방법이다. <그림 4>한계와 베이스를 이용한 어드레스 변환 베이스와 한계 레지스터는 CPU안에 있는 것이 보통이다. 프로세스 교체시에 스케줄러는 이 두 레지스터에 적절한 값을 넣어 주어야 한다. CPU가 매번 메모리를 액세스할 때마다 그 주소가 한계 값과 비교된다. 만일 주소가 한계 값보다 크면 CPU에 인터럽트가 걸리고 운영체제가 제어를 넘겨받아 적절한 에러 처리를 하게 된다. 주소값이 정당하면 베이스 값이 더해져서 이것이 실제 주소가 된다. 만일 베이스가 1000이고 한계가 2000이라면 이 프로세스에 의해 액세스될 수 있는 메모리는 1000번지에서 3000번지 까지로 제한될 것이다. 이러한 시스템에서 응용 프로그램이 생성해 내는 주소는 실제 메모리 주소가 아니고 베이스 레지스터의 옵셋값이 된다. 이는 8086의 세그먼트 구조와도 비슷한데 이러한 방법은 프로세스가 실제 메모리내에 놓이는 위치에 관계없이 실행될 수 있도록 한다. 멀티 태스킹에서 발생하는 문제점들 멀티 태스킹을 하기 위해 해결해야할 문제는 '어떻게 한정된 자원을 각 프로세스들에게 나누어 줄 것인가'하는 것이다 운영체제는 각 프로세스들에게 공평하게 기회를 나누어 주고, 그와 동시에 시스템 전체의 효율을 떨어뜨리지 않도록 하면서, 각 프로세스들 간에 상호 침범을 막으면서 시스템을 운영해 나가야 한다. 이런 면에서 운영체제는 유치원 보모 선생님과 비슷하다. 코흘리개 아이들의 뒤치닥거리 하는일만큼이나 어려운 것이 바로 멀티 태스킹 운영체제가 해야 할 일인 것이다. 프로세스 스케줄링 문제 스케줄러가 하는 대강의 기능은 앞에서 설명했다. 스케줄러가 하는 가장 중요한 작업은 준비 큐에서 다음에 수행될 프로세스를 고르는 일이다. 프로세스를 고르는 방법에 따라 스케줄링 방법은 매우 많은 차이를 보인다. 스케줄러가 추구해야 하는 것은 공평성과 효율성이다. 또 어느 한 프로세스가 CPU를 너무 오랜 시간 독점하는 것도 막아야 한다. 크게 스케줄러는 선점형(preemptive) 과 비선점형(non-preemptive)으로 나눌 수 있다. 이것을 나누는 기준은 현재 CPU를 받아서 수행되고 있는 프로세스가 프로세스 종료,에러,입출력 요구 중 하나가 아닌 다른 이유에 의해서 CPU를 빼앗길 수 있는가의 여부 때문이다. 다른 이유로 프로세스가 CPU를 빼앗기는 것을 프리엠션(preemption)이라고 한다. 스케줄링 방법은 FIFO(first-in,fitst-out),라운드 로빈(round robin),최단작업 우선(SJF : shortest job first), 다단계 귀환 큐(multilevel feedback queue) 등이 있는데,이중 FIFO와 라운드 로빈에 대해 알아보자. FIFO는 비선점형으로서 이름 그대로 준비 큐에 들어왔던 순서대로 CPU를 배당하는 방법이다. 그래서 FCFS(first come, fitst serviced)라고 부르기도 한다. 이 방법은 프로세스가 CPU를 배당받으면 완전히 수행이 끝나거나 입출력 등에 의해 대기 상태로 가기전까지 수행을 계속한다. 이것은 언뜻 보기에 공평한듯한 느낌을 주지만 그렇지 않다. 예를 들어 CPU를 아주 오래 쓰는 프로세스가 있다면 그것이 수행되는 동안 그 뒤에 수행될 프로세스들은 겨우 몇 십분의 일초 동안 CPU를 쓰기 위해 몇 초를 기다려야 한다는 모순점이 있다. 따라서 응답시간(responce time)이 짧은 것이 생명인 대화식 시스템에는 FIFO가 부적합하다. 즉, 배치(batch) 처리에 적합한 스케줄링 방법이다. 라운드 로빈은 선취형이다. 이것의 원리는 시간을 잘게 쪼개어 각 프로세스에 배당하는 것이다. 즉 일정한 시간-타임 슬라이스(time slice)또는 타이 퀀텀(time quantum)이라 한다-을 정하고 프로세스가 CPU를 한 타임 슬라이스보다 많이 쓰지 못하도록 하는 것이다. 이를 위해서는 일정한 시간 간격마다 계속 인터럽트를 걸어 스케줄러를 호출한다. 한 타임 슬라이스내에 작업을 다 끝내지 못한 프로세스는 프리엠션을 당하여 준비 큐의 끝에 가서 다시 차례를 기다린다. 이렇게 프로세스가 준비 큐와 CPU 사이를 뱅뱅 돌게 되므로 라운드 로빈이라 부른다. 물론 한 타임 슬라이스 중간에도 입출력 명령이 있으면 다음 프로세스에게 CPU를 넘겨 주어야 한다. 라운드 로빈의 장점은, 공평성이다. CPU를 많이 쓰는 프로세스는 프리엠션을 많이 당하므로 그만큼 늦게 수행된다. CPU를 쓰는 시간이 짧은 대화형 작업의 프로세스들은 대개 한 타임 슬라이스 이내에 자기의 업무를 처리할 수 있으므로 응답 시간이 빠르게 된다. 그래서 라운드 로빈은 시분할 시스템에 많이 쓰인다. 라운드 로빈의 가장 큰 문제점은 타임 슬라이스의 크기를 정하는 것이다. 이것이 너무 크면 FIFO와 비슷해지고, 너무 짧으면 CPU가 프로세스 작업을 처리해 주는 것보다 스케줄링에 시간을 더 많이 소비하게 되므로 비효율적이 된다. 어떤 스케줄링 알고리즘도 우선 순위(priority) 개념을 채택할 수 있다. 예를 들어 컴퓨터 오퍼레이터의 프로세스보다 우선 순위가 높으면 좋을 것이다. 또 배치(batch) 방식의 프로세스보다는 대화식(interactive) 프로세스가, 그보다는 실시간(real-time) 모드의 프로세스가 더 높은 우선순위를 가지는 것이 보통이다. 경쟁상태와 위험지역 두 개 이상의 프로세스들이 동시에 처리된다고 할 때 가장 골치아픈 문제는 둘 사이의 상호 침범을 방지하는 일이다. 만일 프로세스들이 자신에게 주어진 영역에서만 일하고 다른 것을 건드리지 않는다면 이런 일은 없을 것이다. 그러나 프로세스들은 한정된 자원을 공유하여 사용해야 하고 서로 간에 데이터를 주고 받아야 한다. 쉬운 예로 워드프로세서와 프린터 스풀러가 그것을 꺼내어 프린터로 보낸다. 이때 두 프로세스가 변수들을 공유하여 사용하고 있다고 생각해 보자. 두 프로세스가 공유 변수를 동시에 변화시키면 어떻게 될 것인가? 이해를 돕기 위해 <그림 5>와 같은 두 프로세스의 일부분이 동시에 수행되었다고 하자. <그림 5>동시에 수행된 프로세스 문제는 이들 고급 언어로 표현된 문장이 컴파일되어 나온 기계어 프로그램에 있다. 메모리를 직접 증가하는 명령이 없다면 <그림 6>과 같은 기계어 코드가 나올 것이다. <그림 6><그림 5>의 프로세스가 기계어로 컴파일된 내용 지금 counter의 값이 5이고 프로세스 1이 LOAD 와 ADD명령을 실행했다고 하자. R1에는 6이 들어 있다. 이 시점에서 타임 인터럽트가 걸려서 프로세스 1이 프리엠트 되었다고 하자. 프로세스 1이 STORE명령을 실행하지 못했으므로 counter의 값은 아직 5이다. 이제 프로세스 2가 스케줄되어 이부분을 수행한다. 프로세스가 2가 이 부분을 정상적으로 수행하면 counter에는 4라는 값이 담기게 된다. 조금 후에 프로세스 1이 CPU를 받아서 수행을 계속한다. 문맥 교환에 의해 프로세스 1은 자신이 프리엠트 당하기 바로 직전의 레지스터 값들을 회복한다. 따라서 R1은 6이 되고 STORE명령에 의해 counter에는 6이 담기게 된다. 프로세스 1과 프로세스 2가 각각 counter를 한번씩 증감했으니 그 값은 5가 되어야 마땅한데 6이 되어 버렸다. 이와 같은 상황을 경쟁상태(race condition)라 하고 이러한 현상을 유발시키는 프로그램 부분을 위험지역(critical section)이라 한다. 이 현상은 동시에 실행되는 여러 프로세스들이 공유 변수를 동시에 액세스할 때 발생한다. 한 프로세스가 위험지역에서 수행되고 있을 때는 다른 어떤 프로세스도 그 위험지역에 들어가서는 안된다. 이를 상호 배제(mutual esclusion)라고 한다. 상호 배제를 실현하는 한 가지 방법은 한 프로세스가 공유변수를 액세스할 때는 인터럽트를 금지함으로써 다른 프로세스들이 그 사이에 끼어들지 못하도록 하는 것이다. 그러나 이 방법은 여러 개의 프로세서(CPU)들이 하나씩의 프로세스를 실행하고 있는 멀티 프로세싱 시스템에서는 효과가 없다. 게다가 어떤 프로세스가 위험 지역 안에서 무한 루프에 빠졌을 때 인터럽트를 금지하게 되면 시스템 전체가 다운될 것이다. 상호 배제를 실현하는 방법은 여러 가지가 있으며 제각기 장단점을 갖고 있다. <그림 7>에 설명한 피터슨의 알고리즘도 그들 중의 한 방법인데 2개의 프로세스가 같은 위험지역을 수행하려 할 때 그들 사이에 상호 배제가 이루어지도록 하고 있다. <그림 7>피터슨 알고리즘 <그림 7>에서 i와 j는 두 프로세스의 번호로서 0또는 1이 된다. 알고리즘에서 i는 자기 프로세스의 번호, j는 상대방 프로세스의 번호를 나타낸다. flag[i]가 true이면 지금 i번 프로세스가 위험 지역에 들어가기를 원하고 있거나 이미 위험 지역에 들어가 있음을 나타낸다. turn은 현재 위험 지역에 들어갈 수 있는 권리가 누구에게 있는가를 나타낸다. 최초에는 flag[0]과 flag[1]이 모두false이다. turn의 값은 0이나 1중 어느 것이라도 상관없다. 두 프로세스가 동시에 위험지역에 들어 가려고 한다면 거의 같은 시간에 자신의 flag[i]를 true로 한다. 그 다음 turn을 상대방의 번호, 즉 j로 한다. 따라서 'turn : =j' 문장이 거의 동시에 수행되더라도 turn의 값은 0이나 1중 하나로 되고 이것에 의해 그 번호의 프로세스가 위험 지역에 들어 가게 된다. 만일 0번 프로세스가 위험 지역에 들어 갔다면 1번 프로세스는 flag[0]이 true이고 turn이 0이므로 while 루프에서 걸리게 된다. 0번 프로세스가 위험 지역을 모두 수행하고 나올 때 flag[0] false로 하므로 이때 1번 프로세스가 while 루프를 빠져 나올 수 있게 된다. 아이러니컬하게도 두번째 줄에서 turn을 상대방에게 넘겨 주지 않고 자신이 가지면, 즉 'turn : =i'로 하면 상호 배제가 실현되지 않는다. 이 점은 멀티 태스킹 시스템에서 가장 필요한 것이 바로 서로 양보하는 마음이라는 것을 잘 보여 주는 예라고 하겠다. 피터슨 알고리즘은 두 개의 프로세스가 있을 때의 모델이지만 더 많은 프로세스에 대해서도 쉽게 확장될 수 있다. 이외에도 데커(Dekker) 알고리즘, 딕스트라(Dijkstra)의 세마포어(semaphore)기법 등 많은 방법이 있다. 교착 상태 제한된 컴퓨터 시스템의 자원을 여러 프로세스에게 골고루 분배해야 할 문제가 한 두 가지가 아니다. 그중에서도 제일 골치 아픈 것이 바로 교착 상태(deadlock : 영문 발음 그대로 데드락이라고 읽는 경우가 더 흔하다)이다. 교착상태란 프로세스의 한 상태로 '일어날 수 없는 사건을