Process

Process

Process 모듈은 운영체제 아래에서 돌아가는 "프로세스"를 나타내요. 이 모듈의 메서드들은 현재 프로세스와 그 자식 프로세스를 관리하는 데 쓰이죠.

출처: Ruby 4.0 API

프로세스 생성 (Process Creation)

다음 메서드들은 각자 주어진 명령을 새 프로세스나 서브셸에서 실행해요. 프로세스로 실행될지 서브셸로 실행될지는 명령의 형태에 달려 있어요. command_line인지 exe_path인지를 보면 되는데, 자세한 건 아래에서 설명할게요.

  • Process.spawn, Kernel#spawn — 명령을 실행하고, 끝나기를 기다리지 않고 새 pid를 반환해요.
  • Process.exec — 명령을 실행하면서 현재 프로세스를 대체해요.

추가로:

  • Kernel#system — 주어진 명령줄(문자열)을 서브셸에서 실행하고 true·false·nil을 반환해요.
  • Kernel#\``(백틱) — 주어진 명령줄(문자열)을 서브셸에서 실행하고 그 $stdout` 문자열을 반환해요.
  • Open3 모듈 — 자식 프로세스의 $stdin, $stdout, $stderr 스트림에 접근할 수 있게 자식 프로세스를 만들어 줘요.

실행 환경 (Execution Environment)

선택적인 맨 앞 인자 env는 이름/값 쌍의 해시예요. 이름은 각각 문자열, 값은 문자열이거나 nil이죠. 각 쌍은 새 프로세스의 ENV에 추가돼요.

Process.spawn(                'ruby -e "p ENV[\"Foo\"]"')
Process.spawn({'Foo' => '0'}, 'ruby -e "p ENV[\"Foo\"]"')

출력:

"0"

효과는 보통 envENV#update를 호출하는 것과 비슷해요. 값이 nil이 아니면 각 환경 변수를 만들거나 갱신하고, nil이면 삭제하죠.

다만 새 프로세스가 실패하면 호출 프로세스에 일부 변경이 남을 수 있어요. 예를 들어 하드 자원 한도(hard resource limits)는 복원되지 않아요.

command_line 또는 exe_path 인자

필수 문자열 인자는 다음 둘 중 하나예요:

  • command_line — 셸 예약어나 특수 빌트인으로 시작하거나, 메타 문자를 하나 이상 포함하는 경우.
  • exe_path — 그 외의 경우.

command_line 인자

문자열 인자 command_line은 셸에 넘길 명령줄이에요. 셸 예약어로 시작하거나, 특수 빌트인으로 시작하거나, 메타 문자를 포함해야 해요:

system('if true; then echo "Foo"; fi')          # => true  # Shell reserved word.
system('exit')                                  # => true  # Built-in.
system('date > /tmp/date.tmp')                  # => true  # Contains meta character.
system('date > /nop/date.tmp')                  # => false
system('date > /nop/date.tmp', exception: true) # Raises RuntimeError.

명령줄에는 명령의 인자와 옵션도 들어갈 수 있어요:

system('echo "Foo"') # => true

출력:

Foo

exe_path 인자

exe_path는 다음 둘 중 하나예요:

  • 호출할 실행 파일의 문자열 경로. 예: system('/usr/bin/date'). 공백을 포함하는 경로나 명령 이름이면서 인자가 없으면 위의 command_line과 구분이 안 되므로, 플랫폼에 맞게 인용하거나 이스케이프하거나, 아래 배열 형태를 써야 해요.
    • exe_path에 경로 구분자가 없으면 PATH 환경 변수에 지정된 디렉터리들에서 실행 파일을 찾아요. "실행 가능"의 의미는 플랫폼에 따라 달라요. 내용이 제대로 된 실행 포맷이 아닐 수도 있고, 그 경우 Unix 계열에서는 system(3)처럼 /bin/sh로 실행을 시도해요.
  • 실행 파일 경로와 실행 중인 프로세스 이름에 쓸 문자열을 담은 2-요소 배열. 예: pid = spawn(['sleep', 'Hello!'], '1').

인자 args

command_line이 공백·탭 외에는 셸 메타 문자를 포함하지 않거나, exe_path가 주어지면 Ruby가 실행 파일을 직접 호출해요. 이 형태는 셸을 쓰지 않아요:

spawn("doesnt_exist")       # Raises Errno::ENOENT
spawn("doesnt_exist", "\n") # Raises Errno::ENOENT

spawn("doesnt_exist\n")     # => false
# sh: 1: doesnot_exist: not found

오류 메시지는 셸에서 온 것이고 시스템에 따라 달라져요.

exe_path 뒤에 인자 args를 하나 이상 주면, 각각이 실행 파일에 넘길 인자나 옵션이 돼요:

system('echo', '<', 'C*', '|', '$SHELL', '>')   # => true

출력:

< C* | $SHELL >

다만 Windows에서는 예외가 있어요. 아래 Windows에서의 실행 셸 부분을 참고하세요.

공백을 포함한 경로를 셸 없이 인자 없이 호출하고 싶다면 2-요소 배열 exe_path를 쓰는 게 필요해요:

path = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
spawn(path) # Raises Errno::ENOENT; No such file or directory - /Applications/Google
spawn([path] * 2)

실행 옵션 (Execution Options)

선택적인 맨 뒤 인자 options는 실행 옵션들의 해시예요.

작업 디렉터리 (:chdir)

기본적으로 새 프로세스의 작업 디렉터리는 현재 프로세스와 같아요. 옵션 :chdir로 새 프로세스의 작업 디렉터리를 설정할 수 있어요:

Process.spawn('ruby -e "puts Dir.pwd"', {chdir: '/tmp'})

출력:

/tmp

현재 프로세스의 작업 디렉터리는 바뀌지 않아요.

파일 리다이렉션 (파일 디스크립터)

새 프로세스에서 파일 리다이렉션을 하려면 실행 옵션을 써요. 옵션의 키는 정수 파일 디스크립터(fd)나 fd 배열(여러 소스 지정)이 될 수 있어요. fd를 가리키는 축약 기호도 있어요:

  • :in — 파일 디스크립터 0 (STDIN)
  • :out — 파일 디스크립터 1 (STDOUT)
  • :err — 파일 디스크립터 2 (STDERR)

소스에 주는 값은 다음 중 하나예요:

  • n — 부모 프로세스의 fd n으로 리다이렉트
  • filepathopen(filepath, mode, 0644)로 해당 파일로/에서 리다이렉트. mode는 소스 :in이면 'r', 소스 :out·:err이면 'w'
  • [filepath]open(filepath, 'r', 0644)로 파일에서 리다이렉트
  • [filepath, mode]open(filepath, mode, 0644)로 리다이렉트
  • [filepath, mode, perm]open(filepath, mode, perm)으로 리다이렉트
  • [:child, fd] — 리다이렉트된 fd로 리다이렉트
  • :close — 자식 프로세스에서 파일 디스크립터를 닫음

환경 변수 (:unsetenv_others)

기본적으로 새 프로세스는 부모 프로세스의 환경 변수를 물려받아요. 실행 옵션 키 :unsetenv_otherstrue를 주면 새 프로세스의 환경 변수를 비워요. env 실행 옵션으로 지정한 변경은 새 프로세스가 환경 변수를 물려받거나 비운 뒤에 적용돼요.

파일 생성 접근 (:umask)

실행 옵션 :umask로 파일 생성 접근을 설정해요:

command = 'ruby -e "puts sprintf(\"0%o\", File.umask)"'
options = {:umask => 0644}
Process.spawn(command, options)

출력:

0644

프로세스 그룹 (:pgroup, :new_pgroup)

기본적으로 새 프로세스는 부모와 같은 프로세스 그룹에 속해요. 실행 옵션 :pgroup에 다른 값을 주면 다른 그룹을 지정할 수 있어요:

  • true — 새 프로세스를 위한 새 프로세스 그룹 생성
  • pgid — 그룹 ID가 pgid인 프로세스 그룹에 새 프로세스 생성

Windows에서만 실행 옵션 :new_pgrouptrue를 주면 새 프로세스 그룹을 만들어요.

자원 한도 (Resource Limits)

실행 옵션으로 자원 한도를 설정할 수 있어요. 이 옵션들의 키는 :rlimit_<resource_name> 형태의 심볼이고, <resource_name>Process.setrlimit에서 설명하는 문자열 자원 이름을 소문자로 바꾼 거예요. 예를 들어 키 :rlimit_cpu는 자원 한도 'CPU'에 해당하죠. 값은 정수(현재·최대 한도 모두 지정)이거나 정수 2-요소 배열(현재·최대 한도)이에요.

