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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

+ ?; X8 E; K4 J9 ^! f7 u+ W进程地址空间也就是每个进程所使用的内存,内核对进程地址空间的管理,也就是对用户态程序的内存管理。
( S/ m# ]6 \- U* n5 c) E
# `- ~6 D% C- P/ ^/ d7 w) r+ V主要内容:
! |+ u8 b, d8 d, u' _1 O/ p3 E4 A! f
  • 地址空间(mm_struct)
  • 虚拟内存区域(VMA)
  • 地址空间和页表
    # d6 `: G  X- Q& d

; f( C. P4 f& d) _
8 H( T: X& |9 i: P5 a1. 地址空间(mm_struct)" p; S8 ]' B) a+ m% [8 ~6 ~
地址空间就是每个进程所能访问的内存地址范围。$ l( _! X8 p- J6 B$ `- U2 a% w
  ~% q: B/ m* L9 a8 y+ V
这个地址范围不是真实的,是虚拟地址的范围,有时甚至会超过实际物理内存的大小。
- u# @/ t  J: F, t! l: n' D/ }5 [3 C1 i# H

& Z/ k( H. a' T" R
3 g8 v. P" M* X3 `2 c  m5 s现代的操作系统中进程都是在保护模式下运行的,地址空间其实是操作系统给进程用的一段连续的虚拟内存空间。
& f& T  k- ]& ~$ Y8 X2 y  z1 a4 ~& S" Y# \! O. W% M# n% {( L
地址空间最终会通过页表映射到物理内存上,因为内核操作的是物理内存。2 I8 e! e; X( ?' s, ]9 \2 ^6 n2 L; e
0 J1 @, t& y; p

+ Q  k, H4 P! }. U9 x& m. l) N7 R7 {* s) r3 W( {
虽然地址空间的范围很大,但是进程也不一定有权限访问全部的地址空间(一般都是只能访问地址空间中的一些地址区间),
2 }) S% \, z3 B* X* A, t3 ^! d8 h, h$ q! e4 j( A9 P5 a  u& \' B. P/ g
进程能够访问的那些地址区间也称为 内存区域。
% x" t7 X3 f/ [& A: X" i
; \% O+ Z) k/ a3 W. e进程如果访问了有效内存区域以外的内容就会报 “段错误” 信息。4 h1 i7 o- p) E, j. ?

