프로세스 실행은 순차적으로 진행되어야 합니다.
프로세스의 현재 활동 상태는 프로그램 카운터와 레지스터에 의해 표현됩니다.
프로세스의 메모리 레이아웃에는 여러 섹션이 있습니다.
프로그램의 실행은 GUI 마우스 클릭, 이름의 명령 줄 입력 등을 통해 시작됩니다.
하나의 프로그램은 여러 프로세스가 될 수 있습니다.


프로세스가 실행되는 동안, 그 상태는 변화합니다.
새로운 상태(New): 프로세스가 생성되고 있는 상태입니다.
실행 중(Running): 명령어들이 실행되고 있는 상태입니다.
대기 중(Waiting): 어떤 이벤트가 발생하기를 기다리는 상태입니다.
준비 완료(Ready): 프로세스가 프로세서에 할당되기를 기다리는 상태입니다.
종료(Terminated): 프로세스가 실행을 마친 상태입니다.
간단히 설명하자면, 프로세스는 실행 과정에서 여러 상태를 거칩니다. 처음에는 새로운 프로세스가 생성되는 단계에서 시작합니다. 프로세스가 실행 명령을 받아 실행 중 상태가 되면, 실제 작업을 처리합니다. 때로는 특정 이벤트나 자원을 기다려야 할 때 대기 상태가 됩니다. 준비 상태는 프로세스가 실행을 위해 프로세서 할당을 기다리는 단계입니다. 마지막으로, 프로세스가 모든 작업을 완료하고 종료 상태가 됩니다.

운영 시스템(OS)은 PCB를 사용하여 프로세스를 관리합니다.
프로세스 제어 블록(PCB): 각 프로세스와 관련된 정보를 담고 있습니다.
(작업 제어 블록이라고도 합니다.)
프로세스에 포함된 카테고리 정보
PCB는 운영 시스템이 프로세스를 효과적으로 관리하기 위해 필요한 모든 정보를 포함합니다. 이 정보는 프로세스의 현재 상태, 식별 정보, 실행에 필요한 기술적 세부 사항 등을 포함하여 프로세스의 전체적인 상태와 환경을 반영합니다. PCB를 통해 운영 시스템은 프로세스 간 전환(context switch) 시 필요한 정보를 빠르게 찾아 프로세스의 실행을 원활하게 관리할 수 있습니다.
스레드를 사용하면 하나의 프로세스 내에서 여러 작업을 동시에 실행할 수 있게 됩니다. 이는 프로그램의 효율성을 크게 향상시키며, 특히 멀티코어와 같은 병렬 처리가 가능한 시스템에서 그 장점이 더욱 부각됩니다. 멀티스레딩을 통해 각 스레드는 독립적인 실행 흐름을 가지면서도 프로세스의 자원을 공유할 수 있어, 리소스 사용과 작업 처리 시간을 최적화할 수 있습니다. PCB 내에 여러 프로그램 카운터를 포함하는 등, 운영 체제는 이러한 멀티스레딩 환경을 관리하기 위해 필요한 정보를 확장하여 저장합니다.
한 번에 각 프로세서에서 실행될 수 있는 프로세스는 하나뿐입니다.
프로세스 스케줄러는 다음 실행을 위해 사용 가능한 프로세스 중에서 선택합니다.
다른 프로세스들은 스케줄링 큐에서 대기해야 합니다.
스케줄링의 목표
프로세스 스케줄링은 운영 체제가 자원을 효율적으로 관리하고, 여러 프로세스가 공정하게 CPU 시간을 할당받을 수 있도록 하는 중요한 작업입니다. 이를 통해 시스템의 성능을 최적화하고, 사용자에게 더 나은 반응 시간과 서비스 품질을 제공할 수 있습니다. 다양한 스케줄링 알고리즘이 있으며, 각각은 특정 환경과 요구 사항에 맞게 설계되었습니다. 예를 들어, 단기 스케줄링은 CPU가 다음에 실행할 프로세스를 결정하고, 장기 스케줄링은 어떤 프로세스를 메모리에 적재할지 결정합니다. 또한, 중기 스케줄링은 멀티프로그래밍의 정도를 조절하는 역할을 합니다.
이 두 큐는 운영 체제의 프로세스 스케줄링 메커니즘의 핵심 요소로, 시스템의 자원을 효율적으로 관리하고 프로세스 실행의 순서를 결정하는 데 사용됩니다. 준비 큐는 CPU 자원의 할당을 기다리는 프로세스들을 관리하고, 대기 큐는 비-CPU 자원(주로 I/O)을 기다리는 프로세스들을 관리합니다. 이를 통해 운영 체제는 프로세스들 사이의 공정한 자원 분배와 효율적인 실행을 보장할 수 있습니다.
Representation of process scheduling
◼ Processes migrate among the various queues

