Session 과 Process group

리눅스 에서 실행되는 모든 프로세스들을 Process ID (PID) 와 Parent Process ID (PPID) 관계로만 관리하기엔 부족한점이 있는데요. 그래서 session 과 process group 을 만들어 사용합니다. 예를들어 터미널을 열면 shell 이 실행되는 이때 shell PID 가 Session ID (SID) 가 되며 session leader 가 됩니다. 이후 프롬프트를 통해 명령을 실행시켜 생성되는 자손 process 들은 모두 같은 SID 를 갖게됩니다.

Process Group 은 작업 제어를 목적으로 함께 처리해야 할 관련 프로세스들의 그룹입니다. shell script 가 실행되면 process group 이 만들어 지는데 이때 스크립트 PID 가 Process Group ID (PGID) 가 되며 process group leader 가 됩니다. 새로 생성되는 프로세스는 parent process 의 PGID 를 상속하므로 이후 스크립트 내에서 실행되는 process 들은 모두 같은 PGID 를 갖게됩니다. 다음은 프롬프트 상에서 AA.sh 스크립트를 실행한 예입니다. 모두 bash shell PID 를 SID 로 가지고 있고 AA.sh PID 가 PGID 로 사용된 것을 볼 수 있습니다.

'|' 파이프를 통해 여러 명령을 동시에 실행시킬 때도 process group 이 만들어지는데 이때는 파이프로 연결된 명령들중에 첫번째 명령이 PGID 와 process group leader 가 됩니다. ( 스크립트 내에서 실행될 경우는 스크립트 PGID 를 따릅니다.) 다음은 프롬프트 상에서 cat | grep hello | wc -l 명령을 실행한 예입니다. 모두 bash shell PID 를 SID 로 가지고 있고 파이프로 연결된 명령 중에 첫번째 명령의 PID 가 PGID 로 사용된 것을 볼 수 있습니다.

이와 같이 process group 은 job control 을할때 기본단위 ( job ) 가 됩니다. 하나의 session 에서는 하나의 process group 만 foreground 가 될수있고 나머지는 background 가 됩니다.

실행되는 모든 프로세스는 프로세스 그룹에 속하게 되고, 또한 프로세스 그룹은 세션에 속하게 됩니다.

조회하고 신호보내기

현재 실행되고 있는 process 들의 ppid, pid, pgid, sid 관계는 다음과 같이 ps 명령을 통해 알아볼 수 있고 pgrep , pkill 명령을 사용하면 process group 이나 session 별로 조회하거나 시그널을 보낼 수 있습니다.

$ ps jf

$ ps fo user,ppid,pid,pgid,sid,comm

실행중인 스크립트를 종료하는 방법

스크립트를 종료할 때는 jobspec 을 이용하거나 pgid 를 이용해 process group 에 신호를 보내야 합니다. 또는 Ctrl-c 키를 누르는 것도 process group 에 신호를 보내는 방법 중에 하나입니다. 그렇지 않고 스크립트 pid 에만 신호를 보내게 되면 child process 는 종료되지 않고 남아 있게 됩니다. 다음은 ping 외부 명령을 실행하고 있는 스크립트에 pid 를 이용해 종료 신호를 보낸 경우인데 child process 인 ping 명령은 종료되지 않은 체 남아 있습니다.

-------- test.sh --------
#!/bin/bash
echo test.sh ... start

ping 192.168.1.1

echo test.sh ... end
-------------------------

# test.sh 스크립트 실행후 ps 조회
$ ps j -C test.sh
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
23973 25200 25200 23973 pts/16   25200 S+    1000   0:00 /bin/bash ./test.sh

# test.sh 스크립트 pid 를 이용해 종료
$ kill 25200

# child process 인 ping 명령은 종료되지 않고 ppid 가 init 으로 바뀌어 남아있다.
$ ps j -C ping
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1 25201 25200 23973 pts/16   23973 S     1000   0:00 ping 192.168.1.1

다음과 같이 pgid 를 이용해 스크립트를 종료합니다.

  • kill -- -13056 (pgid 앞에 - 문자를 붙인다.)
  • pkill -g 13056
$ ps j -C test.sh
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
23973 25517 25517 23973 pts/16   25517 S+    1000   0:00 /bin/bash ./test.sh

# test.sh 스크립트 pgid 를 이용해 종료
$ kill -- -25517

# 같은 pgid 를 갖는 ping 명령도 함께 종료되었다.
$ ps j -C ping
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

새로운 session id 로 실행하기

스크립트를 background 로 실행시킬때 setsid 명령을 이용하면 새로운 sid, pgid 가 할당되고 ppid 도 init 으로 바뀌어 실행됩니다. sid 가 바뀌므로 controlling terminal 에서도 떨어져 나가고 parent 도 init 이 되므로 스크립트를 daemon 으로 실행시키는 결과를 갖습니다.

setsid daemon.sh > /dev/null 2>&1 < /dev/null &

데몬만들기 : http://blog.n01se.net/blog-n01se-net-p-145.html

Quiz

ps axj 명령을 실행해 보면 맨 위에서 다음 두 항목을 찾을 수 있는데요. 하나는 시스템의 시작과 종료, 프로세스 생성, 모니터링에 관계된 init 이고 다른 하나는 kernel thread 를 생성하는 kthreadd 입니다. 그런데 모두 PPID 가 0 번인 것을 볼 수 있습니다. PID 0 은 어떤 프로세스일까요?

kernel 초기화 단계에서 실행되는 startup function 을 swapper 또는 process 0 으로 부릅니다. 여기서는 paging 관련 메모리 설정, interrupt handling 설정, 커널에서 사용하는 구조체 설정 등 여러 가지 초기화 작업을 하고 첫 번째 user space 프로세스인 pid 1번 init 과 kernel space 프로세스인 pid 2번 kthreadd 을 생성합니다. ( 따라서 모든 user space 프로세스는 init 의 자손 프로세스가 되고, 모든 kernel space 프로세스는 kthreadd 의 자손 프로세스가 됩니다. ) 이후에는 idle 상태가 되어서 시스템 내에 running 프로세스가 없을 경우 반복적으로 hlt 어셈블리 명령을 실행합니다. init 프로세스는 원래 커널 스레드였는데 이후에 exec() 시스템콜을 실행하여 user space 프로세스가 되어 이름에 [ ] 가 없습니다.


커널의 경우 하드웨어 인터럽트가 발생하거나, system call, exception 이 발생했을때 현재 실행 중인 프로세스가 중단된 상태에서 kernel mode 로 진입하여 실행되는 형태이므로 따로 프로세스라는 개념이 없습니다( 커널 스레드는 제외 ). 아래 그림은 실제 프로세스 주소공간을 (virtual address space) 나타내는데요. 커널 코드와 데이터가 위치한 커널 쪽 주소는 shared library 처럼 모든 프로세스에서 공유되므로 스케줄러에 의해 다른 프로세스로 스위칭이 되어도 항상 실행될 수가 있습니다. 또한 user mode 에서 접근할 경우 page fault 가 발생하므로 접근할 수가 없습니다.

커널 모드에서 사용되는 virtual address 는 커널 자체에서 사용하는 페이지만 매핑되는 것이 아니고 특정 offset 에 시스템에 설치된 전체 physical memory 가 매핑됩니다. 그러니까 커널은 기본적으로 모든 physical memory 에 접근할 수 있고 각각의 사용자 프로세스에서 사용하는 페이지를 컨트롤할 수 있습니다.