`
dingr
  • 浏览: 9679 次
  • 性别: Icon_minigender_1
  • 来自: ...
最近访客 更多访客>>
社区版块
存档分类
最新评论

linux进程的创建及相关知识的学习

 
阅读更多

   一、进程的内核堆栈

         1、  内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。

        2、进程用户栈和内核栈的切换

         当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。

         进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。

         那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?

         关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信心,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。

         3.内核栈的实现

          内核栈在kernel-2.4和kernel-2.6里面的实现方式是不一样的。

          在kernel-2.4内核里面,内核栈的实现是:

 Union task_union {
                   Struct task_struct task;
                   Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];
 };
          其中,INIT_STACK_SIZE的大小只能是8K。

          内核为每个进程分配task_struct结构体的时候,实际上分配两个连续的物理页面,底部用作task_struct结构体(大约1k),结构上面的用作堆栈(大约7k)。使用current()宏能够访问当前正在运行的进程描述符,定义如下:

#define current get_current()
static inline struct task_struct * get_current(void)
{
      struct task_struct *current;
      __asm__("andl %%esp,%0; ":"=r" (current) : "" (~8191UL));
      return current;
}

           ~8191UL表示最低13位为0, 其余位全为1。 %esp指向内核堆栈中,当屏蔽掉%esp的最低13后,就得到这个”两个连续的物理页面”的开头,而这个开头正好是task_struct的开始,从而得到了指向task_struct的指针。

          注意:这个时候task_struct结构是在内核栈里面的,内核栈的实际能用大小大概有7K。

 

          内核栈在kernel-2.6里面的实现是(kernel-2.6.32):

Union thread_union {
                   Struct thread_info thread_info;
                   Unsigned long stack[THREAD_SIZE/sizeof(long)];
 };
struct thread_info {

                   struct task_struct *task;

                   struct exec_domain *exec_domain;

                   __u32 flags;

        __u32 status;

                   __u32 cpu;

                   …  ..

 };
            根据内核的配置,THREAD_SIZE既可以是4K字节(1个页面)也可以是8K字节(2个页面)。thread_info是52个字节长。注意:此时的task_struct结构体已经不在内核栈空间里面了。
          当内核栈为8K时,Thread_info在这块内存的起始地址,内核栈从堆栈末端向下增长。所以此时,kernel-2.6中的current宏是需要更改的。要通过thread_info结构体中的task_struct域来获得于thread_info相关联的task。更详细的参考相应的current宏的实现。     
#define current get_current()
static inline struct task_struct * get_current(void)
{
      return current_thread_info()->task;
}

static inline struct thread_info *current_thread_info(void)
{
       struct thread_info *ti;
       __asm__("andl %%esp,%0; ":"=r" (ti) : "" (~(THREAD_SIZE - 1)));
       return ti;
}

          根据THREAD_SIZE大小,分别屏蔽掉内核栈的12-bit LSB(4K)13-bit LSB(8K),从而获得内核栈的起始位置。

 

二、Linux进程创建

      linux通过系统调用clone()来实现fork().然后由clone()调用do_fork().do_fork()完成了大部分的创建工作。定义在kernel/fork.c文件中。该函数调用copy_process()函数,然后让进程开始运行。copy_process()函数如下:        

981static struct task_struct *copy_process(unsigned long clone_flags,
 982                                        unsigned long stack_start,
 983                                        struct pt_regs *regs,
 984                                        unsigned long stack_size,
 985                                        int __user *child_tidptr,
 986                                        struct pid *pid,
 987                                        int trace)
 988{

         p = dup_task_struct(current);   //从当前进程的PCB复制一个子进程的PCB
         if (!p)
         goto fork_out;
         if (retval)
         goto bad_fork_cleanup_sighand;
         retval = copy_mm(clone_flags, p);

         1、调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,这些与当前进程(父进程)的值相同。此时,自进程和副进程的process-discriptor相同。           

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

    prepare_to_copy(orig);

    tsk = alloc_task_struct();
    if (!tsk)
        return NULL;

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

    *tsk = *orig;
    tsk->thread_info = ti;
    setup_thread_stack(tsk, orig);
    
    …..
}
# define alloc_task_struct()    kmem_cache_alloc(task_struct_cachep, GFP_KERNEL)

#define alloc_thread_info(tsk) \
    ((struct thread_info *) __get_free_pages(GFP_KERNEL,THREAD_ORDER))
#endif

            1/执行alloc_task_struct宏,为新进程获取进程描述符,并将描述符放在局部变量tsk中。

2,           2/执行alloc_thread_info宏以获取一块空闲的内存区,用以存放新进程的thread_info结构和内核栈,并将这块内存区字段的地址放在局部变量ti中(8K  4K, 可配置)。

3,            3/current进程描述符的内容复制到tsk所指向的task_struct结构中,然后把tsk->thread_info置为ti

4,            4/current进程的thread_info描述符的内容复制到ti中,然后把ti->task置为tsk

5,            5/返回新进程的描述符指针tsk

     2、进程地址空间的建立

    copy_process调用copy_mm,下面来分析copy_mm。

 681static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
 682{
 683        struct mm_struct * mm, *oldmm;
 684        int retval;
 685
 686        tsk->min_flt = tsk->maj_flt = 0;
 687        tsk->nvcsw = tsk->nivcsw = 0;
 688#ifdef CONFIG_DETECT_HUNG_TASK
 689        tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
 690#endif
 691
 692        tsk->mm = NULL;
 693        tsk->active_mm = NULL;
 694
 695        /*
 696         * Are we cloning a kernel thread?
 697         *
 698         * We need to steal a active VM for that..
 699         */
 700        oldmm = current->mm;
 701        if (!oldmm)
 702                return 0;
 703
 704        if (clone_flags & CLONE_VM) {
 705                atomic_inc(&oldmm->mm_users);
 706                mm = oldmm;
 707                goto good_mm;
 708        }
 709
 710        retval = -ENOMEM;
 711        mm = dup_mm(tsk);
 712        if (!mm)
 713                goto fail_nomem;
 714
 715good_mm:
 716        /* Initializing for Swap token stuff */
 717        mm->token_priority = 0;
 718        mm->last_interval = 0;
 719
 720        tsk->mm = mm;
 721        tsk->active_mm = mm;
 722        return 0;
 723
 724fail_nomem:
 725        return retval;
 726}

       

692,693行,对子进程或者线程的mm和active_mm初始化(NULL)。

700 - 708行,如果是创建线程,则新线程共享创建进程的mm,所以不需要进行下面的copy操作。

重点就是711行的dup_mm(tsk)。

 

       

625struct mm_struct *dup_mm(struct task_struct *tsk)
 626{
 627        struct mm_struct *mm, *oldmm = current->mm;
 628        int err;
 629
 630        if (!oldmm)
 631                return NULL;
 632
 633        mm = allocate_mm();
 634        if (!mm)
 635                goto fail_nomem;
 636
 637        memcpy(mm, oldmm, sizeof(*mm));
 638
 639        /* Initializing for Swap token stuff */
 640        mm->token_priority = 0;
 641        mm->last_interval = 0;
 642
 643        if (!mm_init(mm, tsk))
 644                goto fail_nomem;
 645
 646        if (init_new_context(tsk, mm))
 647                goto fail_nocontext;
 648
 649        dup_mm_exe_file(oldmm, mm);
 650
 651        err = dup_mmap(mm, oldmm);
 652        if (err)
 653                goto free_pt;
 654
 655        mm->hiwater_rss = get_mm_rss(mm);
 656        mm->hiwater_vm = mm->total_vm;
 657
 658        if (mm->binfmt && !try_module_get(mm->binfmt->module))
 659                goto free_pt;
 660
 661        return mm;

        

633行,用slab分配了mm_struct的内存对象。

637行,对子进程的mm_struct进程赋值,使其等于父进程,这样子进程mm和父进程mm的每一个域的值都相同。

在copy_mm的实现中,主要是为了实现unix COW的语义,所以理论上我们只需要父子进程mm中的start_x和end_x之类的域(像start_data,end_data)相等,而对其余的域(像mm_users)则需要re-init,这个操作主要在mm_init中完成。

       

449static struct mm_struct * mm_init(struct mm_struct * mm, struct task_struct *p)
 450{
 451        atomic_set(&mm->mm_users, 1);
 452        atomic_set(&mm->mm_count, 1);
 453        init_rwsem(&mm->mmap_sem);
 454        INIT_LIST_HEAD(&mm->mmlist);
 455        mm->flags = (current->mm) ?
 456                (current->mm->flags & MMF_INIT_MASK) : default_dump_filter;
 457        mm->core_state = NULL;
 458        mm->nr_ptes = 0;
 459        set_mm_counter(mm, file_rss, 0);
 460        set_mm_counter(mm, anon_rss, 0);
 461        spin_lock_init(&mm->page_table_lock);
 462        mm->free_area_cache = TASK_UNMAPPED_BASE;
 463        mm->cached_hole_size = ~0UL;
 464        mm_init_aio(mm);
 465        mm_init_owner(mm, p);
 466
 467        if (likely(!mm_alloc_pgd(mm))) {
 468                mm->def_flags = 0;
 469                mmu_notifier_mm_init(mm);
 470                return mm;
 471        }
 472
 473        free_mm(mm);
 474        return NULL;
 475}

   

其中特别要关注的是467 - 471行的mm_alloc_pdg,也就是page table的拷贝,page table负责logic address到physical address的转换。

拷贝的结果就是父子进程有独立的page table,但是page table里面的每个entries值都是相同的,也就是说父子进程独立地址空间中相同logical address都对应于相同的physical address,这样也就是实现了父子进程的COW(copy on write)语义。

事实上,vfork和fork相比,最大的开销节省就是对page table的拷贝。

而在内核2.6中,由于page table的拷贝,fork在性能上是有所损耗的,所以内核社区里面讨论过shared page table的实现(http://lwn.net/Articles/149888/)。

      

       2、检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超过给它分配的资源的限制。

              3、子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清零或者设为初始值。那些不是集成而来的进程描述符成员,主要是统计信息。task_struct中大多数数据都依旧未被修改。

             4、子进程的状态被设置为TASK_UNINTERRUPTIBLE,以确保它不会投入运行。

             5、copy_process()调用COPY_FLAGS()以更新task_struct的成员。表明进程是否拥有超级用户权限的PF_SUPERIV标志被清零,表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置。

             6、调用alloc_pid()为新进程分配一个有效的PID.

             7 、根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源被给定进程的所有线程共享;否则,这些资源对每个进程是不同的,因此被拷贝到这里。

            8、最后,copy_process()做扫尾工作返回一个指向子进程的指针。

分享到:
评论

相关推荐

    1.2--Linux并发程序设计.doc

    实验目的 1) 通过在Linux进程之间进行的通信实例来学习并发程序设计的方法。 2) 通过Linux进程通信的程序设计与实现,进一步熟悉操作系统的进程概念,理解Linux进 程管理概念。 3) 通过阅读和分析Linux实验程序,...

    【ASP.NET编程知识】.net Core 3.0 WebApi 创建Linux守护进程的方法.docx

    【ASP.NET编程知识】.net Core 3.0 WebApi 创建Linux守护进程的方法.docx

    linux 创建守护进程的相关知识

    在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程是,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。 在子进程中创建新会话 这个步骤是创建...

    20. Linux开发-Linux下进程编程.pdf

    主要是介绍Linux下进程相关知识点,命令行控制进程的相关命令,介绍创建进程、管理进程相关函数。

    \嵌入式linux开发教程之进程与线程--千锋培训

    基础知识:线程和进程,二.Linux 2.4内核中的轻量进程实现,三.LinuxThread的线程机制,1.线程描述数据结构及实现限制,2.嵌入式linux开发教程:管理线程,3.嵌入式linux开发教程:线程栈,4.嵌入式linux开发教程:...

    学习linux环境下c程序的示例代码.rar

    在学习Linux C程序之前,我们需要了解C语言的基础知识,包括变量、数据类型、运算符、流程控制等等。本节将为读者详细讲解C语言基础。 三、Linux文件操作 在Linux环境中,文件是非常重要的资源。因此linux c程序...

    linux文件系统基础知识

    linux文件系统基础知识 详细介绍Linux文件系统的知识。 文件系统类型: ext2 : 早期linux中常用的文件系统 ext3 : ext2的升级版,带日志功能 RAMFS : 内存文件系统,速度很快 NFS : 网络文件系统,由SUN...

    学习Linux系统相关的知识.rar

    ①进程控制:创建和撤销进程,分配资源、资源回收,控制进程运行过程中的状态转换。 ②进程同步:多进程运行进行协调–进程互斥(临界资源上锁)、进程同步。 ③进程通信:实现相互合作之间的进程的信息交换。 ④...

    linux服务器的一些知识点.pdf

    linux服务器的一些知识点: 1. 什么是Linux操作系统?请简要介绍Linux系统的特点和优势。 2. 在Linux中,如何查看当前目录下的文件列表?请列举相应的命令。 3. 请解释什么是Linux中的文件权限(File Permissions)...

    linux下如何创建守护进程的步骤

    这两天学习了linux 守护进程这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记。 1,进程的概念:程序的一次动态执行过程。  进程存在于内存当中,存在着 创建,调度,执行和消亡,进程号是进程的唯一...

    Linux中使用C语言的fork()函数创建子进程的实例教程

    fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork()函数后,系统先给新的...

    计算机病毒与防护:Linux进程管理.pptx

    自然,从进程的创建到撤销,操作系统会为它安排一切。不过,操作系统的安排是根据固定的算法所进行,纵使这些算法能根据当前情况不断调整,但不可能预先知道你的需要,然后让某个进程在特定某个时间挂起,让某个进程...

    LinuxC编程实战电子书

    本书共计4个压缩包(linuxC编程实战+.part1.rar,linuxC编程实战+.part2.rar,linuxC编程实战 +.part1.rar,Linux+C编程实战源代码.rar) 内容简介 ...随书的光盘包括:全部源代码及相关学习资 料。

    嵌入式Linux应用程序开发标准教程(第2版全)

    7.2 Linux进程控制编程 7.3 Linux守护进程 7.3.1 守护进程概述 7.3.2 编写守护进程 7.3.3 守护进程的出错处理 7.4 实验内容 7.4.1 编写多进程程序 7.4.2 编写守护进程 7.5 本章小结 7.6 思考与练习 第8章 进程间...

    清华大学Linux操作系统原理与应用

    3.4.3 Linux进程调度时机 50 3.4.4 进程调度的依据 51 3.4.5 调度函数schedule()的实现 52 3.5 进程的创建 54 3.5.1 创建进程 55 3.5.2 线程及其创建 56 3.6 与进程相关的系统调用及其应用 58 3.6.1 fork系统调用 58...

    深入分析Linux内核源码.chm

    6.8 进程的创建和执行 第七章 进程间通信 7.1 管道 7.2 信号(signal) 7.3 System V 的IPC机制 第八章 虚拟文件系统 8.1 概述 8.2 VFS中的数据结构 8.3 高速缓存 8.4 文件系统的注册、安装与拆卸 8.5 限额机制 8.6 ...

    嵌入式Linux C语言基础与实例进阶 视频 源程序 PPT

    《嵌入式linux c语言基础与实例进阶》面向学习linux c语言的初中级读者,《嵌入式linux c语言基础与实例进阶》共16章,分别介绍了嵌入式系统基础知识、linux环境下c语言的开发、bootloader、linux环境下c语言编程...

    Linux2.6内核标准教程(共计8-- 第1个)

    《Linux2.6内核标准教程》适合Linux内核爱好者、Linux驱动开发人员、Linux系统工程师参考使用,也可以作为计算机及相关专业学生深入学 习操作系统的参考书。 引用: 目录 第1章 Linux内核学习基础 1 1.1 为什么...

Global site tag (gtag.js) - Google Analytics