7 s6 P! x* H- t
) k1 K/ A* r, u7 U% J* P- ?( }5 e! I) u1 {
内存区域中主要包含以下信息:
9 x3 T6 m" c2 O- ]. x& Y' N" M1 J! K# O# b& S" g3 W
  • - 代码段(text section),即可执行文件代码的内存映射
  • - 数据段(data section),即可执行文件的已初始化全局变量的内存映射
  • - bss段的零页(页面信息全是0值),即未初始化全局变量的内存映射
  • - 进程用户空间栈的零页内存映射
  • - 进程使用的C库或者动态链接库等共享库的代码段,数据段和bss段的内存映射
  • - 任何内存映射文件
  • - 任何共享内存段
  • - 任何匿名内存映射,比如由 malloc() 分配的内存
    * c( X' o* e8 |8 Y2 l4 o2 f
注:bss是 block started by symbol 的缩写。5 j4 f) H/ N- p; R0 k* Q! z

  D! v' A2 A+ u, y
$ b" z6 L; ~, V
* `/ ?  J* p( j3 k. klinux中内存相关的概念稍微整理了一下,供参考:
8 E# Y5 P; j: O! J8 ~$ M: x8 D* y/ P* \* f( C
英文
含义
SIZE进程映射的内存大小,这不是进程实际使用的内存大小
RSS(Resident set size)实际驻留在“内存”中的内存大小,不包含已经交换出去的内存
SHARERSS中与其他进程共享的内存大小
VMSIZE进程占用的总地址空间,包含没有映射到内存中的页
Private RSS仅由进程单独占用的RSS,也就是进程实际占用的内存

0 I/ E# W& {4 K" j! I$ M1.1 mm_struct介绍
! w( F: S& J5 xlinux中的地址空间是用 mm_struct 来表示的。
* b9 o6 m/ F5 k2 w5 {5 u5 ?* _4 h9 F, j6 y' c, N
下面对其中一些关键的属性进行了注释,有些属性我也不是很了解......  c' D7 K. D0 t. s" I7 }
1 I5 o# X. B1 Q5 y
复制代码0 i9 s  |2 I2 ]" Z. l
struct mm_struct {0 u- a. s; g. T" L" L  k
    struct vm_area_struct * mmap;        /* [内存区域]链表 */
8 S4 c  x; g8 t, L% t    struct rb_root mm_rb;               /* [内存区域]红黑树 */
8 Y7 x) N: u  C8 b    struct vm_area_struct * mmap_cache;    /* 最近一次访问的[内存区域] */
' p1 ?1 ?5 _& A6 h    unsigned long (*get_unmapped_area) (struct file *filp,
  w0 M8 W# _6 r; D                unsigned long addr, unsigned long len,7 @" Z7 \4 `$ s1 }4 O- q
                unsigned long pgoff, unsigned long flags);  /* 获取指定区间内一个还未映射的地址,出错时返回错误码 */- W% E0 v9 J% U; R( Z. i7 X
    void (*unmap_area) (struct mm_struct *mm, unsigned long addr);  /* 取消地址 addr 的映射 */8 R3 ^* c4 V4 S
    unsigned long mmap_base;        /* 地址空间中可以用来映射的首地址 */' d) ~2 T& Z5 f
    unsigned long task_size;        /* 进程的虚拟地址空间大小 */
7 z0 L2 A" S9 H  ~    unsigned long cached_hole_size;     /* 如果不空的话,就是 free_area_cache 后最大的空洞 */
0 Z, m- h* }# a1 N1 l    unsigned long free_area_cache;        /* 地址空间的第一个空洞 */
5 M: L* C( ]8 w0 q; I( i    pgd_t * pgd;                        /* 页全局目录 */
! y' |. f8 K1 G' l9 Y    atomic_t mm_users;            /* 使用地址空间的用户数 */
( q& X% W5 f: F    atomic_t mm_count;            /* 实际使用地址空间的计数, (users count as 1) */
: P" K* @8 }( V% Q$ H    int map_count;                /* [内存区域]个数 */
( S9 N8 Q2 A3 J4 C  V  }3 v1 e    struct rw_semaphore mmap_sem;   /* 内存区域信号量 */
6 R+ M7 ^* v; h( a4 ~    spinlock_t page_table_lock;        /* 页表锁 */
  F4 K# {! n8 |9 [7 A7 r6 j0 p- `; M+ e. |7 O7 ?; l
    struct list_head mmlist;        /* 所有地址空间形成的链表 */
) o# }& w. e0 n" ]+ }4 b' j* ]; E, B* m0 w$ x
    /* Special counters, in some configurations protected by the
& \' N7 \6 Q6 c4 N+ ^  t! B3 P     * page_table_lock, in other configurations by being atomic.; z( Q' i; y/ m, y! d# o2 n) I3 [
     */3 l, i9 @% ]5 X& m
    mm_counter_t _file_rss;
$ w# ?, ?/ b$ C9 H% A0 H+ k. C- C    mm_counter_t _anon_rss;
7 l- d2 P/ a/ K4 [' A; [  b  [6 |" y$ K9 ]5 `: |+ X$ j
    unsigned long hiwater_rss;    /* High-watermark of RSS usage */
0 f0 s9 f+ E+ w0 _3 i    unsigned long hiwater_vm;    /* High-water virtual memory usage */% m7 U. i8 L8 j1 i8 R+ `" |, q. i

& R: G- z( ?, Z  C- n% n. c    unsigned long total_vm, locked_vm, shared_vm, exec_vm;
( E. J/ ^6 ?" i5 h    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
4 A, B9 M' B  ?    unsigned long start_code, end_code, start_data, end_data; /* 代码段,数据段的开始和结束地址 */
# A+ ?, E- d! d. y# i9 k4 Y, f    unsigned long start_brk, brk, start_stack; /* 堆的首地址,尾地址,进程栈首地址 */- L3 ]% z! d% R" ?# X
    unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数,环境变量首地址,尾地址 */
+ w( G3 [5 z5 }/ R/ o6 T  b! k$ L
    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */' ]4 z# A9 m( r

3 Y( U& p7 R. O: V9 I. F    struct linux_binfmt *binfmt;; {2 k9 _3 k& I( D

7 J# O# B8 k" f    cpumask_t cpu_vm_mask;
' M4 s! ~% i/ @: u( N" ^0 }. h: d5 h# ?2 E. v2 H% A% h
    /* Architecture-specific MM context */
. w+ [5 l2 L& ]    mm_context_t context;
6 m$ ~' g! p$ o+ b5 t5 n2 d- s% i0 _
    /* Swap token stuff */
$ Z' W/ g6 E4 s: j- e% o+ S$ C    /*
4 w: [/ [" P9 Q8 n6 r     * Last value of global fault stamp as seen by this process.
4 M9 v/ j: E+ }3 o1 [# I2 H) V$ g     * In other words, this value gives an indication of how long
$ Y  o2 g  v, H: g* O     * it has been since this task got the token.! l0 \4 ]! ^, X. c9 M
     * Look at mm/thrash.c
9 `# c* Q& r) y9 g7 `4 A7 R     */9 s7 |/ V% y% \' c: q% \
    unsigned int faultstamp;+ }$ g+ R+ f7 u% R; ?0 X7 C' ?
    unsigned int token_priority;3 i  ]2 o8 o8 k+ r: f$ Z
    unsigned int last_interval;6 }& ~& {- A  e6 u, l( v
; `, a' {2 Y1 ^/ x
    unsigned long flags; /* Must use atomic bitops to access the bits */" |# m, C: G; l8 [# e0 m
3 T. l! @6 q6 r1 R# ^$ m, z
    struct core_state *core_state; /* coredumping support */5 S8 q3 ]( a! n0 f
#ifdef CONFIG_AIO" C4 K! `5 [+ i/ S3 L2 ~+ z6 o! |
    spinlock_t        ioctx_lock;
5 N+ M# K, {$ w1 K    struct hlist_head    ioctx_list;) U& m' s; R9 n1 d& H2 h8 O& l
#endif
0 }/ W$ }( S3 ?#ifdef CONFIG_MM_OWNER+ l6 p& U8 V( n$ s/ ~+ P, e
    /*/ R2 I1 J% X* l  {- ?' _. h
     * "owner" points to a task that is regarded as the canonical
+ R3 h, a" T4 {8 C     * user/owner of this mm. All of the following must be true in2 p; \5 S0 D. M# f6 Q+ y4 b: c
     * order for it to be changed:& S* `! J$ ~7 d  T
     *( T3 Y$ _  w0 w' h
     * current == mm->owner3 c8 B6 A  Q; Z
     * current->mm != mm1 A7 J/ u# l. p# r6 G  i- w$ w$ _
     * new_owner->mm == mm& J- O. j1 d; N  P
     * new_owner->alloc_lock is held
8 y1 Q8 W' C2 f+ W9 R  \  g9 _     */, C2 @& h* E. I5 C
    struct task_struct *owner;0 `  _2 b$ m3 O) y. [) ^$ n
#endif
4 f+ q8 N, H4 @3 C. h! t. n; ^
2 ^4 I2 J1 K# m/ K6 O#ifdef CONFIG_PROC_FS
  l1 L+ b  a- V9 U9 I' L* L4 C    /* store ref to file /proc/<pid>/exe symlink points to */
, C" ?' j0 \7 f& |: I' c4 W8 C! d6 M3 d    struct file *exe_file;
- ^; ^: b5 @& v7 l( V- G    unsigned long num_exe_file_vmas;
- f0 A' l! j* p* a/ S6 X#endif) G% V) x4 e0 G9 P& ^( H
#ifdef CONFIG_MMU_NOTIFIER
% Z6 h& L4 D' Y' Z% ]- S1 P    struct mmu_notifier_mm *mmu_notifier_mm;
) R! [& ?! n3 O#endif
' M3 u  E( g0 W8 B7 R};复制代码: s8 G" M5 D! M; j3 Z( L

, L# H1 O+ E- l9 l- L; ?# [9 M) I- E& i+ r  b2 `. G* @0 D
补充说明1: 上面的属性中,mm_users 和 mm_count 很容易混淆,这里特别说明一下:(下面的内容有网上查找的,也有我自己理解的)7 o. B7 A8 B" ]
$ ]1 r, T8 p7 ^' `$ c- m
mm_users 比较好理解,就是 mm_struct 被用户空间进程(线程)引用的次数。* i% N: }# n* }
8 x  J; @' M' b5 F  K. [" U
如果进程A中创建了3个新线程,那么 进程A(这时候叫线程A也可以)对应的 mm_struct 中的 mm_users = 4
: w) o5 {. d2 k8 _) Z& J! _' Z( j2 g! g8 p

