task_struct 结构,mm_struct 结构, vm_area_struct 结构

1、task_struct

每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

  • 位置:<include\linux\sched.h> - 593行
  • 部分代码如下:
struct task_struct
{
    /*...*/
	struct mm_struct		*mm;
	struct mm_struct		*active_mm;

	/* Per-thread vma caching: */
	struct vmacache			vmacache;
    /*...*/
}
  • *mm
    mm为mm_struct类型的指针,指向mm_struct结构,对于普通的用户进程来说mm字段指向他的虚拟地址空间的用户空间部分,对于内核线程来说这部分为NULL。
  • *active_mm
    对于匿名进程,也可以理解为内核线程,它的mm字段为NULL,表示没有内存地址空间,可也并不是真正的没有,这是因为所有进程关于内核的映射都是一样的,内核线程可以使用任意进程的地址空间。
    内核schedule在进程上下文切换的时候,会根据task->mm判断即将调度的进程是用户进程还是内核线程。
    由于内核线程之前可能是任何用户层进程在执行,故用户空间部分的内容本质上是随机的,内核线程决不能修改其内容,故将mm设置为NULL。所以对于内核线程来说task_struct->mm == NULL,而task_struct->active_mm为某个进程的mm。
    如果切换出去的是用户进程,内核将原来进程的mm存放在新内核线程的active_mm中,因为某些时候内核必须知道用户空间当前包含了什么。
    而当内核线程离开的时候,会把借用的地址空间将会返还给原来的进程,并且清除这一字段。
  • vmcacahe
    vmacache结构体定义在<include\linux\mm_types_task.h>的34行,如下:
#define VMACACHE_BITS 2
#define VMACACHE_SIZE (1U << VMACACHE_BITS)

struct vmacache {
  u64 seqnum;
  struct vm_area_struct *vmas[VMACACHE_SIZE];
};

可以看到它是一个vm_area_struct类型的数组指针,表示指向几段vma。
这是由于为了提高vma的查找速度,由传统的链表式改为了红黑树管理,然而某些进程地址空间中的vma数量众多,而查找vma又是内核中非常频繁的操作,所以为了进一步加快查找速度,内核采取了一种缓存方式,就是把最近访问的几个vma保存起来。
VMACACHE_SIZE为左移2位的一个无符号整形数字,这里感觉大概是保存了4个最近访问的vma。

2、mm_struct

每一个进程都会有唯一的mm_struct结构体

  • 位置:<include\linux\mm_types.h> - 340行
  • 部分代码如下:
struct mm_struct
{
    /*...*/
    struct vm_area_struct *mmap;		/* list of VMAs */
	struct rb_root mm_rb;
	u64 vmacache_seqnum;                /* per-thread vmacache */
    unsigned long mmap_base;	/*映射基地址*/
	unsigned long mmap_legacy_base;	/*不是很明白这里*/
    unsigned long task_size;	/*该进程能够vma使用空间大小*/
	unsigned long highest_vm_end;	/*该进程能够使用的vma结束地址*/
	pgd_t * pgd;
    atomic_t mm_users;
    atomic_t mm_count;
    int map_count;			/* vma的总个数 */
    unsigned long total_vm;	   /* 映射的总页面数*/
    /*...*/
    unsigned long start_code, end_code, start_data, end_data;
	unsigned long start_brk, brk, start_stack;
	unsigned long arg_start, arg_end, env_start, env_end;
    /*...*/
  • *mmap
    该进程内已经使用的进程虚拟空间vm_are_struct结构,以双链表形式存储该指针指向vma链表的头节点。
  • mm_rb
    红黑树组织形式,用于快速查找。
    rb_boot结构体定义在<include\linux\rbtree.h>的43行
struct rb_root {
  struct rb_node *rb_node;
};

rb_node结构体定义在<include\linux\rbtree.h>的36行

struct rb_node {
  unsigned long  __rb_parent_color;
  struct rb_node *rb_right;
  struct rb_node *rb_left;
  } __attribute__((aligned(sizeof(long))));

可以看到该数据结构包含了父节点颜色、左子树指针、右子树指针等字段

