운영체제 메모리 

 

 

 

 

컴퓨터 내에는 여러 종류의 메모리가 있습니다.

크게는 세 개로 나눌 수 있는데, cpu의 레지스터와 캐시 & 메인메모리 & 보조저장장치hdd, sdd 메모리입니다.

 

컴퓨터 속 메모리들

 

 

 

레지스터와 캐시, 메인메모리는 매우매우 비쌉니다. 특히 cpu내에 달린 레지스터와 캐시는 속도가 빠른 만큼 비싸죠. 

cpu로 계산할 땐, 메인 메모리에 있는 값을 레지스터로 가져와 계산합니다. 계산결과는 다시 메인 메모리에 저장이 됩니다. 그런데, 메인메모리는 레지스터에 비해 상대적으로 너~~무 느립니다. 그래서 레지스터와 메인메모리 사이에 캐시라는 메모리가 존재합니다. 메인메모리에서 레지스터로 옮기기엔 너무 오래 걸릴 것 같을 땐, 필요할 데이터를 미리 가져옵니다. 미리 가져온 데이터도 저장할 곳이 있어야 하겠죠?

그게 바로 캐시입니다.

 

캐시는 성능의 이유로 여러 개가 있는데, 만약 cpu가 값을 요청해 메인메모리에서 레지스터로 값을 옮겨야 한다면 단계에 따라 가장 속도가 빠른 L1, 없으면 L2, 그 다음은 L3를 확인하고 그것 조차 없다면 메인 메모리를 확인합니다. 

 

우리가 컴퓨터 성능을 체크할 때 보는 L1~L3가 바로 이 개념입니다. L1 -> L2 -> L3로 가는 발전 과정에서 캐시는 크기는 증가시키고 속도는 늦췄습니다. 레지스터가 캐시 안에 있는 값을 찾는데, 캐시의 크기가 작다면 그 값들이 아직 없을 수 있습니다. 그 찾는 과정이 실패하면 성능이 매우 매우 저하됩니다. 이를 캐시 미스라고도 하는데, 이런 캐시 미스가 많을 수록 성능이 더 저하되기 때문에 L3를 가장 크면서 가장 느린 캐시로 두고, 캐시 적중률을 높이는 것이 중요합니다. 가장 용량이 큰 만큼 메모리에서 옮긴 값들이 다 세세하게 있겠죠. 

 

메인 메모리는 모두 알다시피, 폰노이만 구조에서의 모든 프로그램이 실행되기 위해 올라가야만 하는 그 '메모리'입니다. 이 메모리를 관리하는 것이 이번 포스트에서 가장 중요하게 다룰 내용입니다.

 

hdd,sdd에는 저장되어야할 파일들이 있습니다. 위의 레지스터, 캐쉬, 메모리를 저장메모리로 쓰기엔 값이 너무 비싸기 때문에 가장 저렴하고 전원이 공급되지 않아도 데이터가 지워지지 않는 이 메모리에 정보를 모두 저장합니다.

 

 


 

메인 메모리 

 

 

 

메모리에서 가장 중요한 것은 무엇보다 메인 메모리일 것입니다. 폰 노이만 구조에 입각하여 모든 프로그램을 실행시키기 위해선, 메모리에 올려야 합니다. 점점 프로그램이 복잡해질 수록 메모리를 관리하는 일이 힘들어집니다. 운영체제도 관리해야하고, 프로그램도 관리해야하고, 이런 메모리를 관리하는 것은 메모리 관리 시스템(MMS)가 관리합니다. 

 

메모리 관리자는 가져오기, 배치, 재배치 등의 작업을 하게 됩니다. 

프로세스와 데이터를 메모리를 가져오는 작업, 앞으로 필요할 것 같은 데이터 또한 미리 가져옵니다. 또 프로세스와 데이터를 메모리 어떤 부분에 올려놓을 건지도 결정해야합니다. 가져온 메모리를 어떻게 크기를 할 건지, 어떤 위치를 놓을 건지에 따라 메모리 관리가 달라지기 때문에 이 배치 작업은 매우 중요합니다. 새 프로세스를 가져와야 하는데 메모리가 다 찼다면 메모리의 프로세스를 하드웨어에도 다시 옮겨놓는 작업을 해야합니다. 이 재배치 작업 또한 메모리 관리 시스템이 해줘야합니다. 

 

후에도 다루지만, 배치 정책에서는 메모리를 같은 크기로 자르는 것을 페이징이라고 하며, 프로세스 크기에 맞춰 자르는 것을 세그먼테이션이라 부릅니다. 이 두가지 방법은 각자 장단점이 있으며, 메모리 효율 또한 다릅니다. 그러므로 이 두 가지 방법을 상황에 맞게 적절히 쓰는 것이 MMS가 할 일 입니다.

 

재배치 정책에서는 메모리가 꽉 찼을 때 어떤 프로세스를 내보낼 지 결정하는데, 앞으로 쓰지 않을 프로세스를 찾아내서 내보내는 교체 알고리즘이 쓰입니다. 

 

 

메모리 주소 

 

 

메모리에 접근할 때는 주소를 이용합니다. 그러므로 메모리의 주소를 이해하는 것은 매우 중요합니다.

운영체제는 메모리 관리를 위해 1바이트 크기로 구역을 나누고 숫자를 매겼고, 이 숫자는 주소라고 부릅니다.

 

0x1 0x2 0x3 ... ... ... 0xfff

 

 

 

32bit cpu 64 bit cpu

 

 

 

메모리 얘기 실컷하다가 갑자기 cpu가 나온다고요? 당황스럽지만, cpu의 비트는 메모리 주소 공가의 크기와 연관이 있습니다. 

CPU의 비트는 한 번에 다룰 수 있는 데이터의 최대 크기를 의미합니다. 32bit CPU는 한 번에 다룰 수 있는 데이터의 최대 크기가 32bit라는 뜻이며, 레지스터 크기, 산술 논리 연산장치, 대역폭, 대역폭을 통해 한 번에 옮겨지는 데이터의 크기도 모두 32bit입니다. 32bit의 메모리 주소 레지스터 크기도 32bit이므로, 표현할 수 있는 메모리 주소의 범위가 2의 32승 b, 4GB입니다. 그러므로 32bit CPU컴퓨터는 메모리를 최대 4GB까지 이용할 수 있다는 뜻입니다. 64bit는 64bit만큼 다 다룰 수 있고 32bit보다 한 번에 처리할 수 있는 양이 많기에 속도 또한 매우 빠릅니다. 메모리 주소 공간은 2의 64승으로 거의 무한대에 가까운 메모리를 사용할 수 있습니다.

 

 

물리주소와 논리주소 

 

 

이렇게 32bit CPU든 64bit CPU든, 메모리를 컴퓨터에 연결하면 0번지부터 시작하는 주소공간이 있는데, 이를 물리 주소 공간이라 부릅니다. 물리 주소 공간은 하드웨어 입장의 주소 공간으로 컴퓨터마다 그 크기가 다릅니다, 이와 다르게 사용자 관점에서 본 주소공간을 논리 주소 공간이라고 부릅니다. 이 둘의 차이는 후술할 것입니다. 사용자는 물리 주소는 몰라도 논리 주소로 물리 주소에 접근이 가능합니다. 

 

 

절대주소와 상대 주소 

 

 

메모리

 

메모리에는 운영체제와 수 많은 프로세스가 올라갑니다. 그 중에 운영체제는 매우 특별하기 때문에 운영체제의 공간은 따로 존재합니다. 사용자의 프로세스가 운영체제를 침범하면 안되기 때문에, 하드웨어적으로 운영체제와 사용자 공간을 나누는 경계 레지스터를 만들었습니다.

메모리 관리자는 사용자 프로세스가 경계 레지스터의 값을 벗어나면 그 프로세스를 강제 종료시킬 수 있습니다.

 


 

개발자가 개발을 할 때 프로그램이 실행될 주소를 신경쓰며 개발하는 것을 보셨나요? 주소는 상관없이 그저 코드로 개발만 하면 되는데요, 이는 컴파일러가 컴파일을 할 때 메모리가 0번지에서부터 실행한다고 가정하기 때문입니다. 

 

절대주소와 상대주소

 

개발자의 프로그램을 실행시켜 메모리의 사용공간 4000번지에 올라갔다고 칩시다. 컴파일러는 4000번지라고 따로 생각하지 않고 0번지라고 그저 가정만 할 뿐입니다. 

 

실제 프로그램이 올라간 주소는 4000번지이고, 이는 메모리관리자가 바라본 절대 주소입니다. 이 절대 주소를 물리 주소 공간이라 합니다. 램에 실제로 꼽힌 프로그램의 주소는 4000번지니까요. 

 

하지만 사용자가 쓰는 컴파일러는 프로그램의 주소를 0번지라고 했습니다. 만약 운영체제가 갑자기 3999번지까지 쓰다가 4001번지까지 쓰고 싶다고 하면, 물리주소로 컴파일러가 프로그램의 주소를 4000번지라고 했다면 큰 혼란이 야기될 것입니다. 그런 혼란을 하기 위해 그냥 컴파일러는 0번지라고 가정하는 것입니다. 그래서 사용자가 바라보는 주소인 상대 주소를 논리 주소 공간이라 합니다. 

 

 

상대 주소를 절대 주소로 변환하는 과정 

 

 

상재 주소를 사용시에는 상대 주소는 물리 주소, 절대 주소로 변환해야겠죠? 이 변환 작업은 메모리 관리자의 일입니다. 

 

위 그림에 있는 재배치 레지스터는 프로그램의 시작 주소가 저장되어 있습니다. 앞에 프로그램이 운영체제 뿐이라면, 운영체제의 크기만큼 재배치 레지스터에 들어가있고, 그 주소 다음에 바로 상대주소를 넣으면 됩니다. 이렇게 접근하게 되면 프로그래밍도 편해질 뿐만 아니라, 앞의 프로그램의 시작영역, 끝영역이 바뀌어도 재배치 레지스터만 변경하면 되기 때문에 메모리 관리가 굉장히 쉬워집니다.

 

 

메모리 할당 방식 - 유니프로그래밍 

 

 

메모리 오버레이 & 스왑

 

과거에 메모리가 매우 비쌌을 때, 컴퓨터에 큰 메모리를 사용할 수 없었습니다. 2GB짜리인데, 8GB짜리 프로그램이 메모리에 올라가려면 어떻게 해야했을 까요? 

큰 프로그램을 메모리에 올릴 수 있도록 크기를 잘라서 당장 실행시킬 부분만 메모리에 올리고 나머지는 하드디스크에 저장하는 기법을

메모리 오버레이라고 부릅니다. 메모리 오버레이를 통해 큰 프로그램을 컴퓨터 내부에 실행시키는 것이지요. 

큰 프로그램을 작게 나누어 일부만 실행하고 나머지는 하드디스크에 저장하는데, 정확히는 하드디스크의 스왑영역에 프로그램이 저장됩니다. 

 

스왑은 말 뜻 그대로 스왑영역에 있는 데이터 일부를 메모리로, 메모리에 있는 데이터를 스왑영역에 옮기는 것입니다. 이렇게 된다면 사용자는 메모리가 8GB이상인것처럼 느끼는 것이지요. 하지만 스왑과정을 행해야 하기 때문에 실제 메모리가 8GB이상인 것보다는 한참 느릴 수 밖에 없습니다. 

 

 

뭐, 단일 프로그래밍이 가능했던 시기는 이런 방법을 통해 메모리가 큰 프로그램을 실행시킬 수 있었습니다.

하지만 요즘은 어떻죠? 수 많은 여러 개의 프로세스가 메모리에 올라옵니다.

 

 

메모리 할당 방식 - 멀티 프로그래밍 

 

하나의 프로그램만 취급하는 유니프로그래밍도 나름 복잡한데, 수 많은 프로그램을 다루는 멀티 프로그래밍은 정말 복잡하다 여겨집니다. 

메모리의 크기를 나누는 방식은 2가지로 나뉘어 집니다. 

프로세스의 크기에 따라 메모리를 나누는, 가변 분할 방식과 프로세스 크기 상관없이 메모리를 정해진 크기로 나누는 고정 분할 방식입니다. 

 

 

가변분할방식과  고정분할방식

가변 분할 방식

 

 

가변 분할 방식은 그림과 같이 프로세스 크기에 따라 메모리를 나눕니다. 한 프로세스가 메모리에 연속된 공간에 할당되기 때문에 '연속 메모리 할당'이라고도 부릅니다. 또 세그멘테이션이라고도 부릅니다.

 

고정 분할 방식

 

 