Context Switch : CPU가 한 프로세스에서 다른 프로세스로 전환하는 것
컨텍스트 스위치는 운영 체제가 멀티태스킹을 구현하는 핵심 메커니즘입니다. 운영 체제는 현재 실행 중인 프로세스의 상태(레지스터 값, 프로그램 카운터, 스택 포인터 등)를 PCB에 저장함으로써, 해당 프로세스를 나중에 정확한 지점에서 재개할 수 있도록 합니다. 이후 새로운 프로세스의 PCB에 저장된 상태를 CPU에 로드하여 실행을 시작합니다.
컨텍스트 스위치는 필수적이지만 오버헤드를 발생시키므로, 시스템의 전반적인 성능에 영향을 줄 수 있습니다. 따라서 운영 체제는 컨텍스트 스위치를 최소화하고 효율적으로 관리하기 위해 다양한 스케줄링 알고리즘을 사용합니다. 하드웨어의 지원, 예를 들어 레지스터 세트의 수를 늘림으로써 컨텍스트 스위치의 효율성을 향상시킬 수도 있습니다. 이러한 하드웨어 기능은 컨텍스트 스위치 시간을 줄이고 시스템의 전반적인 성능을 개선하는 데 기여할 수 있습니다.
System Calls와 Context Switch의 차이점
목적과 효과: 컨텍스트 스위치는 CPU가 다양한 프로세스를 공정하게 처리할 수 있도록 돕는 반면, I/O 인터럽트와 시스템 호출은 프로세스가 필요로 하는 입출력 작업이나 커널 수준의 서비스를 요청하는 메커니즘입니다.
최근의 모바일 시스템도 사용자 애플리케이션을 위한 멀티태스킹을 제공합니다.
안드로이드(Android)는 포그라운드와 백그라운드를 모두 실행합니다.
백그라운드 프로세스는 작업을 수행하기 위해 서비스(Service)를 사용합니다.
백그라운드 프로세스가 중단되더라도 서비스는 계속 실행될 수 있습니다.
서비스는 사용자 인터페이스가 없으며, 메모리 사용이 적습니다.
분할 화면(Split Screen)
모바일 운영 체제에서 멀티태스킹은 사용자가 여러 애플리케이션을 동시에 사용할 수 있게 해줍니다. 포그라운드 애플리케이션은 사용자가 직접 상호 작용하는 애플리케이션이며, 화면에 표시됩니다. 반면, 백그라운드 애플리케이션은 화면 뒤에서 실행되며 사용자에게 직접적으로 보이지 않습니다.
안드로이드와 같은 모바일 운영 체제에서, 백그라운드 프로세스는 주로 서비스를 통해 작업을 수행합니다. 서비스는 백그라운드에서 오디오 재생, 파일 다운로드, 네트워크 통신 등을 포함한 작업을 할 수 있으며, 애플리케이션이 화면에 표시되지 않을 때도 계속 실행될 수 있습니다. 이러한 서비스는 사용자 인터페이스가 없으므로, 시스템 자원과 메모리를 적게 사용합니다.
분할 화면 기능은 사용자가 한 화면에서 두 개 이상의 애플리케이션을 동시에 보고 사용할 수 있게 해줍니다. 이는 멀티태스킹을 더욱 효율적으로 만들어주며, 작업 효율성을 높여줍니다. 모바일 기기의 작은 화면에서도 여러 작업을 동시에 처리할 수 있게 되어 사용자 경험이 향상됩니다.
운영 체제는 각 프로세스를 구분하기 위해 프로세스 식별자(PID)를 할당합니다. PID는 시스템 내에서 각 프로세스에 대해 유일한 값을 가지며, 이를 통해 특정 프로세스를 식별하고 관리할 수 있습니다.
PID는 프로세스의 다양한 속성에 접근하는 데 사용될 수 있는 인덱스 역할을 합니다. 예를 들어, 운영 체제는 PID를 사용하여 프로세스의 우선순위, 상태, 메모리 사용량 등을 관리합니다.
프로세스 트리 구조는 운영 체제가 프로세스를 효율적으로 관리하고, 다양한 작업을 동시에 처리할 수 있게 하는 데 중요한 역할을 합니다. 또한, 이 구조는 보안, 자원 관리, 프로세스 간 통신 등 다양한 측면에서 응용됩니다.


