標籤:
http://blog.csdn.net/dyllove98/article/details/8917197
Linux對於記憶體的管理涉及到非常多的方面,這篇文章首先從對進程虛擬位址空間的管理說起。(所依據的代碼是2.6.32.60)
無論是核心線程還是使用者進程,對於核心來說,無非都是 task_struct這個資料結構的一個執行個體而已,task_struct被稱為進程描述符(process descriptor),因為它記錄了這個進程所有的context。其中有一個被稱為‘記憶體描述符‘(memory descriptor)的資料結構 mm_struct,抽象並描述了Linux視角下管理進程地址空間的所有資訊。mm_struct定義在include/linux/mm_types.h中,其中的域抽象了進程的地址空間,如所示: 每個進程都有自己獨立的mm_struct,使得每個進程都有一個抽象的平坦的獨立的32或64位地址空間,各個進程都在各自的地址空間中相同的地址記憶體存放不同的資料而且互不干擾。如果進程之間共用相同的地址空間,則被稱為
線程。其中[start_code,end_code)表示程式碼片段的地址空間範圍。[start_data,end_start)表示資料區段的地址空間範圍。[start_brk,brk)分別表示heap段的起始空間和當前的heap指標。[start_stack,end_stack)表示stack段的地址空間範圍。mmap_base表示memory mapping段的起始地址。
那為什麼mmap段沒有結束的地址呢?bbs段是用來幹什麼的呢?bbs表示的所有沒有初始化的全域變數,這樣只需要將它們匿名映射為‘零頁’,而不用在程式load過程中從磁碟檔案顯示的mapping,這樣既減少了elf二進位檔案的大小,也提高了程式載入的效率。
在mm_struct中為什麼沒有bbs段的地址空間表示呢? 除此之外,mm_struct還定義了幾個重要的域:
215 atomic_t mm_users; /* How many users with user space? */ 216 atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
這兩個counter乍看好像差不多,那Linux使用中有什麼區別呢?看代碼就是最好的解釋了。
681static int copy_mm(unsigned long clone_flags, struct task_struct * tsk) 682{ 683 struct mm_struct * mm, *oldmm; 684 int retval; 692 tsk->mm = NULL; 693 tsk->active_mm = NULL; 694 695 /* 696 * Are we cloning a kernel thread? 697 * 698 * We need to steal a active VM for that.. 699 */ 700 oldmm = current->mm; 701 if (!oldmm) 702 return 0; 703 704 if (clone_flags & CLONE_VM) { 705 atomic_inc(&oldmm->mm_users); 706 mm = oldmm; 707 goto good_mm; 708 }
無論我們在調用fork,vfork,clone的時候最終會調用do_fork函數,區別在於vfork和clone會給copy_mm傳入一個CLONE_VM的flag,這個標識表示父子進程都運行在同樣一個‘虛擬位址空間’上面(在Linux稱之為lightweight process或者線程),當然也就共用同樣的物理地址空間(Page Frames)。
copy_mm函數中,如果建立線程中有CLONE_VM標識,則表示父子進程共用地址空間和同一個記憶體描述符,並且只需要將mm_users值+1,也就是說mm_users表示正在引用該地址空間的thread數目,是一個thread level的counter。
mm_count呢?mm_count的理解有點複雜。
對Linux來說,使用者進程和核心線程(kernel thread)都是task_struct的執行個體,唯一的區別是kernel thread是沒有進程地址空間的,核心線程也沒有mm描述符的,所以核心線程的tsk->mm域是空(NULL)。核心scheduler在進程context switching的時候,會根據tsk->mm判斷即將調度的進程是使用者進程還是核心線程。但是雖然thread thread不用訪問使用者進程地址空間,但是仍然需要page table來訪問kernel自己的空間。但是幸運的是,對於任何使用者進程來說,他們的核心空間都是100%相同的,所以核心可以’borrow‘上一個被調用的使用者進程的mm中的頁表來訪問核心地址,這個mm就記錄在active_mm。
簡而言之就是,對於kernel thread,tsk->mm == NULL表示自己核心線程的身份,而tsk->active_mm是借用上一個使用者進程的mm,用mm的page table來訪問核心空間。對於使用者進程,tsk->mm == tsk->active_mm。
為了支援這個特別,mm_struct裡面引入了另外一個counter,mm_count。剛才說過mm_users表示這個進程地址空間被多少線程共用或者引用,而mm_count則表示這個地址空間被核心線程引用的次數+1。
比如一個進程A有3個線程,那麼這個A的mm_struct的mm_users值為3,但是mm_count為1,所以mm_count是process level的counter。維護2個counter有何用處呢?考慮這樣的scenario,核心調度完A以後,切換到核心核心線程B,B ’borrow‘ A的mm描述符以訪問核心空間,這時mm_count變成了2,同時另外一個cpu core調度了A並且進程A exit,這個時候mm_users變為了0,mm_count變為了1,但是核心不會因為mm_users==0而銷毀這個mm_struct,核心只會當mm_count==0的時候才會釋放mm_struct,因為這個時候既沒有使用者進程使用這個地址空間,也沒有核心線程引用這個地址空間。
We‘ll try to explain the difference between the use of mm_users and mm_count with an example. Consider a memory descriptor shared by two lightweight processes. Normally, its mm_users field stores the value 2, while its mm_count field stores the value 1 (both owner processes count as one).
If the memory descriptor is temporarily lent to a kernel thread (see the next section), the kernel increases the mm_count field. In this way, even if both lightweight processes die and the mm_users field becomes zero, the memory descriptor is not released until the kernel thread finishes using it because the mm_count field remains greater than zero.
linux核心線程,進程,線程