운영체제는 커널 모드에서 실행하는 소프트웨어다. 메모리, 디스크, I/O 장치 등 컴퓨터의 여러 하드웨어 자원들을 관리하고, 또한 추상화된 하드웨어를 제공하여 사용자모드에서 편리하게 사용할 수 있도록 한다.

프로세스

프로세스는 실행 중인 프로그램의 추상화이다. 레시피를 프로그램, 요리사를 CPU 에 비유하자면, 프로세스는 재료를 가져와 요리를 하는 과정으로 구성된 모든 것이다. 이때 CPU 가 한 가지 프로그램만 처리할 수 있게 한다면 전체적인 실행 속도는 현저히 떨어질 것이다. 멀티 코어를 통해 각 코어마다 병렬적으로 프로세스를 실행할 수 있지만, 싱글 코어의 경우에도 프로세스의 병행 처리를 해주어야 한다. 운영체제는 프로세스 추상화를 통해 하나의 CPU 를 다수의 가상 CPU 로 변환하는 멀티태스킹을 제공한다. 한 시점에 여러 프로세스가 실행되는 것이 아니라 프로세스들이 짧은 주기로 교체를 하며 동시에 실행되는 것처럼 보이는 것이기 때문에 병렬(parallel)이 아닌 병행(concurrent)이다.

하나의 프로세스 메모리는 텍스트, 데이터, 스택 세그먼트라는 세 개의 세그먼트로 주소공간을 나눈다. 텍스트 영역은 가장 낮은 주소(0000)에 할당되는데, 프로그램의 코드가 저장된다. 그 다음 주소에는 데이터 영역이 있는데, 데이터 영역과 힙 영역을 나누어 데이터 영역에는 전역변수/정적변수를 저장하고, 힙 영역에는 사용자가 할당한 동적 데이터 변수를 저장한다. 데이터 영역은 프로그램 시작때 할당되고, 힙 영역은 프로그램 런타임에 크기가 결정된다. 마지막으로 스택 영역은 가장 높은 주소(ffff)부터 낮은 주소로 자라는데, 함수 호출 시 생성되는 지역변수나 매개변수를 저장하는 공간이다. 함수 완료 시 변수는 사라지기 때문에, stack 자료구조의 LIFO(Last In First Out) 방법에 따라, 낮은 주소로 자라고 다시 높은 주소로 돌아가는 방법을 사용한다.

스레드

프로세스 내에서 주소 공간을 공유하며 다수의 제어흐름을 가질 수도 있는데, 이를 스레드라고 한다. 웹 서버를 구성하는 방법 중 디스패처 스레드는 클라이언트의 작업 요청을 읽고 스레드풀에 있는 작업 스레드를 선정해 요청을 전달할 수 있다. 작업 스레드는 캐시에 있는 정적 파일을 읽어오거나, 필요한 작업을 진행한다. 만약 스레드가 없다면 한 요청이 종료될 때까지 다른 요청은 블록될 것이다.

스레드는 프로세스보다 가볍기 때문에 생성과 제거가 쉽고 빠르며, 문맥교환 비용도 감소할 수 있다. 스택 영역을 제외한 프로세스의 주소공간 전체를 공유하기 때문에, 만약 어떤 스레드가 파일을 열면 다른 스레드도 해당 파일에 접근할 수 있다. 하지만 자원이 공유되므로 경쟁상태에 의한 일관적이지 않은 결과를 초래할 수 있다. 또한 프로세스가 교환될 때 현재 프로세스의 실행 상태를 저장하기 위한 프로세스 테이블이 있는데, 스레드도 스레드 테이블이 있다. 프로세스 테이블은 커널에 구현된 한편, 스레드 테이블은 커널 레벨 혹은 사용자 레벨(프로세스 내부)에 구현될 수 있다.

경쟁상태, Race Condition

경쟁상태는 프로세스가 공유된 자원을 읽거나 기록할 때 누가 언제 수행하느냐에 따라 최종 결과가 달라지는 상황을 말한다. 프로세스간 통신(InterProcess Communication, IPC)에서 생각해야할 점은, 어떻게 정보를 전달하는지 생각하는 것뿐만 아니라 한 자원에 두 프로세스가 접근할 때, 혹은 프로세스간 의존성이 있을 때 처리방법도 주의해야한다. 스레드 역시 정보를 전달하는 부분을 제외하고는(프로세스 내에 주소공간을 공유하므로) 다른 쟁점이 적용된다.

