内核进程创建之分配task_struct(do_fork->copy_process->dup_task_struct())

来源:互联网 发布:淘宝店铺页头图片 编辑:程序博客网 时间:2024/06/05 14:56
static struct task_struct *dup_task_struct(struct task_struct *orig){struct task_struct *tsk;//sizeof(task_struct) = 3236;这个值是通过gdb得到的,                                        //可以看到单个的task_struct的大小已经超过了3K,但是系统最开始只为一个进程分配了两页的空间,thread_info在                                        //页的开始的地方,页的高端地址即是该进程的内核堆栈。thread_info结构体的第一个指针指向其task_struct结构。        struct thread_info *ti;//sizeof(thead_info) = 72unsigned long *stackend;int node = tsk_fork_get_node(orig); //get node information for about to be created task;                                 //如果使用了NUMA技术,则返回orig中的pref_node_fork字段;否则返回numa_node_id,因为没有定义NUMA所以是0        int err;prepare_to_copy(orig);//arch/x86/kernel/process_32.c:187:从代码看来这个函数主要以非抢占的方式来设置与FPU相关的东西                                //不过,注意这个宏:#define task_thread_info(task)    ((struct thread_info *)(task)->stack)/*kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node)告诉我们,系统是从专业高速缓存中分配空间的,注意,在分配thred_info及相关的页面时,是从分配的page。这里涉及到内存管理的知识,我们以后再讲。*/        tsk = alloc_task_struct_node(node); // include的/linux/slab.h --> mm/slub.cif (!tsk)return NULL;        ti = alloc_thread_info_node(tsk, node); //ti指向thread_info的首地址,同时也是系统为新进程分配的两个连续页面的首地址。/*调用宏,可以看到这部分的内存是分配的正常的页面。#define alloc_thread_info_node(tsk, node) \({ \struct page *page = alloc_pages_node(node, THREAD_FLAGS, \THREAD_ORDER); \struct thread_info *ret = page ? page_address(page) : NULL; \\ret; //返回两个连续页面的首地址 \})#define THREAD_FLAGS (GFP_KERNEL | __GFP_NOTRACK)//意思是这个分配代表运行在内核空间的进程而进行的#define THREAD_ORDER    1                        //分配连续的两个页面 */       if (!ti) {free_task_struct(tsk);return NULL;}        err = arch_dup_task_struct(tsk, orig); // arch/x86/kernel/process.c:33 ; copy the parent's task_struct to child's task_struct.       if (err)goto out;       tsk->stack = ti; //child's threadinfo pointer points in its task_struct points to its parent's threadinfo       /* task_struct的stack字段指向本进程的thread_info结构的首地址,即指向内核为进程分配的两个连续页面的首地址*/ /*       将父进程的task_struct中的内容拷贝到子进程的task_struct中/。 */              err = prop_local_init_single(&tsk->dirties); //???if (err)goto out;setup_thread_stack(tsk, orig); //include/linux/sched.h:2385;copy parent's thread_info to child's thread_info,并检查设置FPU相关项。/*static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org){    *task_thread_info(p) = *task_thread_info(org); //将父进程thread_info中的内容完全复制到子进程相应的字段中    task_thread_info(p)->task = p;                 //但是,还是要将子进程thread_info字段中的task_struct字段指向子进程自己的task_struct结构}clear_user_return_notifier(tsk);  //include/linux/thread_info.h:69;从用户空间返回时不通知内核???/*static inline void clear_user_return_notifier(struct task_struct *p){    clear_tsk_thread_flag(p, TIF_USER_RETURN_NOTIFY);TIF_USER_RETURN_NOTIFY indicates notify kernel of userspace return */        clear_tsk_need_resched(tsk);  //不允许调度该进程/*    clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);    TIF_NEED_RESCHED indicates that the process should be or would like to be replaced with    another process by the scheduler.*/        stackend = end_of_stack(tsk); //注意stackend的计算方法哦。/*static inline unsigned long *end_of_stack(struct task_struct *p){    return (unsigned long *)(task_thread_info(p) + 1); //这个地方+1,则指针向上移动了sizeof(thread_info)指向了系统堆栈的最大下界。}*/*stackend = STACK_END_MAGIC; /* for overflow detection */STACK_END_MAGIC是一个栈溢出标记。#ifdef CONFIG_CC_STACKPROTECTORtsk->stack_canary = get_random_int();#endif/* One for us, one for whoever does the "release_task()" (usually parent) */atomic_set(&tsk->usage,2); /*  将task_struct的usage字段表明有几个进程正在使用该结构,初始化为2,表明一个是进程本身的,而另一个则是表明父进程也使用该结构。*/       atomic_set(&tsk->fs_excl, 0); //禁止该进程独占文件系统#ifdef CONFIG_BLK_DEV_IO_TRACEtsk->btrace_seq = 0;#endiftsk->splice_pipe = NULL;account_kernel_stack(ti, 1); //???/*static void account_kernel_stack(struct thread_info *ti, int account){    struct zone *zone = page_zone(virt_to_page(ti));  //获取thread_info所在的zone结构。    mod_zone_page_state(zone, NR_KERNEL_STACK, account);}调用的第二个函数与内存管理机制有关,以后再说,重点在第一个函数。#define virt_to_page(kaddr)    pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)这里:__pa(kaddr) >> PAGE_SHIFT可以直接获取页面的页号,因为_pa(kaddr)获取实际的物理地址,而它所做的工作不过是将kaddr减3G(为什么呢?);然后右移12位及得到了相应的页号。*/return tsk;out:free_thread_info(ti);free_task_struct(tsk);return NULL;}


至此,dup_task_struct算是讲完了,总结一下,它完成的主要功能如下:

1.在专业高速缓冲内存上分配task_struct,并完成初始化

2.在普通内存中分配thread_info及连续的两个页面,完成初始化

3.将task_struct和thread_info联系起来

4.其他相关的设置。

重点:task_struct 和thread_info的联系,及与thread_info所在的连续的两个内存页面的联系。