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