공유 메모리를 접근하는 프로그램 부분을 임계구역(critical section)이라고 하는데, 경쟁상태를 피하기 위해 두 프로세스가 동시에 임계구역에 존재하지 않도록, 즉 공유 자원에 한 프로세스만 접근하도록 상호배제(mutual exclusion)를 구현해야 한다. 추가적으로 임계구역 외부에 있는 프로세스가 다른 프로세스의 진입을 막아서도 안 되고, 임계구역에 진입하기 위해 무한히 기다리는 프로세스도 없어야 경쟁상태를 완전히 피할 수 있다.

상호배제를 구현하는 몇 가지 방법 중 잘못된 방법을 알아보자. 프로세스가 임계구역에 진입했을 때 인터럽트를 꺼서 다른 프로세스로 문맥교환을 하지 않게 할 수 있다. 하지만 프로세스가 직접 인터럽트를 조절했을 때 해당 CPU 에만 영향을 미치므로 여러 CPU 가 있을 경우 소용이 없다. 두 번째로 락 변수를 두어 임계구역에 진입하는 조건을 만들 수 있다. 하지만 락 변수에 대한 경쟁조건이 또 다시 생겨나며, 다른 방법이 필요하다. 락 변수를 동시에 변경하는 일이 없도록 두 프로세스가 while 문으로 락 변수를 확인하여 임계구역에 진입하는 바쁜 대기(busy waiting)을 사용할 수 있다. Dekker, Peterson 의 알고리즘이 바쁜 대기를 사용하지만 이는 대기 시간이 짧지 않을 경우 프로세스가 계속 while 문에 묶여있어야하므로 적절하지 않다. 또한 우선순위 역전 문제(priority inversion problem)가 발생할 수도 있다. 높은 우선순위 프로세스 H 와 낮은 우선순위 프로세스 L 이 있을 때, H 가 준비상태면 항상 H 가 수행된다고 하자. 만약 L 이 임계구역에 진입하고 H 가 준비상태에 놓이게 될 때, H 가 수행중이므로 L 은 스케줄 되지 않아 임계구역을 벗어나지 못하고, H 는 임계구역에 진입하지 못하므로 무한루프에 빠진다.

문제를 해결하기 위해 버퍼를 공유하는 생산자-소비자라는 두 프로세스를 생각할 수 있다. 생산자는 버퍼가 차있으면 잠들고, 소비자는 비어있으면 잠든다. 소비자가 아이템을 소비하고 버퍼에 빈 공간이 있을 때 생산자를 깨우고, 생산자는 아이템을 추가해 소비자를 깨운다. 하지만 아이템 개수를 표시하는 count 에 대해 경쟁이 발생해 둘 다 영원히 잠들 수 있으므로, 세마포어(또는 뮤텍스)를 통해 sleep 의 실행이 임계구역 안에 있도록 보장한다. 세마포어는 임계구역에 들어갈 수 있는 프로세스의 개수라고 생각할 수 있다. down 을 해서 진입을 시도할 때 0보다 큰지 검사하고, 0일경우 잠든다. 세마포어를 검사-변경-잠드는 동작은 원자적(atomic)으로 (운영체제가 잠시 모든 인터럽트를 끄고)시행되기 때문에 다른 프로세스가 세마포어에 접근할 수 없고, 상호배제가 이루어진다. 임계구역에 진입한 프로세스의 개수를 셀 필요가 없을 경우 뮤텍스를 통해 단순히 구현할 수 있다. 0이면 unlock 으로 임계구역 진입이 가능하고, 1이면 lock 으로 사용중임을 의미한다.

식사하는 철학자 문제

다섯명의 철학자가 원형으로 앉아있을때, 스파게티를 먹기 위해 양쪽의 두 포크를 집어야한다. 철학자는 왼쪽포크 또는 오른쪽포크를 하나씩 집으려고 할 것이다. 만약 모두 동시에 왼쪽포크를 집었을 때 아무도 오른쪽 포크를 잡을 수 없게 되고, 교착상태가 발생한다. 오른쪽 포크를 집을 수 없을 경우에는 왼쪽 포크를 내려놓고 잠시 기다리도록 할 수 있지만, 이 역시 모두 동시에 같은 과정을 수행할 경우 아무도 식사를 하지 못한다. 이렇게 프로그램이 무기한으로 실행하지만 진전은 없는 경우를 기아(starvation)라 부른다. 간단한 해결방안으로는 왼쪽을 내려놓고 잠시 기다리는 시간을 무작위로 설정할 수 있다. 실생활에서는 좋은 해결이지만, 교착상태가 일어날 확률이 0%는 아니다. 따라서 포크를 집을 때와 내려놓을 때를 mutex 로 보호한다. 하지만 한 번에 한명만 식사를 하게 되므로 성능상 버그가 된다. 따라서 철학자가 식사 중인지, 아닌지 파악하는 배열을 만들어 이웃이 식사중인 경우가 아닐 경우에만 식사를 할 수 있도록 수정한다.