부모와 자식이 모든 자원을 공유
자식이 부모의 자원의 부분집합을 공유
부모와 자식이 자원을 공유하지 않음
부모와 자식이 동시에 실행
부모가 자식의 종료를 기다림: 부모 프로세스가 wait() 시스템 호출을 사용하여 자식 프로세스의 종료를 기다리는 경우입니다. 이는 자식 프로세스의 결과를 부모 프로세스가 처리해야 하는 경우에 사용됩니다.
자식이 부모의 복제본: fork() 시스템 호출을 사용할 때, 자식 프로세스는 부모 프로세스의 주소 공간, 즉 코드, 데이터, 스택 등을 복제하여 생성됩니다. 이후, 자식 프로세스는 독립적으로 실행됩니다.
자식에게 새 프로그램을 로드: exec() 계열의 시스템 호출을 사용하여, 자식 프로세스는 새로운 프로그램을 자신의 주소 공간에 로드하여 실행합니다. 이는 부모 프로세스가 다른 프로그램을 실행할 목적으로 자식 프로세스를 생성할 때 사용됩니다.
기능: 새로운 프로세스를 생성합니다. 이 새로운 프로세스는 호출한 프로세스(부모 프로세스)의 정확한 복사본이며, 자식 프로세스라고 합니다.
반환 값:
기능: 현재 프로세스의 주소 공간에서 새로운 프로그램을 실행합니다. 이때, exec() 계열의 호출로 인해 현재 실행 중인 프로그램은 새로운 프로그램으로 대체됩니다.
종류:
기능: 부모 프로세스가 자식 프로세스의 종료를 기다립니다. 자식 프로세스가 종료되기 전까지 부모 프로세스의 실행이 중단됩니다.
사용법: 자식 프로세스가 정상적으로 종료될 때까지 부모 프로세스의 실행을 일시 중지시키며, 자식 프로세스의 종료 상태를 받아올 수 있습니다.


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if ( pid < 0 ) {
fprintf(stderr, "fork failed\n");
return 1;
} else if (pid == 0 ) {
execlp("/bin/ls", "ls", NULL); // /bind/ls 를 ls로 실행되도록 하겠다. NULL: 명령 끝!
} else {
wait(NULL);
printf("Child Completed\n");
}
return 0;
}

