进程地址空间
概念
在 Linux 系统中,采用了虚拟内存管理技术,事实上大多数现在操作系统都是如此!在 Linux 系统中,每一个进程都在自己独立的地址空间中运行。
虚拟地址会通过硬件 MMU(内存管理单元)映射到实际的物理地址空间中,建立虚拟地址到物理地址的映射关系后,对虚拟地址的读写操作实际上就是对物理地址的读写操作,MMU 会将物理地址“翻译”为对应的物理地址,其关系如下所示:
那么,为什么要引入虚拟地址呢?引入虚拟地址有什么优点呢?
struct task_struct (进程的PCB)
太长了,就不放在这里了
几种状态:
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000//进程要么正在执行,要么准备执行,内核中有一个队列,里面都是等待执行的进程。
#define TASK_INTERRUPTIBLE 0x0001 //可中断的睡眠,可以通过一个信号唤醒
#define TASK_UNINTERRUPTIBLE 0x0002//不可中断睡眠,不可以通过信号进行唤醒
#define __TASK_STOPPED 0x0004 //进程停止执行,在进程接收到 SIGTTIN、SIGSTOP、SIGTSTP、STGTTOU信号后会进入此状态
#define __TASK_TRACED 0x0008//进程被追踪,被 debugger 等进程监视。
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010//僵尸状态的进程,表示进程被终止,但是父进程还没有获取它的终止信息,比如进程有没有执行完等信息。
#define EXIT_ZOMBIE 0x0020//进程的最终状态,进程死亡
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040//停放状态
#define TASK_DEAD 0x0080//死亡,最终状态
#define TASK_WAKEKILL 0x0100//唤醒并杀死的进程
#define TASK_WAKING 0x0200//唤醒进程
#define TASK_NOLOAD 0x0400//空载状态
#define TASK_NEW 0x0800//新的状态
#define TASK_STATE_MAX 0x1000//任务状态的最大值
还有好多为了方便设置state的宏 也就是奇技淫巧 我只是为了建立框架 暂时不看了
进程调度信息
调度策略
标识符
IPC进程通信信息
为了使进程能在同一项任务上协调工作,进程之间必须能进行通信即交流数据。 Linux 支持多种不同形式的通信机制。它支持典型的UNIX 通信机制(IPC Mechanisms): 信号(Signals)、管道(Pipes),也支持System V / Posix 通信机制:共享内存(Shared Memory)、 信号量和消息队列(Message Queues)
进程链接信息(Links)
因为一个进程能创建几个子进程,而子进程之间有兄 弟关系,在task_struct 结构中有几个域来表示这种关系
ps:除了初始化进程init,其他进程都有一个父进程(Parent Process) 。可以通过fork()或clone()系统调用来创建子进程,除了进程标识符(PID) 等必要的信息外,子进程的task_struct 结构中的绝大部分的信息都是从父进程中拷贝 。系统有必要记录这种“亲属”关系,使进程之间的协作更加方便
时间和定时器信息
一个进程从创建到终止叫做该进程的生存期(lifetime)。进程在其生存期内使用CPU 的时间,内核都要进行记录,以便进行统计、计费等有关操作。进程耗费CPU 的时间由两部 分组成:一是在用户模式(或称为用户态)下耗费的时间、一是在系统模式(或称为系统态) 下耗费的时间。每个时钟滴答,也就是每个时钟中断,内核都要更新当前进程耗费CPU 的时 间信息
文件系统信息
fs_struct 中描述了两个VFS 索引节点(VFS inode),这两个索引节点叫做root 和pwd,分别指向进程的可执行映像所对应的根目录(Home Directory)和当前目录或工作目录。 file_struct 结构用来记录了进程打开的文件的描述符(Descriptor)
虚拟内存信息
除了内核线程,每个进程都有自己的地址空间
页面管理信息
当物理内存不足时,Linux 内存管理子系统需要把内存中的部分页面交换到外存,其交 换是以页为单位的
SMP信息
处理器相关的环境(上下文)信息
和“处理器”相关的环境信息。进程作为一个执行环境的综合, 当系统调度某个进程执行,即为该进程建立完整的环境时,处理器(Processor)的寄存器、 堆栈等是必不可少的。因为不同的处理器对内部寄存器和堆栈的定义不尽相同,所以叫做“和 处理器相关的环境”,也叫做“处理机状态”。当进程暂时停止运行时,处理机状态必须保 存在进程的 thread_struct 结构(多线程的话每个线程都有一份)中,当进程被调度重新运行时再从中恢复这些环境,也就是恢 复这些寄存器和堆栈的值
struct mm_struct (进程的虚拟地址空间)
一个进程的虚拟地址空间主要由两个数据结来描述。一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。
[
](https://blog.csdn.net/qq_26768741/article/details/54375524)
struct mm_struct {
//指向线性区对象的链表头
struct vm_area_struct * mmap; /* list of VMAs */
//指向线性区对象的红黑树
struct rb_root mm_rb;
//指向最近找到的虚拟区间
struct vm_area_struct * mmap_cache; /* last find_vma result */
//用来在进程地址空间中搜索有效的进程地址空间的函数
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
unsigned long (*get_unmapped_exec_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
//释放线性区时调用的方法,
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
//标识第一个分配文件内存映射的线性地址
unsigned long mmap_base; /* base of mmap area */
unsigned long task_size; /* size of task vm space */
/*
* RHEL6 special for bug 790921: this same variable can mean
* two different things. If sysctl_unmap_area_factor is zero,
* this means the largest hole below free_area_cache. If the
* sysctl is set to a positive value, this variable is used
* to count how much memory has been munmapped from this process
* since the last time free_area_cache was reset back to mmap_base.
* This is ugly, but necessary to preserve kABI.
*/
unsigned long cached_hole_size;
//内核进程搜索进程地址空间中线性地址的空间空间
unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */
//指向页表的目录
pgd_t * pgd;
//共享进程时的个数
atomic_t mm_users; /* How many users with user space? */
//内存描述符的主使用计数器,采用引用计数的原理,当为0时代表无用户再次使用
atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */
//线性区的个数
int map_count; /* number of VMAs */
struct rw_semaphore mmap_sem;
//保护任务页表和引用计数的锁
spinlock_t page_table_lock; /* Protects page tables and some counters */
//mm_struct结构,第一个成员就是初始化的mm_struct结构,
struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung
* together off init_mm.mmlist, and are protected
* by mmlist_lock
*/
/* Special counters, in some configurations protected by the
* page_table_lock, in other configurations by being atomic.
*/
mm_counter_t _file_rss;
mm_counter_t _anon_rss;
mm_counter_t _swap_usage;
//进程拥有的最大页表数目
unsigned long hiwater_rss; /* High-watermark of RSS usage */、
//进程线性区的最大页表数目
unsigned long hiwater_vm; /* High-water virtual memory usage */
//进程地址空间的大小,锁住无法换页的个数,共享文件内存映射的页数,可执行内存映射中的页数
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
//用户态堆栈的页数,
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
//维护代码段和数据段
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;
unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
struct linux_binfmt *binfmt;
cpumask_t cpu_vm_mask;
/* Architecture-specific MM context */
mm_context_t context;
/* Swap token stuff */
/*
* Last value of global fault stamp as seen by this process.
* In other words, this value gives an indication of how long
* it has been since this task got the token.
* Look at mm/thrash.c
*/
unsigned int faultstamp;
unsigned int token_priority;
unsigned int last_interval;
//线性区的默认访问标志
unsigned long flags; /* Must use atomic bitops to access the bits */
struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
spinlock_t ioctx_lock;
struct hlist_head ioctx_list;
#endif
#ifdef CONFIG_MM_OWNER
/*
* "owner" points to a task that is regarded as the canonical
* user/owner of this mm. All of the following must be true in
* order for it to be changed:
*
* current == mm->owner
* current->mm != mm
* new_owner->mm == mm
* new_owner->alloc_lock is held
*/
struct task_struct *owner;
#endif
#ifdef CONFIG_PROC_FS
/* store ref to file /proc/<pid>/exe symlink points to */
struct file *exe_file;
unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
struct mmu_notifier_mm *mmu_notifier_mm;
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
pgtable_t pmd_huge_pte; /* protected by page_table_lock */
#endif
/* reserved for Red Hat */
#ifdef __GENKSYMS__
unsigned long rh_reserved[2];
#else
/* How many tasks sharing this mm are OOM_DISABLE */
union {
unsigned long rh_reserved_aux;
atomic_t oom_disable_count;
};
/* base of lib map area (ASCII armour) */
unsigned long shlib_base;
#endif
};
struct vm_area_struct (进程虚拟地址空间的区间)
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking.
第一个缓存行具有VMA树移动的信息*/
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address within vm_mm. */
/* linked list of VM areas per task, sorted by address
每个任务的VM区域的链接列表,按地址排序*/
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb;
/*
此VMA左侧最大的可用内存间隙(以字节为单位)。
在此VMA和vma-> vm_prev之间,
或者在VMA rbtree中我们下面的一个VMA与其->vm_prev之间。
这有助于get_unmapped_area找到合适大小的空闲区域。
*/
unsigned long rb_subtree_gap;
/* Second cache line starts here.
第二个缓存行从这里开始*/
struct mm_struct *vm_mm; /* 我们所属的address space*/
pgprot_t vm_page_prot; /* 此VMA的访问权限 */
unsigned long vm_flags; /* Flags, see mm.h. */
/*
对于具有地址空间(address apace)和后备存储(backing store)的区域,
链接到address_space->i_mmap间隔树,或者链接到address_space-> i_mmap_nonlinear列表中的vma。
*/
union {
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} linear;
struct list_head nonlinear;
} shared;
/*
在其中一个文件页面的COW之后,文件的MAP_PRIVATE vma可以在i_mmap树和anon_vma列表中。
MAP_SHARED vma只能位于i_mmap树中。
匿名MAP_PRIVATE,堆栈或brk vma(带有NULL文件)只能位于anon_vma列表中。
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem & * page_table_lock
由mmap_sem和* page_table_lock序列化*/
struct anon_vma *anon_vma; /* Serialized by page_table_lock 由page_table_lock序列化*/
/* 用于处理此结构体的函数指针 */
const struct vm_operations_struct *vm_ops;
/* 后备存储(backing store)的信息: */
unsigned long vm_pgoff; /* 以PAGE_SIZE为单位的偏移量(在vm_file中),*不是* PAGE_CACHE_SIZE*/
struct file * vm_file; /* 我们映射到文件(可以为NULL)*/
void * vm_private_data; /* 是vm_pte(共享内存) */
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU映射区域 */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* 针对VMA的NUMA政策 */
#endif
};
从mm_struct 到vm_area_struct到linear address space
这里有张更直观的图
进程退出
exit_mm()—>mmput() 减少mm_user个数—>如果mm_user==0(没有fork的子进程,也没有线程共享他的资源) —> free_mm() —> kmem_cache_free() —>把mm_struct还给slab,可以直接被复用,省得再开辟很浪费资源
(而且内核线程在运行的时候还会借用其他进程的mm_struct,所以内核线程又叫Anonymous user)
关于mm_user mm_count的CONFUSION
(1) mm_user指的就是所有共享此mm_struct描述的进程地址空间的线程数量,即:一个(进程中)线程组中的线程个数。当本进程中的线程退出时,mm_user减1,但只有当所有共享此进程空间的线程退出时,会对mm_count减1,否则不减。
(2)mm_count指的就是对mm_struct本身此结构体的引用次数。不管本进程中有多少线程,在没有其他进程或线程引用的情况下,此mm_count为1,因为本进程中的所有线程共享一个进程地址空间,就是创建线程,fork()时,直接将主线程task_struct中的mm域直接给了被fork出来的task_struct的mm域。当有其他进程或线程(除本进程中的线程)引用此mm_struct时,则mm_count加1。
(3)当本进程中的线程退出时,mm_user会减1,如果mm_user减到0了,则会对mm_count减1,如果此时mm_count也为0了,说明该进程空间么有任何使用者了,则会归还此进程地址空间占的内存给系统。