先看代码
#include
#include
#include
int g_val = 0;
int main(){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error\n");
}
else if(pid == 0){
printf("this is child process...........\n");
printf("g_val:%d g_val'adress: %p\n",g_val,&g_val);
}
else{
printf("this is parent process..........\n");
printf("g_val:%d g_val'adress: %p\n",g_val = 100,&g_val);
sleep(5);
}
return 0;
}
看一下结果
我们看到g_val的值在父进程和子进程分别打印出不同的值,但是地址是相同的,我们所认知的是同一块物理地址空间数据是一定相同的,但是,此时是不同的。说明这块空间不为物理地址。根据冯诺依曼体系结构可知,我们向磁盘写入数据有两种方式:一、直接写入磁盘;二、先写入缓冲区(内存),再让CPU去执行存储到磁盘。CPU是一个高效的处理器,磁盘是读写速率较慢的存储器,所以为了高速进行,采取第二种写入方式。内存的数据是虚拟,不具永久性,断电就会消失。那么,这里面数据不同,但是,地址相同。所以两个进程是在内存中。我们知道task_struct结构体里有一项成员叫做mm_struct——虚拟地址空间。
vim /usr/src/kernels/3.10.0-514.26.2.el7.x86_64/include/linux/mm_types.h
注:kernels不同对应路径里的3.10.0-514.26.2.el7.×86_64不同。
操作系统运用了面向对象的思路对mm_struct进行封装,Linux就是通过mm_struct实现了内存管理。一个进程的虚拟地址空间主要由两个数据结构进行描述,一个是mm_struct,另一个是vm_area_struct。mm_struct描述的是虚拟地址的整体空间,vm_area_struct描述的是虚拟地址空间的一个区间(子集)。可以说,mm_struct结构是对整个用户空间的描述。
在进程的task_struct结构体中包含一个指向mm_struct结构的指针,mm_struct用来描述一个进程的虚拟地址空间。进程的mm_struct则包含装入的可执行映像信息以及进程的页表目录指针pgd。该结构还包含有指向vm_area_struct结构的几个指针,每个vm_area_struct代表进程的一个虚拟地址区间。vm_area_struct结构含有指向vm_operations_struct结构的一个指针,vm_operations_struct描述了在这个区间的操作。vm_operations_struct结构中包含的是函数指针,其中open、close分别用于虚拟区间的打开、关闭,而nopage用于当虚拟页面不再物理内存而引起的”缺页异常”时所调用的函数,当Linux处理这一缺页异常时,就可以为新的虚拟内存分配实际的物理内存。
每个进程都只有一个内存描述符mm_struct。在每个进程的task_struct结构中,有一个指向mm_struct的变量,这个变量常常是mm。
mm_struct是对进程的地址空间(虚拟内存)的描述。一个进程的虚拟空间中可能有多个虚拟区间,对这些虚拟空间的组织方式有两种,当虚拟区较少时采用单链表,由mmap指针指向这个链表,当虚拟区间多时采用红黑树进行管理,由mm_rb指向这棵树。因为程序中用到的地址常常具有局部性,因此,最近一次用到的虚拟区间很可能下一次还要用到,因此把最近用到的虚拟区间结构放到高速缓存,这个虚拟区间就由mmap_cache指向。
指针pgt指向该进程的页表目录(每个进程都有自己的页表目录),当调度程序调度一个程序运行时,就将这个地址转换成物理地址,并写入控制寄存器。
由于进程的虚拟空间及下属的虚拟区间有可能在不同的上下文中受到访问,而这些访问又必须互斥,所以在该结构中设置了用于P,V操作的信号量mmap_sem。此外,page_table_lock也是为类似的目的而设置。
虽然每个进程只有一个虚拟空间,但是这个虚拟空间可以被别的进程来共享。如:子进程共享父进程的地址空间,而mm_user和mm_count就对其计数。
另外,还描述了代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
398 struct mm_struct {
399 struct vm_area_struct * mmap; /* list of VMAs */ //虚拟地址空间结构体,双向链表包含红黑树节点访问到不能访问的区域。
400 struct rb_root mm_rb; //红黑树的根节点
401 struct vm_area_struct * mmap_cache; /* last find_vma result */ //mmap的高速缓冲器,指的是mmap最后指向的一个虚拟地址区间
402 #ifdef CONFIG_MMU
403 unsigned long (*get_unmapped_area) (struct file *filp,
404 unsigned long addr, unsigned long len,
405 unsigned long pgoff, unsigned long flags);
406 void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
407 #endif
408 unsigned long mmap_base; /* base of mmap area */ //mmap区域的基地址
409 unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */ //自底向上的配置
410 unsigned long task_size; /* size of task vm space */ //进程的虚拟地址空间大小
411 unsigned long cached_hole_size; /* if non-zero, the largest hole below free_area_cache */ //缓冲器的最大的大小
412 unsigned long free_area_cache; /* first hole of size cached_hole_size or larger */ //不受约束的空间大小
413 unsigned long highest_vm_end; /* highest vma end address */ //虚拟地址空间最大结尾地址
414 pgd_t * pgd; //页表的全局目录
415 atomic_t mm_users; /* How many users with user space? */ //有多少用户
416 atomic_t mm_count; /* How many references to "struct mm_struct" (users count as 1) */ //有多少用户引用mm_struct
417 atomic_long_t nr_ptes; /* Page table pages */ //页表
418 int map_count; /* number of VMAs */ //虚拟地址空间的个数
419
420 spinlock_t page_table_lock; /* Protects page tables and some counters */ //保护页表和用户
421 struct rw_semaphore mmap_sem; //读写信号
422
423 struct list_head mmlist; /* List of maybe swapped mm's. These are globally strung
424 * together off init_mm.mmlist, and are protected
425 * by mmlist_lock
426 */
427
428
429 unsigned long hiwater_rss; /* High-watermark of RSS usage */ //标志
430 unsigned long hiwater_vm; /* High-water virtual memory usage */
431
432 unsigned long total_vm; /* Total pages mapped */
433 unsigned long locked_vm; /* Pages that have PG_mlocked set */
434 unsigned long pinned_vm; /* Refcount permanently increased */
435 unsigned long shared_vm; /* Shared pages (files) */
436 unsigned long exec_vm; /* VM_EXEC & ~VM_WRITE */
437 unsigned long stack_vm; /* VM_GROWSUP/DOWN */
438 unsigned long def_flags;
439 unsigned long start_code, end_code, start_data, end_data; //开始代码段,结束代码。开始数据,结束数据
440 unsigned long start_brk, brk, start_stack; //堆的开始和结束。
441 unsigned long arg_start, arg_end, env_start, env_end; //参数的起始和结束,环境变量的起始和终点
442
443 unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
...
};
内存描述符mm_struct指向整个地址空间,vm_area_struct只是指向了虚拟空间的一段。vm_area_struct是由双向链表链接起来的,它们是按照虚拟地址降序排序的,每个这样的结构都对应描述一个地址空间范围。之所以这样分隔是因为每个虚拟区间可能来源不同,有的可能来自可执行映像,有的可能来自共享库,而有的可能是动态内存分配的内存区,所以对于每个由vm_area_struct结构所描述的区间的处理操作和它前后范围的处理操作不同,因此linux把虚拟内存分割管理,并利用了虚拟内存处理例程vm_ops来抽象对不同来源虚拟内存的处理方法。不同的虚拟区间其处理操作可能不同,linux在这里利用了面向对象的思想,即把一个虚拟区间看成是一个对象,用vm_area_struct描述这个对象的属性,其中的vm_operation结构描述了在这个对象上的操作。
281 struct vm_area_struct {
282 /* The first cache line has the info for VMA tree walking. */ //第一个高速缓存行包含VMA树便利的信息
283
284 unsigned long vm_start; /* Our start address within vm_mm. */ //虚拟地址空间vm_mm起始地址
285 unsigned long vm_end; /* The first byte after our end address
286 within vm_mm. */ //在vm_mm中结束地址的第一个字节
287
288 /* linked list of VM areas per task, sorted by address */
289 struct vm_area_struct *vm_next, *vm_prev; //双向链表连接以便于遍历
290
E>291 struct rb_node vm_rb; //红黑树节点
292
293 /*
294 * Largest free memory gap in bytes to the left of this VMA. //VMA左边最大的可用内存缺口
295 * Either between this VMA and vma->vm_prev, or between one of the
296 * VMAs below us in the VMA rbtree and its ->vm_prev. This helps //要么在当前的VMA和vma的前一个,要么是在VMA红黑树和它的前 一个中的VMAs之间
297 * get_unmapped_area find a free area of the right size. //这样有助于get_unmapped_area找到合适的空闲区域
298 */
299 unsigned long rb_subtree_gap; //红黑树子树的缺口
300
301 /* Second cache line starts here. */
302
302
303 struct mm_struct *vm_mm; /* The address space we belong to. */ //进程拥有的地址空间
E>304 pgprot_t vm_page_prot; /* Access permissions of this VMA. */ //此VMA访问权限
305 unsigned long vm_flags; /* Flags, see mm.h. */ //标志
306
307 /*
308 * For areas with an address space and backing store,
309 * linkage into the address_space->i_mmap interval tree, or
310 * linkage of vma in the address_space->i_mmap_nonlinear list. //作为地址空间和后备存储区域,链接到间隔树或者链接到非线性链 表里的vma
311 */
312 union {
313 struct {
314 struct rb_node rb;
315 unsigned long rb_subtree_last;
316 } linear; //线性
317 struct list_head nonlinear; //链表非线性
318 } shared;
319
320 /*
321 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
322 * list, after a COW of one of the file pages. A MAP_SHARED vma
323 * can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
324 * or brk vma (with NULL file) can only be in an anon_vma list. //MAP_SHARED只能 在i_mmap树。匿名的对象,栈或者堆只能存在链表 里
325 */
326 struct list_head anon_vma_chain; /* Serialized by mmap_sem &
327 * page_table_lock */
328 struct anon_vma *anon_vma; /* Serialized by page_table_lock */
329
330 /* Function pointers to deal with this struct. */
331 const struct vm_operations_struct *vm_ops; //虚拟内存
332
333 /* Information about our backing store: */
334 unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
335 units, *not* PAGE_CACHE_SIZE */
336 struct file * vm_file; /* File we map to (can be NULL). */ //指向的文件
337 void * vm_private_data; /* was vm_pte (shared mem) */
...
};
vm_operations_struct结构中封装的是函数指针,其中open、close分别是虚拟操作的打开、关闭,而nopage相当于default用于当虚拟内存页面没有实际的物理内存映射而引起的”缺页异常”时所调用的函数指针。
struct vm_operations_struct
{
void (*open)(struct vm_area_strucr *area);
void (*close)(struct vm_area_struct *area);
struct page *(nopage)(struct vm_area_struct *area,unsigned long address,int unused);
}