Linux 2.6.12 内核 进程 1 的创建(七)

2011-11-28 21:08:38 +0000

下面进行 schedule()函数的简单分析!

此时我们仍然在进程 0 中,由进程 0 调用 schedule() 进行进程 0 和进程 1 间的切换!

asmlinkage void __sched schedule(void)
{
    long *switch_count;

    task_t *prev, *next;

    runqueue_t *rq;

    prio_array_t *array;

    struct list_head *queue;

    unsigned long long now;

    unsigned long run_time;

    int cpu, idx;

    /*
     * Test if we are atomic.  Since do_exit() needs to call into
     * schedule() atomically, we ignore that path for now.
     * Otherwise, whine if we are scheduling when we should not be.
     */

    if (likely(!current->exit_state)) {
        if (unlikely(in_atomic())) {
            printk(KERN_ERR "scheduling while atomic: "
                "%s/0x%08x/%d\n",
                current->comm, preempt_count(), current->pid);

            dump_stack();
        }
    }

    profile_hit(SCHED_PROFILING, __builtin_return_address(0));

need_resched:

    preempt_disable();
    禁止内核抢占

    prev = current;
    将进程 0 的进程描述符指针赋给 prev

    release_kernel_lock(prev);
    释放大内核锁

need_resched_nonpreemptible:

    rq = this_rq();
    获得当前cpu运行队列

    /*
     * The idle thread is not allowed to schedule!
     * Remove this check after it has been exercised a bit.
     */
    if (unlikely(prev == rq->idle) && prev->state != TASK_RUNNING) {
    如果当前运行进程为进程 0 并且运行状态不是 TASK_RUNNING

        printk(KERN_ERR "bad: scheduling from the idle thread!\n");

        dump_stack();
    }

    schedstat_inc(rq, sched_cnt);

    rq->sched_cnt++

    now = sched_clock();
    返回当前时间 ns 级

    if (likely((long long)(now - prev->timestamp) < NS_MAX_SLEEP_AVG)) {
    若当前系统时间减去进程最近插入运行队列时间小于小于平均睡眠时间

        run_time = now - prev->timestamp;   

        if (unlikely((long long)(now - prev->timestamp) < 0))
            run_time = 0;
    } else

        run_time = NS_MAX_SLEEP_AVG; 

    /*
     * Tasks charged proportionately less run_time at high sleep_avg to
     * delay them losing their interactive status
     */

    run_time /= (CURRENT_BONUS(prev) ? : 1);
    CURRENT_BONUS 返回 0-10 之间的值,它与进程的平均睡眠时间是成比例的!
    run_time 用于限制进程对CPU的使用!

    spin_lock_irq(&rq->lock); 

    if (unlikely(prev->flags & PF_DEAD))
        prev->state = EXIT_DEAD;
    判断进程是否是正在被终止的进程

    switch_count = &prev->nivcsw;
    上下文切换数

    if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
    如果当前进程运行状态不是TASK_RUNNING 并且没有在内核态被抢占

        switch_count = &prev->nvcsw;

        if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
                unlikely(signal_pending(prev))))
       若进程为非阻塞挂起信号,而且状态为TASK_INTERRUPTIBLE

            prev->state = TASK_RUNNING;

        else {

            否则若当前进程运行状态为 TASK_UNINTERRUPTIBLE 从cpu运行队列中删除当前进程,当前进程编程睡眠状态,等待由某个进程调用 wake_up 将其唤醒并插入到cpu运行队列中!

            if (prev->state == TASK_UNINTERRUPTIBLE)
                rq->nr_uninterruptible++;

            deactivate_task(prev, rq);

        }

    }

    cpu = smp_processor_id();

    if (unlikely(!rq->nr_running)) {
    若cpu运行队列中包含可运行进程数为 0

go_idle:

        idle_balance(cpu, rq);

        if (!rq->nr_running) {
            next = rq->idle;

            rq->expired_timestamp = 0;

            wake_sleeping_dependent(cpu, rq);

            /*
             * wake_sleeping_dependent() might have released
             * the runqueue, so break out if we got new
             * tasks meanwhile:
             */

            if (!rq->nr_running)
                goto switch_tasks;

        }

    } else {

        if (dependent_sleeper(cpu, rq)) {
        由于没有选中内核选项 CONFIG_SCHED_SMT 所以此函数直接返回0

            next = rq->idle;

            goto switch_tasks;
        }

        /*
         * dependent_sleeper() releases and reacquires the runqueue
         * lock, hence go into the idle loop if the rq went
         * empty meanwhile:
         */

        if (unlikely(!rq->nr_running))
            goto go_idle;
    }

    array = rq->active;
    指向cpu运行队列活动优先级数组

    if (unlikely(!array->nr_active)) {
    若活动运行数组中可运行进程数位0,在创建进程 1 的过程中已经 ++

        /*
         * Switch the active and expired arrays.
         */

        schedstat_inc(rq, sched_switch);

        rq->active = rq->expired;

        rq->expired = array;

        array = rq->active;

        rq->expired_timestamp = 0;

        rq->best_expired_prio = MAX_PRIO;

    }

 

    idx = sched_find_first_bit(array->bitmap);
    从活动优先级数组的优先级位图中找到第一个不为 0 的bit 位序号

    queue = array->queue + idx;
    指向对应优先级相同的进程链表

    next = list_entry(queue->next, task_t, run_list);
    算出要被切换到的进程的进程描述符地址

    if (!rt_task(next) && next->activated > 0) {
        unsigned long long delta = now - next->timestamp;

        if (unlikely((long long)(now - next->timestamp) < 0))
            delta = 0;

        if (next->activated == 1)
            delta = delta * (ON_RUNQUEUE_WEIGHT * 128 / 100) / 128;

        array = next->array;

        dequeue_task(next, array);

        recalc_task_prio(next, next->timestamp + delta);

        enqueue_task(next, array);

    }

    next->activated = 0;
    进程被唤醒时所使用的条件代码