고정 분할 방식은 페이징으로도 불리며, 프로세스 크기와 상관없이 메모리를 '정해진 크기'로 나눕니다. 그 정해진 크기가 2mb로 정해지면, 그 프로세스가 어떤 크기이든 2mb씩 잘라지게 됩니다. 그러면 5mb는 2mb씩 잘려, 3개로 나누어지고, 3mb는 2mb+1mb , 1mb는 2mb를 할당받아 1mb는 빈공간으로 남습니다. (듣기만 해도 좀,, 비효율적이네요) 한 프로세스가 메모리에 분산되어 할당되기 때문에 비연속 메모리 할당이라 부릅니다. 

 

 

가변분할방식과 고전분할방식의 장단점

 

 

가변 분할 방식, 고정 분할 방식은 각각 장단점이 명확합니다. 

가변 분할 방식의 장점은 프로세스가 하나로 처리되어 연속된 공간에 배치된다는 점입니다. 고정 분할 방식의 단점인 내부단편화 문제가 없습니다. 

외부 단편화

 

단점으로는 외부 단편화가 발생합니다. 외부 단편화 문제는 가변 분할 방식이기 때문에 생겨납니다. 

그림에서 프로세스 18kb와 17kb의 역할이 끝났다 칩니다. 19kb의 프로세스가 새로 메모리에 들어가고 싶어합니다. 무려 18kb와 17kb가 빠져나가 35kb의 크기가 메모리에 있지만, 18kb와 17kb가 비면서 19kb가 들어갈 곳은 없기 때문에 19kb의 프로세스는 메모리 안으로 들어가지 못합니다. 연속공간이 아니라서 새로운 프로세스에게 할당이 불가한 것을 외부단편화 문제라고 부릅니다. 

 

 

고정 분할 방식의 장점으로는 구현이 간단하고 오버헤드가 작다는 점이 있습니다. 

단점으로는 작은 프로세스도 큰 영역에 할당되기에 공간이 낭비되는 내부단편화가 발생합니다. 아까 1mb여도 할당된 메모리가 2mb면 메모리를 할당받고 남은 1mb는 그냥 빈공간으로 남는다고 하였죠, 이 문제가 바로 내부단편화입니다. 

 


"오늘날,

 

 

운영체제"는 가변 분할 방식과 고정 분할 방식을 혼합하여 장점을 늘리고 단점을 줄이는 방식을 가져갑니다. 

 

 외부단편화는 외부단편화가 발생한 공간을 합쳐주는 '조각모음'을 통해 문제를 어느정도 해결할 수는 있습니다. 하지만 조각모음을 하려면 현재 메모리에 실행되는 프로세스의 작업을 중지하며, 메모리 공간을 이동시키는 작업 또한 해야하기 때문에 오버헤드가 발생할 수 있습니다.

 

내부단편화의 문제점을 해결하는 방법은 거의 없습니다. 분할되는 크기를 조절해서 내부단편화를 최소화를 하는 수 밖에 없습니다.

 

 

버디시스템(가변분할방식+고정분할방식)

 

 

가변분할방식과 고정분할방식의 단점을 최소화한 버디시스템이 있습니다. 사실, 버디 시스템은 가변 분할 방식입니다. 하지만 고정 분할 방식과 유사합니다. 

 

버디시스템은 2의 승수로 메모리를 분할해 할당하는 방식입니다. 메모리 크기가 2048 바이트라면 2의 11승이겠죠? 크기가 500바이트인 프로세스가 메모리 할당을 원한다면, 500바이트와 가까워 질 때 까지 값을 나눕니다.

 

2048 % 2 = 1024

1024 % 2 = 512

512 % 2 = 256 

...

 

이렇게 나눕니다. 그러면, 256바이트, 516바이트 중 500바이트가 할당이 되는 크기를 찾습니다. 512바이트가 그 크기겠네요. 내부단편화가 발생하지만 12바이트 정도밖에 나지 않습니다. 이렇게 2의 승수로 나누는 방법은 근접한 메모리 공간을 합치기도 쉽고, 조각모음보다 훨씬 간단합니다. 

 

버디시스템을 이용하면 프로세스 크기에 따라 할당되는 메모리 크기가 달라지며, 외부단편화를 방지키 위한 메모리 공간 확보가 간단해집니다. 고정분할처럼 내부단편화가 살짝은 발생하지만 원래에 비하면 많은 공간 낭비도 발생하지 않기에 큰 장점이 됩니다. 가변 분할 보다는 더 효과적이지만, 고정 분할 방식이 메모리 관리 측에서 단순하기 때문에 버디 시스템보다는 고정 분할 방식이 선호될 때도 많습니다. 

 

 

 

 


 

이번 포스팅에서는 컴퓨터 내부에 있는 메모리의 종류와, 메인 메모리에 대해 알아보았습니다.

메인 메모리는 프로그램이 무조건 거쳐야 하는 공간으로 매우 매우 cpu다음으로 중요한 부품입니다. 과거에 비해 프로그램 크기도 커지고, 동시에 쓰이는 것도 많아지면서 이를 어떻게 효율적으로 쓸지가 중요해졌습니다. 그래서 메모리 관리자가 생겨, 메모리를 어떻게하면 효과적으로 쓸 지 연구가 많이 되었습니다.

 

프로그램이 실행될 때, 메모리 공간, 주소 '어디에' 들어가는 문제가 중요해졌기 때문에 주소 자체가 생성되며, 그 주소를 효율적으로 쓰기 위해 절대주소/ 상대주소라는 개념도 생기게 되었습니다. 프로그램을 더 쉽게 생성하기 위해 논리주소공간이 생겨 0번지에 들어가게 하였고, 메모리 관리자가 실제로 이 주소를 정확한 물리 공간인 절대 주소에 변환하여 넣습니다. 그렇게 되니 다른 프로그램과 겹쳐져 충돌할 일이 사라집니다.

 

프로그램이 메모리에 들어올 때도 '어떻게' 들어오는 지가 중요합니다. 그래서, 가변 분할 방식 / 고정 분할 방식들이 고안됩니다. 

프로그램의 크기 대로 메모리에 넣으니, 그 프로그램들이 다 쓰고 나가면 크기가 맞지 않아 들어가고 싶어도 들어가지 못하는 외부 단편화 문제가 생겨버립니다. 남는 크기는 많은데, 크기가 맞지 않는다라는 이유로 사용이 안되니 이래저래 불편할 수 밖에 없습니다. (가변분할)

 

그래서 고정으로 특정 크기대로 끊어서 프로그램을 넣으니, 이번에는 크기가 작은 프로그램도 특정 크기대로 할당받아 남아 노는 문제가 생깁니다.(고정분할) 그래서, 이 둘의 문제점을 합쳐 2의 승수로 나눠서 크기에 맞춰 나눠지는 방법이 생깁니다.(버디) 이는 남아 노는 분제인 내부 단편화 문제를 최소화 시키긴 하였으나, 2의 승수를 또 구해야한다는 점이 복잡하기 때문에 고정 분할 방식이 많이 쓰입니다. 

 

 

 

교착상태(데드락: dead lock)

 

 

교통체증인 모습

 

 

아침 8시, 9시와 저녁 6시 7시의 서울의 도로를 경험해 본 사람이라면 누구나 교통체증을 느꼈을 것입니다. 차가 빼곡히 줄지어가고, 기어가듯이 몇십분을 도로에서 시간을 허비하고 난 후에야 조금 뻥 뚫립니다. 심지어, 앞에 사고가 났을 경우에는 더 답이 없죠.

이와 같은 상황이 운영체제 내에서도 일어납니다.

 

여러 프로세스(2개 이상의 프로세스)가 서로 다른 프로세스의  작업의 종료만 기다리다가 아무도 아무것도 작업 진행을 못하는 상태를 교착상태라고 부릅니다. 교착상태는 공유자원 때문에 발생합니다.도로에서 왜  교통체증이 일어나는 것일까요? 바로 '도로를 차지하기 위해서'입니다. 집으로 가는 길에는 도로를 통해서 가야하고, 그 도로는 많은 차량들(프로세스들)이 원하고 써야만 하기에 막히는 것이죠. 프로세스도 마찬가지입니다. 여러 프로세스가 작업을 진행하다 보니 자연적으로 발생하는 일이지요. 

 

이 교착상태를 설명하는 유명한 예시가 있습니다. 바로 '식사하는 철학자'입니다.

 

원형탁자에 음식 3개가 있고, 자리도 3개, 식사를 할 철학자도 3명이 있습니다. 음식을 먹기 위해서는 각자 포크 2개씩(총 6개의 포크)을 써야 먹을 수 있는데 포크 마저도 3개 밖에 없죠. 모든 철학자가 식사를 하기 위해서는 한명이 먼저 먹고 다른 철학자는 고뇌하다가 먹고 난 후에 다음 철학자가 먹고 나머지는 고뇌, 마지막 철학자가 먹어야 합니다. 하지만 철학자 두명 혹은 세명이 자신의 포크를 양보하지 않는다면, 이 때 공유자원이던 '포크'가 사라지게 되어 교착상태가 일어납니다.

 

여기서 교착 상태가 발생하는 조건이 4가지가 있습니다. 이는 교착상태의 필요조건과 연관하여 서술하겠습니다.

 


 

교착상태의 조건 

 

교착상태의 필요조건은 4가지가 있습니다. 이 4가지 조건 중 단 하나도 성립하지 않으면 교착상태는 일어나지 않습니다. 

상호배제, 비선점, 점유&대기, 원형대기가 이 4가지 조건입니다. 

 

1.상호배제

 

한 프로세스가 사용하는 자원은 다른 프로세스와 공유할 수 없는 자원입니다. 자원은 임계구역으로 보호되기에 다른 프로세스가 동시에 사용할 수 없습니다. 즉, 자원은 공유가 되어야 하는데, 공유가 되지 않는 배타적인 자원을 사용하면 교착상태가 발생하는 것입니다.

 

 

식사하는 철학자) 철학자들이 서로 포크를 공유할 수 없는 상태입니다. 포크를 공유하지 못하면 영원히 밥은 못먹게 됩니다. 

 

2. 비선점

 

한 프로세스가 사용 중인 자원은 중간에 다른 프로세스가 빼앗을 수 없어야 합니다. 빼앗을 수 없으면 공유도 안되므로 교착 상태가 발생합니다.

 

식사하는 철학자) 갹 철학자는 다른 철학자의 포크를 빼앗을 수 없습니다. 포크를 빼앗을 수 없으면 언제까지나 양보될 때 까지 기다려야 하므로 밥을 못먹습니다.

 

 

3. 점유&대기

 

프로세스가 자원을 할당받은 상태에서 다른 자원을 기다리는 상태에 문제가 생깁니다. 이러면 진행의 방향이 겹치거나 방해가 되므로 교착 상태가 발생합니다.

 

식사하는 철학자) 각 철학자는 왼쪽 포크를 잡은 채 오른쪽 포크를 기다립니다. 그러면 영원히 포크는 공유가 되지 않고 기다리는 상태만 반복되겠죠?

 

4. 원형 대기

 

점유와 대기를 하는 프로세스 관계가 원이 되는 상태. 특정 자원에 대한 점유아 대기는 자연스러운 일이지만, 점유와 대기가 원형이라면 서로 양보를 하지 않기 때문에 교착 상태가 됩니다.

 

식사하는 철학자) 포크를 원하는 방향이 원이라면 아무도 양보하지 않습니다. 철학자a는 포크 달라고 하고, b도 달라고 하고, c도 달라고 하는 상황인 것입니다.

 

 

 

이 네가지 조건이 지켜져야만, 교착 상태가 일어남에도 불구하고 교착 상태는 빈번하게 일어납니다. 교착 상태는 컴퓨터 자원에서 시스템 자원, 공유변수, 응용 프로그램 등에서 일어납니다. 운영체제가 상호 배제를 보장하기 위해 잠금을 사용하기 때문에 이런 상태로 빠지는 경우가 종종 생기는 것입니다.

그렇다면 이런 교착 상태들은 어떻게 해결해야 할까요?

 


 

교착 상태를 해결하는 방법

 

 

교착 상태를 해결하는 방법으로 예방, 회피, 검출, 회복이 있습니다.

 

해결하는 방법1. 예방

 

교착 상태의 문제점을 찾아 (상호배제, 비선점, 점유&대기, 원형대기) 이 문제점이 하나만 없어도 교착 상태가 일어나지 않기 때문에 하나라도 문제가 발생하지 않기 위해 예방하려 했으나 모두 효과가 없었습니다. 그래서 나온 것이 회피, 검출, 회복입니다.

 

먼저 교착 상태를 회피하는 것을 알아보겠습니다.

 

