linux内核--进程地址空间(三)
引言:上篇博文中,我们简单的介绍了Linux虚拟存储器的概念及组成情况,下面来分析分析进程的创建和终结及跟进程地址空间的联系。
这里首先介绍一个比较重要的概念:存储器映射
在Linux系统中,通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容,这个过程称为存储器映射。存储器映射为共享数据、创建新的进程以及加载程序提供了一种高效的机制。
虚拟存储器区域可以映射到两种类型对象中:
1)普通文件:一个虚拟区域可以映射到普通磁盘文件的连续部分,例如可执行目标文件。虚拟区域分为若干的虚拟页面,这些虚拟页面初始化时并没有实际交换进物理存储器,直到CPU第一次引用页面时才真正的加载进物理内存。如果虚拟区域比映射的文件要大,则剩下的部分用零填充。
2)匿名文件:匿名文件是由内核创建的,包含的全部是二进制零。映射到匿名文件的区域中的页面有时称为 请求二进制零的页。
注意:无论映射到何种文件,一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。这里的交换文件也称为 交换空间。由此可见任何时候,交换空间限制着当前运行着的进程能够分配的虚拟页面的总数。
有了前面的一些概念的基础下面我们开始看进程的创建、执行、退出。
一:进程创建
Unix的进程创建比较特别。许多其他操作系统提供了产生机制。首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。Unix采用了不同方式:它把上述步骤分为两步,分解到两个单独的函数中执行:fork( ) 和 exec( )。
fork( )函数被当前进程调用时,内核为新进程创建各种数据结构(例如内核栈、thread_info结构、task_struct结构)并分配给它一个唯一的PID。为了给新进程创建虚拟存储器,它创建了当前进程所有资源的原样拷贝。它将两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为 私有的写时拷贝。
Linux的fork( )使用写时拷贝页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝。
只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝,从而为父子进程保持了私有地址空间的抽象概念。资源的复制只有在需要写入的时候才进行,在此之前,只是以只读的方式共享。这中技术使得地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。在页根本不会写入的情况下(例如fork()之后立即调用exec() )它们就无须复制了。
下面看个实例:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main( )
{
int pid;
int x = ;
pid = fork();
if(pid == ) /* Child */
{
printf(" child : x = %d\n",++x);
exit();
} /* Parent */
printf("parent : x = %d\n",--x);
exit();
}

输出如下:
由此可以看出:
1)fork()调用一次,返回两次:一次返回到父进程,一次返回到子进程。
2)并发执行:父进程和子进程并发运行,内核能够以任意方式交替运行它们,这里是父进程先运行,然后是子进程。但是在另外一个系统上运行时不一定是这个顺序。
3)父子进程都有自己的私有地址空间,父子进程对x的操作都是独立的,不会反应在另外一个进程的存储器中。
函数 int execl(const char *filename,const char *argv[],const char char *envp[]):
下面我们举例看exec 函数是如何加载和执行程序的:

#include <stdlib.h>
#include<stdio.h>
#include <unistd.h>
int main()
{
int pid = fork();
if(pid<)
{
perror("fork");
} else if(pid == )
{ execl("hello",NULL,NULL);
/* We can only reach this code when there is an error in execl*/
perror("execl");
}
else
{
sleep();
printf("This is parent\n");
} exit(); }

这里的execl调用:execl("hello",NULL,NULL);中的hello是上篇博文中提到的hello.c程序对应的可执行目标文件。

hello.c
#include<stdio.h>
int main()
{ printf("Hello\n");
return ;
}

