EDA365电子论坛网

标题: Linux内核设计与实现之进程地址空间(kernel 2.6.32.60) [打印本页]

作者: pulbieup    时间: 2021-2-4 17:28
标题: Linux内核设计与实现之进程地址空间(kernel 2.6.32.60)
1 p1 j6 G' ?9 [3 a
进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理。* G+ D4 j0 C9 I

: l/ c" o5 x1 u主要内容:* G2 ?  f5 c9 k  @$ b, n
- G6 r$ u3 u: s9 B) {3 ^! s* r, L
$ |3 J/ G% C& W- a5 _( V
4 T( w+ k) X5 A7 C4 z$ ?$ I
1. 地址空间(mm_struct)5 _) }5 L9 ?2 @- Q- L: y8 t
地址空间就是每个进程所能访问的内存地址范围。
6 K3 D/ M2 ^4 T5 k4 n& M* ?
( \( `4 y: O# `! J. h+ T这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小。
! t: C: Z# F7 u9 a% Q6 b
  p4 q* R/ n  B/ \; U
- G+ ]5 O$ t" M+ {3 ?1 U/ `* K" Z
$ o' N) Q  Q2 c* f" w! M% p. k现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间。7 N# L' j( P3 z* G
+ I3 Q. V) |; A+ w8 W/ X3 S
地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。4 {6 A) t2 o  b( K* o5 }

7 L& K1 [& Q6 o9 v, G
1 A8 i  u) c( w% l: s) ?) \' n
; @" \0 e& J) W虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),
) K1 J( O* W% Y4 L% A3 y- _) n, }$ ?1 P& B9 q: ~+ r
进程能够访问的那些地址区间也称为 内存区域。2 U. A. C( P; }1 r8 D, F  n
2 X$ n6 N3 v) A0 z6 G
进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。
3 Y% T2 I8 p0 x
4 \/ O) l; z# C' J0 e  v ! w  |- N- A  L

8 W( d/ L9 t% T6 P内存区域中主要包含以下信息:
; c) D: F* f# I; W# [3 c
5 J3 z: t5 t; {- U; g, d! H注:bss是 block started by symbol 的缩写。0 Q- v' Y' \6 g& P9 ], K
/ l4 Y5 U3 h. i
2 z2 G+ c& Z1 c# _! a

