•
참고 강의
contents
프로세스 생성
Q. 누가 프로세스를 생성하는가?
A. 부모 프로세스가 자식 프로세스를 생성
•
사용자 프로세스가 다른 프로세스를 직접 생성하는 건 아니고 운영체제를 통해서만 생성 가능
→ fork(), exec() (System Call)
Q. 생성 방식?
A. 복제 생성
하나의 부모 프로세스를 복제 생성을 통해서 여러 자식 프로세스를 생성한다.
프로세스의 계층 구조가 트리 구조로 형성된다.
•
프로세스는 자원을 필요로 한다.
◦
운영체제로부터 자원을 받는다.
◦
부모 프로세스와 자원을 공유한다.(경우에 따라서) → 원칙적으로는 공유하지 않음
▪
별도의 프로세스이기 때문에 자원을 가지고 경쟁하는 사이이다.
▪
자원을 공유하지 않는게 일반적이다.
•
부모, 자식 프로세스의 모델
자원의 공유 | 수행(Execution) |
부모, 자식이 모든 자원을 공유하는 모델 | 부모와 자식이 공존하며 수행되는 모델 |
일부를 공유하는 모델 | 자식이 종료될 때까지 부모가 기다리는 모델
- 부모가 Blocked 상태 |
전혀 공유하지 않는 모델 |
◦
가능하면 부모의 자원을 공유하는게 효율적이지만 별개의 프로세스이기 때문에 독립적으로 갖는 것이 원칙
Q. 부모가 자식을 어떤 방식으로 생성하느냐?
부모 프로세스의 주소 공간과 OS 데이터(PCB, 자원들)를 자식 프로세스가 복사를 한다. → fork()
복제된 자식 프로세스에 새로운 프로그램을 덮어씌운다. → exec()
•
복제가 안된 상태에서 exec()를 실행하면 해당 프로세스가 다른 프로그램으로 덮어씌워진다.
copy-on-write(COW)
Write가 발생했을 때, Copy를 하겠다.
•
내용이 바뀔 때 Copy해서 새로운 걸 만들고 이전까지는 부모의 자원을 공유
◦
부모의 Code, Data, Stack를 통째로 Copy 하는 것이 아니라 물리적 메모리에 올라가는 잘게 쪼개진 부분만 Copy하게 된다.
프로세스 종료
종료 과정(자발적)
프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알려준다. exit()
자식이 부모 프로세스에게 Output Data를 보낸다. → wait()
•
프로세스의 세계에서는 자식이 먼저 종료된다.
프로세스의 각종 자원들이 운영체제로 반납된다.
종료 과정(비자발적)
부모 프로세스가 자식의 수행을 종료시킨다. abort()
•
자식이 할당 자원의 한계치를 넘어서는 경우
•
자식에게 할당된 Task가 더 이상 필요하지 않을 경우
•
부모 프로세스가 종료(exit)되는 경우
◦
자식 프로세스가 먼저 죽는 것이 원칙이기 때문에, 자신이 생성한 모든 자식 프로세스를 종료하고 나서 종료된다.
◦
단계적인 종료 트리 밑 부분부터 단계적으로 종료된다.
System Call
fork() 시스템 콜 → create a child(copy)
int main() {
int pid;
pid = fork(); // System Call
if (pid == 0)
print("\n Hello, I am child!\n");
else if (pid > 0)
print("\n Hello, I am parent!\n");
}
C
복사
•
자식 프로세스는 부모 프로세스의 자원을 그대로 Copy하기 때문에, 문맥(PC)도 동일하게 Copy하게 된다. 결론적으로 자식은 fork()이후부터 실행하게 된다.
문제점
1.
자식 프로세스가 자신이 원본이라고 주장한다.
2.
부모와 동일한 프로세스가 만들어지니깐 모든 프로그램이 동일한 제어 흐름을 따라야 할 것이다.
복제본을 만들면 fork()함수의 return value로 부모 프로세스인지, 자식 프로세스인지 알 수 있다.
•
부모 프로세스 - 양수(자식 프로세스의 pid)
•
자식 프로세스 - 0
만약 코드가 이렇게 생겼다면?
int main() {
int pid;
print("\n Hello, System Call!");
pid = fork(); // System Call
if (pid == 0)
print("\n Hello, I am child!\n");
else if (pid > 0)
print("\n Hello, I am parent!\n");
}
C
복사
부모 프로세스 | 자식 프로세스 |
print("\n Hello, System Call!"); | print("\n Hello, I am child!\n"); |
fork() 실행 | |
print("\n Hello, I am parent!\n"); |
exec() 시스템 콜 → overlay new image
다른 시스템을 실행하기 위해서 존재한다.
어떤 프로그램을 완전히 새로운 프로세스로 태어나게 하는 역할을 한다.
int main() {
int pid;
print("\n Hello, System Call!");
pid = fork(); // System Call
if (pid == 0) {
print("\n Hello, I am child!\n");
execlp("/bin/date", "/bin/date", (char *) 0);
}
else if (pid > 0)
print("\n Hello, I am parent!\n");
}
C
복사
•
exec() 시스템 콜이 실행되면 전에 실행하던 프로그램은 잊고 완전히 새로운 프로그램으로 태어난다.
◦
프로그램의 시작부분부터 다시 시작하게 된다.
◦
전에 실행되던 프로그램으로 돌아올 수 없다.
꼭 exec()이라는 것이 자식을 만들어야만 가능한 것이 아니다.
int main() {
print("1");
execlp("echo", "echo", "Hello New Process!", (char*) 0);
print("2");
}
C
복사
•
exec() 이후에 있는 print("\n Hello, I am parent!\n"); 는 영원히 실행이 안된다.
wait() 시스템 콜 → sleep until child is done
프로세스를 block 상태로 보낸다. 잠들게 한다.
자식 프로세스를 생성하고 나서 자식 프로세스가 종료되길 기다리면서 blocked 상태가 된다.
자식 프로세스 종료
부모 프로세스가 ready 상태로 바뀌고 CPU를 얻을 수 있게 된다.
ex) 프롬프트에서 프로그램을 실행한 뒤에 해당 프로그램이 종료되길 기다렸다가 종료가 되면 프로그램 입력 커서가 다시 생성됨.
→ 프로그램이 끝날 때까지 부모 프로세스인 프롬프트가 wait()하고 있었다는 걸 알 수 있음.
exit() 시스템 콜 → frees all the resources, notify parent
프로그램을 종료시킬 때 실행하는 시스템 콜이다.
int main() {
int pid;
print("\n Hello, System Call!");
exit()
pid = fork(); // System Call
if (pid == 0)
print("\n Hello, I am child!\n");
else if (pid > 0)
print("\n Hello, I am parent!\n");
}
C
복사
print("\n Hello, System Call!"); 만 실행되고 끝나버린다.
자발적 종료
•
마지막 Statement 수행 후 exit() 시스템 콜을 통해
•
프로그램에 명시적으로 적어주지 않아도 main()가 리턴되는 위치에 컴파일러가 넣어준다.
비자발적 종료(외부에 의해 종료)
•
부모 프로세스가 자식 프로세스를 강제 종료시킨다.
◦
자식 프로세스가 한계치를 넘어서는 자원을 요청했을 경우
◦
자식에게 할당된 Task가 더 이상 필요하지 않을 경우
•
키보드로 kill, break 등을 친 경우
•
부모가 종료하는 경우
◦
부모 프로세스가 종료하기 전에 자식들이 먼저 종료된다.
프로세스 간 협력
[ 프로세스는 굉장히 독립적이다 ]
독립적 프로세스(Independent process)
: 태어난 자식 프로세스는 스스로 알아서 수행해야 한다. 부모 프로세스와 경쟁을 하며 실행되어야 한다.
: 프로세스는 각자의 주소 공간을 가지고 수행되므로 원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못한다.
[ 협력을 해야만 효율적인 실행이 된다 ]
협력 프로세스(Cooperating process)
: 프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있다.
Q. 협력 프로세스에서 말하는 협력이란?
A. 정보를 주고 받으면서 실행을 하는 것 → 이를 위한 메커니즘 존재(IPC)
Interprocess Communication(IPC)
프로세스 간 협력 메커니즘
•
Message Passing : 프로세스 A가 프로세스 B에게 메시지를 전달하는 방법
◦
프로세스는 독립적이기 때문에 자기 메모리 주소 공간만 볼 수 있다.
◦
프로세스가 다른 프로세스에게 메시지를 전달할 방법이 원칙적으로는 없다.
◦
프로세스 사이에 공유 변수를 사용하지 않기 때문에 메시지를 이용해서 통신를 하는 시스템이다.
따라서 커널을 통해서 메시지를 전달한다. → 커널 : 메신저 역할
Direct Communication
: 상대 메시지 프로세스의 이름을 명시적으로 표시
Indirect Communication
: mailbox(또는 port)를 통해 메시지를 간접 전달
•
Shared Memory : 서로 다른 프로세스 간에도 일부 주소 공간을 공유
◦
물리적인 메모리에 매핑할 때 일부 영역은 공유되도록 매핑
▪
프로세스 A가 어떤 내용을 shared memory에 넣으면 프로세스 B도 그 내용을 바로 전달받아서 볼 수 있게 된다.
◦
프로세스끼리 메모리 공간을 공유하기 위해선 커널에게 shared memory를 사용한다는 System Call를 해서 share하게 해두고 그 때부터 사용자 프로세스끼리 영역을 공유할 수 있게 된다.
▪
처음에는 kernel의 도움을 받지만 한 번 도움을 받고 나면 사용자 프로세스들끼리 공유하면 된다.
따라서 신중하게 shared memory 영역을 사용해야 한다. → 두 프로세스가 상당히 신뢰하는 관계
•
thread : thread는 사실상 하나의 프로세스이므로 프로세스 간 협력으로 보기는 어렵지만 동일한 process를 구성하는 thread들간에는 주소 공간을 공유하므로 협력이 가능
프로세스 간의 협력은 아니다. 하지만 thread들끼리의 공유는 쉽다.
Q. thread들끼리 공유가 쉬운 이유?
A. thread들끼리 주소 공간을 완전히 공유하고 있기 때문에