내용 출처
KOCW 반효경 교수님 <운영체제> 강의
책 <운영체제와 정보기술의 원리>
반효경 교수님의 <운영체제> 강의 내용을 기반으로 정리했으며,
교수님의 저서 <운영체제와 정보기술의 원리>에서 추가할 만한 내용을 이와 같은 인용문 형식으로 추가함.
부모 프로세스(parent process)가 자식 프로세스(children process)를 생성한다. 이때 생성의 방법은 복제 생성이다. 부모 프로세스의 문맥을 복제하여 자식 프로세스를 형성한다.
일단 자식 프로세스가 만들어지고 나면, 독립적인 프로세스가 된다. 그래서 원칙적으로는 부모와 자식이 자원을 공유하지 않는다. 다만, 효율성을 위해 부모와 자식이 자원을 공유하기도 한다.
프로세스의 생성은 부모 프로세스가 직접 하지 않는다. 부모 프로세스가 fork()
시스템 콜을 요청하면, 운영체제가 부모를 그대로 복사해서 자식 프로세스를 생성한다. 일단 복제를 한 이후, exec()
시스템 콜을 통해 새로운 프로그램을 메모리에 올린다.
프로세스의 트리 형성
수행 (Execution)
주소 공간
프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알려준다. (exit)
부모 프로세스가 자식의 수행을 종료시킨다. (abort)
사용자 계정으로 서버 컴퓨터에 접속해서 수행시킨 프로그램을 로그아웃 후에도 계속 수행시켜야 하는 경우가 있을 수 있다. 이를 위해서는 로그아웃 후에도 계속 수행시킬 프로세스를 로그아웃 후에도 존재하는 시스템 프로세스의 자식으로 이양시켜야 한다. 즉, 종료되는 프로세스의 자식 프로세스를 계속 실행시키기 위해서 종료되지 않을 다른 프로세스의 양자로 자식 프로세스를 보내는 것이다.
fork()
: 자식 프로세스를 생성한다.
exec()
: 새로운 프로그램으로 덮어쓴다.
wait()
: 자식 프로세스가 종료될 때까지 blocked 상태로 만든다. 자식 프로세스가 종료되면 ready 상태로 만든다.
exit()
: 프로세스를 종료시키고 부모 프로세스에게 알린다.
int main()
{
int pid;
pid = fork();
if (pid == 0)
printf("\n Hello, I am child!\n");
else if (pid > 0)
printf("\n Hello, I am parent!\n");
}
fork()
를 통한 프로세스의 복제 생성은 부모 프로세스의 문맥을 그대로 복사한다. 위 코드에서 복제가 이루어질 때, 프로그램 카운터는 fork()
가 끝난 시점을 가리키고 있다. 따라서 자식 프로세스는 생성된 이후 main 함수의 처음부터 실행되는 것이 아니라, fork()
이후 부분부터 실행된다.
그렇다면 부모 프로세스와 자식 프로세스를 어떻게 구분하는가? fork()
함수의 결과값을 통해 구분한다. 부모 프로세스는 fork()
의 결과값이 양수이고, 자식 프로세스는 fork()
의 결과값으로 0을 받는다. 이를 통해 둘을 구분할 수 있다.
자식 프로세스를 새로운 프로세스로 덮어쓰는 역할을 한다.
int main()
{
int pid;
pid = fork();
if(pid == 0)
{
printf("\n Hello, I am child! Now I'll run date \n");
execlp("/bin/date", "/bin/date", (char *) 0);
}
else if (pid > 0)
printf("\n Hello, I am parent!\n");
}
execlp 함수의 형식
execlp("프로그램 이름", "프로그램 이름", "전달할 인수들", (char *) 0);
execlp()
함수가 exec()
시스템 콜을 한다. 그러면 자식 프로세스는 date라는 새로운 프로그램의 시작 부분부터 실행된다.
cf) 반드시 자식 프로세스를 생성해서 exec()
을 할 필요는 없다. 아래 코드와 같이 fork()
시스템 콜 없이 exec()
시스템 콜을 요청할 수 있다. 그러면 기존 프로그램에 대한 정보를 잊어버리고, 새로운 프로그램의 시작 부분부터 실행된다. printf("2")
부분은 결코 실행되지 않을 것이다.
int main()
{
printf(“1”);
execlp(“echo”,”echo”,”3”, (char *) 0);
printf(“2”);
}
부모 프로세스가 자식 프로세스가 종료될 때까지 기다린다.
(자식이 종료될 때까지 부모가 기다리는 모델)
프로세스 A가 wait()
시스템 콜을 호출하면,
wait()
시스템 콜 이후 부모 프로세스는 일반적인 blocked 상태에서처럼 자원을 기다리며 줄 서 있는 것이 아니라, 자식 프로세스가 종료되기를 기다리며 수면 상태에 머무르게 되는 것이며, 자식 프로세스가 종료되는 순간 준비 큐에 재진입해 다시 CPU를 얻을 권한을 획득하게 된다.
int main()
{
int childPID;
s1;
childPID = fork();
if (childPID == 0)
<code for child process>
else
{
<code for parent process>
wait();
}
s2;
}
<
프로세스를 종료시킨다.
자발적 종료
exit()
시스템 콜을 통해 종료한다.exit()
을 프로그램에 명시적으로 적어주지 않아도 main 함수가 리턴되는 위치에 컴파일러가 자동으로 넣어준다.비자발적 종료 (부모 프로세스, 사용자 등 외부에서 종료시키는 경우)
독립적 프로세스 (Independent process)
프로세스는 각자의 주소 공간을 가지고 수행되므로 원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못한다.
협력 프로세스(Cooperating process)
프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있다.
프로세스 간 협력 메커니즘 (IPC : Interprocess Communication)
IPC : 하나의 컴퓨터 안에서 실행 중인 서로 다른 프로세스 간에 발생하는 통신
이러한 통신에서는 의사소통 기능과 함께 동기화를 보장해주어야 한다. 공유 데이터를 서로 다른 두 프로세스가 사용할 수 있다면 데이터의 불일치 문제가 발생할 수 있기 때문이다. 따라서 하나의 프로세스가 공유 데이터의 값을 변경하는 동안, 다른 프로세스는 그 데이터에 접근할 수 없게 해야 한다.
메시지를 전달하는 방법
message passing (메시지 전달 방식)
프로세스 사이에 공유 데이터를 일체 사용하지 않고, 커널을 통해 메시지를 전달한다.
통신을 원하는 두 프로세스 사이에 커뮤니케이션 링크를 생성한 후,
send()
와receive()
를 통해 메시지를 주고받게 된다.
통신하려는 프로세스의 이름을 명시적으로 표시한다.
send(P, message)
: 프로세스 P에게 메시지를 보내는 것
receive(Q, message)
: 프로세스 Q로부터 메시지를 받는 것
mailbox 또는 port를 통해 메시지를 간접 전달한다.
간접통신에서 사용되는 커뮤니케이션 링크는 프로세스 간에 메일박스를 공유하는 경우에만 생성된다.
send(A, message)
: A라는 메일박스에 메시지를 보내는 것
receive(A, message)
: A라는 메일박스로부터 메시지를 받는 것
두 방법 모두 운영체제 커널을 통해서 전달해야 한다.
(직접 통신도 커널을 통해 통신한다.)
사용자 프로세스끼리 메시지를 전달하는 것은 불가능하다.
주소 공간을 공유하는 방법
shared memory : 서로 다른 프로세스 간에도 일부 주소 공간을 공유하게 하는 shared memory 메커니즘이 있다.
공유된 주소 공간을 쓰기 위해서는 우선 커널에 시스템 콜을 요청해야 한다.
원칙적으로 프로세스들은 독자적 주소 공간을 가지고 있기 때문에, 자신의 주소 공간에만 접근할 수 있다. 그런데 shared memory 매커니즘을 통해 일부 주소 공간을 공유할 수 있다. 아래 그림에서 프로세스 A가 공유된 주소 공간에 어떤 데이터를 적으면, 프로세스 B도 그 데이터를 접근할 수 있다.
cf) 스레드(thread) : 스레드는 하나의 프로세스이므로 프로세스 간의 협력이라고 보기는 어렵다. 하지만 동일한 프로세스를 구성하는 스레드들은 주소 공간을 공유하므로 협력이 가능하다.