• 进程创建


    进程创建

    根据一下问题来看笔记

    • 进程占多大的线形地址空间
    • 进程实际分配多少物理内存
    • 创建进程的开销在哪里

    一. 从fork系统调用开始

    kernel/sys_call.s第222行

    _sys_fork:
        call _find_empty_process        #为新进程分配id
        testl %eax,%eax                 #若为负数则返回
        js 1f
        push %gs                        #压入copy_process需要的参数
        pushl %esi
        pushl %edi
        pushl %ebp
        pushl %eax
        call _copy_process              #调用copy_process函数
        addl $20,%esp                   #丢弃压栈内容
    1:  ret

    二. copy_process函数分析

    作用: 复制当前进程的代码段和数据段以及环境

    这里需要说明的是每个任务的线性地址为64M,每个任务的线性地址不重叠。

    1. copy_mem函数重点

    • 上面代码重点分析copy_mem函数中get_base函数的意思,首先来看段描述符的格式如下图:

    #define _get_base(addr) ({
    unsigned long __base; 
    #段描述符有两个32位,%3代表 addr+7的内容,也就是上图第一行的基地址31~24的内容放到dh寄存器
    __asm__("movb %3,%%dh
    	" 
        #%2代表addr+4的内容,上图第一行基地址的内容放到dl寄存器
        "movb %2,%%dl
    	" 
        #上面两行构成dx的内容,右移16位空出低16位
        "shll $16,%%edx
    	" 
        #将addr+2的内容,上图第二行31~16的内容放到%dx,edx中构成了完整的段基址
        "movw %1,%%dx" 
        :"=d" (__base) 
        :"m" (*((addr)+2)), 
         "m" (*((addr)+4)), 
         "m" (*((addr)+7))); 
    __base;})
    • copy_mem函数的get_limit(0x0f)中0x0f的意思,段选择子的格式如下:

    get_limit(0x0f)中的0x0f是段选择子,0x0f为 0000 0000 0000 1111,指定了LDT表中具有RPL=3,索引值为1,T1位为1,指定LDT表

    • copy_mem函数代码分析:
    • 拷贝当前进程的页目录和页表给新进程
    • 设置好新进程的ldt表
    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;
        
        //0x0f为代码段选择子
        //下面有对段选择子的说明
        code_limit=get_limit(0x0f);
        //0x17为数据段选择子
        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");
        //进程线形地址的基地址为 64M X 进程号
        new_data_base = new_code_base = nr * TASK_SIZE;
        p->start_code = new_code_base;
        //设置ldt[1]代码段的线性地址
        set_base(p->ldt[1],new_code_base);
        //设置ldt[2]数据段的线性地址
        set_base(p->ldt[2],new_data_base);
        //拷贝页表,在内存管理中详细说明
        if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
            free_page_tables(new_data_base,data_limit);
            return -ENOMEM;
        }
        return 0;
    }

    2. copy_process函数重点

    • 函数中新进程的状态改变,创建时设置为TASK_UNINTERRUPTIBLE,完成创建后,态设置为task_running
    • 新进程的内核堆栈的设置以及新进程的返回值
    p->tss.ep0 = PAGE_SIZE+ (long)p;            //设置新进程的内核堆栈
    p->tss.ss0 = 0x10;                          //内核数据段选择子
            ...
    p->tss.eax = 0;                             //新进程的返回值为0

    下表是任务状态段(tss)的字段表格,这里可以参考任务状态段的描述,tss是task_struct中的一个字段.

    进程的内核堆栈和用户堆栈的区别,内核堆栈分配在分配给task_struct结构的一页内存的顶端,也就是地址(long)p + PAGE_SIZE的位置,
    进程的内核堆栈示意如图:

    进程的用户堆栈示意图:

    • 设置进程的tss段和ldt段
      代码参考fork.c第130行

      //gdt为gdt表的首地址,nr<<1表示每个任务有两项状态段和局部段
      //p->tss表示tss在task_struct中的偏移
      //p->ldt表示ldt在task_struct中的偏移
      set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
      set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));

    每个任务都有一个tss段和ldt段,存放在gdt表中,如下图所示:

    三. 进程管理(创建)依赖内存管理子系统

    copy_process的第77行,用来分配一页物理内存(通常是4k)给新进程

    struct task_struct* p;
    ...
    p = (struct task_struct*)get_free_page();
  • 相关阅读:
    C语言程序设计I—第四周教学
    C语言程序设计I—第三周教学
    C语言程序设计I—第一周教学
    软工实践(四)——热词统计
    软工实践(三)——结对第一次作业(原型设计)
    软工实践(二)——构建之法读后感
    软工实践(一)——目标和规划
    庄子修身养性哲学
    $Matrix-Tree$定理-题目
    $Matrix-Tree$定理-理论
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6281358.html
Copyright © 2020-2023  润新知