Linux 내부 구조 정리 - (3)
해당 블로그는 개인이 공부하고, 정리한 걸 기록하는 공간입니다.
오타, 오류가 존재할 수 있습니다. 댓글을 달아주시면 수정할 수 있도록 하겠습니다.
1.3 프로세스를 정복하는 자가 Linux를 정복하리라
1.3.1 fork와 exec는 프로세스의 분신과 변신
이 일련의 동작에는 프로세스 생성에 대한 깊은 이해를 할 수 있는 열쇠가 숨겨져 있다.
테스트용 Linux 서버를 런레벨 3으로 가동한 뒤 mingetty
프로세스를 확인해보자.
시스템 흐름에서 1번 흐름이다.
# ps -ef | grep "mingetty[y]"
root 1692 1 0 19:21 tty1 00:00:00 /sbin/mingetty /dev/tty1
root 1694 1 0 19:21 tty2 00:00:00 /sbin/mingetty /dev/tty2
root 1696 1 0 19:21 tty3 00:00:00 /sbin/mingetty /dev/tty3
root 1700 1 0 19:21 tty4 00:00:00 /sbin/mingetty /dev/tty4
root 1702 1 0 19:21 tty5 00:00:00 /sbin/mingetty /dev/tty5
root 1704 1 0 19:21 tty6 00:00:00 /sbin/mingetty /dev/tty6
grep 커맨드 중 [y] 는 grep
프로세스 자신을 결과에 출력하지 않는 것이다.
mingetty 프로세스 6개를 확인할 수 있고, 이는 6개의 가상 콘솔 login: 프롬프트이다.
여기서 유저명을 입력해서 유저명 입력 프롬프트 → 비밀번호 입력 프롬프트까지 진행한 뒤 다시 한번 mingetty 프로세스들을 확인해 보자.
# ps -ef | grep "mingetty[y]"
root 1694 1 0 19:21 tty2 00:00:00 /sbin/mingetty /dev/tty2
root 1696 1 0 19:21 tty3 00:00:00 /sbin/mingetty /dev/tty3
root 1700 1 0 19:21 tty4 00:00:00 /sbin/mingetty /dev/tty4
root 1702 1 0 19:21 tty5 00:00:00 /sbin/mingetty /dev/tty5
root 1704 1 0 19:21 tty6 00:00:00 /sbin/mingetty /dev/tty6
출력된 결과를 보면 프로세스 수가 하나 줄었고, 사라진 프로세스 찾기 위해 사라졌던 프로세서 ID(1692)로 검색하면 login 프로세스로 표시되는 것을 볼 수 있다.
1번 흐름과 2번 흐름 사이이다.
# ps -ef | grep "169[2]"
root 1692 1 0 19:21 tty1 00:00:00 /bin/login --
mingetty 프로세스가 exec() 시스템 콜
을 이용하여 login 프로세스가 되었다.
표로 fork
, exec
를 정리해보겠다.
status | exec | fork |
---|---|---|
실행 전 | pid = X 프로그램 = A |
pid = X 프로그램 = A |
실행 후 | pid = X 프로그램 = B |
pid = X 프로그램 = A ——– pid = Y 프로그램 = A |
설명 | 프로세스가 변한다. 부가 정보 [^1]는 남겨두고 프로세스가 실행되는 프로그램 코드만 새로운 코드로 바뀐다. |
프로세스(동일한 프로그램)가 두 개로 나뉜다. |
[^1] : 프로세서를 특정하기 위한 프로세스 ID, FCB(File Control Block), 파일, 파일 디스크림터(파일을 관리하기 위해 운영체제가 필요로 하는 파일의 정보를 가지고 있는 것)
이후 login 프로세스는 패스워드 입력을 받아 유저 인증을 한다.
로그인한 유저의 bash를 가동한다.
다시 한번 같은 프로세스 ID로 검색해보자.
밑의 출력 결과를 보면, login 프로세스는 그대로이고, bash 프로세스가 검색 결과에 포함되어있다.
# ps -ef | grep "169[2]"
root 1692 1 0 19:21 ? 00:00:00 login -- root
root 1818 1692 0 19:21 tty1 00:00:00 -bash
이것은 자식 프로세스로서 bash 프로세스가 가동됨을 알 수있다.
출력은 앞에서 부터 유저 ID
, 프로세스 ID
, 부모 프로세스 ID
이다.
기존 login 프로세스의 부모 프로세스는 init 프로세스이다.
일반적으로 부모 프로세스가 자식 프로세스를 만드는 것은 fork
라고 한다.
다만, 위의 경우는 login 프로세스가 bash를 포크한 것이 아니라, 부모 login 프로세스가 자식 login 프로세스를 fork 한 뒤 자식 프로세스가 exec를 통해 bash 프로세스로 생성된 것이다.
이런 테크닉은 fork-exec
라고 부른다.
fork
는 pid = fork(); 코드를 통해 만들어지는데, 해당 fork() 코드를 기준으로 새로운 프로세스가 하나 생성되며, 부모 프로세스의 pid에는 자신의 프로세스 ID, 자식 프로세스의 pid에는 0이 삽입된다.
이후, if 문을 통해 부모, 자식 프로세스 간 서로 다른 코드를 작성함으로서 자식 프로세스는 다른 프로세스로 exec
할 수 있는 것이다.
fork() 함수 수행시, 전처리(#include) 부터 시작하는 것이 아니라 fork 함수 행부터 자식 프로세스가 실행된다.
fork 실패 시 pid에는 음수가 들어간다.
if 문에서 음수에는 오류 상황 예외 처리를 해주면 된다.
프로세스가 끝날 때 흐름을 살펴보자.
- 부모 프로세스
부모 프로세스인 login은 함수wait
에서 자식 프로세스가 종료할 때까지 슬립 상태로 계속 기다린다.
wait
은 자식 프로세스가 종료되어 CHLD 시그널을 받을 때까지 슬립 상태로 기다리기 위한 함수이다.
밑의 코드를 살펴보면, 슬립 상태를 나타내는 S의 기호가 붙어있음을 알 수 있다.
# ps aux | grep "logi[n]"
root 1692 0.0. 0.2 77004 2532 ? Ss 19:21 00:00 login-- root
- 자식 프로세스
bash
커맨드 프롬프트에서 exit를 입력해서 로그아웃한다.
이때,bash
프로세스는 exit() 시스템 콜에 의해 종료되어 부모 프로세스 login에게 CHLD 시그널을 송신한다.
CHLD 시그널을 받은 login은 함수wait
에서 벗어나서 종료된다.
CHLD 시그널을 송신한 프로세스는 좀비 프로세스상태가 된다.
이때, 프로세스 상태를 확인하면 Z라고 표시된다.
부모 프로세스 측에서 CHLD 시그널을 무시하게 설정되있다면, 자식 프로세스는 계속 좀비 프로세스로 남게된다.
wait 함수를 통해 모든 자식 프로세스를 종료시키나면, 부모 프로세스도 종료된다.
이때, CHLD 시그널은 init 프로세스에게 보내진다.
init 프로세스는 login 프로세스가 종료된 것을 알고, 다시 한번 mingetty 프로세스를 가동한다.(fork-exec
)
마지막으로, fork-exec
에 대한 트러블이 존재하는데, fork-exec에서 새로운 프로세스를 생성할 때 처음 fork한 순간 자식 프로세스의 프로세스 명은 부모 프로세스와 동일하다.
그 뒤에 exec에 의해 프로세스 명이 바뀌는 것인데, 이 사이에 특정 프로세스의 프로세스 수를 감시(체크)하는 경우, fork-exec
에 의해 순간적으로 프로세스 수가 증가 할 수 있다.
“이 이름의 프로세스 명은 반드시 하나만 존재해야 한다. “ 라고 등록한 경우 감시 간격의 절묘한 타이밍으로 문제가 발생하고, 문제 원인을 찾기 어려울 수 있다.
1.3.2 작업 제어의 이것저것
■ 백그라운드 실행
커맨드의 끝에 &
를 붙여 실행하면 백그라운드에서 실행된다.
bash 프로세스가 fork-exec로 새로운 프로세스를 실행한 뒤, 기존에는 sleep 상태가 되어 자식 프로세스의 종료(CHLD 시그널) 기다렸다.
하지만 백그라운드로 실행하게 되면 자식 프로세스의 종료를 기다리지 않고 바로 프롬프트를 표시하게 된다.
여러 서버를 뒀을 때 가정으로 예시를 통해 살펴 보겠다.
#! /bin/sh
ssh node01 /user/local/bin/clstart
ssh node02 /user/local/bin/clstart
ssh node03 /user/local/bin/clstart
위의 코드는 백그라운드 실행이 아니다.
즉 node01의 clstart 실행 완료 → node02의 clstart 실행 완료 → node01의 clstart 실행 순으로 진행된다.
명백한 시간 낭비이다.
#! /bin/sh
ssh node01 /user/local/bin/clstart &
ssh node02 /user/local/bin/clstart &
ssh node03 /user/local/bin/clstart &
위의 예시는 각 커멘드를 백그라운드에서 실행시키는 중이다.
이 경우 3대의 서버에서 동시에 clstart를 실행시킬 수 있다.
하지만, 실행이 완료되기 전 sh 자체가 종료되어 커맨드 프롬프트로 돌아가게 된다.
이럴 경우, 커맨드 프롬프트가 표시된 후 백그라운드 커맨드의 출력 메세지가 표시되어 콘솔을 혼잡하게 한다.
이를 해결한 것이 wait
커맨드이다.
wait
커멘드를 실행하면 그 위치에서 슬립 상태로 대기하며, 백그라운드에서 실행 중인 자식 프로세스가 모두 종료되어 CHLD 시그널을 통지되기를 기다린다.
#! /bin/sh
ssh node01 /user/local/bin/clstart &
ssh node02 /user/local/bin/clstart &
ssh node03 /user/local/bin/clstart &
wait
결론적으로, 프로세스가 따로 따로 실행되어 처리되는 것을 비동기 처리라고하며, 부모 프로세스가 자식 프로세스의 완료를 기다린 뒤 다음으로 진행되는 것을 대기 처리라고 한다.
■ 리다이렉션
백그라운드에서 처리되는 출력을 직접 화면에 표시하면, 콘솔이 혼잡해진다고 앞에서 말했다.
이를 해결하기 위해 콘솔에 출력될 내용을 파일 등에 리다이렉트하는 것이 좋다.
대표적인 세 종류의 예를 보여주겠다.
bash | 의미 |
---|---|
# command >cmd.log 2>cmd.err & |
표준 출력을 cmd.log, 표준 에러 출력 cmd.err에 출력 |
# command >cmd.log 2>&1 & |
표준 출력과 표준 에러 출력을 cmd.log에 출력 |
# command >/dev/null 2>&1 & |
표준 출력과 표준 에러 출력을 버린다. |
>>
는 기존 파일에 내용을 추가하겠다는 의미이고, >
는 기존 파일의 존재 유무에 상관없이 새로 만들겠다는 의미이다.
>cmd.log
의 >
앞에 아무것도 없는 이유는 표준 출력의 파일 디스크립터 번호가 1번
, 표준 에러 출력의 파일 디스크립터 번호는 2번인데 1번의 경우 디폴트 값으로서 생략가능하기 때문이다.
■ screen 커맨드
bash 프로세스의 경우, 종료 될 때 자식 프로세스들에게 HUP 시그널을 보낸다.
이때,HUP 시그널을 받은 프로세스들은 함께 종료된다.
만약, 종료 시키기 싫다면 nohup
커멘드를 붙이면 HUP 시그널을 무시하게 된다.
nohup 이외에 screen
이라는 커멘드도 있다.
screen 커멘드는 가상의 터미널을 여러 개 만들 수 있고, 해당 가상 터미널을 놔두고 나올 수 있다.
다시 이전 가상 터미널에 다시 재접속하여 작업을 이어갈 수 있다.
실행 중인 작업은 ctrl
+ z
버튼으로 멈출 수 있고, fg 커멘드로 다시 실행 시킬 수 있다.
만약 백그라운드에서 실행시키고 싶다면, bg 커멘드를 붙이고 실행 시키면 된다.
반대로, 백그라운 작업도 fg 커맨드를 붙이면 포그라운드로 바꿀 수 있다.
포그라운드로 작업하기
fg %[작업 ID]
백그라운드로 작업하기
bg %[작업 ID]
현재 실행 중인 프로세스들의 작업 ID 확인하기
jobs
댓글남기기