Linux進階之進程與線程

一、進程與線程

1、進程

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。-----------百度百科

在Linux中進程是運行中的程序的一個副本,是被載入內存的一個指令集合,使用進程ID(PID)來標記各個進程。我們可使用echo $$來查看當前程序的進程號。

[ root@vinsent ~ ]#echo $$         # 獲取當前bash的進程號
3267
[ root@vinsent ~ ]#
[ root@vinsent ~ ]#pstree -p
systemd(1)─┬─NetworkManager(520)─┬─dhclient(623)
           │                     ├─{NetworkManager}(531)
           │                     └─{NetworkManager}(533)
       ...中間省略...
           ├─sshd(935)───sshd(3263)───bash(3267)───pstree(7269)
           ├─systemd-journal(361)           ---|---
           ├─systemd-logind(496)            # 通過pstree驗證,和echo $$的結果一樣  
           ├─systemd-udevd(380)
           ├─tuned(920)─┬─{tuned}(1105)
           │            ├─{tuned}(1109)
           │            ├─{tuned}(1111)
           │            └─{tuned}(1116)
           └─vmtoolsd(495)
[ root@vinsent ~ ]#
  • 進程得到的權限,是對應之執行者的權限,除了SUID、SGID等權限。

  • 進程是動態概念,有生命週期;可附加SUI、SGID等權限。

  • 進程號隨機分配( 進程號唯一標識一個進程;一個inode唯一標識一個磁盤文件 )。

  • 在 CentOS 6 及之前的系統中第一個進程爲:init;CentOS 7 中第一個進程爲:systemd。

2、守護進程

守護進程(daemon)是一類在後臺運行的特殊進程,用於執行特定的系統任務。很多守護進程在系統引導的時候啓動,並且一直運行直到系統關閉。另一些只在需要的時候才啓動,完成任務後就自動結束。用戶使守護進程獨立於所有終端是因爲,在守護進程從一個終端啓動的情況下,這同一個終端可能被其他的用戶使用。守護進程沒有控制終端,因此當某些情況發生時,不管是一般的報告性信息,還是需由管理員處理的緊急信息,都需要以某種方式輸出。Syslog 函數就是輸出這些信息的標準方法,它把信息發送給 syslogd 守護進程。例如我們用來監控某些非獨立服務的程序xinetd,他就是一個超級守護進程,用來管理系統的的非獨立守護進程(瞬時守護進程),這些非獨立進程通常情況是處於關閉狀態,由xinetd進程來監控,如果有人使用相關的服務,則喚醒這些非獨立守護進程,進行工作。我們可使用chkconfig來查看。

[root@localhost ~]# yum install xinetd        # 我用的是最小化安裝,自動不安裝xinetd服務
[root@localhost ~]# chkconfig 
auditd             0:off    1:off    2:on    3:on    4:on    5:on    6:off    # 獨立服務
autofs             0:off    1:off    2:on    3:on    4:on    5:on    6:off
blk-availability    0:off    1:on    2:on    3:on    4:on    5:on    6:off
...中間省略
xinetd based services:           # 下面的服務全是非獨立服務,由xinetd來代爲管理
    chargen-dgram:     off
    chargen-stream:    off
    daytime-dgram:     off
    daytime-stream:    off
    discard-dgram:     off
    discard-stream:    off
    echo-dgram:        off
    echo-stream:       off
    tcpmux-server:     off
    time-dgram:        off
    time-stream:       off
[root@localhost ~]# chkconfig --add crond     # 添獨立服務是添加不到xinetd中的

NOTE:我們每次新添加(chkconfig --add server_name)一個服務或者刪除(chkconfig --del server_name)一個服務後,我們必須重啓xinetd服務(service restart xinetd)。

3、線程

有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。

一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以併發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。就緒狀態是指線程具備運行的所有條件,邏輯上可以運行,在等待處理機;運行狀態是指線程佔有處理機正在運行;阻塞狀態是指線程在等待一個事件(如某個信號量),邏輯上不可執行。

