프로세스의 개념
- 프로그램이 실행되고 있는 상태(CPU)
- 2가지 key abstractions
- Logical control flow : 혼자 cpu를 독자적으로 쓰는 것과 같은 느낌을 줌 -> 그러나 동시에 여러가지 프로세스가 실행중임
- 빠르게 스위칭을 하여 multitasking -> 여러 프로세스 실행
- private viretual addess space : 시스템에 있는 메인 메모리를 프로그램 혼자 전부 사용하는 것과 같은 효과
- CPU마다 virtual memory라는 가상메모리를 제공
- 프로세스가 스위칭 될 때마다 예전의 프로세스의 내용을 디스크에 저장하고 새로운 프로세스를 읽어옴
Context Switching
- 수십, 수백개의 프로세스들이 각자 CPU하나를 독자적으로 사용하고 있다고 느끼게 함
- CPU의 코어가 1개라고 가정
- process는 A와 B 두 개
- processA가 10ms가 진행된 후 process B로 switching시켜줌 -> kernel의 역할 : A의 레지스터값과 메모리의 상태들을 저장하고 B가 수행되기 위해 필요한 값들을 복원
- processB 실행
- 10ms후에 A로 스위칭 -> 레지스터값과 메모리 상태를 저장 후 A로 옮기고 A의 정보를 불러옴
- 반복 -> 짧은 시간동안 많은 switching이 일어나기 때문에 1개의 cpu를 사용하고 있는 것 같은 착각을 하게됨
Process States
- Idle : 처음에 생성되어 cpu에 할당, 준비 안된상태
- Runnable : cpu에서 돌아가지는 않지만 cpu에서 돌아갈 준비가 다 되었다는 상태
- Running : cpu를 할당받아 cpu상에서 돌아가는 상태
- Sleeping : 특정한 이벤트가 일어날때 까지 기다리는 상태 -> ex)디스크에서 값을 읽어 오는 경우 불러오는 시간동안 sleeping -> 디스크에서 값을 읽어오면 Runnable상태로 / 타이머의 경우도 비슷함
- 돌아가는 상태의 프로세스에서 중단 Signal을 받게되면 프로세스를 중단시킬 수 있음 -> 다시 실행 Signal을 받으면 실행됨 - ex) ctrl + c
- 수행이 모두 끝나고 나면 Exit로 종료 -> 끝나기 전에 Zombified라는 상태가 될 수 있음 - 부모프로세스가 종료된 프로세스의 뒷처리를 해야함 이것이 완벽하게 안끝난 상태
Process의 구성
- 크게 5개의 부분으로 나뉨
- a code area : 프로그램이 메모리에 로드되어 실행되는 것 -> 프로그램의 코드가 필요, 이것이 메모리에 저장된 것
- a data area : 변수들이나 melloc으로 할당된 메모리들
- a stack area : 함수를 호출할 때, stack에 데이터들이 쌓임
- a user area : 해당 프로세스에대한 정보들을 가지고 있음
- page tables : 사용하고 있는 메모리에대한 정보를 가지고 있음
- 예제 : x86에서 돌아가는 리눅스의 메모리 맵 - 프로세스
- 0번부터 시작하여 총 4GB : 1GB는 kernel에서 맵핑되어 사용되고 3GB는 사용자 프로세스에 의해 사용됨
- 위 그림은 하나의 프로세스에 대한 메모리 맵
- stack, code, data(heap - malloc) 등 저장되어있음
- 4GB가 1개의 프로세스
- 만양 100개의 프로세스라면 400GB의 메모리가 필요한 셈 -> 가상메모리를 사용
- 가상메모리 : 스위칭 되는 시점에서 사용됨
Process Table
- 커널은 어떤 프로세스가 시스템에서 돌아가고 있는지에 대한 총괄적인 정보 저장
- 각각의 프로세스마다 테이블의 엔트리 차지
- 프로세스의 부모프로세스 ID
- 프로세스를 소유한 user나 group
- 프로세스의 현재 상태(6가지)
- code나 data, stack, user area들이 어디에 저장되어 있는지
- pending된 signal들
- 각각의 프로세스에 대한 entry
- PID(프로세스 ID), PPID(부모 process ID), Stat 등에 대한 정보들 저장
- process들을 스위칭 시키며 스케줄링 -> 순서를 정함
Unix/Linux의 process관리
- 처음에 부팅시 1개의 process만 존재 - Init(sbin/init) -> PID는 1
- 프로세스를 만듦 - 기존에 있는 프로세스를 복제하는 방식 -> init(parent process)에서 복제(child process -> init에서 만들었으므로 PPID = 1)
- 그러므로 init은 모든 프로세스들의 조상프로세스
- 프로세스들은 fork()라는 시스템콜로 복제 -> parent process와 child process는 완전히 동일(PIDs, PPIDs만 다름) - ex) code, data, stack을 그대로 카피
- 예를들어 init이 fork로 init을 만들었다면? -> 이 자식프로세스가 프로그램 파일을 가지고 어떤 작업을 수행하길 원함
- code를 원하는 실행파일의 코드로 바꿔줌 -> execve() 시스템 콜 이용 -> 원하는 프로세스가 만들어짐
- fork()를 통해 여러 process를 만들어 놓음 -> 그 후 exec(getty) (= getty라는 프로세스 실행)와 같이 쓰면 프로세스의 code가 바뀜
- getty : login을 handling -> 로그인 메시지를 처리
- login후 fork(bash) -> bash를 실행하는 자식프로세스로 변경됨
- shell prompt에서 명령어를 입력하여 프로세스를 실행시키는 과정
- 콘솔이 깜빡임 : shell process가 수행이 되고 있는 상태
- 실행파일의 이름을 입력하고 엔터
- shell process자체를 복제시킴 - fork이용
- 원하는 명령어를 수행하기위해 코드부분을 우리가 지정한 실행파일의 코드로 대치 -> exec()
- 실행파일의 코드를 수행하는 자식 프로세스가 생성됨
- shell process는 자식프로세스가 명령어를 수행하고 끝나기를 기다림 -> wait()
- 자식 프로세스가 종료되면 부모 프로세스가 깨어나서 뒷처리 후 다음 shell prompt를 화면에 표시
- A라는 shell process(parent)가 있다고 가정
- 실행파일 입력 후 엔터
- fork()실행
- process 복제(child)
- execv라는 시스템 콜을 실행
- 코드영역이 B로 바뀌면서 다른 process가 됨
- 수행 후 종료 -> exit()
- parent process는 자식프로세스가 진행되는 동안에도 같이 실행될 수 있음 -> wait()로 기다림
- child process가 끝나면 대기하고 있던 parent process가 수행을 재기 -> prompt 깜빡거림
Process 관리 시스템 콜
- getpid : 프로세스 ID를 가져오기 위함
- getppid : 부모 프로세스의 ID를 가져옴
- fork : 자신을 복제하여 자식 프로세스를 만들기 위함
- exec : 코드부분을 바꿔서 프로그램을 실행
- exit : 프로세스를 종료하면서 상태변수 전달
- wait : 자식프로세스가 끝날때까지 기다리는 역할
- getpid(2), getppid(2)
- getpid : process ID를 받아오는 함수 : 양의 정수형태, 아무도 사용하지 않는 번호가 있으면 사용
- getpptid : parent process의 process ID를 받아오는 함수
- init process : process ID가 항상 1
- fork(2)
- 자기자신을 복제하여 새로운 프로세스를 만듦
- 두 프로세스는 완전히 똑같은 프로세스 -> PID와 PPID만 다름
- pid_t 타입을 return - return을 두 번하는 시스템 콜
- parent process는 child process의 PID를 child process는 0이라는 값을 return값으로 받음
- 예제
- fork()라는 시스템 콜을 불렀을 경우 완전히 똑같은 프로세스를 복사
- parent process는 child process의 PID가 리턴, child process는 0이 리턴
- fork된 시점부터 process는 두 개
- parent process는 retrun값이 0이 아니므로 else문으로
- child process는 return값이 0이므로 if문으로
- 마지막 printf 수행
- 예제2
- 어떤 함수를 수행하는 프로세스가 있음
- L0 print
- fork하면 동일한 process가 실행됨
- L1 print 두 번 -> 부모, 자식 프로세스가 각각 실행
- fork 하면 부모 프로세스와 자식 프로세스가 각각 동일한 process를 만듦
- L2를 네 번 출력
- fork -> 8개의 프로세스
- Bye를 8번 출력
- 스케줄러가 프로세스들의 순서를 정하기 때문에 우선순위를 주지 않는 이상은 순서를 예측할 수 없음
- exec(3) family
- execve를 이용하여 만든 라이브러리들이 모여있음
- fork를 통해서 생성된 프로세스가 코드부분을 새로운 실행파일의 코드로 바꿈
- parent process와 다른 코드를 실행하게 됨
- execve(실행시키고 싶은 경로이름, 벡터형태로 인자 전달, 환경변수들을 벡터형태로 전달)
- v가 붙어있는 exec함수들 : 벡터로써 argument들을 전달
- l이 붙어있는 exec함수들 : argument들을 순서대로 전달 - 쓰고 싶은 만큼 씀
- e가 붙어있는 exec함수들 : 환경변수를 넘겨줄 수 있음
- p가 붙어있는 exec함수들 : file name을 넘겨주고 경로를 지정된 PATH라는 절대경로에서 찾음
- 예제
- fork값이 0이 나온다면(자식 프로세스)
- execve(프로그램, argv, 환경변수) 넘겨줌
- 리턴값이 0보다 작으면 에러메시지를 띄우고 아니라면 프로그램을 실행
- exit(3), atexit(3)
- 정상적으로 종료시킬때 사용하는 시스템 콜
- 정상적이지 않은 경우는? -> 외부에서 signal을 받아 강제종료하는경우
- child process가 parent process에게 SIGCHLD라는 signal을 보내면 wait하고 있다가 프로세스의 status를 받아서 처리
- function포인터 (함수에 대한 포인터) : 함수를 등록해놓으면 exit하기 전에 함수를 호출해줌
- 예제
- cleanup이라는 함수 등록
- fork후 exit하기 전 cleanup을 부름
- 부모 자식 둘 다 cleanup
- wait(2), waitpid(2)
- 자식 프로세스가 종료되기를 기다림
- 종료된 프로세스가 보내준 status를 가져와서 진행
- return값은 종료된 프로세스에 대한 ID
- status value
- 오른쪽 8bit가 0이면 process가 정상 종료되었다는 의미
- status code는 왼쪽의 8bit에 저장
- 외부 signal에 의해 종료된 경우는 그 signal의 정보가 오른쪽에 저장
- 테스트 매크로를 사용하여 status값을 조작 가능
- 특정 프로세스를 종료하기까지 기다리는 경우 waitpid(pid, status값, option-종료될 때까지 기다리는 것이 아니라 상태변화 했을 경우 등 여러 옵션들로 지정할 수 있음 -> 정교한 조작 가능)
- 예제
- N번 반복하여 자식 프로세스 생성
- 0이 리턴되면 -> child 종료 - exit(100 + i)
- for문을 이용하여 parent process들이 wait
- 종료된 child process들의 status가 변경되어 전달
- WIFEXITED : 정상종료되었는지 정보를 return -> 정상종료시 1 아니면 0
- WEXITSTATUS : status코드 중에서 status부분만 추출(앞 8bit)
- 종료될 때 까지 대기하고 정상종료 되면 printf 메시지 출력
Orphan process
- 자식 프로세스가 실행되는 중 부모 프로세스가 종료?
- parent process가 없는 child process가 됨 -> 이것이 orphan process
- PPID = 1로 세팅하여 init프로세스가 입양한 것 처럼 됨
- exit(staus)할 경우 init프로세스로 전달됨
Zombie process
- child process가 exit된 경우 완전히 종료 된 것이 아님 -> parent process가 wait하여 child process의 status를 가져가야 완전히 종료 된 것
- parent process가 wait를 안한 경우 : 시스템 리소스들은 모두 삭제되지만 완전히 종료는 안됨
- 이러한 경우를 Zombie process라고 함
'임베디드SW공학' 카테고리의 다른 글
[12주차]POSIX Threads(1) (0) | 2018.12.01 |
---|---|
[11주차]시그널과 타이머 (0) | 2018.11.27 |
[9주차]시스템 콜 파일(2) - 디렉토리 (0) | 2018.11.17 |
[8주차]리눅스시스템 프로그래밍: File I/O (0) | 2018.11.11 |
[6주차]Linux/Unix System Programming (0) | 2018.11.01 |