+ o5 E7 `' N5 ~4 Y; Slinux中内存相关的概念稍微整理了一下,供参考:+ }! o& D! L$ b3 w$ @1 G
# W! ]) J, z6 b9 U
英文
含义
SIZE进程映射的内存大小,这不是进程实际使用的内存大小
RSS(Resident set size)实际驻留在“内存”中的内存大小,不包含已经交换出去的内存
SHARERSS中与其他进程共享的内存大小
VMSIZE进程占用的总地址空间,包含没有映射到内存中的页
Private RSS仅由进程单独占用的RSS,也就是进程实际占用的内存

0 k1 P7 @6 Q' \+ y% z1.1 mm_struct介绍7 ~0 `4 b8 F: J: V
linux中的地址空间是用 mm_struct 来表示的。- `0 w+ K# i- K" v
5 J4 X5 a6 s( E+ g/ K7 f
下面对其中一些关键的属性进行了注释,有些属性我也不是很了解......
  q! {- a/ ?) X$ g6 P
2 g/ h+ U7 ^9 d# p( w复制代码
% W2 C4 u- ?$ B' ~$ vstruct mm_struct {
" O& B; w7 Q2 P5 ?2 c4 L# M# g    struct vm_area_struct * mmap;        /* [内存区域]链表 */
- O( p7 w4 s3 p& f    struct rb_root mm_rb;               /* [内存区域]红黑树 */
8 q  J1 ^% V' J* k' i0 z    struct vm_area_struct * mmap_cache;    /* 最近一次访问的[内存区域] */
3 [7 B: t; K6 M) O3 Q    unsigned long (*get_unmapped_area) (struct file *filp,
. r% H: Q2 f; }3 [. h                unsigned long addr, unsigned long len,
1 A0 l! P' q0 u                unsigned long pgoff, unsigned long flags);  /* 获取指定区间内一个还未映射的地址,出错时返回错误码 */) @9 G: P) Y+ u6 y
    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);  /* 取消地址 addr 的映射 */
( g1 v0 k4 q+ H, V( C8 E    unsigned long mmap_base;        /* 地址空间中可以用来映射的首地址 */
  \. O# Q" A, m8 Q3 _    unsigned long task_size;        /* 进程的虚拟地址空间大小 */
$ [, u  t& b1 f7 ~! I7 D    unsigned long cached_hole_size;     /* 如果不空的话,就是 free_area_cache 后最大的空洞 */
2 `/ l9 s( O( L( m. F    unsigned long free_area_cache;        /* 地址空间的第一个空洞 */
* @$ ~6 k. W; _" C* a! t) l* q! z    pgd_t * pgd;                        /* 页全局目录 */+ B7 d4 D1 C5 W6 o; _2 z3 U6 D
    atomic_t mm_users;            /* 使用地址空间的用户数 */+ k; ?: w; d3 J  E
    atomic_t mm_count;            /* 实际使用地址空间的计数, (users count as 1) */* o/ t, {3 e0 j  [+ Z" [
    int map_count;                /* [内存区域]个数 */
7 L! C* P6 X+ y2 b& |9 q; Z! j    struct rw_semaphore mmap_sem;   /* 内存区域信号量 */2 O( x; j, w5 ~
    spinlock_t page_table_lock;        /* 页表锁 */! D0 @! l' E$ ^! W( u; i
" v7 q& N* O8 v  }5 P: P: p
    struct list_head mmlist;        /* 所有地址空间形成的链表 */5 t1 N4 h5 U9 F0 o) }

' v) P4 t5 c. B7 `" D2 z& g    /* Special counters, in some configurations protected by the8 c# }( z, G  [
     * page_table_lock, in other configurations by being atomic.
; M2 W; Y9 P, @- O% d5 V# t" t     */0 ~3 M+ g, s2 }  V- Q
    mm_counter_t _file_rss;, l- N; o7 i/ y! S" p4 x) u
    mm_counter_t _anon_rss;
1 \$ W1 b1 h( f, F' @4 U+ f: }/ a/ @8 |- [; o
    unsigned long hiwater_rss;    /* High-watermark of RSS usage */# H2 D7 b: C2 o" h0 O
    unsigned long hiwater_vm;    /* High-water virtual memory usage */
/ ]  s; l/ T, S$ l  A$ z# [
9 k1 m( t2 b% ]  C/ g    unsigned long total_vm, locked_vm, shared_vm, exec_vm;  b0 \% ?# [' B- L" s, {
    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
: R2 u% G% S( C- ^2 y    unsigned long start_code, end_code, start_data, end_data; /* 代码段,数据段的开始和结束地址 */9 H) K7 m" i5 d4 q
    unsigned long start_brk, brk, start_stack; /* 堆的首地址,尾地址,进程栈首地址 */
9 z* K* A# M" A8 ^# d    unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数,环境变量首地址,尾地址 */1 O( p6 g8 T8 C! x6 r
- H) Y. v  e2 n' ]- r
    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
" t( i2 B. {. H7 M: J. Q- p4 @7 J
2 ^8 Y! E( W: a* }    struct linux_binfmt *binfmt;
/ [: R2 G2 B- n; k' D2 X" l
" ]0 J0 h0 T$ u; \    cpumask_t cpu_vm_mask;
" o9 o! P4 ^" E0 _( ^. W. G6 Z
% B& J' L* y+ V* V5 g* X* j    /* Architecture-specific MM context */
# C1 F) `  {$ x. M  K+ L! l5 I    mm_context_t context;6 G" W8 y* g3 w2 I: d5 I

9 S  ?, j, p* ~/ k    /* Swap token stuff */" Q5 O& M' `- [3 }$ D
    /*
# X# z8 ]% ^# q- J     * Last value of global fault stamp as seen by this process.
: q7 C+ @8 ]% c% O6 Q' Z% _9 L     * In other words, this value gives an indication of how long
3 z* }! I5 c+ b$ u4 \7 m3 Y     * it has been since this task got the token.
* i# G( p- _, M, z  y: p" \* x     * Look at mm/thrash.c
% |% G/ Y/ r8 p0 |     */
5 p0 k# ]3 p0 B) [" a' f9 m. T1 F0 M& f( j3 v    unsigned int faultstamp;
6 v% @1 `: D3 _    unsigned int token_priority;* }. C0 s/ G* O- p! f$ c9 o( q
    unsigned int last_interval;
" ~% F- z- m1 j
: A- F( T/ Y9 _" {. R    unsigned long flags; /* Must use atomic bitops to access the bits */
: V- N' R7 b1 t
. K- ?2 p  }$ q, e    struct core_state *core_state; /* coredumping support */( k' I' g5 r. ~6 x$ Y
#ifdef CONFIG_AIO
2 g- f: H% J0 z! T/ y6 m" t    spinlock_t        ioctx_lock;( ^: _. l: ^- P0 P; d9 P$ @! f! p
    struct hlist_head    ioctx_list;9 R; M* _& o2 n+ w' q
#endif9 O/ x; z% B: j- N# r) H. Z8 ?' ~
#ifdef CONFIG_MM_OWNER
( H5 o* w: C# u4 n; R    /*
3 [4 X$ i( R7 d# d     * "owner" points to a task that is regarded as the canonical
; S/ Y- C; w) ^8 K     * user/owner of this mm. All of the following must be true in% P7 D6 {5 w/ B& \( Q
     * order for it to be changed:
3 R& V/ T2 O1 e. A% o: k     *
# H) e5 p  H- q, }! n4 ~$ X$ Y2 \     * current == mm->owner
( y1 S, z+ w- B: E- J6 r; J     * current->mm != mm
- L% K( g: b# Y0 n# e( t     * new_owner->mm == mm
6 N4 [! d, g. B" T' v     * new_owner->alloc_lock is held! Y, I7 y! z# H+ a! i/ x0 I
     */
. Z' ~' r0 {0 ^# j$ h6 g    struct task_struct *owner;4 L7 _. k% J* y
#endif
9 ?: \; ~8 w- E; D% d( p6 A, p1 f8 j& {5 Y
#ifdef CONFIG_PROC_FS/ E4 @. r6 x5 I% t; N: c) C) @' {
    /* store ref to file /proc/<pid>/exe symlink points to */
! J" e- B+ J( v$ h7 B* y* p0 H    struct file *exe_file;
" D( D! h- W) c/ ~2 f1 N    unsigned long num_exe_file_vmas;
( B* V" ~/ f8 K3 d6 X#endif9 O) S1 y7 E( i! l
#ifdef CONFIG_MMU_NOTIFIER
" s9 a" u. |* n    struct mmu_notifier_mm *mmu_notifier_mm;9 K& D8 c! q9 s  e# s6 q1 r0 j
#endif0 d$ l; d# L: |5 ?
};复制代码: u9 o+ k5 X( f- `5 A3 S

$ V4 C- n0 r: x& [/ n( i
' {( G) U" K- E补充说明1: 上面的属性中,mm_users 和 mm_count 很容易混淆,这里特别说明一下:(下面的内容有网上查找的,也有我自己理解的)
7 J$ D; \; G& s3 B3 c7 }5 g! g% s6 n$ u/ ?2 S! f5 s
mm_users 比较好理解,就是 mm_struct 被用户空间进程(线程)引用的次数。: {( a, D. J1 }  w1 O; n: R% C0 P

2 a0 @) c" ~; Y* n6 O如果进程A中创建了3个新线程,那么 进程A(这时候叫线程A也可以)对应的 mm_struct 中的 mm_users = 4
/ l# [) s) q2 w: w6 O' i
2 M( Y* J+ k5 R
; o4 g% w! S4 N3 @2 G# @* m- w+ Y0 K- w: E/ T" x0 f, @. P
补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。
8 o+ W3 m  ~, x, b0 c
  P$ v* C. v0 d0 _6 G所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果父子进程没有共享进程地址空间,就是2个进程! r1 Z3 g3 L0 S  G' R
3 p! Z; b/ w# S2 U6 o, c
8 C$ h& ^- R* y, G3 I

/ Y) @& E/ `7 y8 amm_count 则稍微有点绕人,其实它记录就是 mm_struct 实际的引用计数。& [3 z2 w2 x$ l8 r. @0 D1 x, \

- R* k/ h8 {9 i2 Z# l3 d简单点说,当 mm_users=0 时,并不一定能释放此 mm_struct,只有当 mm_count=0 时,才可以确定释放此 mm_struct
3 A( ]0 n- @5 f8 I* r2 Q' A1 C- Q, b. f7 E9 M
/ m' z7 n0 a% x/ T' e( r/ {

6 m( K2 H8 F3 C- z( l; C' }从上面的解释可以看出,可能引用 mm_struct 的并不只是用户空间的进程(线程)
8 I$ v9 C6 ^0 r8 s' J
6 C; X; v: }& z* h! h" i当 mm_users>0 时, mm_count 会增加1, 表示有用户空间进程(线程)在使用 mm_struct。不管使用 mm_struct 的用户进程(线程)有几个, mm_count 都只是增加1。6 B# `# r0 D' O$ B
8 ~; ?) y0 E& t; G+ Q2 ^& E
也就是说,如果只有1个进程使用 mm_struct,那么 mm_users=1,mm_count也是 1。) \: v- \# V9 {/ ~$ m5 F
) V- O: v; c8 r! |1 k
如果有9个线程在使用 mm_struct,那么 mm_users=9,而 mm_count 仍然为 1。# ~, O& W1 c6 o6 H# l/ s

8 `' K+ x% ]+ V* W) [ ) ]+ q- k2 V. M
/ R3 h$ `+ b. e0 _3 v7 K. l
那么 mm_count 什么情况下会大于 1呢?
) \2 d, l+ v9 F2 h; Y, {: {; M) Z/ m* A6 ~
当有内核线程使用 mm_struct 时,mm_count 才会再增加 1。4 f* V  F5 T# K+ B4 \4 g# }

# C8 d  _8 n1 y内核线程为何会使用用户空间的 mm_struct 是有其他原因的,这个后面再阐述。这里先知道内核线程使用 mm_struct 时也会导致 mm_count 增加 1。$ E' _% [; Z2 F0 m8 Y5 c3 h
# C+ ?: t' Y% \8 T# i
在下面这种情况下,mm_count 就很有必要了:/ b2 X2 }# s. p; j
0 Q% X0 ~- Y, `, l$ Z

. p0 ?  L$ t+ C$ b
7 `& i2 N% e4 Q" |补充说明2:为何内核线程会使用用户空间的 mm_struct?' m- y. d# M1 H: U

4 W$ N$ m7 h) p* n对Linux来说,用户进程和内核线程都是task_struct的实例,/ z, X) s. i; t' U; c; y, M6 I
% Q/ c9 O8 ?! K: ~
唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的tsk->mm域是空(NULL)。
' G, o$ I2 `9 }
. |5 h4 H' L+ M) Y内核调度程序在进程上下文的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。
: H/ I6 }, G$ y' n& R0 c* `! ^  s4 L: k! f/ s
但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。
% A/ E+ n/ E$ |; j
9 M& G# _, Z7 Q) L$ ?6 C而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。
4 N' O9 ?  U9 W$ W  b, W" a. Q
$ q- O8 H7 z0 M& Y
2 V9 Q* P) L$ l1 P5 k
  ~! X0 w7 I7 u* Q& l简而言之就是,对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。