More About fork() -> child와 parent의 변수값은 서로 공유하지 않는다.
1. fork() 프로세스 생성 과정
- 프로세스 복제: fork()를 호출하면, 부모 프로세스의 전체 주소 공간(코드, 데이터, 힙, 스택 등)이 자식 프로세스에 복제됩니다.
- 메모리 공간: 호출 시점에서 두 프로세스의 메모리 공간 내용은 동일합니다. 이후 각 프로세스에서 수행되는 메모리 쓰기 작업, 파일 매핑, 매핑 해제 등은 다른 프로세스에 영향을 주지 않습니다.
- 반환 값
- 자식 프로세스에서는 0을 반환합니다.
- 부모 프로세스에서는 생성된 자식 프로세스의 PID(프로세스 식별 번호)를 반환합니다.
- 오류 발생 시 -1을 반환합니다.
- 자식 프로세스의 자원
- 데이터(변수): 부모 프로세스의 변수 복사본을 가집니다. 자식 프로세스는 자신만의 주소 공간을 갖기 때문에, 이 변수들은 부모와 독립적으로 관리됩니다.
- 파일
- fork() 호출 전에 열린 파일은 부모와 자식 프로세스가 공유합니다. 이는 파일 디스크립터가 복제되기 때문입니다.
- fork() 호출 후 자식 프로세스에서 열린 파일은 부모 프로세스와 공유되지 않습니다. 이는 자식 프로세스가 독립적인 파일 디스크립터 테이블을 갖기 때문입니다.
- 주요 포인트
- fork() 호출로 인해 생성된 자식 프로세스는 자신만의 독립된 주소 공간을 가집니다.
- 부모와 자식 프로세스는 fork() 호출 시점에서는 메모리 내용이 동일하지만, 이후 각각 독립적으로 메모리를 관리합니다.
- 파일 디스크립터는 fork() 호출 시 복제되므로, 호출 전에 열린 파일은 부모와 자식 프로세스 사이에서 공유됩니다.
- fork()의 성공 여부는 자식 프로세스에서는 0, 부모 프로세스에서는 자식 PID 또는 오류 시 -1을 반환함으로써 확인할 수 있습니다.
More About exec() 계열 함수 + ...는 parameter argument를 의미함.
1. execl()
- 원형: int execl(const char pathname, const char arg, ..., (char *) NULL);
- 설명: pathname에 명시된 프로그램을 실행하며, 명령줄 인자를 개별적으로 전달합니다. 인자 목록의 끝은 NULL로 표시해야 합니다.
- execlp()
- 원형: int execlp(const char file, const char arg0, ..., (char *) NULL);
- 설명: file에 명시된 프로그램을 실행합니다. 만약 file에 슬래시(/)가 포함되어 있지 않다면, PATH 환경 변수에 지정된 디렉토리들을 검색하여 파일을 찾습니다. 명령줄 인자는 개별적으로 전달합니다.
- execle()
- 원형: int execle(const char pathname, const char arg, ..., (char ) NULL, char const envp[]);
- 설명: pathname에 명시된 프로그램을 실행합니다. 이 함수의 특징은 새로운 환경 변수를 설정할 수 있다는 점입니다. 인자 목록의 끝은 NULL로 표시하고, 다음 인자로 환경 변수 배열을 전달합니다.
- execv()
- 원형: int execv(const char pathname, char const argv[]);
- 설명: pathname에 명시된 프로그램을 실행합니다. 명령줄 인자는 문자열 배열로 전달되며, 배열의 마지막 요소는 NULL이어야 합니다.
- execvp()
- 원형: int execvp(const char file, char const argv[]);
- 설명: file에 명시된 프로그램을 실행합니다. file에 슬래시(/)가 포함되어 있지 않다면, PATH 환경 변수에 지정된 디렉토리들을 검색하여 파일을 찾습니다. 명령줄 인자는 문자열 배열로 전달됩니다.
인자 이름 설명
- pathname: 새로운 프로세스 이미지 파일을 식별하는 경로 이름입니다.
- file: 새로운 프로세스 이미지 파일입니다. 만약 경로 이름에 슬래시(/)가 포함되지 않은 경우, PATH 환경 변수에 지정된 디렉토리들을 통해 해당 파일이 식별됩니다.
- exec() 계열 함수는 현재 실행 중인 프로세스를 완전히 새로운 프로그램으로 대체하기 때문에, 함수 호출이 성공하면 반환값은 없습니다. 오류가 발생한 경우에만 -1을 반환하고, errno에 오류 코드를 설정합니다.
프로세스 종료 과정
1. 종료 요청: 프로세스는 마지막 명령문을 실행한 후, exit() 시스템 호출을 사용하여 운영 체제에게 자신을 삭제하도록 요청합니다.
2. 상태 데이터 반환: 자식 프로세스는 exit() 호출을 통해 종료 상태를 부모 프로세스에게 반환할 수 있습니다. 부모 프로세스는 wait() 시스템 호출을 사용하여 자식 프로세스의 종료 상태를 받을 수 있습니다.
3. 자원 해제: 프로세스에 할당된 자원은 운영 체제에 의해 해제됩니다.
4. 부모 프로세스에 의한 자식 프로세스 종료
-> 부모 프로세스가 여러 이유로 자식 프로세스의 실행을 종료시킬 수 있습니다:
: 자식이 할당된 자원을 초과했을 때
: 자식에게 할당된 작업이 더 이상 필요하지 않을 때
: 부모 프로세스가 종료되고, 운영 체제가 부모 없이 자식 프로세스의 계속된 실행을 허용하지 않을 때
- 일부 운영 체제는 부모 프로세스가 종료되면 자식 프로세스도 함께 종료되도록 합니다. 이러한 경우, 모든 자식 프로세스, 손자 프로세스 등이 연쇄적으로 종료되는 것을 연쇄 종료라고 합니다.
wait() 시스템 호출
부모 프로세스는 wait() 시스템 호출을 사용하여 자식 프로세스의 종료를 기다릴 수 있습니다. 이 호출은 자식 프로세스의 PID와 상태 정보를 반환합니다.pid_t pid = wait(&status);좀비 프로세스와 고아 프로세스
1. 좀비 프로세스: 부모 프로세스가 wait()를 호출하지 않고 자식 프로세스가 종료되면, 자식 프로세스는 종료 상태 정보를 보관하기 위해 운영 체제에 의해 유지되는데, 이 상태의 프로세스를 좀비 프로세스라고 합니다.
2. 고아 프로세스: 부모 프로세스가 자식 프로세스보다 먼저 종료되고 wait()를 호출하지 않는 경우, 자식 프로세스는 고아 프로세스가 됩니다. 고아 프로세스는 대부분의 운영 체제에서 init 프로세스(또는 다른 시스템 프로세스)가 자동으로 부모가 되어 wait()를 호출함으로써 처리됩니다.
// 이 밑은 개인 공부 필요함.
wait() 시스템 호출은 부모 프로세스가 자식 프로세스의 종료를 기다리는 데 사용됩니다. 이 함수는 호출하는 프로세스(부모)를 일시 중지시키고, 자식 프로세스 중 하나가 종료될 때까지 기다립니다. 이러한 방식으로, wait()는 프로세스 동기화의 한 형태로 작동합니다. (block function)
// wait() 함수 사용법 #include <sys/wait.h> pid_t wait(int *wstatus);
- wstatus: 자식 프로세스의 종료 상태를 저장하는 정수형 포인터입니다. 이 포인터가 NULL이 아닌 경우, wait() 또는 waitpid() 함수는 상태 정보를 이 정수에 저장합니다. 저장된 정보를 해석하기 위해 몇 가지 매크로(WIFEXITED, WEXITSTATUS 등)를 사용할 수 있습니다.
- 반환 값: 성공 시 종료한 자식 프로세스의 PID를 반환하고, 오류가 발생하면 -1을 반환합니다.
wait()와 waitpid() 차이점
wait() 함수는 어떤 자식 프로세스가 종료되기를 기다리지만, waitpid() 함수는 특정 PID를 가진 자식 프로세스 또는 특정 조건을 만족하는 자식 프로세스의 상태 변경을 기다릴 수 있습니다. 따라서 waitpid()는 wait()보다 더 유연한 사용이 가능합니다.
wait()의 매크로 사용법
종료 상태 wstatus를 해석하기 위한 몇 가지 유용한 매크로가 있습니다:
WIFEXITED(wstatus): 자식 프로세스가 정상적으로 종료되었는지 확인합니다. 참이면, WEXITSTATUS(wstatus) 매크로를 사용하여 자식 프로세스의 종료 코드를 가져올 수 있습니다.
WIFSIGNALED(wstatus): 자식 프로세스가 시그널에 의해 종료되었는지 확인합니다. 참이면, WTERMSIG(wstatus) 매크로를 사용하여 해당 시그널 번호를 가져올 수 있습니다.
WIFSTOPPED(wstatus): 자식 프로세스가 현재 정지 상태인지 확인합니다. 참이면, WSTOPSIG(wstatus) 매크로를 사용하여 정지 시킨 시그널 번호를 가져올 수 있습니다.
wait() 함수는 프로세스 관리와 동기화에 있어 중요한 역할을 하며, 부모 프로세스가 자식 프로세스의 리소스를 정리하고, 좀비 프로세스가 되는 것을 방지하는 데 필수적입니다.
waitpid() 함수는 부모 프로세스가 특정 자식 프로세스의 상태 변경을 기다리는 기능을 제공합니다. 이 함수는 특정 자식 프로세스가 종료되거나, 시그널에 의해 중단 또는 계속 실행되는 등의 상태 변경이 발생할 때까지 부모 프로세스의 실행을 중단시킵니다.
waitpid() 함수 사용법
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
pid: 상태 변경을 기다리고자 하는 자식 프로세스의 PID입니다. 특정 값을 가지는 경우, 해당 PID의 프로세스만 기다립니다. -1을 전달하면 wait() 함수와 동일하게 어떤 자식 프로세스든 상태 변경을 기다립니다.
wstatus: 자식 프로세스의 상태 정보를 저장할 정수형 포인터입니다. wait() 함수에서 설명한 매크로 함수들(WIFEXITED, WEXITSTATUS 등)을 사용하여 이 값을 해석할 수 있습니다.
options: waitpid() 함수의 동작 방식을 조정합니다. 예를 들어, WNOHANG을 지정하면 자식 프로세스가 종료되지 않았어도 부모 프로세스가 즉시 반환되어 계속 실행될 수 있습니다.
options 설명
WNOHANG: 자식 프로세스가 종료되지 않았다면, waitpid() 함수는 즉시 0을 반환하고 부모 프로세스의 실행을 계속합니다. 이 옵션을 사용하면 부모 프로세스가 블록되지 않고, 다른 작업을 계속 수행할 수 있습니다.
반환 값
성공: 상태가 변경된 자식 프로세스의 PID를 반환합니다.
오류: -1을 반환하며, 오류의 원인은 errno에 설정됩니다.
WNOHANG 옵션이 설정되어 있고 자식 프로세스의 상태가 변경되지 않은 경우: 0을 반환합니다.
사용 예
int status;
pid_t pid = fork();
if (pid == -1) {
// fork 실패
} else if (pid > 0) {
// 부모 프로세스
pid_t wpid = waitpid(pid, &status, WNOHANG);
if (wpid == 0) {
// 자식 프로세스가 아직 종료되지 않음
} else if (wpid == -1) {
// waitpid 호출 실패
} else {
// 자식 프로세스가 종료됨
}
} else {
// 자식 프로세스
// 일정 작업 수행
exit(0);
}
waitpid() 함수는 특정 자식 프로세스의 상태를 더 세밀하게 관리하고 싶을 때 유용하며, 비동기 작업이나 복잡한 프로세스 관리가 필요한 상황에서 매우 유용합니다.
시스템 내의 프로세스들은 독립적일 수도 있고 협력할 수도 있습니다.
프로세스 간 통신(IPC)의 두 가지 모델
공유 메모리 시스템은 프로세스 간 통신(IPC)의 한 형태로, 통신을 원하는 프로세스들 사이에서 메모리 영역을 공유하는 방식입니다. 이러한 방식은 운영체제가 아닌 사용자 프로세스의 제어 하에 이루어지며, 이는 프로세스들이 직접적으로 메모리를 공유하고 접근할 수 있게 합니다. 그러나, 공유 메모리 시스템의 주요 이슈 중 하나는 사용자 프로세스들이 공유 메모리에 접근할 때 그들의 행동을 동기화할 수 있는 메커니즘을 제공하는 것입니다.