파일 디스크립터 상속

기본적으로 새 프로세스는 부모의 파일 디스크립터를 물려받아요. 실행 옵션 :close_others => true를 주면, 리다이렉트되지 않은 비표준 fd(3 이상)를 닫아 상속을 수정할 수 있어요.

실행 셸 (Execution Shell)

Unix 계열에서 호출되는 셸은 /bin/sh예요. 전체 문자열 command_line이 셸의 -c 옵션 인자로 넘겨져요. 셸은 명령줄에 정상적인 셸 확장을 수행해요:

system('echo $SHELL: C*') # => true

출력:

/bin/bash: CONTRIBUTING.md COPYING COPYING.ja

Windows에서의 실행 셸

Windows에서 호출되는 셸은 환경 변수 RUBYSHELL(정의되어 있을 때) 또는 COMSPEC으로 결정돼요. RUBYSHELL에는 -c 옵션 인자로, /bin/sh도 마찬가지, COMSPEC에는 /c 옵션 인자로 전체 문자열 command_line이 넘겨져요. 셸은 다음 경우에 자동으로 호출돼요:

  • 명령이 echo 같은 cmd.exe 빌트인일 때.
  • 실행 파일이 .bat·.cmd로 끝나는 배치 파일일 때.

표준 셸 cmd.exe는 환경 변수 확장은 하지만 글롭(globbing) 기능은 없어요:

system("echo %COMSPEC%: C*") # => true

출력:

C:\WINDOWS\system32\cmd.exe: C*

무엇이 있나 (What's Here)

현재 프로세스 getter: ::argv0, ::egid, ::euid, ::getpgrp, ::getrlimit, ::gid, ::pid, ::ppid, ::uid

현재 프로세스 setter: ::egid=, ::euid=, ::gid=, ::setproctitle, ::setpgrp, ::setrlimit, ::setsid, ::uid=

현재 프로세스 실행: ::abort, ::daemon, ::exec, ::exit, ::exit!, ::warmup

자식 프로세스: ::detach, ::fork, ::kill, ::spawn, ::wait·::waitpid, ::wait2·::waitpid2, ::waitall

프로세스 그룹: ::getpgid, ::getpriority, ::getsid, ::groups, ::groups=, ::initgroups, ::last_status, ::maxgroups, ::maxgroups=, ::setpgid, ::setpriority

타이밍: ::clock_getres, ::clock_gettime, ::times

상수 (Constants)

CLOCK_* 상수들(CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_BOOTTIME 등)은 Process.clock_gettime에서 쓰는 시계를 지정해요. PRIO_PGRP·PRIO_PROCESS·PRIO_USERProcess.setpriority에 쓰이고요.

RLIMIT_* 상수들은 Process.setrlimit·getrlimit에서 쓰는 자원 한도를 나타내요:

  • RLIMIT_AS — 가상 메모리(주소 공간)의 최대 크기(바이트)
  • RLIMIT_CORE — 코어 파일의 최대 크기
  • RLIMIT_CPU — CPU 시간 한도(초)
  • RLIMIT_DATA — 데이터 세그먼트의 최대 크기
  • RLIMIT_FSIZE — 프로세스가 만들 수 있는 파일의 최대 크기
  • RLIMIT_MEMLOCK — RAM에 잠글 수 있는 메모리의 최대 바이트 수
  • RLIMIT_MSGQUEUE — POSIX 메시지 큐에 할당할 수 있는 바이트 수 한도
  • RLIMIT_NICE — 프로세스의 nice 값을 올릴 수 있는 상한
  • RLIMIT_NOFILE — 이 프로세스가 열 수 있는 최대 파일 디스크립터 번호보다 1 큰 값
  • RLIMIT_NPROC — 호출 프로세스의 실사용자 ID로 만들 수 있는 프로세스의 최대 수
  • RLIMIT_NPTS — 실사용자 ID로 만들 수 있는 의사 터미널의 최대 수
  • RLIMIT_RSS — 프로세스 상주 집합(resident set)의 한도(페이지 단위)
  • RLIMIT_RTPRIO — 이 프로세스에 설정할 수 있는 실시간 우선순위의 상한
  • RLIMIT_RTTIME — 실시간 스케줄링 정책 아래에서 소비할 수 있는 CPU 시간 한도
  • RLIMIT_SBSIZE — 소켓 버퍼의 최대 크기
  • RLIMIT_SIGPENDING — 실사용자 ID에 큐잉될 수 있는 시그널 수 한도
  • RLIMIT_STACK — 스택의 최대 크기(바이트)

또한 RLIM_INFINITY(자원에 한도 없음), RLIM_SAVED_CUR, RLIM_SAVED_MAXProcess.setrlimit에, WNOHANG·WUNTRACEDProcess.wait에 쓰여요.

공용 클래스 메서드

_fork → integer

fork용 내부 API예요. 직접 호출하면 안 돼요. 현재는 Kernel#fork, Process.fork, IO.popen"-"를 통해 호출돼요. 일반 코드용이 아니라 애플리케이션 모니터링 라이브러리용이에요 — 이 메서드를 오버라이드하면 fork 이벤트 앞뒤에 커스텀 코드를 넣을 수 있죠. 참고로 Process.daemonfork(2)로 구현될 수 있지만 이 메서드를 거치지 않아요.

abort, abort(msg = nil)

실행을 즉시 종료해요. 사실상 Kernel.exit(false)를 호출하는 셈이죠. 문자열 인자 msg가 주어지면 종료 전에 STDERR로 쓰고, 그렇지 않으면 예외가 발생했을 때 그 메시지와 백트레이스를 출력해요.

argv0 → frozen_string

실행 중인 스크립트의 이름을 반환해요. $0에 새 값을 할당해도 값이 바뀌지 않아요. 전역 변수 없이 스크립트 이름을 얻기 위해 Ruby 2.1에서 처음 등장했죠.

clock_getres(clock_id, unit = :float_second) → number

POSIX 함수 clock_getres()가 결정한 시계 해상도(clock resolution)를 반환해요:

Process.clock_getres(:CLOCK_REALTIME) # => 1.0e-09

clock_idunit 값은 Process.clock_gettime을 보면 돼요. clock_gettime이 지원하는 unit 값 외에 이 메서드는 :hertz(초당 클록 틱 수를 나타내는 정수, :float_second의 역수)를 추가로 지원해요:

Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz)        # => 100.0
Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :float_second) # => 0.01