  • vmacache_seqnum
    vma的缓存策略所使用的字段,表示每个进程都有的几个缓存vma,对应task_struct->vmacache->seqnum。
  • *pgd
    该进程页目录表,把新进程mm_struct的PGD字段填充到CR3中就完成了页表的切换。
  • mm_users、mm_count
    为了支持mm与active_mm,在这里设置了两个计数器,mm_users主要用来记录共用此命名空间的线程数量,mm_count则主要记录对此mm_struct结构体的引用情况。
    也就是说mm_users表示有多少真正使用地址空间的进程,当copy_mm函数在创建进程的时候,遇到CLONE_VM字段的话就再到需要让父子进程共享同一个地址空间,这时会把mm_users加1。而mm_count则表示有多少内核线程引用了这个地址空间。
    当mm_users为0时就会释放mm_count,当mm_count为0时结构体mm_struct将会被释放。
3、vm_area_struct

内核每次为用户空间中分配一个空间使用时,都会生成一个vm_are_struct结构用于记录跟踪分配情况,一个vm_are_struct就代表一段虚拟内存空间。

  • 位置:<include\linux\mm_types.h> - 264行
  • 全部代码如下:
struct vm_area_struct {
	unsigned long vm_start; //虚存区起始
	unsigned long vm_end;   //虚存区结束
	struct vm_area_struct *vm_next, *vm_prev;   //前后指针
	struct rb_node vm_rb;   //红黑树中的位置
	unsigned long rb_subtree_gap;
	struct mm_struct *vm_mm;    //所属的 mm_struct
	pgprot_t vm_page_prot;      
	unsigned long vm_flags;     //标志位
	struct {
		struct rb_node rb;
		unsigned long rb_subtree_last;
	} shared;	
	struct list_head anon_vma_chain;
	struct anon_vma *anon_vma;
	const struct vm_operations_struct *vm_ops;  //vma对应的实际操作
	unsigned long vm_pgoff;     //文件映射偏移量
	struct file * vm_file;      //映射的文件
	void * vm_private_data;     //私有数据
	atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
	struct vm_region *vm_region;	/* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
	struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
	struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;
  • *vm_ops
    vm_operations_struct结构体定义在<include\linux\mm.h>的393行
struct vm_operations_struct {
  void (*open)(struct vm_area_struct * area);
  void (*close)(struct vm_area_struct * area);
  int (*split)(struct vm_area_struct * area, unsigned long addr);
  int (*mremap)(struct vm_area_struct * area);
  vm_fault_t (*fault)(struct vm_fault *vmf);
  vm_fault_t (*huge_fault)(struct vm_fault *vmf,
  		enum page_entry_size pe_size);
  void (*map_pages)(struct vm_fault *vmf,
  		pgoff_t start_pgoff, pgoff_t end_pgoff);
  unsigned long (*pagesize)(struct vm_area_struct * area);

  /* notification that a previously read-only page is about to become
   * writable, if an error is returned it will cause a SIGBUS */
  vm_fault_t (*page_mkwrite)(struct vm_fault *vmf);

  /* same as page_mkwrite when using VM_PFNMAP|VM_MIXEDMAP */
  vm_fault_t (*pfn_mkwrite)(struct vm_fault *vmf);

  /* called by access_process_vm when get_user_pages() fails, typically
   * for use by special VMAs that can switch between memory and hardware
   */
  int (*access)(struct vm_area_struct *vma, unsigned long addr,
  	      void *buf, int len, int write);

  /* Called by the /proc/PID/maps code to ask the vma whether it
   * has a special name.  Returning non-NULL will also cause this
   * vma to be dumped unconditionally. */
  const char *(*name)(struct vm_area_struct *vma);

  #ifdef CONFIG_NUMA
  /*
   * set_policy() op must add a reference to any non-NULL @new mempolicy
   * to hold the policy upon return.  Caller should pass NULL @new to
   * remove a policy and fall back to surrounding context--i.e. do not
   * install a MPOL_DEFAULT policy, nor the task or system default
   * mempolicy.
   */
  int (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);

