[운영체제] 4주차 과제
1. 운영체제는 프로그램을 적재하여 프로세스를 만든다. 3장 전체를 공부한 결과 프로세스를 만든다는 의미가 무엇인지 나름대로 긴 3줄로 설명하라. 운영체제가 어떤 작업을 하느냐 하는 관점에서 설명하면 된다.
운영체제의 관점에서 프로세스를 만드는 것은 프로세스에게 필요한 메모리를 할당하고 이곳에 코드와 데이터 등을 적재한다. 운영체제는 프로세스 마다 고유한 번호(PID)를 할당하며 프로세스 제어 블록(PCB)을 생성하여 프로세스의 정보를 저장한다. 그리고 프로세스 테이블의 빈 항목에 PID와 함께 PCB를 연결한다.
2.다음과 같이 동적 할당받은 후 반환하지 않고 종료하는 응용프로그램을 작성하였다.
1
2
3
4
5
6
7
int main() {
... // 다양한 지역변수들이 있고 동적 할당 등의 코드가 실행된다.
int n;
... char* p = (char*)malloc(100000);
.... // free(p);
exit(0);
}
(1) 이 프로그램이 실행 중에 malloc(100000)를 호출하면 물리 메모리에 100000 바이트가 할당되는가? 가상 주소 공간에서 할당되는가? 아니면 둘 다 할당되는가?
- 가상 주소 공간, 물리 주소 공간 모두에 할당된다. 매핑 테이블을 통하여 물리 메모리에 액세스할 수 있다. 운영체제는 가상 메모리 기법을 사용하여 당장 프로세스의 실행에 필요한 부분만 물리 메모리에 적재하고 나머지는 디스크의 특정 영역에 저장하여 필요시 가져오는 방법을 사용한다.
(2) 포인터 변수 p의 값이 30000이라고 하면, 30000 번지는 가상 주소인가 물리 주소인가?
- 가상 주소이다.
(3) 만일 malloc(100000)가 메모리가 부족하다고 NULL을 리턴한다면 그것은 물리 메모리 (RAM)가 부족한 것일까? 어떤 상황이 벌어져서 NULL을 리턴하는 것인지 설명하라.
- 가상 주소 공간이 부족한 것이다. 프로그램은 실행될 때 사용자 공간에 프로그램을 적재하여 실행하는데 이는 가상 주소 공간이며 가상 주소는 매핑 테이블을 통해 물리 메모리에 액세스할 수 있다. 여러 프로세스를 실행하여 물리 메모리가 부족해지는 것을 해결하기 위해 가상 메모리 기법을 통해 어느정도 극복이 가능하다. 하지만 프로그램마다 할당되는 가상 주소 공간을 넘어서는 경우 이는 커널 공간이며 더 사용할 수 있는 사용자 주소 공간이 없기 때문에 NULL이 리턴된다.
(4) 이 프로그램이 종료할 때, 100000 바이트의 메모리를 반환하지 않았다. 그러면 100000바이트의 메모리를 다른 응용프로그램이 사용하지 못하게 되는 ‘메모리 누수’가 발생하는가? 답에 대한 이유도 함께 설명하라.
- 발생하지 않는다. 프로세스가 동적 할당받은 메모리를 delete나 free 이용하여 반환하지 않고 종료한다고 해도. 운영체제가 프로세스에게 할당한 힙을 회수하므로, 시스템에 메모리 누수가 생기지 않는다
3. 프로세스의 상태 중에서
(1) 프로세스가 파일을 읽는 코드를 실행하면 프로세스는 어떤 상태로 바뀌게 되는가?
- Blocked
(2) 프로세스의 상태 중에서 스케줄링 대상이 되는 상태는 무엇인가?
- Ready
(3) 프로세스가 현재 CPU에 의해 실행중인 상태에서 Ready 상태로 바뀌는 것은 어떤 경우인가?
- Running 상태에서 프로세스에게 할당된 CPU 시간이 경과되거나 스스로 다른 프로세스에게 CPU를 양보할 때
- 입출력 장치나 저장 장치로부터 요청 작업이 완료되었을 때이다.
(4) 프로세스의 상태는 바꾸는 작업은 언제 일어나는가? 다음 여러 가지 가능성 중에서 어떤 것인지 설명해보라. 1가지, 2가지, 모두 다, 혹은 모두 아닐 수 있다. ①시스템 호출이 실행되는 도중에? ②아니면 응용프로그램의 실행 중에 응용프로그램의 코드 에서? ③아니면 인터럽트가 실행되는 도중에? ④ 앞의 모든 경우가 아니다.
- ① 시스템 호출이 실행되는 도중, ③ 인터럽트 실행 도중이다. 프로세스의 상태 변경은 커널의 스케줄링 함수를 통해 수행된다. 응용 프로그램의 프로세스가 시스템 호출을 통해 커널 함수를 실행하던 중 I/O 장치로부터 데이터를 기다리게 되면, 커널 함수는 현재 프로세스의 실행을 중단시키고 대기 중인 프로세스 중 하나를 선택하는 스케줄링 함수를 호출한다. 혹은 타이머로 부터 발생한 ISR은 현재 프로세스에게 할당된 타임 슬라이스가 다 된 경우 다른 프로세스를 선택하기 위해서 스케줄링 코드를 호출하기도 한다. 따라서 시스템 호출 및 인터럽트 시 스케줄링 함수를 통해 프로세스의 상태가 변경된다고 볼 수 있다. 프로세스가 종료되면 exit() 시세스템 호출이 발생하고 wait() 시스템 호출을 통해 종료 코드를 읽고 스케줄링 함수로 상태가 변경된다.
4. 운영체제 커널이 만드는 것으로 프로세스의 정보를 저장하는 구조체를 PCB라고 부른다. 어떤 정보들이 저장되는지 간단히 나열해보라.
- 프로세스 번호 (PID)
- 부모 프로세스 번호 (PPID)
- 프로세스 상태
- 프로세스 컨텍스트 (PC, SP 등 CPU 레지스터들)
- 스케줄링 정보 (priority, nice 값 등)
- 프로세스의 종료 코드
- 프로세스의 오픈 파일 테이블
- 메모리 관리를 위한 정보들 (페이지 테이블 주소 등)
- 회계 정보
- 프로세스 소유자 이름
- 기타 프로세스가 사용중인 입출력 장치 목록 등
5. 종료 코드와 exit(), wait() 시스템 호출에 관해
(1) 종료 코드란 무엇인가?
- 종료코드는 프로세스가 종료할 때, 종료 이유를 부모 프로세스에게 전달하기 위한 정수 값으로, 종료한 프로세스의 PCB에 저장된다.
(2) 제대로 작성된 부모 프로세스라면 wait() 시스템 호출을 해야 한다. 이유는 무엇인가?
- 부모가 자식 프로세스가 종료할 때까지 기다려 자식 프로세스의 종료를 확인하기 위해 wait()를 호출한다. wait() 시스템 호출 내에서 자식 프로세스의 PCB에 남겨진 종류.근드(exit code)를 읽고 자식 프로세스를 완전히 제거한다.
(3) 프로세스가 종료할 때, exit() 시스템 호출을 반드시 실행해야 하는 이유는 무엇인지 조금 구체적으로 설명하라?
- 시스템이 정상적으로 종료되기 위해서는 반드시 exit() 시스템 호출이 실행되어야 한다. 이는 exit()을 호출한 프로세스에게 할당된 코드, 데이터, 힙, 스택 등 모든 메모리 자원을 반환하고, 열어놓은 파일이나 스켓 등을 닫는다. 또 PCB와 프로세스 테이블의 항목은 그대로 두지만 PCB내에 프로세스 상태를 Terminated/Zombie로 바꾸고 종료코드를 PCB에 저장한다. 이후 현재 프로세스의 모든 자식 프로세스를 init 프로세스에 입양 시키고 부모 프로세스에게 죽음을 알리기 위해 SIGCHLD 신호를 보내기 때문에 매우 중요한 역할을 맡고 있다.
6, 좀비프로세스에 대해
(1) 좀비프로세스의 정의는 무엇인가?
- 종료하였지만, 부모가 종료코드를 읽지 않는 상태로 시스템에 남아 있을 때, 이 프로세스를 좀비 프로세스라고 한다.
(2) 좀비프로세스가 발생하는 원인은 무엇인가?
- 자식 프로세스가 exit() 시스템 호출을 실행하면, exit()함수는 부모 프로세서에게 자식의 죽음을 알리는데, 이때 부모가 wait() 시스템 호출을 부르도록 작성되어 있지 않다면 자식 프로세스는 좀비 상태로 계속 남게 된다.
(2) 좀비프로세스는 시스템에 해를 끼치는가?
- 직접적인 영향을 끼치지는 않는다. PCB의 크기는 그렇게 크지 않기 때문에 메모리 부족 문제를 일으키지 않고, 좀비 프로세서는 스케줄링에서 배제되기 때문에 CPU 시간을 소모하지 않으며 다른 프로세스의 실행을 방해하지도 않지만, 시스템에는 1개의 프로세스 테이블이 있고 크기도 제한되어 있기 때문에 좀비 프로세스가 많이 존재한다면 새로운 프로세스를 생성할 수 없는 일이 발생할 수도 있다.
7. 리눅스에서 탐구 3-2를 작성하여 실행해보라. 그러면 pinfo의 부모프로세스 번호가 출력된다. 리눅스에서 ps –eal 명령을 실행하여 pinfo의 부모가 누구인지 찾아서 보여라. 프로그램 의 실행 결과와 ps –eal 명령의 실행 결과를 첨부하라.
8. 복합문제 4번의 소스 코드에 대해, 실행과정에서 사용자 주소 공간의 변화를 알아보려 한 다. 탐구 3-1을 참고하여 아래 4개의 문항을 그림으로 채워라.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int a[100];
int main() {
int b=1;
f(2);
return 0;
}
void f(int c) {
int d=3;
g();
printf("%d", c);
}
void g() {
int * p = (int *)malloc(100);
}
(1) main()에서 함수 f()를 호출하기 직전 의 사용자 주소 공간 을 그려라. | (2) f() 함수에서 g() 를 호출하기 직전의 사용자 주소 공간을 그려라 | (3) g() 함수가 리턴 하기 직전의 사용자 주소 공간을 그려라. | (4) f() 함수로부터 리턴한 직후 사용자 주소 공간을 그려라. | |
---|---|---|---|---|
Code | main() 함수 코드, f() 함수 코드, g() 함수코드 malloc()함수 코드 | 동일 | 동일 | 동일 |
Data | a[0] ~ a[99] | 동일 | 동일 | 동일 |
Heap | - | - | 100 바이트 | 100바이트 |
Stack | b = | 1 | | d = | 3 | c = | 2 | b = | 1 | | p | 힙 영역의 가상 주소 | d = | 3 | c = | 2 | b = | 1 | | b = | 1 | |
9. 탐구 3-7을 작성하여 실행해보고, 실행되는 과정을 fork()와 execlp() 그리고 wait()가 실 행되는 상황을 나름대로 그림으로 그리면서 설명하라. 실행한 코드와 실행 화면도 캡쳐하여 제출하라.
child.c
1
2
3
4
5
#include <stdio.h>
int main() {
printf("I am a child\n");
return 100;
}
waitex.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid;
int status;
pid = fork();
if(pid > 0) {
printf("부모프로세스: 자식의 종료를 기다림\n");
wait(&status);
printf("부모프로세스: child의 종료 코드=%d \n", WEXITSTATUS(status));
return 0;
}
else if(pid == 0) {
execlp("./child", "child", NULL);
}
else {
printf("fork 오류");
return 0;
}
}
10. fork()를 이용하여 부모와 자식이 각각 동시에 실행되는 멀티태스킹 프로그램 multi.c를 작성하라. 이 멀티태스킹 프로그램은 다음과 같이 작동한다. 부모 프로세스는 fork()를 사용하여 자식프로세스를 생성한다. 자식 프로세스는 1에서 10까지 홀수 합을 계산하고 이 값을 종료 코드로 리턴한다. 부모 프로세스는 자식 프로세스를 생성 한 후 1에서 10까지 짝수 합을 구하고 자식 프로세스가 종료할 때까지 기다려 자식의 종료 코드와 합쳐 1에서 10까지의 합을 출력한다. multi.c를 작성할 때, 부모와 자식을 동시에 실행시키는 멀티태스킹을 목표로 한다는 점을 잊지 말고 프로그램을 작성하라. 소스 코드에 주석을 달고, 소스 코드와 컴파일, 실행 과정을 모두 캡쳐하여 제출하라.
child.c
1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
int sum = 0;
for(int i = 1 ; i <= 10 ; i ++)
if(i % 2 != 0) // 홀수 판별
sum += i;
return sum; // 반환
}
multi.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid;
int status;
pid = fork(); // 자식프로세스 생성
if(pid > 0) { // 부모 프로세스 코드
int sum = 0;
for(int i = 1 ; i <= 10 ; i++)
if(i % 2 == 0) // 짝수 판별
sum += i;
wait(&status); // 자식프로세스 종료 대기. status에 종료 상태 받음
printf("결과 = %d\n", sum + WEXITSTATUS(status)); // sum값과 자식 프로세스에게 받은 값의 하위 8비트 값을 더하여 출력
return 0;
}
else if(pid == 0) {
execlp("./child", "child", NULL); // child를 자식프로세스로 실행
}
else { // fork() 오류
printf("fork 오류");
return 0;
}
}
This post is licensed under CC BY 4.0 by the author.