프로세스 관리(process Management)에 대해 알아보자
Process Management
Process Creation
프로세스 생성: fork(2)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
- 프로세스 생성: fork(2)
- 새로운 프로세스를 생성 : 자식 프로세스
- fork 함수를 호출한 프로세스 : 부모 프로세스
- 자식 프로세스는 부모 프로세스의 메모리 주소 공간을 그대로 복사
- 메모리 공간이 똑같이 복제됨에 따라, 아래 자원들도 그대로 복사됨
- RUID, EUID, RGID, EGID, 환경변수
- 열린 파일기술자, 시그널 처리, setuid, setgid
- 현재 작업 디렉토리, umask, 사용가능자원 제한
- 부모 프로세스와 다른 점
- 자식 프로세스는 유일한 PID를 갖는다
- 자식 프로세스는 유일한 PPID를 갖는다. (Parent PID)
- 프로세스 잠금, 파일 잠금, 기타 메모리 잠금은 상속 안함
- 자식 프로세스의 tms구조체 값은 0으로 설정 (수행 시간 관련 구조체)
- 부모 프로세스와 자식 프로세스는 열린 파일을 공유하므로 읽거나 쓸 때 주의해야 한다. (동기화 문제 발생 가능)
프로세스 종료: exit(2)
#include <stdlib.h>
void exit(int status);
- 프로세스 종료: exit(2)
- status : 종료 상태값
- 부모 프로세스가 이 상태값을 보고, 0이면 정상, 그 외 다른 값이면 비정상 종료되었다고 판단함.
Main()
함수에서 return 하는 것은exit()
와 동일한 효과exit()
는 문맥 어디에서든 수행하여 프로세스를 종료할 수 있음
- status : 종료 상태값
#include <stdlib.h>
int atexit(void (*func)(void));
프로세스 종료시 수행할 작업 등록: atexit(2)
- func : 종료 시, 수행할 작업을 지정한 함수명
- 등록한 함수들이 등록 순서의 역순으로 수행됨
Main()
에서 return 할 때도 수행됨
프로그램 종료 함수의 일반적 종료 절차 0. atexit() 로 등록된 함수들을 수행한다.
- 모든 파일 기술자를 닫는다.
- 부모 프로세스에 종료 상태를 알린다.
- 자식 프로세스들에 SIGHUP 시그널을 보낸다.
- 부모 프로세스에 SIGCHLD 시그널을 보낸다.
- 프로세스간 통신에 사용한 자원을 반납한다.
Program Execution
다른 프로그램을 실행시키려면? system(3)
#include <stdlib.h>
int system(const char *string);
- 프로그램 실행 : system(3)
- 새로운 프로그램을 실행하는 가장 간단한 방법
- 실행할 프로그램명을 인자로 지정
- 콘솔에 직접 입력하는 것과 마찬가지 효과를 냄
- 예)
system(“ls -al”)
;
- 위험하고 비효율적이므로 남용하지 말 것
- 환경 변수 해킹 등에 의해 전혀 다른 명령이 수행되어 보안 상 위험할 수 있음
- 관련 설명
환경변수의 이해
- 환경변수
- 프로세스가 실행되는 기본 환경을 설정하는 변수
- 로그인명, 로그인 쉘, 터미널에 설정된 언어, 경로명 등
- 환경변수는 “환경변수=값”의 형태로 구성되며 관례적으로 대문자로 사용
- 현재 쉘의 환경 설정을 보려면 env 명령을 사용
# env
_=/usr/bin/env
LANG=ko
HZ=100
PATH=/usr/sbin:/usr/bin:/usr/local/bin:.
LOGNAME=jw
MAIL=/usr/mail/jw
SHELL=/bin/ksh
HOME=/export/home/jw
TERM=ansi
PWD=/export/home/jw/syspro/ch5
TZ=ROK
...
다른 프로그램의 실행: exec(3) series
- exec 함수군
- exec로 시작하는 함수들로, 명령이나 실행 파일을 실행할 수 있다.
- exec 함수가 실행되면 프로세스의 메모리 이미지는 해당 실행파일로 바뀐다.
#include <unistd.h>
int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
int execvp(const char *file, char *const argv[]);
- Exec 함수군의 형태 6가지
- Path or file : 실행할 명령의 파일 경로 지정
- arg#, argv : main 함수에 전달할 인자 지정
- envp : main 함수에 전달할 환경변수 지정 * 함수의 형태에 따라 NULL 값 지정에 유의!
Execlp() 함수 사용하기
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
printf("--> Before exec function\n");
if (execlp("ls", "ls", "-a", (char *)NULL) == -1) {
perror("execlp");
exit(1);
}
printf("--> After exec function\n");
return 0;
Execv() 함수 사용하기
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
char *argv[3];
printf("Before exec function\n");
argv[0] = "ls";
argv[1] = "-a";
argv[2] = NULL;
if (execv("/usr/bin/ls", argv) == -1) {
perror("execv");
exit(1);
}
printf("After exec function\n");
return 0;
}
Execve() 함수 사용하기
int main(void) {
char *argv[3];
char *envp[2];
printf("Before exec function\n");
argv[0] = "arg.out";
argv[1] = "100";
argv[2] = NULL;
envp[0] = "MYENV=hanbit";
envp[1] = NULL;
if (execve("./arg.out", argv, envp) == -1) {
perror("execve");
exit(1);
}
printf("After exec function\n");
return 0;
}
일반적인 exec() 사용 방법
- Fork()로 생성한 자식 프로세스에서 exec() 함수군을 호출
- 자식 프로세스의 메모리 이미지가 부모 프로세스 이미지에서 exec() 함수로 호출한 새로운 명령으로 대체
- 이를 이용해 자식 프로세스는 부모 프로세스와 다른 프로그램을 실행
- Exec() 수행이 완료된 이후, 해당 코드 이하의 내용은 해당 프로세스에서는 삭제되므로 실행되지 않음
- 자식 프로세스의 메모리 이미지가 부모 프로세스 이미지에서 exec() 함수로 호출한 새로운 명령으로 대체
- 부모 프로세스와 자식 프로세스가 각기 다른 작업을 수행해야 할 때 fork() 와 exec() 함수를 함께 사용
Process Termination
프로세스 트리
- 프로세스는 항상 부모로부터 태어남
- 즉, 모든 프로세스는 가족으로 family tree 가 형성됨
- 부모의 역할
- 자식 프로세스의 종료 상태를 확인하고,
- 상태에 따른 작업을 처리함
부모의 역할: 자식의 종료 상태 확인
- 예) 파일 전송을 수행하는 서버 프로세스
- 각 사용자의 요청에 따라 실제 파일 전송을 담당하는 child process 생성
- 만약 child 가 파일 전송에 실패했다고 하면?
- 에러 코드에 따라 문제 상황을 해결하고,
- 에러 코드: main() 함수의 return 값. 혹은 exit() 로 전달되는 값
- 이걸 어떻게 전달 받을 수 있을까? -> wait() 시스템콜
- 다른 child 를 만들어 파일 전송을 다시 시도함
- 만약 에러 코드를 전달하지 못하고, (아무도 에러 코드를 받아주지 않고) 프로세스가 종료되면?
- Zombie 프로세스!
- 프로세스는 종료되었지만, PID 등의 자원이 반환되지 않음
- Zombie 프로세스!
- 따라서 parent는 항상 wait() 를 이용해 child process의 종료를 확인하여야 함
자식 프로세스 상태 대기: wait(3)
#include <sys/types.h>
#include <sys/wait.h>
pid_t wiat(int *wstatus);
- wait(3)
- 부모 프로세스는
wait()
함수에서 멈춘 상태로, - 프로세스의 상태 변화를 대기: 종료, (시그널에 의한) 중단, 재개 등
wait()
함수를 호출 전에 child가 이미 종료했다면wait()
함수는 호출 즉시 리턴
- wstatus : 자식 프로세스가 전달한 상태 정보를 저장할 주소
- wait 함수의 리턴값은 자식 프로세스의 PID
- 리턴값이 -1이면 살아있는 자식 프로세스가 하나도 없다는 의미
- 부모 프로세스는
부모-자식 프로세스의 관리
- 부모 프로세스와 자식 프로세스의 종료 절차
- 부모, 자식 프로세스는 순서와 상관없이 실행하고, 먼저 실행을 마친 프로세스는 종료
wait()
이 제대로 진행되지 않는 상황에 따라 좀비 or 고아 프로세스 발생
- 좀비 프로세스
- 실행을 종료한 자식 프로세스의 종료 상태를 부모 프로세스가 가져가지 않는 경우
- 좀비 프로세스는 프로세스 테이블에만 존재 (제한된 슬롯: PID는 모두 65536개)
- 웹서버 등 사용자 요청에 따라 많은 프로세스를 생성해 사용하는 경우, 문제가 될 수 있음
- 좀비 프로세스는 일반적인 제거 방법은 없음
- 고아 프로세스 (orphan process)
- 자식 프로세스보다 부모 프로세스가 먼저 종료할 경우, 자식 프로세스들은 고아 프로세스가 됨
- 이를 해결하기 위해, 고아 프로세스는 1번 프로세스(init)의 자식 프로세스로 등록
- Init 프로세스가
wait()
를 호출해서 정상 종료시켜 줌
- Init 프로세스가