해결하는 방법2.회피

 

교착 상태를 회피하는 것은 할당되는 자원의 수를 조절하여 교착 상태를 아예 피하는 것입니다. 이는 유명한 은행원 알고리즘의 기본 개념입니다. 

 

은행원 알고리즘

 

 

은행원 알고리즘은 은행이 대출해주는 방식, 대출 금액이 안정상태이면 대출 승인이 나지만 그렇지 않다면 거부되는 것과 유사한 방식입니다. 

은행은 '돈'을 아무나한테 빌려주지 않습니다. 만약 아무에게나 빌려준다면, 갚아야 할 사람이 못갚는 상태에 갔을 때 손해가 이만저만이 아니며 [교착상태]에 빠질 수 있습니다. 꼼꼼한 대출 심사를 거쳐 은행은 돈을 빌려줍니다. 이런 꼼꼼한 대출 심사또한 운영체제도 행하게 됩니다.

먼저 운영체제는 프로세스에게 자원을 할당하기 전에 총 자원이 몇 개있는 지 시스템의 총 자원을 파악하고, 프로세스들은 각자 자기가 필요한 자원의 최대숫자를 운영체제에게 알려줘야 합니다. 그래야 정확한 대출심사가 가능하니까요.

 

예시로 운영체제에게 총 자원이 14개 있다고 칩니다.

 

p1, p2, p3가 운영체제에게 자원을 달라 요청을 했다고 칩시다.

 

총자원 14       이용가능 자원: 2
process 최대 요구 자원 할당된 자원 요청 예상 자원  
p1 7 2 3 1개모자람
p2 6 4 2 딱 맞음!
p3 10 6 4 2개모자람

 

 총 자원이 14개인데 현재 할당된 자원은 12개입니다. 2개의 자원이 남아 있습니다. p2의 요청 예상 지원이 2개기에 p2에게 자원을 다 할당해주고 p2는 총 6개의 자원을 다 쓰고 돌려줄 수 있습니다. 그리고 p1, p3에서 다시 나눠 줄 수 있습니다. 

 

이렇게 자원 문제가 유기적이로 해결되는 것을 안정 상태라고 부릅니다. 딱딱 맞아 떨어집니다!

 

하지만 항상 총자원과 최대요구자원, 이용가능한 자원이 다 맞을 수는 없습니다. 

 

총자원 14       이용가능 자원: 1
process 최대 요구 자원 할당된 자원 요청 예상 자원  
p1 7 3 3 2개모자람!
p2 6 4 2 1개모자람!
p3 10 6 4 3개모자람

 

이렇게 이용가능한 자원이 1개고 요청예상 자원이 2~4개라면 어떤 프로세스도 끝낼 수 없는 상태가 옵니다. 이를 불안정 상태라고 부릅니다. 은행원 알고리즘은 이론적으로는 완벽하지만, 비용이 비싸고 비효율적입니다. 프로세스가 모든 자원을 미리 선언하고, 전체 자원 수도 고정적이여야하며, 자원이 낭비된다는 큰 단점이 있습니다. 

 

 

해결하는방법3.검출

 

위에 서술했던 해결 방법인 예방과 회피는 각각 실제로 구현하기 어려움과, 자원을 낭비하는 문제가 있습니다. 해결방법 '검출'은 해결방법 중 가장 현실적이다라는 평을 듣습니다.

교착 상태 검출은 운영체제가 프로세스의 작업을 관찰하며 교착 상태 발생 여부를 계속 주시하는 방식입니다. 교착 상태가 발견되면 해결을 위한 회복 단계를 거치지요..

 

교착 상태  검출은 두가지 방법으로 나눠지는데, 이 방법들은 가벼운 교착 상태 검출과 무거운 교착 상태 검출입니다. 

가벼운 교착 상태의 검출은 타임 아웃을 이용하며 무거운 교착 상태 검출은 자원 할당 이용 그래프를 이용합니다.

 

먼저 가벼운 교착 상태의 검출을 살펴보겠습니다.

 

타임아웃을 이용한 교착 상태 검출

 

컴퓨터를 쓰면서 '프로그램이 응답이 없어 종료합니다'를 본 적이 한 번쯤은 있을 것입니다. 이 메세지가 바로 타임아웃을 이용하는 방법입니다. 작업이 여러 개일 때, 잠금도 여러개를 얻는데 이 작업 도중 타임아웃으로 프로세스가 종료되면 데이터에 문제가 생길 수 있습니다. 이 문제를 해결하기 위해서 체크포인트와 롤백을 사용합니다. 체크포인트는 문제가 발생하면 저장된 상태로 되돌아오기 위한 표시입니다. 이 체크포인트 설정과 함께 현재 시스템 상태는 하드디스크에 저장되며 이 저장 데이터를 스냅쇽이라고 하죠. 롤백은 말 그대로, 작업을 하다가 문제가 발생되면 체크포인트로 돌아가는 것입니다. 

타이머를 이용하여 프로세스가 일정시간동안 진행되지 않는다면 교착상태가 발생했다고 간주하고, 일정시점마다 만든 체크포인트로 작업을 저장하여 프로세스 진행이 안된다면 그 저장된 시간으로 롤백시키는 것입니다. 

롤백이 일어나면 스냅숏을 복원해 다시 체크포인트 시점으로 돌아가는 것이죠. 

 

 

 

타임아웃을 이용하면 두 가지 단점이 있습니다. 문제가 있는 프로세스 뿐만이 아니라 다른 프로세스까지 강제종료 당하는 것이 가장 큰 단점입니다. 어떤 앱을 이용하다가 렉이 걸렸는데, 그 렉 때문에 함께 사용하던 다른 앱들도 강제 종료된 경험이 있을 것입니다. 그럴 때 너무 억울한 상황이 벌어지기도 하죠!! 또, 모든 시스템에 적용이 힘들다는 것도 문제가 됩니다.

 

하지만, 자원 할당 그래프를 이용하는 방법보다 더 효율적으로 쓰일 때가 많으므로 자주 쓰이는 방식이며, 자원 할당 그래프는 좀 더 구현하기가 힘들기 때문에 비교적 구현하기 쉬운 타임아웃을 '가벼운 교착 상태의 검출'이라고 부릅니다.

 

 

자원 할당 그래프를 이용한 교착 상태 검출

 

자원 할당 그래프를 운영체제가 지켜보며 교착 상태가 발생하면 해결하는 방식입니다. 

 

 

그림에서 a는 p1은 p2의 자원을 기다리고, p2는 p3의 자원을 기다리고 있습니다. 철학자의 탁자에서 철학자 한명이 포크를 사용하는 것을 기다리며 사색을 하는 것이죠. 이러면 자원이 교착상태가 되는 일이 없습니다.

 

하지만 b에서 p1->p2->p3->p2->p1 ... 의 사이클이 생깁니다. 철학자들 한명씩 포크를 들고 계속 옆에 포크를 요구하는 상황이나 다름 없습니다. 이를 교착상태로 포착하여 문제를 해결하는데, 정확한 문제를 파악할 수 있는 장점이 있지만 이 그래프를 지켜보며 유지, 갱신, 검사하는 추가 작업으로 오버헤드가 발생할 수 있습니다. 그래서 무거운 교착 상태의 검출이라 부르며, 오버헤드의 문제로 가벼운 교착 상태의 검출의 단점이 있어도 타임아웃을 주로 사용하는 이유입니다.

 

 

해결하는방법4.회복

 

교착 상태 검출 후에 교착 상태를 푸는 방법이 있어야겠죠? 회복 단계에서는 교착 상태를 유발한 프로세스를 강제 종료하며, 강제 종료된 프로세스가 실행되기 전 시스템을 복구해야 합니다.

 

 

 


 

이번 포스트에서는 교착 상태 dead lock을 알아보았습니다.

교착 상태는 그냥 일어나는 것이 아니라 4가지의 필요조건에 의해 야기 되며, 프로세스끼리의 통신을 위한 임계구역의 존재로 인한 상호배제 문제가 원인입니다. 임계구역을 해결 방법은 그 구역을 lock하는 것이였고, lock에 대한 오류로 인해 교착 상태가 일어나므로 lock은 죽은 상태, 반응을 하지 않는 상태이기에 dead lock이라고도 부릅니다. 이를 해결하기 위해 4가지의 해결방법이 있으며, 주로 검출의 타임아웃 방법을 사용하여 교착상태를 해결을 합니다.  

 

 

 

 

독립적인 프로세스끼리 작업을 하면 서로 데이터가 필요할 때가 있습니다. 데이터를 주고받을 때 통신을 사용하게 됩니다. 통신을 하면서, 누가 먼저 작업할 건지 언제 작업이 끝날지에 대해 서로 알려주는 것을 동기화라고 합니다. 같은 데이터를 여러 프로세스가 사용할 땐 서로 침범하면 안되는 임계구역도 필요합니다. 프로세스끼리 어떻게 통신을 하는지, 자원을 어떻게 나누며 동기화는 어떻게 하는 지  임계구역이 왜 필요한지를 이번 글에서 다뤄보도록 하겠습니다.

 


 

프로세스  통신

 

프로세스는 독립적으로 실행되기도 하지만 다른 프로세스와 데이터를 주고 받으며 협업을 하며 통신을 주고 받는 경우가 있습니다. 통신은 세 가지 방식으로 가능한데요, 프로세스 내부에서, 한 컴퓨터 내에서  실행되는 다른 프로세스와, 네트워크로 연결된 다른 컴퓨터에 있는 프로세스와도 함께 할 수 있습니다. 

 

첫 번째로 다룰 것은 하나의 프로세스 내부에서 스레드끼리의 통신입니다. 쓰레드는 프로세스 내에서 코드, 데이터, 힙을 공유한다고 했죠. 이 공유하는 데이터의 전역변수나 파일, 힙을 통해 서로 통신하며 데이터를 주고받을 수 있습니다. 이때는 굳이 운영체제의 도움이 필요하지 않습니다. 그냥 데이터를 자기들끼리 갖다 쓰는 거니까요

 

두 번째는 프로세스 간 데이터 통신입니다. 프로세스 간 데이터 통신은 파일과 파이프를 이용합니다. 파일은 통신하려는 프로세스들이 하나의 파일을 이용해 읽고 쓰는 방법입니다. 파이프는 운영체제가 생성한 파이프를 이용해 데이터를 읽고 쓰는 방법입니다. 프로세스 내부에서는 운영체제가 개입하지 않지만 프로세스 간 데이터 통신에서는 파이프를 운영체제가 제공한다는 것을 알 수 있습니다.

 

마지막으로는 네트워크를 이용한 데이터 통신입니다. 여러 컴퓨터가 네트워크에 연결되어 있거나, 하나의 컴퓨터와 다른 컴퓨터의 데이터를 주고 받을 수 있는 통신입니다. 이 경우 프로세스는 운영체제가 제공하는 소켓을 이용해 데이터를 주고 받습니다. 소켓 통신으로 다른 컴퓨터의 함수를 호출하는 원격 프로시저 호출PRC도 포함하며, 소켓 통신을 네트워킹 통신이라고도 부릅니다.

 

 

이렇게 세가지의 방식으로 통신이 발생하곤 합니다. 여기서 전역 변수, 파일, 파이프, 소켓은 프로세스 간 통신의 매체입니다. 이 매체들의 특징과 이를 이용한 프로세스 동기화가 어떻게 이루어지는지 다뤄보겠습니다.

 

 

1. 전역변수

 

전역 변수는 같은 프로세스 안, 쓰레드에서 쓰입니다. 당연한 말이게도, 전역변수를 이용하면 공동으로 관리하는 메모리를 사용하여 데이터를 주고 받습니다.

 

데이터를 보내는 쪽에서 전역 변수에 값을 쓰고 데이터를 받는 쪽에서는 전역 변수의 값을 읽는 식입니다. 운영체제는 전역 변수에 개입하지 않습니다.

 

서로 연관이 없는 프로세스 간 통신에도 extern 변수와 같은 전역 변수를 사용할 수 있습니다.

 

2. 파일

 

파일은 말 그대로 저장장치에 파일을 읽고 쓰는 코드이며, 입출력을 하여 통신을 합니다. 파일 입출력 코드는 세 부분으로 나눠지는데, 열고open, 쓰거나 읽고 write read 연산 후, 파일을 닫는 close 것입니다. 파일 입출력 코드를 프로세스 입장에서 살펴보면, 쓰기를 요구하면 데이터가 저장되고, 읽기를 요구하면 입출력 관리 프로세스로부터 데이터를 가져옵니다. 이런 데이터 전송 명령, 데이터 가져오는 명령등이 운영체제에서도, 프로세스 입장에서도, 일반 프로세스와 입출력 프로세스 간의 통신입니다. 파일을 이용한 통신은 자주 사용되지만 운영체제가 프로세스 동기화를 제공하지는 않습니다. 프로세스 알아서 동기화를 하는데, 주로 wait()함수를 이용합니다.

 

