Linux 0.11-fork 中进程基本信息的复制-27

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 0.11-fork 中进程基本信息的复制-27相关的知识,希望对你有一定的参考价值。

Linux 0.11-fork 中进程基本信息的复制-27


fork 中进程基本信息的复制

书接上回,上回书咱们说到,fork 触发系统调用中断,最终调用到了 sys_fork 函数,借这个过程介绍了一次系统调用的流程。

那今天我们回到正题,开始讲 fork 函数的原理,实际上就是 sys_fork 函数干了啥。

还是个汇编代码,但我们要关注的地方不多。

_sys_fork:
    call _find_empty_process
    testl %eax,%eax
    js 1f
    push %gs
    pushl %esi
    pushl %edi
    pushl %ebp
    pushl %eax
    call _copy_process
    addl $20,%esp
1:  ret

其实就是调用了两个函数。我们先从方法名直接翻译一下,猜猜意思。

先是 find_empty_process,就是找到空闲的进程槽位。

然后 copy_process,就是复制进程。

那妥了,这个方法的意思非常简单,因为存储进程的数据结构是一个 task[64] 数组,这个是在之前 第18回 | 大名鼎鼎的进程调度就是从这里开始的 sched_init 函数的时候设置的。

就是先在这个数组中找一个空闲的位置,准备存一个新的进程的结构 task_struct,这个结构之前在 一个新进程的诞生(三)如果让你来设计进程调度 也简单说过了。

struct task_struct 
    long state;
    long counter;
    long priority;
    ...
    struct tss_struct tss;

这个结构各个字段具体赋什么值呢?

通过 copy_process 这个名字我们知道,就是复制原来的进程,也就是当前进程。

当前只有一个进程,就是数组中位置 0 处的 init_task.init,也就是零号进程,那自然就复制它咯。

好了,以上只是我们的猜测,有了猜测再看代码会非常轻松,我们一个个函数看。

先来 find_empty_process

long last_pid = 0;

int find_empty_process(void) 
    int i;
    repeat:
        if ((++last_pid)<0) last_pid=1;
        for(i=0 ; i<64 ; i++)
            if (task[i] && task[i]->pid == last_pid) goto repeat;
    for(i=1 ; i<64; i++)
        if (!task[i])
            return i;
    return -EAGAIN;

一共三步,很简单。

第一步,判断 ++last_pid 是不是小于零了,小于零说明已经超过 long 的最大值了,重新赋值为 1,起到一个保护作用,这没什么好说的。

第二步,一个 for 循环,看看刚刚的 last_pid 在所有 task[] 数组中,是否已经被某进程占用了。如果被占用了,那就重复执行,再次加一,然后再次判断,直到找到一个 pid 号没有被任何进程用为止。

第三步,又是个 for 循环,刚刚已经找到一个可用的 pid 号了,那这一步就是再次遍历这个 task[] 试图找到一个空闲项,找到了就返回素组索引下标。

最终,这个方法就返回 task[] 数组的索引,表示找到了一个空闲项,之后就开始往这里塞一个新的进程吧。

由于我们现在只有 0 号进程,且 task[] 除了 0 号索引位置,其他地方都是空的,所以这个方法运行完,last_pid 就是 1,也就是新进程被分配的 pid 就是 1,然后即将要加入的 task[] 数组的索引位置,也是 1。

好的,那我们接下来就看,怎么构造这个进程结构,塞到这个 1 索引位置的 task[] 中?

来看 copy_process 方法。

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;


    p = (struct task_struct *) get_free_page();
    if (!p)
        return -EAGAIN;
    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;
    p->tss.eip = eip;
    p->tss.eflags = eflags;
    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;
    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;
    p->tss.ldt = _LDT(nr);
    p->tss.trace_bitmap = 0x80000000;
    if (last_task_used_math == current)
        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
    if (copy_mem(nr,p)) 
        task[nr] = NULL;
        free_page((long) p);
        return -EAGAIN;
    
    for (i=0; i<NR_OPEN;i++)
        if (f=p->filp[i])
            f->f_count++;
    if (current->pwd)
        current->pwd->i_count++;
    if (current->root)
        current->root->i_count++;
    if (current->executable)
        current->executable->i_count++;
    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;

