Working with Unix processes

Ch1 引言

Resque Unicorn

Ch2 基础

程序不可以直接访问内核,所有的通信都是通过系统调用来完成的。系统调用为内核和用户空间搭建了桥梁。它规定了程序与计算机硬件之间所允许发生的一切交互。

所有的程序都运行在用户空间。系统调用允许用户空间程序通过内核间接地与计算机硬件进行交互。

Ch3 PID

$$ Process.pid

查看 ps -p pid

Ch4 PPID

Process.ppid

Ch5 文件描述符

Unix中一切都是文件.

打开资源获得其文件描述符,进程结束时资源会一同关闭.文件描述符只存在于所属进程中,不会与其他进程共享.

所分配的文件描述符编号是尚未使用的最小的数值.资源一旦关闭,对应的文件描述符编号就回收重用.

文件描述符只是用来跟踪打开的资源,已经关闭的资源是没有文件描述符的。

标准流

STDIN.fileno #0

STDOUT.fileno #1

STDERR.fileno #2

Ch6 资源限制

系统资源限制:

Process.getrlimit(:NOFILE)

#[256, 9223372036854775807]

#[软限制, 硬限制]

硬限制的值等同于 Process::RLIM_INFINITY, 即表示无限大.

Process.setrlimit(:NOFILE, 2048, 4096)

http://ruby-doc.org/core-2.4.1/Process.html#method-c-setrlimit

Ch7 环境变量

所有进程都从其父进程处继承环境变量。

比起解析命令行选项,使用环境变量的开销通常更小一些。

$ uid=CbdFocus irb
>> ENV['uid']
=> "CbdFocus"

ENV 是不完全的Hash

Ch8 参数数组

ARGV是数组, 可在运行时修改.

# main.rb
p ARGV
p ARGV.include?("-c") && ARGV[ARGV.index("-c") + 1]

#$ ruby main.rb -c diy.conf
#["-c", "diy.conf"]
#"diy.conf"

Ch9 进程名

进程名,全局变量: $PROGRAM_NAME($0)

Ch10退出码

退出码0代表顺利结束

exit,正常退出(0),执行at_exit块

exit!,异常退出(1),不执行at_exit块

abort,异常退出(1),执行at_exit块,打印到STDERR

raise,抛异常,如果没有被捕获就异常退出(1),执行at_exit块,打印到STDERR

Ch11 fork

子进程从父进程处继承了其所占用内存中的所有内容,以及所有属于父进程的已打开的文件描述符。

子进程是一个全新的进程,所以它拥有自己唯一的pid。

子进程fork返回nil,父进程fork返回子进程pid.

p "[#{Process.pid}]start at parent"

fork do
 p "[#{Process.pid}]only at son and exit"
end

p "[#{Process.pid}]only at parent"

#"[1837]start at parent"
#"[1837]only at parent"
#"[1838]only at son and exit"

Ch12 孤儿进程

父进程结束后,子进程安然无恙.

p "[#{Process.pid}]start"

fork do
 3.times do
 sleep 1
 p "[#{Process.pid}]im a orphan"
 end
end

abort "[#{Process.pid}]parent process died."

#$ ruby main.rb
#"[1905]start"
#[1905]parent process died.
#"[1906]im a orphan"
#"[1906]im a orphan"
#"[1906]im a orphan"

守护进程是一种长期运行的进程,为了能够一直保持运行,它们有意作为孤儿进程存在。

Ch13 CoW

单纯的标记-清除方式的垃圾回收使CoW失效.(首次标记即改动了所有对象)

Ch14 wait

等待子进程退出:

(内核将退出的进程信息加入队列,父进程总能依次接收退出消息,即使子进程已经退出了很久.)

# Process.wait() -> fixnum
# Process.wait(pid=-1, flags=0) -> fixnum
# Process.waitpid(pid=-1, flags=0) -> fixnum


# Process.wait2(pid=-1, flags=0) -> [pid, status]
# Process.waitpid2(pid=-1, flags=0) -> [pid, status]


# Process.waitall -> [ [pid1,status1], ...]

son_pid = fork do
 p "son..."
 sleep 1
end

p son_pid
p Process.wait

__END__
1221
"son..."
1221

# > 0:: Waits for the child whose process ID equals _pid_.
#
# 0:: Waits for any child whose process group ID equals that of the
# calling process.
#
# -1:: Waits for any child process (the default if no _pid_ is
# given).
#
# < -1:: Waits for any child whose process group ID equals the absolute
# value of _pid_.

成为 master/worker 或 preforking

Ch15 zombie

