背景知識
內存管理
OS進行內存管理不是以字節為單位的,而是以內存塊為單位的,默認大小為4kb;系統和磁盤文件進行IO交互的單位是4kb(8個扇區);OS對內存管理實質上是對頁框進行管理。
頁框(Page Frame):
頁框是物理內存中的固定大小的連續塊,用于存儲進程的頁面數據。操作系統將物理內存劃分為多個大小相等的頁框,作為內存管理的基本單位。例如,在x86架構中,常見頁框大小為4KB。頁幀(Page Frame):
頁幀與頁框通常被視為同義詞,均指物理內存中的固定塊。但在某些上下文中,"頁幀"可能更強調其作為地址映射的載體,而"頁框"側重物理劃分。實際使用中兩者常互換。
對此,操作系統使用struct page結構體來描述和管理頁框(struct page memory[1048576])。
struct page {unsigned long flags; // 頁狀態標志(如臟頁、鎖定位等)union {struct { // 頁緩存和匿名頁使用struct list_head lru;struct address_space *mapping;pgoff_t index;unsigned long private;};struct { // slab分配器使用struct kmem_cache *slab_cache;void *freelist;union {void *s_mem;unsigned long counters;};};// 其他使用場景的union分支...};atomic_t _refcount; // 引用計數unsigned long compound_head; // 復合頁頭指針unsigned char compound_dtor; // 復合頁析構函數IDunsigned char compound_order; // 復合頁階數atomic_t compound_mapcount; // 復合頁映射計數unsigned int page_type; // 頁面類型標識void *virtual; // 內核虛擬地址(若非高端內存)// 其他架構相關成員...
};
頁表
虛擬地址32位,系統這樣劃分:
因此,真實的頁表是這樣的,頁表本質是搜索頁框號(二級頁表):
頁目錄4kb,頁表一個2kb(每個2字節),一共最大4kb+2MB。根據數據類型就可以拿到完整的數據,每個進程會把自己的頁目錄起始地址放在CR3寄存器中,CPU使用mmu和該寄存器就可以計算出物理地址。
線程概念
基本概念
線程在進程內部執行,是CPU調度的基本單位。在一個程序里的一個執行路線就叫做線程(thread),更準確的定義是:線程是“一個進程內部的控制序列;每個進程至少一個執行線程。Linux中線程就是創建一批共享地址空間和頁表的task_struct,來執行代碼中的一部分任務。
進程定義的內核觀點:承擔分配系統資源的基本實體。
為了方便對線程進行管理,映入struct tcb結構體(Windows),但是在linux不單獨設計,復用PCB表示統一執行流,這樣不需要設計單獨的調度算法:
struct task_struct {volatile long state; // 線程狀態(運行、就緒等)void *stack; // 線程棧指針unsigned int flags; // 標志位int prio; // 動態優先級struct list_head tasks; // 線程鏈表節點struct mm_struct *mm; // 內存管理信息// 更多字段...
};
Linux中的執行流都叫做輕量級進程,CPU看到的執行流<=進程。
線程獨立的資源:線程ID,一組寄存器,棧,errno,信號屏蔽字,調度優先級。
線程創建
在Linux中線程叫用戶級線程,Windows中叫Windows內核級線程,因為Windows真正實現這一概念。
樣例代碼:
Linux中沒有線程只有進程,因此編譯時要引入庫-L pthread,系統調研只會給上層用戶提供創建輕量級進程的接口,pthread庫是Linux自帶的原生線程庫,對輕量級進程接口進行封裝,按照線程接口方式交給用戶。
#include<iostream>
using namespace std;
#include<unistd.h>
#include<pthread.h>
void* newthread(void * s)
{while(1){cout << "this is new thread,pid:" <<getpid()<< endl;sleep(2);}
}
int main()
{pthread_t id;pthread_create(&id, nullptr, newthread, (void *)"syx 666");while(1){sleep(1);cout << "this is main thread,pid:" <<getpid()<< endl;}return 0;
}
運行:
查看進程發現:
說明這本就是一個進程,只不過是不同執行流而已,pid一樣。
查看線程:
ps -aL
這里LWP(Light Weight Thread)就是線程號。因此,OS調度時看的是LWP,LWP才是執行流的標識,main函數式主線程,其他線程是新線程。
線程優缺點
優點
cpu會把熱點數據放在緩存;多線程因為會使用同一個熱點數據,減少了進程間的cache切換,相比進程具有更輕量的上下文切換開銷,所以調度效率高,進程切換的時候會把上個cache存儲的數據刪掉,重用率低。
線程上下文切換通常只需保存寄存器狀態和棧指針,而進程切換還需刷新TLB、更新內存管理單元等操作。實測數據顯示,線程切換開銷約為進程切換的1/10到1/30,這種差異在高并發場景下尤為明顯。
1.創建一個新線程的代價要比創建一個新進程小得多
2.與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多
3.線程占用的資源要比進程少很多 能充分利用多處理器的可并行數量
4.在等待慢速I/O操作結束的同時,程序可執行其他的計算任務
5.計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現
6.I/O密集型應用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
線程不是越多越好,合適最好。計算密集型應用時線程數和核數相等最好。IO可以多創建。
缺點
1.性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與共它線程共享同一個處理器。如果計算密集型 線程的數量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的 同步和調度開銷,而可用的資源不變。
2.健壯性降低:編寫多線程需要更全面更深入的考慮,在一個多線程程序里,因時間分配上的細微偏差或者因共享了 不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。一個線程出問題,其他線程都會出問題。
3.缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調用某些OS函數會對整個進程造成影響。 編程難度提高 編寫與調試一個多線程程序比單線程程序困難得多
4.編寫難度高。