스케줄링

운영체제의 스케줄러는 한 CPU 에서 프로세스 혹은 스레드의 동시성을 구현하기 위해 실행 순서를 결정한다. 프로세스간 문맥교환이 일어날 때 사용자모드에서 커널모드로 전환되고, 프로세스테이블에 프로세스의 상태를 저장한 후 새 프로세스를 선택해 메모리를 적재하여 실행한다. 프로세스에 적용되는 스케줄링 알고리즘은 스레드에도 적용된다. 다만 사용자 레벨 스레드의 경우 스케줄러가 프로세스를 선택하고, 그 내부에서 다시 스레드를 스케줄링한다. 커널 레벨 스레드는 스케줄러가 스레드를 직접 스케줄링한다.

다음 프로세스를 선정 때는 프로세스가 종료될 때 외에도, 현재 프로세스가 I/O 때문에 대기하거나 I/O 인터럽트가 발생한 경우 스케줄링이 필요하다. 만약 하드웨어 클록이 주기적인 인터럽트를 발생시킨다면 (k번째)인터럽트마다 스케줄링 결정이 이루어진다. 선점형 스케줄링은 일정 시간을 넘지 않는 동안만 프로세스를 실행하고 다른 프로세스를 선정한다. 비선점 스케줄링은 한 프로세스를 강제로 중단 시킬 수 없고, 클록 인터럽트에서도 스케줄링이 이루어지지 않는다.

스케줄링이 잘 이루어지는지 판단하기 위해 시간 당 작업 개수인 처리율, 프로세스가 도착한 시간부터 종료될 때까지 걸린 평균 시간인 반환 시간, 그리고 CPU 이용률로 확인할 수 있다. 주로 반환 시간을 기준으로 알고리즘의 성능을 판단한다. 가장 간단한 알고리즘으로 선입선출(FIFO, First In First Out 또는 FCFS, First Come First Served)가 있다. 하지만 앞에 오래걸리는 프로세스가 있다면 뒤에 있는 모든 프로세스를 대기 시켜 평균 반환시간이 길어진다. 입력 큐에 대기 중인 프로세스 중 실행 시간이 가장 짧은 작업을 선택하는 최단작업우선(SJF, Shortest Job First)를 사용할 수 있다. 하지만 모든 작업이 동시에 대기할 경우에만 최적인 알고리즘이며, 작업의 도착시간이 다를 경우 최단잔여시간우선 알고리즘을 사용한다. 최단잔여시간우선은 새로운 프로세스가 도착할 때마다 남은 실행 시간이 가장 짧은 작업을 선택하는 선점형 알고리즘이다. 짧은 작업만 수행하고 긴 작업을 수행하지 않으면 처리율은 높아지지만, 긴 작업이 실행되지 않는 기아현상이 발생해 반환 시간이 무한대가 된다.

배치 시스템이 아닌 대화식 사용자 환경에서는 한 프로세스가 CPU 를 독점하는 경우를 막기위해 선점이 필수적이다. 또한 미리 프로세스의 실행 시간을 알지 못하는 경우가 많다. 가장 널리 사용되는 라운드 로빈(RR, Round Robin)은 할당 시간이 정해지고, 프로세스가 할당시간을 초과하거나 그 이전에 대기/종료하면 교체가 일어나고, 아직 수행이 남은 프로세스는 작업 큐의 맨 뒤로 간다. 이 때 할당시간이 너무 작으면 빈번한 문맥교환에 따른 오버헤드가 생기고, 시간이 너무 크면 요청이 많을 때 대기가 오래걸리므로 적절한 시간 설정이 중요하다. 한편 프로세스에 우선순위를 설정하여 우선순위가 높은 프로세스가 다음에 수행하도록 하는 우선순위 스케줄링을 할 수 있다. 낮은 우선순위의 기아현상을 막기 위해, 클록 인터럽트마다 현재 실행 중인 프로세스의 우선순위를 낮추거나, 대기 중인 작업의 순위를 높일 수 있다. 라운드로빈과 우선순위를 함께 사용한 다단게 큐(Multilevel Queue)는 우선순위가 낮아질수록 할당시간이 길어지는 그룹을 두어, 프로세스가 한 그룹에서 할당시간을 채웠으면 낮은 우선순위의 그룹으로 내려가도록 한다. 이는 다른 짧은 작업이 CPU 를 차지하도록 하면서 긴 작업에서 일어나는 문맥교환 비용을 줄인다.