. e; S, B7 r+ Y* M- x4 d2 Q- L& v7 h& B0 v/ s8 _0 m
补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。
8 W" w- l( c* ]  w+ a% ?: ?( b( y8 s, h2 g6 M  n7 n+ }
所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果父子进程没有共享进程地址空间,就是2个进程
9 S0 [6 }8 X* U6 `! }, o! T+ R# m% ~- D9 b3 |3 S3 Z

# J/ |, n; |, _1 B  o
. U8 U2 V/ S' X1 ]3 K) A; Umm_count 则稍微有点绕人,其实它记录就是 mm_struct 实际的引用计数。# u4 K& _9 v9 ~1 ~+ L
9 q& C& _7 o4 i8 m; B( Z2 O
简单点说,当 mm_users=0 时,并不一定能释放此 mm_struct,只有当 mm_count=0 时,才可以确定释放此 mm_struct
/ E' A/ ]/ w# R: J2 L3 P6 M/ e2 B) W7 D2 K. b! h# J  J
# W/ M! l$ d, p& ^% w+ G; G

% S% C$ h! e: Y9 f- ?- @+ g从上面的解释可以看出,可能引用 mm_struct 的并不只是用户空间的进程(线程)6 z' g1 F" z; x# D

! C8 K+ Z/ g2 X! G/ r  T当 mm_users>0 时, mm_count 会增加1, 表示有用户空间进程(线程)在使用 mm_struct。不管使用 mm_struct 的用户进程(线程)有几个, mm_count 都只是增加1。
# r5 N1 ~6 z) m; i2 [! I2 a: ?0 I3 o2 M6 O, b
也就是说,如果只有1个进程使用 mm_struct,那么 mm_users=1,mm_count也是 1。
7 E! M) L" M' F6 _
' e5 p0 n6 }) V0 G' H如果有9个线程在使用 mm_struct,那么 mm_users=9,而 mm_count 仍然为 1。/ J; q& _1 F) ^; n  b$ M" X

; y8 G' D! {4 c. t- d( d% c) s  j0 }
4 y$ w/ I  y  H0 |5 L3 P0 a, J9 [2 V: E) V
那么 mm_count 什么情况下会大于 1呢?
3 |6 w9 P& y- V0 Z2 [4 T# G, n, ~, I/ t5 O4 p
当有内核线程使用 mm_struct 时,mm_count 才会再增加 1。' `" q. _* j* B- o- W. j$ i8 R
) v$ Q9 b) e& W7 b4 E. J
内核线程为何会使用用户空间的 mm_struct 是有其他原因的,这个后面再阐述。这里先知道内核线程使用 mm_struct 时也会导致 mm_count 增加 1。
+ v. V8 _6 a4 |/ H. k/ W& ^/ c+ v$ ^9 ~* d! j+ \: j9 r
在下面这种情况下,mm_count 就很有必要了:# \$ f9 Y8 i- Q( g' l