참고로 일부 플랫폼의 기반 버그 때문에 반환된 해상도가 부정확할 수 있어요.

clock_gettime(clock_id, unit = :float_second) → number

POSIX 함수 clock_gettime()이 결정한 시계 시간을 반환해요:

Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID) # => 198.650379677

인자 clock_id는 반환할 시간의 시계를 지정하는 심볼이나 상수예요. 지원되는 시계는 운영체제에 따라 달라요(:CLOCK_REALTIME, :CLOCK_MONOTONIC, :CLOCK_PROCESS_CPUTIME_ID, :CLOCK_THREAD_CPUTIME_ID 등. 지원되지 않는 시계로 호출하면 Errno::EINVAL 발생). SUS(Single Unix Specification)는 :CLOCK_REALTIME을 필수로, 나머지는 선택으로 정의해요. 주어진 clock_id가 직접 지원되지 않을 때는 에뮬레이션이 쓰이기도 해요 (예: :TIME_BASED_CLOCK_REALTIMEtime()을, :TIMES_BASED_CLOCK_PROCESS_CPUTIME_IDtimes()를 사용).

선택 인자 unit(기본 :float_second)은 반환값의 단위를 지정해요:

  • :float_microsecond·:float_millisecond·:float_second — 각각 마이크로초·밀리초·초 단위 float
  • :microsecond·:millisecond·:nanosecond·:second — 각각 정수 단위

밑바탕 함수 clock_gettime()은 나노초 수를 반환해요. :CLOCK_REALTIME 값은 float(IEEE 754 double)로 온전히 표현할 수 없으니, 정확한 나노초 값이 필요하면 :nanosecond 단위를 쓰세요. 반환값의 원점(시간 0)은 시스템에 따라 달라요. :CLOCK_REALTIME의 원점은 Epoch(1970-01-01 00:00:00 UTC)로 정의돼요.

daemon(nochdir = nil, noclose = nil) → 0

