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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
. q0 R; n' v# U& ~# }: O
进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理。
, d) V  r3 x4 d+ ?2 g% h, T6 g: F9 a' T/ A/ t0 `
主要内容:8 b; ~# Y+ W2 a3 ~
% h+ p: D& y  d0 X0 q
  • 地址空间(mm_struct)
  • 虚拟内存区域(VMA)
  • 地址空间和页表3 |: x( q$ p! L. v: M. N$ O4 t& n) m
: y! W& U1 _: @  ~
8 j: F) e! F; r* v7 ]! P
1. 地址空间(mm_struct)' @" U7 _% C: a6 J0 L$ @& S; n
地址空间就是每个进程所能访问的内存地址范围。# B' i" @3 n4 f4 ^( e1 ~$ o7 [  t, y
; C  `+ _' c- {5 b* W* e0 a4 R
这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小。
2 ~* q+ i* B9 X1 @3 K0 @. L+ F6 Z1 S% W- _0 d- Y

* ?! x: R: y* |) V8 @( ]+ k0 D! P) B3 L9 w
现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间。
+ W) O' y5 W& t; L  X1 J; i2 D1 N0 T# U5 }( E- B8 D$ O
地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。- Y2 p( ~: b+ V

" q) G1 i) H8 _( d
$ i+ s0 n& ?6 X, W; w- O/ {8 t+ r
虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),- x4 i9 T0 o7 y) c
6 A' ?$ `2 C& G0 C. |3 J. Q+ G& ?
进程能够访问的那些地址区间也称为 内存区域。2 c# b2 `8 l2 ^2 r! D' Z' {  w, R

. K, O' ?1 w- v4 e进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。2 `6 n7 y) s5 D* S  u
+ X9 y8 S' c$ i3 }5 k/ ]
2 }7 }0 b$ a- g+ q7 k

