Linux 的同步機制從 2.0 到 2.6 以來不斷髮展完善。
從最初的原子操做,到後來 的信號量,從大內核鎖到今天的自旋鎖。
這些同步機制的發展伴隨 Linux 從單處 理器到對稱多處理器的過渡;伴隨着從非搶佔內核到搶佔內核的過分。Linux 的 鎖機制愈來愈有效,也愈來愈複雜。自旋鎖最多隻能被一個可執行線程持有,若是一個執行線程試圖請求一個已被爭 用已經被持有)的自旋鎖,那麼這個線程就會一直進行忙循環——旋轉——等待 鎖從新可用。要是鎖未被爭用,請求它的執行線程便能馬上獲得它而且繼續進行。 自旋鎖能夠在任什麼時候刻防止多於一個的執行線程同時進入臨界區。 信號量的睡眠特性,使得信號量適用於鎖會被長時間持有的狀況;只能在進程上 下文中使用,由於中斷上下文中是不能被調度的;另外當代碼持有信號量時,不 能夠再持有自旋鎖。 Linux 內核中的同步機制:原子操做、信號量、讀寫信號量和自旋鎖的 API,另 外一些同步機制,包括大內核鎖、讀寫鎖、大讀者鎖、RCU (Read-Copy Update, 顧名思義就是讀-拷貝修改),和順序鎖。node
MS-DOS 等操做系統在單一的 CPU 模式下運行,可是一些類 Unix 的操做系統則使 用了雙模式,能夠有效地實現時間共享。在 Linux 機器上,CPU 要麼處於受信任 的內核模式,要麼處於受限制的用戶模式。除了內核自己處於內核模式之外,所 有的用戶進程都運行在用戶模式之中。 內核模式的代碼能夠無限制地訪問全部處理器指令集以及所有內存和 I/O 空間。 若是用戶模式的進程要享有此特權,它必須經過系統調用向設備驅動程序或其餘 內核模式的代碼發出請求。另外,用戶模式的代碼容許發生缺頁,而內核模式的 代碼則不容許。 在 2.4 和更早的內核中,僅僅用戶模式的進程能夠被上下文切換出局,由其餘進 程搶佔。
除非發生如下兩種狀況,不然內核模式代碼能夠一直獨佔 CPU: (1) 它自願放棄 CPU; (2) 發生中斷或異常。
2.6 內核引入了內核搶佔,大多數內核模式的代碼也能夠被搶佔。linux
在 Linux 內核環境下,申請大塊內存的成功率隨着系統運行時間的增長而減小, 雖然能夠經過 vmalloc 系列調用申請物理不連續但虛擬地址連續的內存,但畢竟 其使用效率不高且在 32 位系統上 vmalloc 的內存地址空間有限。因此,通常的 建議是在系統啓動階段申請大塊內存,可是其成功的機率也只是比較高而已,而不是100%。若是程序真的比較在乎這個申請的成功與否,只能退用「啓動內 存」Boot Memory)。算法
下面就是申請並導出啓動內存的一段示例代碼:緩存
void* x_bootmem = NULL; EXPORT_SYMBOL(x_bootmem); unsigned long x_bootmem_size = 0; EXPORT_SYMBOL(x_bootmem_size); static int __init x_bootmem_setup(char *str) { x_bootmem_size = memparse(str, &str); x_bootmem = alloc_bootmem(x_bootmem_size); printk("Reserved %lu bytes from %p for xn", x_bootmem_size, x_bootmem); return 1; } __setup("x-bootmem=", x_bootmem_setup);
可見其應用仍是比較簡單的,不過利弊老是共生的,它不可避免也有其自身的限制:內存申請代碼只能鏈接進內核,不能在模塊中使用。被申請的內存不會被頁分配 器和 slab 分配器所使用和統計,也就是說它處於系統的可見內存以外,即便在 未來的某個地方你釋放了它。通常用戶只會申請一大塊內存,若是須要在其上實 現複雜的內存管理則須要本身實現。服務器
在不容許內存分配失敗的場合,經過啓動內 存預留內存空間將是咱們惟一的選擇。數據結構
1)管道 Pipe):管道可用於具備親緣關係進程間的通訊,容許一個進程和另外一個 與它有共同祖先的進程之間進行通訊併發
2)命名管道 named pipe):命名管道克服了管道沒有名字的限制,所以,除具備 管道所具備的功能外,它還容許無親緣關係進程間的通訊。命名管道在文件系統 中有對應的文件名。命名管道經過命令 mkfifo 或系統調用 mkfifo 來建立。ide
3)信號 Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發 生,除了用於進程間通訊外,進程還能夠發送信號給進程自己;linux 除了支持 Unix 早期信號語義函數 sigal 外,還支持語義符合 Posix.1 標準的信號函數 sigaction 實際上,該函數是基於 BSD 的,BSD 爲了實現可靠信號機制,又可以 統一對外接口,用 sigaction 函數從新實現了 signal 函數)。函數
4)消息 Message)隊列:消息隊列是消息的連接表,包括 Posix 消息隊列 system V 消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則 能夠讀走隊列中的消息。性能
5)消息隊列克服了信號承載信息量少,管道只能承載無格 式字節流以及緩衝區大小受限等缺
6)信號量 semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。
7)套接字 Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間 通訊。起初是由 Unix 系統的 BSD 分支開發出來的,但如今通常能夠移植到其它 類 Unix 系統上:Linux 和 System V 的變種都支持套接字。
在物理頁面管理上實現了基於區的夥伴系統 zone based buddy system)。對不 同區的內存使用單獨的夥伴系統(buddy system)管理,並且獨立地監控空閒頁。 相應接口 alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。
補充知識:
1.原理說明 頁全局目錄(Page Global Directory) 頁中間目錄(Page Middle Directory) 頁全局目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄 的地址,而頁中間目錄又包含若干頁表的地址,每個頁表項指 向一個頁框。
Linux 中採用 4KB 大小的 頁框做爲標準的內存分配單元。 1.1.夥伴系統算法 爲了不出現這種狀況,Linux 內核中引入了夥伴系統算法(buddy system)。把 全部的空閒頁框分組爲 11 個 塊鏈表,每一個塊鏈表分別包含大小爲 1,2,4,8, 16,32,64,128,256,512 和 1024 個連續頁框的頁框塊。最大能夠申請 1024 個連 續頁框,對應 4MB 大小的連續內存。
每一個頁框塊的第一個頁框的物理地址 是該塊大小的整數倍。 頁框塊在釋放時,會主動將兩個連續的頁框塊合併爲一個較大的頁框塊。 slab 分配器源於 Solaris 2.4 的 分配算法,工做於物理內存頁框分配器之上, 管理特定大小對象的緩存,進行快速而高效的內存分配。
2.經常使用內存分配函數 unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order) __get_free_pages 函數是最原始的內存分配方式,直接從夥伴系統中獲取原始 頁框,返回值爲第一個頁框的起始地址。__get_free_pages 在實現上只是封裝 了 alloc_pages 函 數,從代碼分析,alloc_pages 函數會分配長度爲 1< struct kmem_cache kmem_cache_create(const char name, size_t size void (ctor)(void, struct kmem_cache , unsigned long), void kmem_cache_alloc(struct kmem_cache *c, gfp_t flags) kmem_cache_create/ kmem_cache_alloc 是基於 slab 分配器的一種內存分配方 式,適用於反覆分配釋放同一大小內存塊的場合。
首先用 kmem_cache_create 創 建一個高速緩存區域,而後用 kmem_cache_alloc 從 該高速緩存區域中獲取新的 內存塊。kmem_cache_alloc 一次能分配的最大內存由 mm/slab.c 文件中的 MAX_OBJ_ORDER 宏定義,在默認的 2.6.18 內核版本中,該宏定義爲 5,因而一次最多能申請 1<<5 * 4KB 也就是 128KB 的連續物理內存。分析內核源碼發現, kmem_cache_create 函數的 size 參數大於 128KB 時會調用 BUG()。測試結果驗證 了分析結果,用 kmem_cache_create 分配超過 128KB 的內存時使內核崩潰。
void *kmalloc(size_t size, gfp_t flags) 2.4.vmalloc 前面幾種內存分配方式都是物理連續的,能保證較低的平均訪問時間。可是在某 些場合中,對內存區的請求不是很頻繁,較高的內存訪問時間也 能夠接受,這 是就能夠分配一段線性連續,物理不連續的地址,帶來的好處是一次能夠分配較 大塊的內存。
圖 3-1 表 示的是 vmalloc 分配的內存使用的地址範圍。vmalloc 對 一次能分配的內存大小沒有明確限制。出於性能考慮,應謹慎使用 vmalloc 函數。 在測試過程當中, 最大能一次分配 1GB 的空間。
2.5.dma_alloc_coherent ma_addr_t dma_handle, gfp_t gfp) 2.6.ioremap ioremap 是一種更直接的內存「分配」方式,使用時直接指定物理起始地址和需 要分配內存的大小,而後將該段 物理地址映射到內核地址空間。ioremap 用到 的物理地址空間都是事先肯定的,和上面的幾種內存 分配方式並不太同樣,並 不是分配一段新的物理內存。ioremap 多用於設備驅動,可讓 CPU 直接訪問外 部設備的 IO 空間。ioremap 能映射的內存由原有的物理內存空間決定,因此沒 有進行測試。 若是要分配大量的連續物理內存,上述的分配函數都不能知足,就只能用比較特 殊的方式,在 Linux 內 核引導階段來預留部份內存。 void alloc_bootmem(unsigned long size) 2.7.2.經過內核引導參數預留頂部內存 3.幾種分配函數的比較 __get_free_pages 直接對頁框進行操做 4MB 適用於分配較大量的連續物理內存 kmalloc 基於 kmem_cache_alloc 實現 128KB 最多見的分配方式,須要小於頁框 大小的內存時可使用 dma_alloc_coherent 基於__alloc_pages 實現 4MB 適用於 DMA 操 做 alloc_bootmem 在啓動 kernel 時,預留一段內存,內核看不見小於物理內存大 小,內存管理要求較高。
虛擬文件系統。
struct super_block struct inode struct file struct dentry;
struct file_operations
執行文件,普通文件,目錄文件,連接文件和設備文件,管道文件
clone(),fork(),vfork();系統調用服務例程:sys_clone,sys_fork,sys_vfork;
1.系統調用 do_fork(); 2.定時中斷 do_timer(); 3.喚醒進程 wake_up_process 4.改變進程的調度策略 setscheduler(); 5.系統調用禮讓 sys_sched_yield();
Liunx 調度程序是根據根據進程的動態優先級來調度進程的,可是動態優先級又 是根據靜態優先級根據算法計算出來的,二者是兩個相關聯的值。
由於高優先級 的進程老是比低優先級的進程先被調度,爲防止多個高優先級的進程佔用 CPU 資 源,致使其餘進程不能佔有 CPU,因此引用動態優先級概念。
struct runqueue, struct task_struct, struct sched_struct
insmod 加載,rmmod 卸載
模塊運行在內核空間,應用程序運行在用戶空間
應用程序實現,Linux 中的浮點運算是利用數學庫函數實現的,庫函數可以被應 用程序連接後調用,不能被內核連接調用。這些運算是在應用程序中運行的,然 後再把結果反饋給系統。
Linux 內核若是必定要進行浮點運算,須要在創建內核 時選上 math-emu,使用軟件模擬計算浮點運算,聽說這樣作的代價有兩個:用戶 在安裝驅動時須要重建內核,可能會影響到其餘的應用程序,使得這些應用程序 在作浮點運算的時候也使用 math-emu,大大的下降了效率。
模塊程序運行在內核空間,不能連接庫函數。
TLB,頁表緩存,當線性地址被第一次轉換成物理地址的時候,將線性地址和物 理地址的對應放到 TLB 中,用於下次訪問這個線性地址時,加快轉換速度。
字符設備和塊設備。網卡是例外,他不直接與設備文件對應,mknod 系統調用用 來建立設備文件。
字符設備描述符 struct cdev,cdev_alloc()用於動態的分配 cdev 描述符, cdev_add()用於註冊一個 cdev 描述符,cdev 包含一個 struct kobject 類型的 數據結構它是核心的數據結構。
open(),read(),write(),llseek(),realse();
Linux 使用一個設備編號來惟一的標示一個設備,設備編號分爲:主設備號和次 設備號,通常主設備號標示設備對應的驅動程序,次設備號對應設備文件指向的 設備,在內核中使用 dev_t 來表示設備編號,通常它是 32 位長度,其中 12 位用 於表示主設備號,20 位用於表示次設備號,利用 MKDEV(int major,int minor); 用於生成一個 dev_t 類型的對象。
靠軟件中斷實現的,首先,用戶程序爲系統調用設置參數,其中一個編號是系統 調用編號,參數設置完成後,程序執行系統調用指令,x86 上的軟中斷是有 int 產生的,這個指令會致使一個異常,產生一個事件,這個事件會致使處理器跳轉 到內核態並跳轉到一個新的地址。並開始處理那裏的異常處理程序,此時的異常 處理就是系統調用程序。
Linux 中的軟中斷和工做隊列是中斷處理。
1.軟中斷通常是「可延遲函數」的總稱,它不能睡眠,不能阻塞,它處於中斷上 下文,不能進城切換,軟中斷不能被本身打斷,只能被硬件中斷打斷(上半部), 能夠併發的運行在多個 CPU 上。因此軟中斷必須設計成可重入的函數,所以也需 要自旋鎖來保護其數據結構。
2.工做隊列中的函數處在進程上下文中,它能夠睡眠,也能被阻塞,可以在不一樣 的進程間切換。已完成不一樣的工做。 可延遲函數和工做隊列都不能訪問用戶的進程空間,可延時函數在執行時不可能 有任何正在運行的進程,工做隊列的函數有內核進程執行,他不能訪問用戶空間地址。
點擊直接進去分享學習資料
相關視頻推薦:
【linux服務器】初識Linux內核進程通訊能這麼玩\(上\)
【linux服務器】初識Linux內核進程通訊能這麼玩\(中\)
【linux服務器】初識Linux內核進程通訊能這麼玩\(下\)