현재 프로세스를 제어 터미널에서 떼어내 시스템 데몬으로 백그라운드에서 돌려요. 기본으로:

  • 현재 작업 디렉터리를 루트 디렉터리로 바꿔요.
  • $stdin, $stdout, $stderr를 null 장치로 리다이렉트해요.

선택 인자 nochdirtrue면 작업 디렉터리를 바꾸지 않고, noclosetrue면 stream들을 리다이렉트하지 않아요.

detach(pid) → thread

자식 프로세스가 좀비(zombie)가 되는 걸 막아줘요. 프로세스 pid가 종료될 때 그 상태를 거둬들이기(reap) 위한 별도 Ruby 스레드를 세우죠. 이 메서드는 부모 프로세스가 자식 프로세스를 절대 기다리지 않을 때만 필요해요.

pid = Process.spawn('ruby', '-e', 'exit 13') # => 313213
thread = Process.detach(pid)
sleep(1)
# => #<Process::Waiter:0x00007f038f48b838 run>
system("ps -ho pid,state -p #{pid}")        # Finds no zombies.

기다리는 스레드는 분리된 자식 프로세스의 pid를 반환할 수 있어요:

thread.join.pid # => 313262

egid → integer, egid = new_egid

egid는 현재 프로세스의 유효 그룹 ID(effective group ID)를 반환해요. egid=는 유효 그룹 ID를 설정해요. 모든 플랫폼에서 쓰이는 건 아니예요.

euid → integer, euid = new_euid

euid는 현재 프로세스의 유효 사용자 ID(effective user ID)를 반환하고, euid=는 설정해요.

exec([env, ] command_line, options = {}), exec([env, ] exe_path, *args, options = {})

다음 중 하나를 실행해서 현재 프로세스를 대체해요:

  • 문자열 command_line을 셸에 넘기기
  • exe_path의 실행 파일 호출

신뢰할 수 없는 입력으로 호출하면 보안 취약점이 있을 수 있어요(Command Injection 참고). 새 프로세스는 exec 시스템 콜로 만들어지고, 호출 프로그램의 환경 일부(열린 파일 디스크립터 포함)를 물려받을 수 있어요. 인자 env가 주어지면 새 프로세스의 ENV에 영향을 주는 해시이고, options는 새 프로세스의 실행 옵션 해시예요. 첫 필수 인자는 command_line(셸 예약어·특수 빌트인으로 시작하거나 메타 문자 포함 시) 또는 exe_path예요.

command_line 예:

exec('if true; then echo "Foo"; fi') # Shell reserved word.
exec('exit')                         # Built-in.
exec('date > date.tmp')              # Contains meta character.
exec('echo "Foo"')                   # With args and options

exe_path 예:

exec('/usr/bin/date')
exec('echo', 'hello', 'world')

새 프로세스를 실행하지 못하면 예외가 발생해요.

exit(status = true)

SystemExit를 발생시켜 Ruby 스크립트의 종료를 시작해요. 이 예외는 잡을 수 있어요. status는 기본 운영체제에 반환되는 종료 상태예요. 값 true·false는 각각 성공·실패를 나타내고, 정수 값의 의미는 시스템에 따라 달라요.

begin
  exit
  puts 'Never get here.'
rescue SystemExit
  puts 'Rescued a SystemExit exception.'
end
puts 'After begin block.'

출력:

Rescued a SystemExit exception.
After begin block.

최종 종료 직전에 Ruby는 at-exit 절차(Kernel::at_exit)와 객체 파이널라이저(ObjectSpace::define_finalizer)를 실행해요:

at_exit { puts 'In at_exit function.' }
ObjectSpace.define_finalizer('string', proc { puts 'In finalizer.' })
exit

출력:

In at_exit function.
In finalizer.

exit!(status = false)

프로세스를 즉시 종료해요. 어떤 종료 핸들러도 호출되지 않아요. status는 운영체제에 반환되는 종료 상태예요.

fork { ... } → integer or nil, fork → integer or nil

자식 프로세스를 만들어요.

블록이 주어지면 자식 프로세스에서 그 블록을 실행하고, 블록이 끝나면 상태 0으로 자식이 종료돼요:

puts "Before the fork: #{Process.pid}"
fork do
  puts "In the child process: #{Process.pid}"
end                   # => 382141
puts "After the fork: #{Process.pid}"

블록 없이 호출하면 fork는 두 번 반환돼요:

  • 부모 프로세스에서는 자식 프로세스의 pid를 반환.
  • 자식 프로세스에서는 nil을 반환.