3. 파이프

 

파이프는 운영체제가 제공하는 동기화 통신 방식입니다. 운영체제가 만든 파이프에 쓰기 연산, 읽기 연산을 통해 데이터 전송과 받아오는 것을 행합니다. 이또한 open(), close()함수를 이용합니다.

 

4. 소켓

 

여러 컴퓨터에 있는 프로세스 간 통신을 네트워킹이라고 부른다 하였습니다. 네트워킹 상황에서 통신은 원격 프로시저 호출이나 소켓을 이용합니다. 프로시저 호출은 한 컴퓨터 내에 있는 함수를 호출하는 것이고, 원격 프로시저 호출은 다른 컴퓨터의 함수를 호출하는 것입니다. 보통은 원격 프로시저 호출을 소켓을 이용합니다. 소켓을 이용하여 다른 컴퓨터에 있는 프로세스와 통신을 하려면, 컴퓨터의 위치를 파악하고 원격하려는 어떤 프로세스와 통신할 건지도 결정해야 합니다. 우리가 노트북과 패드로 크롬 페이지를 연결할 때, 괜히 패드의 위치를 묻는것을 생각하면 쉽습니다.

통신을 원하는 프로세스는 소켓에 쓰기 연산을 하여 데이터 전송을 하고 읽기 연산을 통해 데이터를 받습니다.

 

 

프로세스 간 데이터를 주고 받는 것을 통신이라고 하며, 이 행위는 읽기와 쓰기 연산으로 단순화 됩니다. 프로세스 특성에 따라 전역변수, 파일, 파이프, 소켓 등으로 통신을 할 수 있습니다. 

 

프로세스 통신 방법 운영체제 동기화 통신 종류
전역변수 X 프로세스 내부(쓰레드)
파일 X (wait()함수) 쓰레드, 프로세스 간
파이프  O 프로세스 간
소켓 O 네트워크 간

 

 

 


 

공유 자원과 임계구역 

 

 

프로세스 간 통신을 할 때 공동으로 이용하는 변수, 메모리, 파일들을 공유 자원이라고 합니다. 공유 자원은 여러 프로세스가 공유하기 때문에 각 프로세스의 접근 순서에 따라, 프로세스 결과값이 달라질 수 있습니다. 그렇다고 어떤 프로세스가 먼저 실행되는 지 예측하기는 쉽지 않습니다. (Cpu스케줄링 처럼) 이 연산결과를 예측하기 힘들기 때문에 이 문제를 동기화 문제라고 부릅니다.

 

예시를 들어보겠습니다. 누구나 공동으로 이용하는 변수에 예금 10만원을 넣어보겠습니다. 앞으로 이 변수는 예금입니다. 그 후에, 프로세스1과 프로세스2가 이 예금에 접근한다고 생각해보겠습니다. 프로세스1은 예금 10만원을 확인하고 빠져있다가, 프로세스 2가 예금에 접근해 5만원을 넣습니다. 5만원을 저장하기도 전에 프로세스1이 예금에 10만원을 넣습니다. 그리고 프로세스2가 예금을 확인하면 20만원이 총 잔액으로 들어와 있습니다. 5만원은 어디간걸까요? 이미 5만원을 냈기 때문에 프로세스2에겐 5만원이 더 이상 존재하지 않지만, 총 예금에는 프로세스2가 낸 5만원이 있지 않습니다. 

 

2개 이상의 프로세스가 공유 자원을 병행적으로 읽거나 쓰는 것을 '경쟁 조건'이 발생했다고 합니다. 이 경쟁 조건으로 인해 큰 문제가 더 일어나면서 혼란이 야기될 수 있겠죠. 이를 고치기 위해 프로세스가 동시에 사용하면 안되는 영역인 "임계구역"을 설정합니다. 

 

이 임계구역에서는 프로세스들이 동시에 작업하면 안되며, 프로세스들은 임계구역에 들어간 프로세스를 대기큐에서 기다립니다. 

 

한편, 임계구역이 명확하지 않으면 문제가 야기 됩니다. 하드웨어 측면에서 살펴보면, 프린터 1대를 두 컴퓨터가 나눠 쓰며 출력을 동시에 했다고 칩니다. 이 때, 명확하지 않은 임계구역으로 인해 두 개의 컴퓨터의 출력물이 뒤섞여 나올 수 있는 문제가 생깁니다.

 

임계구역을 어떻게 사용할 건지에 대한 해결방법으로, 여러 방안들이 나오곤 했습니다. 이 해결 방법을 알아보기 전에 알아야 할 세가지 조건이 있습니다. 이 조건들을 임계 구역 해결 조건이며, 이 세가지 조건을 만족해야만 임계구역의 문제를 해결하 수 있습니다. 

 

 

1. 상호 배제

2. 한정 대기

3. 진행의 융통성

 

 

상호 배제 특성은 이 조건 중 제일 중요합니다. 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없는 특성입니다.꼭 임계구역 내에는 한 번에 하나의 프로세스만 있어야하는 원칙이 상호 배제입니다.

 

한정 대기 조건은 임계구역에 들어가지 못하여 기다리는 프로세스들이 무한 대기하지 않는 것입니다. 모든 프로세스들은 임계 구역에 진입할 수 있습니다.

 

진행의 융통성은 한 프로세스가 다른 프로세스의 진행을 방해해서는 안 되는 것을 의미합니다.

 

이 세가지가 지켜져야 임계구역 해결 방법이라고 할 수 있으며, 특히 상호 배제 특성이 제일 중요합니다.

 

 


 

임계구역 해결방안 

 

 

임계구역의 문제 해결방안은 그냥 잠그는 것입니다. 임계구역을 잠금으로써, 상호 배제적 특성을 바로 지닐 수 있습니다. 하지만 냅다 잠금다고 모든 문제가 해결되는 것은 아니겠죠? 잠금과 관련하여 수많은 해결방안 알고리즘이 있었습니다. 

 

세마포어

 

피터슨, 데커 알고리즘 등 임계구역의 문제를 해결하려는 시도는 많았습니다. 세마포어는 임계구역 문제를 해결할 수 있는 좋은 알고리즘입니다. 

세마포어에서 프로세스는 공유자원을 쓰기 위해 대기큐에서 기다리며, 운영체제가 순서대로 공유자원을 임계구역에 들어가게 해줍니다. 세마포어는 세마포어를 선언하고, wait()함수를 사용해 임계구역을 잠금니다. 그리고 signal()함수가 호출될 때까지 기다립니다. 하지만 세마포어의 코드가 실행되는 도중에 다른 코드가 실행된다면? 상호 배제 원칙에서 어긋날 수 있습니다. 이처럼 세마포어를 이용하면 여러 프로세스가 동시에 접근하지는 못하지만, 함수의 이상한 사용으로 인해 임계구역이 제대로 보호받지 못합니다. 

 

그렇다면 어떻게 임계구역도 보호받고, 해결방안의 조건을 만족할 수 있을까요?

 

바로 모니터입니다. 모니터는 진짜 우리가 아는 물리적 모니터가 아니라 코드, 알고리즘입니다. 

 

 

모니터

 

 

모니터는 공유 자원을 내부적으로 숨기고 공유 자원에 접근하기 위한 인터페이스만 제공함으로써 자원을 보호하며 프로세스 간에 동기화를 시킵니다. 모니터는 시스템 콜의 개념과 같습니다. 시스템 콜은 운영체제가 자원을 숨기고 유저가 원하는 인터페이스만을 제공하는 것을 의미합니다. 시스템 콜처럼 모니터도 보호 자원을 임계구역에 숨기고 임계구역에서 작업할 수 있는 인터페이스만 제공하여 자원을 보호합니다. 

 

즉, 임계구역에 들어가고자하는 프로세스는 모니터에 직접 작업요청을 하며, 모니터는 대기 해야할작업을 저장하고 순서대로 처리하여 결과만 프로세스에 알려주는 식입니다. 아예 접근을 못하게요! 이렇게 설정한다면 상호 배제원칙에도 어긋나지 않고, 무한 대기도 시키지 않으며, 서로 프로세스 간의 방해도 일어나지 않겠죠! 

 

모니터의 구현이 완벽하다면, 프로그래머는 임계구역을 신경쓰지 않고 편리하고 안전하게 코드를 작성할 수 있습니다.

 

모니터의 하나 큰 특징이 있다면, 프로그래밍 언어차에서 지원한다는 것입니다. 대표적으로 자바에서 이를 지원하는데, 자바에서 Syncronized가 붙는다면 그 구역은 임계지역으로 건들이면 안되는 곳입니다.

 

 

 

이번 포스팅에서는 프로세스 간 데이터를 주고 받는 것을 어떻게 하는 지(통신), 통신 종류는 무엇이 있는지(전역변수, 파일, 파이프, 소켓), 

통신을 통한 데이터 접근을 하면서 어떤 과정을 겪는 지,  그 과정에서 공유 자원을 어떻게 활용하는 지, 공유 자원을 쓰면서 문제점인 임계구역의 문제와 해결 조건, 해결 방법을 알아보았습니다. 

'CS' 카테고리의 다른 글

OS | 메모리  (0) 2025.03.26
OS | 교착상태, 데드락  (5) 2025.03.26
OS | CPU 스케줄러, CPU 스케줄링 알고리즘  (0) 2025.03.26
OS | 프로세스와 스레드  (0) 2025.03.26
OS | 운영체제 컴퓨터의 구조, 특성  (0) 2025.03.26

 

 

CPU스케줄링이란?

 

 

 

프로그램을 실행시키면 메모리는 프로세스를 생성하고 각 프로세스는 쓰레드가 생깁니다. CPU를 차지하기 위해 모든 프로세스등은 운영체제의 명령을 기다립니다. 운영체제가 모든 프로세스에게 CPU를 할당/해제 하는데 이를 CPU스케줄러라고 부릅니다. 운영체제의 목표는 컴퓨터 자원, 특히 cpu를 얼마나 효율적으로 쓰는 지에 있습니다. 프로세스들이 얼마나 빠르게 반응하고, 그에 따라 자원들이 적재적소로 활용되는 것. 이 목표가 중요하기 때문에 cpu스케줄링의 최적화도 매우 중요합니다. 

 

CPU스케줄링의 최적화 목표에는, 

 

 1. 어떤 프로세스에게 cpu리소스를 주어야 하는지?

 2. cpu를 할당받은 프로세스가 얼마의 시간동안 사용되는가?

 

 

이 두개가 있습니다.

 

프로그램을 가동시켜 프로세스가 되면 <생성-준비-대기-실행-완료>의 다섯가지 과정을 거치게 됩니다. 이 때 여러개의 프로세스가 가동될 시에는 운영체제는 cpu를 프로세스들에게 번갈아서 할당해줍니다. 실행에 있는 프로세스는 할당이 끝나면 준비단계로, i/o요청이 있다면 대기 상태로 가게 됩니다. 

 

참고로 준비, 대기 상태는 큐 자료 구조로 관리됩니다. *큐 자료구조는 마트 계산대다. 먼저 온 사람이 먼저, 나중에 온 사람은 나중에. 

 

 

프로세스들이 cpu를 받는 대기 상태로 들어왔을 때, 어떤 프로세스가 우선순위를 갖게 될까요? 저번에 다룬 주제에서 프로세스 별로 우선순위가 있다고 언급했습니다. 그 우선순위는 빠르게 들어왔거나, 자주 많이 쓰거나, 그 외의 이유 등등이 있습니다. 준비, 대기 상태가 큐 자료 구조로 관리되기 때문에 주로 먼저 들어온 프로세스가 실행이 먼저되지만 우선순위라는 것은 항상 존재합니다. 우선순위가 높으면 더 빨리 들어온 프로세스보다도 더 빨리, 더 많이 cpu가 할당되곤 합니다.

cpu스케줄러가 매번 프로세스 제어 블록을 뒤져 우선순위를 검색하는 것은 효율적이지 않습니다. 그러므로 프로세스의 우선순위 별로 정리 되어있는 준비상태의 다중 큐로 프로세스의 정보가 들어있는 pcb가 들어갑니다. 이 pcb의 정보에 따라 cpu스케줄링의 프로세스 관리가 정해집니다.

 

 정리하자면, 운영체제는 프로세스들을 실행할 때, cpu를 할당해주는데, cpu할당을 하는 것은 다양한 조건을 가지며, 프로세스의 정보를 가지고 있는 pcb가 준비상태의 다중큐에 들어가고, cpu스케줄러는 준비상태의 다중큐를 보고 어떤 것을 작동시킬지 결정합니다.

 

