Chinaunix首页 | 论坛 | 博客
  • 博客访问: 98084
  • 博文数量: 16
  • 博客积分: 1113
  • 博客等级: 少尉
  • 技术积分: 170
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-31 13:36
文章分类

全部博文(16)

文章存档

2011年(4)

2010年(12)

分类: LINUX

2010-08-15 14:38:48

接下来我们继续分析copy_process() 函数!

 

p->pid = pid;

 

p->tgid = p->pid;

新建进程的线程组号为新建进程的进程号!

这里有一个问题需要注意:

对于普通进程,线程组号tgid和进程号pid是相同的;对于拥有多个线程的进程来说,进程第一个创建的线程的线程组号tgid 和进程号pid相同,而随后创建的其他线程的tgid 为第一个创建线程的进程号pid。当我们调用sys_getpid()取得进程的进程号时,此函数返回的是当亲进程的线程组号tgid!

 

retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);

 

为新建进程设置进程描述符中成员变量thread!该成员变量保存了重要的信息,进程使用的内核栈等信息

int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,

    unsigned long unused,

    struct task_struct * p, struct pt_regs * regs)

{

    struct pt_regs * childregs;

    struct task_struct *tsk;

    int err;

 

    childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;

childregs指向新创建的进程的内核栈(栈底减去一个struct pt_regs 结构体的大小)

的内核栈处!

   

    childregs = (struct pt_regs *) ((unsigned long) childregs - 8);

childregs再指向childregs减去8字节的内核栈

 

    *childregs = *regs;

将之前设置的假的硬件上下文信息保存到新创建进程的内核栈中

此结构中保存着新创建进程的执行的代码指针

 

    childregs->eax = 0;

pt_regs结构中的eax字段设为0,这就是为什么fork()系统调用返回时子进程得到的返回值是0的原因。在新创建的进程得到处理器时,在执行真正的新创建进程的代码前,会把内核栈中pt_regs结构中内容弹出,而childregs->eax 被弹出到eax寄存器中,所以返回值是0

    childregs->esp = esp;

根据参数esp更新新创建进程的继承自父进程的用户态栈栈指针esp,在新创建进程返

回到用户态时,其将作为新创建进程用户态栈的栈顶指针寄存器esp的值!

因为现在的情况是创建进程1,当最后执行iret指令后,从栈中弹出cs,ip以及EFLAGS

的内容,此时cpu检查并没有发生特权级的改变所以不会弹出ss,esp的内容!也就是

说进程1是一个内核态进程,根本就没有用户态!

 

    p->thread.esp = (unsigned long) childregs;

    更新新创建进程描述符的thread成员变量,用来保存进程的硬件上下文。在switch_to()执行进程切换时,会根据thread中内容来判断新进程的执行地址!

    thread.esp为内核栈栈顶。当新建进程在内核态执行时,会根据此字段设置内核态栈。

 

    p->thread.esp0 = (unsigned long) (childregs+1);

    该成员变量指向内核态栈低减8字节。在通过中断门、陷阱门进入到内核态时,处理器的控制器会根据任务状态段esp0的值来设置内核态栈顶。

 

    p->thread.eip = (unsigned long) ret_from_fork;

    thread成员变量eip的值为标记ret_from_fork()的地址。在新创建进程获得处理器时,内核调度器schedule()会根据thread中的信息来设置硬件上下文,设置任务状态段中的内核态栈栈底字段ESP0为此处成员变量esp0的值;设置处理器的栈指针寄存器esp为此处的成员变量esp的值;设置eip为此处成员变量eip的值。在新创建进程获得处理器时,将从代码ret_from_fork()开始执行!

 

    为了更好的分析栈中情况,图如下:

 

    栈底的8字节空间废掉了,留作他用!

   

    savesegment(fs,p->thread.fs);

    savesegment(gs,p->thread.gs);

    fs,ds段寄存器内容保存到 thread中!

 

    tsk = current;

    if (unlikely(NULL != tsk->thread.io_bitmap_ptr)) {

        p->thread.io_bitmap_ptr = kmalloc(IO_BITMAP_BYTES, GFP_KERNEL);

        if (!p->thread.io_bitmap_ptr) {

            p->thread.io_bitmap_max = 0;

            return -ENOMEM;

        }

        memcpy(p->thread.io_bitmap_ptr, tsk->thread.io_bitmap_ptr,

            IO_BITMAP_BYTES);

    }

    在父进程包含I/O访问许可权限位图的情况下,使新创建进程继承父进程的I/O访问许可权限位图!

 

    /*

     * Set a new TLS for the child thread?

     */

    if (clone_flags & CLONE_SETTLS) {

        struct desc_struct *desc;

        struct user_desc info;

        int idx;

 

        err = -EFAULT;

        if (copy_from_user(&info, (void __user *)childregs->esi, sizeof(info)))

            goto out;

        err = -EINVAL;

        if (LDT_empty(&info))

            goto out;

 

        idx = info.entry_number;

        if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)

            goto out;

 

        desc = p->thread.tls_array + idx - GDT_ENTRY_TLS_MIN;

        desc->a = LDT_entry_a(&info);

        desc->b = LDT_entry_b(&info);

    }

在参数clone_flags包含CLONE_SETTLS标记的情况下,设置进程的TLS

在此不详细分析!

 

    err = 0;

 out:

    if (err && p->thread.io_bitmap_ptr) {

        kfree(p->thread.io_bitmap_ptr);

        p->thread.io_bitmap_max = 0;

    }

 

    return err;

    返回错误号!

}

 

 

sched_fork(p);

初始化新建进程成员变量sched_info,设置运行状态为TASK_RUNNING;进程抢占标志设为1,禁止内核抢占,将父进程所剩时间片给子进程分配一半!

 

attach_pid(p, PIDTYPE_PID, p->pid);

attach_pid(p, PIDTYPE_TGID, p->tgid);

将新创建进程描述符添加到系统进程号的哈希表中,使得可通过进程号快速找到相应进程描述符地址!

 

fork_out:

    if (retval)

        return ERR_PTR(retval);

    return p;

最后返回新创进程的进程描述符!

 

 

copy_process()函数结束后返回到do_fork()函数中!

do_fork()中有这样一段代码:

 

        if (!(clone_flags & CLONE_STOPPED))

            wake_up_new_task(p, clone_flags);

        else

            p->state = TASK_STOPPED;

 

若含有CLONE_STOPPED 标记则将新创建进程状态设置为 TASK_STOPPED ,说明新创建的进程不会运行,除非有进程通过信号唤醒它!

        若没有 把当前进程添加到当前处理器队列中!

 

 

马上进入wake_up_new_task()函数!

阅读(1945) | 评论(0) | 转发(0) |
0

上一篇:读内核感悟!

下一篇:wake_up_new_task() 函数

给主人留下些什么吧!~~