找回密码
 注册
关于网站域名变更的通知
查看: 355|回复: 1
打印 上一主题 下一主题

Linux内核设计与实现之进程地址空间(kernel 2.6.32.60)

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2021-2-4 17:28 | 只看该作者 |只看大图 回帖奖励 |正序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

/ O% h) O8 ]) A进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理。
+ a) N) G# W4 G( T& a8 {
3 a" F, e, z& G. x# h3 M; v2 z主要内容:% F9 _5 h+ u% l4 |1 v

" O/ g( D$ {* F
  • 地址空间(mm_struct)
  • 虚拟内存区域(VMA)
  • 地址空间和页表9 E- g% }: `3 ~& k6 L
! q0 Q( ~7 _$ u& ]  t
6 B3 V9 a: n+ A" y1 n
1. 地址空间(mm_struct)
4 T* Y% C* A7 M* R! v- U地址空间就是每个进程所能访问的内存地址范围。, q% V- g' Y: D, ~8 u

2 I3 z8 \6 z" Z: p, m* u这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小。! H# j& c1 w. p% g: U. r% u

1 Y/ i4 @# a/ w% j$ C   z" i+ _2 i* G; h% J9 v& s* P; g+ f

$ z- _7 @4 ^: z, f现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间。! ?% }% [! j: U2 l/ Q

/ i* k/ m; }5 R3 M5 z" O5 v地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。
* r% u9 g# w2 h' R1 X2 ^
$ Z& Y0 m  g- v
1 P0 V, a: R7 o3 k; Y+ M2 m; L% d( \
) l: T5 P9 K  e3 f6 w+ v6 T7 R虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),* j. j/ t6 @8 r( a5 o5 h
+ {9 l- i4 t9 s7 o1 _- L: g6 Q
进程能够访问的那些地址区间也称为 内存区域。
3 q5 U' d" t; Q+ k; K# b6 G: i: Z4 q% g, c
进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。6 u" M( H1 W6 d+ S1 p& A& l

7 f* K& i7 c5 N
9 z3 O; k1 M$ M& T6 x, J  M  q9 Q' n/ a& [6 v+ `
内存区域中主要包含以下信息:
7 a6 [) o: L) C" E7 @6 \; O% c; F0 a6 J2 A
  • - 代码段(text section),即可执行文件代码的内存映射
  • - 数据段(data section),即可执行文件的已初始化全局变量的内存映射
  • - bss段的零页(页面信息全是0值),即未初始化全局变量的内存映射
  • - 进程用户空间栈的零页内存映射
  • - 进程使用的C库或者动态链接库等共享库的代码段,数据段和bss段的内存映射
  • - 任何内存映射文件
  • - 任何共享内存段
  • - 任何匿名内存映射,比如由 malloc() 分配的内存! k1 L# O! U6 @1 A
