|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
3 `* y$ V/ a" e7 p- M }进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理。
; K. h' Z) `5 o% P) j9 ?/ L0 _6 ^# t }; k
主要内容:
) Q# `2 ^% \' o. ]* l$ n: |' N! G# u$ C9 N
- 地址空间(mm_struct)
- 虚拟内存区域(VMA)
- 地址空间和页表& o( O0 ?. M5 o+ q
% h3 ] g) ]6 ~8 L: O: ^. s( K5 j
5 F5 p9 ]3 P& A0 z+ Y( e3 S1. 地址空间(mm_struct)
4 }5 S8 X* p. O$ _3 H1 |# S地址空间就是每个进程所能访问的内存地址范围。
0 M: I% e+ v# m0 y2 q: y& v! a5 d, |, c5 x+ [4 p, ] `
这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小。5 B( _" y6 k2 [: G$ q3 s( B. ~
4 r, T. F; Y" }; ^- v2 L, L
) S4 D% Z+ o+ A+ M. t" E0 I$ y* i3 Z& \! I4 j" o
现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间。
$ r) L- p$ c+ K: _) \# W& ?) n, c
地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。: t N, c2 R1 b
% o9 M/ o2 \: D
7 V2 O v% |5 S5 q2 Z
. ]$ z' F& W% ?1 D- Z虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),
9 e+ g% O9 b3 X) V+ E2 k9 b) h' s' w0 y7 a" S* v' W- d
进程能够访问的那些地址区间也称为 内存区域。& [1 r' F' W" f. o
) J0 R, i' F' ?! B7 Q/ U) G% d进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。
/ [+ L+ W% A8 ?' j3 Y, ]5 v; h
3 U; a1 K( d) j' j& H+ d2 \1 K" C " ?0 ~5 o' H9 }
- @( O' Z9 e1 Y9 A" C T
内存区域中主要包含以下信息:
, Z- r1 s# B' Y) G. Q2 Y2 Y% {7 q' g& N+ F7 Y; ~) @% a
- - 代码段(text section),即可执行文件代码的内存映射
- - 数据段(data section),即可执行文件的已初始化全局变量的内存映射
- - bss段的零页(页面信息全是0值),即未初始化全局变量的内存映射
- - 进程用户空间栈的零页内存映射
- - 进程使用的C库或者动态链接库等共享库的代码段,数据段和bss段的内存映射
- - 任何内存映射文件
- - 任何共享内存段
- - 任何匿名内存映射,比如由 malloc() 分配的内存
) z9 s' T8 S; Z3 z* Y- H 注:bss是 block started by symbol 的缩写。* c/ J. `% N8 x u. Y
9 c2 \. p% `. Z
) U) v! O1 r1 y1 K
4 }4 u! `# B: g) x" q& h6 Tlinux中内存相关的概念稍微整理了一下,供参考:" v: B- @! o3 @5 b$ c/ B! Z! a
$ T5 m1 p7 o5 Y英文 | 含义 | | SIZE | 进程映射的内存大小,这不是进程实际使用的内存大小 | | RSS(Resident set size) | 实际驻留在“内存”中的内存大小,不包含已经交换出去的内存 | | SHARE | RSS中与其他进程共享的内存大小 | | VMSIZE | 进程占用的总地址空间,包含没有映射到内存中的页 | | Private RSS | 仅由进程单独占用的RSS,也就是进程实际占用的内存 | 2 ~2 x! U$ v, x& e7 {
1.1 mm_struct介绍# o9 |: r! Q4 V. Q! I, T
linux中的地址空间是用 mm_struct 来表示的。
, k3 g4 q* S0 X7 A$ w
( b1 V! `. [: [ Q/ C3 S6 u下面对其中一些关键的属性进行了注释,有些属性我也不是很了解......! Q+ t) ?# i6 \) ~. R) a |4 w
! D8 E/ n$ l5 c5 C$ t1 y1 _复制代码
# M# }* _ |4 E+ _2 a( Nstruct mm_struct {
% l# L$ t( Y3 _- L struct vm_area_struct * mmap; /* [内存区域]链表 */
( {' i) @5 x) J0 T struct rb_root mm_rb; /* [内存区域]红黑树 */3 R8 P1 G9 j% X0 h
struct vm_area_struct * mmap_cache; /* 最近一次访问的[内存区域] */( }4 T2 `0 S p% t
unsigned long (*get_unmapped_area) (struct file *filp,+ l# \% o: c9 `( k' _1 r
unsigned long addr, unsigned long len,4 q) _( d' n+ f) S9 m! |
unsigned long pgoff, unsigned long flags); /* 获取指定区间内一个还未映射的地址,出错时返回错误码 */4 g* u1 @/ i9 @6 Z( x
void (*unmap_area) (struct mm_struct *mm, unsigned long addr); /* 取消地址 addr 的映射 */7 Z2 `& E! K: `9 u3 e5 R
unsigned long mmap_base; /* 地址空间中可以用来映射的首地址 */& c( \3 ^% f( {, D
unsigned long task_size; /* 进程的虚拟地址空间大小 */
. r: Q7 ?+ s& r4 M6 H% F9 ` unsigned long cached_hole_size; /* 如果不空的话,就是 free_area_cache 后最大的空洞 */, r2 U. p5 ~6 n: u# O D
unsigned long free_area_cache; /* 地址空间的第一个空洞 */
, N4 E7 y- h& m a2 P pgd_t * pgd; /* 页全局目录 */
F+ X2 a0 Q5 l/ m atomic_t mm_users; /* 使用地址空间的用户数 */
" H# J9 K9 Q+ h5 Y4 l" ?0 n( D atomic_t mm_count; /* 实际使用地址空间的计数, (users count as 1) */
, {) N$ g J& M" ~# C int map_count; /* [内存区域]个数 */
9 [7 `9 B, C& V/ l$ e struct rw_semaphore mmap_sem; /* 内存区域信号量 */
0 l- J' D. u% T spinlock_t page_table_lock; /* 页表锁 */
. V# l9 R# Y6 K% q: _1 }
9 Q) `" R, e) f. G; d- p9 ^( {$ ? struct list_head mmlist; /* 所有地址空间形成的链表 */) Q/ K, U* m7 _% d
* r) j9 ^) d5 o /* Special counters, in some configurations protected by the
8 x- X# c1 E/ z# D; E# q3 i7 m5 g * page_table_lock, in other configurations by being atomic.$ x2 P8 f( o v# Y9 j( [1 s
*/
7 ^! z5 `5 T4 v8 J mm_counter_t _file_rss;0 `4 l# q9 r. u% l+ j$ ?' q
mm_counter_t _anon_rss;
, |3 B* T, ?5 r. x" l& O! Z9 K: j( A. `5 H
unsigned long hiwater_rss; /* High-watermark of RSS usage */, s2 K- a, `/ R- D4 N
unsigned long hiwater_vm; /* High-water virtual memory usage */9 Z3 U6 ^. W3 o. y% ^- n# o2 D
" W& y6 G# {( E& b unsigned long total_vm, locked_vm, shared_vm, exec_vm;
6 h0 O; l& M& H4 n' t unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
6 _5 }: T. o! Q6 N unsigned long start_code, end_code, start_data, end_data; /* 代码段,数据段的开始和结束地址 */
W0 |4 q+ Z; s$ F: X: `' C% }0 } unsigned long start_brk, brk, start_stack; /* 堆的首地址,尾地址,进程栈首地址 */! i9 X- Q4 v+ |: w2 F
unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数,环境变量首地址,尾地址 */" Y/ ^9 b l1 T: |" X
( [/ n+ }& f& b$ J* P
unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
# K6 U4 \# F' \' c B; o
9 C4 g) ?" `) g3 J' `! k struct linux_binfmt *binfmt;! s7 F: b0 q: C2 c
; G1 _; e' l) K2 p; V9 S cpumask_t cpu_vm_mask;
* A- l3 m8 a1 g! O0 O/ S5 Z9 D/ H l& b; O2 Q4 R7 f
/* Architecture-specific MM context */
8 l; K8 S$ `3 d( P: ~ mm_context_t context;) R- @6 o. n _/ \0 N
0 }9 ~$ g! G+ O* @6 k1 | /* Swap token stuff *// Y7 f1 s* X2 B% O4 B3 C5 e
/*$ ^ W: N0 d% M3 r% ^! b8 n/ S
* Last value of global fault stamp as seen by this process.
/ C) X& r& h- Y/ z) K3 w( ^ * In other words, this value gives an indication of how long
' k: o7 K1 u& j0 [! w! O * it has been since this task got the token.
; _8 H5 k4 ]1 w" \, y * Look at mm/thrash.c/ C& f/ |, L2 T+ H( y% s: r
*/7 c3 S$ T! R( @: z; v! e# B& b
unsigned int faultstamp;4 i: \2 Y2 s. w! V3 H
unsigned int token_priority;
) O: V% E% f$ {3 w- O. [* c unsigned int last_interval;* I4 Y% p, p1 D5 H3 a1 ]
6 L/ \* B- N$ d2 U
unsigned long flags; /* Must use atomic bitops to access the bits */
; s6 \: @# J- {+ w4 ?' c0 S5 R% K) H2 \2 `
struct core_state *core_state; /* coredumping support */
0 z3 T! e. e0 G& i#ifdef CONFIG_AIO+ j$ S! [' w2 k1 ~4 Y, F5 F5 F
spinlock_t ioctx_lock;. K6 Q/ G9 e' e
struct hlist_head ioctx_list;
9 M z7 z. J" s8 }9 k* D1 j#endif8 D1 X0 o& W: f% |+ G
#ifdef CONFIG_MM_OWNER
* } P4 c+ f6 T z5 i6 h/ ? /*
3 v f+ c% ?/ u" i8 Y * "owner" points to a task that is regarded as the canonical5 o* O! v- O. b1 O5 [# j! I& V+ X
* user/owner of this mm. All of the following must be true in9 s8 E/ [" P# \& { G( C9 ?, P2 ~" R
* order for it to be changed:
1 Y& R4 U6 }2 K9 y8 J2 q* r+ | *
+ a/ K* B# u# Q& t * current == mm->owner9 n) h z* J+ ]5 G
* current->mm != mm
2 _1 p! e0 u4 b9 a% _% U * new_owner->mm == mm. j+ C- {/ S1 m) W& [2 {) H
* new_owner->alloc_lock is held- q, |# T( ?3 ~, `3 q* g1 q5 J4 s
*// D" X% i5 U, [( Y; V4 x
struct task_struct *owner;
' j% M0 t# q. n, ^5 l9 m* }8 Y#endif
v$ y6 c5 [. v/ q, d
& b& S1 K5 j N2 P( d#ifdef CONFIG_PROC_FS
6 G g8 O9 X6 S/ \ /* store ref to file /proc/<pid>/exe symlink points to */7 C, G2 C9 c7 O# L* M
struct file *exe_file;
9 x4 R; H7 l* r* z unsigned long num_exe_file_vmas;
( j; [; u6 w7 C#endif9 p, S6 [ w! f( i1 U1 P* o6 Q% t
#ifdef CONFIG_MMU_NOTIFIER
9 T& ^ O6 b$ D% p* e struct mmu_notifier_mm *mmu_notifier_mm;# U9 B t. [. I, }/ C8 H1 P
#endif) i- {+ q Q: [" a z& P
};复制代码9 y3 l7 @ G' L: V- p, \7 d
0 Y: _# g( N/ S% q* x4 B1 G; r9 n* r7 q0 W. P
补充说明1: 上面的属性中,mm_users 和 mm_count 很容易混淆,这里特别说明一下:(下面的内容有网上查找的,也有我自己理解的)+ {, s: n8 f% f( a e# r
# e e- _; _9 |: S* ~
mm_users 比较好理解,就是 mm_struct 被用户空间进程(线程)引用的次数。
0 j, z9 f5 b% J$ a% x. Q/ z8 F
如果进程A中创建了3个新线程,那么 进程A(这时候叫线程A也可以)对应的 mm_struct 中的 mm_users = 4* c) V( A% z" f6 K& f
7 k4 M9 ?+ Z3 X2 n$ d 2 u. t$ {' p! o. k# F, u6 B
, R, F {% |, Q
补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。! ]5 N( d6 ~2 y3 ^7 r9 l& J/ C1 }
/ E; X& e( T q; n o4 V
所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果父子进程没有共享进程地址空间,就是2个进程/ a- O, W+ x9 x) L# A
% b% s9 H% D9 p6 J; u
8 h% n# L( r9 p; N9 U
' N/ u& T% \0 rmm_count 则稍微有点绕人,其实它记录就是 mm_struct 实际的引用计数。5 g+ F y% @8 _) ^# x4 J1 S
* r0 P/ L2 N/ ~' \1 j% g简单点说,当 mm_users=0 时,并不一定能释放此 mm_struct,只有当 mm_count=0 时,才可以确定释放此 mm_struct
& P. `6 @3 e% j' ?3 r9 g
6 ?1 N \; P: I
# |- x7 M0 S; N5 v" ^1 t! [: u, v% V/ ^ @# M# Z* i1 W5 s
从上面的解释可以看出,可能引用 mm_struct 的并不只是用户空间的进程(线程). q l4 u: a# }% @( B8 R% g2 u/ l/ i
" e! X* `% t* @5 ~" N当 mm_users>0 时, mm_count 会增加1, 表示有用户空间进程(线程)在使用 mm_struct。不管使用 mm_struct 的用户进程(线程)有几个, mm_count 都只是增加1。
6 C$ J3 R* t2 }
4 R- h( \0 T$ l7 Z2 |也就是说,如果只有1个进程使用 mm_struct,那么 mm_users=1,mm_count也是 1。
1 Q e' |, Z) Y/ \7 M# e7 M! g9 f* h( B1 X# Y a' ^3 T; o
如果有9个线程在使用 mm_struct,那么 mm_users=9,而 mm_count 仍然为 1。
, B! k+ K/ ^! g" P. l# I. ?( M! N/ X/ K9 k
6 H% R# y0 F$ {- R' c% S! O1 Q
- Y" j) i/ l: k) E7 \2 G) f6 _那么 mm_count 什么情况下会大于 1呢?
: l Y' ~6 V# u9 _8 E
5 B9 _) v% L% B/ v6 y% Z当有内核线程使用 mm_struct 时,mm_count 才会再增加 1。9 ], R# ]: _+ ^
2 e# Z% ?- @5 ?( t
内核线程为何会使用用户空间的 mm_struct 是有其他原因的,这个后面再阐述。这里先知道内核线程使用 mm_struct 时也会导致 mm_count 增加 1。
& K4 D9 H$ F5 t9 F7 C, G h
! u- z$ h( B; {) f0 j在下面这种情况下,mm_count 就很有必要了:
1 Q1 {/ g; E% a, n# |; ~( w( A" k# o6 D$ }, P4 M
- - 进程A启动,并申请了一个 mm_struct,此时 mm_users=1, mm_count=1
- - 进程A中新建了2个线程,此时 mm_users=3, mm_count=1
- - 内核调度发生,进程A及相关线程都被挂起,一个内核线程B 使用了进程A 申请的 mm_struct,此时 mm_users=3, mm_count=2
- - CPU的另一个core调度了进程A及其线程,并且执行完了进程A及其线程的所有操作,也就是进程A退出了。此时 mm_users=0, mm_count=1
- 在这里就看出 mm_count 的用处了,如果只有 mm_users 的话,这里 mm_users=0 就会释放 mm_struct,从而有可能导致 内核线程B 异常。
- - 内核线程B 执行完成后退出,这时 mm_users=0,mm_count=0,可以安全释放 mm_struct 了
' w7 g# }9 C; V9 ~# P5 `6 j# g& \ / a F0 k# a. b# b2 c0 i
0 e! s" _/ { |, d+ c
补充说明2:为何内核线程会使用用户空间的 mm_struct?" m5 F' u% x" [4 h
. H1 S z$ e# f: R) U* p对Linux来说,用户进程和内核线程都是task_struct的实例,! }3 k( t# S1 F% M) G& Y. O
2 W4 w/ F! m! K唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的tsk->mm域是空(NULL)。
2 n; K9 @7 u8 K. ?4 T U! f# W) w6 e; `
1 L' a: O v( o内核调度程序在进程上下文的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。8 Z' ?* ^' I( a* S& h
! D& ~5 g( X' C3 W# r但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。
# w$ d N5 {6 J4 f3 _! _, ]# ?# |# J7 d9 G8 N) p* w, A; {
而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。+ a9 I* |# I/ J& T4 q; j [( {( r) F9 T. m
) ~ \- v0 K% r. ]( N % d7 r) L0 V8 {2 t1 A, s
, l7 ~' Z3 |6 Y9 I2 e. s1 y% Q简而言之就是,对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。
; d# D7 g1 Z6 N" l8 Q$ |: K+ p/ v# T/ u4 |5 |
对于用户进程,tsk->mm == tsk->active_mm。1 V3 A6 M* X; y- ]
" b* s. s7 x5 x7 y5 i: e8 R 3 `0 w5 ?9 ~1 M. S( v
& D* n; K. H2 V- d+ B( g
补充说明3:除了 mm_users 和 mm_count 之外,还有 mmap 和 mm_rb 需要说明以下:7 c; A+ V9 |# ^# X. q
3 Y+ X" M0 E& |) T其实 mmap 和 mm_rb 都是保存此 进程地址空间中所有的内存区域(VMA)的,前者是以链表形式存放,后者以红黑树形式存放。
9 S$ Y+ B1 r9 H0 G9 j. e, t3 K/ r& v
用2种数据结构组织同一种数据是为了便于对VMA进行高效的操作。
, c" _1 L8 {% s F& h0 T8 w! n) ?/ h1 Y
5 Y2 g5 ~2 ], Z& Z; g; S2 {
! s- g/ y0 z0 o1.2 mm_struct操作- i$ u2 {! B- A3 Q8 {' t+ h) Y0 K7 |
1. 分配进程地址空间0 J d1 m) [1 K2 ^+ p3 T; b8 }! R" \
! O0 a# s6 }+ D4 G8 R( `# R' a
参考 kernel/fork.c 中的宏 allocate_mm0 W# g5 N6 Z% L- M& k7 m" e
: `2 |& ^4 W4 } R5 U* x
#define allocate_mm() (kmem_cache_alloc(mm_cachep, GFP_KERNEL)); Y1 l A. S! b% Z: H6 F4 v9 Q
#define free_mm(mm) (kmem_cache_free(mm_cachep, (mm)))
2 S$ _$ V2 c) b
: O9 k4 J! N/ F4 h* s' t
' H9 K1 o3 k; V# k, G; @) u其实分配进程地址空间时,都是从slab高速缓存中分配的,可以通过 /proc/slabinfo 查看 mm_struct 的高速缓存
/ l) [; ]0 ?3 O% C' q! N3 ?5 o
1 n+ I; ]0 B, x$ R9 i. M# cat /proc/slabinfo | grep mm_struct
3 Y" @* e9 Z0 H3 ~! Umm_struct 35 45 1408 5 2 : tunables 24 12 8 : slabdata 9 9 08 G' O; m d2 f* W* R) U" ]
R. q" a4 F& h- L+ z3 [. Y6 z5 f4 x
2. 撤销进程地址空间
7 Y! }0 }; k/ a1 A6 d. r& a* U1 B4 X! X: l: x$ G
参考 kernel/exit.c 中的 exit_mm() 函数
3 K! o. O6 G E( v7 x$ u. P. S9 {
% j7 P7 j! u$ S+ P" H5 t. i- \该函数会调用 mmput() 函数减少 mm_users 的值,
' ?, m, @$ j+ J5 Y1 f
' c7 u, C" a1 }2 w$ X当 mm_users=0 时,调用 mmdropo() 函数, 减少 mm_count 的值,8 s& M3 O# V n& z
4 m1 ?* z5 J( F" @8 }% `如果 mm_count=0,那么调用 free_mm 宏,将 mm_struct 还给 slab高速缓存8 z. O0 |$ n/ v7 V' x; i8 ~/ f- F
8 z" p6 L" V! D9 ]9 ?+ L* b/ i: C 1 E& E. [/ r. U3 T) D1 B/ z
& O, x5 A8 g8 c9 I( d3. 查看进程占用的内存:
1 H% _, E7 S' Q' C# ~2 l
1 |7 y" W# C7 v/ X X2 [- t* s% ?( p! _cat /proc/< PID>/maps4 l& V/ f, o; D% g
或者
- L- x' o! h/ h1 U7 S' Ipmap PID
5 J x# T, W. X0 T$ C5 w
; e- j7 B+ q, i1 z5 ^+ o
$ o3 Y% s/ z7 s f! u7 q' W2. 虚拟内存区域(VMA)
& ?8 O% e* Z; I" C" O4 V内存区域在linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。
* |' w, d7 }# ~& a
5 a$ V; e& B$ d, h6 q
! X7 v- V9 x; Y1 ^ g! R- R. Y& B) H9 l5 Q" r. {+ a
2.1 VMA介绍6 S8 A! b. [) B
VMA的定义也在 <linux/mm_types.h> 中/ t k8 E3 n4 I) _- k1 B: J
4 r' M; `' O" v* P& r
复制代码) B. M# _* Q2 G! p4 Y9 T
struct vm_area_struct {
% T8 y4 z$ E; B) ? struct mm_struct * vm_mm; /* 相关的 mm_struct 结构体 */
& U. ?$ a+ q f/ R unsigned long vm_start; /* 内存区域首地址 */. z0 _: d+ G. c9 r( `& ?. I! u
unsigned long vm_end; /* 内存区域尾地址 */. h: F5 [/ P g5 T5 E7 q, d
$ ?$ u% A2 Y, b) `5 n% i' G4 Z /* linked list of VM areas per task, sorted by address */
3 a' w0 s' m" J5 i& B. ` struct vm_area_struct *vm_next, *vm_prev; /* VMA链表 */ Q5 C5 L8 ] |; Q
+ _1 A; i0 G6 H& |9 R/ ` pgprot_t vm_page_prot; /* 访问控制权限 */* K$ b! B! q, S
unsigned long vm_flags; /* 标志 */
' R( C8 ^. h" q/ x& G- E2 W
# l: }+ p; `- w4 t struct rb_node vm_rb; /* 树上的VMA节点 */
) U7 G: U& V7 `
0 ~0 F0 m1 A1 F3 N4 O1 d& n3 B6 I /*
( d, ~8 x% t: f8 f N * For areas with an address space and backing store,
4 |" d, F1 x' F2 h. G! u * linkage into the address_space->i_mmap prio tree, or0 X7 K+ m; k) R
* linkage to the list of like vmas hanging off its node, or
) \1 o6 ^; ^0 Z* ~2 I * linkage of vma in the address_space->i_mmap_nonlinear list.- c7 Z/ c V/ ?" }" f+ r( h, @
*/" s/ x' [# t E' c9 {
union {
; ` M6 K/ I- z; z8 }3 Y" D struct {8 B* c W; \4 ]2 \. ^8 z
struct list_head list;% c8 @% O, J5 N1 ~
void *parent; /* aligns with prio_tree_node parent */& ^ c6 ]0 s( w
struct vm_area_struct *head;4 y/ l" ~! ]! B; a6 s* h: O6 @! U$ l/ |
} vm_set;% ~/ m9 f; d# ]% z9 Q
- {/ {& q w/ D: ^& K
struct raw_prio_tree_node prio_tree_node;
u ?7 b3 q0 u: a3 ^5 g- }" i } shared;
+ @* ] U* S0 P9 Q% P
9 Y- ]8 d$ b5 z! D# R /*
2 }4 ~. j4 l/ w+ Y" y * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma3 J3 r7 J5 n) G3 l
* list, after a COW of one of the file pages. A MAP_SHARED vma
% F' n3 J) P7 }7 [: f" [3 b * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack6 z3 X/ n4 n7 K2 Y& n
* or brk vma (with NULL file) can only be in an anon_vma list.$ [" |) e% s4 P/ x
*/
+ t2 K$ S6 O8 `6 | struct list_head anon_vma_node; /* Serialized by anon_vma->lock */
; `* w. I& Z8 B0 |5 p% r4 { struct anon_vma *anon_vma; /* Serialized by page_table_lock */
: A4 A% x2 Z# }# q% n5 F
J# k8 K& O) d# @8 t /* Function pointers to deal with this struct. */9 z ]1 N* `4 [
const struct vm_operations_struct *vm_ops;
0 F8 ?) ? Y6 t5 n4 }7 ~1 ~ ]& Q% F" z) C, [6 H$ J
/* Information about our backing store: */
" X+ A, ~$ E9 e) E5 S1 j7 O unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
1 ]/ Z+ y4 `4 n) E( U, t units, *not* PAGE_CACHE_SIZE */
* i n, o- g3 V# H struct file * vm_file; /* File we map to (can be NULL). */
' L2 n) S; w$ i void * vm_private_data; /* was vm_pte (shared mem) */
: W) v2 t, Z6 y6 q" L1 p; r4 V unsigned long vm_truncate_count;/* truncate_count or restart_addr */
A) U6 |$ a5 g/ T. e
4 {+ J5 h. o7 y! j4 a#ifndef CONFIG_MMU
' l( W: f$ b, o/ m8 o4 n( W struct vm_region *vm_region; /* NOMMU mapping region */
# G) @* a7 E8 `) `' Q" w+ P/ ]#endif
# {3 X9 m' }& m. Q, X4 O! U ~#ifdef CONFIG_NUMA. D+ K5 `! }- p1 P6 t
struct mempolicy *vm_policy; /* NUMA policy for the VMA */% M( j l1 C+ p `1 Y1 l
#endif
! f4 x8 }4 \9 Z" i};
* H$ P# b8 o- `复制代码 e* H+ C$ @. U: n
2 w1 J" `; ^ V2 u, e
s. |9 {$ l4 @0 I# r0 K这个结构体各个字段的英文注释都比较详细,就不一一翻译了。' B( C! W" Z& A6 j
* S3 A6 T# A& I3 w
上述属性中的 vm_flags 标识了此VM 对 VMA和页面的影响:5 ^& F4 i/ P8 K
4 i$ r, ~5 _( W( Uvm_flags 的宏定义参见 <linux/mm.h>
9 z2 a/ L9 k8 a2 [+ T9 O n" F) o4 Z9 D
标志 | 对VMA及其页面的影响 | | VM_READ | 页面可读取 | | VM_WRITE | 页面可写 | | VM_EXEC | 页面可执行 | | VM_SHARED | 页面可共享 | | VM_MAYREAD | VM_READ 标志可被设置 | | VM_MAYWRITER | VM_WRITE 标志可被设置 | | VM_MAYEXEC | VM_EXEC 标志可被设置 | | VM_MAYSHARE | VM_SHARE 标志可被设置 | | VM_GROWSDOWN | 区域可向下增长 | | VM_GROWSUP | 区域可向上增长 | | VM_SHM | 区域可用作共享内存 | | VM_DENYWRITE | 区域映射一个不可写文件 | | VM_EXECUTABLE | 区域映射一个可执行文件 | | VM_LOCKED | 区域中的页面被锁定 | | VM_IO | 区域映射设备I/O空间 | | VM_SEQ_READ | 页面可能会被连续访问 | | VM_RAND_READ | 页面可能会被随机访问 | | VM_DONTCOPY | 区域不能在 fork() 时被拷贝 | | VM_DONTEXPAND | 区域不能通过 mremap() 增加 | | VM_RESERVED | 区域不能被换出 | | VM_ACCOUNT | 该区域时一个记账 VM 对象 | | VM_HUGETLB | 区域使用了 hugetlb 页面 | | VM_NONLINEAR | 该区域是非线性映射的 | - o9 q* B- a3 t- T, y0 ?6 a
# z! B" S+ Y, h- |4 j( y2 A, y
8 b* k" J8 D9 X6 d/ Z; y
2.2 VMA操作
5 _: a7 c2 z; @3 wvm_area_struct 结构体定义中有个 vm_ops 属性,其中定义了内核操作 VMA 的方法
0 k/ I3 [! D) p4 c5 c9 k1 c. n1 q3 {3 p- \0 R6 t
复制代码
0 U% e# ~% D7 ^) U& I; b% \" B, o/*1 H7 K4 b; j* }3 x! Y9 u5 D+ B9 ^
* These are the virtual MM functions - opening of an area, closing and
! f& K/ @/ S, f6 F' l * unmapping it (needed to keep files on disk up-to-date etc), pointer* ~+ P m7 E% L. e3 [: P
* to the functions called when a no-page or a wp-page exception occurs. 0 r0 S9 r0 J" K7 @2 P
*/- W% s) @. F. P
struct vm_operations_struct {
- Y& ~3 ]0 O! H# T void (*open)(struct vm_area_struct * area); /* 指定内存区域加入到一个地址空间时,该函数被调用 */
3 J1 I/ T2 V8 ^: R2 h3 b& p/ Y% f void (*close)(struct vm_area_struct * area); /* 指定内存区域从一个地址空间删除时,该函数被调用 */% b7 |+ h) l# }6 l% U: z
int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); /* 当没有出现在物理页面中的内存被访问时,该函数被调用 */9 Z* _; z" {1 h2 ]
4 q9 T' W9 Y! a- S /* 当一个之前只读的页面变为可写时,该函数被调用,( p9 H7 S3 X+ [! A
* 如果此函数出错,将导致一个 SIGBUS 信号 */
# u% f, [0 x) [7 g. c' I' ] int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);4 h. h) U# Y/ {7 y4 p, d
# H2 K* `# B( U, E4 W+ g" l /* 当 get_user_pages() 调用失败时, 该函数被 access_process_vm() 函数调用 */
; r3 B4 i5 T% W3 c5 i int (*access)(struct vm_area_struct *vma, unsigned long addr,
8 Z8 U1 w3 y o% N8 ` void *buf, int len, int write);
( g+ g& P. w/ t0 n; Y% ^#ifdef CONFIG_NUMA0 M5 O9 L, e* x7 @5 e, f3 s
/*
) Z; u# A* K/ g/ n' y# v * set_policy() op must add a reference to any non-NULL @new mempolicy
: A5 ^: W! C" A( f& S * to hold the policy upon return. Caller should pass NULL @new to( b- }; }# S- R
* remove a policy and fall back to surrounding context--i.e. do not- E6 r) ]7 {( L$ U
* install a MPOL_DEFAULT policy, nor the task or system default# ]1 y6 {0 w' x& {$ p+ m
* mempolicy.
- q7 z K) t" m$ M5 u */. _2 G( B8 a2 U4 `+ o% p6 P
int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);6 y$ }3 I: e/ b* a- Q5 U6 E$ z
+ ]& F6 j/ e, j6 l: \ X$ V8 y
/*/ O* A5 ]( r% c7 h( S) M
* get_policy() op must add reference [mpol_get()] to any policy at
. [* ?6 k$ m1 w * (vma,addr) marked as MPOL_SHARED. The shared policy infrastructure& ~1 w1 b. i0 K7 q3 _+ ~
* in mm/mempolicy.c will do this automatically.
/ g& u* U+ P( L2 s+ g y0 |0 G * get_policy() must NOT add a ref if the policy at (vma,addr) is not
* }( F& f2 F2 U# w% m * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
) j) o9 ^% p( g) ?8 K5 } * If no [shared/vma] mempolicy exists at the addr, get_policy() op
5 S3 W- m W, H * must return NULL--i.e., do not "fallback" to task or system default
1 w' v7 j) Z; M0 h! a& B( V * policy.
) s I, G; q) M" C) [- q* K */' E j7 \9 E" Z& n0 Q5 Q5 c
struct mempolicy *(*get_policy)(struct vm_area_struct *vma,* m" z0 X ~1 m: I# q
unsigned long addr);
. y& E$ s) E* g% M* k8 j" _3 z int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
3 R- ~" d, M* ?- m H0 O! U$ {: v const nodemask_t *to, unsigned long flags);
2 t0 r1 v Z! ]) d% b- E c7 S#endif- N2 U- R( t y- x4 R
};7 I6 k; M3 j8 C! @3 `% \! {
复制代码
% A+ N( p4 c0 L
% E% j/ n1 ?8 g; ^
( p6 S1 [- B% j除了以上的操作之外,还有一些辅助函数来方便内核操作内存区域。' {0 u- A, I: I8 Y3 B. k) z
6 i- n1 Q+ R) r
这些辅助函数都可以在 <linux/mm.h> 中找到
: G+ X p5 s w( t6 @
4 U4 E8 N, l: y- m$ Y1. 查找地址空间: b( N. t+ I( c& F. @* X" T
" T& C0 C. }7 n( ?* e" H
复制代码/ @4 A i) u$ h: _9 ]7 \
/* Look up the first VMA which satisfies addr < vm_end, NULL if none. */
9 C. F) U3 c: G9 W' y8 q& bextern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);
( t% ^, W& B3 k. ?- C. z2 _extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
' e: C) q) U# i/ j2 v' s+ D3 a7 X7 @ struct vm_area_struct **pprev);
( q/ |2 R- m: }
+ u* D( L% E, e' L4 W, p }/* Look up the first VMA which intersects the interval start_addr..end_addr-1,
' G- O0 e. K9 x. E. j NULL if none. Assume start_addr < end_addr. */# \2 |/ H- b: B( S+ G9 p
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
; o/ \( _6 H' _0 X( H( k' M7 c{
. t j: l9 h5 `0 h* F* F0 {# ] struct vm_area_struct * vma = find_vma(mm,start_addr);( l/ A) f+ i1 ^! X" I! Y
3 T3 Q' Z5 i( j3 p/ B6 y8 |9 R5 w if (vma && end_addr <= vma->vm_start)) {% O7 i& r! a9 p. t
vma = NULL;
* Y' `& u. X6 j. ^) S8 r return vma;+ V- `9 z; \4 c9 w% }& I
}) {- V* \# O# F
复制代码
& F/ \6 H; l! ~5 C9 C9 B S
4 y" X0 T4 L# b; b2 R0 T; f
; v9 m1 B i3 f7 T: y' h5 Q2. 创建地址区间
2 U% F, T. W" Q' U( W* T4 ?+ R8 w" d
复制代码2 k _! x: N, N, c2 z5 l
static inline unsigned long do_mmap(struct file *file, unsigned long addr," G X1 x8 W& c( {, i
unsigned long len, unsigned long prot,
- N4 v4 n" m, \$ c4 t unsigned long flag, unsigned long offset)- K1 w6 A6 r+ E9 {: t& i- G
{
2 P s8 C4 l' z" l( _. F; q1 Y unsigned long ret = -EINVAL;
! C8 n5 f& n: Q: P& s6 ^! U e if ((offset + PAGE_ALIGN(len)) < offset); x" v) ~; @9 V& j
goto out; X; `2 R* z g o0 M
if (!(offset & ~PAGE_MASK))4 t# e0 e! P M
ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);
) D# b& j: C t: a4 }out:
/ O/ O4 m/ v( O. _7 W' Q/ {4 d return ret;
' S" h& k _+ {) I3 {& ^% G}1 h5 L: ?2 l* K8 U, U$ P% ^! A# K
复制代码) c8 O( y) S y1 c
/ Y! R& K" |8 E% p& q
$ M5 A8 ^, S1 J4 ~' u$ ` k# v) h; e3. 删除地址区间
0 P& A& s( b) Q+ b
; j: |: u2 v A3 A8 W! v: Q2 iextern int do_munmap(struct mm_struct *, unsigned long, size_t);
5 S" G0 j- \* F& B; H3 E $ E% |: G/ x3 {3 {" b; T# q; l# H+ H
' v! l8 g w( O7 M8 V. m
3. 地址空间和页表7 _; O: r3 t8 E) y
地址空间中的地址都是虚拟内存中的地址,而CPU需要操作的是物理内存,所以需要一个将虚拟地址映射到物理地址的机制。
5 u: O0 }# ]) [# @1 U: Y5 t, h4 U" V* F8 F
这个机制就是页表,linux中使用3级页面来完成虚拟地址到物理地址的转换。- |" {$ r* a# h5 z2 |
7 s# P9 t1 `# i7 p: Q: `( n3 ^1. PGD - 全局页目录,包含一个 pgd_t 类型数组,多数体系结构中 pgd_t 类型就是一个无符号长整型
7 {5 S& z+ z, G/ g# N: d
5 m& S3 l; j7 y& X8 n& D% m2. PMD - 中间页目录,它是个 pmd_t 类型数组2 X; N" B* R: c2 X2 q0 x% v
/ m1 r% u3 A0 Z: g3. PTE - 简称页表,包含一个 pte_t 类型的页表项,该页表项指向物理页面
% J) ?) J7 e4 N+ s4 A r1 E% z- v) ^: F
1 z1 D5 V/ u4 ~' O& ^: i! K# {2 \7 y# c1 w
虚拟地址 - 页表 - 物理地址的关系如下图:4 Q+ x0 F/ V# D) z4 B X
% N, U% m5 i* _* X% a6 |4 c, A
. Y4 P7 P" }* ` X5 x+ W* U0 t5 O
|
|