深度介紹Linux內核是如何工做的

本文發表於Linux Format magazine雜誌,做者從技術深度上解釋了Linux Kernel是如何工做的。相信對Linux開發者來講有不小的幫助。html

牛津字典中對"kernel"一詞的定義是:"較軟的、一般是一個堅果可食用的部分。"固然還有第二種定義:"某個東西核心或者最重要的部分。"對Linux來講,它的Kernel無疑屬於第二種解釋。讓咱們來看看這個重要的東西是如何工做的,先從一點理論提及。linux

廣義地來講kernel就是一個軟件,它在硬件和運行在計算機上的應用程序之間提供了一個層。嚴格點從計算機科學的角度來講,Linux中的Kernel指的是Linus Torvalds在90年代初期寫的那點代碼。程序員

全部的你在Linux各版本中看到的其餘東西--Bash shell、KDE窗口管理器、web瀏覽器、X服務器、Tux Racer以及全部的其餘,都不過是運行在Linux上的應用而已,而不是操做系統自身的一部分。爲了給你們一個更加直觀的感受,我來舉個例子,好比RHEL5的安裝大概要佔據2.5GB的硬盤空間(具體多大固然視你的選擇安裝來定),在這其中,kernel以及它的各個模塊組件,只有47MB,所佔比例約爲2%。web

在kernel內部shell

那麼kernel究竟是如何工做的呢?以下面的圖表。Kernel經過許多的進入端口也就是咱們從技術角度所說的系統調用,來使得運行在它上面的應用程序可用。Kernel使用的系統調用好比"讀"和"寫"來提供你硬件的抽象(abstraction)。ubuntu


從程序員的視角來看,這些看起來只是普通的功能調用,然而實際上系統調用在處理器的操做模式上,從用戶空間到Kernel空間有一個明顯的切換。同時,系統調用提供了一個"Linux虛擬機",能夠被認爲是對硬件的抽象。瀏覽器

Kernel提供的更明顯的抽象之一是文件系統。舉例來講,這裏有一段短的程序是用C寫的,它打開了一個文件並將內容拷貝到標準的輸出:安全

#include <fcntl.h>
int main()
{
int fd, count; char buf[1000];
fd=open("mydata", O_RDONLY);
count = read(fd, buf, 1000);
write(1, buf, count);
close(fd);
}服務器

在這裏,你能夠看到四個系統調用的例子:打開、讀、寫和關閉。不談這段程序語法的細節,重點是:經過這些系統調用Linux Kernel提供了一個文件的"錯覺",而實際上它不過是一堆數據有了個名字,這樣一來你就沒必要去與硬件底層的堆棧、分區、頭和指針、分區等交涉了,而是直接以例子中的方式與硬件"交流",這也就是咱們所說的抽象(abstraction),將底層的東西以更易懂的方式表達出來。網絡

臺前幕後

系統文件是Kernel提供的較爲明顯的一種抽象。還有一些特性不是這麼的明顯,好比進程調度。任何一個時間,均可能有好幾個進程或者程序等待着運行。Kernel的時間調度給每一個進程分配CPU時間,因此就一段時間內來講,咱們會有種錯覺:電腦同一時間運行好幾個程序。這是另一個C程序:

#include <stdlib.h>
main()
{
if (fork()) {
write(1, "Parent\n", 7);
wait(0);
exit(0);
}
else {
write(1, "Child\n", 6);
exit(0);
}
}

在這個程序中建立了一個新進程,而原來的進程(父進程)和新進程(子進程)都編寫了標準輸出而後結束。注意系統調用fork(), exit() 以及 wait()執行程序的建立、結束和各自同步。這是進程管理和調度中最典型的簡單調用。

Kernel還有一個更加不易見到的功能,連程序員都不易察覺,那就是存儲管理。每一個程序運行得都好像它有個本身的地址空間來調用同樣,實際上它跟其餘進程同樣共享計算機的物理存儲,若是系統運行的存儲太低,它的地址空間甚至會被磁盤的交互區暫時寄用。存儲管理的另一個方面是防止一個進程訪問其餘進程的地址空間--對於多進程操做系統來講這是很必要的一個防範措施。

Kernel一樣還配置網絡連接協議好比IP、TCP和UDP等,它們在網絡上提供機器對機器(machine-to-machine)和進程對進程(process-to-process)的通訊。這裏又會形成一種假象,即TCP在兩個進程之間提供了一個固定鏈接--就好像鏈接兩個電話的銅線同樣,實際中卻並無固定的鏈接,特殊的引用協議好比FTP、DNS和HTTP是經過用戶級程序來實施的,而並不是Kernel的一部分。

Linux(像以前的Unix)在安全方面口碑很好,這是由於Kernel跟蹤記錄了每一個運行進程的user ID和group ID,每次當一個應用企圖訪問資源(好比打開一個文件來寫入)的時候,Kernel就會覈對文件上的訪問許可而後作出容許/禁止的命令。這種訪問控制模式最終對整個Linux系統的安全做用很大。