注:bss是 block started by symbol 的缩写。) L/ Z% V  g" ^, j9 Z! L1 ^5 I
1 b) F; d- f( g- m1 ~6 B0 W
8 r7 x, z& q! z* f6 t+ G
2 A1 v# G2 J( r$ Z6 e2 c3 t( Y7 H- [
linux中内存相关的概念稍微整理了一下,供参考:
8 x/ O0 j4 p  q4 B, f+ g% p8 S5 [0 o: o" f6 u6 v" |% [$ Y" I
英文
含义
SIZE进程映射的内存大小,这不是进程实际使用的内存大小
RSS(Resident set size)实际驻留在“内存”中的内存大小,不包含已经交换出去的内存
SHARERSS中与其他进程共享的内存大小
VMSIZE进程占用的总地址空间,包含没有映射到内存中的页
Private RSS仅由进程单独占用的RSS,也就是进程实际占用的内存

' w+ D. Z) G* G% B+ e1.1 mm_struct介绍- v5 a% ]: Z' n0 r; [4 {* M
linux中的地址空间是用 mm_struct 来表示的。6 o' \4 D. V8 P- v; f7 z+ M

% C/ h& z, e+ ?" t- j6 ?+ D' R下面对其中一些关键的属性进行了注释,有些属性我也不是很了解......0 ?" l+ s/ {1 R
# i! i" S, P" S2 v# T
复制代码% }3 g; ^, |* d  Y/ r
struct mm_struct {
, y, @5 ]/ X9 E) r5 _: t2 N( v    struct vm_area_struct * mmap;        /* [内存区域]链表 */( k( J  y9 B. c  V" U' L; N& E
    struct rb_root mm_rb;               /* [内存区域]红黑树 */3 ^& c* [4 c+ n
    struct vm_area_struct * mmap_cache;    /* 最近一次访问的[内存区域] */8 o6 u& o! Z1 [/ n6 `; Y
    unsigned long (*get_unmapped_area) (struct file *filp,3 r* d+ L0 B, o5 N2 z& q
                unsigned long addr, unsigned long len,
3 N. e6 r( j: ]& E+ _; Q9 N3 T1 T  W                unsigned long pgoff, unsigned long flags);  /* 获取指定区间内一个还未映射的地址,出错时返回错误码 */% _8 K/ j5 C+ y* p0 G$ E
    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);  /* 取消地址 addr 的映射 */, N  m. H3 o- r2 B3 j
    unsigned long mmap_base;        /* 地址空间中可以用来映射的首地址 */9 w5 V$ W7 n! |/ G( }' v
    unsigned long task_size;        /* 进程的虚拟地址空间大小 */
- [1 z" j5 t/ M, N  ^3 x    unsigned long cached_hole_size;     /* 如果不空的话,就是 free_area_cache 后最大的空洞 */" r1 C8 q9 r7 _* y
    unsigned long free_area_cache;        /* 地址空间的第一个空洞 */
9 ~9 U/ w. d8 ~1 O9 A. y( J    pgd_t * pgd;                        /* 页全局目录 */
5 R1 A7 W( I  P$ Q    atomic_t mm_users;            /* 使用地址空间的用户数 */
; `' c3 u$ L( I    atomic_t mm_count;            /* 实际使用地址空间的计数, (users count as 1) */
% B4 _+ s! w" J0 Y' c: ^- _" j    int map_count;                /* [内存区域]个数 */9 W& x5 o4 I" C0 E7 d
    struct rw_semaphore mmap_sem;   /* 内存区域信号量 */- G4 K& N: j; J* o3 V. {  R
    spinlock_t page_table_lock;        /* 页表锁 */
4 b/ f# Q- L/ _! W- |! s7 M  u; t) f3 M7 G# L
    struct list_head mmlist;        /* 所有地址空间形成的链表 */& R8 M6 t! E! Z+ k  G+ ~. v

1 B1 M7 L3 X* w) t& Y* F    /* Special counters, in some configurations protected by the/ s  ^3 D9 k+ `3 D
     * page_table_lock, in other configurations by being atomic.; r6 I* O# w0 f( U2 p% z
     */) u2 T& q' m$ F1 A& D
    mm_counter_t _file_rss;
3 X$ e* z4 L1 I: U3 L& ]  }- F5 S    mm_counter_t _anon_rss;
% l6 Y4 U* F0 c: b& m  |# @+ Z5 M
    unsigned long hiwater_rss;    /* High-watermark of RSS usage */
3 w* w  ?4 ~4 C5 ]$ V/ ]    unsigned long hiwater_vm;    /* High-water virtual memory usage */& I4 @, S+ v, t0 F  |3 l

: j( d2 z) f8 |6 ]) E6 j& I    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
( k- j% K# B1 g. @% |1 j' Y+ K. G    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;1 s3 h) J4 i: \( h/ _
    unsigned long start_code, end_code, start_data, end_data; /* 代码段,数据段的开始和结束地址 */. c9 I* I, g, ]
    unsigned long start_brk, brk, start_stack; /* 堆的首地址,尾地址,进程栈首地址 */! ?+ @1 f& L7 F2 S3 |
    unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数,环境变量首地址,尾地址 */
/ q& T2 `  M6 h2 n: o- F) Z$ ~9 r3 z( y5 H, y9 @
    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
2 S: d7 }/ h4 O4 F5 z# U7 _4 r3 k* u3 t9 p, c1 F4 c( Y
    struct linux_binfmt *binfmt;
) \' E; v/ }4 A! h
3 Z" _& x  q3 D5 w    cpumask_t cpu_vm_mask;& u0 k5 m. y' {. g7 q( Q

3 F6 T) @4 X" u    /* Architecture-specific MM context */8 D4 S9 ?4 U! [* M0 }
    mm_context_t context;, u' j9 |( |4 B+ Z" g: u2 i

! ?$ J  W' y  G9 m    /* Swap token stuff */
$ Y3 l, m* d" F! G- j5 B    /*
+ i- G, `/ l8 a2 u( R; q% W0 e     * Last value of global fault stamp as seen by this process.
* S- C, W% ]; n  U; F     * In other words, this value gives an indication of how long
+ K- ^8 R: q5 P, ^  X     * it has been since this task got the token.+ @3 _: s! K4 |: d( c
     * Look at mm/thrash.c
# s* h7 m8 C# [     */  q: P7 M' J- G9 e: X/ v! G0 ^
    unsigned int faultstamp;+ j; p1 D- d+ U! A2 n( h8 E' s0 g* B
    unsigned int token_priority;( `" l( w; s- b: P" T
    unsigned int last_interval;7 l  @- S! z- X% Q+ Q& c) V
# w) ^  @; p5 A! u& V9 H
    unsigned long flags; /* Must use atomic bitops to access the bits */
  K. L, n$ R2 T9 c7 r9 u7 c. S! q6 G  G; l$ {
    struct core_state *core_state; /* coredumping support */3 N( G( o1 G/ b! R" }7 l- m/ R" G* U/ L
#ifdef CONFIG_AIO, x0 C* N5 F  p1 B5 h3 |3 D& S
    spinlock_t        ioctx_lock;
; j4 \! b- @  P6 p) v0 D: i  k    struct hlist_head    ioctx_list;* \1 ]& z- D, K5 t6 y; X& F
#endif
. r. m& v8 n  M% ^* s2 I" E#ifdef CONFIG_MM_OWNER
: ^( L* D  O" g) j    /*, L/ K: Z6 n( R8 H& D. E
     * "owner" points to a task that is regarded as the canonical
2 x9 {9 U4 N  \% a. b" @! D. U* ]     * user/owner of this mm. All of the following must be true in6 }: k; a+ s  b7 d2 a6 |7 A# w
     * order for it to be changed:
0 `/ X0 S0 G- Y& N' _     *
" C2 |% w! V- h! U& }     * current == mm->owner. G7 x) g  q$ v$ o2 X5 X+ c5 x" Q* ~
     * current->mm != mm" R  ?/ y9 D, L- o
     * new_owner->mm == mm
; G! f' J: ]% M# f     * new_owner->alloc_lock is held, I% m2 J2 Z2 w0 ~7 j6 {
     */' f2 q1 j/ o- T+ T' j6 o, p5 G
    struct task_struct *owner;% N  L9 _3 K9 T, a# L5 q3 |9 g
#endif
" J7 A, K8 [; @7 Q/ n$ g+ l1 B* j  l& e- ~$ F
#ifdef CONFIG_PROC_FS; `8 T3 A8 S3 U( y% z7 s, b
    /* store ref to file /proc/<pid>/exe symlink points to */
$ l) D  a  @  g/ p( ~    struct file *exe_file;
+ E4 S9 R3 n& o% a1 y5 t/ Q5 |- M$ O    unsigned long num_exe_file_vmas;- n' Z0 E- h. U
#endif- Z% J7 `. j4 K
#ifdef CONFIG_MMU_NOTIFIER
) g- i* ?$ S$ _& _    struct mmu_notifier_mm *mmu_notifier_mm;4 p. }* X/ a6 p
#endif) n5 G9 N2 M6 f- n. k
};复制代码7 S) B2 h  k0 q- K& \& j7 K, b; e4 W
! t% @' l3 a0 c, Q8 |+ H7 v, d8 N1 D. z
1 X; t4 S8 X  f( [$ K
补充说明1: 上面的属性中,mm_users 和 mm_count 很容易混淆,这里特别说明一下:(下面的内容有网上查找的,也有我自己理解的)0 }$ r  ?+ ^" k; G) b

) e! P; }' [5 |* @) Ymm_users 比较好理解,就是 mm_struct 被用户空间进程(线程)引用的次数。
; i: c; r2 l( w( k- w
5 o. ~: [; o& j+ x# M7 B) d如果进程A中创建了3个新线程,那么 进程A(这时候叫线程A也可以)对应的 mm_struct 中的 mm_users = 4& M; R2 ?( {3 j- m) ^

% q4 C: H# ^) j6 ~) B& t  k ; F& e  d$ n/ w  }

" ?# S* W6 ^; W- J补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。$ w, Y4 X" M; G

' G3 q  P% x( q2 |  X0 O所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果父子进程没有共享进程地址空间,就是2个进程" I! I/ d& I% U- ~8 C8 w  m
6 R6 S6 V! F3 P0 s

0 w6 T' J. G  l1 }# @& @3 |; b: D8 d& a8 @/ \" y* M
mm_count 则稍微有点绕人,其实它记录就是 mm_struct 实际的引用计数。% M/ c0 C; z/ Y/ n
* ~% I& d8 \6 h: P8 o  h
简单点说,当 mm_users=0 时,并不一定能释放此 mm_struct,只有当 mm_count=0 时,才可以确定释放此 mm_struct: d7 O3 v3 j& S

9 @8 \+ Y* A  j- @& [ 2 P8 |* i4 z/ h- ?+ M/ r

) R. l5 ?- y% K/ s, b5 f从上面的解释可以看出,可能引用 mm_struct 的并不只是用户空间的进程(线程)1 O* K% R& c' G) e/ H
- X7 ]8 C  h; k3 U
当 mm_users>0 时, mm_count 会增加1, 表示有用户空间进程(线程)在使用 mm_struct。不管使用 mm_struct 的用户进程(线程)有几个, mm_count 都只是增加1。
/ l2 j- g# P/ q: K1 @
3 k$ W- n% Y; j. g6 q" \3 v+ F也就是说,如果只有1个进程使用 mm_struct,那么 mm_users=1,mm_count也是 1。
% @" b" D2 J5 S
0 A* Y) F. t  o0 |如果有9个线程在使用 mm_struct,那么 mm_users=9,而 mm_count 仍然为 1。* b- J* J; `( A7 N# Y

4 p) U6 s7 d- }" ^0 W* e * I" k- |; L# w5 b/ c2 e! f
1 t0 g) ?2 w! [; x# k, d
那么 mm_count 什么情况下会大于 1呢?
& @/ |8 M. T4 T+ b* q: Z) B3 A6 I& E+ W. o) R6 F, l
当有内核线程使用 mm_struct 时,mm_count 才会再增加 1。
0 M6 \8 x5 U0 [3 I
; R% j) J+ O7 J7 G1 h' {1 P内核线程为何会使用用户空间的 mm_struct 是有其他原因的,这个后面再阐述。这里先知道内核线程使用 mm_struct 时也会导致 mm_count 增加 1。
. ~3 n; [/ z/ F1 ]3 I; b( P% F: b0 Z3 m3 S5 v1 ^: [( t
在下面这种情况下,mm_count 就很有必要了:; j1 }  U0 Q" x
6 f2 D) v1 l) B9 w
  • - 进程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 了+ e$ A7 N0 b# u6 n# M

& l+ s3 `7 }2 B! g5 a8 v: _% f# |
1 u4 V& O$ `9 Q. d. U补充说明2:为何内核线程会使用用户空间的 mm_struct?4 Y$ T/ `1 h  Z0 c3 O' X
  K( b; B& i9 H0 a" k' O( w
对Linux来说,用户进程和内核线程都是task_struct的实例,' W% ?2 ~3 }: @5 g- v* L% _0 l; z

& n) i( I) N9 E; m! T* C! A唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的tsk->mm域是空(NULL)。
* u. l! d) g5 b: G! G- D) L
3 H% y0 e) d1 V0 n: O: e# |内核调度程序在进程上下文的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。
! Q, |& Z) R. y: y0 F+ O! F5 b0 d) |' x& A+ t# c! F5 J& i5 H4 `
但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。
" _) X2 E* e, _2 O7 |% c, Y' H* h! e% A
而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。
) [. k3 @2 L: A& n8 }2 R
  c+ w0 e* m; @: o; Q+ k8 Q) M , n: Z) ]$ J7 s0 I5 @" C