& [, Z+ ?, X4 ?内存区域中主要包含以下信息:
8 l# l+ a2 E: }+ v, _; J* M# U6 P1 o4 P- ~! P
  • - 代码段(text section),即可执行文件代码的内存映射
  • - 数据段(data section),即可执行文件的已初始化全局变量的内存映射
  • - bss段的零页(页面信息全是0值),即未初始化全局变量的内存映射
  • - 进程用户空间栈的零页内存映射
  • - 进程使用的C库或者动态链接库等共享库的代码段,数据段和bss段的内存映射
  • - 任何内存映射文件
  • - 任何共享内存段
  • - 任何匿名内存映射,比如由 malloc() 分配的内存6 B: q  b& ~; E+ K
注:bss是 block started by symbol 的缩写。
: h+ Q4 j* ?3 A# W. k7 b* u' E1 M
; p% R9 k5 p& W
8 D0 V" v# j! M
linux中内存相关的概念稍微整理了一下,供参考:
+ G- D+ {& o+ d. m- `, A% B# M( ^" }
5 b& T7 o) v" k' m2 i: l$ W9 E6 b/ J
英文
含义
SIZE进程映射的内存大小,这不是进程实际使用的内存大小
RSS(Resident set size)实际驻留在“内存”中的内存大小,不包含已经交换出去的内存
SHARERSS中与其他进程共享的内存大小
VMSIZE进程占用的总地址空间,包含没有映射到内存中的页
Private RSS仅由进程单独占用的RSS,也就是进程实际占用的内存

6 s2 c" W. z4 k9 V% @1.1 mm_struct介绍
3 T$ g# B0 M/ Q  N+ Plinux中的地址空间是用 mm_struct 来表示的。
5 W, ]0 ]' l+ i1 T, F: n+ D$ O1 p7 Q* f! t, m) ^3 n! d+ k
下面对其中一些关键的属性进行了注释,有些属性我也不是很了解......& y5 d/ m0 g, [6 |& J/ _
& `2 B: k: L/ U$ Z8 \9 N
复制代码  c- |# N* v) G. O# D
struct mm_struct {
, l: h* Q# ^$ o4 Q    struct vm_area_struct * mmap;        /* [内存区域]链表 */& Y' M8 N9 G) P+ Y  ?
    struct rb_root mm_rb;               /* [内存区域]红黑树 */
  R8 ^  F9 c; T  x. X. e    struct vm_area_struct * mmap_cache;    /* 最近一次访问的[内存区域] */! f% u! ~8 X/ v/ x+ R
    unsigned long (*get_unmapped_area) (struct file *filp,
" `. d# m3 Z; }0 g9 w% r+ H                unsigned long addr, unsigned long len,4 h" k! L, K9 z2 }* W0 p7 ^
                unsigned long pgoff, unsigned long flags);  /* 获取指定区间内一个还未映射的地址,出错时返回错误码 */
: K9 r4 r& M- h* I8 I    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);  /* 取消地址 addr 的映射 */, w( V8 N% [. V" |- J/ I6 w( \" v  V- p
    unsigned long mmap_base;        /* 地址空间中可以用来映射的首地址 */
) S" z) q+ U8 K9 b; U0 R" N    unsigned long task_size;        /* 进程的虚拟地址空间大小 */
3 d+ X! J5 {. h, O' k5 b( m- [! k4 v    unsigned long cached_hole_size;     /* 如果不空的话,就是 free_area_cache 后最大的空洞 */% I1 f4 u; k: C! {
    unsigned long free_area_cache;        /* 地址空间的第一个空洞 */- O: _2 \$ D+ P! E* \2 ~
    pgd_t * pgd;                        /* 页全局目录 */7 h$ t7 B; T6 d2 w& v. Q
    atomic_t mm_users;            /* 使用地址空间的用户数 */
4 x0 w8 j; T- [- S    atomic_t mm_count;            /* 实际使用地址空间的计数, (users count as 1) */
9 @6 y, ~* P" H  I: f+ o    int map_count;                /* [内存区域]个数 */, O8 }! @, y* v" b% m7 w
    struct rw_semaphore mmap_sem;   /* 内存区域信号量 *// O3 X; v4 }4 ~$ q
    spinlock_t page_table_lock;        /* 页表锁 */7 K8 Y" t, K/ Y1 v
. S: @! T* L9 L4 x: v0 G. P
    struct list_head mmlist;        /* 所有地址空间形成的链表 */4 d% G' [" |5 E4 a- l; F) m6 b
4 a  O! O9 L* l8 N( j
    /* Special counters, in some configurations protected by the" h+ b; V( L& ?2 }! ]. n7 ]
     * page_table_lock, in other configurations by being atomic.
/ _  c0 ~' P$ Z& F# N2 E     */6 j3 A% h( I+ i, V
    mm_counter_t _file_rss;/ M  a: _3 \8 [# f& e+ p: {9 {& Y
    mm_counter_t _anon_rss;' k8 s7 v- p1 i9 {

# ]- r7 \2 Q- y" @5 f! Z# V    unsigned long hiwater_rss;    /* High-watermark of RSS usage */
2 U7 V4 J+ ^1 W- l" A3 k# ]    unsigned long hiwater_vm;    /* High-water virtual memory usage */
) w& E  N# \( }) q. J; O0 j1 b/ E; o# {: S) L1 x0 l# H# B/ I  i7 n9 \
    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
4 Q; s1 W) b  T4 D1 m  p    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
, m4 R3 ~2 ~: ]; l    unsigned long start_code, end_code, start_data, end_data; /* 代码段,数据段的开始和结束地址 */
" W1 @3 y5 U1 r( g# `# C    unsigned long start_brk, brk, start_stack; /* 堆的首地址,尾地址,进程栈首地址 */
( D7 ]* s. U- h% K    unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数,环境变量首地址,尾地址 */
- a) E) n+ ^6 t3 V; w# l! ~. j/ J) J8 D* p. r
    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */4 Q  x. J, @4 t' x

% d+ L' _4 y/ t0 D6 j0 |    struct linux_binfmt *binfmt;
. r  A" c+ N9 c, Y  R
2 ~+ R; S6 u* [0 L$ E+ m5 }6 z    cpumask_t cpu_vm_mask;
- _5 V5 ~) R* b9 x1 ?" r: O- b9 Q7 I* u5 h! ]$ {" J( b$ g
    /* Architecture-specific MM context */4 ~# E9 H  H! F0 p' ?5 [+ z2 L
    mm_context_t context;! }8 p, h* ^3 ]# ?$ e  O! w) d

1 h1 X: `( w: n5 M# e% k+ D    /* Swap token stuff */' M' c) [" |4 L, R  i4 `4 i
    /*
6 J) _2 N) }4 Z+ `8 p! K     * Last value of global fault stamp as seen by this process.
! R2 V  s* o. t& u! s9 u8 z! b% g     * In other words, this value gives an indication of how long
0 c8 I8 A1 J; ]) ~( l     * it has been since this task got the token.
- m1 t: k, @, Z( G! U     * Look at mm/thrash.c
. ], E. ]+ s" p) y% W     */
0 z7 E7 D7 f5 a    unsigned int faultstamp;; f$ R. k2 a/ _' s" C6 [
    unsigned int token_priority;