메모리 추상화, 가상 메모리

프로세스가 물리 메모리의 모든 주소를 접근할 수 있다면, 사용자는 운영체제를 파괴하고, 시스템을 중단시킬 수 있다. 또한 여러 프로그램을 실행할 때에 한 주소에 두 프로세스가 접근하는 것을 막을 방안이 없어 원하지 않은 결과를 초래한다. 따라서 여러 프로그램을 동시에 메모리에 적재하고 서로 간섭 없이 실행하기 위해서 주소공간(address space)이라는 메모리 추상화가 필요하다. 주소공간이란 프로세스가 메모리를 접근할 때 사용하는 주소들의 집합으로, 각 프로세스는 독립적인 주소공간을 갖는다. 주소공간을 구현하기 위한 간단한 방법으로는 base 와 limit 이라는 하드웨어 레지스터를 사용하는 것이다. base 에 프로그램이 적재된 메모리의 위치, limit 는 프로그램의 크기가 저장되어 CPU 는 프로세스가 메모리를 참조할 때마다 base 값을 더하고 limit 보다 큰지 판단한다.

하지만 실제로 물리 메모리의 용량이 모든 프로세스를 적재할만큼 충분하지 않다. 스와핑은 메모리에 적재되어있는 프로세스를 다시 디스크로 내려보내는 방법이다(이때 보내지는 디스크 영역이 스왑 영역이다). 프로세스가 디스크로 스왑아웃 되었다가 다시 메모리로 스왑인 될 경우 프로세스의 메모리 위치가 바뀌었을 수도 있기 때문에 재배치 과정이 필요하다. 프로세스가 메모리에 적재될 때 적절한 메모리 크기를 할당하는 것이 중요하다. 대부분 프로세스의 크기가 실행 중에 증가하므로 여분의 메모리를 할당한다. 스와핑을 하면 여러 개의 분리된 빈 공간이 만들어지고, 이를 합치기 위해 메모리 조각모음을 하기도 한다. 또한 메모리를 할당할 때 어떤 공간에 할당하는지도 결정이 필요하다. 가장 간단하고 좋은 방법은 최초적합(first fit)으로 메모리 리스트를 순서대로 검색하다가 빈 공간이 발견되면 그 공간을 할당하는 빠른 알고리즘이다. 다음적합(next fit)은 지난번 할당에서부터 검색을 하고, 최적적합(best fit)은 요청된 크기에 가장 근접하게 큰 공간을 할당한다. 최악적합(worst fit)은 가장 큰 빈 공간을 할당하는데, 최적적합이 작게 조각난 빈 공간을 많이 생성하는 문제를 해결하기 위한 알고리즘이다. 시뮬레이션 결과 최초적합이 가장 좋은 알고리즘으로 분석되었다.

하지만 응용 프로그램의 개수와 크기는 메모리보다 더 빠르게 증가하고 있고, 스와핑은 I/O 속도가 매우 느리기 때문에 이를 처리하기 적절하지 않아졌다. 따라서 가상메모리(virtual memory)라는 개념을 도입해 프로그램의 일부만 메모리에 적재되어도 실행이 가능하도록 했다. 프로그램이 참조하는 주소는 가상 주소인데, MMU(Memory Management Unit)에 의해 물리 주소로 매핑되어 메모리 버스에 실린다. 가상 주소공간의 단위가 페이지(page)이며, 물리 메모리에 대응되는 단위는 페이지 프레임(page frame)으로 둘은 같은 크기를 가진다. 프로세스가 매핑되지 않은 주소를 참조하면 MMU 가 페이지 테이블에서 present/absent 비트를 참조해 매핑되어 있지 않음을 파악하고 CPU 에게 트랩을 발생시키는 페이지 폴트(page fault)를 일으킨다. 운영체제는 요청된 페이지의 내용을 디스크에서 읽어와 기존 페이지 프레임 위치에 적재하는 교체 과정을 거친다. 한편 페이지의 크기가 커질 수록 페이지가 사용되지 않고 낭비되는 현상을 내부 단편화(internal fragmentation)이라고 한다. 한편 페이지 크기가 작을 경우에도 페이지의 개수가 많아지므로 디스크 접근 시간이 늘어난다.