execl("hello",NULL,NULL)调用后在当前子进程中加载并运行包含在可执行目标文件 hello中的程序,用hello程序有效的替代了当前程序。加载并运行hello的步骤如下:
1)删除已经存在的用户区域,删除当前调用execl的子进程的虚拟地址的用户部分中的已存在的区域结构。
2)将可执行文件hello的连续的片映射到连续的虚拟存储器中。
段头部表 描述了这种映射关系:
下面我们用readelf 查看可执行文件 hello 的段头部:
从图中可以看出:
1:映射私有区域:即为新程序的代码、数据、bss、和栈区域创建和初始化新的区域结构。
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x005bc 0x005bc R E 0x1000
代码段:对齐到一个4KB(0x1000=2^12,为X86平台一个页面的大小)的边界,有可读、可执行权限。从可执行目标文件中偏移量为0开始的0x005bc字节长的代码段(其中包括了ELF头部、段头部表、以及.init、.text、和 .rodata节)被映射到开始于虚拟地址0x08048000,长度为0x005bc字节的虚拟存储区域中。
LOAD 0x000f14 0x08049f14 0x08049f14 0x00100 0x00108 RW 0x1000
数据段:同样对齐到4KB大小的边界,有可读可写权限。可执行文件偏移0x000f14处开始,长度为0x100个字节的数据段被映射到开始于虚拟地址0x08049f14处,长度为0x00108字节的虚拟存储区域中。
bss段、栈段、堆段被初始化为0,即被映射到匿名文件。
2:映射共享区域:如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间的共享区域内。
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align 1
DYNAMIC 0x000f28 0x08049f28 0x08049f28 0x000c8 0x000c8 RW 0x4
动态链接ELF中最重要的结构就是.dynamic段,这个段保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表位置、动态链接重定位表的位置、共享对象初始化代码的地址等。
如上图所示:这里列出了hello可执行文件动态链接依赖的对象。
动态链接共享库的步骤:
1:加载和运行动态链接器
动态链接器本身就是一个共享目标,所以要先加载本身,在可执行文件hello中的.interp段包含了动态链接器的路径名:
2:装载所有需要的共享对象
启动完动态链接器之后,动态链接器将可执行文件hello和链接器本身的符号表都合并在一起,组成全局符号表。然后链接器开始寻找可执行文件所依赖的共享对象,在之前提到的.dynamic段中,有一种入口的类型是 DT _NEEDED,它所指出的Shared library: [libc.so.6]就是该可执行文件所依赖的共享对象。由此,链接器可以列出可执行文件hello所需要的所有共享对象,并将这些对象名字放入到一个装载集合中。然后链接器根据名字找到相应的文件,并将它相应的代码段、数据段映射到进程地址空间的共享区域中。
3:重定位和初始化
当上面步骤完成后,根据进程的全局符号表,对GOT/PLT中的每个需要重定位的位置进行修正,这种技术称为延迟绑定。
完成重定位和初始化后,所有准备工作结束,所需要的共享对象也都已经装载并且链接完成。最后将进程的控制权转交给hello程序的入口并开始执行。
可执行elf文件格式,及加载完可执行文件hello后的进程地址空间如下图:
在文章中介绍了几个重要的结构体,关于这几个结构体的含义和之间的关系如下:
主要的数据结构有:
task_struct : 进程描述符结构,定义在<linux/sched.h>文件中,描述了该进程打开的文件、进程的地址空间、进程的状态等信息。
mm_struct :进程虚拟内存描述符,定义在<linux/sched.h>文件中,该结构包含了和进程地址空间相关的全部信息。
vm_area_struct:虚拟内存区域描述符,定义在<linux/mm.h>文件中,该结构体描述了指定地址空间内连续区间上的一个独立的内存范围(比如进程用户空间栈、代码段、数据段等)。
三个结构之间的关系:
task_struct 中的一个 字段 mm 指向当前进程的虚拟地址空间描述符 mm_struct , mm_struct中的字段 mmap 指向一个vm_area_struct组成的链表,其中每个vm_area_struct 都描述了当前虚拟地址空间的一个区域。
除了这三个结构体外,还有几个重要的结构体,在其他临近的博文中有介绍,比如thread_info结构体,这个结构体中包括了task_struct结构体,这几个结构体的内容和含义层次不同。
linux内核--进程地址空间(三)的更多相关文章
- linux内核--进程地址空间(一)
引言:现代操作系统提供了一种对内存的抽象概念,叫做虚拟存储器,它为每个进程提供了一个大的,一致的,和私有的地址空间.通过一个很清晰的机制,虚拟存储器提供了3个重要的能力: 1)它将主存看成是一个存储在 ...
- Linux内核分析(三)----初识linux内存管理子系统
原文:Linux内核分析(三)----初识linux内存管理子系统 Linux内核分析(三) 昨天我们对内核模块进行了简单的分析,今天为了让我们今后的分析没有太多障碍,我们今天先简单的分析一下linu ...
- 十天学Linux内核之第三天---内存管理方式
原文:十天学Linux内核之第三天---内存管理方式 昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今 ...
- Linux内核设计第三周——构造一个简单的Linux系统
Linux内核设计第三周 ——构造一个简单的Linux系统 一.知识点总结 计算机三个法宝: 存储程序计算机 函数调用堆栈 中断 操作系统两把宝剑: 中断上下文的切换 进程上下文的切换 linux内核 ...
- linux内核分析第三周
20135103王海宁 linux内核分析第三周 http://mooc.study.163.com/course/USTC-1000029000 按照课堂提供的方法,命令行一行行敲上去,我是手机缓 ...
- LINUX内核分析第三周学习总结——构造一个简单的Linux系统MenuOS
LINUX内核分析第三周学习总结——构造一个简单的Linux系统MenuOS 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163. ...
- 20135327郭皓--Linux内核分析第三周 构造一个简单的Linux系统MenuOS
Linux内核分析第三周 构造一个简单的Linux系统MenuOS 前提回顾 1.计算机是如何工作的三个法宝 1.存储程序计算机 2.函数调用堆栈 3.中断 2.操作系统的两把宝剑 中断上下文的切换 ...
- Linux内核分析第三周学习笔记
linux内核分析第三周学习笔记 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.co ...
- Linux内核分析第三周学习博客——跟踪分析Linux内核的启动过程
Linux内核分析第三周学习博客--跟踪分析Linux内核的启动过程 实验过程截图: 过程分析: 在Linux内核的启动过程中,一共经历了start_kernel,rest_init,kernel_t ...
随机推荐
- Wireshark - 过滤规则
使用 Wireshark 的默认设置抓包时,会得到大量的冗余信息,以至于很难找到自己所需的封包.使用过滤器可以帮助我们在庞杂的结果中快速地找到我们所需的封包.过滤器分为两种:捕捉过滤器和显示过滤器. ...
- LSJ_NHibernate第三章 IDAL,DAL,BLL
前言: 做项目(面向数据编程),首先必须了解业务,这是核心,不懂业务写出来的代码毫无意义.业务我这里分为两种,简单业务操作,复杂业务操作,我以他们操作表的界限进行区分,假设我更新一条数据,只操作了一张 ...
- 20151205 jquery 学习笔记--Ajax
Ajax全称为:“Asynchronous JavaScript and XML”(异步 JavaScript 和 XML), 它并不是 JavaScript 的一种单一技术,而是利用了一系列交互式网 ...
- android开发者博客二月-Marshmallow and User Data
又是一篇翻译,这篇快了很多,不过也花了快一个小时,可能熟悉一点.关于6.0权限的,让你做用户认为正确的事情. Marshmallow and UserData 2016,2,1 棉花糖和用户数据 由J ...
- (一)初识Android
第一节:手机操作系统简介 目前的主流智能操作系统有:Android , IOS , windows mobile ; Android 开源,属于谷歌公司,市场份额较大,前景广阔: IOS 属于苹果公司 ...
- HUD加载动画支持gif
下载地址 https://pan.baidu.com/s/1c2E7QW4
- swift入门-day01
Swift 简介 简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 2014 年,在 Apple WWDC 发布 历史 2010 年 7 月,苹果开发者工 ...
- c编程:用户输入一个数值n,打印出出1到n之间的所有质数
#include <stdio.h> int func(int i ) { //定义一个变量temp=2,当主函数引入的数大于temp时进入for循环.当它在比自己小的数中找到一个能背整除 ...
- iOS控件——UIView的viewWithTag:(int)findTag方法描述
UIView拥有一个viewWithTag:(int)findTag方法,调用方式为[MyView viewWithTag:整形数字]该方法返回tag == findTag的控件.ios控件中允许多个 ...
- Codevs 1074 食物链 2001年NOI全国竞赛
1074 食物链 2001年NOI全国竞赛 时间限制: 3 s 空间限制: 64000 KB 题目等级 : 钻石 Diamond 传送门 题目描述 Description 动物王国中有三类动物 A,B ...