有線程,那麼就有多線程的說法。線程是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指運行中的程序的調度單位。在單個程序中同時運行多個線程完成不同的工作,稱爲多線程。線程共享整個程序的內存資源;但是也存在一個線程出故障,影響其他線程,爭奪資源。爲了避免這種爭奪資源的情況,系統加入了鎖 fork()機制clone()機制。通常採用寫時複製的原則:

wKioL1msFuOSW5JVAAASdudXfuM516.png

在Linux中我們通過pstree能看到線程通常在進程樹中表現爲"{ thread_name  }"形式。

[ root@vinsent ~ ]#yum -y install httpd  &>/dev/null
[ root@vinsent ~ ]#pstree -p
systemd(1)─┬─NetworkManager(520)─┬─dhclient(623)
           │                     ├─{NetworkManager}(531)  # NetworkManager開啓的多線程
           │                     └─{NetworkManager}(533)
           ├─atd(509)
           ├─auditd(471)───{auditd}(482)
           ├─automount(929)─┬─{automount}(930)
           │                ├─{automount}(931)
           │                ├─{automount}(948)
           │                └─{automount}(960)
           ├─crond(506)
           ├─dbus-daemon(492)───{dbus-daemon}(493)
           ├─firewalld(515)───{firewalld}(619)
           ├─httpd(7491)─┬─httpd(7494)          # httpd服務開啓的多個進程
           │             ├─httpd(7495)
           │             ├─httpd(7496)
           │             ├─httpd(7497)
           │             └─httpd(7498)
           ...後面省略...
[ root@vinsent ~ ]#

4、線程與進程的關係

  • 一個進程可包含多個線程;

  • 每一個程序都至少有一個線程,若程序只有一個線程,那就是程序本身;

  • 地址空間和其它資源(如打開文件):進程間相互獨立,同一進程的各線程間共享。某進程內的線程在其它進程不可見。

  • 通信:進程間通信IPC,線程間可以直接讀寫進程數據段(如全局變量)來進行通信——需要進程同步和互斥手段的輔助,以保證數據的一致性。

  • 調度和切換:線程上下文切換比進程上下文切換要快得多。

  • 在多線程OS中,進程不是一個可執行的實體。


二、進程優先級

有時我們面臨着很多選擇,同一時間有許多事情要辦,那麼我們總會有選擇性的去執行,先做什麼後做什麼,這就是一種優先級,同樣的作爲一個高速運轉的計算機而言,有序的優先級約定顯得異常重要。

優先級(priority)是一種約定,優先級高的先做,優先級低的後做。優先級是計算機分時操作系統在處理多個作業程序時,決定各個作業程序接受系統資源的優先等級的參數。優先級是計算機操作系統給任務指定的優先等級。它決定任務在使用資源時的優先次序。給設備指定的優先等級。它決定設備在提出中斷請求時,得到處理機響應的先後次序。任務調度優先級主要是指任務被調度運行時的優先級,主要與任務本身的優先級和調度算法有關。特別在實時系統中,任務調度優先級反應了一個任務重要性與緊迫性。-------百度百科

1、系統優先級

進程優先級起作用的方式從發明以來基本沒有什麼變化,無論是隻有一個cpu的時代,還是多核cpu時代,都是通過控制進程佔用cpu時間的長短來實現的。就是說在同一個調度週期中,優先級高的進程佔用的時間長些,而優先級低的進程佔用的短些。在Linux系統中系統優先級的範圍爲 0-139 共 140 及等級 ;其值越小則優先級越高 ;CentOS 6之後爲 系統優先級的範圍變成了0-98。

2、實時優先級與非實時優先級

實時操作系統需要保證相關的實時進程在較短的時間內響應,不會有較長的延時,並且要求最小的中斷延時和進程切換延時。對於這樣的需求,一般的進程調度算法,無論是O1還是CFS都是無法滿足的,所以內核在設計的時候,將實時進程單獨映射了100個優先級,這些優先級都要高與正常進程的優先級(nice值),而實時進程的調度算法也不同,它們採用更簡單的調度算法來減少調度開銷。總的來說,Linux系統中運行的進程可以分成兩類實時優先級與非實時優先級。實時優先級又稱動態優先級,其的範圍是0-99;其值越大,優先級越高;動態優先級不能人爲調整,系統自己調度。所有優先級值在0-99範圍內的,都是實時進程,所以這個優先級範圍也可以叫做實時進程優先級,而100-139範圍內的是非實時進程。在系統中可以使用chrt命令來查看、設置一個進程的實時優先級狀態。