/ }+ K0 v$ d4 `# k
  • - 进程A启动,并申请了一个 mm_struct,此时 mm_users=1, mm_count=1
  • - 进程A中新建了2个线程,此时 mm_users=3, mm_count=1
  • - 内核调度发生,进程A及相关线程都被挂起,一个内核线程B 使用了进程A 申请的 mm_struct,此时 mm_users=3, mm_count=2
  • - CPU的另一个core调度了进程A及其线程,并且执行完了进程A及其线程的所有操作,也就是进程A退出了。此时 mm_users=0, mm_count=1
  •   在这里就看出 mm_count 的用处了,如果只有 mm_users 的话,这里 mm_users=0 就会释放 mm_struct,从而有可能导致 内核线程B 异常。
  • - 内核线程B 执行完成后退出,这时 mm_users=0,mm_count=0,可以安全释放 mm_struct 了
    / F. Z- S8 `6 ~' u" _$ A

6 L; I+ f& F5 L3 i
, ~' g9 B$ P' n, ~. o# Y补充说明2:为何内核线程会使用用户空间的 mm_struct?
0 v. S2 v1 w3 B/ s
9 z7 H' T; s' U$ k8 m对Linux来说,用户进程和内核线程都是task_struct的实例,
* w$ f( E3 e# V$ S7 R
% f- ^2 U& o  x, {唯一的区别是内核线程是没有进程地址空间的(内核线程使用的内核地址空间),内核线程的mm描述符是NULL,即内核线程的tsk->mm域是空(NULL)。
+ f  V3 B0 w! o
( c! F; A- j$ q0 l内核调度程序在进程上下文的时候,会根据tsk->mm判断即将调度的进程是用户进程还是内核线程。
( F1 q8 L6 `, ]8 C$ C5 y/ Y4 F! g* m
但是虽然内核线程不用访问用户进程地址空间,但是仍然需要页表来访问内核自己的空间。4 C9 Y2 O% [8 e8 c( m
( x- m4 Y2 x' N1 c
而任何用户进程来说,他们的内核空间都是100%相同的,所以内核会借用上一个被调用的用户进程的mm_struct中的页表来访问内核地址,这个mm_struct就记录在active_mm。1 ?# |( B& Y! B3 U- C' F2 @# z

( K) }& w7 x# I  \6 P2 A
+ g. r- U; X% D5 {% k5 v7 c
9 D' X- L$ f% v# I! ~# z5 {! @* ]简而言之就是,对于内核线程,tsk->mm == NULL表示自己内核线程的身份,而tsk->active_mm是借用上一个用户进程的mm_struct,用mm_struct的页表来访问内核空间。* B4 `6 K7 y; ]7 m7 z' x5 `

9 Z5 `. m- U5 t& \: n  T- I对于用户进程,tsk->mm == tsk->active_mm。
8 U# V5 L: D! L% V! v" E7 }3 k% ~4 X- l; M" D! [/ ^

$ H6 X; k5 y/ |$ U' c
( Z# s# _8 B; m& R3 `补充说明3:除了 mm_users 和 mm_count 之外,还有 mmap 和 mm_rb 需要说明以下:
/ f, I2 I2 j% Z. V8 ?- S% J4 G& ~8 w( z: G8 |. J
其实 mmap 和 mm_rb 都是保存此 进程地址空间中所有的内存区域(VMA)的,前者是以链表形式存放,后者以红黑树形式存放。3 |2 J- L6 s. c3 Z
) H6 V+ T0 ]4 U
用2种数据结构组织同一种数据是为了便于对VMA进行高效的操作。
( Q# V) `: _: f8 X. w6 S( p8 ~, _0 G6 @% a

+ u- e3 C- k$ _" m8 F5 T4 m; D( e* ^: y2 d0 V
1.2 mm_struct操作1 R4 ?; K4 a1 q* K4 {: \4 A8 S
1. 分配进程地址空间% N# s  M4 u& l, H* @

9 W$ d' @0 f1 J参考 kernel/fork.c 中的宏 allocate_mm
0 C7 s( P' |3 ~+ N, R
8 }2 I. L* @/ \' X7 ^; w0 s8 V#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
& i8 p8 F/ U1 y  G8 R#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm)))
& E$ h0 }$ D" g- Z3 o* R 5 C) G: L( F+ N% T0 n

$ ?! |& K4 g' g' `" `, B其实分配进程地址空间时,都是从slab高速缓存中分配的,可以通过 /proc/slabinfo 查看 mm_struct 的高速缓存
8 c& p& ~8 \. w. D2 c( |- _9 e# x/ c1 X& B) k/ H
# cat /proc/slabinfo | grep mm_struct
% O( \7 F3 H/ T3 q0 Emm_struct             35     45   1408    5    2 : tunables   24   12    8 : slabdata      9      9      0+ K& N* ?8 y$ r. a/ R; M3 X

* h( ^3 L9 L: s, u* h, Z2 t9 A
. J% @- z, a3 ?& p: ]" @; ]% M2. 撤销进程地址空间
$ W# o9 P! K1 w+ r3 W* ]+ ?2 x6 o1 Q5 g: n( q
参考 kernel/exit.c 中的 exit_mm() 函数. ~* o$ ?; c! b! }

  A, w5 C# l1 s该函数会调用 mmput() 函数减少 mm_users 的值,
0 M+ W+ e7 J2 B' @! s& ~
1 \8 q$ _7 i- p" C1 V9 Y1 r- |当 mm_users=0 时,调用 mmdropo() 函数, 减少 mm_count 的值,) o) A# G) M1 w  ?1 Q

8 |( Y$ N2 |8 U; {# l如果 mm_count=0,那么调用 free_mm 宏,将 mm_struct 还给 slab高速缓存  P; B4 f" }6 p( @  i  H/ i  Q

4 w) Y: b. B" d9 p6 A! Q : @7 n$ V. O0 W3 T& ~
6 N# c: i' v, |
3. 查看进程占用的内存:; J9 i/ o& I" A0 u8 Z/ Q
  ^6 ]& A" e" W4 v8 \
cat /proc/< PID>/maps5 u$ x2 F+ E8 a
或者
7 r3 z$ o7 m$ d+ ~' {pmap PID& `' |- d( q& j4 \- n$ j1 o
7 g8 B: n# l6 J4 ?1 D. z9 a. K; p
8 G9 ]" U- n: `! f+ q& Q
2. 虚拟内存区域(VMA)  X. B& a" Y0 M; O1 @
内存区域在linux中也被称为虚拟内存区域(VMA),它其实就是进程地址空间上一段连续的内存范围。
: F/ @2 i+ I9 J8 Z7 B( T7 W% U5 ~* d8 U6 ]: y& H4 V1 j
; B6 ?! a2 t& O4 m( R

+ z. P6 W" s* l2.1 VMA介绍
8 c- v- J: G  e. K4 B2 yVMA的定义也在 <linux/mm_types.h> 中" U3 b* n6 C( [1 _+ F; n
4 `" ]4 U: [; n. h8 e* R
复制代码
" w5 B7 `; t2 V( B' j6 k7 `struct vm_area_struct {
/ D- _) y2 p% I! T    struct mm_struct * vm_mm;    /* 相关的 mm_struct 结构体 */* S, Z& l6 m- s" L1 H' v3 _
    unsigned long vm_start;        /* 内存区域首地址 */
: F/ H1 P  r+ u/ b    unsigned long vm_end;        /* 内存区域尾地址 */  }4 P$ v+ M! t/ r% e* l
8 C) q3 V! v0 b; H
    /* linked list of VM areas per task, sorted by address */) z( ~* }/ z0 w- [! {: R- v4 ^
    struct vm_area_struct *vm_next, *vm_prev;  /* VMA链表 */
3 r2 u" N: ]) |1 A3 m4 ^( M& _7 g9 c" @8 r7 E
    pgprot_t vm_page_prot;        /* 访问控制权限 */
6 ?) U+ O% |# z) ]    unsigned long vm_flags;        /* 标志 */5 ~  M) H# Z+ b
  K+ R/ z* L( E5 q, V
    struct rb_node vm_rb;       /* 树上的VMA节点 */
1 L9 S: S6 f8 ?6 c7 B: |* F+ q. b+ ~; M# F0 x: X" E7 M1 j. B
    /*0 z. _/ j! t4 l( i! X) t- L* }+ e: o
     * For areas with an address space and backing store,
% A+ I8 q3 j" C. @- |     * linkage into the address_space->i_mmap prio tree, or
: {# w7 D6 G! h. o% ^5 e0 b     * linkage to the list of like vmas hanging off its node, or
' q, P1 @- c  o: `     * linkage of vma in the address_space->i_mmap_nonlinear list.
4 m+ A& b; F1 \4 S( S* O1 f     */
" ]. B' W. U; a/ m9 r    union {
0 l) D1 y0 Q& o0 `  u        struct {; U0 ^* Q6 Q& L! F- Y) l
            struct list_head list;5 w: @+ H* _6 d* N: K' w
            void *parent;    /* aligns with prio_tree_node parent */
# \* H5 Y+ V/ D6 X3 J            struct vm_area_struct *head;
: Z0 V" @4 e5 L: I0 @        } vm_set;
/ d# e$ l) P0 [  c7 k* @$ q/ A( Z+ D& M4 T, n
        struct raw_prio_tree_node prio_tree_node;
1 {* `3 A; ]: m2 I: H9 a    } shared;) Q% _! ?( C3 B# _( ]/ O
$ b! Z+ i& _$ Z2 E
    /*$ s0 X" o" K# i" L$ v# D, `2 _
     * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma  P7 X: x8 d5 a( a. E: x) }
     * list, after a COW of one of the file pages.    A MAP_SHARED vma* @+ x  J7 [% x7 O2 m, D
     * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
, D) o( L2 _1 \% O     * or brk vma (with NULL file) can only be in an anon_vma list.
- |  v, h: `7 ]$ t- C& h- A     */
: _3 l5 ?  L& @4 |    struct list_head anon_vma_node;    /* Serialized by anon_vma->lock */! R4 J! a: l' v1 z$ L4 Z, [9 _
    struct anon_vma *anon_vma;    /* Serialized by page_table_lock */  p- t* i- s) o