( T- l# l- [0 g7 a- i4 P    unsigned int last_interval;9 m5 R6 G4 O  Y8 \

; @0 i/ C, L/ m    unsigned long flags; /* Must use atomic bitops to access the bits */0 w/ w9 }! j, P

: b9 ]5 \1 l! f2 B    struct core_state *core_state; /* coredumping support */+ ?5 S' e  t: r9 U, ~
#ifdef CONFIG_AIO- g$ @$ v! D$ h  @
    spinlock_t        ioctx_lock;( D4 H' z* V3 k% E
    struct hlist_head    ioctx_list;
  T+ O8 @. Y: c# G" r#endif8 \: z4 i' X) r9 ?! U7 c
#ifdef CONFIG_MM_OWNER# t- h, L4 B. j& ]9 O
    /*
  o, y9 _( G$ |, F& Q; k& O4 P     * "owner" points to a task that is regarded as the canonical
1 D& p* d& p6 l; R' E7 p) A4 t     * user/owner of this mm. All of the following must be true in
- O( ^- v, T  R$ M* J3 u5 k     * order for it to be changed:7 S0 H- E- U9 Y7 @7 h; |
     *
7 h5 G- n7 }8 O* N+ }6 ?9 S+ x     * current == mm->owner" _8 M9 D2 ]9 v% C; k
     * current->mm != mm3 ]6 l$ u1 O: m" D% R
     * new_owner->mm == mm: o! j5 C, W  e: v5 y" M
     * new_owner->alloc_lock is held$ ^2 d/ l- a% Q) z- i; X
     */0 R' B7 k# Z3 p; ~5 X8 I0 f% p
    struct task_struct *owner;
0 F5 [4 ^& |" ^8 ]# @. Z#endif
% E$ Z6 C5 q, o1 q5 t+ B9 B! S6 p8 i* v
#ifdef CONFIG_PROC_FS
' `( J* |/ J' I( ?9 C) }& E1 k    /* store ref to file /proc/<pid>/exe symlink points to */
5 G. l, I3 q# R4 }5 F; a$ |/ O. r    struct file *exe_file;
5 O: |! W/ U2 x: }' @6 z5 z$ R    unsigned long num_exe_file_vmas;) e% ?# W0 d2 C. U  P2 v& j: M8 J, ?. y
#endif- I% w( u3 C" c9 c7 @1 X3 c, |
#ifdef CONFIG_MMU_NOTIFIER
7 s, \- L, ^* Y- B* i" y    struct mmu_notifier_mm *mmu_notifier_mm;
. Y5 s/ H- n" @6 x#endif7 W5 C: N1 d3 ~2 g" q) i
};复制代码( P: @# ~+ a/ ^0 f# n1 j
' I! v! R, ^9 A& H% o0 t" k

4 r1 b: E! ^4 G! ?# e/ T  y补充说明1: 上面的属性中,mm_users 和 mm_count 很容易混淆,这里特别说明一下:(下面的内容有网上查找的,也有我自己理解的)
9 d7 R( k* s  r/ a1 Z9 i# A* h, }- K& b% Q% {! o4 S0 f( A% x" X
mm_users 比较好理解,就是 mm_struct 被用户空间进程(线程)引用的次数。
( }1 G2 ~9 e9 |$ x! M6 r6 H! d4 ]: I* y8 K- [* s
如果进程A中创建了3个新线程,那么 进程A(这时候叫线程A也可以)对应的 mm_struct 中的 mm_users = 46 k5 Y, P9 B. s% h" r3 m$ ?
+ Q2 f- Q8 m. ?% @. K0 T6 ]- l
6 R, r! r, o% X- d4 Q8 a9 O+ M
" d7 F. s# h, I' c
补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。" d6 O4 A: D) I7 @$ ]2 n

  u. K- u, q! X. w所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果父子进程没有共享进程地址空间,就是2个进程9 @! [5 f. u$ v
$ j2 _3 `+ [1 }$ Q, n1 L

- f% {, t) ^- {& I
0 b! n6 B  \' O9 Q4 t6 S- G+ ~+ zmm_count 则稍微有点绕人,其实它记录就是 mm_struct 实际的引用计数。
4 L$ e9 a/ R# l& a. u3 T# w6 F
2 K9 d( ]( V$ L5 [# b7 }- j& ^- w简单点说,当 mm_users=0 时,并不一定能释放此 mm_struct,只有当 mm_count=0 时,才可以确定释放此 mm_struct
1 B  O6 d& k  F* u
6 ]: J" X/ K9 ^. I8 Y ( U$ t$ n0 I. m7 r
( L8 M+ N7 g+ p0 C- j
从上面的解释可以看出,可能引用 mm_struct 的并不只是用户空间的进程(线程)" b1 {+ O0 n6 l% |8 W& Y
5 B8 I" q. ?2 Y- o- \# s* Z/ S+ l4 {
当 mm_users>0 时, mm_count 会增加1, 表示有用户空间进程(线程)在使用 mm_struct。不管使用 mm_struct 的用户进程(线程)有几个, mm_count 都只是增加1。% K5 c" ~0 R. y" }3 n- S
2 s/ i4 t+ B1 b. b2 p
也就是说,如果只有1个进程使用 mm_struct,那么 mm_users=1,mm_count也是 1。
  }: _& v; {# N6 H+ _6 ?4 J' `6 r4 m
如果有9个线程在使用 mm_struct,那么 mm_users=9,而 mm_count 仍然为 1。2 f9 u" l; {* f  a1 S8 t  g
$ |3 n2 r- Y1 s
. \1 z8 K8 Q+ q/ G. v/ ]

* `5 t3 j  j6 D那么 mm_count 什么情况下会大于 1呢?
" N* J$ a. F4 d" g4 P( p9 j$ g  [/ }) o4 W  e, ^7 k% y
当有内核线程使用 mm_struct 时,mm_count 才会再增加 1。, j0 G$ s4 Y+ ~8 E

% t# V% m* E8 g. L( \内核线程为何会使用用户空间的 mm_struct 是有其他原因的,这个后面再阐述。这里先知道内核线程使用 mm_struct 时也会导致 mm_count 增加 1。% R9 @2 L# |" I6 G+ h
1 j! K' F% l2 B
在下面这种情况下,mm_count 就很有必要了:
0 Z  x# w# n) S4 l0 j% {
! q. H$ R" l: ^5 `$ m% x
  • - 进程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 了
    " |6 {5 ^0 p9 Q' w; Y
' F8 [) f- q# @9 R5 r
% ]2 Y: N2 i1 R2 i
补充说明2:为何内核线程会使用用户空间的 mm_struct?$ ~7 O) D' w5 \
( i! i' f: P/ R$ M$ i* R
对Linux来说,用户进程和内核线程都是task_struct的实例,
. e! ~% v' n! o/ K# E) m6 G( @7 U$ h1 L. g' w
唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的tsk->mm域是空(NULL)。
/ I2 e7 G7 i6 P" e& \
6 q( D3 a% j2 z# V) `内核调度程序在进程上下文的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。! i& X/ }2 T6 k  y

