Unix進程特性

  本篇文章主要總結分享記錄一下運維工做中常常打交道的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套接字實現。

相關文章
相關標籤/搜索