switch_tasks:

    if (next == rq->idle)
        schedstat_inc(rq, sched_goidle);

    prefetch(next);
    提示cpu控制单元把next的进程描述符的第一部分内容装入到硬件高速缓存

    clear_tsk_need_resched(prev);
    清除当前进程(进程 0)的 TIF_NEED_RESCHED 标志

    rcu_qsctr_inc(task_cpu(prev)); 

    update_cpu_clock(prev, rq, now);
    更新当前进程 sched_time 字段

    prev->sleep_avg -= run_time;
    减少平均睡眠时间

    if ((long)prev->sleep_avg <= 0)
        prev->sleep_avg = 0;

    prev->timestamp = prev->last_ran = now;
    更新时间戳

    sched_info_switch(prev, next);
    更新cpu运行队列的字段

    if (likely(prev != next)) {
        next->timestamp = now;

        rq->nr_switches++;
        更新当前处理器上进行进程切换的数目

        rq->curr = next;
        更新运行队列当前运行进程为进程 1

        ++*switch_count;
        更新当前进程nivcsw字段

        prepare_arch_switch(rq, next);

        prev = context_switch(rq, prev, next);
        进程上下文切换

        barrier();

        finish_task_switch(prev);      

    } else

      此时表示当前 cpu 运行队列中没有优先级较高或相等的其他活动进程

        spin_unlock_irq(&rq->lock);

    prev = current;

    if (unlikely(reacquire_kernel_lock(prev) < 0))
        goto need_resched_nonpreemptible;

    preempt_enable_no_resched();

    if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
        goto need_resched;
     检查是否一些其他进程设置了当前进程的 TIF_NEED_RESCHED 标志。若设置了,则重新调度

}
#define NS_MAX_SLEEP_AVG        (JIFFIES_TO_NS(MAX_SLEEP_AVG))</pre>
<p></p>


schedule() 分析完毕!其中几个重要的函数(context_switch())以后会分析。

Linux 2.6.12 内核 进程 1 的创建(六)

2011-11-28 20:48:35 +0000

现在进程 1 已经创建完成!但还没有执行,此时运行的仍然是进程 0

回到rest_init()函数:

static void noinline rest_init(void)
    __releases(kernel_lock)
{
    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
    此函数已执行完毕!

    numa_default_policy();
    空函数

    unlock_kernel();
    解锁大内核锁

    preempt_enable_no_resched();
    由于没有开启内核抢占,此函数为空!

    cpu_idle();
}</pre>
<p></p>