2 c/ y$ `% r- u0 a9 M但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。& C$ b! R0 ], V& K: S1 `$ d
# w9 ]1 e' e: a5 K8 y' h. D+ F
而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。' v6 i% @/ o3 D

5 y) ~, }2 p! K5 t" h$ O 9 k, z+ Z. s' R) q! R. k% G; ]

& k9 z3 d' r6 V; [2 M5 p简而言之就是,对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。$ ~1 l) s# z8 n. T. w3 D0 R
1 ?6 Z. }# I8 e. T
对于用户进程,tsk->mm == tsk->active_mm。
4 @9 k2 w7 S. }1 H2 |! V2 `+ X( l; ]2 s; N2 ~8 b- T- K0 v1 U; ~

0 M; _2 ~2 _3 ?$ ?  D
7 ]5 M/ p5 Q+ u0 \补充说明3:除了 mm_users 和 mm_count 之外,还有 mmap 和 mm_rb 需要说明以下:
& t& ?  o. T, u* Q  H( U; j3 l4 u  }6 N7 A: J* q/ l5 x
其实 mmap 和 mm_rb 都是保存此 进程地址空间中所有的内存区域(VMA)的,前者是以链表形式存放,后者以红黑树形式存放。
- S' h" l$ d/ ^6 \7 v2 P" r8 E7 R+ x3 U* j
用2种数据结构组织同一种数据是为了便于对VMA进行高效的操作。
, `8 e: U6 }" p% X+ N" l
5 D8 Z6 j! N* l
2 d, r. s9 V# j
+ o8 W& F/ L( \, U1.2 mm_struct操作
; v# J" G( ]+ V! E1. 分配进程地址空间; J4 k- N$ r* _! ]. `* |
( W; Z: h. J, o% H- N
参考 kernel/fork.c 中的宏 allocate_mm) y2 Q, P; I4 j+ ^% n4 U' _

$ y, V3 j3 L6 Y) J5 t* z' Z5 X#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
1 A6 F+ N  x# G7 W8 L+ {#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm)))
) d2 I# G7 j( Q6 b8 U% K' ^3 h" k
7 ~# F# k1 u% l* ~9 t. d; y
& z" q4 W6 t; B% E) X其实分配进程地址空间时,都是从slab高速缓存中分配的,可以通过 /proc/slabinfo 查看 mm_struct 的高速缓存) C! B- M7 B7 c3 Y) S- {