Kernel還提供了一大套模塊的集合,其功能包括如何處理與硬件設備交流的諸多細節、如何從磁盤讀取一個分區、若是從網絡接口卡獲取數據包等。有時咱們稱這些爲設備驅動。

模塊化的Kernel

如今咱們隊Kernel是作什麼的已經有了一些瞭解,讓咱們再來簡單看下它的物理組成。早期版本的Linux Kernel是總體式的,也就是說全部的部件都靜態地鏈接成一個(很大的)執行文件。

相比較而言,如今的Linux Kernel是模塊化的:許多功能包含在模塊內,而後動態地載入kernel中。這使得kernel的內核很小,並且在運行kernel時能夠沒必要reboot就能載入和替代模塊。

Kernel的內核在boot time時從位於/boot 目錄的一個文件加載進存儲中,一般這個/boot 目錄會被叫作KERNELVERSION,KERNELVERSION與kernel版本有關。(若是你想知道你的kernel版本是什麼,運行命令行顯示系統信息-r。)kernel的模塊位於目錄/lib/modules/KERNELVERSION之下,全部的組件都會在kernel安裝時被拷貝。

管理模塊

大部分狀況下,Linux管理它的模塊不須要你的幫忙,可是若是必要的時候有命令行能夠來手動檢查和管理模塊。好比,爲了查清楚當前到底哪一個模塊在載入kernel。這裏有一個輸出的例子:

# lsmod
pcspkr              4224  0
hci_usb            18204  2
psmouse            38920  0
bluetooth          55908  7 rfcomm,l2cap,hci_usb
yenta_socket       27532  5
rsrc_nonstatic     14080  1 yenta_socket
isofs              36284  0

輸出的內容包括:模塊的名字、大小、使用次數和依賴於它的模塊列表。使用次數對防止卸載當前活躍的模塊很是總要。Linux只容許使用次數爲零的模塊被移除。

你可使用modprobe來手動加載和卸載模塊,(還有兩個命令行叫作insmod和rmmod,但modprobe更易於使用由於它自動移除了模塊依賴)。好比lsmod的輸出在咱們的電腦上顯示了一個名叫isofs的卸載模塊,它的使用次數是零並且沒有依賴模塊,(isofs是一個模塊,它支持CD上使用的ISO系統文件格式)這種狀況下,kernel會容許咱們卸載模塊:

# modprobe -r isofs

如今,isofs再也不顯示在Ismod的輸出中,kernel由此節省了36,284字節的存儲。若是你放入CD而且讓它自動安裝,kernel將自動從新載入isofs模塊,並且isofs的使用次數增長到1次。若是這時候你還試圖移除模塊,就不會成功了由於它正在被使用:

# modprobe -r isofs
FATAL: Module isofs is in use.

Lsmod只是列出了當前被載入的模塊,modprobe則將列出全部可用的模塊,它實際上輸出了/lib/modules/KERNELVERSION目錄下全部的模塊,名單會很長!

實際上,使用modprobe來手動加載一個模塊並不常見,但確實能夠經過modprobe命令行來對模塊設置參數,例如:

# modprobe usbcore blinkenlights=1

咱們並非在建立blinkenlights,而是usbcore模塊的實參數。

那麼如何知道一個模塊會接受什麼參數呢?一個比較好的方法是使用modinfo命令,它列出了關於模塊的種種信息。這裏有一個關於模塊snd-hda-intel的例子

# modinfo snd-hda-intel
filename:       /lib/modules/2.6.20-16-generic/kernel/sound/pci/hda/snd-hda-intel.ko
description:    Intel HDA driver
license:        GPL
srcversion:     A3552B2DF3A932D88FFC00C
alias:          pci:v000010DEd0000055Dsv*sd*bc*sc*i*
alias:          pci:v000010DEd0000055Csv*sd*bc*sc*i*
depends:        snd-pcm,snd-page-alloc,snd-hda-codec,snd
vermagic:       2.6.20-16-generic SMP mod_unload 586
parm:           index:Index value for Intel HD audio interface. (int)
parm:           id:ID string for Intel HD audio interface. (charp)
parm:           model:Use the given board model. (charp)
parm:           position_fix:Fix DMA pointer (0 = auto, 1 = none, 2 = POSBUF, 3 = FIFO size). (int)
parm:           probe_mask:Bitmask to probe codecs (default = -1). (int)
parm:           single_cmd:Use single command to communicate with codecs (for debugging only). (bool)
parm:           enable_msi:Enable Message Signaled Interrupt (MSI) (int)
parm:           enable:bool

對咱們來講比較有興趣的以"parm"開頭的那些部分:顯示了模塊所接受的參數。這些描述都比較簡明,若是想要更多的信息,那就安裝kernel的源代碼,在相似於/usr/src/KERNELVERSION/Documentation的目錄下你會找到。

