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、关系图