pcb eip 进程_从linux0.11看一个进程的诞生

系统有一个GDT表。该表保存了系统和所有进程的tss和ldt描述符信息。tss就是我们平时说的进程上下文。每个进程有一个ldt数组,里面保存了代码段和数据段的描述符信息。
首先,从一个进程的诞生说起。我们知道,通过fork可以创建一个进程。下面我们来看一下fork的过程都做了什么事情。先通过find_empty_process获取一个可用的进程id和pcb。pid是进程id。pcb是管理进程的结构体。

int find_empty_process(void)
{
    int i;

    repeat:
        // 先找到一个可用的pid
        if ((++last_pid)<0) last_pid=1;
        for(i=0 ; i<NR_TASKS ; i++)
            if (task[i] && task[i]->pid == last_pid) goto repeat;
    // 再找一个可用的pcb项,从1开始,0是init进程
    for(i=1 ; i<NR_TASKS ; i++)
        if (!task[i])
            return i;
    return -EAGAIN;
}

接着调用copy_process复制父进程的信息。

/*
 *  Ok, this is the main fork-routine. It copies the system process
 * information (task[nr]) and sets up the necessary registers. It
 * also copies the data segment in it's entirety.
 */
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx,
        long fs,long es,long ds,
        long eip,long cs,long eflags,long esp,long ss)
{
    struct task_struct *p;
    int i;
    struct file *f;
    // 申请一页存pcb
    p = (struct task_struct *) get_free_page();
    if (!p)
        return -EAGAIN;
    // 挂载到全局pcb数组
    task[nr] = p;
    // 复制当前进程的数据
    *p = *current;  /* NOTE! this doesn't copy the supervisor stack */
    p->state = TASK_UNINTERRUPTIBLE;
    p->pid = last_pid;
    p->father = current->pid;
    p->counter = p->priority;
    p->signal = 0;
    p->alarm = 0;
    p->leader = 0;      /* process leadership doesn't inherit */
    p->utime = p->stime = 0;
    p->cutime = p->cstime = 0;
    // 当前时间
    p->start_time = jiffies;
    p->tss.back_link = 0;
    // 页末
    p->tss.esp0 = PAGE_SIZE + (long) p;
    p->tss.ss0 = 0x10;
    // 调用fork时压入栈的ip,子进程创建完成会从这开始执行,即if (__res >= 0) 
    p->tss.eip = eip;
    p->tss.eflags = eflags;
    // 子进程从fork返回的是0,eax会赋值给__res
    p->tss.eax = 0;
    p->tss.ecx = ecx;
    p->tss.edx = edx;
    p->tss.ebx = ebx;
    p->tss.esp = esp;
    p->tss.ebp = ebp;
    p->tss.esi = esi;
    p->tss.edi = edi;
    // 段选择子是16位
    p->tss.es = es & 0xffff;
    p->tss.cs = cs & 0xffff;
    p->tss.ss = ss & 0xffff;
    p->tss.ds = ds & 0xffff;
    p->tss.fs = fs & 0xffff;
    p->tss.gs = gs & 0xffff;
    /*
        计算第nr进程在GDT中关于LDT的索引,切换任务的时候,
        这个索引会被加载到ldt寄存器,cpu会自动根据ldt的值,把
        GDT中相应位置的段描述符加载到ldt寄存器(共16+32+16位)
    */
    p->tss.ldt = _LDT(nr); 
    p->tss.trace_bitmap = 0x80000000;
    if (last_task_used_math == current)
        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
    /*
    设置线性地址范围,挂载线性地址首地址和限长到ldt,赋值页目录项和页表
    执行进程的时候,tss选择子被加载到tss寄存器,然后把tss里的上下文
    也加载到对应的寄存器,比如cr3,ldt选择子。tss信息中的ldt索引首先从gdt找到进程ldt
    结构体数据的首地址,然后根据当前段的属性,比如代码段,
    则从cs中取得选择子,系统从ldt表中取得进程线性空间
    的首地址、限长、权限等信息。用线性地址的首地址加上ip
    中的偏移,得到线性地址,然后再通过页目录和页表得到物理
    地址,物理地址还没有分配则进行缺页异常等处理。
    */
    if (copy_mem(nr,p)) {
        task[nr] = NULL;
        free_page((long) p);
        return -EAGAIN;
    }
    // 父子进程都有同样的文件描述符,file结构体加一
    for (i=0; i<NR_OPEN;i++)
        if (f=p->filp[i])
            f->f_count++;
    // inode节点加一
    if (current->pwd)
        current->pwd->i_count++;
    if (current->root)
        current->root->i_count++;
    if (current->executable)
        current->executable->i_count++;
    /*
        挂载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));
    p->state = TASK_RUNNING;    /* do this last, just in case */
    return last_pid;
}

int copy_mem(int nr,struct task_struct * p)
{
    unsigned long old_data_base,new_data_base,data_limit;
    unsigned long old_code_base,new_code_base,code_limit;
    // 代码段限长
    code_limit=get_limit(0x0f);
    // 数据段限长
    data_limit=get_limit(0x17);
    old_code_base = get_base(current->ldt[1]);
    old_data_base = get_base(current->ldt[2]);
    if (old_data_base != old_code_base)
        panic("We don't support separate I&D");
    if (data_limit < code_limit)
        panic("Bad data_limit");
    // 设置进程的线性地址的首地址,每个进程占64MB
    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
    if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
        free_page_tables(new_data_base,data_limit);
        return -ENOMEM;
    }
    return 0;
}

下面具体分析一下几个地方。
1 _LDT的宏展开如下:

/*
 * 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))

_LDT是计算出进程ldt在GDT表的索引。

2

// 设置线性地址到ldt的描述符中     
set_base(p->ldt[1],new_code_base);     
set_base(p->ldt[2],new_data_base)


3

    /*
        挂载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));

设置完后的结构

637cf5f4fd6dfe29b9152cb9371db513.png

进程创建的本质就是申请一个新的pcb,里面保存了该进程的相关信息,假设现在轮到该进程执行。系统会根据tss选择子到gdt表中找到tss结构体的地址。然后使用tss结构体的内容恢复执行上下文。然后找到tss中的ldt选择子,把ldt选择子加载到ldtr寄存器,然后根据ldt选择子到gdt表中可以找到对应的ldt描述符。根据cs:ip的值。cs寄存器里存的是代码段的选择子。是0x17。即ldt的第二项,和数据段一样。从ldt第二项中找出基地址和限长。基地址+ip得到线性地址的值。然后再根据页目录和页表就能得到物理值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值