2 w0 P1 g7 o0 I! _# cat /proc/slabinfo | grep mm_struct
# x; m, j! I8 l! k6 h+ \mm_struct             35     45   1408    5    2 : tunables   24   12    8 : slabdata      9      9      0) A3 S5 K3 Y. A. n5 P

2 Z5 n* b3 o7 R, m3 |: e" p  p: Y( q- b$ c$ f( Q
2. 撤销进程地址空间
% x1 i* ^( j. X# ]; T4 N8 g8 [  q; S" F& K& E! d- B0 n+ U$ }# M
参考 kernel/exit.c 中的 exit_mm() 函数
9 _3 x4 C! K& f' _0 ~
9 N- W2 W* u+ |: K1 _( {$ _该函数会调用 mmput() 函数减少 mm_users 的值,: R$ r" p* {! `% z8 `! L/ A! c: n
$ A4 d( \# f$ }9 e) k1 A# y
当 mm_users=0 时,调用 mmdropo() 函数, 减少 mm_count 的值,
% v  t& w' M6 T! l+ ]+ C. i" k0 z( u; S! }1 ?) K2 ]8 ?
如果 mm_count=0,那么调用 free_mm 宏,将 mm_struct 还给 slab高速缓存
( E" ]4 {! ^' J* d) a- r4 r% q, K' j0 k& ~* V, u

, v3 h# J! ]+ u- _" m
) l- W& ^5 M' A7 b' _- }# X3. 查看进程占用的内存:
) X* Q1 L' j' N- k1 d. v. H) [5 ~. T% ?, ]$ r
cat /proc/< PID>/maps
& x) X0 E" K! B) M4 y或者4 M% L8 i, ~/ T6 H# w& R
pmap PID
& @$ ?9 Y8 R7 F7 f7 {$ t0 n: F * w/ C0 o) J; `' _8 H8 n0 D& |

8 i0 i4 c2 ~& R) V! g. _2 e2. 虚拟内存区域(VMA)# r4 S$ l& F# D: ?. C
内存区域在linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。
. `" m& I; N+ C# v) t
5 K' L- [; X# u1 g0 { 7 I) J* C$ s( a3 A! a- l