裏面會有一些有趣的東西,好比文件/usr/src/KERNELVERSION/Documentation/sound/alsa/ALSA-Configuration.txt描述的是被許多ALSA聲音模塊認可的參數;/usr/src/KERNELVERSION/Documentation/kernel-parameters.txt這個文件也頗有用。

前幾天在Ubuntu論壇有一個例子,說的是如何將參數傳遞到一個模塊(詳見https://help.ubuntu.com/community/HdaIntelSoundHowto)。實際上問題的關鍵是snd-hda-intel參數在正確驅動聲音硬件時須要一點操做,並且在boot time加載時會停止。解決方法的一部分是將probe_mask=1選項賦給模塊,若是你是手動加載模塊,你須要輸入:

# modprobe snd-hda-intel probe_mask=1

更有可能,你在文件/etc/modprobe.conf中放置這樣相似的一行:options snd-hda-intel probe_mask=1

這"告訴"modprobe每次在加載snd-hda-intel模塊時包含probe_mask=1選項。如今的有些Linux版本將這一信息分離進/etc/modprobe.d下的不一樣文件中了,而不是放入modprobe.conf中。

/proc系統文件

Linux kernel一樣經過/proc系統文件來展現了許多細節。爲了說明/proc,咱們首先須要擴展咱們對於文件的理解。除了認爲文件就是存儲在硬盤或者CD或者存儲空間上的持久信息以外,咱們還應當把它理解爲任何能夠經過傳統系統調用如:打開、讀、寫、關閉等訪問的信息,固然它也能夠被常見的程序訪問。

/proc之下的"文件"徹底是kernel虛擬的一個部分,給咱們一個視角能夠看到kernel內部的數據結構。實際上,許多Linux的報告工具均可以很好地呈如今/proc下的文件中尋到的格式化版本的信息。好比,一列/proc/modules將展現一列當前加載的模塊。

一樣的,/proc/meminfo提供了關於虛擬存儲系統當前狀態的更多細節信息,而類如vmstat的工具則是以一種更加可理解的方式提供了相同的一些信息;/proc/net/arp顯示了系統ARP cache的當前內容,從命令行來講,arp -a顯示的也是相同的信息。

尤爲有意思的是/proc/sys下的"文件"。/proc/sys/net/ipv4/ip_forward下的設置告訴咱們kernel是否將轉發IP數據包,也就是說是否扮演網關的做用。如今,kernel告訴咱們這是關閉的:

# cat /proc/sys/net/ipv4/ip_forward
0

當你發現你能夠對這些文件寫入的時候,你會以爲更加有意思。繼續舉例來講:

# echo 1 > /proc/sys/net/ipv4/ip_forward

將在運行的kernel中打開IP 轉發(IP forwarding)

除了使用cat和echo來檢查和更正/proc/sys下的設置之外,你也可使用sysctl命令:

# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

這等同於:
# cat /proc/sys/net/ipv4/ip_forward
0

也等同於:
# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

還等同於:
# echo 1 > /proc/sys/net/ipv4/ip_forward


須要注意的是,以這種方式你所作的設置改變只能影響當前運行的kernel的,當reboot的時候就再也不有效。若是想讓設置永久有效,將它們放置在/etc/sysctl.conf文件中。在boot time時,sysctl將自動從新肯定它在此文件下找到的任何設置。

/etc/sysctl.conf下的代碼行大概是這樣的:net.ipv4.ip_forward=1

性能調優(performance tuning)

有這樣一個說法:/proc/sys下可寫入的參數孕育了整個Linux性能調優的亞文化。我我的以爲這種說法有點過誇,但這裏會有幾個你確實很想一試的例子:Oracle 10g的安裝說明(www.oracle.com/technology/obe/obe10gdb/install/linuxpreinst/linuxpreinst.htm)要求你設置一組參數,包括:kernel.shmmax=2147483648 這將公用存儲器的大小設置爲2GB。(公用存儲器是處理期內的通訊機制,容許存儲單元在多個進程的地址空間內同時可用)

IBM 'Redpaper'在Linux性能和調優方面的說明(www.redbooks.ibm.com/abstracts/redp4285.html)在調教/proc/sys下的參數方面給出了很多建議,包括:vm.swappiness=100 這個參數控制着存儲頁如何被交換到磁盤。

一些參數能夠被設置從而提升安全性,如net.ipv4.icmp_echo_ignore_broadcasts=1 它"告訴"kernel沒必要響應ICMP請求,從而使得你的網絡免受類如Smurf攻擊之類的拒絕服務器(denial-of-service)型攻擊。
net.ipv4.conf.all.rp_filter=1 則是"告訴"kernel增強入站過濾(ingress filtering)和出站過濾(egress filtering)

那麼有沒有一個說明能涵蓋這全部的參數?好吧,這有一行命令:# sysctl -a 它將展現全部的參數名字和當前值。列表很長,可是你沒法知道這些參數是作什麼的。另外比較有用的參考是Red Hat Enterprise Linux Reference Guide,對此有整章節的描述,你能夠從www.redhat.com/docs/manuals/enterprise上下載。

相關文章
相關標籤/搜索