- 프로세스 : 프로그램을 실행하면 객체화, 실체화되는 것
- 프로그램 : .exe 파일로 존재하며, 해당하는 클래스를 malloc을 통해 만들어진 것이 프로세스이다.
- 프로그램을 깔면 디스크로 간다.
- 프로그램을 실행시키면 인스턴스화 되어서 로딩이 되면 메모리를 차지한다. (프로그램은 메모리를 잡아먹지 않는다)
- 같은 프로그램을 여러번 시키면 별도의 프로세스가 작동한다. 섹션이 같을지라도 Data, Heap, Stack은 다를 수 있다.
- 프로세스의 구성 (4가지 Address Space)
- Program Counter
- Data Section : containing global variable
- Code
- Data
- Stack : containg temporary data
- Heap : containg memory dynamically allocated during run time
- 프로세스의 상태 (5가지 transition)
- New : 프로세스가 만들어짐
- Ready : 프로세스가 실행할 준비가 됨 = 프로세스가 CPU에 할당되기를 기다리는 상태
- Running : 스케줄러가 Ready 상태 중 하나의 프로세스를 CPU에 넣고 돌린다.
- Waiting : interrupt가 오면 Running 상태의 프로세스를 Waiting 상태로 바꾼다.
- Terminated : 프로세스의 실행이 종료된 상태
- I/O event wait : 프로세스가 입출력 처리를 해야하는 상황일 때 Running 상태에서 Waiting 상태로 바뀐다.
스케줄러에 의해 선택될 수 없다. Ready 상태로 가야한다. - I/O event completion : 입출력이 끝난 프로세스는 Waiting에서 Ready 상태로 바뀐다.
스케줄러에게 선택될 수 있다.
- 프로세스의 실행
- fork()
- 프로세스가 하나 더 생긴다.
- 새로운 프로세스를 위한 메모리 할당
- pid_t = 프로세스의 번호를 저장하는 타입
- 자식프로세스와 부모프로세스의 순서는 스케줄러가 결정한다.
- exec() : 생성되는 프로세스는 없다.
- 메모리를 할당하지 않고 exec()에 의해 호출된 프로세스만 메모리에 남는다.
- 호출된 프로세스는 exec()를 호출한 프로세스의 PID가 그대로 적용된다.
- 새로운 프로세스에 의해 덮어 쓰여지게 된다.
- exit() : 프로세스가 끝나면 exit()하면서 종료된다.
- wait() : 프로세스가 종료되면 반환값이 나오는데 parent가 wait()로 받아간다.
- fork()
- 프로세스의 종료
- Voluntary (자발적으로 죽음)
- Involuntary (비자발적으로 죽음)
- Jombie Process : 자식이 죽었는데 부모가 wait()를 안하고 있음, 한없이 대기타는 프로세스
부모가 wait() 해주면 좀비프로세스는 사라진다. - Orphan Process : 부모가 죽은 프로세스
- Cascading termination : 부모프로세스가 죽으면 자식프로세스 트리를 다 날려버린다.
- Reparenting : 최초의 프로세스인 init 에 입양 보낸다, 붙여준다.
- PCB(= Process Control Block) : 프로세스 제어 블록
- 프로세스의 모든 정보를 갖고 있다.
- 하나의 프로세스는 하나의 PCB를 갖는다.
- 스케줄러가 프로세스의 ready 상태의 프로세스의 PCB를 보고 최적인 애들을 실행시킨다.
실질적으로 운영체제가 무얼 하는지 배워보자
프로그램과 프로세스를 구분해야한다.
프로세스는 우리가 깔아놓은 프로그램이 실행을 하면서 실제 객체화(인스턴스화)된 것이다.
우리가 인터넷에서 다운받아서 설치돼서 깔려있는 것이 프로그램이다.
프로그램은 안변한다. 움직이지 않는다. 실행파일로 존재한다.
탐색기로 띄워서 더블클릭해서 프로그램을 실행시키면 실행돼서 만들어지는게 프로세스이다.
해당하는 클래스를 만들어서 malloc을 해서 만들어진 것이 process이다.
하나의 프로그램을 여러 개의 인스턴스로 객체화 시킬 수 있다.
프로그램을 인스턴스화 해서 만들어진 것이 프로세스이다.
QnA : 프로그램을 깔면 디스크로 가고 실행을 시키면 인스턴스화 되어서 로딩이 되면 메모리를 잡아 먹는다.
그래서 프로그램을 깐다고 해도 메모리를 잡아 먹진 않는다.
프로세스는 각자 자신의 address space가 따로 있다.
heap 에서부터 할당이 된다.
프로그램이 있을 때 실제로 이 프로그램들이 네가지 섹션중 어디로 가는지 생각해보면
이 프로그램을 빌드하면
코드는 Code로 간다.
int x; int y; 는 Data 로 간다.
main을 시작하면 function parameter들은 다 Stack으로 간다.
malloc을 호출하면 40이라는 값이 들어간다.
malloc이 동적인 메모리에서 allocation한거인데 values 를 만든다
i를 초기화되고 value가 가지는 1,2,3,4,5 하나씩 실행이 된다.
섹션이 4개가 되고 필요한 것으로 가서 동적으로 돌아간다.
우리들이 짠 컴퓨터 프로그램은 컴파일을 해서 실행파일을 만든다.
실행파일은 코드와 데이터만 있고 우리가 실제로 실행하려면
메모리로 로딩해서 인스턴스가 만들어지는데 이게 프로세스이고
프로세스는 address space에 따로 분배한다.
프로세스가 만들어지면 밑도 끝도 없이 도는게 아니다.
프로세스는 보통 만들어지면 여러가지 상태를 거쳐가면서 조금씩 조금씩 바뀐다.
여러가지 state가 바뀌어 가면서 동작이 된다.
5가지 state가 있다.
처음에 프로세스가 만들어지면 new에서부터 시작된다.
만들고 초기화도 하고
프로세스가 실행을 할 준비가 완료가 되면 ready 상태로 간다.
익스플로러에서 ppt를 띄우면 ,운영체제한테 파워포인터 프로세서 만들어달라고 요청하면
운영체제가 파워포인트 프로세서를 new로 만들고 ready상태로 만든다.
이 상태에서 운영체제는 컴퓨터에 돌 준비가 되어있는 ready상태 중 하나를 뽑아서 CPU에 넣고 돌린다.
이런 역할을 하는 것이 스케줄러 이다.
ready상태에서 적당한 애를 하나 뽑아서 CPU에 넣고 돌리기 시작한다.
파워포인터를 빼서 running 상태로 만든다. 익스플로러는 ready상태에 있다.
돌리다가 컴퓨터에 인터럽트가 발생하면 현재 돌고있는 프로세서는 ready 상태로 다시 빠진다.
이런게 계속 반복한다.
CPU입장에서는 running 상태에서 파일을 기다리고 있는 것을 비효율적이라고 생각한다.
디스크에서 데이터를 읽던지 뭔가 event를 통해서 기다리는 상태가 오면
running 상태로 있지 않고 waiting 상태로 간다.
그림을 다 읽으면 running -> ready
waiting -> running
↓정리
************************************************************************************************************************
new 단계 : 프로세스가 생성되는 단계
-> 프로그램을 실행시켰을 때의 첫 번째 상태가 됩니다.
************************************************************************************************************************
준비(ready)단계 : 프로세스가 프로세서에 할당되기를 기다리고 있는 상태
실행(running)상태 : 프로세스가 실행중인 상태
대기(waiting)상태 : 프로세스가 어떤 사건(event)을 기다리고 있는 상태
************************************************************************************************************************
terminated상태 : 프로세스의 실행이 종료된 상태
************************************************************************************************************************
디스패치(dispatch) : 준비(ready)상태에서 실행(running)상태로 전이되는 과정을 말하며, 이는 작업 스케줄러가 해당 프로세스를 선택하여 실행되어지는 것으로 이때 실행된 프로세스가 CPU를 점유하게 됩니다.
************************************************************************************************************************
인터럽트(Interrupt) : 인터럽트 신호를 받게되면, 실행(running)중이던 프로세스는 준비(ready)상태로 전이되고, 우선순위(Priority)가 높은 프로세스를 실행(running)상태로 전이시키게 됩니다. (프로세스는 각각 우선순위를 부여받고, 우선순위에 따라 프로세스가 준비상태로 전이되거나, 실행상태로 전이됩니다.)
************************************************************************************************************************
입출력 혹은 이벤트대기(I/O or event wait) : CPU를 점유하고 있는 프로세스가 입출력 처리를 해야만 하는 상황이라면, 실행되고 있는 프로세스는 실행(running)상태에서 대기/보류(waiting) 상태로 바뀌게 됩니다. 그리고 대기상태로 바뀐 프로세스는 입출력 처리가 모두 끝날때까지 대기상태로 머물게 됩니다. 그리고 실행 상태(running)이던 프로세스가 대기상태(waiting)로 전이됨과 함께, 준비상태(ready)이던 또 다른 프로세스가 실행 상태(running)로 전이됩니다. 또한 대기 상태인 프로세스는 우선순위가 부여되지 않으며 스케줄러에 의해 선택 될 수 없습니다.
************************************************************************************************************************
입출력 혹은 이벤트완료(I/O or event completion) : 입출력 처리가 끝난 프로세스는 대기상태(waiting)에서 준비상태(ready)로 전이되어 스케줄러에게 선택될 수 있게 됩니다. 추가로 프로세스를 종료(Terminate)시킬 때에도 Blocked 상태를 거칠 수 있습니다.
************************************************************************************************************************
프로세스가 있으면 운영체제한테 만들어달라고 하면
children이 만들어져서 트리형태로 붙는다.
init이라는 프로세서가 거의 처음으로 창조된다.
그래서 착착착 만들어져서 초기화를 다 시킨다.
최상위 프로세스가 하나 있으면서 트리형태로 분화되면서 나아가서 컴퓨터가 구동이 된다.
프로세스가 어떤 원하는 프로그램으로 동작하려면
fork : 어떤 프로세스가 자기와 동일한 다른 시스템을 만드는 것
하나가 두개로 쪼개진다.
하나의 프로세스가 있는데
프로세스가 진행을 하다가 fork() 를 부르면 새로운 애를 하나 만들어서 분기한다.
fork() 라는 시스템 콜은 새로운 시스템을 만드는데 현재 시스템을 클로닝 해서 만든다.
그러다가 어떤 프로세스가 있다면 그 프로세스의 address space가 있는데
이 프로세스의 address space와 똑같은 address space를 복붙해서 만드는 것이다.
parent를 복붙해서 chidren을 만든다.
이것이 바로 children process이다.
system call 이름이 fork() 이다.
pid_t 에 대한 설명
********************************************************************************************************************************
프로세스 번호(pid)를 저장하는 타입(t)이라는 의미입니다.
fork(); = 자식 프로세스 생성
시스템마다 프로세스번호가 int일 수도 있고 아닐 수도 있기 때문에
pid_t를 사용하는 것이 이식성면에서 더 낳은 코드가 될 것 같네요.
리눅스도 미래에는 long형을 프로세스 번호로 사용하게 될 지도 모르죠
pid_t 는 2~32768까지의 범위를 갖습니다. 0번 프로세스는 부팅 후 바로 사라지고 1번은 모든 프로세스의 부모 프로세스인 init프로세스입니다. 그 다음은 여러가지 데몬 프로그램들이 번호를 차지하겠죠... 글구나서 우리가 사용하는 프로그램들의 프로세스 번호가 매겨지는 것입니다. 32768개 이상은 프로세스 생성이 불가능 한 걸루 알고 있습니다.
fork()함수를 호출하고 성공하면 pid 변수값만 다른 완전히 똑같은 프로세스가 생성됩니다.
그러므로 지금 프로그램이 원래 실행되었던 부모 프로세스인지, 아니면 새로 생성된 자식 프로세스인지는 fork()에서 반환한 값으로 확인합니다.
#include <stdio.h>
#include <unistd.h>
int main()
{
int counter = 0;
pid_t pid;
printf( "작식 프로세스 생성");
pid = fork();
switch( pid)
{
case -1 :
{
printf( "자식 프로세스 생성 실패\n");
return -1;
}
case 0 :
{
printf( "저는 자식 프로세스로 디스카운트하겠습니다.\n");
while( 1 )
{
printf( "자식: %d\n", counter--);
sleep( 1);
}
}
default :
{
printf( "저는 부모 프로세스로 카운트하겠습니다.\n");
printf( "자식 프로세스의 pid는 %d입니다.\n", pid);
while( 1 )
{
printf( "부모: %d\n", counter++);
sleep( 1);
}
}
}
}
********************************************************************************************************************************
Process A와 똑같은 Process B를 만든다.
A는 B의 parent가 되는 것이다.
프로세스가 fork(); 라는 것을 만들면 똑같은 것을 만든다.
이걸 동작을 딱 시키면
p id = 100 부터 시작해서
자기가 실행했던 곳부터 fork가 시작되는 것이다. 처음부터 만들어지는 것이 아니다.
이 프로그램을 실행시키면 hello 가 8번 출력이 된다.
child 에서 fork()의 리턴값을 보면 0이 나오고
parent에서 fork()의 리턴값을 보면 프로세스 아이디가 리턴된다.
********************************************************************************************************************************
fork()를 이용하여 부모 프로세스 1개에 자식 프로세스 5개를 생성하는 코드입니다.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
pid_t pid; //pid_t 선언
pid = fork(); //fork 발생
if(pid == -1) { //-1 이면 fork생성 에러
printf("can't fork, erro\n");
exit(0);
}
if(pid == 0) { //0이면 자식 프로세스
int j;
for(j = 0; j < 10; j++) {
printf("child: %d\n", j);
sleep(1);
}
exit(0);
} else { //부모프로세스
int i;
for(i = 0; i < 10; i++) {
printf("parent : %d\n", i);
sleep(1);
}
exit(0);
}
return 0;
}
간단한 fork예제 (wiki 참고)
그전에 fork의 기본 예제부터 살펴보겠습니다. wiki에 올라와 있고, 이미 많은 곧에서 배포되는 간단한 코드입니다.
아래와 같이 부모와 자식이 각각 구동되고 있습니다. 각각 0에서 9까지 출력하는 프로그램입니다.
실행중인 프로세스 확인
ps명령어로 실행되는 프로세스를 확인 할 수 있습니다.
********************************************************************************************************************************
fork 를 부른 시점에서 쪼개져서 시작하는데
parent는 차일드의 pi를 받고
.
.
.
서로 알아낼 수 있는 방법이 있다.
이 프로그램 실행시키면 x=1
이 값을 공유하는게 아니라 주소공간의 스택에 있었을 텐데
같은 값을 가지고 시작했는데 복사본이 생겼고
갈라진 이후로는 각자 자신의 address space를 가지고 동작을 하게 된다.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int x = 3;
int main(int argc, const char *argv[])
{
while(x--){
fork();
printf("%d\n",x);
}
return 0;
}
컴파일 결과
2
2
1
1
1
1
0
0
0
0
0
0
0
0
글로벌 변수는 데이터로 간다.
fork()를 부를 때마다 복사돼서 쪼개질 것이다.
이걸 돌려보면 처음에 3으로 들어와서 2로 감소한 상태로 쪼개지고
2에서 포크로 1로 만든 다음에 쪼개지고
또 0으로 만든 다음 출력되거나 한다.
트리 처럼 그려가면서 트래킹 해보거라.
fork로 새로운 걸 만들었다.
현재 돌고 있는 프로그램의 address program에
현재 돌고 있는 애를 내가 요청하는 애로 싹 갈아치우는게 exec() 라는 system call이다.
p 계열들은 argument로 들어간 것을 찾아보면서 하시오.
e : 환경변수는 다 가지고 갈 것이냐
execvp() : 벡터형태로 받아서 실행파일
Process Creation
exec(vim) 치면
child 는 pid=0 으로 받아서 진행하고 부모의 인생으로 시작했지만
내 인생을 시작하고 싶다
exec() 치면
자신의 인생을 살기 시작한다.
bash shell로 만들어졌는데 다식을 vim으로 살거야
exec("vim");
부모프로세스와 자식프로세스는 남남이다. 아예 다른 프로세스로 나눠진다.
누가 먼저 돌지는 프로세스가 결정한다.
binary라는 것은 프로세스가 어떤 프로그램으로부터 만들어졌냐이다.
Binary : 실행파일 //별로 안중요
0보다 작으면 죽고 만들면 실행.
사용자한테 입력을 하나 받는다.
argv
그걸 가지고 새로운 프로세스 하나 만들고
기다리게하고
아닌 애들을 실행시키는 것
execv가 실행이 되면
새로 시작이 되는
<0 이면 command not found이고
>0 이면 나가서 실행이 되므로
When thie line 줄은 나올 수가 없다.
Process Termination
프로세스가 끝났다라는 사실도 운영체제 입장에서도 중요한 일이다.
띡 죽는게 아니라 나 죽을래, 운영체제에 요청해서 죽게되면 ( exit()를 하면 죽는다.)
내가 죽는것 뿐 아니라 쟤 죽여 할 수 있는 시스템콜도 있다. abort() kill()
이 슬라이드에서 중요한 것은
자기가 죽던지 다른 누구를 찍어서 죽이는 것이 가능하다 는 것.
죽는 것도 과정이 있다.
프로세스도 죽으면서 자기의 최종 실행 결과값을 만든다. 유서같은 것
뭐가 리턴되냐면 return 값이다. return 0;
운영체제가 parent한테 전달한다.
parent 프로세스도 누군가 죽었으면 받아줘야지 하는 시스템콜이 있다 : wait
자기 자식중에 누군가 죽으면 걔가 만들어낸 리턴값이 시스템콜을 통해서 반환하는 것이다.
exit() 종료됨 -> 반환값 -> parent가 wait로 받아간다.
parent -> fork()로 자식 만들고 자식은 exec()로 자기 인생 산다 -> 인생 끝나면 exit()로 종료 -> 부모가 wait()라는 시스템으로 받아오고
waitpid : 누간가 자식에게 프로세스 아이디를 주고
이 프로세스가 죽으면 반환이되고 다른애가 죽으면 반환 안 됨
-1 을 주면 누구든 죽으면 반환해 주시오
과제 1에 wait시스템 콜을 써야한다.
man 페이지에서 봐라.
자발적으로 죽는 경우, 비자발적으로 죽는 경우
태초의 프로세스가 있다
fork() → exec() → exit() → wait()
세상이 원하는대로 돌아가지 않는것처럼
몇몇 이상한 케이스들이 발생한다.
원래는 child 프로세스가 죽으면 부모는 반환된 값을 가져가야 되는게 도리다.
자식이 죽었는데 부모가 wait를 안하고 있다.
그럼 자식 프로세스는 죽어가고 있고 반납을 하고 완전히 없어져야 하는데 죽지를 못하고 있다.
죽지않는 상태로 남아있게 된다.
부모프로세스가 wait를 안 부르고 있다면 한 없이 대기 타야하는 상황이다.
이걸 Jombie process 라고한다.
부모가 wait를 불러주게 되면 좀비는 사라진다.
wait를 언제 부르냐에 따라서 어떤 프로세스는 좀비상태로 남아있는다.
Orphan process
부모가 wait 불러서 처리하기로 했는데 부모프로세스가 죽어버렸다. 가버렸다.
그럼 자식프로세스는 어떡해?
그런 프로세스를 Orphan process라고 한다.
고아가 되면 문제가 뭐냐면
누가죽으면 자식까지 다 죽는 것이다.
부모가 죽으면 child process tree를 싹 날려버리는 프로그램이 있다.
cascading termination이다.
또한 다른프로세스에 입양을 보내는 방법이 있다.
reparenting 이다.
리눅스는 reparenting을
모든 프로세스의 최초의 프로세스인 init 프로세스에 붙여준다.
ps의 부모도 init이 되고 emacs의 부모도 init이 된다.
QnA : fork함수를 호출하여 만들어진 자식 프로세스와 부모 프로세스의 실행순서가 무작위인 이유는 무엇인가요?
운영체제에서는 부모자식간의 관계를 고려해서 누구를 진행시키냐는 결정하는 것은 스케줄러이다.
그 순서는 무작위는 아니고 스케줄러가 어떻게 하느냐에 다르다.
QnA : init이 죽으면? 죽으면 컴퓨터가 꺼진것이거나 망한것이다.
프로세스를 어떻게 구현하느냐
A라는 프로세스의 모든 걸 가지고있는 PCB가 있다. = Process Control Block
하나의 프로세스는 하나의 PCB 블록을 가지고 있다.
process ID, parent process ID, CPU register 이런 모든 정보를 다 들어가있다.
프로세스마다 PCB 하나씩 있다.
스케줄러 : 프로세스 상태를 보고 ready인 애들 뽑는다.
PCB들이 연결리스트로 연결되어있다.
스캔하면서 ready인 애들 보면서 최적인 PCB를 찾아서 실행을 한다.
프로세스를 잘 이해해야 뒤에 나오는 스레드를 알 수 있다.
요약
Program & Process
process ID = PID
4가지 Address space : Code, Data, Heap, Stack
5개의 transition : new, ready, running ,waiting, terminated
프로세스는 init 프로세스를 하나 만들고 자식관계로 tree형태로 만든다.
자식을 새로 만들 땐 똑같은 애를 만들어서 만든다. 이게 fork()다.
exec() : 나의 인생을 살겠다, 원래 있던 address space는 다 날아간다.
태어나면 죽어야한다 exit()
자발적으로, 비자발적으로도 죽을 수 있다. Voluntary, Involuntary
희생을 부모에게 알려줘야한다.
부모는 wait()로 반납받는다.
자식 중 Jombie process와 Orphan process가 있다.
구현하는 것은 PCB를 통해서 한다.
'🚦 Server > Operating System' 카테고리의 다른 글
07. Threads (1) (0) | 2020.04.21 |
---|---|
06. Inter-Process Communication (0) | 2020.04.14 |
04. Operating System Structures (2) (0) | 2020.03.30 |
03. Operating System Structures (1) (0) | 2020.03.25 |
02. System Call (0) | 2020.03.23 |