cpu스케줄링은 빠르고 정확한 것이 중요하겠지요? 이럴 땐 항상 알고리즘을 세워 어떻게 하면 효율적이게 작동하는 지 보는 것이 중요합니다. cpu스케줄링을 빠르게 하는 시도는 컴퓨터가 빠르게 작동하고 발전하기 위해 필연적이였습니다. 다양한 cpu스케줄링 알고리즘이 있으며, 이에 따른 평가 기준을 알아보겠습니다.

 

 


 

CPU스케줄링 알고리즘 평가기준

 

cpu스케줄링 알고리즘 평가 기준

1. 리소스 사용률 

    a. cpu 사용률 높이기 가만 있는 cpu 절대 못봐! cpu가 무조건 많이 쓰여야 합니다.

    b. i/o 디바이스 사용률 높이기

2. 오버헤드 최소화 (overhead: 과부화)

    스케줄링 계산이 너무 복잡하거나 문맥교환 context switch가 빈번하면 오버헤드가 일어납니다.

3. 공평성

    모든 프로세스에게 공평한 cpu할당이 이뤄져야 합니다.

4. 처리량

    같은 시간 내에 더 많이 처리할 수 있는 방법이 목표가 됩니다.

5. 대기 시간 

    작업 요청, 처리 시 대기시간이 짧은 것이 중요합니다.

6. 응답 시간 

    얼마나 빠르게 반응이 오는 지가 중요하기 때문에 응답시간이 매우 매우 중요합니다.

7. 반환 시간

    대기시간 + 실행시간 더한 값

 

  스케줄링 알고리즘의 성능을 비교할 땐 평균 대기 시간을 측정합니다. 평균 대기 시간은 모든 프로세스의 대기 시간을 합한 후, 프로세스로 나눈 값입니다.

 

이런 평가 기준으로 그동안의 스케줄링 알고리즘이 발전했고 평가 받습니다. 

 

 


 

스케줄링 알고리즘 

 

 

1. FCFS (= FIFO First Come First Served, First In First Out)

   

말 그대로 먼저 온 사람이 먼저 나가는 선입선출 스케줄링입니다.

 

장점: 단순하며 직관적입니다.

 

단점: 프로세스가 끝나야 다름 프로세스가 진행됩니다. 실행 시간이 짧고 늦게 도착한 프로세스1이 실행 시간이 길고 빨리 도착한 프로세스2를 기다리니 비효율적입니다. 또, i/o작업을 할 땐 cpu가 쉬기 때문에 cpu사용률이 떨어집니다.

 

 

2. SJF (=Shortest Job First)

   

 

큐에 있는 프로세스 중 실행 시간이 가장 짧은 작업부터 cpu를 할당하는 방식이며, FCFS에서 짧은 시간의 프로세스가 먼저 실행되면 평균 대기 시간이 짧아지는 아이디어를 보완한 알고리즘입니다. 최단 작업 우선 스케줄링이라 부릅니다. 

 

장점: 실행시간(=burst time)이 작은 작업을 선 실행하기에 시스템의 효율성이 높아지는 장점이 있어 효율적입니다.

 

단점: 실제로 구현 시 문제가 많습니다. 일단 1번, 어떤 프로세스가 얼마나 실행될 지 예측할 수가 없습니다. 2번, burst time이 긴 프로세스는 자꾸 뒤로 밀려 아주 오랫동안 실행되지 않을 수 있습니다. 이는 cpu스케줄러 알고리즘의 공평성에 매우 위배되는 문제이기 때문에 잘 쓰지 않습니다.

 

3. HRN (=Highest Response Ratio Next)

     

 

 SJF 알고리즘을 보완해서 또 만든 알고리즘! 최고 응답률 우선 스케줄링입니다. HRN은 SJF의 공평성 문제를 해결하기 위해 기다린 시간과 CPU 사용시간을 고려하여 스케줄링하는 방식입니다. 평균 대기 시간을 계산하여 우선순위를 만들었지만 이 또한 공평성에 또 위배되기에 잘 쓰이지 않습니다.

 

4. RR (=Round Robin)

 

     

FCFS를 또 보완해서 만든 알고리즘입니다. 똑같이 FCFS처럼 순서대로 작업을 처리하지만, 프로세스1에게 일정시간 만큼 cpu를 할당하고 할당시간이 지나면 강제로 다른 프로세스2에게 cpu점유율권을 빼앗깁니다. 이 때 프로세스 1의 순서는 맨 뒤로 가며, 할당시간을 타임슬라이스, 타임퀀텀이라 부릅니다.

 

    

 

 *만약 타임슬라이스가 무한대라면?

   

 FCFS알고리즘과 다를 바가 없으며 프로세스1의 실행이 매우 길다면, 프로세스2를 동시에 실행할 시에 굉장히 끊기는 상황이 생길 것입니다.

 

*타임슬라이스가 굉장히 작은 단위 1ms가 된다면? (*1,000밀리초 = 1초)

     

프로세스1과 프로세스2가 동시에 작업하는 것처럼 느껴지겠지만, 문맥교환이 빈번해 프로세스 처리량보다 문맥교환 처리량이 더 커지면서 오버헤드가 생깁니다.

 

*그렇다면 최적의 타임슬라이스는? 

 

사용자가 동시에 프로세스가 실행되는 것처럼 느끼면서 오버헤드도 적당한 정도입니다.

유닉스 운영체제는 타임슬라이스를 100ms, 윈도우는 20ms를 유지하고 있으며 10~200ms가 평균입니다.

 

 

5. SRT (=Shortes Remaining Time)

 

 

SJF + RR의 혼합방식입니다. RR을 기본으로 하지만, CPU를 할당받을 프로세스를 선택할 땐 남아 있는 작업 시간이 가장 적은 프로세스를 선택합니다.

 

장점: SJF와 비교했을 땐 평균 대기 시간이 짧습니다.

 

단점: SJF의 단점이였던 운영체제의 종료시간을 예측하기 어렵다는 것과, 불공평하다는 단점이 있습니다. 또 남은 시간을 계산하고 남은 시간이 적은 프로세스와 문맥교환이 빈번히 이뤄지므로 복잡합니다.

 

 

6. MQ (=Multilevel Queue)

 

 

우선순위에 따라 준비 큐를 여러 개 사용하는 방식입니다. 프로세스는 운영체제로 받은 우선순위로 해당 우선순위의 큐에 삽입됩니다. RR로 운영되는 큐며, 우선순위에 따라 다단계로 나뉘어져 있고, 상단의 큐에 있는 모든 프로세스 작업이 끝나야 다음 우선순위의 큐로 작업이 진행됩니다.

 

장점: 우선순위가 높은 프로세스가 우선순위 낮은 프로세스보다 먼저 작동하며, 우선순위와 작업 형태에 따라 타임슬라이스를 조절해 작업 효율을 높입니다.

 

단점: 우선순위가 높은 프로세스 때문에 비교적 낮은 프로세스의 작업이 연기됩니다.

 

 

7. MLFQ (=Multi Level Feedback Queue) 운영체제에서 가장 일반적으로 쓰이는 알고리즘

 

 

rr, mq의 업그레이드된 알고리즘입니다. 우선 순위를 가진 큐를 여러개 준비해서 우선 순위가 높으면 타임슬라이스를 적게 배치합니다. 우선 순위가 낮을 수록 타임 슬라이스의 크기는 커집니다. 프로세스 p1이 큐에 들어갔다가 첫 타임슬라이스 10초 후엔 20초인 큐에 들어가고, 계속 반복되면 최종적으로 타임 슬라이스가 무한초로 할당 받게 됩니다. 마지막 큐는 FCFS 스케줄링 방식으로 작동하니, 작업 속도가 제일 효율적입니다.

 


 

*cpu처리가 우선인 p1과 i/o작업이 병행되는 p2가 있다면? 

 

cpu처리가 우선인 작업을 cpu bound , i/o처리가 우선인 작업을 i/o bound라고 하는데,  cpu bound는 컴퓨터 내의 설치, 수학 계산, 과학 수치 계산을 주로 쓰며 i/o bound는 우리가 컴퓨터에 글을 쓰는 등의 행위이기 때문에 i/o  bound는 응답속도가 제일 중요하며 cpu bound는 cpu사용률과 처리량이 가장 중요합니다. 이를 병행했을 시에, 타임 슬라이스가 길다면 cpu사용률이 좋을지는 몰라도 i.o bound의 응답속도는 매우 느려서 답답할 것입니다. 이 때 타임슬라이가 적다면 문맥교환, 컨텍스트 스위칭은 자주 일어나지만 i/o사용률이 높아져서 사용자가 느끼기엔 매우 쾌적한 컴퓨터 환경일 것입니다. 이득을 크게 하고 손해를 보지 않는 작업을 MLFQ알고리즘은 계속 진행합니다.

 

*CPU바운드 프로세스와 i/o bound 구분을 어떻게 할까?

 

 cpu를 사용하는 프로세스가 실행하다가 스스로 cpu를 반납하면 cpu 사용률이 적은 i/o일 확률이 높고 반대로 cpu를 사용하는 프로세스가 타임슬라이스 크기를 오버해서 cpu스케줄러에 의해 강제로 cpu를 빼앗기면 Cpu bound일 확률이 높기 때문에 이를 계산해서 구분을 할 수 있습니다.

 

 

 

우리가 컴퓨터를 켜서 어떤 앱을 실행할 때, 그 앱은 프로그램이라고 부르며 실행 시 메모리에 올라가게 되므로써 프로세스가 됩니다. 프로세스들은 cpu의 할당을 받아야 제대로 실행이 됩니다. 프로세스들은 pcb를 생성하고, 쓰레드도 생성합니다. 프로세스pcb는 앱이 켜졌다가 종료될 때 까지  생성-준비-대기(입출력)-실행-완료의 단계를 거칩니다. 준비상태에서는 다중 큐라는 작업대에서 기다리게 되는데, 이 작업대는 대체적으로 순서대로 들어가는 곳입니다. 하지만 우선순위라는 것이 pcb정보 내에 존재하기 때문에 우선순위가 높은 것이 더 빨리 실행되곤 합니다. cpu스케줄러는 실행한 앱들, 프로세스들이 잘 실행되는 것이 중요하기 때문에 순서대로 작업을 받긴 하지만 우선순위를 갖는 것을 먼저 실행시킵니다. 한 프로세스만 실행시키면 매우 불공평하기 때문에 cpu스케줄러는 번갈아 cpu를 할당해주는데, 이 할당 방법이 매우 중요하므로 정교한 알고리즘이 필요합니다. 그 알고리즘은 꾸준히 발전하였으며 평가 기준이 있습니다. 지금 쓰는 알고리즘은 우선순위대로 타임퀀텀을 배당해주는 것입니다. 

 

이를 보면 cpu의 할당 방법과 기준을 잡는 것이 운영체제에게 얼마나 중요한 지 알 수 있습니다. 

'CS' 카테고리의 다른 글

OS | 교착상태, 데드락  (5) 2025.03.26
OS | 프로세스 동기화, 통신, 공유자원, 임계구역  (0) 2025.03.26
OS | 프로세스와 스레드  (0) 2025.03.26
OS | 운영체제 컴퓨터의 구조, 특성  (0) 2025.03.26
OS | 운영체제란?  (0) 2025.03.26

 

프로그램과 프로세스

 

 

 

프로그램과 프로세스의 차이를 아시나요? 프로그램은 하드디스크에 저장된 명령문의 집합체라고 볼 수 있습니다. 카카오톡이라는 프로그램은 컴퓨터에 설치한 순간 하드디스크로 들어가며, 더블클릭을 누르기 전까지는  아이콘으로만 보여지죠. 더블클릭을 누른 순간 카카오톡 프로그램은 실행됩니다. 프로그램이 실행이 되기 위한 필수조건은 무엇일까요? 바로 '메모리에 올라가는 것'입니다. 메모리에 올라갔을 때 실행중인 프로그램을 프로세스라고 합니다. 즉, 프로그램이 실행되어 메모리에 올라가면 '프로세스'가 되는 거지요. 프로그램은 저장장치만 사용하는 수동적 존재이며 그 반대로 프로세스는 메모리, cpu 등의 사용을 하는 운영체제 속에서 능동적인 하나의 작업 단위입니다. 즉, 카카오톡 자체는 프로그램 누르면서 실행되는 순간 프로세스가 됩니다.

 