페이지 교체 알고리즘

페이지 폴트가 발생하면 새로운 페이지를 위한 공간을 위해 메모리의 페이지를 제거해야한다. 이때 자주 사용되지 않을 페이지를 선택하는 것이 시스템 성능에 도움이 된다. 프로세스가 모든 페이지를 균일하게 접근하는 것이 아니라 일부 페이지들을 집중적으로 참조하는 참조지역성(locality of reference)을 보이기 때문이다. 하지만 미리 미래에 자주 참조될 페이지를 아는 것이 불가능하기 때문에 과거의 기록을 통해 예측을 하며 교체될 페이지를 선택할 수밖에 없다. 페이지 교체 알고리즘은 웹서버의 캐시 교체에도 적용될 수 있다. 페이지는 수정되어서 디스크에 갱신 적재를 할 경우가 있지만, 캐시는 수정할 일이 없다는 점이 차이가 있다.

NRU(Not Recently Used)는 참조비트 R과 변경비트 M의 여부에 따라 4가지 클래스로 나누고, 클래스가 낮은 페이지를 먼저 교체한다. 참조비트는 꺼져있고, 변경비트가 켜져있는 상태가 불가능해보이지만 클록 인터럽트마다 참조비트가 초기화되기 때문에 가능한 상태이다. 이 알고리즘은 클록 틱 간격 동안 참조되지 않은 페이지를 우선적으로 교체한다. LRU(Least Recently Used)는 가장 오랫동안 사용되지 않은 페이지를 교체하게 되는데, 비트값을 갖는 행렬을 구현하여, k 번째 페이지프레임이 참조되면 행을 1로 열을 0 으로 변경하는 작업을 진행한다. 이진 값이 가장 작은 행이 가장 과거에 참조한 프레임이 된다.

NFU(Not Frequently Used)는 각 페이지마다 카운터를 유지해 클록 인터럽트마다 모든 페이지의 R 비트를 더한다. 카운터는 페이지가 얼마나 자주 참조되었는지 그 정도를 알려주고, 페이지 폴트 발생 시 가장 적은 카운터의 페이지가 교체된다. 하지만 모든 참조를 기록하기 때문에, 이전에는 자주 참조했지만 현재 자주 참조하지 않는 페이지일 경우에도 교체하게 된다. 따라서 에이징(aging)으로 문제를 해결한다. R 비트를 더하기 전에 오른쪽으로 1 비트 시프하고, R 비트는 왼쪽 최상위 비트에 추가한다. 똑같이 카운터 값이 가장 적은 페이지가 교체되지만, 과거에 여러번 참조되었더라도 현재 참조되지 않은 페이지는 우선적으로 교체된다. LRU 가 단순히 마지막 참조 시간에 따라 교체하는 것과 비교해 좀 더 정교한 알고리즘이다.

FIFO 가 변경된 알고리즘으로, Second-chance 알고리즘이 있다. 페이지 큐를 탐색하는데, R 비트가 0 이면 교체하고 1이면 0으로 변경후 페이지를 큐 뒤에 삽입하여 방금 적재된 것처럼 기회를 주는 것이다. 이때 페이지를 리스트에서 이동시키는 작업을 효율화 하기 위해 시계모양의 원형 리스트로 관리하는 알고리즘이 있다. WSClock 알고리즘은 에이징 알고리즘과 함께 실제 시스템에서 많이 사용되는데, 화살표가 가르키는 페이지의 R 이 1이면 0으로 변경 후 다음 페이지를 검사한다. R이 0인 경우에는 나이(time of last use)가 현재 t 보다 오래되었고, 페이가 수정되지 않았으면 페이지가 교체된다. 수정된 상태이면 디스크 쓰기를 스케줄링 한 후 다음 페이지를 검사한다. 쓰기를 위해 스케줄링된 페이지는 완료되면 M 비트가 0으로 바뀐다.

교착상태, Deadlock

