首页 > 代码库 > [linux]进程(二)--进程地址空间
[linux]进程(二)--进程地址空间
7,linux进程命名空间的概念
关于命名空间具体可以参考深入linux内核架构2.3.2章节
linux的命名空间属于操作系统级虚拟化,各个命名空间互相隔离,互相不可见,不同命名空间的进程PID可以相同,因此从操作系统层面看可能有相同的uid和pid,父命名空间同时还可以包含子命名空间,子命名空间不知道父命名空间的存在,但是父命名空间知道子命名空间的存在。
命名空间的实现:
为了task_struct的精简,内核引入了struct nsproxy来统一管理进程所属的命名空间,在task_struct中只需存一个指向struct nsproxy的指针就行了
sched.hstruct task_struct{ //...... struct pid_link pids[PIDTYPE_MAX]; struct nsproxy *nsproxy; /* 多个进程可以共享命名空间 */ //......}
新命名空间的创建:
在用fork或clone系统调用创建新进程时,有特定的选项可以控制是与父进程共享命名空间,还是建立新的命名空间,每个命名空间都有一个对应的标志
sched.h #define CLONE_NEWUTS 0x04000000 #define CLONE_NEWIPC 0x08000000 #define CLONE_NEWUSER 0x10000000 #define CLONE_NEWPID 0x20000000 #define CLONE_NEWNET 0x40000000
8,进程的虚拟地址空间
在32位的cpu系统上,每个进程都可以访问到4G的虚拟地址空间,其中最高的1G为内核虚拟地址空间,每个进程的0-3G的用户空间对其它进程是不可见的。因为MMU的存在,一个进程对用户空间某一个地址访问和其它进程对同一地址访问是不冲突的。
用户虚拟地址空间又至少可以分为三部分
堆空间,栈空间,MMAP空间
linux中的虚拟地址空间是用内存描述符mm_struct来表示的,每个进程描述符都有一个mm_struct来描述该进程对应的虚拟地址空间:
struct task_struct{ //...... void *stack; struct mm_struct *mm, *active_mm; unsigned brk_randomized:1; //......};struct mm_struct{ /* Linux对线性区对象的管理,既使用了链表,也使用了红黑树,这是基于操作效率考虑的。一般而言,红黑树用来确定含有指定地址的线性区;链表用来扫描整个线性区集合时使用 */ struct vm_area_struct *mmap; /* 线性区对象链表的头,链表是有序的,按照线性地址从小到大排列 */ struct rb_root mm_rb; /* 线性区对象红黑树的树根 */ //...... unsigned mmap_base; /* 地址空间中可以用来映射的首地址 */ unsigned task_size; /* 进程的虚拟地址空间大小 */ //...... pgd_t *pgd; /* MMU页表 */ atomic_t mm_users; /* 多少个用户空间使用该mm_struct描述,多线程时mm_users > 1 */ atomic_t mm_count; /* 内核中多少次引用了该mm_struct,如果为0则将被释放 */ int map_count; /* VMAs个数/mmap链表节点个数 */ //...... unsigned start_code, end_code, start_data, end_data; /* 代码段的起始地址、结束地址.已初始化数据段的起始地址、结束地址 */ unsigned start_brk, brk, start_stack; /* 堆的起始地址及当前堆的最后地址, 栈的起始地址 */ unsigned arg_start, arg_end, env_start, env_end; /* 命令行参数的起始地址、结束地址, 环境变量的起始地址、结束地址 */ //......};
C语言的malloc()函数最终是通过brk()系统调用在堆上分配内存的,在linux的实现上,brk()只是去改变mm_struct结构体中brk(堆结束地址)的值,当使用free()函数时候,程序并没有将真正的物理内存还给OS,mm_struct结构体中的brk值不会减少,因为下次再使用malloc()函数时就不需要调用brk()了,一般情况下,程序使用的虚拟地址空间是只变多不变少的~
如何通过命令查看某个进程的虚拟地址空间:
pmap -d命令
解释如下:
每列的含义如下:
参数 解释
Address:进程所占的地址空间
Kbytes:该虚拟段的大小
RSS:设备号(主设备:次设备)
Anon:设备的节点号,0表示没有节点与内存相对应
Locked:是否允许swapped
Mode 权限:r=read, w=write, x=execute, s=shared, p=private(copy on write)
Mapping:bash 对应的映像文件名
Resident :表示在内存中驻留的段的空间
shared :表示这些北分配的内存是被系统中其他进程共享的。
private :表示只能被该进程使用的空间大小。你可以发现share的空间不具有 private的属性。
Prstat -LP 的输出的意义是:
size:就是该进程占用的地址空间。
RSS:实际被分配的内存的大小。
你看到的resident和RSS不同,是RSS是进程在内存中的实际的大小,这个数值最大可以达到Resident显示数值。
补充:将进程可以连续访问的区域称为线性区
Linux通过类型为vm_area_struct的对象实现线性区(简称VMA),。当一个可执行程序映射到进程虚拟地址空间时,一组vm_area_struct数据结构将被产生。每个vm_area_struct数据结构表示可执行印象的一部分;是可执行代码,或是初始化的数据,以及未初始化的数据等
进程描述符的task_struct结构的map_count字段描述了该进程拥有的线性区数目
struct vm_area_struct{ struct mm_struct *vm_mm; /* 所属的内存描述符,所属的mm_struct实例 */ unsigned vm_start, vm_end; /* 地址区间,该区域在用户空间的起始地址和结束地址 */ struct vm_area_struct *vm_next, *vm_prev; /* 进程所有的vm_area_struct实例的链表是通过vm_next实现的,而红黑树是通过vm_rb实现 */ pgprot_t vm_page_prot; /* 该虚拟内存区域的访问权限 */ unsigned vm_flags; /* 访问权限 */ struct rb_node vm_rb; /* 红黑树中对应的节点 */ //...... const struct vm_operations_struct *vm_ops; /*该vma上的各种标准操作函数指针集*/ unsigned vm_pgoff; /* 映射文件的偏移量,以PAGE_SIZE为单位 */ struct file *vm_file; /* 映射的文件,没有则为NULL */ void * vm_private_data; /* was vm_pte (shared mem) */};
mmap()系统调用可以讲磁盘文件全部或者部分映射到用户空间,进程读写文件操作变为直接对内存操作,mmap()只是将file结构的值赋值给vm_area_struct结构体的vm_file成员,并没有将真正的数据映射进来,在真正使用的时候如果发现真实内容部存在,产生缺页异常。
struct vm_area_struct { union { struct { struct list_head list; void *parent; /* aligns with prio_tree_node parent */ struct vm_area_struct *head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared;}
给出文件中的一个区间,内核有时候需要知道该区间映射到的所有进程,这种映射称为共享映射,为提供所需要的信息,所有的vm_area_struct实例都还通过一个优先树管理,包含在以上的shared成员中,
补充:堆和栈的含义:
一个由C/C++编译的程序占用的内存分为以下几个部分:
1、栈区(stack):又编译器自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构的栈。
2、堆区(heap):一般是由程序员分配释放,若程序员不释放的话,程序结束时可能由OS回收,值得注意的是他与数据结构的堆是两回事,分配方式倒是类似于数据结构的链表。
3、全局区(static):也叫静态数据内存空间,存储全局变量和静态变量,全局变量和静态变量的存储是放一块的,初始化的全局变量和静态变量放一块区域,没有初始化的在相邻的另一块区域,程序结束后由系统释放。
4、文字常量区:常量字符串就是放在这里,程序结束后由系统释放。
5、程序代码区:存放函数体的二进制代码。
堆和栈的区别
1,申请方式不一样
栈由系统自动分配,堆需要程序员自己手动去分配释放,分配C用的是malloc()函数,释放用free()函数,C++用new()函数,释放用delete()函数
2,申请后系统的回应
栈:只要栈空间大于系统申请的空间,否则系统报出异常提示栈溢出
堆:堆:首先应该知道操作系统有一个记录内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请的空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete或free语句就能够正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会将多余的那部分重新放入空闲链表中。
3,申请大小限制不一样:
栈向低地址扩展,堆是向高地址扩展的。
4,申请效率不一样
栈:系统申请,速度快
堆:程序员自己申请,速度慢
5,存储内容不一样
堆:一般是在堆得头部用一个字节存放堆得大小,具体内容由程序员安排
栈:在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令的地址,然后是函数的各个参数,在大多数的C编译器中,参数是从右往左入栈的,当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令。
6,生命周期
栈空间:自动内存空间,其中数据的大小在编译时确定,数据的分配和释放也由编译器在函数进入和退出时插入指令完成,数据生命周期和函数一样。
堆空间:动态(手动)内存空间,其中数据的大小和初始值在运行时确定,数据生命周期不定。
用ulimit -s命令可以看出linux默认支持的最大栈空间为8192KB(8M)
ASLR(Address space layout randomization)技术:一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,
通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的
[linux]进程(二)--进程地址空间