3、靜態優先級

  • 靜態優先級又稱非實時優先級:通常用nice值表示;

  • nice值所在範圍對應系統優先級的 100-139 ;其值爲:-20~19;

  • 進程默認啓動時的nice值爲0,優先級爲120;

  • 只有根用戶才能降低nice值(提高優先性);

  • nice 優先級 其值越小則優先級越高;

在幾種優先級中,nice的值是我們可以手動調整的,我們可以指定以某個nice值打開某個程序;使用renice命令可以對一個正在運行的進程進行nice值的調整,我們也可以使用比如top、ps等命令查看進程的nice值,具體方法我會在下一章給大家介紹,但對於普通用戶來說:renice在調整程序的優先級的時候只能將其值調低。

[ root@vinsent ~ ]#ps axo pid,cmd,pri,nice  # 只顯示進程進程號,進程名,pri優先級,nice值
   PID CMD                         PRI  NI
     1 /usr/lib/systemd/systemd --  19   0

...中間省略...
  4344 -bash                        19   0
  4381 vim switch_arry.sh           19   0     # vim進程默認的NICE值爲0
  4388 [kworker/0:2H]               39 -20
  4416 /sbin/dhclient -d -q -sf /u  19   0
  4422 ps axo pid,cmd,pri,nice      19   0
[ root@vinsent ~ ]#
[ root@vinsent ~ ]#renice -n -1 4381                 # 修改vim的nice值
4381 (process ID) old priority 0, new priority -1
[ root@vinsent ~ ]#ps axo pid,cmd,pri,nice
   PID CMD                         PRI  NI
     1 /usr/lib/systemd/systemd --  19   0
     ...中間省略...
  4344 -bash                        19   0
  4381 vim switch_arry.sh           20  -1         # 修改了NICE
  4388 [kworker/0:2H]               39 -20
  4428 [kworker/0:0H]               39 -20
  4429 ps axo pid,cmd,pri,nice      19   0
[ root@vinsent ~ ]#su - tom     # 切換至普通用戶
[ tom@vinsent root ]$renice -n -5 4602     # 調高NICE優先級,受到限制;
renice: failed to set priority for 4602 (process ID): Permission denied
[ tom@vinsent root ]$renice -n 5 4602     #調低NICE值成功;說明普通用戶只能調低NICE優先級
4602 (process ID) old priority 0, new priority 5

需要大家注意的是,我在這裏都在使用nice值這一稱謂,而非優先級(priority)這個說法。當然,nice和renice的man手冊中,也說的是priority這個概念,但是要強調一下,請大家真的不要混淆了系統中的這兩個概念,一個是nice值,一個是priority值,他們有着千絲萬縷的關係,但對於當前的Linux系統來說,它們並不是同一個概念。nice值雖然不是priority,但是它確實可以影響進程的優先級。在英語中,如果我們形容一個人nice,那一般說明這個人的人緣比較好。什麼樣的人人緣好?往往是謙讓、有禮貌的人。比如,你跟一個nice的人一起去吃午飯,點了兩個一樣的飯,先上了一份後,nice的那位一般都會說:"你先吃你先吃!",這就是人緣好,這人nice!但是如果另一份上的很晚,那麼這位nice的人就要餓着了。這說明什麼?越nice的人搶佔資源的能力就越差,而越不nice的人搶佔能力就越強。這就是nice值大小的含義,nice值越低,說明進程越不nice,搶佔cpu的能力就越強,優先級就越高。在原來使用O1調度的Linux上,我們還會把nice值叫做靜態優先級,這也基本符合nice值的特點,就是nice值設定好了之後,除非我們用renice去改它,否則它是不變的。而priority的值在之前內核的O1調度器上表現是會變化的,所以也叫做動態優先級。

4、三種優先級的關係

系統優先級、實時優先級、nice值之間有其對應關係,其關係如下圖:

wKiom1msIV7zrPlEAAALP0l0ttY623.png

三、調度算法

1、實時優先級的調度策略

在上面第二節中我們提到了一點調度算法這個概念,這一小節重點來給大家說說一些常見的系統調度算法。調度策略主要是針對實時優先級與非實時優先級而言的。我們可以通過chrt命令來查看或設置進程實時優先級的狀態。

[ root@vinsent ~ ]#chrt
Show or change the real-time scheduling attributes of a process.

Set policy:
 chrt [options] <priority> <command> [<arg>...]
 chrt [options] --pid <priority> <pid>

Get policy:
 chrt [options] -p <pid>

Policy options:
 -b, --batch          set policy to SCHED_BATCH
 -d, --deadline       set policy to SCHED_DEADLINE
 -f, --fifo           set policy to SCHED_FIFO
 -i, --idle           set policy to SCHED_IDLE
 -o, --other          set policy to SCHED_OTHER
 -r, --rr             set policy to SCHED_RR (default)

Scheduling options:
 -R, --reset-on-fork       set SCHED_RESET_ON_FORK for FIFO or RR
 -T, --sched-runtime <ns>  runtime parameter for DEADLINE
 -P, --sched-period <ns>   period parameter for DEADLINE
 -D, --sched-deadline <ns> deadline parameter for DEADLINE
...後面省略...

從上面的Policy options選項中我們發現,系統給我們提供了6中調度策略。但是這裏並沒有說明的是,這五種調度策略是分別給兩種進程用的,對於實時進程可以用的調度策略是:SCHED_FIFO、SCHED_RR,而對於非實時進程則是:SCHED_OTHER、SCHED_BATCH、SCHED_IDLE。系統的整體優先級策略是:如果系統中存在需要執行的實時進程,則優先執行實時進程。直到實時進程退出或者主動讓出CPU時,纔會調度執行非實時進程。實時進程可以指定的優先級範圍爲1-99,將一個要執行的程序以實時方式執行的方法爲:

[ root@vinsent ~ ]#bash           # 新打開一個bash
[ root@vinsent ~ ]#chrt -p $$     # 查看該bash的調度策略
pid 5140's current scheduling policy: SCHED_OTHER
pid 5140's current scheduling priority: 0
[ root@vinsent ~ ]#

可以看到,新打開的bash已經是實時進程,默認調度策略爲SCHED_OTHER,優先級爲0。如果想修改調度策略,就加個參數;

[ root@vinsent ~ ]#chrt -f 10 bash         # 以優先級10 ,SCHED_FIFO策略打開一個bash
[ root@vinsent ~ ]#chrt -p $$
pid 5169's current scheduling policy: SCHED_FIFO
pid 5169's current scheduling priority: 10

SCHED_RR和SCHED_FIFO都是實時調度策略,只能給實時進程設置。對於所有實時進程來說,優先級高的(就是priority數字小的)進程一定會保證先於優先級低的進程執行。SCHED_RR和SCHED_FIFO的調度策略只有當兩個實時進程的優先級一樣的時候纔會發生作用,其區別也是顧名思義:

SCHED_FIFO:以先進先出的隊列方式進行調度,在優先級一樣的情況下,誰先執行的就先調度誰,除非它退出或者主動釋放CPU。

SCHED_RR:以時間片輪轉的方式對相同優先級的多個進程進行處理。時間片長度爲100ms。

2、非實時優先級調度策略

上面講述了Linux對於實時進程的優先級和相關調度算法的描述。整體很簡單,也很實用。而相對更麻煩的是非實時進程,它們纔是Linux上進程的主要分類。對於非實時進程優先級的處理,我們首先還是要來介紹一下它們相關的調度算法:O1和CFS。