  /*
   * get_policy() op must add reference [mpol_get()] to any policy at
   * (vma,addr) marked as MPOL_SHARED.  The shared policy infrastructure
   * in mm/mempolicy.c will do this automatically.
   * get_policy() must NOT add a ref if the policy at (vma,addr) is not
   * marked as MPOL_SHARED. vma policies are protected by the mmap_sem.
   * If no [shared/vma] mempolicy exists at the addr, get_policy() op
   * must return NULL--i.e., do not "fallback" to task or system default
   * policy.
   */
  struct mempolicy *(*get_policy)(struct vm_area_struct *vma,
  				unsigned long addr);
  #endif
  /*
   * Called by vm_normal_page() for special PTEs to find the
   * page for @addr.  This is useful if the default behavior
   * (using pte_page()) would not find the correct page.
   */
  struct page *(*find_special_page)(struct vm_area_struct *vma,
  				  unsigned long addr);
  };

vm_operations结构中包含的是函数指针,其中open为虚存区的打开,close用于虚存区的关闭,另外还有split、mremap、acess等操作。

  • shared
    对于具有地址空间和后备存储的区域,链接到地址空间的i_mmap间隔树。
  • rb_subtree_gap
    该vma子树最大可用内存间隔(以字节为单位),具体为与vma和vma->vm_prev之间,或子vma和它的vma->vm_prev之间的内存间隙,有助于找到大小合适的空闲区域。
  • vm_flags
    描述该区域的一段标志,包括其读写属性等,采用bit位方式,状态标志位定义在<include\linux\mm.h>文件173行中:
#define VM_NONE		0x00000000  //无标志

  #define VM_READ		0x00000001	//可读
  #define VM_WRITE	0x00000002  //可写
  #define VM_EXEC		0x00000004  //可操作
  #define VM_SHARED	0x00000008  //允许被多个线程访问

  /* mprotect() hardcodes VM_MAYREAD >> 4 == VM_READ, and so for r/w/x bits. */
  #define VM_MAYREAD	0x00000010	//允许设置可读
  #define VM_MAYWRITE	0x00000020	//允许设置可写  
  #define VM_MAYEXEC	0x00000040	//允许设置可操作
  #define VM_MAYSHARE	0x00000080	//允许设置可共享

  #define VM_GROWSDOWN	0x00000100	//向低地址增长
  #define VM_UFFD_MISSING	0x00000200	
  #define VM_PFNMAP	0x00000400	
  #define VM_DENYWRITE	0x00000800	//不允许写入
  #define VM_UFFD_WP	0x00001000	
  #define VM_LOCKED	0x00002000	//VMA被锁定,不会被交换到交换分区
  #define VM_IO           0x00004000	//该VMA用于IO 映射

  #define VM_SEQ_READ	0x0000800	//应用程序会顺序读该VMA的内容
  #define VM_RAND_READ	0x00010000	//应用程序会随机读取该VMA内存
  #define VM_DONTCOPY	0x00020000	//fork时不会复制该VMA
  #define VM_DONTEXPAND	0x00040000	
  #define VM_LOCKONFAULT	0x00080000	//当处于page fault时锁定该VMA 对应的物理页
  #define VM_ACCOUNT	0x00100000	
  #define VM_NORESERVE	0x00200000	//不做保留
  #define VM_HUGETLB	0x00400000	//huge TLB page对应的VM
  #define VM_SYNC		0x00800000	//page fault时同步映射
  #define VM_ARCH_1	0x01000000	
  #define VM_WIPEONFORK	0x02000000	//不会从父进程相应的VMA中复制页表到子进程的VMA中
  #define VM_DONTDUMP	0x04000000	//VMA不会被包含到core dump中

  #ifdef CONFIG_MEM_SOFT_DIRTY
  # define VM_SOFTDIRTY	0x08000000	
  #else
  # define VM_SOFTDIRTY	0
  #endif

  #define VM_MIXEDMAP	0x10000000	
  #define VM_HUGEPAGE	0x20000000	
  #define VM_NOHUGEPAGE	0x40000000	
  #define VM_MERGEABLE	0x80000000
  • vm_page_prot
    vm_flags代表VMA的状态位,但是相应的状态位最后要转化成实际内存的下发到硬件状态位,而pgprot则是代表实际物理页状态位。
4、关系图

wm_task_removed 含义 task_struct mm_#define