僵尸进程:内核将已经退出的子进程状态信息加入队列,如果不调用Process.wait系列方法,这些状态信息将一直占用内核资源.

Process.detach(sonPID) #清除该PID在退出之后遗留在内核中的状态信息.

Ch16 unix signal

信号投递不可靠.

Signal.list
=> {"EXIT"=>0, "HUP"=>1, "INT"=>2, "QUIT"=>3, "ILL"=>4, "TRAP"=>5, "ABRT"=>6, "IOT"=>6, "EMT"=>7, "FPE"=>8, "KILL"=>9, "BUS"=>10, "SEGV"=>11, "SYS"=>12, "PIPE"=>13, "ALRM"=>14, "TERM"=>15, "URG"=>16, "STOP"=>17, "TSTP"=>18, "CONT"=>19, "CHLD"=>20, "CLD"=>20, "TTIN"=>21, "TTOU"=>22, "IO"=>23, "XCPU"=>24, "XFSZ"=>25, "VTALRM"=>26, "PROF"=>27, "WINCH"=>28, "USR1"=>30, "USR2"=>31, "INFO"=>29}

发送信号:

Process.kill(:INT, 1200) # 向pid为1200的进程发送:INT (“INT” / :SIGINT / “SIGINT”) 的信号.

重定义信号:

# 重新定义接收到:USR1信号时的行为
Signal.trap(:USR1) do |signo|
	# redefine response of :USR1 Signal
end


# 忽略对INT信号的捕获
Signal.trap(:INT, "IGNORE")


# 考虑多处定义
old_handler = Signal.trap(:INT) do
	old_handler.call if old_handler.response_to?(:call)
end

Ch17 IPC

通道是单向的.

只要w不关闭, r就会一直阻塞等待.w关闭是写入EOF,r一直读直到读到EOF.

r, w = IO.pipe
w.write("Pied")
w.write("Piper")
w.close

p r.read
p r.read
__END__
$ ruby -v main.rb
ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-darwin16]
"PiedPiper"
""

fork时文件描述符也会被复制. 由于IO管道是单向的,必须把不需要的管道关闭.

r, w = IO.pipe

pid = fork do
 puts "this is son process."
 r.close

 5.times do
 w.write "#{Time.now}\n"
 sleep 1
 end
end

puts "this is father."
w.close
Process.detach pid
while msg = r.gets
 puts msg
end

__END__
$ ruby -v main.rb
ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-darwin16]
this is father.
this is son process.
2017-09-01 13:46:56 +0800
2017-09-01 13:46:57 +0800
2017-09-01 13:46:58 +0800
2017-09-01 13:46:59 +0800
2017-09-01 13:47:00 +0800

Unix套接字是一种只能用于在同一台物理主机中进行通信的套接字。它比TCP套接字快得多,非常适合用于IPC。

在管道中无法使用消息, 在Unix套接字中可以.

管道是单向的,套接字对是双向的.

require 'socket'

Maxlen = 1000
s1, s2 = Socket.pair(:UNIX, :DGRAM, 0)


pid1 = fork do
 s2.close
 s1.send "hi", 0
 sleep 1
 s1.send "hello", 0
 sleep 1
 s1.send "world", 0
 sleep 1
 # s1.send "!", 0
end

pid2 = fork do
 s1.close

 3.times do |i|
 msg = s2.recv(Maxlen)
 puts "#{i}: #{msg}" if msg
 end
end



Process.detach pid1
Process.wait pid2
__END__
$ ruby -v main.rb
ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-darwin16]
0: hi
1: hello
2: world

Ch18 守护进程

def daemonize_app
 if RUBY_VERSION < "1.9"
 exit if fork

		#该进程变成一个新会话的会话领导;该进程变成一个新进程组的组长;该进程没有控制终端
 Process.setsid

		#再次衍生,不再是会话领导和进程组组长
 exit if fork

	 #切换工作目录
 Dir.chdir "/"

		# 忽略标准输入输出, 但保持其可用不报错
 STDIN.reopen "/dev/null"
 STDOUT.reopen "/dev/null", "a"
 STDERR.reopen "/dev/null", "a"
 else
 Process.daemon
 end
end

fork 在父进程返回子进程pid, 在子进程返回nil.

孤儿进程的ppid为1(init).

Ch19 终端进程

exec将当前进程转变成另外一个进程, 然后就回不来了.

把字符串传递给 exec ,它实际上会启动一个shell进程,然后再将这个字符串交由shell解释。传递一个数组的话,它会跳过shell,直接将此数组作为新进程的 ARGV

Ch20 尾声

与Unix进程打交道事关两件事:抽象和通信。