프로그램에서 프로세스로 전환될 때 생성되는 프로세스 제어 블록pcb는 다양한 정보값을 지니며 메모리와 cpu사이를 왔다갔다하고, 프로세스의 이용이 끝나면 폐기 됩니다. 

 

위 그림은 프로그램에서 프로세스로 전환시 저장장치와 메모리의 관계를 보여줍니다. 저장장치(하드디스크)에 있는 프로그램 a,b,c를 실행시킬 때 메모리에 프로그램이 올라가며 프로세스로 전환됩니다. 위에 언급한 프로세스 제어 블록pcb는 이때 생성되며 운영체제 영역에서 활동하게 됩니다. 

 

프로세스를 이해해야 스레드, cpu스케줄링 이해가 더 쉽기 때문에 프로세스의 구조, 상태, pcb와 그외에 많은 개념들을 알아보겠습니다.

 

 

 


 

 

프로세스의 구조

프로세스의 구조

 

프로세스의 구조는 코드영역, 데이터영역, 스택영역, 힙영역으로 나뉩니다. 

 

코드는 자신을 실행하는 코드입니다. 프로그램은 코드로 작성되었으니, 당연하게도 프로그램이 실행될 때 코드가 실행되어야 겠죠. 

데이터는 코드가 실행되면서 사용하는 전역 변수& 상수 변수나 파일, 각종 데이터를 모아놓은 곳입니다. 데이터는 변하는 깞이기에 기본적으로 데이터 내용은 상수는 읽기 전용만, 나머니 변수는 읽기 쓰기가 가능합니다.

스택 영역은 운영체제가 프로세스를 실행키 위해 부수적으로 필요한 데이터를 모아놓은 곳입니다. 지역변수나 함수가 호출되었을 때 필요한 정보나 위치를 이 영역에 저장하며, 스택 영역은 운영체제가 사용자를 위해 유지하는 영역이므로 사용자에게는 보이지 않습니다.

힙 영역은 프로그래머가 동적으로 메모리를 할당하는데 쓰입니다. malloc, free heap등의 영역에 자원을 할당/ 해제할 수 있습니다.

 

 


 

코드가 메모리에 올라가서 프로세스가 되는 과정

 

index.c라는 프로그램이 있다고 칩시다. 이 프로그램을 더블 클릭하면, 메모리에 올라가서 프로세스로 변환되겠죠? 이 과정을 알아봅시다.

 

test.c의 정의한 숫자를 치환하고 필요한 파일을 불러온다 -> 전처리기를 거치면 파일 확장자는 index.i가 된다 -> 고수준인 c언어를 저수준인 어셈블리어로 바꿔준다. -> 어셈블리어 기계어와 1대1 매칭을 한다 -> 어세믈러가 어셈블리어를 기계어로 바꿔준다. 이때, 기계어는 0과 1로만 이루어져있다. -> index.o -> 링커가 링킹을 한다(라이브러리, 다른 소스 코드 연결) -> index.exe가 된다 -> 파일 더블클릭한다 -> exe가 메모리에 올라가게 되면서 index는 프로세스가 되며 운영체제로부터 관리받는다. 

 

 


 

PCB 프로세스 제어 블록

 

위에서 언급했던대로, pcb는 프로세스가 만들어지면 생기는 것입니다. 이를 이해하기 위해선 먼저 pcb의 구조가 어떻게 생겼는 지 아는 것이 중요합니다.

pcb의 구조

 

pcb의 첫 번째 블록엔 포인터가 저장됩니다. 부모와 자식 프로세스에 대한 포인터, 할당된 자원에 대한 포인터 등이 있습니다. 프로세스의 한 상태에서 다른 상태로 전환될 때 저장하는 포인터를 가지고 있습니다* 프로세스의 상태(예: 준비 -> 실행 단계로 갈 때)

 

프로세스의 상태는 생성, 준비, 실행, 대기, 완료로 현재 프로세스의 상태를 나타냅니다. 

프로세스 구분자id는 프로세스를 식별하기 위한 숫자id가 저장됩니다.

프로그램 카운터는 다음에 실행될 명령어의 주소를 포함하는 프로그램 카운터 값을 저장합니다. 후에 알아보겠지만, 오늘날의 os는 시분할 처리로 짧은 시간동안 프로세스를 cpu가 번갈아 실행하기 때문에 cpu를 뺏겼다가 다시 실행될 때, 원래 실행하던 명령어를 내보내야 하기 때문에 프로그램 카운터가 필수입니다.

프로세스는 우선순위가 다릅니다. 예를들어 창을 두개 키고 하나는 참고 용도, 하나는 글쓰기를 하고 있다면 글쓰기 하는 창이 더 중요도가 높겠죠. 다양한 우선순위의 프로세스가 '대기 상태'에 있을 때 중요도 별로 따로 운영되거나 더 자주 실행되기 때문에 우선순위의 정보도 가지고 있어야 합니다.

레지스터 정보는 프로세스가 실행될 때 사용했던 레지스터 값이 저장됩니다. 이전에 사용하던 값을 보관해야 또 실행할 수 있기 때문입니다.

메모리 관리 정보는 프로세스가 메모리 어디에  있는 지에 대한 위치정보, 메모리 보호를 위한 경계 레지스터 값, 한계 레지스터 값이 저장됩니다.

할당된 자원 정보는 프로세스를 실행키 위한 입출력 자원, 파일에 대한 정보를 말합니다.

게정 정보엔 cpu스테줄링에 필요한 우선순위, 최종실행시간, cpu점유 시간 등이 저장됩니다.

PPID는 parent process, CPID는 child process를 지칭합니다. 이 부모프로세스와 자식프로세스의 정보도 pcb구조 안에 저장됩니다.

 

 


 

 

프로세스의 상태 

프로세스의 상태

 

Pcb안에는 프로세스 상태에 대한 정보가 들어가 있다고 했습니다. 프로세스의 상태에는 생성, 준비, 대기, 실행, 완료 상태가 있습니다.

요즘날의 컴퓨터는 cpu를 통해 시분할 처리를 합니다. cpu는 한 번에 하나의 프로세스 밖에 처리를 못하기 때문입니다. 하지만 처리 속도가 매우 빠르기 때문에 우리는 프로세스가 동시에 실행된다고 느낍니다. 프로세스들은 실행되면서 cpu를 기다리며 다섯가지의 상태를 지니게 됩니다.  

 

생성상태는 프로세스가 실행되며 메모리에 올라갔을 때를 의미합니다.

준비상태는 cpu를 사용키위해 기다리고 있는 상태입니다. cpu에는 우선순위가 있다고 하였죠. 실행 시킨 순번이나 중요도 순에 따라 cpu  스케줄러에 의해 cpu가 할당됩니다.

실행상태는 준비상태에 있는 프로세스가 cpu스케줄러에 의해 cpu를 할당받아 실행되는 상태입니다. cpu 부여시간이 끝나면 할당된 프로세스를 강제로 빼앗기게 되고 다시 준비단계로 돌아갑니다.

대기상태는 프로세스가 입출력 요청이 있을 때 입출력 되기 까지 기다리는 상태를 의미합니다. 입출력 시에는 다른 프로세스에게 cpu를 할당하고 입출력이 완료되면 대기 상태에 있는 프로세스에게 cpu할당을 합니다.

완료상태는 프로세스가 종료된 상태입니다. 이 때 사용한 데이터를 메모리에서 제거하며, pcb도 폐기 됩니다. 

 

과정이 많다고 느껴질 수 있지만, 정말 간단합니다. 카카오톡이라는 프로그램을 열었을 때, 카카오톡은 컴퓨터 메모리에 적재되며 프로세스가 되고, cpu의 할당을 기다리다가 실행이 되며, 대화를 보낼 땐 키보드를 쳐야 하기 때문에 입출력을 기다리는 대기 상태가 되었다가 그 사이에는 다른 프로세스에게 cpu를 할당했다가, 입출력이 끝나면 다시 실행상태로 돌아갔다가 대화가 종료되고 카카오톡이라는 프로세스를 끄면 pcb도 제거되는 것을 상상하면 됩니다. 데이터도 메모리에서 제거된다고 하는데, 카카오톡의 대화는 저장되어있지 않나요? 라는 질문엔 카카오톡 대화는 카카오톡의 클라우드에서 저장되고 있는 것이고, 이 데이터는 카카오톡에서 채팅을 치다가 깜빡하고 카카오톡을 제거했을 때, 채팅이 사라지는 것을 생각하면 될 것입니다. 

 

 


Context Switching 문맥교환

 

문맥 교환은 Cpu를 차지하던 프로세스가 나가면서 실행중인 프로세스의 상태를 저장하고 새로운 프로세스의 상태값으로 교체하는 작업을 말합니다. 이전 작업 상태가 되어야 다음 작업을 할 수 있기 때문입니다. 

 

문맥 교환이 일어나는 이유는 cpu점유 시간이 다 되었거나, 입출력i/o요청이 있거나 인터럽트가 있을 때 발생합니다.

문맥 교환이 일어나는 가장 많은 이유는 프로세스 끼리의 교환이 있을 때일 것입니다. a라는 프로세스가 실행 단계에서 cpu를 점유하다가, 준비 상태로 변환하게 되면서 b프로세스가 실행 단계로 오게 됩니다. 이를 문맥교환 context switching이라고 부릅니다. 문맥교환이 일어날 때는 프로세스가 변경된다는 의미니 각 프로세스가 지니던 pcb의 내용도 변경이 됩니다. pcb는 이 과정에서 실행중인 프로세스 작업내용을 저장합니다. 프로그램  카운터, 레지스터 값들도 다 같이 변경됩니다. 

 

메모리에 있는 모든 프로세스들은 이 문맥교환을 끝날 때 까지 모두 반복합니다. 

 


 

프로세스 생성과 종료 

 

[cmd(ctrl) + N] 을 눌러보세요. 지금 보고 있는 창을 크롬으로 보고 있다면 크롬 페이지 하나가 또  복사될 것입니다. 이를 프로세스 복사 함수 --fork()함수--라고 부르는데, 원래 실행하던 프로세스를 부모 프로세스, 새로 생긴 프로세스는 자식 프로세스로서 부모-자식 관계를 이룹니다. 

 

fork() 시스템 호출의 동작 과정

 

fork()시스템 호출을 하게 되면 pcb프로세스 제어 블록을 포함한 부모 프로세스의 대부분의 영역이 자식 프로세스에 복사됩니다. 단, 몇가지가 바뀝니다. 먼저 프로세스 구분자PID가 바뀝니다. 복사된 프로세스라도 독자적인 프로세스이기에 당연히 ID가 바뀌겠지요. 마찬가지로 독자적인 프로세스기에 기존 부모 프로세스와 다른 메모리 위치를 가집니다. 그러므로 메모리 관련 정보도 바뀝니다. PPID와 CPID가 바뀝니다. 기존 부모프로세스가 가지던 PID가 자식프로세스의 PPID로 생성되며 부모 프로세스의 CPID도 자식 프로세스의 PID로 바뀝니다. 서로를 알아보기 위한 장치라고 할 수 있습니다. 

 

fork()시스템 호출의 몇 가지 장점을 알아보겠습니다. 먼저 프로세스의 생성 속도가 빨라집니다. 더블클릭해서 프로그램을 가져오는 것보다 기존 메모리에서의 복사가 생성 속도에 더 이득을 가져다 줍니다. 또, 추가 작업 없이 자원을 상속할 수 있습니다. 이미 부모 프로세스가 초기화한 작업을 자식 프로세스는 바로 사용할 수 있습니다. 마지막으로 시스템 관리를 효율적으로 할 수 있습니다. 부모 프로세스와 자식 프로세스 구분자가 연결되어 있기 때문에 자식이 사용하던 자원을 부모가 정리할 수 있어 시스템이 효율적으로 관리됩니다.

 

exex()시스템 호출

 

이렇게 fork()함수로 코드, 데이터 등을 모두 복사해서 쓰는 방법은 매우 간편합니다. 똑같이 간편하고 싶은데, 코드는 바꾸고 싶을 경우엔 어떻게 해야할까요? exec()함수를 쓰면됩니다! 기존의 코드영역을 새 코드로, 데이터 영역도 새로운 변수로 채워지면서 새 데이터로, 스택 영역은 리셋됩니다. 하지만 프로세스 구분자나 부모 프로세스 구분자, 자식 프로세스 구분자, 메모리 등은 그대로입니다. 이는 프로세스의 구조를 재활용하는 것인데요, 자원을 재활용하면 사용이나 회수가 간편하고 빠르게 됩니다.

 