4 k8 d' f2 e* E1 A0 e/ P2.1 VMA介绍
4 o4 N9 V- a& e8 F3 HVMA的定义也在 <linux/mm_types.h> 中' I( I5 V* m) y2 I/ D
- `2 G: f- ~5 t3 C9 S% D
复制代码
6 Y+ x. N8 n- Q: I$ ]struct vm_area_struct {
2 s# i% X6 F2 X& V# {( V( w/ D    struct mm_struct * vm_mm;    /* 相关的 mm_struct 结构体 */
5 c6 `* p; h& P8 ?( u/ d" A    unsigned long vm_start;        /* 内存区域首地址 */
- D7 e. K7 L* x6 m! I2 v( I& i/ F    unsigned long vm_end;        /* 内存区域尾地址 */1 b: ~; l  d% D& r
# I5 }7 k8 ~0 m5 D/ j; m) q
    /* linked list of VM areas per task, sorted by address */
) R9 k- I( g$ ~% G" }- F( E    struct vm_area_struct *vm_next, *vm_prev;  /* VMA链表 */! p6 D% i0 F# w& b; g3 Q' M5 W
0 Z3 T# }/ F3 Z+ o3 Y" W; y( v+ q
    pgprot_t vm_page_prot;        /* 访问控制权限 */
* _$ a# ~1 P: |$ U, @8 D- A" O    unsigned long vm_flags;        /* 标志 */; e. u4 Y2 z2 A' P, g

6 S# T2 A9 `8 {    struct rb_node vm_rb;       /* 树上的VMA节点 */& z# n6 [- k- k
& N6 U; `- S% ~( c, A1 a$ \0 i
    /*
3 N+ y* x: c6 }# X; s     * For areas with an address space and backing store,) Y0 o) J2 m0 P; d' @
     * linkage into the address_space->i_mmap prio tree, or
' x% m* ~7 S- i     * linkage to the list of like vmas hanging off its node, or. v. h2 Y8 _$ Z( v
     * linkage of vma in the address_space->i_mmap_nonlinear list.
/ t( U! }1 V1 X7 ^& j/ S     */- M- ^- M& U' s; L- p' z
    union {7 s" s2 k8 e1 M
        struct {3 g$ w( s. U* x' e: u$ B9 Y
            struct list_head list;
( W6 D5 V- L% ?: H+ t            void *parent;    /* aligns with prio_tree_node parent */0 R6 T6 L/ t3 U3 Y: D: d
            struct vm_area_struct *head;$ G0 w2 Q9 y! v* K4 p# V
        } vm_set;
9 m0 t3 K5 d+ c8 M6 U( y4 g; r' W$ @; v; }/ e0 g( F
        struct raw_prio_tree_node prio_tree_node;
& `0 v, O  g+ m( q4 ?9 O    } shared;9 O( K5 u9 j  Q+ y. w; {

+ q5 }7 _- Y# c    /*
; v  G$ z, `  m( a7 m2 @     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma& P- z6 a* U' J
     * list, after a COW of one of the file pages.    A MAP_SHARED vma
9 Q. j' R9 E4 B5 Y) h# Z     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack1 E% @, A7 I4 F+ {6 [3 _
     * or brk vma (with NULL file) can only be in an anon_vma list.
$ c3 h. |* J* I     */
  ?7 i9 K7 G6 P    struct list_head anon_vma_node;    /* Serialized by anon_vma->lock */
  [6 e0 R8 `# t$ c/ l    struct anon_vma *anon_vma;    /* Serialized by page_table_lock */: F& k9 A* z" I7 t

' f# X4 j/ c& }" u1 I3 F/ ?    /* Function pointers to deal with this struct. */) y7 r4 n/ @4 V4 @$ i( ]
    const struct vm_operations_struct *vm_ops;4 d& D0 r4 S6 b& j" M' t0 p
1 `. @* U( \* k
    /* Information about our backing store: */
" d; y: Z; _, l$ J3 y    unsigned long vm_pgoff;        /* Offset (within vm_file) in PAGE_SIZE
" [8 x5 z! A1 U4 o$ Z1 a                       units, *not* PAGE_CACHE_SIZE */! v& d$ a8 K) r2 U4 g
    struct file * vm_file;        /* File we map to (can be NULL). */  A- H9 i+ F, h' c' d8 J2 y
    void * vm_private_data;        /* was vm_pte (shared mem) */
' l" F( N' K9 e. \& R8 n    unsigned long vm_truncate_count;/* truncate_count or restart_addr */3 s# ~+ |$ q/ @; C& V* N

* \6 ^; N6 T1 M#ifndef CONFIG_MMU
, X/ V3 |( d: e( u* T7 y8 }    struct vm_region *vm_region;    /* NOMMU mapping region */
- [* w/ ~. m! i6 d/ _#endif
) _# s$ O; @* H" b* e/ h0 D#ifdef CONFIG_NUMA
0 T/ J0 s" k9 _7 F    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
: }8 P2 v  N7 c: m: s9 P1 q7 b; y#endif
: i& h& a/ P3 d' N5 n};
$ E$ E7 e9 z, N$ i& b复制代码
$ L1 H# D& V2 A) ^
  o, r% b9 o" }* e4 M, ^
: f0 f0 P: d. I, ?5 e" m这个结构体各个字段的英文注释都比较详细,就不一一翻译了。; y9 d4 z& o& `9 T& ^/ L

* O5 W3 Y, F% [1 f上述属性中的 vm_flags 标识了此VM 对 VMA和页面的影响:9 n/ P* P- o! A# q3 s" t6 _
$ A. Z" Y, H6 q& v8 d2 R$ {
vm_flags 的宏定义参见 <linux/mm.h>/ g& d) r4 j0 X- E

. e* z$ t8 ~3 A/ j5 ?6 y& m# E
标志
对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该区域是非线性映射的

' M: D: o5 t5 Y) V! |; ^3 X) ^# ^; O9 h0 O# L( L1 f

  j' Q8 q0 ?! N$ ]( J/ @2.2 VMA操作' T/ I' [  N5 Z9 M9 }- ~. h% ?& i
vm_area_struct 结构体定义中有个 vm_ops 属性,其中定义了内核操作 VMA 的方法( |. s2 @8 d; [, f  u+ ~

/ @7 h$ C5 n' ~+ ~复制代码
; C# W  U' `2 R1 }% N/*4 y) g' S9 [8 C. |, N
* These are the virtual MM functions - opening of an area, closing and7 k, r+ I1 `1 w) c2 r; C
* unmapping it (needed to keep files on disk up-to-date etc), pointer
: ~3 I' L/ O" ]* C8 O3 c- [ * to the functions called when a no-page or a wp-page exception occurs.
: N; v, T0 ]. V: f */
+ i+ F) h- F+ }: b8 N4 Vstruct vm_operations_struct {
% n3 X( e# M/ g2 M2 K5 b5 t    void (*open)(struct vm_area_struct * area);  /* 指定内存区域加入到一个地址空间时,该函数被调用 */% X1 V- Z2 V' Z% j! {/ ^* [
    void (*close)(struct vm_area_struct * area); /* 指定内存区域从一个地址空间删除时,该函数被调用 */6 d: ~) c  s3 p' z* A" ^3 L
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); /* 当没有出现在物理页面中的内存被访问时,该函数被调用 */
# E) c0 z" d* ?' R, `
2 e. b4 e5 }" _. T) ~& r6 R( D    /* 当一个之前只读的页面变为可写时,该函数被调用,9 \( S3 j; S% z
     * 如果此函数出错,将导致一个 SIGBUS 信号 */9 i9 C. {8 w. R! k
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
; C& b& U3 X) L0 l8 m  W& ~
. _  k' T! z9 X% j  Z! B    /* 当 get_user_pages() 调用失败时, 该函数被 access_process_vm() 函数调用 */2 T( c+ a6 U$ U" d% O
    int (*access)(struct vm_area_struct *vma, unsigned long addr,
$ r% c, b1 G2 ^5 F/ A              void *buf, int len, int write);9 x6 C: H# R$ B$ @
#ifdef CONFIG_NUMA
5 w, K% y* M' \    /*
8 r+ _7 P% q# y: l6 R. K9 c$ G; w! e     * set_policy() op must add a reference to any non-NULL @new mempolicy
9 L* p, |+ Z7 A  l     * to hold the policy upon return.  Caller should pass NULL @new to5 T. _4 _+ h1 s: n& G
     * remove a policy and fall back to surrounding context--i.e. do not  S6 ~  [& p5 d/ l
     * install a MPOL_DEFAULT policy, nor the task or system default
0 `& o8 ?* C- z     * mempolicy.+ [& `; Q! B  n4 H0 g- @$ \
     */9 _; U7 r, X; o& g% A3 V
    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);! {8 `4 b: k( X3 Q) c9 Q
: j- ?5 K! [, {1 X# K$ D
    /*% x4 H  @" W: r# a  A
     * get_policy() op must add reference [mpol_get()] to any policy at
' L: r8 g5 j$ Q3 C9 H  r* \) S  K     * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
7 J: Q: }7 |; X' w3 p, M( {     * in mm/mempolicy.c will do this automatically.) u3 k2 q6 @) |8 R
     * get_policy() must NOT add a ref if the policy at (vma,addr) is not
2 d8 C1 Q* i# Z$ u3 C     * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
& k8 J$ T0 P' {+ I- C0 {& H     * If no [shared/vma] mempolicy exists at the addr, get_policy() op
5 u9 i+ S/ _8 S: x/ u1 Z     * must return NULL--i.e., do not "fallback" to task or system default
, t1 H  G. T, L, I! p1 w     * policy.
6 q2 v' S, a9 {; W* v/ m! R  a     */6 I2 X0 U6 R& ?
    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,, \1 @2 l/ ?1 b  C4 T
                    unsigned long addr);
" F5 c" M7 J: \7 O* h    int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,2 s& w* H/ v/ I; n" u2 u
        const nodemask_t *to, unsigned long flags);- M3 H' J: U8 T- f7 [+ g  c1 t
#endif, O; l3 m9 A: N" g
};; \; j0 B# k. I  T
复制代码
/ J, G% }; k& z* e, s$ {7 {6 h+ P, T/ y) M: B0 t
- J1 p( o4 g7 o/ {6 R+ [) _4 |
除了以上的操作之外,还有一些辅助函数来方便内核操作内存区域。+ O/ ?. l! P/ K: a. j

1 f  E& l& m! c$ B6 a, r* h7 B5 d6 t这些辅助函数都可以在 <linux/mm.h> 中找到
& w1 X1 ]9 Y$ d9 t; |+ p4 J4 {  L4 R% a/ R) C3 z3 b) o( P
1. 查找地址空间
2 k4 {! {7 Q2 B4 B* J8 j" w- D% J! N8 f- H2 C+ p7 H. p
复制代码: z3 x9 s) @7 B( z0 x
/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
$ P4 f, E% K2 k8 }/ bextern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);
/ H4 ^) ?/ ], G6 U4 eextern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
/ L( ?1 m% W& |+ b4 C" Q                         struct vm_area_struct **pprev);
2 a: O6 |6 S5 E4 X) }' h3 c$ R% ]3 F" [) h" x3 G, e0 n5 x
/* Look up the first VMA which intersects the interval start_addr..end_addr-1,1 u# G/ B. _, o9 w, Z% {2 e
   NULL if none.  Assume start_addr < end_addr. */  u( |8 j2 I2 k' R
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)) v# L# r- w1 |  c8 A3 V
{
1 C; M: @- {1 m    struct vm_area_struct * vma = find_vma(mm,start_addr);- Q/ z4 P1 n/ H$ C3 A2 e
3 R1 g( E" A  a; E
    if (vma && end_addr <= vma->vm_start)
9 y4 j$ D6 C$ B" R; j+ w4 f        vma = NULL;
9 R# W. _5 b. @- H: v# g" \8 j    return vma;
3 z  u) b* t, B6 R9 ~3 r/ _: Q}
6 x# D& h6 M2 O, P6 ?' }' O. W复制代码( x- I1 s, z$ t5 ?% h: x5 A

- d" E. a8 l3 k6 q- |: S5 x5 e! z" A, p
2. 创建地址区间
' E- X3 a) |( L3 M& W4 K6 V
& Z% ^: l7 S9 H' a0 ~! |' D% `复制代码
6 ?: R/ U$ I1 \! y2 a' H9 hstatic inline unsigned long do_mmap(struct file *file, unsigned long addr,8 r7 s7 }- q2 y5 G1 ^3 J
    unsigned long len, unsigned long prot,
* T0 @# e- L+ |. D    unsigned long flag, unsigned long offset)
; }: `( `8 B, _. r" _( [{
7 ], }+ e! q  ^" P, z    unsigned long ret = -EINVAL;
' t; H4 `* r: y1 L    if ((offset + PAGE_ALIGN(len)) < offset)4 r  A" C6 y! P: K9 A
        goto out;
( W. i4 H- C9 o* v9 R3 n" J& W7 O    if (!(offset & ~PAGE_MASK))( q) W0 `7 N0 v; N0 e; {$ u
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);! S$ ?' Q) W* a7 M  Q
out:. F# r& y- ^% B/ d
    return ret;
/ @# j# S  j; q9 S) E! E2 a( [  I/ c}
- Q* I0 j: }% y) M7 t3 u复制代码
0 @0 E1 n# @2 R+ y& k& H
" Q6 q' `' A* e" p* ]: ^8 L5 [5 G/ ]/ {& K* W6 C( ^: [6 U
3. 删除地址区间
( m2 |, r6 ]# w0 ^' M( K9 z$ E: A0 Q. L9 q  n/ B9 b( o( T
extern int do_munmap(struct mm_struct *, unsigned long, size_t);8 d2 @( Q( g' d- r1 a' ^

. [/ e5 z3 g* H$ e6 \- v6 G( w: J/ l7 u1 C* E
3. 地址空间和页表
- D1 d* C* Q4 }& {/ f- t地址空间中的地址都是虚拟内存中的地址,而CPU需要操作的是物理内存,所以需要一个将虚拟地址映射到物理地址的机制。2 Q$ i) e# w! J6 Q7 w

# a! k: i; ?2 i# Z, r1 t这个机制就是页表,linux中使用3级页面来完成虚拟地址到物理地址的转换。
7 \# T2 T9 w# H3 Z1 k+ x
# e4 D# V) U, p1. PGD - 全局页目录,包含一个 pgd_t 类型数组,多数体系结构中 pgd_t 类型就是一个无符号长整型
& K' d- E+ C) V1 D  R( s9 i  a0 K/ i, ~. F, q. d  J
2. PMD - 中间页目录,它是个 pmd_t 类型数组! t/ @* g9 F' G
, Z+ u" E: m  n2 p, M
3. PTE - 简称页表,包含一个 pte_t 类型的页表项,该页表项指向物理页面- F6 [' H1 z- I. T6 ]$ r8 b  J

6 t9 e+ v! }! F* ~
3 R- y3 W( Q# J
9 Q3 c/ P2 i9 V; D6 G; u虚拟地址 - 页表 - 物理地址的关系如下图:
! y% {# _& b( C6 e0 p$ y9 l; X! x1 [$ Y/ m0 s$ z# f( O

0 x/ E! x* M# f

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-24 19:43 , Processed in 0.187500 second(s), 26 queries , Gzip On.

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

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

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