실행 중인 프로그램으로 자원을 소유할 수 있고, 상태를 가진다.
프로세스 ID
특정 시점에서 프로세스가 가지는 유일한 식별자. 보통 순차적으로 할당된다.
계층구조
init 프로세스를 제외한 모든 프로세스는 부모자식 관계를 가짐(pstree 명령어로 확인가능)
프로세스 그룹
모든 프로세스는 프로세스 그룹을 가지고, 유사한 관계가 있는 프로세스를 그룹으로 묶음(그룹id도 존재)
같은 프로세스 그룹 내의 프로세스는 실행/정지/종료를 같이한다.
프로세스 스케줄링
하나의 CPU 코어는 한개의 프로세스밖에 실행할 수 없고, CPU내에서 실행중(running)이지 않은 프로세스는 큐에서 대기(ready)한다.
리눅스 2.6에서는 O(1) 스케줄러, 그 이후엔 CFS 스케줄러를 이용한다.
상태
CPU를 time division방식으로 프로세스에 할당하기 때문에, 동작이 끝나면 해당 프로세스는 다시 ready 상태로 돌아간다.
각 할당된 시간을 time quantum이라 한다.
프로세스 관리 자료구조
프로세스 사용자 ID
프로세스마다 아이디는 3가지 종류가 있다. real ID, effective ID, saved ID이다. 그 중 프로세스의 실행 권한 검사는 effective ID를 기준으로 하고, saved ID엔 이전 effective ID가 담겨있다.
사용자가 root 사용자인 경우
1. seteuid()를 실행 시 euid값이 매개변수로 준 값으로 설정된다.(어떤 값이든 상관없다)
2. setuid()를 실행 시 real uid, effective uid, saved uid모두 매개변수로 준 값으로 변경된다.
사용자가 root 사용자가 아닌 경우
1. seteuid()를 실행 시 euid값이 uid, saved uid중 하나의 값으로만 바뀔 수 있다.
2. setuid()를 실행 시, euid값이 매개변수의 값으로 바뀐다.
고아 프로세스
자식 프로세스가 종료하기 전, 부모 프로세스가 종료되어 부모가 없는 프로세스.
커널이 고아 프로세스를 자동으로 init 프로세스의 자식이 되도록 만들어줌.
좀비 프로세스
자식 프로세스가 종료하였으나, 종료 시그널이 부모 프로세스에게 아직 도달하지 않아 자식 프로세스와 관련된 메모리가 소멸되지 않은 상태.
커널이 프로세스ID, 종료 상태 값, CPU 사용 시간과 같은 정보를 아직 가지고 있음.
프로세스가 실행될 때 수행되는 코드 및 이와 관련된 자료구조. fork를 통해 자신과 동일한 코드데이터를 가진 새로운 프로세스를 생성할 수 있고, exec을 통해 기존의 프로세스 id를 가지지만 실행 이미지를 대체시킬 수 있다.
<fork를 통해 상속되는 속성>
<fork를 하며 달라지는 속성>
<exec을 통해 상속되는 속성>
<exec을 하며 달라지는 속성>
문제점 : fork후 바로 exec시 엄청난 overhead 발생, 따라서 vfork와 Copy-On-Write방식 등장했다.
vfork
자식 프로세스는 즉시 exec이나 _exit()한다는 확신을 가지고 fork하는 것. 하지만 자식이 exec을 하지 않는다면 부모는 무한 대기를 하게 된다.
Copy-On-Write
가상 메모리에서 사용가능한 방법으로, 부모 프로세스가 가리키는 물리 메모리를 자식 프로세스도 가리키도록 한다.
자식 프로세스에서 write 이벤트 발생 시, 그때 새로운 물리메모리를 할당하고, 데이터를 복사하여 write한다.(페이지 폴트 인터럽트)
운영체제는 프로세스가 open한 파일 디스크립터를 모두 닫고, 프로세스가 차지하고 있던 메모리를 가용 메모리(pool)로 변환하여 할당받은 메모리를 반납시킨 뒤 프로세스를 종료시킨다.
정상종료 : _exit()
main함수에서 return이 발생하거나, exit() 라이브러리 함수 호출 시 _exit() 시스템호출이 실행되고, 프로세스가 종료됨.
exit()과 _exit()의 차이
exit()은 표준 입출력 정리 루틴(atexit)을 실행한 후, _exit()함수를 호출한다.
표준 입출력 정리 루틴은 open한 파일스트림을 fclose하고, 버퍼의 내용을 flush하는 과정이다.
비정상종료 : abort()
커널이 종료 상태값을 설정했거나, signal에 의한 종료거나, abort() 시스템호출 시 프로세스는 비정상종료됨.
부모 프로세스가 자식 프로세스의 종료를 대기하는 것. 자식 프로세스는 종료시 부모 프로세스에게 SIGCHLD 시그널 전송, 부모는 시그널을 받아 block상태가 해제됨.
자식이 여러개인 경우, 그 중 하나만 기다림.
종료 상태값 : int *statloc
종료된 프로세스의 상태값 저장, 매크로 지원
매크로함수
SIGNAL | 설명 |
---|---|
WIFEXITED | 정상적으로 종료한 경우 참 값 리턴 |
WEXITEDSTATUS | exit 인자에서 하위 8비트 리턴 |
WIFSIGNALED | 시그널에 의한 종료인 경우 참 값 리턴 |
WTERMSIG | 시그널 번호 리턴 |
WCOREDUMP | 코어파일 생성 시 참 |
WIFSSTOPPED | 실행이 일시중단된 경우 참 값 리턴 |
WSTOPSIG | 실행을 일시중단시킨 시그널 번호 리턴 |
pid_t: 프로세스 id 타입
호출한 프로세스의 id
호출한 프로세스의 부모id
real user id
effective user id
real group id
effective group id
부모는 생성된 자식프로세스의 프로세스id를 반환받고, 자식은 0을 반환받는다. -1은 에러
유닉스 운영체제에서 사용자가 프로세스를 생성할 수 있는 유일한 방법이다.
fork() 이후엔 부모 프로세스와 자식 프로세스가 스케줄링에 따라 병렬로 실행된다.
<커널의 동작>
1. 새로운 프로세스에게 task_struct와 u-area 할당
2. 자식 프로세스에게 고유의 프로세스 ID 부여
3. 부모 프로세스의 수행이미지 복사(TEXT, DATA, STACK)
4. 부모 프로세스가 open한 파일을 똑같이 open하므로 system file table과 inode table count 증가
5. fork()에 반환을 프로세스 ID or 0 넣어주는 부분부터 두 프로세스가 병렬실행
pid_t vfork()
fork와 동작은 동일하나, 부모 프로세스의 수행 이미지(메모리나 제어권)을 모두 자식에게 너믹고 부모는 block 상태가 됨. 자식은 곧바로 _exit()이나 exec를 해야함
그냥 exit 사용하면 안된다. 이것은 open한 파일들을 모두 닫고 종료하기 때문이다.
int execl(char path, char arg0, char arg1 ..., (char)0)
int execv(char path, char argv[])
int execle(char path, char arg0, char arg1 ..., (char)0, char *envp[])
int execve(char path, char argv[], char *envp[])
int execlp(char file, char arg0, char arg1 ..., (char)0)
int execvp(char file, char argv[])
l은 list형태, v는 벡터형태, e는 환경변수, p는 파일경로
오류가 없다면 리턴하지 않는다. -1은 에러
결국에 execve호출(envp 지정 안할 시 현재 프로세스의 환경변수로 설정됨)
void _exit(int status)
int system(const char *cmdstring)
새로운 쉘을 열어 fork->exec->waitpid를 실행
fork 실패나 waitpid가 EINTR 리턴 시 error값 지정 후, -1 반환
exec 실패시 127리턴, 모두 성공 시 waitpid로 받은 쉘의 종료 상태값 반환
atexit 함수 실행 후 _exit() 시스템호출 실행
종료될 때 자동으로 실행하도록 등록하는 함수. 최대 32개 등록, 역순으로 실행
종료된 자식 프로세스의 id 반환
특정 자식 프로세스의 종료를 기다릴 수 있다.
pid가
==-1: wait과 동일
==0 : 자신과 동일한 프로세스 그룹에 있는 어느 하나가 종료시
>0 : pid 프로세스 ID를 가진 프로세스
int options
- WNOHANG : pid 프로세스가 종료되지 않아도 리턴(리턴값은 0)
- WUNTRACED : pid 프로세스가 stopped된 경우에 리턴