프로세스

Jongwon·2021년 12월 15일
0

Linux Programming

목록 보기
18/25

프로세스

실행 중인 프로그램으로 자원을 소유할 수 있고, 상태를 가진다.

프로세스 ID
특정 시점에서 프로세스가 가지는 유일한 식별자. 보통 순차적으로 할당된다.

  • swapper process(idle process, 0번 pid) = 실행될 프로세스가 없을 때 커널이 실행하는 프로세스
  • Init process(1번 pid) = 리눅스 커널이 만드는 모든 프로세스의 조상 프로세스, 시스템 초기화 담당

계층구조
init 프로세스를 제외한 모든 프로세스는 부모자식 관계를 가짐(pstree 명령어로 확인가능)

프로세스 그룹
모든 프로세스는 프로세스 그룹을 가지고, 유사한 관계가 있는 프로세스를 그룹으로 묶음(그룹id도 존재)
같은 프로세스 그룹 내의 프로세스는 실행/정지/종료를 같이한다.

프로세스 스케줄링
하나의 CPU 코어는 한개의 프로세스밖에 실행할 수 없고, CPU내에서 실행중(running)이지 않은 프로세스는 큐에서 대기(ready)한다.
리눅스 2.6에서는 O(1) 스케줄러, 그 이후엔 CFS 스케줄러를 이용한다.

상태
CPU를 time division방식으로 프로세스에 할당하기 때문에, 동작이 끝나면 해당 프로세스는 다시 ready 상태로 돌아간다.
각 할당된 시간을 time quantum이라 한다.

  • user mode : 흔히 생각하는 프로세스의 동작이 user mode에서 실행됨.
  • kernel mode : 시스템 호출이나 인터럽트를 통해 컴퓨터 자원을 사용해야할 때, kernel mode가 실행됨.
  • ready : 실행 준비가 된 프로세스, 큐에 프로세스들이 입력되어 있다.
  • sleeping: 실행 중 입출력 요구가 발생하면 해당 프로세스는 sleep 상태로 된다. sleep 큐 내부에 있는 프로세스는 실행할 수 없고, 입출력 종료 시 ready상태로 된다.

프로세스 관리 자료구조

  • task_struct: 리눅스에서 프로세스가 만들어질 때마다 task_struct 구조체에 프로세스에 관한 정보를 기록하고, process table 내에 넣어진다. 실행 여부와 관계없이 항상 접근이 가능해야하는 프로세스 정보는 task_struct에 보관한다.
    • 프로세스 상태
    • 프로세스 플래그
    • 프로세스 우선순위
    • 부모 프로세스 ID
    • U-area 포인터
    • 알람 시그널 발생까지 남은시간
    • Swap 가능한 영역 크기와 주소
  • u_area: 현재 실행중인 프로세스가 접근해야 하는 필드를 가진다.
    • 시스템 호출에 의해 나온 errno
    • 프로세스 테이블의 포인터
    • 프로세스에 의해 open된 fd
    • TEXT, DATA, STACK 크기 = 프로세스 실행 이미지
    • user mode와 kernel mode에서 수행된 시간
    • umask 정보
    • Real ID, effective ID
    • 현재 디렉토리, 루트 디렉토리

프로세스 사용자 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를 통해 상속되는 속성>

  • real ID, effective ID
  • 프로세스 그룹 ID, 세션 ID(로그인한 터미널마다 가지는 아이디)
  • 제어 터미널
  • set-user-ID 플래그, set-group-ID 플래그
  • 현재 작업 디렉토리, 루트 디렉토리
  • 파일 생성 마스크
  • 시그널 마스크
  • open된 파일의 close-on-exec 플래그(exec시 open된 파일들을 닫을건지)
  • 환경변수
  • 자원 제약
  • open된 파일 디스크립터

<fork를 하며 달라지는 속성>

  • 프로세스ID, 부모 프로세스ID
  • 자식 프로세스의 실행시간 정보(tms_utime, tms_stime, tms_cutime, tms_cstime)은 0으로 초기화
  • 부모 프로세스가 lock한 파일들
  • 처리되지 않은 alarm와 signal

<exec을 통해 상속되는 속성>

  • open 되어있던 fd(단, FD_CLOEXEC이 없어야만 가능)
  • pid, ppid, 우선순위, 소유자와 그룹
  • 현재 작업 디렉토리, root 디렉토리
  • 파일 생성 마스크
  • 파일 잠금
  • CPU 사용시간(실행시간 정보)

<exec을 하며 달라지는 속성>

  • 대기 중인 시그널을 잃어버림
  • 메모리 잠금이 풀림
  • 쓰레드 속성 대부분이 기본값으로 돌아감
  • 프로세스 통계 대부분이 재설정
  • mmap 파일과 프로세스 메모리 관련 설정이 해제


문제점 : 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
종료된 프로세스의 상태값 저장, 매크로 지원

  • exit 함수 호출 시
    하위 8비트에 exit함수가 넘겨준 인자를 넣고, 상위 8비트는 0
  • 시그널에 의한 종료 시
    하위 8비트는 0, 그 다음 1비트엔 core 플래그(core dump 파일, 실행 이미지 파일이 만들어졌는지), 그 뒤의 7비트엔 시그널 번호가 저장됨.
  • 시그널에 의해 잠시 멈춘 경우 : SIGSTP, SIGSTOP
    하위 8비트에 시그널 번호가 저장되고, 상위 8비트엔 0x7f가 채워짐

매크로함수

SIGNAL설명
WIFEXITED정상적으로 종료한 경우 참 값 리턴
WEXITEDSTATUSexit 인자에서 하위 8비트 리턴
WIFSIGNALED시그널에 의한 종료인 경우 참 값 리턴
WTERMSIG시그널 번호 리턴
WCOREDUMP코어파일 생성 시 참
WIFSSTOPPED실행이 일시중단된 경우 참 값 리턴
WSTOPSIG실행을 일시중단시킨 시그널 번호 리턴


<unistd.h> 헤더

pid_t: 프로세스 id 타입

  • pid_t getpid(void) 호출한 프로세스의 id
  • pid_t getppid(void) 호출한 프로세스의 부모id
  • pid_t getuid(void) real user id
  • int setuid(pid_t uid)
  • pid_t geteuid(void) effective user id
  • int seteuid(pid_t uid)
  • pid_t getgid(void) real group id
  • int setgid(pid_t gid)
  • pid_t getegid(void) effective group id
  • int setegid(pid_t gid)
  • pid_t fork()
    부모는 생성된 자식프로세스의 프로세스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로 받은 쉘의 종료 상태값 반환



<stdlib.h> 헤더

  • void exit(int status)
    atexit 함수 실행 후 _exit() 시스템호출 실행
  • int atexit(void(*function)(void))
    종료될 때 자동으로 실행하도록 등록하는 함수. 최대 32개 등록, 역순으로 실행


<sys/wait.h> 헤더

  • pid_t wait(int *statloc)
    종료된 자식 프로세스의 id 반환
  • pid_t waitpid(pid_t pid, int *statloc, int options)
    특정 자식 프로세스의 종료를 기다릴 수 있다.
pid가
==-1: wait과 동일
==0 : 자신과 동일한 프로세스 그룹에 있는 어느 하나가 종료시
>0 : pid 프로세스 ID를 가진 프로세스

int options
- WNOHANG : pid 프로세스가 종료되지 않아도 리턴(리턴값은 0)
- WUNTRACED : pid 프로세스가 stopped된 경우에 리턴
profile
Backend Engineer

0개의 댓글