Process State Codes
ps ax
명령을 실행하면 STAT 컬럼에 현재 프로세스의 상태를 나타내는 기호들이 표시됩니다. 아래 첫번째 테이블의 상태 기호가 제일 앞에 오고 그다음에 두번째 테이블의 부가적인 기호가 표시됩니다.
기호 | 설명 |
---|---|
D | uninterruptible sleep (usually IO) |
I | Idle kernel thread |
R | running or runnable (on run queue) |
S | interruptible sleep (waiting for an event to complete) |
T | stopped, either by a job control signal or because it is being traced. |
t | stopped by debugger during the tracing |
X | dead (should never be seen) |
Z | defunct ("zombie") process, terminated but not reaped by its parent. |
다음은 BSD 포멧의 부가적인 정보를 나타냅니다.
기호 | 설명 |
---|---|
< | high-priority (not nice to other users) |
N | low-priority (nice to other users) |
L | has pages locked into memory (for real-time and custom IO) |
s | is a session leader |
l | is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) |
+ | is in the foreground process group. |
다음은 현재 실행중인 linux OS 의 ps 정보를 발췌한 것입니다.
I : 상단에는 현재 idle 상태에 있는 kernel threads 들을 볼 수 있습니다.
S : 대부분의 프로세스들이 이벤트 처리를 위해 현재 대기중에 있는것을 볼 수 있습니다.
R : ps af 명령은 현재 실행 중이므로 R 로 표시됩니다.
+ : 터미널에 연결돼 있는 interactive bash shell 프로세스들과 ps af 명령은 현재 foreground 로 실행 중인 것을 알 수 있습니다.
s : 터미널에 연결돼 있는 interactive bash shell 프로세스들은 session leader 인것을 알 수 있습니다.
T : test.sh 명령은 현재 suspend job control 명령에 의해 stop 상태입니다. ctrl-z 에 의해 프로그램 실행이 중단됐을때도 이 상태가 됩니다.
Z : sub2.sh 은 test.sh 에서 실행된 background process 인데 현재 종료된 상태 (defunct) 이나 parent 프로세스가 stop 되어있어 좀비 상태로 남아 있습니다. parent 프로세스가 실행을 재개하면 삭제됩니다.
좀비 프로세스는 이미 종료된 프로세스로 프로세스 테이블에서 pid 만 차지하고 있는 상태입니다. 그러므로 실행 중에 사용했던 메모리나 리소스들은 모두 반환된 상태입니다. child process 가 실행되면 parent process 는 종료 상태 값을 얻기 위해 wait system call 을 실행하는데 이때 종료된 child process 가 프로세스 테이블에서 제거됩니다. 하지만 위의 경우는 parent process 가 stop 상태에 있어서 프로세스 테이블에서 제거되지 못하고 좀비 상태로 남아있습니다.
t : 현재 stop 상태인 sleeper 명령은 gdb 에의해 trace 되고 있습니다.
< :
[ ]
로 표시되는 대부분의 kernel threads 들과 pulseaudio 는 현재 높은 우선순위로 실행되고 있습니다.N : rtkit-daemon 은 현재 낮은 우선순위로 실행되고 있습니다.
l : X, pulseaudio, chrome 같은 프로그램들은 멀티 스레드를 사용하고 있습니다.
L : 메모리 locking 은 프로세스가 사용하는 가상 주소 공간을 물리적인 메모리에 lock 합니다. lock 된 페이지는 paging, swapping 에서 제외되고 unlock 될때까지 메모리에 존재하는 것이 보장되므로 주로 실시간 처리가 요구되는 프로그램에서 latency 를 줄이기 위해 사용됩니다.
D : D ( uninterruptible sleep ) 은 S ( interruptible sleep ) 과는 다르게 sleep 하는 동안 signal을 처리하지 않습니다. 심지어 kill 신호로 프로세스를 종료시킬 수도 없습니다. 주로 디바이스 드라이버에서 디스크나 네트워크 I/O 를 기다릴때 사용됩니다
[ ]
으로 표시되는 경우 : ps 명령은 COMMAND 컬럼의 값을 표시할때 /proc/<pid>/cmdline 에 있는 내용을 사용하는데 여기에 값이 없을 경우 /proc/<pid>/stat 에 있는 명령 이름에 [ ] 를 더해서 표시한다고 합니다. 위 그림에서 보면 kernel threads 들과 zombie process 가된 sub2.sh 이 [ ] 로 표시되고 있습니다.
프로세스 상태 변화 과정
OS 가 실행되는 방식
OS 가 실행되는 방식을 다음과 같이 분류해 볼 수 있습니다.
- interrupt context
하드웨어 인터럽트는 highest priority 로 현재 실행 중인 프로세스와 관계없이 즉시 처리됩니다. 인터럽트 핸들러는 빠른 시간 내에 처리돼야 하므로 top half 와 bottom half 로 나누어서 top half 를 먼저 처리하고 시간이 걸리는 bottom half 는 이후에 softirq, tasklet 에서 처리되거나 kernel thread 에의해 처리됩니다. top half 에서 실행될 때는 중복 실행되는 것을 방지하기 위해 모든 코어에서 해당 interrupt 가 disable 되고 실행 중에 block 되는 일이 없습니다.
스케줄러는 프로세스가 직접 스케줄 함수를 호출하여 실행될 수도 있고( voluntary ), timer interrupt 에의해 실행될 수도 있는데 ( involuntary ) 이때는 interrupt handler 가 특정 flag 값을 설정하면 커널 모드에서 유저 모드( process context )로 복귀 과정에서 check 되어 실행됩니다.
- process context
process context 는 프로세스이므로 스케줄러에 의해 스케줄 될 수 있다는 점입니다. 그러므로 여기서는 실행 중에 sleep, block 될 수 있고 preempted 될 수도 있습니다. 프로그램이 실행되면서 발생하는 system call, exception 이 처리되는 것이 여기에 속합니다. system call 이 atomic 하다는 말이 있는데 이것은 실행 중간에 signal 에의해 interrupt 됐다가 이어서 다시 실행되지 않는다는 뜻입니다. system call 은 wait 상태에서 signal sending 이 되면 EINTR 에러를 반환하고 종료하거나 자동으로 다시 처음부터 시작합니다.
[ kernel threads ]
이것은 위에서 [ ]
로 표시되는 커널 스레드에 해당합니다.
커널 스레드는 일반 사용자 스레드와 동일하게 스케줄 되고 실행되는데
차이점은 유저모드가 아닌 커널모드에서 실행된다는 점이 다릅니다.
/proc/$PID/maps 에서는 프로세스의 메모리 매핑 정보를 볼 수 있는데
커널 스레드는 정보가 표시되지 않습니다.
- atomic context
분류를 process context 와 atomic context 로 하기도 합니다. process context 가 아니기 때문에 스케줄러에 의해 스케줄 되지 못하므로 여기서는 실행 중에 sleep, block 될 수 없고, mutex 도 사용할 수 없습니다. 또한 user space virtual address 에도 접근할 수 없는데 page fault 가 발생할 수 있기 때문입니다 ( 그러면 scheduling 돼야 하므로 )
# 인터럽트 후반부 처리는 softirq 또는 커널 스레드를 이용해 처리될 수 있는데
# 인터럽트 발생 빈도가 아주 높거나 바로 응답이 되어야 할 경우는 softirq 를 사용합니다.
$ watch -d -n1 'cat /proc/softirqs'
# 리눅스 커널에서 제공하는 10가지 softirq 서비스
HI 가장 우선 순위가 높으며 TASKLET_HI로 적용
TIMER per-cpu 동적 타이머로 사용
NET_TX 네트워크 패킷 송신용으로 사용
NET_RX 네트워크 패킷 수신용 사용
BLOCK 블록 디바이스에서 사용
IRQ_POLL IRQ_POLL 연관 동작
TASKLET 일반 태스크릿으로 사용
SCHED 스케줄러에서 주로 사용
HRTIMER 현재 사용 안하나 하위 호완성을 위해 남겨둠
RCU RCU 처리용으로 사용
# ksoftirqd 는 per-cpu 커널 스레드로 softirq 서비스를 스레드 레벨에서 처리합니다.
# softirq 서비스는 프로세스 실행이 멈춘 상태에서 동작하므로 실행 시간이 길면 문제가 될수있어
# 특정 시간이 넘어서면 나머지 부분을 ksoftirqd 커널 스레드가 처리합니다.
$ ps ax | grep ksoftirq[d]
9 ? S 0:29 [ksoftirqd/0] # 이름 뒤에 보이는 숫자는 cpu core 를 나타냄
18 ? S 0:15 [ksoftirqd/1]
24 ? S 0:06 [ksoftirqd/2]
. . .
$ ps ax | grep irq[/] # irq 스레드 : 인터럽트 후반부 처리를 위한 전용 프로세스
183 ? S 0:00 [irq/27-pciehp] # 27 은 인터럽트 번호, pciehp 는 이름
621 ? S 0:00 [irq/42-iwlwifi]
21241 ? S 0:00 [irq/39-mei_me]
21242 ? S 0:00 [irq/18-mmc0]
# worker 스레드 : work 를 실행하는 workqueue 전용 프로세스로
# watch -d -n.5 'ps ax | grep kworke[r]' 를 해보면 스레드 이름이 계속 변경되는 것을 볼 수 있습니다.
$ ps ax | grep kworke[r]
26 ? I< 0:00 [kworker/2:0H-kblockd]
44 ? I< 0:00 [kworker/5:0H-kblockd]
178 ? I< 0:00 [kworker/u17:0]
. . .
softirq 전체 실행 흐름도 http://egloos.zum.com/rousalome/v/9978837
Local timer interrupts 실시간 조회해보기
- Real Time Clock (RTC)
- Programmable Interval Timer (PIT)
- Local APIC timer ( 각 코어마다 가지고있는 Time Stamp Counter (TSC) based timer )
컴퓨터에는 시계가 3 종류가 있습니다. 컴퓨터의 전원을 껐다가 나중에 다시 켜도 시간이 틀리지 않는것은 보드에 있는 건전지로 유지되는 Real Time Clock (RTC) 이 있기 때문인데요. 하지만 이것은 프로그래밍에 자주 사용되지는 않습니다. ( hardware clock 이라고도 함 )
Programmable Interval Timer (PIT) 가 프로그래밍에 사용되는 것으로 보통 system timer ( 또는 timer 또는 system clock ) 라고 합니다. 이 칩에 의해서 주기적으로 인터럽트가 발생해서 커널이 필요한 작업을 하게 됩니다. 가령 10 milliseconds 마다 인터럽트가 발생한다면 ( 이것을 tick 이라고 함 ) 인터럽트가 발생했을 때 커널은 10 milliseconds 가 지난 것을 알 수 있겠죠. 그에 따라 현재 시간을 갱신하고, 현재 실행 중인 프로세스의 timeslice 를 차감하거나 타이머 걸어놓은 것이 있으면 expire 하는 등의 처리를 하게 됩니다.
그런데 이 주기적인 tick 을 이용하는 것은 tick 간격을 크게 하면 그 이하로는 스케줄링을 할 수 없게 되고 그렇다고 tick 간격을 너무 작게하면 tick 처리에만 CPU 시간이 많이 소모되게 됩니다. 그리고 무엇보다도 요즘 같은 저전력 시대에 CPU 가 idle 상태에 남아있는 것을 방해합니다.
따라서 요즘에는 PIT 를 이용하는 주기적인 tick 을 사용하지 않고 ( tickless ), 각 CPU 코어 마다 가지고 있는 Local APIC timer 를 이용해서 커널이 직접 다음 tick 을 스케줄 합니다 ( dynamic tick ). 일종의 on-demand 형식이기 때문에 새로운 태스크가 처리 대기열에 들어오지 않는 이상 CPU 는 저전력 idle 상태로 남아 있을 수 있게 됩니다.
/proc/interrupts
를 이용해서 시스템의 hardware interrupts 를 조회해 볼 수 있습니다.
제일 왼쪽에 있는 값이 인터럽트 번호인데 제일 위의 0 번이 PIT 입니다.
만약에 PIT 값이 변동이 없고 0 에 가까우면 그것은 PIT 를 사용하지 않는 tickless kernel 입니다.
다음은 각 코어마다 가지고 있는 Local APIC timer 의 인터럽트를 조회해보는 것인데요. PIT 인터럽트의 경우는 global interrupt 로 어떤 코어에도 전달될 수 있지만 Local APIC timer 인터럽트는 해당 코어에만 전달됩니다. 아래 명령을 실행했을 때 보이는 숫자는 interrupt 가 처리된 횟수를 나타냅니다. 각 코어 별로 숫자가 표시되고 값이 증가되는 것을 볼 수 있습니다.
$ watch -d -n1 'grep -C3 -i "local timer interrupts" /proc/interrupts'
위로 두 번째는 저같은 경우 IR-PCI-MSI 409600-edge enp0s25 네트워크 카드로 되어있는데 인터넷에서 파일을 다운로드할 때 조회해 보면 값이 크게 증가하는 것을 볼 수 있습니다. 키보드, 마우스, 디스크도 한번 찾아보시고 버튼을 눌렀을 때 몇번 인터럽트가 발생하는지 알아보세요.
네트워크 카드 같은 주변장치에서 발생하는 인터럽트의 처리를 여러 코어에 분배할 수도 있지만 각 코어마다 cache 가 있기 때문에 ( 동기화해야 하므로 ) 효율적이 못하여 특정 코어에 할당합니다.
/proc/irq/번호
디렉토리에서 smp_affinity 파일을 통해 코어를 변경할 수 있습니다.New API (NAPI) : high-speed 네트워크 에서는 interrupt 처리에만 많은 CPU 자원이 소모되므로 polling 방식을 함께 사용하여 오버헤드를 줄입니다.
Local APIC timer 는 periodic, one-shot 모드 에서는 카운트에 CPU's external frequency (즉 bus frequency) 가 사용되고 TSC-deadline 모드 에서는 CPU's internal frequency 가 사용됩니다. 그러니까 나노초까지 컨트롤이 가능한 hrtimer (high resolution) 타이머 입니다. PIT 은 standard frequency (1,193,182 Hz) 로 정해져 있습니다. ( https://wiki.osdev.org/APIC_timer )
# /proc/timer_list 를 통해서 현재 pending timers, clock-event sources 등을 볼수있습니다.
$ sudo watch -n.5 'grep -m1 -A20 "^active timers" /proc/timer_list'
$ sudo watch -n.5 'grep -m1 -B21 ^jiffies /proc/timer_list'
시스템에서 사용 가능한 clock sources 에대해 좀더 자세한 설명은 여기를 참고하세요
# 현재 시스템에서 사용 가능한 clock sources
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm
# 현재 선택되어 사용되고 있는것
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc
Quiz
RTC 도 다른 타이머와 동일하게 periodic, one-shot alarm 인터럽트를 사용할 수 있는데요. 그런데 RTC 은 다른 타이머와 달리 별도의 건전지로 시간이 유지되기 때문에 시스템이 suspend 또는 power off 되어 있더라도 나중에 alarm 인터럽트를 발생시켜 스스로 시스템을 깨울 수가 있습니다.
다음은 시스템을 suspend 상태로 진입시킨 후에 60 초 후에 자동으로 깨우는 것입니다.
# 실행중인 모든 프로세스들이 frozen 상태가 되고 CPU 는 idle 상태가 됩니다.
# 시스템이 아직 running 상태이므로 Suspend-to-RAM 보다는 전력 saving 이 적습니다.
$ sudo rtcwake -m freeze -s 60
rtcwake: wakeup from "freeze" using /dev/rtc0 at Sun Mar 29 02:03:04 2020
# Suspend-to-RAM 모드로 메모리를 제외하고 시스템 전체가 저전력 상태로 진입합니다.
$ sudo rtcwake -m mem -s 60
rtcwake: wakeup from "mem" using /dev/rtc0 at Sun Mar 29 02:03:23 2020
# 이것은 Suspend-to-disk (hibernation) 모드로 메모리 내용을 swap 디스크에 저장하고
# 시스템 전원이 완전히 off 되는 것입니다.
# 이 모드를 사용하려면 시스템 메모리의 2 배의 swap space 가 있어야 합니다.
$ sudo rtcwake -m disk ...
# '-m off' 는 시스템이 shutdown 되고 power off 되는 것입니다.
# 내일 아침 06:30 분에 자동으로 컴퓨터가 켜집니다.
$ sudo rtcwake -m off -t `date +%s -d 'tomorrow 06:30'`
# '-m no' 는 바로 suspend 되거나 power off 되지는 않고 단지 타이머만 설정됩니다.
# 나중에 작업을 마치고 컴퓨터의 전원을 끄거나 suspend 해두면 내일 아침에 자동으로 켜집니다.
# 설정된 타이머를 취소하고 싶으면 'rtcwake -m disable' 명령을 사용하면 됩니다.
$ sudo rtcwake -m no -t `date +%s -d 'tomorrow 06:30'`
다음과 같이 하면 컴퓨터를 알람시계로 사용할 수 있습니다.
# 내일 아침 06:30 분에 컴퓨터가 suspend 모드에서 깨어남과 동시에 musicplayer 가 실행됩니다.
# 만약에 hardware clock 이 UTC (Universal Time Coordinated) 이 아니라 local time (예:한국시간)
# 으로 설정되어 있으면 rtcwake 명령에 '-l' 옵션을 추가해야 합니다.
$ sudo rtcwake -m mem -t `date +%s -d 'tomorrow 06:30'` && musicplayer
# hardware clock 시간은 다음 명령으로 확인해볼 수 있습니다.
# 한국일 경우 현재 시간보다 9 시간 적으면 UTC 입니다.
$ cat /proc/driver/rtc
rtc_time : 01:10:10
rtc_date : 2020-11-14
. . .
$ date '+%F %T'
2020-11-14 10:10:12
-----------------------------------------------------------------
# suspend 중간에 다시 컴퓨터를 킬경우에도 '&&' 연산자로 설정한 명령이 실행되는데
# 다음과 같이 하면 실행되지 않게 할 수 있습니다.
rtcwake_fun () {
local start expire delta
start=`date +%s`
expire=$1
sudo rtcwake -m mem -s "$expire" &&
{
delta=$( expr `date +%s` - "$start" )
if ! [ "$delta" -lt "$expire" ]; then
echo 111111111111111111
fi
}
}
# 60 초가 지나면 'echo 1111' 명령이 실행되지만 그 이전에 다시 킬경우는 실행되지 않는다.
rtcwake_fun 60
깨어난 후에 /proc/interrupts 의 rtc
을 조회해 보면 CPU0 값이 증가된 것을 확인할 수 있습니다.
$ grep rtc /proc/interrupts | column -t
8: 87 0 0 0 1 0 0 0 IR-IO-APIC 8-edge rtc0
철도 시간표가 유닉스 시간이 되기까지: https://parksb.github.io/article/39.html