進程就是一段執行的程式,每當一個程式運行時,對於作業系統本身來說,就建立了一個進程,並且分配了對應的資源。進程可以分為3個類別:
1.互動式進程(I/O)
2.批處理進程 (CPU)
3.即時進程 (REAL-TIME)
對於互動式進程來說,一般其佔用的cpu時間片很段,但是優先順序偏高;批處理進程佔用的cpu時間片很長,但是優先順序偏底;即時進程是核心所使用的,其優先順序高於前面兩種。
上面說到了優先順序,linux進程是具有優先順序的,一般分為兩種:
1.即時優先順序
2.靜態優先順序
即時優先順序的取值範圍是1-99,值越底,優先順序越底,一般為系統核心所使用;靜態優先順序的取值範圍為100-139,值越底優先順序越高,一般為應用程式所使用,通過使用nice值可以調靜態優先順序的先後,其取值範圍為-20到19,對應與100-139;而即時優先順序比靜態優先順序的層級高。
linux的內部支援3種調度類別:
對於即時進程來說其支援兩種調度類別:
1.SCHED_FIFO:first in first out
2.SCHED_RR:round robin
對於FF類型的進程就通過SCHED_FIFO來調用;對於RR類型的進程就通過SCHED_RR來調用
對於使用者進程來說其支援1種調度類別:
1.SCHED_OTHER
對於OS類型的進程就通過SCHED_OTHER來調用
那麼如何查看當前作業系統進程使用的是那些資訊呢?可以通過如下命令來查看
# ps -e -o class,rtprio,pri,nice,command
CLS RTPRIO PRI NI COMMAND
TS - 24 0 init [3]
FF 99 139 - [migration/0]
TS - 5 19 [ksoftirqd/0]
TS - 29 -5 [events/0]
TS - 28 -5 [khelper]
TS - 29 -5 [kthread]
TS - 29 -5 [kblockd/0]
TS - 19 -5 [kacpid]
TS - 20 -5 [cqueue/0]
TS - 29 -5 [khubd]
TS - 29 -5 [kseriod]
TS - 24 0 [khungtaskd]
TS - 24 0 [pdflush]
TS - 29 -5 [kswapd0]
TS - 19 -5 [aio/0]
TS - 28 -5 [kpsmoused]
TS - 29 -5 [mpt_poll_0]
TS - 19 -5 [mpt/0]
TS - 19 -5 [scsi_eh_0]
TS - 19 -5 [ata/0]
TS - 19 -5 [ata_aux]
TS - 19 -5 [kstriped]
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/apache/bin/httpd -k start
TS - 29 -5 [kjournald]
TS - 22 0 /usr/local/apache/bin/httpd -k start
TS - 22 0 /usr/local/apache/bin/httpd -k start
TS - 22 0 /usr/local/apache/bin/httpd -k start
TS - 29 -5 [kauditd]
TS - 24 -4 /sbin/udevd -d
TS - 21 0 /usr/local/apache/bin/httpd -k start
TS - 17 0 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data --pi
TS - 21 0 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --data
TS - 24 0 /sbin/dhclient -1 -q -lf /var/lib/dhclient/dhclient-eth0.leas
TS - 20 -5 [kgameportd]
TS - 23 0 sshd: chenqiguo [priv]
TS - 24 0 sshd: chenqiguo@pts/0
TS - 24 0 -bash
TS - 23 0 su -
TS - 24 0 -bash
TS - 19 -5 [kmpathd/0]
TS - 19 -5 [kmpath_handlerd]
TS - 29 -5 [kjournald]
TS - 29 -5 [kjournald]
TS - 24 0 /usr/sbin/vmtoolsd
TS - 24 0 cupsd
TS - 24 0 tpvmlpd2
TS - 26 -5 [iscsi_eh]
TS - 22 -5 [cnic_wq]
TS - 36 -20 [bnx2i_thread/0]
TS - 29 -5 [ib_addr]
TS - 19 -5 [ib_mcast]
TS - 19 -5 [ib_inform]
TS - 19 -5 [local_sa]
TS - 19 -5 [iw_cm_wq]
TS - 19 -5 [ib_cm/0]
TS - 19 -5 [rdma_cm]
TS - 25 -10 iscsiuio
TS - 21 0 iscsid
TS - 34 -10 iscsid
TS - 28 -4 auditd
TS - 32 -8 /sbin/audispd
TS - 24 0 syslogd -m 0
TS - 24 0 klogd -x
TS - 24 0 portmap
TS - 29 -5 [rpciod/0]
TS - 14 0 rpc.statd
TS - 24 0 dbus-daemon --system
TS - 24 0 /usr/sbin/hcid
TS - 14 0 /usr/sbin/sdpd
TS - 30 -10 [krfcommd]
TS - 17 0 pcscd
TS - 21 0 /usr/sbin/acpid
TS - 24 0 hald
TS - 24 0 hald-runner
TS - 21 0 hald-addon-acpi: listening on acpid socket /var/run/acpid.soc
TS - 24 0 hald-addon-keyboard: listening on /dev/input/event0
TS - 23 0 hald-addon-storage: polling /dev/hdc
TS - 14 0 /usr/bin/hidd --server
TS - 17 0 automount --pid-file /var/run/autofs.pid
TS - 24 0 /usr/sbin/sshd
TS - 24 0 sendmail: accepting connections
TS - 24 0 sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue
TS - 24 0 gpm -m /dev/input/mice -t exps2
TS - 24 0 crond
TS - 21 0 xfs -droppriv -daemon
TS - 21 0 /usr/sbin/atd
TS - 21 0 libvirtd --daemon
TS - 24 0 avahi-daemon: running [localhost-2.local]
TS - 15 0 avahi-daemon: chroot helper
TS - 24 0 /usr/sbin/dnsmasq --strict-order --bind-interfaces --pid-file
TS - 24 0 /usr/sbin/smartd -q never
TS - 23 0 login -- root
TS - 24 0 /sbin/mingetty tty2
TS - 24 0 /sbin/mingetty tty3
TS - 24 0 /sbin/mingetty tty4
TS - 19 0 /sbin/mingetty tty5
TS - 20 0 /sbin/mingetty tty6
TS - 24 0 -bash
TS - 5 19 /usr/bin/python -tt /usr/sbin/yum-updatesd
TS - 5 19 /usr/libexec/gam_server
TS - 5 19 /usr/bin/python -tt /usr/libexec/yum-updatesd-helper --check
TS - 22 0 ps -e -o class,rtprio,pri,nice,command
TS - 21 0 rpc.idmapd
TS - 29 -5 [kjournald]
TS - 24 0 [pdflush]
TS - 24 0 /usr/sbin/snmptrapd -Lsd -p /var/run/snmptrapd.pid
TS - 24 0 smbd -D
TS - 24 0 nmbd -D
TS - 21 0 smbd -D
TS - 24 0 /usr/local/php/bin/php server.php
TS - 20 0 /usr/local/php/bin/php server.php
TS - 24 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 21 0 /usr/local/php/bin/php server.php
TS - 24 0 /usr/sbin/snmpd -Lsd -Lf /dev/null -p /var/run/snmpd.pid -a
CLS對應核心的調度演算法,RTPRIO對應即時優先順序,PRI對應靜態優先順序,NICE對應靜態優先順序的指定層級,COMMAND對應進程的命令。注意上面[]都是核心的一些線程。
由於進程具有優先順序的概念,在極端的情況下可能會出現一種情況就是由於前面有著很多優先順序高的進程,導致優先順序底的進程永遠運行不上,所以又引出了動態優先順序的概念。為了避免有些優先順序偏底的進程運行不上,linux核心內部會臨時的調高這些優先順序偏底的進程;當然相反,對於有些優先順序偏高的進程,則會臨時的調底他的優先順序。動態優先順序作用在SCHED_OTHER上,主要就是使用者空間的進程。其演算法為:dynamic priority = max (100,min(static priority - bonus + 5,139))。bonux的取值範圍為0-10
當然動態優先順序不是萬能的,有些時候可能違背我們的意願。比如我們現在正在運行一台web伺服器,其並發量相對較高,需要即時運行,而動態優先順序可能有時候會降低我們的web伺服器的進程,顯示這違背了我們的意願,我們期待的是web伺服器本身就應該較高的獲得cpu的使用權。所以可以手動調整一個進程的優先順序。
對於100-139的進程優先順序可以使用nice和renice命令來調整。nice表示啟動一個進程的時候手動調整一個進程的優先順序;renice表示更改一個正在啟動並執行進程的優先順序。
例如對剛啟動的httpd手執行優先順序的命令:
# nice --5 /usr/local/apache/bin/apachectl start
# ps -e -o class,rtprio,pri,nice,command | grep httpd
TS - 24 -5 /usr/local/apache/bin/httpd -k start
TS - 23 -5 /usr/local/apache/bin/httpd -k start
TS - 23 -5 /usr/local/apache/bin/httpd -k start
TS - 23 -5 /usr/local/apache/bin/httpd -k start
可以看到優先順序已經被改成了-5。對正在啟動並執行進程改變優先順序的命令:
# ps -ef | grep httpd
root 5474 1 0 06:50 ? 00:00:00 /usr/local/apache/bin/httpd -k start
# renice -10 5474
# ps -e -o class,rtprio,pri,nice,command | grep httpd
TS - 34 -10 /usr/local/apache/bin/httpd -k start
可以看到以前優先順序為-5的httpd進程已經被改成了-10。
對於1-99的進程可以使用chrt命令來改變優先順序。而對於即時優先順序來說,又分為sched_fifo和sched_rr,所以在調整優先順序的時候還需要指定是那一個調度演算法。
這裡我們來調整一個fifo類別的優先順序:
# ps -e -o class,rtprio,pri,nice,command,pid | grep FF
FF 99 139 - [migration/0] 2
# chrt -f -p 90 2 (-f指定是fifo類別的,-p指定層級,2指定進程的pid)
# ps -e -o class,rtprio,pri,nice,command,pid | grep FF
FF 90 130 - [migration/0] 2
對於rr類別的使用給上面一樣,只需要把-f換成-r即可。
那麼linux是如何在眾多進程中去選擇當前需要使用的進程的呢?因為進程的優先順序別總共有139種,所以建立139個隊列,每個隊列中存放了當前對應層級的進程。當需要調用一個進程的時候,就按照進程的優先順序去掃描整個隊列的首部(第一個元素),如果當前較高的優先順序中有了就取出,沒有就繼續往下尋找,直到找到一個為止。這種尋找方式可以忽略進程的多少,促使進程的演算法複雜度始終為0(1),以達到快速尋找到高優先順序的進程。事實上,每一個隊列又分為了兩種情況,活動隊列和到期隊列。當一個進程被調度以後,但是又沒有執行完成的情況下,就會被放到到期隊列中排隊。當活動隊列中的所有進程運行完畢以後,再把活動隊列和到期隊列交換一下,重新執行活動隊列(老的到期隊列)。
在一個多cpu的作業系統上,由於記憶體是共有資源,所以在訪問記憶體上的資源的時候可能會產生資源競爭,我們把這種cpu的架構叫做SMP(Symmetric Multi-Processor,對稱式多處理器)
一個cpu在訪問記憶體資料後至少需要3個cpu的刻度:
1.向記憶體控制器傳輸一個定址要求,記憶體控制器返回定址指令。
2.找到對應的記憶體位址,並施加一定的請求機制。例如讀鎖和寫鎖。
3.完成讀或者寫的操作。
因此一個cpu在給記憶體打交道的時候,其餘cpu是不能訪問記憶體的。所以在smp架構下,cpu多了並不一定是好事,因為cpu越多,競爭資源的機會就越大,效能反而會降低。因此產生了另外一種cpu的架構numa( Non-Unified Memory Access,非一致記憶體訪問)
硬體已經趨向使用多條系統匯流排,每條系統匯流排為一小組處理器提供服務。每組處理器都有自己的記憶體,並可能有自己的 I/O 通道。但是,每個 CPU都可以通過一致的方式訪問與其他組關聯的記憶體。每個組稱為一個“NUMA 節點”。NUMA 節點中的 CPU 數量取決於硬體供應商。訪問本地記憶體比訪問與其他 NUMA 節點關聯的記憶體快。這就是“非統一記憶體存取體繫結構”名稱的由來。
在numa架構的cpu上,由於訪問其它“NUMA”節點記憶體的速率沒有本地的快,所以要儘可能的使得cpu只訪問自己控制的記憶體。由於核心自身會平衡進程,所以使得進程會在兩個cpu之間頻繁的切換,最終帶來的結果就是一定會交叉記憶體訪問,即訪問不是自己的"NUMA"節點記憶體,從而造成效能的降低。因此,在一個繁忙的伺服器上,例如web伺服器,可以使用cpu綁定來讓web伺服器固定的運行在某個cpu上來避免交叉記憶體的訪問。
在有些時候,平衡是必然的,不然會產生一顆cpu很閑,而另外一顆cpu很忙的情況,所以必須要找到一個平衡點。一般來說,在numa架構下,當我們的記憶體本身叫用次數很低的情況下,這種效果就很有用了。可以使用numastat命令來查看。
# numastat
node0
numa_hit 25102521
numa_miss 0
numa_foreign 0
interleave_hit 46349
local_node 25102521
other_node 0
由於本機是非numa架構的cpu所以只顯示了node0,在真正的numa架構下,還會顯示node1等等。numa_hit表示在本地的記憶體訪問命中的次數,numa_miss表示沒有命中的次數。因為只有一段記憶體空間,所以這裡全部命中。如果在numa架構下,numa_miss的值過高的話,就應該把核心服務進程綁定到指定的cpu上,並且啟動numad進程。numad可以在硬體層級將我們的進程和某些cpu綁定在一起。
要想達到將指定的進程和指定的cpu綁定,可以使用taskset命令,並且使用掩碼的方式來標記cpu。0x0000 0001表示第0顆cpu,0x00000003表示第0顆和第一顆cpu。taskset的基本命令:taskset -p mask pid。例如我們想在5132這個進程綁定在1號cpu上的話可以使用:
# taskset -p 0x00000002 5132
但是這種方法不夠直觀,所以可以使用-c選項直接指定綁定的cpu號數。例如加了-c選項的上述寫法為:
# taskset -p -c 1 5132
這樣就直接將5132這個進程綁定在了第一號cpu上。
例如我現在的機子上有一顆2核心的cpu。查看某些進程綁定在了那些cpu上可以使用:
# ps -e -o pid,psr,command
PID PSR COMMAND
1 0 init [3]
2 0 [migration/0]
3 0 [ksoftirqd/0]
4 1 [migration/1]
5 1 [ksoftirqd/1]
6 0 [events/0]
7 1 [events/1]
8 0 [khelper]
由於篇幅限制就不列舉完了,其中的PSR就是當前進程運行在了那顆cpu號上。觀察當前mysql服務運行在了那顆cpu上:
# ps -e -o pid,psr,command | grep mysql
4498 0 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data --pid-file=/data/localhost.localdomain.pid
可以看到mysql進程運行在了0號cpu上,於是我們實驗一下將他綁定在1號cpu上。
# taskset -p -c 1 4498
pid 4498's current affinity list: 0,1
pid 4498's new affinity list: 1
# ps -e -o pid,psr,command | grep mysql
4498 1 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data --pid-file=/data/localhost.localdomain.pid
這樣我們就完成了對mysql進程到對應cpu的綁定過程。
但是上面仍然不能滿足1號cpu只運行mysql進程,還有其它的進程也會運行在1號cpu,所以仍然需要進程間的大量切換。因此可以在/etc/grub.conf中的kener參數中指定某些cpu開機以來不運行其它進程或者服務。例如:
kernel /vmlinuz-2.6.18-371.6.1.el5 ro root=LABEL=/ rhgb quiet isolcpus=1
isolcpus的文法是isolcpus=cpu number,...,cpu number,就是把指定的cpu隔離出來不供其它進程使用。指定上述以後重新啟動操作以後再來觀察cpu的進程使用方式。
# ps -e -o pid,psr,command
PID PSR COMMAND
1 0 init [3]
2 0 [migration/0]
3 0 [ksoftirqd/0]
4 1 [migration/1]
5 1 [ksoftirqd/1]
6 0 [events/0]
7 1 [events/1]
8 0 [khelper]
13 0 [kthread]
18 0 [kblockd/0]
19 1 [kblockd/1]
20 0 [kacpid]
190 0 [cqueue/0]
191 1 [cqueue/1]
194 0 [khubd]
196 0 [kseriod]
274 0 [khungtaskd]
275 0 [pdflush]
276 0 [pdflush]
277 0 [kswapd0]
278 0 [aio/0]
279 1 [aio/1]
486 0 [kpsmoused]
527 0 [mpt_poll_0]
528 0 [mpt/0]
529 0 [scsi_eh_0]
533 0 [ata/0]
534 1 [ata/1]
535 0 [ata_aux]
545 0 [kstriped]
558 0 [kjournald]
584 0 [kauditd]
617 0 /sbin/udevd -d
1800 0 [kgameportd]
2409 0 [kmpathd/0]
2410 1 [kmpathd/1]
2411 0 [kmpath_handlerd]
2444 0 [kjournald]
2446 0 [kjournald]
3111 0 /usr/sbin/vmtoolsd
3178 0 cupsd
3223 0 tpvmlpd2
3329 0 [iscsi_eh]
3375 0 [cnic_wq]
3382 0 [bnx2i_thread/0]
3383 1 [bnx2i_thread/1]
3395 0 [ib_addr]
3405 0 [ib_mcast]
3406 0 [ib_inform]
3407 0 [local_sa]
3411 0 [iw_cm_wq]
3415 0 [ib_cm/0]
3416 1 [ib_cm/1]
3420 0 [rdma_cm]
3437 0 iscsiuio
3442 0 iscsid
3443 0 iscsid
3850 0 /sbin/dhclient -1 -q -lf /var/lib/dhclient/dhclient-eth0.leases -pf /v
3958 0 auditd
3960 0 /sbin/audispd
3992 0 syslogd -m 0
3995 0 klogd -x
4075 0 irqbalance
4108 0 portmap
4144 0 [rpciod/0]
4145 1 [rpciod/1]
4152 0 rpc.statd
4190 0 rpc.idmapd
4222 0 dbus-daemon --system
4236 0 /usr/sbin/hcid
4240 0 /usr/sbin/sdpd
4256 0 [krfcommd]
4302 0 pcscd
4317 0 /usr/sbin/acpid
4332 0 hald
4333 0 hald-runner
4340 0 hald-addon-acpi: listening on acpid socket /var/run/acpid.socket
4354 0 hald-addon-keyboard: listening on /dev/input/event0
4363 0 hald-addon-storage: polling /dev/hdc
4393 0 /usr/bin/hidd --server
4439 0 automount --pid-file /var/run/autofs.pid
4464 0 /usr/sbin/sshd
4481 0 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data --pid-file=/d
4632 0 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data
4689 0 sendmail: accepting connections
4697 0 sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue
4712 0 gpm -m /dev/input/mice -t exps2
4726 0 crond
4758 0 xfs -droppriv -daemon
4772 0 anacron -s
4785 0 /usr/sbin/atd
4802 0 libvirtd --daemon
4845 0 avahi-daemon: running [localhost.local]
4846 0 avahi-daemon: chroot helper
4918 0 /usr/sbin/dnsmasq --strict-order --bind-interfaces --pid-file=/var/run
4940 0 /usr/sbin/smartd -q never
4944 0 login -- root
4945 0 /sbin/mingetty tty2
4946 0 /sbin/mingetty tty3
4947 0 /sbin/mingetty tty4
4952 0 /sbin/mingetty tty5
4955 0 /sbin/mingetty tty6
4960 0 /usr/bin/python -tt /usr/sbin/yum-updatesd
4962 0 /usr/libexec/gam_server
可以看到大量的進程都運行在0號cpu上,除了核心的一些線程以外。也就是說所謂的隔離cpu並不是完全的,因為核心的一些少量的線程仍然會運行在被隔離的cpu上。於是我們還是拿上面的mysql進程來說。先讓它運行在1號cpu上。
# taskset -p -c 1 4481
pid 4481's current affinity list: 0
pid 4481's new affinity list: 1
看到這裡的區別了嗎?上面沒有隔離之前current affinity list的值為0和1,而這裡只為0,說明隔離cpu是成功了。
當然即使這樣做了,仍然不能保證1號cpu上只服務mysql進程,因為cpu還要服務於中斷。1號cpu正在服務於mysql的時候,突然其它硬體發出了一個插斷要求,1號cpu可能還會被轉入核心模式去處理插斷要求。因此還可以把1號cpu給隔離出來,也不處理中斷的請求,這樣1號cpu就可以完完全全服務於mysql進程和一些少量的核心線程了。
為了實現cpu的中斷綁定,我們還需要關閉系統內部的的一個服務,叫做irqbalance。這個服務會定期平均的重新分配硬體服務中斷到各個cpu號上去。為了紡織irqbalance服務分配中斷服務到隔離的cpu號上去,那麼就應該禁止啟用該服務。
# service irqbalance stop
# chkconfig irqbalance off
將某些中斷號給某顆或者多顆cpu綁定起來的文法格式為:echo cpu_mask > /proc/irq/<irq_num>/smp_affinity,當前我的機子上的中斷號有:
# cat /proc/irq/
0/ 10/ 12/ 14/ 2/ 4/ 51/ 6/ 7/ 8/
1/ 11/ 13/ 15/ 3/ 5/ 59/ 67/ 75/ 9/
# cat /proc/interrupts查看當前的中斷資訊
CPU0 CPU1
0: 4719693 0 IO-APIC-edge timer
1: 83 0 IO-APIC-edge i8042
6: 5 0 IO-APIC-edge floppy
7: 0 0 IO-APIC-edge parport0
8: 1 0 IO-APIC-edge rtc
9: 0 0 IO-APIC-level acpi
12: 132 0 IO-APIC-edge i8042
15: 2964 39221 IO-APIC-edge ide1
51: 12271 13247 IO-APIC-level ehci_hcd:usb1, ioc0
59: 9142 3854647 IO-APIC-level uhci_hcd:usb2
67: 86 10107 IO-APIC-level eth0
75: 0 0 IO-APIC-level Ensoniq AudioPCI
NMI: 0 0
LOC: 4719083 4720165
ERR: 0
MIS: 0
可以看到一些中斷資訊。我們拿0號中斷來說明:
0就為中斷號,4719693表示在cpu0上響應了4719693個中斷,0表示在cpu1上響應了0 個中斷,IO-APIC-edge表示連結在這個連接埠的中斷鏈表上裝置介面,timer表示裝置名稱。
注意:對於0和2的中斷號來說是特殊的,核心不允許這2個中斷號被修改。所以在我當前的系統下除了0以外的都可以設定其中斷固定在某個cpu號上。假如我們要將中斷號為59的綁定在cpu0上可以使用:
# echo 00000001 > /proc/irq/59/smp_affinity
這樣所有發生在59號上的中斷都會教給cpu0來處理,從而徹底隔離了cpu1(原來59號中斷都是由cpu1來處理)。這裡我們採用了掩碼的方式來計算cpu的號數,當然也可以用十進位的方式來處理。cpu的號數和對應的十進位如下:
Zero-based CPU ID: 7 6 5 4 3 2 1 0
Decimal Value: 128 64 32 16 8 4 2 1
假如這裡我們想把所有中斷綁定在1-4號cpu上,可以使用echo "15" > /proc/irq/59/smp_affinity(15=1+2+4+8)。
由於重新關機以後,這些設定都不會起作用,要使他們生效該怎麼做,這裡就不再說明了把,想必大家都知道了。