어느 경우든 자식 프로세스는 Kernel.exit!로 종료해 Kernel#at_exit 호출을 피할 수 있어요. 좀비 프로세스를 피하려면 부모가 Process.wait(자식의 종료 상태 수집)나 Process.detach(상태에 무관심 등록)를 호출해야 해요. fork를 호출한 스레드가 만들어진 자식 프로세스의 유일한 스레드예요 — fork는 다른 스레드를 복사하지 않아요. fork는 일부 플랫폼에서 사용 가능하고 일부는 아니에요. Process.respond_to?(:fork)로 확인할 수 있고, 없는 경우 spawn을 쓰면 돼요.

getpgid(pid) → integer

주어진 프로세스 ID pid의 프로세스 그룹 ID를 반환해요. 모든 플랫폼에서 쓰이진 않아요.

getpgrp → integer

현재 프로세스의 프로세스 그룹 ID를 반환해요.

getpriority(kind, id) → integer

지정된 프로세스·프로세스 그룹·사용자의 스케줄링 우선순위를 반환해요. 인자 kindProcess::PRIO_PROCESS·Process::PRIO_PGRP·Process::PRIO_USER 중 하나이고, id는 그 대상의 ID예요. id가 0이면 kind의 현재 ID를 지정해요. 모든 플랫폼에서 쓰이진 않아요.

getrlimit(resource) → [cur_limit, max_limit]

주어진 resource에 대한 현재(소프트) 한도와 최대(하드) 한도의 2-요소 배열을 반환해요. resourceProcess.setrlimit에서 설명하는 자원을 지정해요.

Process.getrlimit(:CORE) # => [0, 18446744073709551615]

모든 플랫폼에서 쓰이진 않아요.

getsid(pid = nil) → integer

주어진 프로세스 ID pid의 세션 ID를 반환해요(주어지지 않으면 현재 프로세스). 모든 플랫폼에서 쓰이진 않아요.

gid → integer, gid = new_gid

gid는 현재 프로세스의 (실제) 그룹 ID를 반환하고, gid=는 설정해요.

groups → array

현재 프로세스의 보충 그룹 접근 목록(supplemental group access list)의 그룹 ID 배열을 반환해요. 반환 배열의 속성(정렬 여부, 유효 그룹 ID 포함 여부, 중복 여부, maxgroups 초과 여부)은 시스템에 따라 달라요. 정렬되고 유일한 배열을 얻으려면 Process.groups.uniq.sort를 쓰면 돼요.

groups = new_groups

보충 그룹 접근 목록을 주어진 그룹 ID 배열로 설정해요.

initgroups(username, gid) → array

보충 그룹 접근 목록을 설정해요. 새 목록에는 사용자 username이 속한 그룹들의 그룹 ID와 그룹 ID gid가 포함돼요. 모든 플랫폼에서 쓰이진 않아요.

kill(signal, *ids) → count

ids가 지정하는 각 프로세스에 시그널을 보내고, 보낸 시그널 수를 반환해요. 각 id에 대해:

  • 양수 — 프로세스 ID가 id인 프로세스에 시그널
  • 0 — 현재 프로세스 그룹의 모든 프로세스에 시그널
  • 음수 — 시스템에 의존하는 프로세스 모음에 시그널

인자 signal은 보낼 시그널을 지정하며, 정수 번호(-29, 0, 29), 시그널 이름 문자열('SIGPOLL', '-POLL' 등), 시그널 심볼(:SIGPOLL, :'-POLL' 등)로 줄 수 있어요. signal이 음수 정수나 -가 붙은 이름·심볼이면 그룹 ID id인 각 프로세스 그룹에 시그널을 보내요. 어떤 시그널을 쓰는지는 Signal.list로 확인할 수 있어요. 시그널 0은 프로세스 존재 여부를 확인하는 데 유용해요.

pid = fork do
  Signal.trap('HUP') { puts 'Ouch!'; exit }
  # ... do some work ...
end
# ...
Process.kill('HUP', pid)
Process.wait

출력:

Ouch!

예외: signal이 정수인데 유효하지 않으면 Errno::EINVAL·RangeError, 문자열·심볼인데 유효하지 않으면 ArgumentError, ids 중 하나가 유효하지 않으면 Errno::ESRCH·RangeError, 권한이 없으면 Errno::EPERM. 마지막 두 경우 일부 프로세스에는 시그널이 이미 보내졌을 수 있어요.

