zoukankan      html  css  js  c++  java
  • Linux进程启动过程简析

    朱宇轲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

    今天,我们将通过阅读linux的内核代码来对linux系统中进程的创建过程进行简单的分析。

    大家都知道,linux通过进程控制块PCB来对进程进行控制和管理,它存放了进程的数据。在linux中,PCB的代码如下(当然是节选的==):

    struct task_struct {
        volatile long state;//进程状态    
        void *stack;//进程堆栈指针
        atomic_t usage;
        unsigned int flags;
        unsigned int ptrace;
    ...
        //一些记录优先级的变量
        int prio, static_prio, normal_prio;
        unsigned int rt_priority;
        const struct sched_class *sched_class;
        struct sched_entity se;
        struct sched_rt_entity rt;
    ...
        //进程链表
        struct list_head tasks;
    #ifdef CONFIG_SMP
        struct plist_node pushable_tasks;
        struct rb_node pushable_dl_tasks;
    #endif
        //pid号
        pid_t pid;
        pid_t tgid;
    ...
        //父进程以及子进程
        struct task_struct __rcu *real_parent;
        struct task_struct __rcu *parent; 
    
        struct list_head children;    
        struct list_head sibling;
        struct task_struct *group_leader;
    ...
    };

      可以看到,PCB中记录了进程的ID号、优先级、状态、与其他进程关系等信息,操作系统由此对进程进行管理。

      需要注意的是,在操作系统内核的具体实现中,是将当前的进程全部存入到一个循环链表中来进行管理的。如下图所示:

     具体的实验则是利用gdb调试进程创建的函数,实验截图如下:

    此次我们在6个函数(or 系统调用)处设置了断点:

    1.sys_clone
    2.do_fork
    3.dup_task_struct
    4.copy_process
    5.copy_thread
    6.ret_from_fork

    在实验楼所使用的虚拟系统中,进程创建的底层函数是sys_clone,而sys_clone实际上调用的是do_fork,因此可以说do_fork才是真正实现了创建进程细节的函数。

    long do_fork(unsigned long clone_flags,
              unsigned long stack_start,
              unsigned long stack_size,
              int __user *parent_tidptr,
              int __user *child_tidptr)
    {
        struct task_struct *p;
        int trace = 0;
        long nr;
    
        ......
    
        p = copy_process(clone_flags, stack_start, stack_size,
                 child_tidptr, NULL, trace);
        ......
    }

     在这个函数里,p是记录了新的进程的PCB的变量,通过copy_process,系统将父进程的PCB拷贝并修改到子进程中。

      copy_process函数则是首先利用dup_task_struct将父进程的PCB全盘拷贝,之后再具体修改与父进程不同的子部分。

       

    static struct task_struct *copy_process(unsigned long clone_flags,
                        unsigned long stack_start,
                        unsigned long stack_size,
                        int __user *child_tidptr,
                         struct pid *pid,
                        int trace)
    {
        int retval;
        struct task_struct *p;
    
        //拷贝父进程的PCB
        p = dup_task_struct(current);
        if (!p)
            goto fork_out;
    
        //修改具体的部分结构
        p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
        p->flags |= PF_FORKNOEXEC;
        INIT_LIST_HEAD(&p->children);
        INIT_LIST_HEAD(&p->sibling);
        rcu_copy_process(p);
        p->vfork_done = NULL;
        spin_lock_init(&p->alloc_lock);
    ...
    retval=copy_thread(clone_flags,stack_start,stack_size,p);
    }

      需要注意的是,在copy_process函数中有一个copy_thread函数,在它的函数实现中,将创建的子进程的栈底空间找到,并根据此修改了子进程的ip和sp数据:sp指向栈底,而ip指向ret_from_fork段。那ret_from_fork段又是什么呢,它其实就是下面一段代码:

    ENTRY(ret_from_fork)
        CFI_STARTPROC
        pushl_cfi %eax
        call schedule_tail
        GET_THREAD_INFO(%ebp)
        popl_cfi %eax
        pushl_cfi $0x0202        # Reset kernel eflags
        popfl_cfi
        jmp syscall_exit
        CFI_ENDPROC
    END(ret_from_fork)

      可以看到,它最终跳转到了syscall_exit,而syscall_exit就是我们上次分析的中断处理程序sys_call中的代码。这样,sp指向的实际是我们触发终端后存储的上下文的那块儿堆栈,ip则指向syscall_eixt,当新的进程创建时,它会首先执行syscall_exit处的代码,不久就会遇到Restore_All,恢复上下文环境,新的进程得以执行。

    总结:

      在linux内核中,创建新的进程时,基本思路是复制父进程的PCB给子进程,如果有不同的数据再进行调整。同时,将子进程执行的第一条命令指向中断恢复的代码,从而实现创建新进程的效果。

  • 相关阅读:
    mysql外键和连表操作
    数据库的操作
    进程之select和epoll
    jwt的应用生成token,redis做储存
    为什么前后端分离不利于seo
    redis的bitmap
    lnmp环境的nginx的tp5配置
    虚拟机安装cenos7后ifcfg看网卡无inet地址掩码等信息
    rsa加密
    hydra命令
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/4418909.html
Copyright © 2011-2022 走看看