首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解进程的新建和执行过程

理解进程的新建和执行过程

作者头像
theanarkh
发布2020-03-12 00:32:38
7040
发布2020-03-12 00:32:38
举报
文章被收录于专栏:原创分享原创分享

本文以linux0.11版本为基础,分析进程的内存布局,现代版本已经发生比较大的变化,都是很多原理都是类似的。 系统维护了一个全局的数据结构叫GDT( Global Descriptor Table),他保存了所有进程的代码段数据段的一些信息。系统有专门的寄存器保存了GDT的地址,叫GDTR。GTDR的格式如下。

在这里插入图片描述
在这里插入图片描述

有专门的指令把这个地址加载到GDTR中。叫LGDT。每个进程可以定义一个LDT,用于存储代码段和数据段信息。GDT布局如下。

在这里插入图片描述
在这里插入图片描述

GDT每个项(GDT描述符)对应的结构体是

GDT描述符
GDT描述符

我们回顾task_struct结构,看到有两个属性desc_struct,和tss_struct。desc_struct是保存进程代码段和数据段信息的,tss_struct是保存进程执行上下文的。这两个结构体的定义如下。

struct desc_struct {
    unsigned long a,b;
}

struct tss_struct {
    long    back_link;  /* 16 high bits zero */
    long    esp0;
    long    ss0;        /* 16 high bits zero */
    long    esp1;
    long    ss1;        /* 16 high bits zero */
    long    esp2;
    long    ss2;        /* 16 high bits zero */
    long    cr3;
    long    eip;
    long    eflags;
    long    eax,ecx,edx,ebx;
    long    esp;
    long    ebp;
    long    esi;
    long    edi;
    long    es;     /* 16 high bits zero */
    long    cs;     /* 16 high bits zero */
    long    ss;     /* 16 high bits zero */
    long    ds;     /* 16 high bits zero */
    long    fs;     /* 16 high bits zero */
    long    gs;     /* 16 high bits zero */
    long    ldt;        /* 16 high bits zero */
    long    trace_bitmap;   /* bits: trace 0, bitmap 16-31 */
    struct i387_struct i387;
};

我们从fork函数开始,看看这些数据结构的设置和关系。我们先来看一下一些宏定义。他是根据进程号(进程id)计算出在GDT中的索引。可参考上图。

/*
 * Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall
 * 4-TSS0, 5-LDT0, 6-TSS1 etc ...
 */
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
// 第一个tss选择子的偏移是4<<3,4乘以8,等于32,即从GDT的偏移为32开始算,第一个进程的n是0,tss是32
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
// 第一个ldt选择子的偏移是5<<3,5乘以8,等于40,即从GDT的偏移为40开始算,第一个进程的n是0,ldt是40
#define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))

下面代码来自fork。

// nr是进程id,计算进程的ldt结构在gdt中的索引,执行该进程的时候,从GDT的第tss->ldt项中取得进程的信息。p即task_struct
p->tss.ldt = _LDT(nr); 
// 设置进程的线性地址的首地址,每个进程占64MB,0.11的进程线性地址是每个进程64M,不是4GB
new_data_base = new_code_base = nr * 0x4000000;
p->start_code = new_code_base;
// 设置线性地址到ldt的描述符中
set_base(p->ldt[1],new_code_base);
set_base(p->ldt[2],new_data_base);
// 把父进程的页目录项和页表复制到子进程,old_data_base,new_data_base是线性地址,父子进程共享物理页面,即copy on write
copy_page_tables(old_data_base,new_data_base,data_limit);
/*
    挂载tss和ldt地址到gdt,nr << 1即乘以2,这里算出的是第nr个进程距离第一个tss描述符地址的偏移,
    单位是8个字节,即选择描述符大小,_LDT是偏移的大小,单位是1,这里是8
*/
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
在这里插入图片描述
在这里插入图片描述

fork执行完之后,新新建的相关数据结构已经建立好了,并且也和系统的管理数据产生了关联。有自己独立的页表,和父进程共享物理地址。那么当这个进程被调度的时候,他会发生什么。 执行进程的时候,根据进程号,算出tss在gdt的索引,然后把索引里指向的tss里的上下文也加载到对应的寄存器,tss信息中的ldt索引首先从gdt找到进程ldt结构体数据的首地址,即desc_struct结构体数组,然后根据当前段的属性,比如代码段,则从cs中取得选择子,系统从ldt表中取得进程线性空间的首地址、限长、权限等信息。用线性地址的首地址加上ip中的偏移,得到线性地址,然后再通过页目录和页表得到物理地址,物理地址还没有分配则进行缺页异常等处理。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档