last_status → Process::Status or nil

현재 스레드에서 가장 최근에 종료된 자식 프로세스를 나타내는 Process::Status 객체를 반환해요. 없으면 nil.

Process.spawn('ruby', '-e', 'exit 13')
Process.wait
Process.last_status # => #<Process::Status: pid 14396 exit 13>

maxgroups → integer, maxgroups = new_max

maxgroups는 보충 그룹 접근 목록에 허용되는 그룹 ID의 최대 수를 반환하고, maxgroups=는 설정해요.

pid → integer

현재 프로세스의 프로세스 ID를 반환해요.

ppid → integer

현재 프로세스의 부모의 프로세스 ID를 반환해요. 일부 플랫폼에서는 신뢰할 수 없는 값을 반환할 수 있어요.

setpgid(pid, pgid) → 0

프로세스 ID pid의 프로세스의 프로세스 그룹 ID를 pgid로 설정해요. 모든 플랫폼에서 쓰이진 않아요.

setpgrp → 0

setpgid(0, 0)과 동일해요. 모든 플랫폼에서 쓰이진 않아요.

setpriority(kind, integer, priority) → 0

Process.getpriority와 짝을 이루는 메서드로, 프로세스·프로세스 그룹·사용자의 스케줄링 우선순위를 설정해요. 모든 플랫폼에서 쓰이진 않아요.

setproctitle(string) → string

ps(1) 명령에 나타나는 프로세스 제목을 설정해요. 모든 플랫폼에서 효과가 있는 건 아니고, 결과와 무관하게 예외를 던지지 않아요. 이 메서드는 $0 값에는 영향을 주지 않아요. 전역 변수 없이 프로세스 제목을 바꾸기 위해 Ruby 2.1에서 처음 등장했죠.

setrlimit(resource, cur_limit, max_limit = cur_limit) → nil

주어진 resource에 대해 현재 프로세스의 한도를 cur_limit(소프트)과 max_limit(하드)으로 설정하고 nil을 반환해요. resource는 심볼·문자열·Process::RLIMIT_ 상수로 줄 수 있어요 (:CORE, 'CORE', Process::RLIMIT_CORE). 사용 가능한 자원은 시스템에 따라 달라요(:AS, :CORE, :CPU, :DATA, :FSIZE, :NOFILE, :NPROC, :RSS, :STACK 등).

cur_limit·max_limit는 정수이거나 심볼·문자열·상수로 줄 수 있어요:

  • :SAVED_MAX·'SAVED_MAX'·Process::RLIM_SAVED_MAX — 저장된 최대 한도
  • :SAVED_CUR·'SAVED_CUR'·Process::RLIM_SAVED_CUR — 저장된 현재 한도
  • :INFINITY·'INFINITY'·Process::RLIM_INFINITY — 자원에 한도 없음

예: 코어 덤프를 시도할 수 있도록 코어 크기의 소프트 한도를 하드 한도까지 올리기:

Process.setrlimit(:CORE, Process.getrlimit(:CORE)[1])

모든 플랫폼에서 쓰이진 않아요.

setsid → integer

현재 프로세스를 새 세션·프로세스 그룹 리더로 만들고, 제어하는 tty가 없게 해요. 세션 ID를 반환해요. 모든 플랫폼에서 쓰이진 않아요.

spawn([env, ] command_line, options = {}) → pid, spawn([env, ] exe_path, *args, options = {}) → pid

자식 프로세스에서 다음 중 하나를 실행해서 새 자식 프로세스를 만들어요:

  • 문자열 command_line을 셸에 넘기기
  • exe_path의 실행 파일 호출

신뢰할 수 없는 입력이라면 보안 취약점이 있을 수 있어요. 완료를 기다리지 않고 새 프로세스의 프로세스 ID(pid)를 반환해요. 좀비 프로세스를 피하려면 부모가 Process.waitProcess.detach를 호출해야 해요. env는 새 프로세스의 ENV에 영향을 주는 해시, options는 실행 옵션 해시예요.

command_line 예:

spawn('if true; then echo "Foo"; fi') # => 798847 # Shell reserved word.
Process.wait                          # => 798847
spawn('echo', 'hello', 'world')