(1)O1調度器的調度過程

  • 首先,進程產生(fork)的時候會給一個進程分配一個時間片長度。這個新進程的時間片一般是父進程的一半,而父進程也會因此減少它的時間片長度爲原來的一半。就是說,如果一個進程產生了子進程,那麼它們將會平分當前時間片長度。比如,如果父進程時間片還剩100ms,那麼一個fork產生一個子進程之後,子進程的時間片是50ms,父進程剩餘的時間片是也是50ms。這樣設計的目的是,爲了防止進程通過fork的方式讓自己所處理的任務一直有時間片。不過這樣做也會帶來少許的不公平,因爲先產生的子進程獲得的時間片將會比後產生的長,第一個子進程分到父進程的一半,那麼第二個子進程就只能分到1/4。對於一個長期工作的進程組來說,這種影響可以忽略,因爲第一輪時間片在耗盡後,系統會在給它們分配長度相當的時間片。

  • 針對所有R狀態進程,O1算法使用兩個隊列組織進程,其中一個叫做活動隊列,另一個叫做過期隊列。活動隊列中放的都是時間片未被耗盡的進程,而過期隊列中放時間片被耗盡的進程。

  • 新產生的進程都會先獲得一個時間片,進入活動隊列等待調度到CPU執行。而內核會在每個tick間隔期間對正在CPU上執行的進程進行檢查。一般的tick間隔時間就是cpu時鐘中斷間隔,每秒鐘會有1000個,即頻率爲1000HZ。每個tick間隔週期主要檢查兩個內容:

    (1) 當前正在佔用CPU的進程是不是時間片已經耗盡了?

    (2) 是不是有更高優先級的進程在活動隊列中等待調度?如果任何一種情況成立,就把則當前進程的執行狀態終止,放到等待隊列中,換當前在等待隊列中優先級最高的那個進程執行。

(2)CFS完全公平調度

O1已經是上一代調度器了,由於其對多核、多CPU系統的支持性能並不好,並且內核功能上要加入cgroup等因素,Linux在2.6.23之後開始啓用CFS作爲對一般優先級(SCHED_OTHER)進程調度方法。在這個重新設計的調度器中,時間片,動態、靜態優先級以及IO消耗,CPU消耗的概念都不再重要。

CFS採用了一種全新的方式,對上述功能進行了比較完善的支持。其設計的基本思路是,我們想要實現一個對所有進程完全公平的調度器。又是那個老問題:如何做到完全公平?答案跟上一篇IO調度中CFQ的思路類似:如果當前有n個進程需要調度執行,那麼調度器應該再一個比較小的時間範圍內,把這n個進程全都調度執行一遍,並且它們平分cpu時間,這樣就可以做到所有進程的公平調度。那麼這個比較小的時間就是任意一個R狀態進程被調度的最大延時時間,即:任意一個R狀態進程,都一定會在這個時間範圍內被調度相應。這個時間也可以叫做調度週期,其英文名字叫做:sched_latency_ns。進程越多,每個進程在週期內被執行的時間就會被平分的越小。調度器只需要對所有進程維護一個累積佔用CPU時間數,就可以衡量出每個進程目前佔用的CPU時間總量是不是過大或者過小,這個數字記錄在每個進程的vruntime中。所有待執行進程都以vruntime爲key放到一個由紅黑樹組成的隊列中,每次被調度執行的進程,都是這個紅黑樹的最左子樹上的那個進程,即vruntime時間最少的進程,這樣就保證了所有進程的相對公平。在基本驅動機制上CFS跟O1一樣,每次時鐘中斷來臨的時候,都會進行隊列調度檢查,判斷是否要進程調度。當然還有別的時機需要調度檢查,發生調度的時機可以總結爲這樣幾個:

  • 當前進程的狀態轉換時。主要是指當前進程終止退出或者進程休眠的時候。

  • 當前進程主動放棄CPU時。狀態變爲sleep也可以理解爲主動放棄CPU,但是當前內核給了一個方法,可以使用sched_yield()在不發生狀態切換的情況下主動讓出CPU。

  • 當前進程的vruntime時間大於每個進程的理想佔用時間時(delta_exec > ideal_runtime)。這裏的ideal_runtime實際上就是上文說的sched_latency_ns/進程數n。當然這個值並不是一定這樣得出,下文會有更詳細解釋。

  • 當進程從中斷、異常或系統調用返回時,會發生調度檢查。比如時鐘中斷。


參閱:http://www.linuxidc.com/Linux/2016-05/131244.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章