Pythonでざっくり学ぶUnixプロセス

tell-k
Pycon JP 2018 (2018.09.18)

お前だれよ

https://pbs.twimg.com/profile_images/1045138776224231425/3GD8eWeG_200x200.jpg https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/vxjmiemo.png

speaker note test

connpass - エンジニアをつなぐIT勉強会支援プラットフォーム

PyQ - 本気でプログラミングを学びたい人のPythonオンライン学習サービス

TRACERY - システム開発チーム向け知識共有サービス

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/b26511423900438399ebbf3c56650781.png

目的/動機

なるほどUnixプロセス

_images/working_with_unix_process.jpg

対象

目標

_images/genbaneko.jpg

前提

目次

目次

システムコール

システムコール

システムコール

システムコール

プロセス

プロセス

  • 身近な例だと データベースサーバ、Webサーバとか、専用のプロセスがずっと動いてる

プロセスにはIDがある

プロセスID

>> import os
>> os.getpid()
16801 # <- プロセスID

プロセスIDの確認

$ ps -p  16801

  PID TTY           TIME CMD
16801 ttys003    0:00.04 /usr/local/Cellar/python/3.7.0/~省略~/Python

PID  ... プロセスID
TTY  ... 仮想端末ファイル
TIME ... CPU使用時間
CMD  ... 実行コマンド

プロセスには親がいる

プロセスには親がいる

>>> import os
>>> os.getppid()
14455

例えばターミナル.app 起動 して、Bashのプロンプトが表示した場合

  1. ターミナル.app のプロセス
  2. 1を親に bashプロンプトの子プロセス

という親子関係のプロセスが作られます

プロセスにはファイルディスクリプタがある

ファイルディスクリプタとは?

プロセスとともに生まれ・死ぬ

>>> open('test.txt', 'w').fileno()
3

なぜ3から採番される?

>>> import sys
>>> sys.stdin.fileno()
0
>>> sys.stdout.fileno()
1
>>> sys.stderr.fileno()
2

ファイルディスクリプタは再利用される

with open('test.txt', mode='w') as fp:
    print(fp.fileno()) # => 3

with open('test2.txt', mode='w') as fp:
    print(fp.fileno()) # => 3 上と同じ

プロセスにはリソースの制限がある

プロセスにはリソースの制限がある

>>> import resource
>>> resource.getrlimit(resource.RLIMIT_NOFILE)
(4864, 9223372036854775807)

最初の数字がソフトリミット
次の数字がハードリミット

今日の声に出して読みたい日本語

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/c11b45b10a284276b07c0cd279b9bd8d.png

制限を変えることもできる

>>> import resource
>>> resource.setrlimit(resource.RLIMIT_NOFILE, (3, 3))
>>> open('test.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  OSError: [Errno 24] Too many open files: 'test.txt'

プロセスには環境がある

プロセスには環境がある

$ MESSAGE='pyconjp2018.' python -c "import os; print(os.getenv('MESSAGE'))"
pyconjp2018

プロセスには引数がある

# show_argv.py

import sys
print(sys.argv) # 単純に引数を表示するだけ
$ python show_argv.py arg1 arg2
['show_argv.py', 'arg1', 'arg2']

プロセスには名前がある

プロセスには名前がある

>>> from setproctitle import setproctitle, getproctitle
>>> getproctitle()
'python'
>>> setproctitle("newprocname")
>>> getproctitle()
'newprocname'
>>>

プロセスには終了コードがある

プロセスには終了コードがある

exit() #=> 引数無しは0
exit(1) #=> 異常終了
def hello():
   print("hello")

import atexit
atexit.register(hello)

via https://docs.python.org/ja/3/library/atexit.html#atexit-example

プロセスは子プロセスを作れる

プロセスは子プロセスを作れる

  • 親プロセスで使われてる全てのメモリーのコピーを引き継ぐ
  • 親プロセスのファイルディスクリプタも引き継ぐ

実行するとどうなるか?

import os

if os.fork():
   print('spam')
else:
   print('ham')

こうなる

// 実行結果
spam
ham

なぜなのか?

確認

import os

print(f'親プロセス: {os.getpid()}')

if os.fork(): # <= 子プロセスはここから始まる
   print(f'親プロセス: {os.getpid()}')
else:
   print(f'子プロセス: {os.getpid()}')
// 実行結果
親プロセス: 88220
親プロセス: 88220
子プロセス: 88221

孤児プロセス

孤児プロセス

import os
import time

if os.fork():
   exit('親プロセスは死んだ') # 親プロセスはforkした瞬間に死ぬ

for i in range(5):
   time.sleep(1)
   print(f'孤児として生きてる {i}')

孤児プロセス - 実行結果

$ python orphan_process.py
親プロセスは死んだ
$ 孤児として生きてる 0  <- ここで一旦Terminalに戻る
孤児として生きてる 1
孤児として生きてる 2
孤児として生きてる 3
孤児として生きてる 4

プロセスは優しい

プロセスは優しい

プロセスは待てる

import os
import time

if os.fork():
   os.wait() # 子プロセスが終了するまで待つ
   exit('親プロセスは死んだ')

for i in range(5):
    time.sleep(1)
    print(f'孤児として生きてる {i}')
# 実行結果
孤児として生きてる 0
孤児として生きてる 1
孤児として生きてる 2
孤児として生きてる 3
孤児として生きてる 4
親プロセスは死んだ

複数のプロセスを待つ

import os
import sys
import time
import random

for _ in range(3):
    if os.fork() == 0:
        # ランダムで1〜5秒待つ子プロセスを生成する
        time.sleep(random.randint(1, 5))
        sys.exit()

for _ in range(3):
    pid = os.wait()
    print(f'終了プロセスID {pid}')

複数のプロセスを待つ

終了プロセスID (26238, 0)
終了プロセスID (26236, 0)
終了プロセスID (26237, 0)

ゾンビプロセス

ゾンビプロセスとは

ゾンビプロセス確認

import os
import time
import sys

pid = os.fork()
if pid == 0:
    time.sleep(1)
    sys.exit() # 先に子が終了

# os.waitしない
print(pid) # => 終了した子プロセスID 92763
time.sleep(10)
# 状態を確認する

$ ps -ho pid,state -p 92763
  PID STAT
92763 Z+ # <= Z+

孤児とゾンビの違いは?

デーモンプロセス

デーモンプロセス

デーモン化

デーモン化

# 子プロセス生成
pid = os.fork()
if pid > 0:
    # 親死
    sys.exit(0)

# 子プロセスを制御端末から切り離すk
os.setsid()

# 孫プロセス生成 <- 完全に制御端末から切り離される
pid = os.fork()
if pid > 0:
    print(f"Daemon PID {pid}")
    sys.exit(0)

# ディレクトリ消されても動く
os.chdir('/')
os.umask(0)

# デーモンには不要なので標準ストリームを潰す
devnull = os.open('/dev/null', os.O_RDWR)
os.dup2(devnull, 0)
os.dup2(devnull, 1)
os.dup2(devnull, 2)
os.close(devnull)

# デーモン化したい処理
main()

標準のデーモン化

今日話さなかったこと

参考書籍

参考書籍

参考リンク

感謝

まとめ

ご静聴ありがとうございました

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)