2 L! W; u0 v3 ~  `2 k' k, l
    /* Function pointers to deal with this struct. */
0 R/ _: }0 g( b; L9 u$ r    const struct vm_operations_struct *vm_ops;: @: R5 ~0 ~+ [

; b, x5 g! a. b    /* Information about our backing store: */
- j# t. B( l. u  L% w    unsigned long vm_pgoff;        /* Offset (within vm_file) in PAGE_SIZE* q; b* s. d3 Q& ]  B7 X
                       units, *not* PAGE_CACHE_SIZE */
% ]0 A' r  G; o/ w# V    struct file * vm_file;        /* File we map to (can be NULL). */
7 D) @) v' A0 q' }8 g$ r    void * vm_private_data;        /* was vm_pte (shared mem) */
+ u$ [# s7 F4 u# W4 y    unsigned long vm_truncate_count;/* truncate_count or restart_addr */" R' w* {$ @2 |/ x! [
4 F& K0 V( }" F0 f
#ifndef CONFIG_MMU4 H: w5 z; i: P% s' A
    struct vm_region *vm_region;    /* NOMMU mapping region */
5 F: U" Q  y* |1 L0 s3 B#endif$ n: H' [- e% n
#ifdef CONFIG_NUMA" C( K' l0 {+ S, |! e
    struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
  K: u) [& B  X! M2 J: f% e. |#endif  j* \& C( T# u2 i1 ~
};
; n6 K& e$ k% o# {5 A复制代码" Z# s4 z4 R( I3 Z

( r2 n: k+ x5 e8 }" P8 U  f/ H! N2 l. [. i# B! [7 S4 X0 q
这个结构体各个字段的英文注释都比较详细,就不一一翻译了。3 \; L3 ~, x+ z% h$ O

1 p; M( t" N' v# |1 @上述属性中的 vm_flags 标识了此VM 对 VMA和页面的影响:
! p( p. D9 L; \( ~. a) G2 z8 |7 ^7 G
4 j& Q9 A: y$ |4 x4 Wvm_flags 的宏定义参见 <linux/mm.h>* c- L/ {' G4 d1 |/ k( Q

' j* E4 i! W) \, E# m8 e
标志
对VMA及其页面的影响
VM_READ页面可读取
VM_WRITE页面可写
VM_EXEC页面可执行
VM_SHARED页面可共享
VM_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该区域是非线性映射的
9 l. f$ B( b/ T/ B! ^0 e4 J

9 |7 k2 H5 e5 u6 W
  L1 k4 k8 b- D# G1 Y2.2 VMA操作
1 w9 [7 e4 S/ V- }" y$ lvm_area_struct 结构体定义中有个 vm_ops 属性,其中定义了内核操作 VMA 的方法" Y  C9 c) J- P+ S' a

$ ], b! p. ]- s1 x/ B2 K复制代码
/ R) ^# G/ E+ S9 o4 c/*- W. U5 D- H$ m  B4 [% W! |6 O7 \! ]
* These are the virtual MM functions - opening of an area, closing and
) m) L% [" R* c9 w& g# ?8 j * unmapping it (needed to keep files on disk up-to-date etc), pointer1 y- v3 U$ q  _) f
* to the functions called when a no-page or a wp-page exception occurs.
9 Z- U  q# v3 Y6 ]: H  Y */5 u7 f/ @( b  @- o
struct vm_operations_struct {: c5 ^" s; N7 b7 ^5 k6 L
    void (*open)(struct vm_area_struct * area);  /* 指定内存区域加入到一个地址空间时,该函数被调用 */5 C2 D. w) F2 o- @3 z6 ^0 @
    void (*close)(struct vm_area_struct * area); /* 指定内存区域从一个地址空间删除时,该函数被调用 */( p, B: [' T8 F7 Q
    int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf); /* 当没有出现在物理页面中的内存被访问时,该函数被调用 */9 W% j9 j1 H( n2 A3 B* h
9 i4 F% ^4 |) j
    /* 当一个之前只读的页面变为可写时,该函数被调用,, F0 G# G5 U6 {! g" R$ b) x
     * 如果此函数出错,将导致一个 SIGBUS 信号 */% L  n) o6 _$ M: g9 U. o& s
    int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);8 S9 r, m8 q& ~: O

5 w# R$ m) N8 m: X1 I    /* 当 get_user_pages() 调用失败时, 该函数被 access_process_vm() 函数调用 */' s( [6 X* O2 m# D3 w
    int (*access)(struct vm_area_struct *vma, unsigned long addr,
& y5 @' t4 P8 {; x4 t- i              void *buf, int len, int write);
9 ?" s# i6 |* b1 [' k#ifdef CONFIG_NUMA  y8 D: {1 A$ R
    /*
9 c( g2 W- b+ J( ~- ~) }8 O     * set_policy() op must add a reference to any non-NULL @new mempolicy
5 S; e$ t/ U& H2 C! z' H     * to hold the policy upon return.  Caller should pass NULL @new to
/ t- j& g+ U6 ~( P3 X+ g/ j     * remove a policy and fall back to surrounding context--i.e. do not
" L8 Z7 H; o$ Y, Y  F* O     * install a MPOL_DEFAULT policy, nor the task or system default
) ?9 i+ W9 r! A$ }; q3 _     * mempolicy.
" O. l4 |  t! a# P     */
; A5 H; H/ D4 Q0 `7 e$ q' E4 L    int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);0 q7 ^5 b) D6 b5 r
: D& S5 x1 d) p: l- K: [3 S3 o
    /*4 j& L3 F# E* w& h$ t
     * get_policy() op must add reference [mpol_get()] to any policy at
% c. J2 B" D9 s" d1 P& ^     * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure& m7 M; m) h$ E
     * in mm/mempolicy.c will do this automatically.- X/ [& ]' j# u, n, B' l
     * get_policy() must NOT add a ref if the policy at (vma,addr) is not2 _; V8 E1 h6 m8 }
     * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
  |, ]  N% t$ l7 F; i! y9 `/ E     * If no [shared/vma] mempolicy exists at the addr, get_policy() op
8 d! S1 X4 B6 Q: C7 @# b& l* R     * must return NULL--i.e., do not "fallback" to task or system default" E, _9 Z- d9 W5 g! W0 ?, e
     * policy.
; W( @; a* _8 M4 C     */
' _% N, A9 U% X  f    struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
4 ]& {  A7 r: [" a+ t( G! q                    unsigned long addr);
3 m1 o% F6 k3 O. t    int (*migrate)(struct vm_area_struct *vma, const nodemask_t *from,
5 [' P# u! G% s, B9 p. ~4 g        const nodemask_t *to, unsigned long flags);
2 I, c3 a- R" K3 X+ [* z( Y#endif
- i6 j8 u8 g! h% L. v2 g};
( J1 H$ K0 s" G# Z: x6 s复制代码( i" {( C& y1 @
  s4 A+ t0 \0 ~
9 W8 d$ m% }7 `& T
除了以上的操作之外,还有一些辅助函数来方便内核操作内存区域。% S7 s! a. y* k
& }* w" n' f" T
这些辅助函数都可以在 <linux/mm.h> 中找到  C1 J( g. n) u1 l& K: a  w

5 k! G3 B/ G* o4 K1. 查找地址空间! C+ ]! B$ G4 t$ H) w

. l* u" \$ K6 p复制代码2 ]2 \& W; v5 z! [+ @  C9 G+ J4 _
/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */7 J  D0 ~7 b" {' i7 s
extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);
- ]7 g" o6 _( W" V& eextern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,
. `3 w1 X0 g4 j% l; Z; Z. v! X7 O                         struct vm_area_struct **pprev);
2 _' C! }( O4 v; d8 M
5 L; B* ~% r* C6 C: R( ]/* Look up the first VMA which intersects the interval start_addr..end_addr-1,
$ X0 ^, G! R, |" B; ~( K" S   NULL if none.  Assume start_addr < end_addr. */  r3 {. q+ I/ D( f- W7 P
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
; J. S$ N) N$ Q9 z{
6 ^5 j; F' c( d: P. R    struct vm_area_struct * vma = find_vma(mm,start_addr);5 E3 l$ P$ r# w

+ W9 @1 G/ y) y4 f    if (vma && end_addr <= vma->vm_start)3 W' K! Z/ Y/ Z2 H6 |
        vma = NULL;
8 x4 v6 G" ?* d. e8 A8 G, c    return vma;7 z% g2 N7 e! V5 v% D5 y( N6 r+ p0 T
}
' M$ o4 g; d' Z0 {& o) ]3 k, T复制代码! e5 H) \! N) L# q
! K2 O2 O& Q3 e

6 f, r0 W; [" B1 ~3 G2. 创建地址区间0 i; p, ?) `5 c8 u( ]) w# P( G( e

9 y  a, F) l; ?# Q# Q, R; p复制代码
: f* h  s" u! W, K. estatic inline unsigned long do_mmap(struct file *file, unsigned long addr,
- }$ v7 g9 Z. t: G0 y) ~    unsigned long len, unsigned long prot,7 b% T: c; K- x" H3 Q
    unsigned long flag, unsigned long offset)* i& P" v3 V, |: P2 e: V
{! y+ W; r* r1 v( O* f1 O9 m
    unsigned long ret = -EINVAL;3 s# R5 s, ?" p
    if ((offset + PAGE_ALIGN(len)) < offset)
/ q9 t0 A3 Y( Y5 _4 K        goto out;
0 ~' h* a# G, u. h    if (!(offset & ~PAGE_MASK))2 Q- y! j. J% s! P4 P% X
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);% D! @6 c6 y3 i' Z' W# T  O
out:
+ S% l2 O* T8 F' E0 s; `5 ~    return ret;
& p) u( o8 M  ?3 j, _! r}
3 p% D0 d/ t3 g' W3 {) \$ \复制代码9 P0 I) c+ F: q  D! M

/ ?- y+ C, v) i7 Y& L* w
) W5 ]+ E& q  D# t5 C/ b. ]3. 删除地址区间
9 n( j# `+ g  t7 z6 D$ M2 l. X# B5 ~( N
extern int do_munmap(struct mm_struct *, unsigned long, size_t);
( o# s; d3 _) ]$ Z
. w0 [# ]$ J  r; S; P
& T$ p7 @8 L. ^2 _$ I) u! C, j3. 地址空间和页表  Z  c! A: d1 ^1 U
地址空间中的地址都是虚拟内存中的地址,而CPU需要操作的是物理内存,所以需要一个将虚拟地址映射到物理地址的机制。
9 F' u1 I# k. k
" j9 V* O# c+ t' L% q0 d" C' f这个机制就是页表,linux中使用3级页面来完成虚拟地址到物理地址的转换。
3 G% f- X; a. e# |/ ^
: Y6 _% @( y* I5 i2 x1. PGD - 全局页目录,包含一个 pgd_t 类型数组,多数体系结构中 pgd_t 类型就是一个无符号长整型: {3 ?6 M. R. A: t0 e

6 o. ^2 N. \3 M2. PMD - 中间页目录,它是个 pmd_t 类型数组
3 D7 [% T, N% ~/ ~: {( j9 W4 u
; p9 D$ x' f/ R% ~- C3 |3. PTE - 简称页表,包含一个 pte_t 类型的页表项,该页表项指向物理页面
3 v5 Q- e% j. e" [5 M
1 J% [( z$ E8 B' h( [" O
4 i" y7 a" E+ Z; t5 k2 I3 G
( U3 m9 o' b  Q" z' V9 y+ A虚拟地址 - 页表 - 物理地址的关系如下图:; [8 T! K* k% ?5 A) U! I
7 Y. ^+ N2 S9 k  p; F  T: G
/ L4 o, R& N; L7 i3 ~3 Y1 m6 j

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-24 15:57 , Processed in 0.187500 second(s), 27 queries , Gzip On.

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

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

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