운영체제의 구조는 이처럼 부모-자식 관계를 잘 이용합니다. exit()함수를 사용한다면, 자식프로세스는 부모 프로세스에게 종료를 알립니다. 부모는 자식을 먼저 단속해서 끄게하는데요, 자식이 너무 많은 나머지 부모가 자식을 보지 못하고 먼저 종료된다면, 자식은 그대로 프로세스에 남게 됩니다. 프로세스는 꺼졌는데 말이죠. 그러면 프로세스는 제대로 꺼진 게 아니겠죠? 이때 남아있는 프로세스를 좀비 프로세스 혹은 고아 프로세스라고 칭합니다. 좀비 프로세스가 많아지면 자원이 낭비되고, 메모리에 침투하여 쾌적한 메모리 공간을 휘젓기 때문에 컴퓨터가 느려집니다. 프로세스를 여러 개 종료했는데 관리자 화면엔 프로세스가 남아있거나 컴퓨터가 버벅인다는 느낌이 든다면, 좀비 프로세스가 컴퓨터 안에 남아있는 것입니다. 엄청 느려진 컴퓨터를 복구하기 위해 우리는 컴퓨터 전원을 껐다 키죠. 그러면 정말 말끔하게 빨라집니다. 부팅을 하면 메모리에 있는 좀비프로세스가 다 사라지기 때문입니다. 

 

우리가 여기서 알면 되는 것은 그저 '운영체제는 프로세스의 복사, 상속, 계층 구조를 가진다, 이런 상속 관꼐는 자원의 사용을 용이하게 하고 회수도 빠르게 할 수 있어서 효율적이다' 입니다.

 

 

 


쓰레드

 

운영체제가 작업을 처리하는 단위는 '프로세스'입니다. 운영체제에게 작업을 요구할 때마다 프로세스가 생성되고, pcb도 함께 생성됩니다. 그러면 메모리에 코드, 데이터, 스택, 힙 영역이 적재되며 프로세스가 생성될 때마다 메모리에 가중됩니다. 독립적인 프로세스는 데이터를 주고 받을 때 프로세스 간 통신IPC을 이용하는데 이 통신 비용도 많이 들기도 합니다. 이런 문제가 있기 때문에 쓰레드가 고안됩니다.

 

쓰레드의 정의는 '프로세스의 코드에 정의된 절차에 따라 CPU에 작업 요청을 하는 실행단위이다.'인데, 그냥 쓰레드는 프로세스  내에 존재하며 그 갯수는 여러 개이다. 라고만 생각하셔도 됩니다. 쓰레드는 프로세스 내에서 pcb, 코드, 데이터, 힙 영역을 공유합니다. 하지만 스택을 공유하진 않습니다. 예를 들어 마이크로소프트의 웹을 상상해보겠습니다. 마이크로소프트의 웹브라우저를 누르면 메모리에 적재되며 프로세스가 됩니다. 이 때, 프로세스가 생성되면서 쓰레드도 생성됩니다. 탭을 추가합니다. 그러면 쓰레드가 또 생성됩니다. n개 까지 탭을 늘리면 쓰레드도 n개 까지 늘어납니다. 웹 브라우저의 pcb, 코드, 데이터, 힙 영역은 공유하기 때문에 탭을 추가하게 된다면 우리가 아는 그 화면이 나옵니다. 하지만 스택은 저장되지 않기 때문에 내가 보던 화면은 그대로 나타나진 않습니다. 이렇게 탭 추가하여 쓰레드를 이용한다면 프로세스의 코드, 데이터, 힙이 공유가 되니 메모리 절약이 정말 많이 됩니다! 

 

프로세스와 쓰레드의 장단점

 

이렇게 보면 쓰레드가 간편하고 좋은 것 같지만 또 각자 사용 시 장단점을 가집니다. 

안정성 문제에서는 프로세스는 서로 독립적이기에 하나의 프로세스가 문제가 있더라도 다른 프로세스는 거의 영향을 받지 않습니다. 반면 쓰레드는 하나의 프로세스 내에 여러개가 존재하기 떼문에 하나의 쓰레드 문제가 생기면 감염되는 것 처럼 다 문제가 일어나게 됩니다. 웹 브라우저 탭을 여러 개 켜놨다가 하나의 탭이 에러가 생겨도 그 탭이 모두 영향을 받는 것 처럼요. 안정성에서는 프로세스가 쓰레드보다 낫습니다.

각각의 프로세스는 데이터 공유가 어렵지만 쓰레드는 스택 제외하고는 모두 공유할 수 있기에 오버헤드도 적습니다. 자원 공유, 그로 인한 효율성 향상이 쓰레드의 최대 장점입니다.  쓰레드가 빠르고 용이하게 메모리에 접근해도 데이터가 공유되면서 문제가 생길 수 있는 점은 첫번 째 단점과 이어지는 부분입니다.

 

 

 

컴퓨터 하드웨어의 기본 구성

 

  코딩에 관심을 가지게 된 계기가 무엇인가요? 한다면, 저는 주저 않고 '컴퓨터가 좋아서'라고 대답하곤 합니다. 구체적으로는 하드웨어를 조립해서 작동하는게 신기하고 재밌어서 흥미를 가지게 되었습니다. 컴퓨터를 이루는 하드웨어는 생각보다 별로 거창하지 않습니다. Cpu, 메모리, 저장장치(ssd), 입출력장치(키보드, 마우스) 등만 있으면 되죠. 여기에 본인이 원하는 운영체제를 사서 깔기만 하면 됩니다. 컴퓨터로 주로 무엇을 하느냐에 따라 어떤 cpu, 메모리, 그래픽카드 등을 쓸 지를 정하기만 하면 됩니다. 그러면 더 자세하게 컴퓨터 구조는 어떻게 되어있는 지, cpu와 메모리는 어떤 일을 하고 얼마나 중요한 지 알아보겠습니다. 

 

 


 

컴퓨터 하드웨의 구조

 

   컴퓨터 부품은 '메인보드'(혹은 마더보드라고도 불리움) 판에서 연결됩니다. CPU단자, RAM(메모리)단자, 그래픽 카드 연결 단자, 하드디스크 연결 단자, 사운드 단자, 마우스 키보드 단자 등등이 다 메인보드에 장착되어 장치를 연결하고 데이터를 지나갈 수 있게끔 합니다. 이를 폰노이만 구조라고 하는데요, 이는 폰노이만이 프로그래밍을 할 때 하드웨어를 바꿀 필요 없이, 메모리 위에만 프로그램을 올려서 프로그래밍이 가능하도록 하는 방식을 제안했기 때문입니다. 지금도 널리 쓰이고 있는 이 방식은 모든 프로그램은 메모리에 올라와야만 실행이 가능합니다. 티스토리를 켜놓고 카톡창을 켜놓고 멜론을 켜놨을 때 모두 메모리 위로 올라와야만 실행가능하며 하드웨어 구조에는 메모리가 매우 중요함을 의미합니다.

 

 


 

CPU central processing unit

 

   CPU는 중앙처리장치라고 불리우며, 컴퓨터의 뇌입니다. CPU의 구성 요소에는 산술논리 연산장치, 제어장치, 레지스터가 있습니다. 

 산술논리 연산장치는 데이터의 산술, 논리연산을 담당합니다. 

 제어장치는 모든 장치들의 동작을 지시하고 제어하는 장치입니다. 

 레지스터는 CPU내에서 계산을 위해 데이터를 임시로 보관하는 장치입니다. 

 


 

CPU 명령어 처리 과정

 

   2+3를 더하라는 프로그램이 있다면 이 프로그램을 CPU가 어떻게 처리하는 지 과정을 살펴보겠습니다.

 

1. 제어장치가 먼저 덧셈 프로그램의 명령어를 해석하고 제어 신호를 보낸다 -> 2. 메모리에서 2와 3의 데이터를 가져와 레지스터에 둔다. (프로그램은 항상 메모리 위에서 수행되어야 하니까, 메모리에 프로그램의 데이터가 있습니다!!) -> 3. 가져온 데이터의 덧셈을 산술논리 연산장치에서 실행한다. -> 4. 덧셈의 결과값을 레지스터에 넣는다 -> 5. 레지스터에 있는 결과값 5를 메모리에 옮긴다. 

 

  제어장치는 계속 제어 신호를 보내며 명령을 하고, 레지스터에는 데이터를 계속 저장하고, 산술논리 연산장치에서는 데이터 값을 계산합니다. 

 


 

메모리 

 

   모든 프로그램은 메모리에 올라와야 실행될 수 있습니다. 이 부분이 메모리 파트에서 제일 중요한 개념입니다. 메모리의 종류에는 읽거나 쓸 수 있는 램, 읽기만 가능한 롬으로 구분 됩니다. 램은 휘발성 메모리와 비휘발성 메모리로 나누어집니다. 

 

램 Random Access Memory 

 

   사용자가 자유롭게 데이터를 읽고 쓰고 지울 수 있는 기억장치이며 CPU의 모든 동작이 이 주기억장치에 저장됩니다. 램이 많으면 많을 수록 많은 일을 빠르게 처리할 수 있기 때문에 '램다익선', 램은 많으면 많을 수록 좋다 라는 말도 있을 정도 입니다. 

 

휘발성 램 & 비휘발성 램

 

   휘발성 램은 컴퓨터 전원이 꺼지자 마자 '기억'들이 사라집니다. 예를들어 우리가 문서작업을 할 때 컴퓨터를 끄게 되는 불의의 사고를 겪게 되면, 다시 켰을 때 작업들이 깜쪽같이 사라진 경험이 꽤나 있을겁니다. 휘발성 램에서 작업이 실행되었기 때문에 전원이 꺼지면 정보도 날라가게 되는 것입니다.(cmd/ctrl+s의 중요성..)

 비휘발성 램은 말 그대로 휘발되지 않는 '기억'들입니다. 문서를 작업하다가 저장을 누르고 파일로 만들게 되면 컴퓨터를 껐다 켜도 그대로 존재하는 것이 이 비휘발성 램입니다.

 

롬 Read Only Memory 

 

   롬은 전력이 끊겨도 데이터를 보관하지만 데이터를 한 번 저장하면 수정이 불가하기 때문에 컴퓨터 부팅과 관련한 바이오스를 저장하는데 주로 쓰입니다. 

 

 


메모리 보호

 

   메모리는 매우 중요합니다. 메모리가 보호되지 않는다면 악성 소프트웨어, 바이러스로 프로그램을 파괴당하거나 데이터가 지워지는 불상사가 일어날 수 있습니다. 운영체제의 작업이 중단된 경우에 메모리를 보호하기 위해선 하드웨어의 역할이 중요합니다. 메모리를 보호하기 위해 cpu는 메모리 작업시 경계 레지스터와 한계레지스터를 정하여 메모리 주소에 넣고 그 값이 주소에서 벗어난다면 메모리 오류에 관련한 인터럽트가 발생합니다. 인터럽트가 발생하면 모든 작업이 중단되고 강제종료되는 상황이 일어나게 됩니다. 

 

부팅 과정

 

   컴퓨터를 껐다가 키면 어떤 과정이 일어날까요? 운영체제도 응용 프로그램이기 때문에 메모리에 올려져서 실행되어야 합니다. 운영체제를 메모리에 올리는 과정을 부팅이라고 합니다. 

 

   컴퓨터 전원을 키면 롬에 저장된 바이오스가 실행됩니다. 하드웨어에 이상이 없는 지 체크하고 이상이있다면 삐~소리와 오류메세지를 출력합니다. 이상이 없다면 하드디스크의 마스터 부트 레코드에 저장된 운영체제를 실행하기 위한 코드인 부트스트랩을 메모리로 가져와 실행합니다. 

 

인터럽트

 

   초기 컴퓨터 시스템에서는 cpu가 직접 입출력 장치에서 데이터를 가져오거나 내보냈습니다. 이를 폴링 방식이라 합니다. 언제쯤 입출력 명령이 완료될 지 모르기 때문에 cpu는 이를 주기적으로 관리했는데 이는 매우 비효율적이 였기에 인터럽트 방식이 도입됩니다. 인터럽트는 입출력 관리자가 cpu에 보내는 완료신호입니다. cpu가 입출력 관리자에게  입출력을 맡기고 다른 일을 하며 일이 완료 됐을 땐 cpu신호를 받아 인터럽트 서비스 루틴을 실행시켜 작업을 완료하기에 효율적입니다. 

 

메모리 매핑 입출력 Memory Mapped I/O MMIO

 

