프로세스 ID
- pid, 각 프로세스는 해당 시점에 unique한 pid를 가짐
- pid 최대 값은 32768 (2^15 = 32768)
- 부호형(signed) 16비트 정수값 사용
sudo vi /proc/sys/kernel/pid_max 로 최대값 확인 가능
프로세스 계층
- 최초 프로세스: init 프로세스, pid 1
- init 프로세스는 운영체제가 생성
- 다른 프로세스는 또 다른 프로세스로부터 생성
- ppid값이 부모 프로세스의 pid를 뜻함
ppid 값 확인해보기
프로세스와 소유자(owner)관리
프로세스 관리 관련 시스템콜
사전작업: 우분투 리눅스에 gcc설치 (+vi 에디터/한글 설정)
getpid() 와 getppid()
-
함수 원형
-
실습 코드
ㄴ ./pidinfo2 하면 프로그램 실행
부모 프로세스와 자식 프로세스
- bash 프로세스가 실행 파일의 부모 프로세스인 예
프로세스 생성
- 기본 프로세스 생성 과정
i. TEXT, DATA, BSS, HEAP, STACK의 공간을 생성
ii. 프로세스 이미지를 해당 공간에 업로드하고, 실행 시작
- 프로세스 계층: 다른 프로세스는 또 다른 프로세스로부터 생성
fork() 와 exec() 시스템콜
- fork() 시스템콜 - (복사)
- 새로운 프로세스 공간을 별도로 만들고, fork() 시스템콜을 호출한 프로세스(부모 프로세스) 공간을 모두 복사
- 별도의 프로세스 공간을 만들고, 부모 프로세스 공간의 데이터를 그대로 복사
- exec() 시스템콜 - (덮어씌움)
- exec() 시스템콜을 호출한 현재 프로세스 공간의 TEXT, DATA, BSS영역을 새로운 프로세스의 이미지로 덮어씌움
- 별도의 프로세스 공간을 만들지 않음
fork() 시스템콜
ㄴ(부모 프로세스 복사해서 실행하는 거라 인자가 없다)
ㄴpid 값을 보고 부모/자식 프로세스 구별함
- 예시)
정리
- pid = fork()가 실행되면 부모 프로세스와 동일한 자식 프로세슥 ㅏ별도 메모리 공간에 생성
- 자식 프로세스는 pid가 0으로 리턴, 부모 프로세스는 실제 pid리턴
- 두 프로세스의 변수 및 PC(Program Count)값은 동일
- 새로운 프로세스 공간을 별도로 만들고, fork() 시스템콜을 호출한 프로세스(부모 프로세스) 공간을 모두 복사한 후, fork() 시스템콜 이후 코드부터 실행
exec() 시스템콜 family
- 예시)
ㄴls -l이 CODE에 덮어씌워져 실행된다
execl()와 execlp() 시스템콜 사용법
- 명령어 인수 리스트
- argv[0] = "ls"
- argv[1] = "-al"
예) $ ls -al
- execlp 첫번 쨰 인자는 PATH를 설정할 수 있고 기본적으론 /bin 디렉토리에 Path가 설정되어 있다(그래서 쉘명령어에 ls만 치면 됐던거)
- 즉, execlp에 p가 붙으면 디폴트로 설정되어 있는 환경 변수 PATH값을 참고해서 실행파일을 실행하겠다는 의미이다. 그래서 디렉토리 명을 다 쓸 필요 없고 path명만 있으면 동일하게 ls 실행파일 명만 쓰면된다.
execle() 시스템콜 사용법
PATH값이 있더라도 그걸 안 쓰고 정의할 환경변수를 따로 envp에 설정해서 실행파일이 실행할 환경변수에 미리 설정해서 넣어줌.
execv(), execvp(), execve() 시스템콜 사용법
인자를 string / 다른 변수로 미리 만들어서 넣을 수 있다
(arg로 따로 넣음)
exec() 시스템콜 family정리
l과 v의 차이는 인자를 argv변수로 설정해서 넣을지 아니면 직접 설정할지의 차이다.
execl() 시스템콜 예
- execl() 시스템콜을 실행시킨 프로세스 공간에 새로운 프로세스 이미지를 덮어씌우고, 새로운 프로세스를 실행
- perror() 함수가 호출된다는 의미는 새로운 프로세스 이미지로 덮어씌우는 작업이 실행되지 못했다는 의미
- 즉, execl() 시스템콜 실행 실패
execve() 시스템콜 예
- 환경변수를 새로 생성하므로, 기존 사용자가 설정한 환경 변수값은 사용할 수 없음
- 다음 코드에서, envp에는 PATH를 설정하지 않았으므로, execve()에서 "ls"만 쓰면 파일을 찾을 수 없다고 뜨기 때문에 "/bin/ls"로 전체 경로를 써야, 실행 가능하다
wait() 시스템콜
-
wait() 함수를 사용하면, fork() 함수 호출시, 자식 프로세스가 종료할 때까지, 부모 프로세스가 기다림
-
자식 프로세스와 부모 프로세스의 동기화, 부모 프로세스가 자식 프로세스보다 먼저 죽는 경우를 막기 위해 사용(고아 프로세스 / 좀비 프로세스)
- 메모리에 프로세스 정보가 계속 남아 있을 수 있다.
- 자식 프로세스가 종료되면, 좀비 프로세스가 되어, 해당 프로세스 조사를 위한 최소 정보만 가지고 있는 상태가 됨
- 완전히 끝나면, 해당 정보도 삭제되고, 부모 프로세스에 SIGCHLD시그널이 보내짐
-
wait() 리턴값
- 에러가 발생한 경우
-
status 정보를 통해 기본적인 자식 프로세스 관련 정보를 확인할 수 있음
- 자식 프로세스 상태 정보를 알기위한 여러 매크로함수가 정의되어 있다.
- 예)
- 0이 나올 경우, 정상 종료되지 않았다는 뜻.
- 전체 예시)
fork코드를 shell이라고 보고 exec에서 ls명령을 실행했다고 가정한다면,
- shell안에 wait라는 함수를 적어 놓으면 자식 프로세스가 끝날 때까지 기다리고,
- 자식 프로세스가 끝나고 부모 프로세스가 SIGCHLD 시그널을 받으면 wait() 이후의 code가 실행된다.
- 예시)
- 전형적인 쉘 프로그램 작성하는 구조
- 프로세스를 리눅스에서 생성하는 방식
fork(), execl(), wait() 시스템콜
- execl()만 사용하면, 부모 프로세스가 사라짐.
ㄴ이를 유지하기 위해, fork() 새로운 프로세스 공간 복사 후, execl() 사용.
- wait() 함수를 사용해서 부모 프로세스가 자식 프로세스가 끝날 때까지 기다릴 수 있음.
예시)
stdin (표준 입출력 Standard Input Stream) : 키보드 입력 + 엔터까지 친 내용을 stdin파일에 들어감.
0x00 : 키보드 입력 후 엔터를 치면 엔터까지 저장되기 때문에 엔터키를 지우기 위한 코드.
copy-on-write
: 프로세스 생성 속도를 높일 수 있는 기능.
-
fork()는 새로운 프로세스 공간 생성 후, 기존 프로세스 공간 복사
4GB를 복사한다면, 프로세스 생성 시간이 오래 걸림
-
자식 프로세스 생성시, 복사하지 않고 부모 프로세스 페이지를 우선 사용
-
부모 또는 자식 프로세스가 해당 페이지를 읽기가 아닌, 쓰기를 할 때(업뎃)
ㄴ이 때 해당 페이지를 복사하고, 분리함
-
장점
- 전체 복사 않해서 프로세스 생성 시간을 줄일 수 있음
- 새로 생성된 프로세스에 새롭게 할당되어야 하는 페이지 수도 최소화
read시
자식 프로세스에서 read만 할 때, 해당 공간을 위해 별도의 페이지를 생성하지 않고 기존에 생성된 부모 프로세스의 물리 메모리 주소를 그대로 사용한다.
C언어로 말하면 데이터는 메모리 공간에 그대로 있고 a또는 b 포인터로 가르키기만 하면된다.
프로세스의 kernel공간을 공유하는 기술과 비슷하다.
write시
write를 요청할 때, 페이지 테이블에서 물리 메모리에 있는 해당 공간을 복사해서 새로운 물리 공간에 데이터를 넣어두고, 페이지 테이블은 새로운 공간에 주소를 가리키게 한다.
프로세스 종료
- exit() 시스템콜: 프로세스 종료
- main함수의 return 0; 와 exit(0);의 차이는?
- exit() 함수: 즉시 프로세스를 종료함(exit()함수 다음에 있는 코드는 실행되지 않음)
- return 0: 단지 main()이라는 함수를 종료함
- 단, main()에서 return시, C언어 실행 파일에 기본으로 표함된 _start() 함수를 호출하게 되고, 해당 함수는 결국 exit() 함수를 호출함
main()함수에서 return0;은 exit()호출과 큰 차이가 없다.
exit() 시스템콜
- exit() 시스템콜 주요 동작
- atexit()에 등록된 함수 실행
- 열려 있는 모든 입출력 스트림 버퍼 삭제 (stdin, stdout, stderr 파일처럼 다루어지는 데이터를 지움)
- 프로세스가 오픈한 파일을 모두 닫음
- tmpfile() 함수를 통해 생성한 임시 파일 삭제
- 참고: tmpfile() - 임시 파일을 wb + (쓸 수 있는 이진파일 형태) 모드로 오픈가능
atexit() 함수
- 프로세스 종료시 실행될 함수를 등록하기 위해 사용
- 여러개 등록 가능하고, 등록된 함수를 등록된 역순서대로 실행
- 함수 예)
wait() 시스템콜
- 자식 프로세스가 종료되면, 좀비 프로세스가 되어, 해당 프로세스 조사를 위한 최소 정보만 가지고 있는 상태가 됨
- 완전히 끝나면, 해당 정보도 삭제되고, 부모 프로세스에 SIGCHLD시그널이 보내짐
정리
[1-1] 새로운 프로세스를 생성할 때, 부모 프로세스 안에 fork()라는 명령을 넣어서 동일한 데이터/코드를 가진 별도의 프로세스 공간(자식 프로세스)을 생성
[1-2] fork()를 실행한 다음 칸에 PC가 놓여지고 fork()의 return값이 pid값을 주며, 이 pid값을 기반으로 부모/자식 프로세스인지 구분함.
[2] 자식/부모 프로세스의 pid값을 기반으로 조건문을 사용해서,
- 자식: exec() 시스템콜로 인자에 내가 실행할 실행파일을 넣어서 (인자:실행파일) 실행을 시키면 해당 실행파일 이미지로 자식 프로세스가 덮어씌워지고 해당 실행파일을 처음부터 실행된다. 부모와는 다른 프로세스가 실행되게 된다.
[3]
- 부모: wait() 시스템콜을 사용해서 자식 프로세스가 끝나기전에 부모 프로세스가 죽으면 안되기 때문에 기다리든 다른 코드를 진행해야 한다. 그래서 자식 프로세스가 어떻게 종료했는지 상태정보를 알아내서, 그 상태정보에 맞게 뒷처리 작업을 하고 프로세스를 끝내든 다른 작업을 하든 한다.
[4] 자식 프로세스에서는 실행파일을 실행한 마지막 코드에 status(종료 상태정보)를 넣은 exit()시스템콜이 실행되고 끝난다. 그러면, 부모 프로세스에 시그널(SIGCHLD)를 보내게 되고 그걸 받은 부모 프로세스는 wait()가 풀리고 다음 코드가 실행된다.