exe_path 예:

spawn('/usr/bin/date')              # Path to date on Unix-style system.
spawn('echo', 'C*')

새 프로세스를 실행하지 못하면 예외가 발생해요.

times → process_tms

현재 프로세스와 그 자식 프로세스들의 사용자·시스템 CPU 시간을 담은 Process::Tms 구조체를 반환해요. 정밀도는 플랫폼에 따라 정의돼요.

Process.times
# => #<struct Process::Tms utime=55.122118, stime=35.533068, cutime=0.0, cstime=0.002846>

uid → integer, uid = new_uid

uid는 현재 프로세스의 (실제) 사용자 ID를 반환하고, uid=는 설정해요.

wait(pid = -1, flags = 0) → integer, waitpid

적절한 자식 프로세스가 종료되기를 기다리고, 그 프로세스 ID를 반환하며, $?를 그 프로세스에 대한 정보를 담은 Process::Status 객체로 설정해요. 어떤 자식을 기다리는지는 pid 값에 따라 달라요:

  • 양수 정수 — 프로세스 ID가 pid인 자식 프로세스를 기다림
  • 0 — 현재 프로세스와 같은 그룹 ID인 자식 프로세스 중 아무거나 기다림
  • -1 (기본) — 아무 자식 프로세스나 기다림
  • -1보다 작은 수 — 프로세스 그룹 ID가 -pid인 자식을 기다림

인자 flags는 다음 상수 중 하나이거나 둘의 논리합으로 줘요:

  • Process::WNOHANG — 사용 가능한 자식 프로세스가 없으면 블록하지 않음
  • Process::WUNTRACED — 아직 보고되지 않았어도 멈춘 자식 프로세스를 반환할 수 있음

모든 플래그가 모든 플랫폼에서 쓰이는 건 아니예요. 적절한 자식이 없으면 Errno::ECHILD. Process.waitpidProcess.wait의 별칭이에요.

wait2(pid = -1, flags = 0) → [pid, status], waitpid2

Process.waitpid와 같지만, 자식 프로세스 pid와 Process::Status 상태를 담은 배열을 반환해요:

pid = Process.spawn('ruby', '-e', 'exit 13') # => 309581
Process.wait2(pid)
# => [309581, #<Process::Status: pid 309581 exit 13>]

Process.waitpid2Process.wait2의 별칭이에요.

waitall → array

모든 자식을 기다리고, 2-요소 배열들의 배열을 반환해요. 각 하위 배열은 거둬들인 자식 프로세스 하나의 정수 pid와 Process::Status 상태를 담아요:

pid0 = Process.spawn('ruby', '-e', 'exit 13') # => 325470
pid1 = Process.spawn('ruby', '-e', 'exit 14') # => 325495
Process.waitall
# => [[325470, #<Process::Status: pid 325470 exit 13>], [325495, #<Process::Status: pid 325495 exit 14>]]

warmup → true

Ruby 가상 머신에 부팅 시퀀스가 끝났다고 알리고, 애플리케이션을 최적화하기 좋은 때임을 알려줘요. 오래 실행되는 애플리케이션에 유용하죠. 애플리케이션 부팅 끝에 호출하길 기대해요. 프리포킹(pre-forking) 모델로 배포한다면 첫 fork 전에 원래 프로세스에서 Process.warmup을 호출해야 해요.

실제 수행되는 최적화는 구현마다 완전히 다르고 향후 예고 없이 바뀔 수 있어요. CRuby에서 Process.warmup은:

  • 메이저 GC 수행
  • 힙 압축(compact)
  • 살아남은 모든 객체를 old generation으로 승격
  • 모든 문자열의 coderange 미리 계산
  • 빈 힙 페이지 해제 및 그 수만큼 할당 가능 페이지 카운터 증가
  • 가능하면 malloc_trim 호출로 빈 malloc 페이지 해제

마무리

Process는 운영체제 프로세스 관리의 정수를 담은 모듈이에요. 프로세스 생성(spawn·fork·exec), 종료(exit·exit!·abort), 시그널(kill), 대기(wait·wait2·waitall), 그리고 ID·우선순위·자원 한도·시계 등 현재 프로세스의 상태 관리까지 — 시스템 프로그래밍을 Ruby로 할 때 거의 모든 걸 이 모듈에서 찾을 수 있어요.