메모리 매핑 입출력 작업 영역

 

   CPU는 입출력 관리자에게 인터럽트 방식을 사용하여 입출력을 전담으로 맡겼습니다. 그 과정에서 메모리에 접근할 수 있는 권한이 필요하여 직접 접근을 허용합니다. 하지만 직접 접근을 사용하면 메모리가 복잡해지며 데이터도 섞일 우려가 있기 때문에 이를 보완하는 방법이 도입되었습니다. 바로 CPU가 사용하는 메모리 공간과 직접 메모리 접근의 공간을 분리하는 것입니다. 이를 메모리 매핑 입출력 MMIO라고 부릅니다. 

 

 

 


 

버퍼란?

 

   버퍼는 속도의 차이가 나는 두가지 장치에서 일정량의 데이터를 모아 옮김으로써 속도의 차이를 완화하는 장치입니다. 하드웨어 뿐만 아니라 동영상을 볼 때 '버퍼링 걸린다'라는 말을 많이 들어보셨을 텐데요, 네티워크 데이터의 시간과 플레이어 재생 시간의 속도차이가 일어났을 때 주로 이 말을 쓰입니다. 이 현상을 방지하기 위해 동영상 데이터 일정 부분을 버퍼에 넣은 후 실행하기도 합니다.

 


 

스풀?

 

   스풀은 cPU와 입출력장치가 독립적으로 동작하도록 고안된 소프트웨어적 버퍼입니다. 주로 프린터기가 대표적인 스풀러인데요, 순차적으로 내용물을 인쇄하지만 출력 명령을 내린 프로그램과는 독립적으로 동작한다는 특징이 있습니다. 

 

 


 

캐시란?

 

   캐시는 메모리와 CPU간의 속도 차이를 완화하기 위해 메모리의 데이터를 미리 저장해두는 임시 장소입니다. 미리 필요한 데이터를 모아 한꺼번에 전달하는 버퍼의 일종이며, cpu의 속도를 쫓기 위해 메모리는 앞으로 사용할 것으로 예상되는 데이터를 미리 가져다 놓습니다. 컴퓨터의 성능 향상을 위해서는 캐시 적중률이 높아야하는데, 적중률을 높이기 위해서는 캐시의 크기가 늘려져야 합니다. 크기가 커질 수록 더 많은 데이터를 미리 가져올 수 있으며 많은 데이터는 캐시 적중률을 올리기 때문이죠. 

 

 

운영체제란? 

 

    운영체제는 다른 말로 OS라고 부릅니다. OS 어디서 많이 들어봤는데? 싶을텐데 아이폰을 쓰는 사람은 'ios'가 매우 익숙할 거에요. 컴퓨터에 쓰이는 유명한 운영체제는 Mac OS, Window 두 개가 있습니다. 제일 대중적인 만큼 저또한 현재 window 11, mac monterey를 쓰고 있습니다. 그외의 운영체제는 유닉스, 리눅스 등이 있고 두 개의 운영체제 또한 정말 많이 쓰입니다. 특히 리눅스는 안드로이드의 기반으로 유명합니다.

    운영체제의 정의가 그래서 무어냐 물으신다면 하드웨어를 관리하고 사용자에게 편리함을 주는 컴퓨터 관리자, 수호자 쯤 됩니다. 우리가 컴퓨터를 킬 때마다 검은 배경화면에 쓰여진 곳에 명령어를 외워 쓰는 것보단 아이콘으로 대체된 편리한 인터페이스를 누르며 쓰는게 편할 겁니다. 또, 컴퓨터 자원, 하드웨어hardware는 너무 비싸죠.  컴퓨터의 발전의 역사는 'CPU를 어떻게 하면 잘쓸까? 어떻게 100프로 활용을 할 수 있을까?' 에서부터 시작되었는데, 컴퓨터 자원을 놀게 할 수도 없고 또 너무 막 대할 순 없기 때문에, 운영체제는 사용환경을 쾌적하게 만들고 비싼 컴퓨터 자원들을 효율적으로 관리하기 위해서 존재합니다.

   그럼 어떻게 운영체제가 만들어지고 발전했는 지 살펴보고, 운영 체제의 구성 존재이유에 대해 알아보겠습니다.

 

 

 


(참고) 운영체제는 그럼 하드웨어일까? 소프트웨어일까?

  운영체제 자체는 소프트웨어의 종류입니다. 운영체제의 목표이자  역할은 하드웨어를 조정하고 관리하기 위함입니다. 소프트웨어가 하드웨어를 조종하는 것이 애매한 개념이기 대문에 소프트웨어와 하드웨의 결합형태인 펌웨어firmware라고도 부릅니다.


 

운영체제의 역사

  컴퓨터는 어디서, 언제부터 빠르게 발전된 것일까요? 수학과 과학이 발전하면서 컴퓨터의 존재는 필연적이였을 지 모릅니다. 하지만 확연한 성장은 '전쟁'을 통해서입니다. 전쟁에서의 전략과 싸움은 '수를 계산'하는 것부터 시작되었고, 컴퓨터의 기본 알고리즘 개념의 창시자, 컴퓨터의 조상이라고 불리우는 '앨린튜링'의 튜링머신 또한 2차 세계대전 독일의 비밀 암호를 풀기 위해 만들어졌습니다. 또, 역대급 천재라고 불리우고 컴퓨터의 내부구조를 확립했다고 알려진 폰 노이만도 전쟁을 위해서 수학 이론을 발전 시키죠.

  최초의 컴퓨터인 애니악도 비사일 탄도를 계산하기 위해 존 모클리& 존 에클리가 만들었습니다. 사진을 보면 알 수 있듯이 컴퓨터라 치기엔 너무 큽니다. 애니악은 진공관이라는 소자를 사용해 전선으로 논리회로를 구성해  결과값을 얻었는데, 기존 수학을 푸는 것보다얀 빨랐지만 다른 수식을 사용하기 위해선 전선을 다시 연결하고 구성했었습니다. 너무 번거로웠고 금액, 노동력이 많이 들었기에 더 편리한 방식으로 진화합니다.

 

애니악 컴퓨터의 모습 (출처: 쉽게 배우는 운영체제)

 

  그 다음으로는 IC칩으로 이루어진 컴퓨터가 등장하게 됩니다. 천공카드 리더와 라인프린터로 이루어진 이 컴퓨터는 다른 작업을 하기 위해서 전선을 뽑아 새로 연결했던 애니악과는 다르게 천공카드 리더만 바꾸면 됐습니다. 하지만 작업은 한 개 이상 할 수 없었고 이를 일괄 작업 시스템이라고 부릅니다.

 

 이후에 키보드와 모니터가 발명됩니다. 키보드의 탄생으로 사용자는 컴퓨터에 입력을 할 수 있게 되었고, 컴퓨터는 사용자에게 모니터를 통해 출력을 내보낼 수 있게 됐습니다. 이를 대화형 시스템이라고 하며, 이 때부터 컴퓨터 발전이 급속화 됩니다.

 

 운영체제는 하드웨어를 관리한다고 했죠. 우리가 지금 쓰는 컴퓨터는 사실 동시에 실행되고 있지 않습니다. 동시에 실행되는 것처럼 보이는 거죠. 운영체제는 어떻게 하면 동시에 작업할 수 있을 지 고민했습니다. 수많은 알고리즘을 수행시키면서 발전된 형태가 시분할 시스템입니다. 운영체제는 모든 프로그램에 cpu 사용시간을 잘게 쪼개서 주었고, 이를 매우 빠른 속도로 반복합니다. 잘게 쪼갠 시간을 타임 슬라이스 혹은 타임 퀀텀이라 부릅니다.

 

 이 곳에 다 쓰진 않았지만 컴퓨터는 보완과 실패, 반복이 계속 되면서 현재의 컴퓨터로 발전될 수 있었습니다. 좀 더 나은 환경을 위한 노력의 역사가 지금 인공지능까지 이어졌으니 시도와 반복이 얼마나 중요한 지 여기서도 알 수 있습니다.

 

 


(참고) GUI란?

   Graphic user interface의 약자로, 마우스를 이용해서 누를 수 있는 아이콘이 그 예입니다. 옛날에는 검은 화면에 명령어를 치는 식으로 컴퓨터를 사용했는데, 애플의 스티브 잡스가 맥킨토시 이전에 만든 리사 컴퓨터에서 파일 모양이나 쓰레기통 모양의 GUI를 처음 도입합니다. 대박이난 맥킨토시에는 GUI를 포함한 이런저런 기능을 넣어 애플은 사용자들이 컴퓨터를 더 친화적으로 느끼게 되는 계기와  혁신을 만들어냅니다. 

 

 

 


운영체제의 구성

 

  운영체제는 제어 프로그램 처리 프로그램으로 구성됩니다. 제어프로그램에는 운영체제의 핵심 기능을 모아 놓은 커널이 있는 것이며, 처리 프로그램은 커널에 명령을 전달하고 실행 결과를 사용자와 응용 프로그램에 돌려주는 인터페이스입니다.

  제어 프로그램인 커널Kernel에서는 CPU관리, 프로세스 관리, 메모리 관리, 저장장치 관리 등 운영체제의 핵심적인 기능이 모아져 있습니다. 고로 커널은 매우 매우 매우 중요합니다. 

 

커널 내부도 시스템 콜 드라이버 두가지로 나뉩니다. 

 

시스템 콜은 운영체제가 사용자와 응용 프로그램이 하드웨어에 직접 접근을 못하게 막으면서 컴퓨터 자원을 보호하며 하드웨어의 자원은 이용할 수 있도록 인터페이스를 제공하는 것입니다. 사용자가 직접 접근해서 관리한다면 쉽게 망가질 수 있고, 프로그램이 직접 접근 한다면 데이터가 지워질 수도 있는 불상사가 생길 수도 있습니다. 그래서 운영체제는 시스템콜을 통해 응용 프로그램은 write()함수로 커널 내부에서 저장되고, read()함수로 불러와지며 GUIgraphic user interface를 통해 사용자를 직접적으로 접근하는 것을 막고 컴퓨터 자원을 관리합니다.

 

 응용프로그램과 커널을 관리했다면 이제 하드웨어의 인터페이스도 관리해야겠죠? 이는 드라이버가 관리합니다. 하드웨어는 회사나 제조사가 다양하기 때문에 운영체제는 드라이버에 맞게 모든 환경을 만들어낼 수 없습니다. 목마른 놈이 우물판다고, 드라이버를 만든 회사에서 드라이버 내에 관련 소프트웨어를 설치합니다. 그래픽카드를 새로 사서 컴퓨터에 조립하면 그래픽카드 드라이버를 설치하는 이유가 이 때문입니다. 

*쉬운 예로 마우스와 키보드도 드라이버입니다. 컴퓨터 뒤쪽에 usb를 꼽아서 키보드 마우스를 사용하지요! 우리가 마우스를 사용할 때 마다 따라 오는 화살표! 이는 사용자가 보기 쉽게끔 나타낸 GUI입니다. 커널은 마우스를 인식하고 사용자에게 화살표를 보여주는, 사용자와 하드웨어의 중간 다리 역할이라 보시면 됩니다. 

 

 

사용자와 운영체제와 컴퓨터자원(하드웨어) 순서 한눈에 보기

 

 

 


운영체제의 역할과 목표

 

운영체제제의 역할

 

  여기까지 보면 알겠지만 운영체제의 가장 중요한 역할은 바로바로 컴퓨터의 자원을 관리하고 보호하는 것입니다. 또 운영체제의 구성에서 알 수 있듯이 하드웨어와 연결 지을 수 있을 인터페이스를 제공하고 사용자에게도 GUI를 통해 컴퓨터를 잘 쓰게끔 하는 역할도 있습니다. 

 

운영체제의 목표

 

  운영체제의 가장 큰 목표는 역할과 연결되어 이어집니다. 자원관리를 위해선 효율성, 보호를 위해선 안정성, 하드웨어 인터페이스 제공을 위해서는 다양한 하드웨어를 설치할 수 있어야 하니 확장성, 사용자 인터페이스를 위해선 편리성 총 4가지를 추구하게 되겠지요.

 

 

  컴퓨터의 발전은 cpu를 어떻게 하면 더 잘쓸까 & 사용자가 어떻게 하면 더 편리하게 쓸 수 있을까(a.k.a 어떻게 하면 매출을 더 올릴까..)로 부터 이어졌습니다. 그러니, 윈도우를 만든 마이크로소프트나 맥os의 애플이나 컴퓨터 자원이 효율적으로 유지되는 것과 사용자의 편리성의 극대화를 추구화하며 운영체제를 발전시켰습니다.

 

 

 

 

 

글 참조 <쉽게 배우는 운영체제, 조성호, 한빛 아카데미>

+ Recent posts