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

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

[复制链接]

该用户从未签到

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

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)实际驻留在“内存”中的内存大小,不包含已经交换出去的内存
SHARERSS中与其他进程共享的内存大小
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_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该区域是非线性映射的
- 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

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-24 18:44 , Processed in 0.265625 second(s), 26 queries , Gzip On.

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

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

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