好,下面进入cpu_idle() 函数的分析,分析这个有去无回的函数!


    
    void cpu_idle (void)
    {
        /* endless idle loop with no priority at all */
        while (1) {
    
            while (!need_resched()) {
            首先检测当前进程,也就是进程0有没有设置 TIF_NEED_RESCHED 标志。表示当前进程需要立刻被调度!若设置则立刻调用下面的 schedule() 函数进行调度,否则进入循环!
    
            在此时进程0已经设置了 TIF_NEED_RESCHED 标志!还记得在哪设置的吗?没错就在 init_idle() 函数中:set_tsk_need_resched(idle) 此函数设置的!在 init_idle() 函数中还设置了进程0的优先级(最小)等等……
    
            注意进程0描述符不在 cpu 的运行队列中,所以进程0并不参与调度!
    
                void (*idle)(void);
                定义一个函数指针
     
                if (__get_cpu_var(cpu_idle_state))
                    __get_cpu_var(cpu_idle_state) = 0;
                声明了一个每处理器变量 per_cpu_cpu_idle_state ,类型为 unsigned int!负责记录每个 cpu 的 idle 进程的状态信息!
    
                rmb();
                内存屏障原语
    
                idle = pm_idle;
                idle应该为NULL
    
                if (!idle)
                    idle = default_idle;
                此时idle指向函数default_idle     
    
                __get_cpu_var(irq_stat).idle_timestamp = jiffies;
                更新每 cpu 变量 per_cpu_irq_stat 中 idle_timestamp 字段值为系统时钟中断次数!此字段好像表示进程执行idle() 函数也就是 default_idle() 函数的时间?(不确定)
    
                idle();
                若当前进程一直没设置TIF_NEED_RESCHED 标志,则就一直循环调用idle()函数,直到设置了为止!
    
            }
    
            schedule();
            进行进程调度,此时目的调度进程1的执行!
        }
    }</pre>
    <p></p>
    
    
        
        static inline int need_resched(void)
        {
                return unlikely(test_thread_flag(TIF_NEED_RESCHED));
        }</pre>
        <p></p>
        
        
            
            static inline int test_thread_flag(int flag)
            {
                    return test_bit(flag,&current_thread_info()->flags);
            }</pre>
            <p>  
            检测当前进程描述符中 thread_info 字段指向的 thread_info 结构中 flags 字段是否设置 TIF_NEED_RESCHED 标志</p>
            
            
                
                static DEFINE_PER_CPU(unsigned int, cpu_idle_state)
                
                #define DEFINE_PER_CPU(type, name) \
                    __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name </pre>
                <p></p>
                
                
                    
                    void default_idle(void)
                    {
                            if (!hlt_counter && boot_cpu_data.hlt_works_ok) {
                    
                                    local_irq_disable();
                                    禁止中断!
                    
                                    if (!need_resched())
                                    再次判断当前进程是否设置需要调度标志
                    
                                            safe_halt();
                                            若还没设置此标志则执行开中断和 hlt 指令!
                                            对于hlt指令本人并不是很清楚,也从来没用过。Google一下:
                    
                                            当 CPU 执行 HLT 指令时,CS 和 IP 指向 HLT 之后要执行的一条指令的地址,而CPU则处于“空操作”的暂停状态。此时,如果发生外部硬件中断,在CPU响应中断执行中断服务程序后,将返回到 HLT 后面的一条指令。所以,简要地说,HLT 指令的执行实际上是用软件方法使CPU处于暂停状态等待硬件中断,而硬件中断的发生又使CPU退出暂停状态。
                    
                    除了外部硬件中断会使CPU退出暂停状态外,对系统进行复位操作,也会使CPU退出暂停状态。
                    
                                    else
                    
                                            local_irq_enable();
                                            若此时终于设置了此标志则开中断,返回执行调度程序!
                    
                            } else {
                    
                                    cpu_relax();
                                    执行一些空操作
                            }
                    }</pre>
                    <p></p>
                    
                    
                        
                        #define safe_halt()             __asm__ __volatile__("sti; hlt": : :"memory")\
                        
                        #define cpu_relax()     rep_nop()
                        
                        static inline void rep_nop(void)
                        {
                                __asm__ __volatile__("rep;nop": : :"memory");
                        }</pre>
                        <p></p>
                        
                        
                        接下来我们就应该分析schedule() 函数了,个人认为此函数非常难懂,我们下回分析吧!

Linux 2.6.12 内核 进程 1 的创建(五)

2011-11-28 18:40:47 +0000

现在进程 1 所需要的我们都已经创建好了。可以说是万事俱备只欠东风!

那么怎么能让进程1运行呢,我们必须把进程1的进程描述符插入到 cpu 的活动运行队列中去,这样只要再调用 schedule() 那么进程1便可运行啦!废话少说马上进入 wake_up_new_task() 函数!