% b- t' U# S* K( b2 v
简而言之就是,对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。4 r8 C2 G! w! T) _4 t6 H$ c

( _0 A' b  l' I1 P对于用户进程,tsk->mm == tsk->active_mm。- G/ `- y. ^( u1 C6 X
9 K- k) X* n7 ^" |# D9 d6 {

& B- {0 T' v% ^/ |. e$ p0 F/ z9 o, c7 B
补充说明3:除了 mm_users 和 mm_count 之外,还有 mmap 和 mm_rb 需要说明以下:, m+ a" u+ `& x! N) H
  T( v; [7 l5 u# t- H# m
其实 mmap 和 mm_rb 都是保存此 进程地址空间中所有的内存区域(VMA)的,前者是以链表形式存放,后者以红黑树形式存放。4 }0 C4 v8 Z: d! H$ `# v
% c# L3 h1 Q& J4 ]" [
用2种数据结构组织同一种数据是为了便于对VMA进行高效的操作。
2 y' T3 N7 d* J$ t, s
/ Q. p4 D6 W% r6 C5 ? 8 c# I7 d( N7 Q5 f( o7 U

' n( A; r9 x' v0 ]6 K* ]: Q1.2 mm_struct操作/ A( P& t3 o# V' T
1. 分配进程地址空间
0 o3 p7 M% v$ q0 ~! H- U0 e# v- G  C: O7 D6 g8 @1 L: V
参考 kernel/fork.c 中的宏 allocate_mm
4 f9 S- }. z# I7 o8 @0 j& w- @6 P- W# D
#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))) f8 `" h. g3 T/ _5 c. s7 r8 s
#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm)))) E4 `* r6 ?2 n/ V( Z/ O/ }$ s4 y
0 P. c6 t. K2 R" s$ B( v& y

& T+ R+ l  `# Z/ O$ Z其实分配进程地址空间时,都是从slab高速缓存中分配的,可以通过 /proc/slabinfo 查看 mm_struct 的高速缓存
7 B1 t* k9 m! R( w0 [: t+ R4 `! u
; u% K6 X, k) r& x" U" D# cat /proc/slabinfo | grep mm_struct5 M- t: O2 t' o9 p4 I
mm_struct             35     45   1408    5    2 : tunables   24   12    8 : slabdata      9      9      0
9 u) m& w$ w6 j8 F2 c6 m
; Z; X: R: Y, E1 W) _1 f% L" v* Q8 h5 f) E; Q& t7 V$ z& @
2. 撤销进程地址空间
' x4 j& C' ^: Y- A: h# R0 B# K# i5 T5 _% q
参考 kernel/exit.c 中的 exit_mm() 函数
# x. J, y5 e1 x; q& b
6 R) Z: e; T8 f9 {! n/ q该函数会调用 mmput() 函数减少 mm_users 的值,
# b5 h8 z+ E1 n0 t+ ?7 a
  O1 d, H; s! w" h: B当 mm_users=0 时,调用 mmdropo() 函数, 减少 mm_count 的值,6 d7 ?5 t; y( ^
7 P1 v) ^/ ~7 i5 b. f3 C+ P8 I: l
如果 mm_count=0,那么调用 free_mm 宏,将 mm_struct 还给 slab高速缓存; \1 c& z! P4 w& ^$ L/ X# [
" Y7 V: |7 i( e1 r9 P6 B5 j
# t' X- U- n& q  |) H6 {4 E& y