교착상태는 프로세스들의 집합이 있고, 각각의 프로세스가 집합 내의 다른 프로세스가 발생시키는 이벤트를 기다리고 있는 상황이다. 모두 다른 프로세스를 기다리기 때문에 아무도 이벤트를 발생시킬 수 없고, 결국 영원히 기다리기만 할 것이다. 대부분의 경우 이 이벤트는 자원의 반환인데, 네 가지 조건이 만족되어야 자원 교착상태가 발생한다. 우선 상호배제(mutual exclusion)로 한 번에 한 프로세스만 해당 자원을 이용할 수 있는 상태에서, 점유대기(hold and wait)가 있어서 현재 자원을 보유한 프로세스가 다른 자원을 요청하기 때문에 대기하고 있다. 이때 비선점(no preemption)이기 때문에 할당된 자원을 강제로 빼앗을 수 없으며, 순환대기(circular wait)가 있어 각 프로세스가 순환적으로 다른 자원을 대기하고 있을 때 교착상태가 일어난다.

교착상태를 탐지(detection)하기 위해서는 자원들이 하나씩 있을 경우에는 자원 할당 그래프를 통해 싸이클이 있는지 여부를 확인하면 된다. 한편 자원들이 여러개씩 있을 때는 현재 자원이 할당된 행렬 C 와 자원을 요청하는 행렬 R 을 설정하여 실행가능한 요청을 수행함으로써 완료할 수 있는 순서가 존재하는지 확인한다. 하지만 교착 상태를 미리 탐지하기 위해 자원이 요청될 때마다 매번 검사하기에는 비용이 많이 소요된다. 매 k 분마다 혹은 CPU 이용률이 저하될 때에 검사할 수 있다.

탐지 후 교착상태를 회복(recovery)하기 위해서는 가장 간단하게는 자원을 회수하여 다른 프로세스에게 할당하는 선점 방법을 쓸 수 있다. 하지만 프로세스로부터 자원을 뺏고 다시 돌려주는 방법을 매우 어렵거나 불가능하다. 또는 싸이클에 포함된 프로세스를 강제 종료함으로써 해결할 수 있다. 하지만 데이터베이스를 갱신하는 것과 같이 어떤 필드에 1을 더한채로 강제종료되고, 다시 실행되어 필드가 2가 되는 일관적이지 못한 결과가 생길 수 있다. 한편 체크포인트를 두어 교착상태가 탐지될 경우 자원을 획득하기 이전의 시점으로 롤백을 하고(이후 수행을 원래대로 돌리고) 다른 프로세스에 자원을 할당하며 교착상태를 벗어날 수 있다.

교착상태를 탐지하고 회복하기 위한 방법은, 프로세스가 자원을 요청할 때 자원 모두를 한 번에 요청할 때에만 가능하다. 하지만 대부분 하나씩 자원을 요청하게 되며, 시스템은 자원을 승인하는 것인지 확인하여 안전할 때에만 할당하는 회피(avoidance)의 방법을 선택해야한다. 가장 유명한 알고리즘으로는 은행원 알고리즘이 있다. 자원 요청이 발생하여 이를 승인할 경우 안전한지 불안전한지 확인하고 안전할 경우에만 승인한다. 안전한 상태는 모든 프로세스들이 수행을 완료할 수 있는 순서를 보장하는 것이고, 이후 요청에 따라 순서를 보장할 수 없다면 불안전한 상태이다. 하지만 실제로 프로세스 별로 최대 자원 요구량을 알 수 없고, 프로세스의 수도 동적으로 변하기 때문에 잘 사용하지 않는 알고리즘이다.

예방(prevention)을 통해 교착상태의 4가지 조건 중 하나를 막아 애초에 구조적으로 발생이 불가능하게 할 수 있다. 상호배제를 제거하면 경쟁상태가 발생하므로 제거하기 힘든 조건이다. 하지만 불필요한 자원의 할당을 피하고, 최소한의 프로세스들이 자원을 요구하게끔 아이디어를 변경할 수 있다. 점유대기를 피하기 위해 프로세스가 필요한 모든 자원을 할당받을 때에만 실행을 하게끔할 수 있다. 하지만 미리 알 수 없고, 자원이 최적으로 사용되지 않는 문제가 있다. 변형을 한다면 어떤 프로세스가 자원을 요구할 때 모든 자원을 반환하고 그 시점까지 필요한 것을 한번에 획득하도록 할 수 있다. 비선점 조건 대신 필요한 자원을 강제로 뺏는 경우인데, 예상치 못한 결과가 나올 수 있어 위험하다. 마지막으로 순환 대기를 제거하는 방법이다. 모든 자원에게 인덱스를 매기고, 프로세스가 자원을 요청할 때 오름차순(혹은 내림차순)으로만 자원을 요청하게 하여 싸이클을 만들지 못하게 한다. 하지만 수많은 자원에 순서를 매기는 점, 프로세스별로 최적의 요청 순서를 알아내는 점이 한계점이다.