艾玛,这也太多了!

别急,大部分都是 tss 结构的复制,以及一些无关紧要的分支,看我简化下。

int copy_process(int nr, ...) 
    struct task_struct p = 
        (struct task_struct *) get_free_page();
    task[nr] = p;
    *p = *current;

    p->state = TASK_UNINTERRUPTIBLE;
    p->pid = last_pid;
    p->counter = p->priority;
    ..
    p->tss.edx = edx;
    p->tss.ebx = ebx;
    p->tss.esp = esp;
    ...
    copy_mem(nr,p);
    ...
    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;
    return last_pid;

这个函数本来就是 fork 的难点了,所以我们慢慢来。

首先 get_free_page 会在主内存末端申请一个空闲页面,还记得我们之前在 第13回 内存初始化 mem_init 里是怎么管理内存的吧?

那 get_free_page 这个函数就很简单了,就是遍历 mem_map[] 这个数组,找出值为零的项,就表示找到了空闲的一页内存。然后把该项置为 1,表示该页已经被使用。最后,算出这个页的内存起始地址,返回。

然后,拿到的这个内存起始地址,就给了 task_struct 结构的 p。

int copy_process(int nr, ...) 
    struct task_struct p = 
        (struct task_struct *) get_free_page();
    task[nr] = p;
    *p = *current;
    ...

于是乎,一个进程结构 task_struct 就在内存中有了一块空间,但此时还没有赋值具体的字段。别急。

首先将这个 p 记录在进程管理结构 task[] 中。

然后下一句 *p = *current 很简单,就是把当前进程,也就是 0 号进程的 task_struct 的全部值都复制给即将创建的进程 p,目前它们两者就完全一样了。

嗯,这就附上值了,就完全复制之前的进程的 task_struct 而已,很粗暴。

最后的内存布局的效果就是这样。

然后,进程 1 和进程 0 目前是完全复制的关系,但有一些值是需要个性化处理的,下面的代码就是把这些不一样的值覆盖掉。

int copy_process(int nr, ...) 
    ...
    p->state = TASK_UNINTERRUPTIBLE;
    p->pid = last_pid;
    p->counter = p->priority;
    ..
    p->tss.edx = edx;
    p->tss.ebx = ebx;
    p->tss.esp = esp;
    ...
    p->tss.esp0 = PAGE_SIZE + (long) p;
    p->tss.ss0 = 0x10;
    ...

不一样的值,一部分是 statepidcounter 这种进程的元信息,另一部分是 tss 里面保存的各种寄存器的信息,即上下文

这里有两个寄存器的值的赋值有些特殊,就是 ss0 和 esp0,这个表示 0 特权级也就是内核态时的 ss:esp 的指向。

根据代码我们得知,其含义是将代码在内核态时使用的堆栈栈顶指针指向进程 task_struct 所在的 4K 内存页的最顶端,而且之后的每个进程都是这样被设置的。

进程在用户态使用的栈可以被对应用户程序随意摆弄,但是进程在内核态对应的内核栈,则是由内核负责操作的,用于进程切换。

好了,进程槽位的申请,以及基本信息的复制,就讲完了。

今天就这么点内容,就是内存中找个地方存一个 task_struct 结构的东东,并添加到 task[] 数组里的空闲位置处,这个东东的具体字段赋值的大部分都是复制原来进程的

接下来将是进程页表和段表的复制,这将会决定进程之间的内存规划问题,很是精彩,也是 fork 真正的难点所在。

要看懂为什么子进程大部分属性都是copy父进程的,那么就需要弄明白fork系统调用执行后,会产生什么效果: fork和exec函数解析

欲知后事如何,且听下回分解。


转载

本文转载至闪客图解操作系统系列文章

以上是关于Linux 0.11-fork 中进程基本信息的复制-27的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程监控系统

Linux的进程管理基本指令

Linux基本命令之ps

linux中的进程于计划任务管理

中秋节爆肝万字,带你熟悉Linux进程的基本操作!!!

中秋节爆肝万字,带你熟悉Linux进程的基本操作!!!