Process Creation
• 부모 프로세스:
이미 실행 중인 프로세스, 새로운 프로세스를 생성하는 역할.
• 자식 프로세스:
부모 프로세스가 생성한 새로운 프로세스.
부모 프로세스의 context (메모리, 스택, 레지스터)를 복사하여 생성되며, 부모와 자식은 독립적으로 실행됨.
자식 프로세스는 복사된 메모리 공간에 새로운 프로그램을 load할 수 있음.
부모와 구분되기 위해 새로운 고유 PID(Process ID)를 부여받음.
• fork()
: 부모 프로세스의 모든 내용을 그대로 복사하여 새로운 자식 프로세스를 생성.
부모와 자식은 동일한 코드를 실행하나, 반환값(return value)을 통해 구분됨.
부모는 fork()의 반환값으로 자식의 PID를 받음.
자식은 반환값으로 0을 받음.
• exec()
: 부모 프로세스의 복사된 상태를 지우고, 새로운 프로그램을 자식 프로세스의 메모리 공간에 load하여 실행.
fork() 시스템 콜
• fork()의 동작
: fork()를 호출하면 실행 중인 부모 프로세스의 주소 공간을 그대로 복제.
결과적으로 부모와 자식은 동일한 코드, 데이터, 스택 구조를 가지지만,
각각 독립적인 프로세스로 실행되며, 메모리도 분리되어 관리됨.
• fork()의 반환값
부모 프로세스 :
fork()의 반환값이 자식 프로세스의 PID (양수)입니다.
반환값이 0보다 크므로 a > 0 조건을 만족.
부모 프로세스는 자식 프로세스가 아닌 다른 작업을 수행할 수 있습니다.
자식 프로세스 :
fork()의 반환값이 0.
자식 프로세스는 주로 부모와 독립적인 작업을 수행.
int main() {
int a = fork(); // fork 호출: 자식 프로세스 생성
if (a == 0) {
// 자식 프로세스 실행
printf("\n Hello, I am child!\n");
} else if (a > 0) {
// 부모 프로세스 실행
printf("\n Hello, I am parent!\n");
}
return 0;
}
fork() 호출 후 두 프로세스가 동시(병렬) 실행되기 때문에 출력 순서는 보장되지 않음.
exec() 시스템콜
: 프로세스가 현재 실행 중인 프로그램 대신 다른 프로그램을 실행하도록 만듦.
• 메모리 변경
: 현재 프로세스의 코드, 데이터, 스택을 교체하여 새로운 프로그램으로 바꿈.
• PID 유지
: 프로세스 ID는 변경되지 않으면서, 기존의 프로세스를 대체함.
• 호출 후 반환 없음
: exec()가 성공적으로 실행되면 호출한 프로그램으로 돌아오지 않음.
→ 기존 프로그램은 더 이상 실행되지 않음.
int main() {
printf("\n Now, I'll run ls.\n");
execlp("ls", "ls", "-l", (char *) 0); // ls -l 명령이 실행되며, 현재 프로세스는 ls 프로그램으로 교체됨
printf("\n I cannot do anything now!\n"); // 프로세스가 교체되어 실행되지 않음
}
int main() {
if (fork() == 0) {
// 자식 프로세스
printf("\n Hello, I am child! Now I'll run date\n");
execlp("date", "date", (char *) 0); // 자식 프로세스가 date 명령으로 교체되어 현재 날짜와 시간을 출력
} else {
// 부모 프로세스
printf("\n Hello, I am parent!\n");
}
}
• 커널에는 exec()라는 하나의 system call 함수만 존재.
: but 개발자가 사용하는 다양한 exec 함수 (execlp, execvp 등)는 라이브러리 함수로 구현되어 있음. system call 함수가 아님.
→ 라이브러리 함수는 내부적으로 exec() 시스템 호출을 수행.
첫 번째 argument로 전달된 파일 열기:
실행할 프로그램의 파일을 엽니다.
ex. a.out 또는 스크립트 파일.
magic number 읽기:
파일의 처음 몇 바이트(magic number)를 읽어 해당 파일이 실행 가능한 바이너리인지, 스크립트인지 판별.
• 실행 파일:
ELF(Executable and Linkable Format) 또는 바이너리 프로그램.
• 스크립트 파일:
첫 줄이 #!로 시작되며, 해당 인터프리터 경로를 가리킴.
ex. #!/bin/bash로 시작하는 쉘 스크립트.
wait() 시스템 콜
부모 프로세스가 wait()를 호출하면:
자식 프로세스가 종료될 때까지 부모 프로세스는 Block 상태로 전환.
커널의 Ready Queue에서 제거되어 CPU를 사용하지 않음.
자식 프로세스가 종료되면:
커널이 부모 프로세스를 Ready 상태로 전환하고 다시 실행.
(종료된 자식 프로세스의 상태를 wait()의 반환 값으로 받음.)
int main() {
int a = fork(); // 부모-자식 프로세스 생성
if (a == 0) {
// 자식 프로세스의 코드 실행
<code for child process>
} else {
// 부모 프로세스: 자식 프로세스가 끝날 때까지 대기
wait();
<code for parent process>
}
}
Process의 수행 모델
: 자식 프로세스는 Background에서 실행됨.
부모 프로세스는 자식의 종료를 기다리지 않고 자신의 작업을 계속 수행.
$ a.out & // &: 명령어를 백그라운드에서 실행하도록 지정
$ _ // 부모 프로세스(쉘)가 다음 명령을 즉시 입력받을 수 있음.
$ a.out // Foreground 실행: 부모는 자식 프로세스의 종료를 기다림.
Process Termination (프로세스 종료)
: 프로세스가 스스로 종료되는 경우.
exit() 시스템 호출을 통해 종료를 명시적으로 수행.
• main() 함수가 끝나거나 명시적으로 exit() 호출.
• 프로세스가 종료되면 부모 프로세스에 signal을 보내 종료를 알림.
: 외부 요인이나 부모 프로세스가 강제로 종료시키는 경우.
• 자식 프로세스가 리소스 한계를 초과하거나 더 이상 작업이 필요하지 않을 때 부모 프로세스가 강제 종료.
• 사용자 입력에 의한 종료: kill, Ctrl + C, Ctrl + Break 등의 신호(signal).
• 부모 프로세스가 종료하는 경우, 자식 프로세스가 먼저 종료.
exit() 시스템 콜
• Signals: Ignore them all
프로세스가 종료되는 동안 더 이상 어떤 신호(signal)도 처리하지 않음.
→ 예외 상황 없이 안전하게 종료를 보장.
• Files: Close
프로세스가 열어 둔 모든 file descriptor를 닫음.
파일 시스템 리소스를 반환하여 다른 프로세스에서 사용할 수 있도록 함.
• Image: Deallocate
해당 프로세스의 메모리 공간(코드, 데이터, 스택 등)을 해제(deallocate).
메모리 자원을 반환하여 시스템의 다른 작업에 재사용 가능.
• Parent: Notify (send signal)
부모 프로세스에 신호를 보내 해당 자식 프로세스가 종료되었음을 알림.
부모가 종료를 확인하지 않으면 zombie process가 생성됨.
• State: Set it to ZOMBIE
프로세스가 종료되었음을 나타내기 위해 상태를 좀비 프로세스(ZOMBIE)로 설정.
부모가 종료 신호를 수신하고 좀비 상태를 처리해야 완전히 제거됨.
• During exit(), kernel
CPU 반환: 현재 프로세스에서 커널이 CPU를 회수하여 다른 프로세스에 할당.
CPU는 Ready State에 있는 다른 프로세스에게 할당됨.
Library vs. System call (revisited)
: 사용자 주소 공간에서 실행
수학 연산: sin(), cos()
입출력 함수: printf(), scanf(), fopen(), fclose()
프로세스 관련: execlp()
: 커널 내부에 정의된 함수, 커널 주소 공간에서 실행
사용자 모드(User Mode)에서 커널 모드(Kernel Mode)로 전환하여 실행.
입출력 관련: read(), write(), open(), close()
프로세스 관리: fork(), exec(), wait(), exit()
Shell 환경의 프로세스 수행
Booting
컴퓨터가 부팅되면 Kernel이 메모리에 가장 먼저 로드됨.
Kernel의 역할
부팅 과정에서 커널은 기본 시스템 리소스(CPU, 메모리, 장치 등)를 초기화함.
초기화 후, Shell(bash) 프로세스를 실행.
Terminal Power-on
터미널 실행 시, 커널은 bash와 같은 쉘 프로그램을 자식 프로세스로 실행.
• 쉘(Shell) : 사용자 명령어를 입력받아 실행하는 프로그램.
Bash (Shell)
사용자가 입력한 명령어를 읽어들이고, 그 명령을 수행하기 위한 프로그램을 실행.
이 과정에서 새로운 프로세스를 생성하며, 명령어 실행은 커널(System Call)을 통해 이뤄짐.
Kernel과 Shell의 관계
커널은 터미널을 통해 사용자가 입력한 명령어를 처리하기 위해 Shell을 생성.
User A와 User B
User A와 User B는 각각 독립된 터미널을 사용하고 있음.
각 사용자에게는 독립적인 bash 프로세스가 생성.
커널은 각 사용자 터미널을 구분하며, 요청에 따라 각각의 bash 프로세스를 생성하고 관리.
구조의 특징
독립성 : 각 사용자에게는 독립적인 bash 프로세스가 할당되어 다른 사용자와 간섭 없이 작업 가능.
확장성 : 새로운 터미널이 열릴 때마다 커널은 추가적인 bash 프로세스를 생성.
보안성 : 사용자별로 독립적인 프로세스를 통해 보안 및 리소스 격리를 보장.
• User A와 vi 프로세스
: User A가 터미널을 통해 vi 명령어를 실행.
vi는 bash의 child process로 실행됨.
→ bash 프로세스가 fork() 시스템 콜을 통해 vi 프로세스를 생성한 후, exec()를 사용하여 메모리 공간을 vi 프로그램의 코드로 대체.
실행된 vi는 독립적인 프로세스로 관리됨.
• User B의 상태
: User B는 별도의 bash 프로세스를 통해 독립적으로 작업.
User B의 bash는 User A의 bash와 별개로 동작하며, 서로의 프로세스는 간섭하지 않음.
Shell process
쉘 : 사용자가 입력한 명령을 처리하고, 새로운 프로세스를 생성 및 실행
main(...) {
while (1) {
print prompt
read a line from terminal
take first word
search directories in $PATH
fork();
exec(); // load a.out
if (last char is not &)
wait();
}
}
프롬프트 출력
쉘이 사용자에게 명령어 입력을 요청하기 위해 프롬프트($ 또는 >)를 출력.
사용자 입력 처리
사용자가 명령어를 입력하면 쉘은 명령어를 읽어서 첫 번째 단어를 추출.
→ 실행할 프로그램의 이름
이후 경로 변수($PATH)를 탐색하여 실행 가능한 파일을 찾음.
fork() 호출
새로운 프로세스를 생성하여 현재 쉘 프로세스의 복사본을 만듦.
exec() 호출
자식 프로세스에서 exec()를 호출하여 메모리 공간을 새로운 프로그램(예: a.out)으로 교체.
이제 자식 프로세스는 입력된 프로그램을 실행.
백그라운드 실행 확인
쉘은 명령의 마지막 문자가 &인지 확인.
• &가 없으면 :
부모 프로세스는 wait()를 호출하여 자식 프로세스가 종료될 때까지 기다림.
• &가 있으면 :
부모 프로세스는 기다리지 않고, 바로 프롬프트를 다시 출력.
wait() 호출 (&가 없으면)
자식 프로세스가 종료(exit())되면, 부모 프로세스는 Block 상태에서 자식 프로세스의 상태를 회수하고 프롬프트를 다시 출력.
Booting the Kernel
CPU가 디스크에서 bootstrap loader의 위치를 찾아 메모리에 로드
: 부팅 시, CPU는 하드웨어의 초기화 및 부팅 로더를 디스크에서 읽어옴
Loader가 kernel의 위치를 찾아 메모리에 로드 및 실행
: 로더는 운영체제 커널을 메모리에 적재하고 실행을 시작.
Kernel은 process 0을 수작업으로 만듦
: 커널이 초기화되는 동안, 첫 번째 프로세스(process 0)를 생성.
Process image가 없는 특별한 프로세스
: 실행 가능한 코드 없이 생성되는 시스템의 기본 프로세스.
Process 0은 fork()/exec()로 process 1을 생성
: root process는 init 프로세스를 생성하기 위해 fork()와 exec() 시스템 호출을 사용.
fork()/exec()를 통해 셸을 생성
: 시스템의 첫 번째 사용자 레벨 프로세스로, /etc/init 실행 후 operator console 셸을 준비.
Process hierarchy
• root: 시스템 부팅 후 생성된 가장 최초의 프로세스 (process 0).
• init: root 프로세스가 fork()와 exec()를 호출하여 생성된 프로세스.
시스템 초기화 및 다른 프로세스 관리 역할을 수행.
• bash: 운영자 콘솔에서 실행될 기본 셸 프로세스.
• getty :
터미널에서 login:을 출력하고 대기(blocked).
사용자가 입력을 제공하면 login 프로세스를 실행.
• bash: 운영자 셸로서 대기.
• login:
화면에 passwd:를 출력하고 대기(blocked).
사용자가 올바른 암호를 입력하면 bash 프로세스를 실행.
• bash (운영자 셸 프로세스):
화면에 prompt를 출력하고 대기(blocked).
사용자가 명령어를 입력하면 fork()로 자식 프로세스(child bash)를 생성.
자식 프로세스가 끝나길 기다림 (wait() 호출).
Booting Sequence
: 컴퓨터가 부팅될 때 발생하는 단계별 과정
Sector 0 (Bootstrap Loader)
: 하드 디스크의 가장 첫 번째 섹터(Sector 0)에 저장된 bootstrap loader가 실행됨.
BIOS 또는 펌웨어가 ROM에서 실행되면서 부트스트랩 로더를 메모리로 로드하고 실행.
→ 운영 체제의 커널 이미지를 파일 시스템에서 메모리로 로드.
Kernel Image
부트스트랩 로더가 파일 시스템에서 커널 이미지를 메모리로 로드.
커널 이미지가 실행되면 운영 체제가 초기화됨.
→ 시스템의 하드웨어 초기화, 프로세스 0 생성.
Process 0 (Root Process)
커널은 부팅이 완료된 후 Process 0을 생성.
Process 0 : 메모리에 독립적인 이미지가 없는 커널 전용 프로세스로,
초기화 작업을 담당하며, 운영 체제의 다른 모든 프로세스의 부모 역할을 함.
→ Process 0은 fork()를 호출하여 Process 1을 생성.
Process 1 (Init Process)
Process 0이 fork()와 exec()를 호출하여 /sbin/init을 실행.
Init의 역할:
• 시스템의 초기화.
• 사용자 인터페이스를 제공하는 프로세스를 생성.
ex. /bin/bash 또는 getty
Process 2 이후
Init 프로세스는 추가적으로 터미널 프로세스(ex. bash)와 로그인 프로세스를 생성.
• /bin/bash: 운영자가 사용할 수 있는 쉘 환경 제공.
• getty: 터미널에서 로그인 화면을 출력.
"Daemon" Process
: 백그라운드에서 실행되며 사용자를 직접 상대하지 않는 서버형 프로세스.
형식은 사용자 프로세스이지만 커널의 작업을 도와주는 시스템 프로세스 역할.
• 부팅 시 시작되어 시스템 종료 시까지 실행.
• 특정 터미널이나 쉘과 연결되지 않음.
ex.
httpd: 웹 서버 프로세스.
ftpd: 파일 전송 프로토콜(FTP) 서버.
sshd: 보안 쉘(SSH) 데몬.
kswapd: page swapping daemon (메모리 관리).
Boot Time: 부팅 시 시작.
대기 상태: 요청이 없을 경우 Blocked 상태로 대기.
요청 도착: 요청이 도착하면 깨어나서 요청 처리.
요청 처리 완료: 다시 Blocked 상태로 돌아감.
• 크기(Small size)
: 커널 내부에 모든 프로세스를 포함할 경우 커널이 지나치게 커질 수 있음.
• 유연성(Flexibility)
: 독립적으로 실행되는 사용자 공간 프로세스로 유지하면 수정 및 업데이트가 쉬움.
Linux 의 Process 족보
Root Process
시스템 부팅 시 최초로 생성되는 프로세스로 process 0 (root)라고 함.
→ Init process를 생성 (PID 0).
대부분의 데몬 프로세스와 사용자 프로세스의 최상위 부모 역할.
Init Process
PID 1을 가지며, 시스템의 모든 프로세스가 init으로부터 시작됨.
→ 여러 데몬 프로세스와 사용자 프로세스를 fork() 및 exec() 시스템 콜을 통해 생성.
ex. kswapd: 메모리 관리 데몬.
sshd: 보안 연결 데몬.
httpd: 웹 서버 데몬.
SSHD와 Bash
• SSHD : 원격 접속을 처리하는 데몬.
getty와 login 과정을 거쳐 사용자를 인증하고, bash 세션을 생성.
• Bash (쉘): 명령어를 해석하고, 사용자 프로세스를 생성 (fork()/exec()) 및 관리.
각 사용자(예: User A, User B)마다 별도의 bash 프로세스가 생성됨.
User Process
사용자 요청에 의해 생성된 프로세스들.
각 bash의 자식 프로세스로 생성되며, 특정 작업(예: 프로그램 실행)을 담당.
ex. User Process 1-1에서 vi 실행.
• pstree 명령어:
현재 실행 중인 프로세스의 계층 구조를 보여줌.
프로세스 간 부모-자식 관계를 시각적으로 확인 가능.
(root → init → bash → 사용자 프로세스)