( C; Y! D' j5 ]* z: l3. 查看进程占用的内存:
( y/ M1 m: _( I. s
% ]$ w& w  x! O1 C/ g2 [" Ncat /proc/< PID>/maps" M4 L9 x. ~9 u7 _/ V! c4 Y, K# D
或者
; ~+ c- ~+ d6 I9 I4 F  T' apmap PID
3 b, ~: }( N9 ~  G1 L. W6 u1 n, l1 k 9 N/ }) ^4 c! t3 v" `

9 E- N# X: t' C" i2. 虚拟内存区域(VMA)
: A. i8 ^, M0 O" R$ y* `% L内存区域在linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。. g8 y+ ^) g5 O5 i, k6 F+ j

$ e* c- a$ T4 q+ c; D3 G3 C % E& D6 [8 \! Q/ J/ F
  p/ w' G8 q! f  {+ i, O% H) I6 N
2.1 VMA介绍
% a: t  P. u' H, [& S- qVMA的定义也在 <linux/mm_types.h> 中
" m/ s  X; U9 T' R& e0 y, D, Y. B! }  b; r
复制代码
9 ?8 S) v( {7 E/ R' V% @7 zstruct vm_area_struct {4 T* e$ s5 y, j5 O: w
    struct mm_struct * vm_mm;    /* 相关的 mm_struct 结构体 */
) k) X; k0 |- r  b  l2 ~    unsigned long vm_start;        /* 内存区域首地址 */) C8 T3 Q% A- E# p
    unsigned long vm_end;        /* 内存区域尾地址 */