동기화 메커니즘 -> chapter 5에서 추가적으로 배움. (여기서는 자세히 알 필요는 없음)
세마포어(Semaphores): 세마포어는 일종의 카운터로, 공유 자원에 대한 접근을 제어하는 데 사용됩니다. 세마포어는 프로세스가 공유 자원을 사용할 수 있는지 여부를 나타냅니다. 세마포어의 값이 0보다 크면, 프로세스는 자원을 사용할 수 있고, 그 값이 0이면 대기 상태에 들어갑니다.
뮤텍스(Mutexes): 뮤텍스(상호 배제)는 한 번에 하나의 프로세스만 공유 자원에 접근할 수 있도록 합니다. 뮤텍스는 세마포어와 유사하지만, 주로 락(lock)과 언락(unlock)의 개념으로 사용되며, 세밀한 접근 제어가 필요할 때 사용됩니다.
모니터(Monitors): 모니터는 고수준의 동기화 메커니즘으로, 프로세스가 공유 메모리에 대한 접근을 요청할 때 자동으로 동기화를 제공합니다. 모니터 내에서는 변수와 함께 조건 변수를 사용하여 프로세스의 실행을 조율할 수 있습니다.
조건 변수(Condition Variables): 조건 변수는 특정 조건이 충족될 때까지 프로세스를 대기시키는 데 사용됩니다. 이는 특정 조건 하에서만 접근을 허용하고자 할 때 유용합니다.

버퍼는 데이터를 임시 저장하는 메모리 영역으로, 특히 생산자-소비자 문제에서 중요한 역할을 합니다. 버퍼는 크게 두 가지 유형으로 나눌 수 있습니다: 무제한(unbounded) 버퍼와 유한(bounded) 버퍼입니다.
유한 버퍼의 동작 방식
이와 같이, 유한 버퍼는 생산자와 소비자 사이의 데이터 흐름을 조절함으로써 시스템의 성능과 안정성을 유지하는 데 중요한 역할을 합니다. 동시에, 세마포어나 뮤텍스와 같은 동기화 도구를 사용하여 생산자와 소비자 사이의 동기화를 관리할 수 있습니다.
교재에 있는 example 내용 무조건 볼 것.