; a9 m! s% N* g5 x& N! S- N
- l6 {! m9 n# W% @, q9 f1 X% N对于用户进程,tsk->mm == tsk->active_mm。7 T  D, t0 ]% A0 O9 \; |- d

0 u' ~# J+ t: x
; Y$ u+ c/ ]. w& V1 O# R5 @: y7 ^. ^$ `8 @- Y+ C) o7 Y
补充说明3:除了 mm_users 和 mm_count 之外,还有 mmap 和 mm_rb 需要说明以下:* e. q$ T$ r! i$ a, Z  x- I
, x0 U# c2 _- h) Q' i
其实 mmap 和 mm_rb 都是保存此 进程地址空间中所有的内存区域(VMA)的,前者是以链表形式存放,后者以红黑树形式存放。
* s7 b: q' b% d3 }6 m! j1 }& G4 R5 N" F1 {4 E) t
用2种数据结构组织同一种数据是为了便于对VMA进行高效的操作。! E/ [% R0 ~7 |( S8 ~
# ^3 ~2 r+ k! h! @

* ~! [0 i9 R7 w5 W; L: p
! V5 y$ q$ r2 f7 I) m4 f1.2 mm_struct操作
! [& N& K& k( N  d9 z3 O1. 分配进程地址空间3 w$ d6 X1 |3 T9 D

. F# E2 y( e" p! L+ M7 O' `0 ^参考 kernel/fork.c 中的宏 allocate_mm
% X: G* x  d+ Q% z4 p- C0 r
. B9 ~" I0 }1 e2 x#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))7 s6 V# U# q6 @) f* W
#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm))), S! j9 T4 t* w8 |; O& Y
7 G3 x( F- P% h% }0 E) Z# j8 u  D

, e1 q& @- I' }其实分配进程地址空间时,都是从slab高速缓存中分配的,可以通过 /proc/slabinfo 查看 mm_struct 的高速缓存( d0 s6 D6 r7 e& ~

  b5 t) S! x% j- K  ]( M' L# cat /proc/slabinfo | grep mm_struct
; V$ m7 a7 [3 e3 b+ Vmm_struct             35     45   1408    5    2 : tunables   24   12    8 : slabdata      9      9      0
0 W- P4 P6 u7 W' z$ j - O  o7 ^- I/ b$ r! v7 i
  K2 |* s& J; e! v& ^
2. 撤销进程地址空间
9 [5 V8 j: e9 F+ A5 Z/ e' f( p$ V' z$ Y( \' C; X- d; s3 e
参考 kernel/exit.c 中的 exit_mm() 函数
9 g6 t, c! i, }% B) [+ ~( y0 g: e% r  Q6 |; v- G
该函数会调用 mmput() 函数减少 mm_users 的值,
% C1 k) \( q0 L+ w% R! S
2 N8 ~3 ?( Q. U8 x当 mm_users=0 时,调用 mmdropo() 函数, 减少 mm_count 的值,
7 Y" E" [2 }- l' y. {3 O( j* S+ F/ h2 C
如果 mm_count=0,那么调用 free_mm 宏,将 mm_struct 还给 slab高速缓存0 t# c% E5 o1 J4 {- O1 m

7 d, c% l4 J9 ?+ B/ d" t) L! _$ J+ g
" T8 E* o3 t& a1 a
) s: {! e6 b# g9 u; |& `# e3. 查看进程占用的内存:
4 ^; m$ Z* ?( D& H) h* f& C
( n& `# z9 L: @( `  F8 pcat /proc/< PID>/maps
* B: S7 P' p# a- g或者, a: T# n# t6 r  v
pmap PID7 \" s! `( M" g( B* [8 d! @' O2 j
/ a: ]4 O6 R; k. q+ U) u
% L, b9 |9 ?- I" L
2. 虚拟内存区域(VMA)
" U) L( M: {9 {6 I: J内存区域在linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。+ ?; ^% g$ U' o7 M1 ~  f3 F1 Y$ M

& e6 }  Z' H+ Z6 g5 U& b+ d) U ) N1 h" u4 M! I% q  r) V

* m& a& D0 Q0 N/ M; j# s# G2.1 VMA介绍5 G: r4 J/ W. N8 f8 w, Y# [
VMA的定义也在 <linux/mm_types.h> 中! d' ]+ t; g/ J4 ~! M7 Z. c
* a; k; Z; ?& G
复制代码% V5 W% j. @, x
struct vm_area_struct {) l! s! \4 R# C2 d% T) H5 F
    struct mm_struct * vm_mm;    /* 相关的 mm_struct 结构体 */, \" z& W8 }6 W+ n1 v! J0 d; I/ z: K
    unsigned long vm_start;        /* 内存区域首地址 */& k, k8 E5 D) t1 M
    unsigned long vm_end;        /* 内存区域尾地址 */
" d% v6 n8 z- V& }  v% Q
: b: M6 B! o- I9 _, u+ y    /* linked list of VM areas per task, sorted by address */" M6 e4 [( |1 h" k4 K9 p
    struct vm_area_struct *vm_next, *vm_prev;  /* VMA链表 */: ?  S  f% J* m6 Y

. B6 A; w) y! U& m' c    pgprot_t vm_page_prot;        /* 访问控制权限 */
2 d6 q- e" N9 A  z/ s    unsigned long vm_flags;        /* 标志 */
) J3 _5 I1 q0 O  f1 I6 ^( r# ^: H) ~# s" V" f/ @2 A
    struct rb_node vm_rb;       /* 树上的VMA节点 */" x4 w; {. A6 z/ i7 S, E8 d& ^

; E3 u1 \* D( {, R    /*/ [  C, a% R' P3 b5 @* ]+ P& d
     * For areas with an address space and backing store,% G1 t1 C1 i- h8 O
     * linkage into the address_space->i_mmap prio tree, or; }0 X- |* {+ c, F9 m& }
     * linkage to the list of like vmas hanging off its node, or
! g8 K" T' G. i0 o) T/ H     * linkage of vma in the address_space->i_mmap_nonlinear list.# k! V/ c8 a) _
     */& l# k7 }! r, L' M3 n" w8 b  O
    union {3 ^& J' ^( N) x9 L1 m: N' {
        struct {
9 ]5 ]9 M: z% ^8 W' }8 \            struct list_head list;
) f9 y  D1 r. L6 T# c8 t& z            void *parent;    /* aligns with prio_tree_node parent */8 Q9 ^, x% ]. [5 C, a
            struct vm_area_struct *head;
& c/ u" G2 i* E( d3 s* ?        } vm_set;5 Q; Z# G& N  [. n6 X2 e! ]) B

' k% E( m& f4 b, \( q        struct raw_prio_tree_node prio_tree_node;9 X" i/ A* T: E' m: E! @
    } shared;
2 T( J; l' }+ ?/ _* T8 U4 }9 x% R" _' _( |; P6 \, [
    /*: d8 N( Z6 T! C
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma' R* Z. h1 j+ _' K/ [
     * list, after a COW of one of the file pages.    A MAP_SHARED vma# z/ d+ W5 G3 `, ~8 L3 E3 x
     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack4 d: p/ [/ |9 X( r! F
     * or brk vma (with NULL file) can only be in an anon_vma list.
2 \# d+ y9 ^7 R6 `2 r( A( m     */
9 v9 F; \  m) h3 |3 p    struct list_head anon_vma_node;    /* Serialized by anon_vma->lock */7 C% S' q% @) M, R
    struct anon_vma *anon_vma;    /* Serialized by page_table_lock */* w5 G( Z, R) E$ U8 x
: Y$ T0 k6 q' l' i
    /* Function pointers to deal with this struct. */
) C0 B( y% ?6 i' g6 @/ e* q    const struct vm_operations_struct *vm_ops;
& N# z& \2 ]2 ^" x! I4 W9 Q! q! b( a- i5 B( O! c+ a
    /* Information about our backing store: */# \' d  S: N* O+ A( ?
    unsigned long vm_pgoff;        /* Offset (within vm_file) in PAGE_SIZE# w: \: ~. m) X! x8 ]# p
                       units, *not* PAGE_CACHE_SIZE */6 a+ K$ m2 Z; F0 Q5 i
    struct file * vm_file;        /* File we map to (can be NULL). */
' s  l3 T( Q- u9 i. W% I    void * vm_private_data;        /* was vm_pte (shared mem) */
. X: n* H' W; y    unsigned long vm_truncate_count;/* truncate_count or restart_addr */( `+ [- ~$ F* n# W- M
8 D9 \0 i  C" J$ p. k2 H
#ifndef CONFIG_MMU
2 `& v+ q3 r) F3 P    struct vm_region *vm_region;    /* NOMMU mapping region */
; Q6 A9 _, s. v6 K. \#endif
& ~2 @9 x" \/ e+ B- j#ifdef CONFIG_NUMA
2 i! \' z) e) x+ L" j9 G    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
* |+ }- o3 r2 Z#endif
( W* I8 F7 b& T$ O7 s+ i1 V};+ L" x" R% c# }1 F! o1 |
复制代码# G; x& l3 ], f6 D$ q: j! W
- k) L5 F6 m0 e' o9 {4 ~, S3 u7 l

6 N1 g' W. y5 u这个结构体各个字段的英文注释都比较详细,就不一一翻译了。, J' \& d0 F1 f# b6 o. _3 V, o

9 j. T, k& O/ r8 w上述属性中的 vm_flags 标识了此VM 对 VMA和页面的影响:
* o- v! a" P# L7 o1 {# }' ^
1 Y' O8 Z* h6 r% @  yvm_flags 的宏定义参见 <linux/mm.h>. I4 H5 p( x& Y4 a/ M

; ]- Y: h  {* }: W1 d! H
标志
对VMA及其页面的影响
VM_READ页面可读取
VM_WRITE页面可写
VM_EXEC页面可执行
VM_SHARED页面可共享
VM_MAYREADVM_READ 标志可被设置
VM_MAYWRITERVM_WRITE 标志可被设置
VM_MAYEXECVM_EXEC 标志可被设置
VM_MAYSHAREVM_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该区域是非线性映射的

0 m7 R( k+ C  l9 D
; I' ?; |' Y0 p5 ^2 s' a' J* W2 Y" ~
2.2 VMA操作# L/ W1 A+ ~* a6 b) `8 k6 ]$ @
vm_area_struct 结构体定义中有个 vm_ops 属性,其中定义了内核操作 VMA 的方法& |- j+ @- S: |8 F

& ^2 x+ M& B, ^& D复制代码1 E) H4 |: f2 f3 q( P( R: g
/*8 x4 b4 o( X9 ]7 Y' {, j% _
* These are the virtual MM functions - opening of an area, closing and- @3 W- a% Z$ b" z2 Z6 d) L
* unmapping it (needed to keep files on disk up-to-date etc), pointer
/ X! I/ X0 y# ]6 Q! g" D; Y * to the functions called when a no-page or a wp-page exception occurs.
4 d3 g9 I$ s& S$ \ */
. \, _: ~1 O: ^0 F/ vstruct vm_operations_struct {
) G% a; h: b7 X4 H. E5 q5 B    void (*open)(struct vm_area_struct * area);  /* 指定内存区域加入到一个地址空间时,该函数被调用 */, E. |4 N7 w- ~" ]4 `# S
    void (*close)(struct vm_area_struct * area); /* 指定内存区域从一个地址空间删除时,该函数被调用 */
# u7 \# }7 z8 E* B5 G/ A( S    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); /* 当没有出现在物理页面中的内存被访问时,该函数被调用 */* g' J# M. f. @+ o3 d
$ O- O9 K+ C: ?& x: z& ~8 M) ~  U
    /* 当一个之前只读的页面变为可写时,该函数被调用,
& Z4 O$ G. V  _     * 如果此函数出错,将导致一个 SIGBUS 信号 */, t( A' g$ V" Z& z/ L7 E3 T- O
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
, U! t) F; }2 n% G' }
6 `$ k7 T! ~; D  E0 o+ Q; [# k    /* 当 get_user_pages() 调用失败时, 该函数被 access_process_vm() 函数调用 */
0 |- i' N! s  N+ K' }9 {    int (*access)(struct vm_area_struct *vma, unsigned long addr,1 Y5 r3 E  H3 b' Y4 y
              void *buf, int len, int write);3 |5 H& J9 `; q; p0 Q
#ifdef CONFIG_NUMA
* O. d/ w) e4 m8 w( j- J$ u% r    /*
0 @$ U+ C& i* x7 r     * set_policy() op must add a reference to any non-NULL @new mempolicy$ A+ h6 q! p: B. ~" A7 N, k
     * to hold the policy upon return.  Caller should pass NULL @new to4 N! c1 @; w! a% r% u. I
     * remove a policy and fall back to surrounding context--i.e. do not
& l" J/ M+ q7 b     * install a MPOL_DEFAULT policy, nor the task or system default1 g3 u3 K# I. N. P# e
     * mempolicy.
  M0 s8 {$ Y: x     */  K$ D! L) J3 ]6 v# N% ?4 r
    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
; C- q( m1 h9 L3 ~: G
$ L- i# }' X; P% U. k3 \6 _, t    /*
6 U1 n! H- l( X  i# E. ~     * get_policy() op must add reference [mpol_get()] to any policy at* |& c' h3 d, T8 F& p- t$ f, D8 Y
     * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
% H8 o* N. J0 ^! B; W- e; t     * in mm/mempolicy.c will do this automatically.# [* m# s1 x; y% l9 |/ Z
     * get_policy() must NOT add a ref if the policy at (vma,addr) is not
( M! r& ^- t! S. v& A- ^; S2 h# D     * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.4 b- w) q% [* A: K) o
     * If no [shared/vma] mempolicy exists at the addr, get_policy() op5 \$ o# M4 ~9 k% X
     * must return NULL--i.e., do not "fallback" to task or system default/ s  o4 B) `& {' `9 T8 j
     * policy.
) W& Q3 q; V$ B4 s( Y     */4 f# s4 @/ I- h( s
    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,: G' M& N  i2 C0 j4 j$ `
                    unsigned long addr);+ Y: t# Y) w5 o. A
    int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
9 A2 [; d7 B& a: _7 w        const nodemask_t *to, unsigned long flags);% J$ L1 E* n3 p+ D) v
#endif. D+ }& L8 `/ j+ J
};: ?0 p3 X* e. n0 J- R3 a* p
复制代码) E, Q2 h. c/ N2 q

0 T. g+ i, i4 {3 M. P) H3 Y& Y) M7 ~$ E, [9 I% B6 E9 D
除了以上的操作之外,还有一些辅助函数来方便内核操作内存区域。
/ a2 N' I& f+ k& c- B# E7 K  h/ P% Z5 I0 H1 R
这些辅助函数都可以在 <linux/mm.h> 中找到, b3 F5 X4 {: Y0 |2 D8 z

# K& a' Y- z  S1. 查找地址空间$ L7 [7 ?) T6 ]0 M! g6 V! i/ v

$ x" u& n5 L6 p& ^& E! y复制代码
7 x1 Z7 o# `% T* ~+ T1 G. {/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
. Z8 f0 s5 M2 N3 N# Jextern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);3 s& n# D! j% E& _, k
extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
: }& x- {; o( Y0 A  c. O' c                         struct vm_area_struct **pprev);
& {5 b4 a! o. ^4 B0 d; A
+ V9 A+ |, F4 t2 T5 W& ~1 \. h0 A/* Look up the first VMA which intersects the interval start_addr..end_addr-1,6 p1 Z8 |4 ?1 I" m. @/ [' R( e# Q
   NULL if none.  Assume start_addr < end_addr. */
0 o9 C1 B) O' ~) W4 [static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
, |  A+ [7 `' I0 }, f4 k{
% a+ G; G$ U4 i: H, o, G& \    struct vm_area_struct * vma = find_vma(mm,start_addr);
% q, A. [' q5 x5 y1 a& n$ O+ @$ H5 p) @% |0 \
    if (vma && end_addr <= vma->vm_start)0 N7 V# _; K: M6 f5 ]" a! ^. s
        vma = NULL;
) {. u3 G" S- {/ q* Z" _( r* a    return vma;
6 n3 S6 T4 ]9 x- H6 e( k8 S% [}
8 S2 w. v; P5 Q% R+ f+ m) K0 n复制代码
6 Q3 B2 F3 O, Q( t( d3 K1 V
" X  [9 `9 ~+ X2 n" M* s/ J
5 ]9 @( R: |/ ]2. 创建地址区间
. b/ ^. d5 t" V: `* I( ^
+ }+ O+ y. m; l9 U# I) V复制代码  [# z" w  M( y! B* u  Q1 M* R
static inline unsigned long do_mmap(struct file *file, unsigned long addr,+ H6 w! L. A* W& B. D: Q  o
    unsigned long len, unsigned long prot,6 V2 ~5 T* a  O
    unsigned long flag, unsigned long offset)
; D4 B, D9 W: O/ }" i' J+ P, K{9 }  O* j7 ^* H3 o' s, V4 O* a
    unsigned long ret = -EINVAL;
7 S+ e& U, l) i2 ~# Y    if ((offset + PAGE_ALIGN(len)) < offset)$ o( s2 B7 [. _) j) P0 I2 s: x
        goto out;- O7 v! Z) {7 D. H
    if (!(offset & ~PAGE_MASK))+ q* z) c2 O, w. B6 X
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);6 y3 w0 p- y/ E1 E5 K0 M
out:
) |. i7 P" _5 w) C) K/ h# E( S    return ret;; F" h6 y: x5 f  P7 X. N
}
8 a0 r0 F, I4 C- h复制代码
) L' y: i3 H! u; Y; i5 F8 S
7 x# L  }8 b9 O& F3 E8 r/ L- M/ ?! p. T2 `# o
3. 删除地址区间" @7 s6 s, k" O1 [4 I
9 Z" i# C( C7 {1 R* j. Z
extern int do_munmap(struct mm_struct *, unsigned long, size_t);$ u  e/ T2 g0 l5 X
4 z" n) E7 l% l% z6 M

+ W9 `$ y6 P2 [% X, q3. 地址空间和页表
8 F7 @5 I. `. y6 [4 f地址空间中的地址都是虚拟内存中的地址,而CPU需要操作的是物理内存,所以需要一个将虚拟地址映射到物理地址的机制。, ?  D7 U: X. G) Q- A' L$ B
1 G: g/ l$ {2 ~! |3 ^
这个机制就是页表,linux中使用3级页面来完成虚拟地址到物理地址的转换。
: o# v) ~6 S$ F( `3 I
/ C4 m7 Y( s6 ^7 l3 ~7 |1. PGD - 全局页目录,包含一个 pgd_t 类型数组,多数体系结构中 pgd_t 类型就是一个无符号长整型6 y/ d5 S% \+ ^% Z" M

' a- ~5 L; X) n2. PMD - 中间页目录,它是个 pmd_t 类型数组2 B) `6 }7 `7 X5 K
) P- t& i3 v. T( o- ^
3. PTE - 简称页表,包含一个 pte_t 类型的页表项,该页表项指向物理页面
, O/ z  @% D/ c1 e4 G' |: Y$ h0 m; l" J

8 N" p$ g3 x/ _4 i' k# S4 J  j, q6 O6 J1 n2 v9 h8 l
虚拟地址 - 页表 - 物理地址的关系如下图:# U" S* y6 d( b

. t$ \8 ]4 ^5 `* |
0 Y4 x/ e4 d; A$ P5 n0 ?+ p
作者: youOK    时间: 2021-2-4 17:43
Linux内核设计与实现之进程地址空间(kernel 2.6.32.60)




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2