void fastcall wake_up_new_task(task_t * p, unsigned long clone_flags)
{
    把新创建进程添加到 cpu 运行队列中去

    unsigned long flags;

    int this_cpu, cpu;

    runqueue_t *rq, *this_rq; 

    rq = task_rq_lock(p, &flags);
    锁住运行队列结构。并获得cpu运行队列指针!

    cpu = task_cpu(p);
    获得新创建进程的 cpu 编号

    this_cpu = smp_processor_id();
    获得当前cpu编号

    BUG_ON(p->state != TASK_RUNNING);

    /*
     * We decrease the sleep average of forking parents
     * and children as well, to keep max-interactive tasks
     * from forking tasks that are max-interactive. The parent
     * (current) is done further down, under its lock.
     */

    p->sleep_avg = JIFFIES_TO_NS(CURRENT_BONUS(p) *
        CHILD_PENALTY / 100 * MAX_SLEEP_AVG / MAX_BONUS);
    计算进程的平均睡眠值

    p->prio = effective_prio(p);
    设置进程的动态优先权(100-139)
    进程的静态优先级 static_prio 用于计算进程的时间片,而动态优先级作为调度器选择合适的进程的依据!

    if (likely(cpu == this_cpu)) {

        if (!(clone_flags & CLONE_VM)) {
            /*
             * The VM isn't cloned, so we're in a good position to
             * do child-runs-first in anticipation of an exec. This
             * usually avoids a lot of COW overhead.
             */

            if (unlikely(!current->array))
                __activate_task(p, rq);
            else {
                p->prio = current->prio;

                list_add_tail(&p->run_list, &current->run_list);

                p->array = current->array;

                p->array->nr_active++;

                rq->nr_running++;

            }

            set_need_resched();

        } else

            /* Run child last */
            __activate_task(p, rq);
            添加到runqueue中

        /*
         * We skip the following code due to cpu == this_cpu
         *
         *   task_rq_unlock(rq, &flags);
         *   this_rq = task_rq_lock(current, &flags);
         */

        this_rq = rq;

    } else {
        this_rq = cpu_rq(this_cpu);

        /*
         * Not the local CPU - must adjust timestamp. This should
         * get optimised away in the !CONFIG_SMP case.
         */

        p->timestamp = (p->timestamp - this_rq->timestamp_last_tick)
                    + rq->timestamp_last_tick;

        __activate_task(p, rq);

        if (TASK_PREEMPTS_CURR(p, rq))
            resched_task(rq->curr);

        /*
         * Parent and child are on different CPUs, now get the
         * parent runqueue to update the parent's ->sleep_avg:
         */

        task_rq_unlock(rq, &flags);

        this_rq = task_rq_lock(current, &flags);

    }

    current->sleep_avg = JIFFIES_TO_NS(CURRENT_BONUS(current) *
        PARENT_PENALTY / 100 * MAX_SLEEP_AVG / MAX_BONUS);

    task_rq_unlock(this_rq, &flags);

}</pre>
<p></p>


<pre class="brush: cpp; title: ; notranslate" title=""> static inline runqueue_t *task_rq_lock(task_t *p, unsigned long *flags)
__acquires(rq-&gt;lock) {
struct runqueue *rq;

repeat_lock_task:

local_irq_save(*flags);

rq = task_rq(p);

spin_lock(&rq-&gt;lock);

if (unlikely(rq != task_rq(p))) {

    spin_unlock_irqrestore(&rq-&gt;lock, *flags);

    goto repeat_lock_task;

}

return rq; }

</pre>

<pre class="brush: cpp; title: ; notranslate" title=""> #define task_rq(p)      cpu_rq(task_cpu(p)) #define cpu_rq(cpu)     (&per_cpu(runqueues, (cpu)))

</pre>

取得每cpu变量per_cpu_runqueues本地拷贝的地址


</p>
<pre>
static inline unsigned int task_cpu(const struct task_struct *p)
{
    return p->thread_info->cpu;
}</pre>


</p>
<pre>
static int effective_prio(task_t *p)
{
    int bonus, prio;

    if (rt_task(p))
        return p->prio;
    首先判断是否为实时进程!(prio<100)

    bonus = CURRENT_BONUS(p) - MAX_BONUS / 2;
    获得创建进程的奖惩

    prio = p->static_prio - bonus;

    if (prio < MAX_RT_PRIO)
        prio = MAX_RT_PRIO;

    if (prio > MAX_PRIO-1)
        prio = MAX_PRIO-1;

    return prio;
}</pre>