; o8 q# ]3 _0 m  e/ N: t% w6 G
8 }, h' ]; N# ~  _$ y/ A9 K    /* linked list of VM areas per task, sorted by address */
- _( t/ b& u, |- U( s$ w    struct vm_area_struct *vm_next, *vm_prev;  /* VMA链表 */. n# p( r: }3 o. M, ^
  S/ j+ @) }0 j. w% R0 z) O
    pgprot_t vm_page_prot;        /* 访问控制权限 */
, D2 |: T! Y* V* X: v4 a" o: @    unsigned long vm_flags;        /* 标志 */
; m$ e; m* e  |- ?
! M3 D) ]# u% y' V) B9 U# r5 Y3 _    struct rb_node vm_rb;       /* 树上的VMA节点 *// m- }! T' E( ?+ S9 J# D

  I6 G7 ?3 z) A# H    /*) z+ O4 ~) j0 S# F9 E1 b
     * For areas with an address space and backing store,
" j0 R2 j% Y2 s" H     * linkage into the address_space->i_mmap prio tree, or
* d0 C) q. P5 L% D5 b3 \; z$ R     * linkage to the list of like vmas hanging off its node, or5 [( R( o3 U7 Q. W  b/ T# r
     * linkage of vma in the address_space->i_mmap_nonlinear list.' B' C- ~; Z& T, h
     */
8 W6 w9 ?  d. D) p    union {
7 ~8 I! b2 U$ r8 ~. \        struct {& h0 m8 a. W: \, a" u% g0 h
            struct list_head list;
' q$ {6 m! @1 N6 e' r            void *parent;    /* aligns with prio_tree_node parent */
4 t+ d8 ^& r1 B% q0 V            struct vm_area_struct *head;
2 }8 l, Y, l' B* X+ t3 n        } vm_set;. ^: X7 P) N# B# W% `
2 d3 j5 r' u1 X! h* ~
        struct raw_prio_tree_node prio_tree_node;
+ S3 b6 p0 B( z: x. x- B/ _    } shared;7 y, Y0 v" }* w0 T+ G0 O4 N4 P
! A1 @4 _! r$ }/ a
    /*7 r. q/ |& b4 d6 e9 H6 U0 t
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
5 K; l+ ^3 y0 e2 w     * list, after a COW of one of the file pages.    A MAP_SHARED vma
" q; P; c' K  `5 V7 u! E7 j& Q3 u     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
2 s" q4 ]1 T/ r1 p1 V4 }8 ?+ b     * or brk vma (with NULL file) can only be in an anon_vma list.
6 L. h/ H/ o' x9 J     */2 g" S, ]# i4 h8 Z6 m) U4 L
    struct list_head anon_vma_node;    /* Serialized by anon_vma->lock *// k) f/ U( ?* q, t
    struct anon_vma *anon_vma;    /* Serialized by page_table_lock */
( V9 v& s* ?6 D: {0 O1 V: }# @( Q8 T/ U
    /* Function pointers to deal with this struct. */9 a& w; H. E) Q( w2 ]3 C& ~8 q
    const struct vm_operations_struct *vm_ops;' I; K4 F, |: G+ j2 g

8 g6 p* x$ c4 K6 `. _) R# [; b" o    /* Information about our backing store: */( E) K5 c, A2 {* o) p+ ~& ?
    unsigned long vm_pgoff;        /* Offset (within vm_file) in PAGE_SIZE
% |% q( n+ i0 m                       units, *not* PAGE_CACHE_SIZE *// [* D' y  w! ]% _/ M: T8 G; Y! ?
    struct file * vm_file;        /* File we map to (can be NULL). */6 l/ D9 ^. H' O4 \& z
    void * vm_private_data;        /* was vm_pte (shared mem) */. X/ r0 G4 D. `4 ~5 u2 M4 E9 \' M
    unsigned long vm_truncate_count;/* truncate_count or restart_addr */, j( K! S! x: a7 Y! g' C
# H1 [' ^# s; c6 j- B6 N
#ifndef CONFIG_MMU& J3 Z0 x1 k7 P' ~9 W
    struct vm_region *vm_region;    /* NOMMU mapping region */5 _+ i+ d0 _# U" r2 R) K% x
#endif
" h9 g0 J2 n% l0 |' ^#ifdef CONFIG_NUMA+ l1 R9 o/ o' X5 u: |2 \
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */2 y  \3 }, B9 @% O! C2 K! {% `
#endif
+ R0 L  h- j6 n( [};; i" S1 N! h. d+ h
复制代码
; V# X' F1 e  ~% S( c   x) {1 x. ]) ?( a+ ~

  `+ N3 c5 g; M9 J+ P* p6 K! a9 y8 t这个结构体各个字段的英文注释都比较详细,就不一一翻译了。
& M2 n$ G- d1 G( S. h' R/ G+ o2 b  k4 t3 c  }" b# \3 Y, V
上述属性中的 vm_flags 标识了此VM 对 VMA和页面的影响:
" L6 I7 _7 J& z" B+ i5 a: U& G3 o
, E2 H! g. W' _  I2 cvm_flags 的宏定义参见 <linux/mm.h># D1 H" w) p! K4 P$ V6 b

% B2 C+ d6 ~" @) |5 k- w
标志
对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该区域是非线性映射的
  j# T# {" c. E- r) v5 T8 P

& g. p. l& @% ?/ g+ L3 H* C
4 v$ P1 Q8 J/ F/ {- o5 t& {% I  P2 a2.2 VMA操作, q  n' _( P8 w8 J
vm_area_struct 结构体定义中有个 vm_ops 属性,其中定义了内核操作 VMA 的方法
: _* y$ K, T0 l( h
$ L: V) \6 w% a复制代码4 L; F2 c  e9 _1 \# H+ s% r! j- U
/*9 x& Y5 V7 J: B8 Q
* These are the virtual MM functions - opening of an area, closing and
: P2 x- G  k+ w% F * unmapping it (needed to keep files on disk up-to-date etc), pointer
4 _9 R; P; t. p. P6 o  S * to the functions called when a no-page or a wp-page exception occurs. " ~" ?4 z  ]9 g& ^# N1 f
*/  r  p' T. w3 [* F* P/ C
struct vm_operations_struct {
; U9 i1 Q6 ~! l. x( }+ J    void (*open)(struct vm_area_struct * area);  /* 指定内存区域加入到一个地址空间时,该函数被调用 */) v4 r& x, g( U* k5 U+ a8 a
    void (*close)(struct vm_area_struct * area); /* 指定内存区域从一个地址空间删除时,该函数被调用 */" q9 c; h. E( [& t5 L
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); /* 当没有出现在物理页面中的内存被访问时,该函数被调用 */
$ Q, I; W) Q* m0 @  M: d* O' z& }4 g1 s
    /* 当一个之前只读的页面变为可写时,该函数被调用,
/ l4 A* m1 c0 ^9 h! i; E     * 如果此函数出错,将导致一个 SIGBUS 信号 */! t5 K- K( p1 b9 n  E
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
# x5 E5 g4 \( \9 E. D2 R4 S/ ]
# j3 u- x" m6 ^9 D  I$ [: }    /* 当 get_user_pages() 调用失败时, 该函数被 access_process_vm() 函数调用 */
- C9 m& w& _" a9 ]9 U; X    int (*access)(struct vm_area_struct *vma, unsigned long addr,
5 P, O2 i. q7 C              void *buf, int len, int write);
7 y% k( q! C( f# q2 l( W% H#ifdef CONFIG_NUMA# q* L+ G; n$ K8 o! T9 y) X( J7 ^
    /*
0 a0 J7 {, a2 H$ q6 u( T! [, [     * set_policy() op must add a reference to any non-NULL @new mempolicy$ ]$ H2 a3 Q, G! e- q# @
     * to hold the policy upon return.  Caller should pass NULL @new to. ^9 G  o4 L6 O9 i& Q
     * remove a policy and fall back to surrounding context--i.e. do not  a& S$ C9 F7 x0 E! Q
     * install a MPOL_DEFAULT policy, nor the task or system default  j: u3 D- K& f' r/ ^6 L
     * mempolicy.8 y  [# H3 M0 l, M5 x0 c/ S  `6 `  n
     */7 p. Y, X4 t$ _' X" O0 O& B
    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);
( r1 x/ l2 B1 V) {* I, ^6 p+ a3 U% W( R9 J8 J
    /*; F3 |+ y$ H9 s8 N8 z; C. Z0 ]
     * get_policy() op must add reference [mpol_get()] to any policy at+ ^( ]/ n8 C. B5 p
     * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure0 o; e# Z# l5 [0 J. k! F
     * in mm/mempolicy.c will do this automatically.
& ^4 t" s; N9 x; {9 v; L     * get_policy() must NOT add a ref if the policy at (vma,addr) is not
6 ~2 J' d, q$ E1 z     * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
* r+ U0 D/ p  B* t     * If no [shared/vma] mempolicy exists at the addr, get_policy() op
5 c3 b0 O" m  F' C/ X     * must return NULL--i.e., do not "fallback" to task or system default6 U) B" C: F1 \6 s* L
     * policy.6 j! M: Q" V5 g& [4 Z" O. t! x
     */1 F3 Z$ e0 f5 F. I+ U0 G
    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
' |% y% ?- w; g! k6 A% r3 [                    unsigned long addr);* D3 ~% h# n, @9 J2 y0 [/ {, `7 a' d% X
    int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
4 c1 B; Q0 y9 A4 {: y2 W& W, F3 L        const nodemask_t *to, unsigned long flags);2 P2 W* t4 _! @. ]
#endif" e- [7 e  R4 k! q0 m
};
2 f. Y" @2 N* v/ L5 G9 u复制代码
8 o) Q( i3 j, S1 f0 q+ H; Z
4 t9 W% }9 {( b5 E" E) L) A# v
( b' C; [( S  l" S  C' Y* J除了以上的操作之外,还有一些辅助函数来方便内核操作内存区域。( M; B/ ]) C/ J9 x3 H

+ X3 M& S2 n5 A6 z  T; R这些辅助函数都可以在 <linux/mm.h> 中找到. b% ^+ W: a: P' I

* L+ O! w) w* C2 W) X" w' G1. 查找地址空间% y" W0 T, S5 b7 ?* L% V

+ }8 s+ V# c+ v/ N( B5 A5 F复制代码6 E7 O8 B" Q0 |) U
/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
3 _& N3 `, O! g, u) W! z- Aextern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);9 j9 m: o* F$ c
extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
. t  B+ Z% @4 h: q. L                         struct vm_area_struct **pprev);
$ F! {  O7 B' v0 I9 m; h% a7 D4 r, b/ X3 V
/* Look up the first VMA which intersects the interval start_addr..end_addr-1,5 C) Z% p, A/ r6 g0 |+ U
   NULL if none.  Assume start_addr < end_addr. */
- y: R1 k5 [  B. I) q3 ]static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
) o8 j- R3 s% D+ Q- F( a{/ i" ?) i  {: X4 Z6 Z6 z
    struct vm_area_struct * vma = find_vma(mm,start_addr);
' L& }0 m  O2 {* l) Z0 b$ O1 Z
4 Z( I2 e9 Z) ~' o3 K% T% \    if (vma && end_addr <= vma->vm_start)
% o5 T% J& j" U3 |: f/ |        vma = NULL;) P" K4 B, F* z
    return vma;3 ~9 [. H, n+ K9 g
}
- i) p+ l6 X! |' F6 a复制代码4 V+ A6 m8 A9 S7 S! d& I) n: P
/ M* X9 u6 n- V0 ?1 C

# I! ]/ O, X5 f# d6 N9 p, G$ J% W2. 创建地址区间# {& d2 h$ U% m4 w8 Q3 f
5 q: e1 y! F: Z; U3 U3 \; i
复制代码
( E; s2 S; Q& T# p0 estatic inline unsigned long do_mmap(struct file *file, unsigned long addr,9 Z/ V* L5 U( k/ @$ A6 e' X% \, C
    unsigned long len, unsigned long prot,
( N9 U# {* @$ K! s3 d    unsigned long flag, unsigned long offset)
' p; J- ?" P1 x: G# n{$ I4 B+ C; p7 N' i
    unsigned long ret = -EINVAL;
2 t; X8 E8 x* y( N    if ((offset + PAGE_ALIGN(len)) < offset)/ }6 s) C# s# [* D; H2 `/ [
        goto out;
9 C- n" y: M9 I2 A    if (!(offset & ~PAGE_MASK))) |. S1 C6 [( l
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);1 N# p9 S/ b7 j+ O. G- n9 T/ J
out:
/ m  `! V- c3 k0 w* v    return ret;
" q# P) a5 c9 ~9 s; H}
$ |0 t* Y8 y- ?. \0 v0 L/ Q- G( Q# O复制代码
/ ~1 l' g2 V! B4 D- V
. b" B/ H! A5 ~7 c5 w; W
0 b. T1 [7 V0 Y) z3. 删除地址区间' @, C  u! f1 k( E9 ~* R

! \/ A0 Y9 ~/ f; ?3 U* @8 Xextern int do_munmap(struct mm_struct *, unsigned long, size_t);. Q6 ^3 P: |, L+ M

. j$ t$ |$ h$ X; ^9 ^+ T. c# A( u: F( n: c+ w
3. 地址空间和页表4 C5 f* f9 x" c  b) H: Z; U' P; R
地址空间中的地址都是虚拟内存中的地址,而CPU需要操作的是物理内存,所以需要一个将虚拟地址映射到物理地址的机制。
; [( y' H/ S3 `3 k& v9 N
4 ?: H7 U8 K1 v9 w; X这个机制就是页表,linux中使用3级页面来完成虚拟地址到物理地址的转换。
) Z( p  h3 t$ _0 L) Q
# t9 R2 X, j. [" U1. PGD - 全局页目录,包含一个 pgd_t 类型数组,多数体系结构中 pgd_t 类型就是一个无符号长整型
" [4 W9 ^" k0 p; o# e) A3 |8 r7 N" u& x! b6 `  S; T( r* l3 w
2. PMD - 中间页目录,它是个 pmd_t 类型数组1 D6 w8 z9 z' q$ h

3 h  S' ?. D1 ]3. PTE - 简称页表,包含一个 pte_t 类型的页表项,该页表项指向物理页面7 j9 Q# ^" j& o1 {) Y
+ w, h9 U( y- {- q' o

5 r% W  R4 f  D% Q8 L$ Z3 q+ O" e9 Y8 _( q: ~" {5 Y* J
虚拟地址 - 页表 - 物理地址的关系如下图:( h1 p7 j# `$ L5 l, {& j( [
6 i7 i8 x1 ?4 _
/ a9 j: T/ ]1 Y' s( a

该用户从未签到

2#
发表于 2021-2-4 17:43 | 只看该作者
Linux内核设计与实现之进程地址空间(kernel 2.6.32.60)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-24 19:12 , Processed in 0.218750 second(s), 27 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表