本篇文章主要总结分享记录一下运维工做中常常打交道的Unix进程。程序是代码的集合,而进程是运行中的程序产生的。那么进程都有那些特性呢?且看下文,部分经典且难懂的地方,使用python代码实现,可让读者更好的理解与记忆。python
在系统中运行的全部进程都有一个惟一的进程标识符,称之为pid。shell
pid并不传达关于进程的任何信息,它仅仅是一个顺序字符标识。进程在内核眼中只是个数字而已。编程
pid是对进程的一种简单通用的描述,它与进程内容无关。数组
系统中运行的每个进程都有对应的父进程。每一个进程都知道其父进程的标识符(ppid)。bash
在多数状况下,特定进程的父进程就是调用它的那个进程。并发
举例:启动终端并进入bash提示,执行ls命令。那么bash的父进程是终端进程,ls的父进程是bash进程。运维
Unix哲学指出,在Unix世界中,一切皆文件。设备、套接字、管道等都是文件。 文件描述符表明资源。不管什么时候打开一个资源,都会获得一个文件描述符编号(file descriptor number)。文件描述符只存在其所属的进程之中,不会在无关进程之间共享。当进程结束,它会和其余有进程所打开的资源一同被关闭。异步
文件描述符编号的分配是从还没有使用的最小的值开始。资源一旦关闭,对应的文件描述符编号就又能使用了。文件描述符只是用来跟踪打开的资源,已经关闭的资源是没有文件描述符的。编程语言
接上文,文件描述符表明已打开的资源,那么一个进程能够拥有多少文件描述符,也就是能够拥有多少资源呢?这是由系统配置决定的。内核为进程施加了某些资源限制。好比并发数,最大的文件数,进程栈的段大小等。spa
环境变量是包含进程数据的键值对(key-value pairs)。 全部进程都从其父进程处继承环境变量。它们由父进程设置并被子进程所继承。每一个进程都有环境变量,环境变量对于特定进程而言是全局性的。(ENV)
全部进程均可以访问名为ARGV的特殊数组。不一样编程语言可能在实现方式上略微不一样,可是都会有argv。
argv的全称:argument vector。换句话说,argv是一个参数向量或数组。它保存了在命令行中传递给当前进程的参数。
Unix进程几乎没有什么固有的方法来获悉彼此的状态。有两种运行在进程自身层面上的机制能够用来通讯。一个是进程名称,另外一个是退出码。
系统中每个进程都有名称。
当进程即将结束时,它还有最后一线机会留下自身的信息:退出码。全部进程在退出的时候都带有数字退出码(0-255),用于指明程序是否正常结束。按照惯例,退出码为0表明进程顺利结束,其余退出码则表明出现了错误,不一样的退出码对应不一样的错误。 在shell脚本中可使用exit退出,并指定退出码。如:exit 1
衍生(forking)是Unix编程中最强大的概念之一。fork(2)系统调用容许运行中的进程以编程的形式建立新的进程。这个新进程和原始进程如出一辙。
进行衍生时,调用fork(2)的进程被称为父进程,新建立的进程被称为子进程。
子进程从父进程处继承了其所占有内存中的全部内容,以及全部属于父进程的已打开的文件描述符。
子进程是一个全新的进程,它拥有本身惟一的pid。子进程的ppid就是调用fork(2)的进程的pid。
在fork(2)调用时,子进程从父进程处继承了全部的文件描述符,也得到了父进程全部的文件描述符的编号。这样,父子进程就能够共享打开的文件、套接字等资源。
子进程继承了父进程内存中全部内容,即子进程实际上各自享有一份已载入内存代码库的副本。子进程能够随意更改其内存内容的副本,而不会对父进程形成任何影响。
fork示例:
handetiandeMacBook-Pro:~ handetian$ cat 1.py #!/usr/bin/env python import os print '1: %s' % os.getpid() if os.fork(): print 'This process is 2: %s' % os.getpid() else: print 'This process is 3: %s' % os.getpid() handetiandeMacBook-Pro:~ handetian$ python 1.py 1: 21198 This process is 2: 21198 This process is 3: 21199
从上面的示例发现,fork方法的一次调用实际上返回了两次。fork创造了一个新进程。因此它在调用进程(父进程)中返回一次,在新建立的进程(子进程)中又返回一次。if语句块中的代码是由父进程执行的,而else语句块中的代码是子进程执行的。子进程执行完else语句块以后退出,父进程则继续运行。
上述代码的输出是跟fork的返回值有关的。在父进程中,fork返回的值是新建立的子进程的pid,因此父进程执行if语句块中的代码。在子进程中,fork返回的值是nil,nil为假,因此子进程执行了else语句块中的代码。
根据上边的示例以及返回结果能够代表:经过生成新的进程,程序代码能够(不能彻底保证)被分配到多个CPU核心中。
fork(2)建立了一个和旧进程如出一辙的新进程。若是一个使用了1GB内存的进程进行了衍生,那么就有2GB的内存被占用了。若是重复几回该操做,内存会很快被耗尽。这也就是所谓的fork炸弹,所以,咱们在执行并发操做前,必定要确保程序执行的后果!!!
在上文中咱们发现,若是涉及子进程,不少事情就变得不那么简单了。
经过终端启动单个前台进程,只有该进程向STOUT输出,键盘输入CTRL-C能够退出该程序。
一旦进程衍生了子进程,咱们如何肯定CTRL-C能够结束父进程仍是子进程,仍是所有呢?明白这其中的原理能够避免孤儿进程的产生。
当父进程结束后,子进程会怎样? 子进程会安然无恙的运行,由于操做系统不会对子进程区别对待。
父进程结束后,子(孤儿)进程由谁接管? 在Linux系统中,孤儿(子)进程会由init(1号)进程接管。其实守护进程也是孤儿进程。
上文中咱们聊到fork(2)建立了一个跟父进程如出一辙的子进程。它包含了父进程在内存中的一切内容。若是确实复制一份全部数据,系统开销会很大,现代Unix系统采用了写时复制(copy-on-write, CoW)机制,解决该问题。也就是说父进程和子进程其实是在共享内存中的内容,只有其中某个进程须要对数据进行修改时才会进程内存复制,是得不一样进程之间具备必定的隔离。
上文中咱们使用fork(2)衍生的子进程都是跟父进程同时进行,若是咱们但愿子进程异步的处理事务那么没有问题。此种状况下,若是父进程先退出,那么子进程就会变成孤儿进程。那么父进程是否能够等待子进程退出后再退出呢?答案是能够的,能够经过Process.wait或Process.waitpid等待子进程执行完成。
示例:
handetiandeMacBook-Pro:~ handetian$ cat 1.py #!/usr/bin/env python import os,time print 'This process is 1: %s' % os.getpid() pid = os.fork() if pid: print 'This process is 2: %s' % os.getpid() #os.wait() sleep(5) #print "prcess 1 is died!!!" else: for i in xrange(10): time.sleep(1) print 'This process is 3: %s' % os.getpid()
上述示例,添加注释(#os.wait,#print...),效果是父进程和子进程同步执行,父进程退出后,子进程继续执行。解除注释(#os.wait,#print...),父进程会等待子进程执行完后,再退出,而且打印process is died!!!
内核会一直保留已退出的子进程的状态信息,直到父进程使用使用Process.wait请求这些信息。若是父进程一直不发出请求,那么状态信息就会一直被内核保留,形成内核资源浪费。
示例:
handetiandeMacBook-Pro:~ handetian$ cat 1.py #!/usr/bin/env python import os,time print 'This process is 1: %s' % os.getpid() pid = os.fork() if pid: print 'This process is 2: %s' % os.getpid() while 1: time.sleep(1) else: print 'This process is 3: %s' % os.getpid()
执行结果:
handetiandeMacBook-Pro:~ handetian$ python 1.py & [3] 21626 handetiandeMacBook-Pro:~ handetian$ This process is 1: 21626 This process is 2: 21626 This process is 3: 21627 handetiandeMacBook-Pro:~ handetian$ ps -ho pid,state -p 21626 PID STAT 21626 S handetiandeMacBook-Pro:~ handetian$ ps -ho pid,state -p 21627 PID STAT 21627 Z
示例代码是fork(2)一个子进程,子进程执行后退出,父进程长眠。经过执行结果处的输入,能够看到父进程S(睡眠),子进程Z(僵尸)。这就是父进程一直没有读取子进程的状态信息致使的。相应的解决的办法就是父进程在子进程退出读取
上文中父进程能够经过Process.wait监管子进程。但Process.wait是一个block调用,直到子进程结束,调用才会返回。现实中,父进程不可能一直有时间等待子进程结束。因此,下面将要聊得是Unix信号。 信号是什么?信号是一种异步通讯。进程能够对接收到的信息进行以下操做:1.忽略该信号 2.执行特定操做 3.执行默认操做
信号来自哪里?信号由一个进程发送,通过内核,内核发送给另外一个进程。内核是信号发送的中介。
经常使用信号操做:
Term -> 表示进程会当即结束
Core -> 表示进程会当即结束并进行核心转储(栈跟踪)
Ign -> 表示进程会忽略该信号
Stop -> 表示进程会中止运行(暂停)
Cont -> 表示进程会恢复运行(继续)
经常使用的信号:
SIGHUP 1 Term
SIGINT 2 Term
SIGQUIT 3 Core
SIGKIAL 9 Term
SIGTERM 15 Term
SIGUSR1 30,10,16 Term #自定义信号
SIGUSR2 31,12,17 Term # 自定义信号
SIGSTOP 17,19,23 Stop #该信号不能被捕捉,阻塞或忽略。
进程间通讯(IPC)的实现有不少方法,最多见的两种:管道(Pipe)和套接字(Socket pairs)
一个进程打开一个wirte管道,一个进程打开read管道,rea管道一直接收数据,直至接收到一个EOF标志时中止接收。管道通讯是单向的。
IPC是运行在同一台机器上的进程间的通讯,不一样机器之间的进程通讯须要使用TCP套接字实现。