</p>
<pre>
static inline void __activate_task(task_t *p, runqueue_t *rq)
{
    enqueue_task(p, rq->active);

    rq->nr_running++;
    更新队列中可运行进程数!
}</pre>


</p>
<pre>
static void enqueue_task(struct task_struct *p, prio_array_t *array)
{
    sched_info_queued(p);

    list_add_tail(&p->run_list, array->queue + p->prio);
    根据进程的动态优先级,把进程描述符添加到活动链表中!

    __set_bit(p->prio, array->bitmap);
    根据 prio 把 active 链表中相应进程位图置 1!位数代表优先级,表示此优先级有活动的进程!schedule() 函数正是根据位图中的哪位是1,到相应数组链表中去选择下一个要运行的进程!

    array->nr_active++;
    更新优先级链表中可运行进程数目!

    p->array = array;
    进程描述符中的array字段指向活动进程链表
}</pre>


至此,进程 1 成功创建完毕,但是创建归创建(只是创建了一些数据结构,并且添加到 cpu 运行队列中去),此时新创建的进程还没有运行,接下来就是 schedule() 这个主调度器的事情了 。。。。。。

Linux 2.6.12 内核 进程 1 的创建(四)

2011-11-28 12:49:59 +0000

下面分析一下 copy_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() 开始执行!

    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;
    返回错误号!
}

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

process_switch_stack

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

copy_process() 函数结束后返回到 do_fork() 函数中! 在do_fork()中有这样一段代码:

`</p>

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() 函数!

Linux 2.6.12 内核 进程 1 的创建(三)

2011-11-27 22:09:51 +0000

重头戏来了!copy_process() 函数完成父进程描述符中相关资源到子进程中的复制工作,并对关键资源进行了修改,以标识新生成的子进程!

由于此函数非常之长,在此就不列出完整源代码,只对关键部分进行分析!

`</p>

static task_t *copy_process(unsigned long clone_flags,unsigned long stack_start,
                 struct pt_regs *regs,
                 unsigned long stack_size,
                 int __user *parent_tidptr,
                 int __user *child_tidptr,
                 int pid)
{
       首先对参数 clone_flags 的合理性进行验证!

       p = dup_task_struct(current);
       复制一份当前进程的描述符的拷贝!

       ...... 

       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 !该成员变量保存了重要的信息,进程使用的内核栈等信息

       ......

       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 函数结束了它的使命 ……

`</p>

static struct task_struct *dup_task_struct(struct task_struct *orig)
{
       struct task_struct *tsk;
       struct thread_info *ti;

       prepare_to_copy(orig);
       在当前进程使用FPU的情况下,保存当前进程的 FPU 上下文内容到进程的成员变量 thread_info 中

       tsk = alloc_task_struct();
       申请 sizeof(struct task_struct) 大小内存空间,用作新创建进程的进程描述符!

       if (!tsk)
           return NULL;

       ti = alloc_thread_info(tsk);
       为新创建的进程申请 thread_info 结构所需的内存空间! 

       if (!ti) {
           free_task_struct(tsk);
           return NULL;
       }

       *ti = *orig->thread_info;
       将当前进程的 thread_info 结构中内容赋值给新创建的进程的 thread_info 结构中(进程1)!

       *tsk = *orig;
       同样道理赋值 task_struct 结构

       tsk->thread_info = ti;
       修改新创建进程进程描述符的 thread_info 字段,使其指向新创建的 thread_info 结构!

       ti->task = tsk;
       同样道理修改 thread_info 结构的 task 字段使其指向 tss_struct 结构!

       /* One for us, one for whoever does the "release_task()" (usually parent) */
       atomic_set(&tsk->usage,2);
       设置新创建子进程的进程描述符的引用计数usage为2。其中一个代表本进程对该进程描述符的引用计数;另一个是父进程的引用计数

       return tsk;
}`

`</p>

# define alloc_task_struct()    kmem_cache_alloc(task_struct_cachep,GFP_KERNEL)`

从 task_struct_cachep 高速缓存描述符中分配一个对象!

在 fork_init() 函数中 已经创建好了task_struct 结构专用的缓冲队列!

`</p>

#define alloc_thread_info(tsk)                  \
({                          \
        struct thread_info *ret;            \
                                \
        ret = kmalloc(THREAD_SIZE, GFP_KERNEL);     \
        if (ret)                    \
            memset(ret, 0, THREAD_SIZE);        \
        ret;                        \
})`

从普通缓存中申请 thread_info 大小的内存空间,并清 0

copy_thread 函数对于理解内核栈很重要,所以接下来分析!