精华内容
下载资源
问答
  • 之前有听到别人的面试题是问系统创建进程的具体过程是什么,首先想到的是CreateProcess,但是对于具体过程却不是很清楚,今天整理一下。 从操作系统的角度来说 创建进程步骤: 1.申请进程块 2.为进程分配内存资源 ...

    之前有听到别人的面试题是问系统创建进程的具体过程是什么,首先想到的是CreateProcess,但是对于具体过程却不是很清楚,今天整理一下。

    从操作系统的角度来说

    创建进程步骤:
            1.申请进程块      
            2.为进程分配内存资源
            3.初始化进程块
            4.将进程块链入就绪队列

    课本上的知识。。。

     

    从CreateProcess的具体流程来说:

    CreateProcess它首先创建一个执行体进程对象,即EPROCESS 对象,然后创建一个初始线程,为初始线程建立一个栈,并设置好它的初始执行环境。完成这些工作以后,该线程就可以参与系统的线程调度了。然而,通过Windows API 函数创建的进程也要接受Windows 子系统的管理,在这种情况下,仅仅内核部分的工作还不够,系统在创建进程过程中,还需要跟子系统打交道。另外,还需建立起独立的内存地址空间。

    CreateProcess通过内核创建进程的步骤,大致分为六个阶段:

    NtCreateProcess,它只是简单地对参数稍作处理,然后把创建进程的任务交给NtCreateProcessEx 函数,所以我们来看NtCreateProcessEx 的原型及其流程。

     

    NTSTATUS  
    NtCreateProcessEx(  
    __out PHANDLE ProcessHandle,  
    __in ACCESS_MASK DesiredAccess,  
    __in_opt POBJECT_ATTRIBUTES ObjectAttributes,  
    __in HANDLE ParentProcess,  
    __in ULONG Flags,  
    __in_opt HANDLE SectionHandle,  
    __in_opt HANDLE DebugPort,  
    __in_opt HANDLE ExceptionPort,  
    __in ULONG JobMemberLevel  
    ); 

     

    NtCreateProcessEx 函数的代码只是简单地检查ProcessHandle 参数代表的句柄是否可写,然后把真正的创建工作交给PspCreateProcess 函数,所以,PspCreateProcess 才是真正创建进程的函数。

    PsCreateSystemProcess 可用于创建系统进程对象,它创建的进程都是PsInitialSystemProcess 的子进程。所以,PspCreateProcess函数负责创建系统中的所有进程,包括System 进程。下面介绍此函数的基本流程。

     

     

    第一阶段:打开目标映像文件

    第二阶段:创建内核中的进程对象

    第三阶段:创建初始线程

    第四阶段:通知windows子系统进程csrss.exe进程来对新进程进行管理

    第五阶段:启动初始线程

    第六阶段:用户空间的初始化和Dll连接

     

    具体内容:

    在Windows中,CreateProcess要先通过系统调用NtCreateProcess创建进程,成功以后就立即通过系统调用NtCreateThread创建其第一个线程。
     
    第一阶段:打开目标映像文件
     
    首先用CreateProcess(实际上是CreateProcessW)打开指定的可执行映像文件,并创建一个内存区对象。注意,内存区对象并没有被映射到内存中(由于目标进程尚未建立起来,不可能完成内存映射),但它确实是打开了。
     
     
    第二阶段:创建内核中的进程对象
     
    实际上就是创建以EPROCESS为核心的相关数据结构,主要包括:
     
    调用内核中的NtCreateProcessEx 系统服务,实际的调用过程是这样的:kernel32.dll 中的CreateProcessW调用ntdll.dll 中的存根函数NtCreateProcessEx,而ntdll.dll的NtCreateProcessEx 利用处理器的陷阱机制切换到内核模式下;在内核模式下,系统服务分发函数KiSystemService 获得控制,它利用当前线程指定的系统服务表,调用到执行体层的NtCreateProcessEx 函数。然后,执行体层的NtCreateProcessEx 函数执行前面介绍的进程创建逻辑,包括创建EPROCESS 对象、初始化其中的域、创建初始的进程地址空间、创建和初始化句柄表,并设置好EPROCESS 和KPROCESS 中的各种属性,如进程优先级、安全属性、创建时间等。到这里,执行体层的进程对象已经建立起来,进程的地址空间已经初始化,并且EPROCESS 中的PEB 也已初始化。
     
    第三阶段:创建初始线程
     
    这个阶段是通过调用NtCreateThread()完成的,主要包括:
     现在,虽然进程对象已经建立起来,但是它没有线程,所以,它自己还不能做任何事情。接下来需要创建一个初始线程,在此之前,首先要构造一个栈以及一个可供运行的环境。初始线程的栈的大小可以通过映像文件获得,而创建线程则可以通过调用ntdll.dll 中的NtCreateThread 函数来完成。
    创建和设置目标线程的ETHREAD数据结构,并处理好与EPROCESS的关系(例如进程块中的线程计数等等)。
    在目标进程的用户空间创建并设置目标线程的TEB。
    将目标线程在用户空间的起始地址设置成指向Kernel32.dll中的BaseProcessStart()或BaseThreadStart(),前者用于进程中的第一个线程,后者用于随后的线程。
         用户程序在调用NtCreateThread()时也要提供一个用户级的起始函数(地址), BaseProcessStart()和BaseThreadStart()在完成初始化时会调用这个起始函数。
         ETHREAD数据结构中有两个成份,分别用来存放这两个地址。
    调用KeInitThread设置目标线程的KTHREAD数据结构并为其分配堆栈和建立执行环境。
       特别地,将其上下文中的断点(返回点)设置成指向内核中的一段程序KiThreadStartup,使得该线程一旦被调度运行时就从这里开始执行。
    系统中可能登记了一些每当创建线程时就应加以调用的“通知”函数,调用这些函数。
     
     
    第四阶段:通知windows子系统
     
    每个进程在创建/退出的时候都要向windows子系统进程csrss.exe进程发出通知,因为它担负着对windows所有进程的管理的责任,
    注意,这里发出通知的是CreateProcess的调用者,不是新建出来的进程,因为它还没有开始运行。
     
    至此,CreateProcess的操作已经完成,但子进程中的线程却尚未开始运行,它的运行还要经历下面的第五和第六阶段。
     
     
    第五阶段:启动初始线程
     

    在内核中,新线程的启动例程是KiThreadStartup函数,这是当PspCreateThread 调用KeInitThread 函数时,KeInitThread 函数调用KiInitializeContextThread(参见base\ntos\ke\i386\thredini.c 文件)来设置的。

    KiThreadStartup 函数首先将IRQL 降低到APC_LEVEL,然后调用系统初始的线程函数PspUserThreadStartup。这里的PspUserThreadStartup 函数是PspCreateThread 函数在调用KeInitThread 时指定的,。注意,PspCreateThread函数在创建系统线程时指定的初始线程函数为PspSystemThreadStartup  。线程启动函数被作为一个参数传递给PspUserThreadStartup,在这里,它应该是kernel32.dll 中的BaseProcessStart。

    PspUserThreadStartup 函数被调用。逻辑并不复杂,但是涉及异步函数调用(APC)机制。


    新创建的线程未必是可以被立即调度运行的,因为用户可能在创建时把标志位CREATE_ SUSPENDED设成了1;
    如果那样的话,就需要等待别的进程通过系统调用恢复其运行资格以后才可以被调度运行。否则现在已经可以被调度运行了。至于什么时候才会被调度运行,则就要看优先级等等条件了。
     
     
    第六阶段:用户空间的初始化和Dll连接
     

    PspUserThreadStartup 函数返回以后,KiThreadStartup 函数返回到用户模式,此时,PspUserThreadStartup 插入的APC 被交付,于是LdrInitializeThunk 函数被调用,这是映像加载器(image loader)的初始化函数。LdrInitializeThunk 函数完成加载器、堆管理器等初始化工作,然后加载任何必要的DLL,并且调用这些DLL 的入口函数。最后,当LdrInitializeThunk 返回到用户模式APC 分发器时,该线程开始在用户模式下执行,调用应用程序指定的线程启动函数,此启动函数的地址已经在APC 交付时被压到用户栈中。


    DLL连接由ntdll.dll中的LdrInitializeThunk()在用户空间完成。在此之前ntdll.dll与应用软件尚未连接,但是已经被映射到了用户空间
    函数LdrInitializeThunk()在映像中的位置是系统初始化时就预先确定并记录在案的,所以在进入这个函数之前也不需要连接。

     

    涉及到了Windows内核的知识,细节处还有待理解。。。

    参考资料:

    http://www.cnblogs.com/csyisong/archive/2010/10/22/1858115.html

    http://www.cnblogs.com/Gotogoo/p/5262536.html

    http://book.51cto.com/art/201011/235767.htm

    《Windows内核原理与实现》潘爱民

     

    转载于:https://www.cnblogs.com/HsinTsao/p/6534317.html

    展开全文
  • 追踪Linux系统创建进程过程,在实验楼虚拟中使用gdb调试工具进行调试

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

    实验环境:https://www.shiyanlou.com/courses/195

    实验过程

    本次实验主要追踪Linux系统创建进程的过程,在实验楼虚拟中使用gdb调试工具进行调试,下面是实验截图,主要的流程分析通过代码进行:
    do_fork断点
    这里写图片描述
    copy_process断点
    这里写图片描述
    dup_task_struct断点
    这里写图片描述

    进程创建分析

    Linux中创建进程一共有三个函数:

    1. fork,创建子进程
    2. vfork,与fork类似,但是父子进程共享地址空间,而且子进程先于父进程运行。
    3. clone,主要用于创建线程
      这里值得注意的是,Linux中得线程是通过模拟进程实现的,较新的内核使用的线程库一般都是NPTL。

    下面通过fork系统调用的实现,观察Linux系统中进程的创建步骤。

    进程创建的大概过程

    通过之前的学习,我们知道fork是通过触发0x80中断,陷入内核,来使用内核提供的提供调用。:

    SYSCALL_DEFINE0(fork)
    {
    return do_fork(SIGCHLD, 0, 0, NULL, NULL);
    }
    #endif
    
    SYSCALL_DEFINE0(vfork)
    {
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
    0, NULL, NULL);
    }
    
    SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
     int __user *, parent_tidptr,
     int __user *, child_tidptr,
     int, tls_val)
    {
    return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
    }
    

      通过上面的精简代码精简,我们可以看出,fork、vfork和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;
    
    // ...
    
    // 复制进程描述符,返回创建的task_struct的指针
    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);
    
    if (!IS_ERR(p)) {
        struct completion vfork;
        struct pid *pid;
    
        trace_sched_process_fork(current, p);
    
        // 取出task结构体内的pid
        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);
    
        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);
    
        // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行
        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }
    
        // 将子进程添加到调度器的队列,使得子进程有机会获得CPU
        wake_up_new_task(p);
    
        // ...
    
        // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间
        // 保证子进程优先于父进程运行
        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
        }
    
        put_pid(pid);
    } else {
        nr = PTR_ERR(p);
        }
    return nr;
    }
    

    我们通过上面的代码,可以看出,do_fork大概做了这么几件事情:

    1. 调用copy_process,将当前进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。
    2. 初始化vfork的完成处理信息(如果是vfork调用)
    3. 调用wake_ up_ new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。
    4. 如果是vfork调用,需要阻塞父进程,知道子进程执行exec。
      上面的过程对vfork稍微做了处理,因为vfork必须保证子进程优先运行,执行exec,替换自己的地址空间。

    抛开vfork,进程创建的大部分过程都在copy_process函数中,下面我们详细观察这个函数。

    进程创建的关键-copy_process

    copy_process的代码非常复杂,这里我精简了大部分,只留下最重要的一些:

    /*
    创建进程描述符以及子进程所需要的其他所有数据结构
    为子进程准备运行环境
    */
    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;
    
    // 分配一个新的task_struct,此时的p与当前进程的task,仅仅是stack地址不同
    p = dup_task_struct(current);
    
    // 检查该用户的进程数是否超过限制
    if (atomic_read(&p->real_cred->user->processes) >=
            task_rlimit(p, RLIMIT_NPROC)) {
        // 检查该用户是否具有相关权限,不一定是root
        if (p->real_cred->user != INIT_USER &&
            !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
            goto bad_fork_free;
    }
    
    retval = -EAGAIN;
    // 检查进程数量是否超过 max_threads,后者取决于内存的大小
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;
    
    // 初始化自旋锁
    
    // 初始化挂起信号
    
    // 初始化定时器
    
    // 完成对新进程调度程序数据结构的初始化,并把新进程的状态设置为TASK_RUNNING
    retval = sched_fork(clone_flags, p);
    // .....
    
    // 复制所有的进程信息
    // copy_xyz
    
    // 初始化子进程的内核栈
    retval = copy_thread(clone_flags, stack_start, stack_size, p);
    if (retval)
        goto bad_fork_cleanup_io;
    
    if (pid != &init_struct_pid) {
        retval = -ENOMEM;
        // 这里为子进程分配了新的pid号
        pid = alloc_pid(p->nsproxy->pid_ns_for_children);
        if (!pid)
            goto bad_fork_cleanup_io;
    }
    
    /* ok, now we should be set up.. */
    // 设置子进程的pid
    p->pid = pid_nr(pid);
    // 如果是创建线程
    if (clone_flags & CLONE_THREAD) {
        p->exit_signal = -1;
        // 线程组的leader设置为当前线程的leader
        p->group_leader = current->group_leader;
        // tgid是当前线程组的id,也就是main进程的pid
        p->tgid = current->tgid;
    } else {
        if (clone_flags & CLONE_PARENT)
            p->exit_signal = current->group_leader->exit_signal;
        else
            p->exit_signal = (clone_flags & CSIGNAL);
        // 创建的是进程,自己是一个单独的线程组
        p->group_leader = p;
        // tgid和pid相同
        p->tgid = p->pid;
    }
    
    if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
        // 如果是创建线程,那么同一线程组内的所有线程、进程共享parent
        p->real_parent = current->real_parent;
        p->parent_exec_id = current->parent_exec_id;
    } else {
        // 如果是创建进程,当前进程就是子进程的parent
        p->real_parent = current;
        p->parent_exec_id = current->self_exec_id;
    }
    
    // 将pid加入PIDTYPE_PID这个散列表
    attach_pid(p, PIDTYPE_PID);
    // 递增 nr_threads的值
    nr_threads++;
    
    // 返回被创建的task结构体指针
    return p;
    }
    

    看完这份精简代码,我们总结出copy_process的大体流程:

    1. 检查各种标志位(已经省略)
    2. 调用dup_ task_ struct复制一份task_struct结构体,作为子进程的进程描述符。
    3. 检查进程的数量限制。
    4. 初始化定时器、信号和自旋锁。
    5. 初始化与调度有关的数据结构,调用了sched_ fork,这里将子进程的state设置为TASK_RUNNING。
    6. 复制所有的进程信息,包括fs、信号处理函数、信号、内存空间(包括写时复制)等。
    7. 调用copy_thread,这又是关键的一步,这里设置了子进程的堆栈信息。
    8. 为子进程分配一个pid
    9. 设置子进程与其他进程的关系,以及pid、tgid等。这里主要是对线程做一些区分。
      进一步追踪dup_task_struct

    简化后的代码如下:

    static struct task_struct *dup_task_struct(struct task_struct *orig)
    {
    struct task_struct *tsk;
    struct thread_info *ti;
    int node = tsk_fork_get_node(orig);
    int err;
    
    // 分配一个task_struct结点
    tsk = alloc_task_struct_node(node);
    if (!tsk)
        return NULL;
    
    // 分配一个thread_info结点,其实内部分配了一个union,包含进程的内核栈
    // 此时ti的值为栈底,在x86下为union的高地址处。
    ti = alloc_thread_info_node(tsk, node);
    if (!ti)
        goto free_tsk;
    
    err = arch_dup_task_struct(tsk, orig);
    if (err)
        goto free_ti;
    
    // 将栈底的值赋给新结点的stack
    tsk->stack = ti;
    
    // ...
    
    // 返回新申请的结点
    return tsk;
    }
    dup_task_struct的代码要结合一个联合体的定义来分析。
    
    union thread_union 
    {
        struct thread_info thread_info;
        unsigned long stack[THREAD_SIZE/sizeof(long)];
    };
    

      这个联合体的定义非常关键。我们知道x86体系结构的栈空间,按照从高到低的方式增长。而C中的结构体,是按从低到高的方式使用。
      这样我们可以声明一个联合体,低地址用作thread_info,高地址用作栈底。这样做还有一个好处,就是thread_info中存放着一个task_struct的指针,这样我们根据栈底地址就可以通过thread_info快速定位到进程对应的task_struct指针。

    上面的dup_task_struct中:

    1. 先调用alloc_task_struct_node分配一个task_struct结构体。
    2. 调用alloc_thread_info_node,分配了一个union,注意,这里不仅仅分配了一个thread_info结构体,还分配了一个stack数组。返回值为ti,实际上就是栈底。
    3. tsk->stack = ti;这句话,就是将栈底的地址赋给task的stack变量。
      所以,最后为子进程分配了内核栈空间。
      执行完dup_task_struct之后,子进程和父进程的task结构体,除了stack指针之外,完全相同!

    进一步追踪copy_thread函数

    上面的copy_process中,我们提到copy_thread函数为子进程准备了上下文堆栈信息。代码如下:

    // 初始化子进程的内核栈
    int copy_thread(unsigned long clone_flags, unsigned long sp,
    unsigned long arg, struct task_struct *p)
    {
    
    // 获取寄存器信息
    struct pt_regs *childregs = task_pt_regs(p);
    struct task_struct *tsk;
    int err;
    
    // 栈顶 空栈
    p->thread.sp = (unsigned long) childregs;
    p->thread.sp0 = (unsigned long) (childregs+1);
    memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));
    
    // 如果是创建的内核线程
    if (unlikely(p->flags & PF_KTHREAD)) {
        /* kernel thread */
        memset(childregs, 0, sizeof(struct pt_regs));
        // 内核线程开始执行的位置
        p->thread.ip = (unsigned long) ret_from_kernel_thread;
        task_user_gs(p) = __KERNEL_STACK_CANARY;
        childregs->ds = __USER_DS;
        childregs->es = __USER_DS;
        childregs->fs = __KERNEL_PERCPU;
        childregs->bx = sp; /* function */
        childregs->bp = arg;
        childregs->orig_ax = -1;
        childregs->cs = __KERNEL_CS | get_kernel_rpl();
        childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
        p->thread.io_bitmap_ptr = NULL;
        return 0;
    }
    
    // 将当前进程的寄存器信息复制给子进程
    *childregs = *current_pt_regs();
    // 子进程的eax置为0,所以fork的子进程返回值为0
    childregs->ax = 0;
    if (sp)
        childregs->sp = sp;
    
    // 子进程从ret_from_fork开始执行
    p->thread.ip = (unsigned long) ret_from_fork;
    task_user_gs(p) = get_user_gs(current_pt_regs());
    
    return err;
    }
    

    我们看到,copy_thread的流程如下:

    1. 获取子进程寄存器信息的存放位置
    2. 对子进程的thread.sp赋值,将来子进程运行,这就是子进程的esp寄存器的值。
    3. 如果是创建内核线程,那么它的运行位置是ret_from_kernel_thread,将这段代码的地址赋给thread.ip,之后准备其他寄存器信息,退出
    4. 将父进程的寄存器信息复制给子进程。
    5. 将子进程的eax寄存器值设置为0,所以fork调用在子进程中的返回值为0.
    6. 子进程从ret_from_fork开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器。
      从上面的流程中,我们看出,子进程复制了父进程的上下文信息,仅仅对某些地方做了改动,运行逻辑和父进程完全一致。

    另外,我们得出结论,子进程从ret_from_fork处开始执行。

    新进程的执行

    上文已经得知,新进程从ret_from_fork处开始执行,子进程的运行是由这几处保证的:

    1. dup_task_struct中为其分配了新的堆栈
    2. copy_process中调用了sched_fork,将其置为TASK_RUNNING
    3. copy_thread中将父进程的寄存器上下文复制给子进程,这是非常关键的一步,这里保证了父子进程的堆栈信息是一致的。
    4. 将ret_from_fork的地址设置为eip寄存器的值,这是子进程的第一条指令。
      创建进程时的函数调用堆栈

    在gdb中使用bt命令即可。见截图:

    对进程创建的理解

    Linux中所有的进程创建都是基于复制的方式,然后对子进程做一些特殊的处理。
    而Linux中得线程,又是一种特殊的进程。

    展开全文
  • LInux进程创建过程

    千次阅读 2019-04-15 22:58:41
    传统的fork系统调用直接把所有的资源复制给新创建进程,但是这种实现过于简单,效率低下,因为并不支持拷贝数据的共享。 更糟的是如果新进程打算立即执行一个新的映像那么所有的拷贝都将前功尽弃。 Linux下面的...

    写时拷贝

    传统的fork系统调用直接把所有的资源复制给新创建的进程,但是这种实现过于简单,效率低下,因为并不支持拷贝数据的共享。

    更糟的是如果新进程打算立即执行一个新的映像那么所有的拷贝都将前功尽弃。

    Linux下面的fork采用的是写时拷贝的方法,也就是说让父进程与子进程拥有同一份拷贝,之后如果发生了写入数据的情况。再也根本不会被写入的情况下,它就不会复制了。例如直接调用了exec()

    vfork函数其实在系统调用的时候步骤和fork一致但是,它不会拷贝父进程的页表项,子进程作为父进程的一个单独的线程执行。子进程运行在自己的内存空间内,父进程会在它执行的时候阻塞,知道子进程退出或执行exec

    创建进程的详细步骤

    fork函数是通过clone系统来完成创建进程的。这个调用来标识一下父进程和子进程之间共享的资源。

    1. 调用dup_task_struct()来创建一个内核栈,thread_info,task_struct等结构,一开始的时候其实父进程和子进程里面的资源和里都是一样的,此时子进程与父进程的进程描述符完全一致
    2. check 检查当前进程是否已经超过了系统的最大文件描述符
    3. 清除子描述符里面一些内容,使它与父进程分开。但是进程描述符不需要去改变就是直接复制就好了,因为主要起的是描述作用,而不是记录的作用
    4. 设置标志位,TASK_UNINTERRUPTIBLE 来保证该进程不会被投入使用
    5. copy-flags()用来更新struct_task的一些flags成员,
    6. get_pid()给进程一个pid号
    7. 根据传递给clone的参数标志,copy_process函数拷贝或共享打开文件,文件系统信息等、信号处理函数、进程地址空间和命名空间
    8. 让父进程和子进程平分剩余的时间片
    9. 最后copy_process会返回一个指向子进程的指针

    线程在Linux内部的实现

    其实大体上与进程差不多也就有一下几点区别

    同一程序内共享内存地址空间运行一组线程,这些线程可以共享打开的文件和其他资源,支持并发程序设计技术(多处理器)

    线程父子共享内存空间,文件系统资源,文件描述符信号处理程序等。

    内核线程

    • 内核线程没有独立的地址空间,只在内核运行,并且只能从来不切换到用户空间去。
    • 可以被调度和抢占
    • 只能被其他的内核线程创建

    进程终结

    1. 设置task_struct成员为PF_EXITING
    2. 使用del_tim_sync删除该进程的内核定时器
    3. 放弃内存使用的mm_struct,如果没有其他进程使用就彻底的放弃它(没有人在和它共享资源)
    4. 在IPC队列中删除掉这个进程的存在
    5. 递减文件描述符,文件系统数据、进程空间名字和信号处理函数的引用计数
    6. 将任务退出代码发给父进程存放
    7. 向父进程发送信号,将子进程的父进程重新设置为线程组中的其他线程或者调用init,把进程设置为TASK_ZOMBIE
    8. 切换到其他进程

     

    展开全文
  • 进程创建过程

    2020-10-11 14:50:15
    (1)任何进程都是别的进程创建的:...(2)进程创建过程 1.映射EXE文件 2.创建内核对象EPROCESS 3.映射系统DLL(ntdll.dll) 4.创建线程内核对象 5.系统启动线程 映射DLL(ntdll.LdrlnitializeThunk) 线程开始执行 ...

    (1)任何进程都是别的进程创建的:CreateProcess()

    (2)进程的创建过程
    1.映射EXE文件
    2.创建内核对象EPROCESS
    3.映射系统DLL(ntdll.dll)
    4.创建线程内核对象
    5.系统启动线程
    映射DLL(ntdll.LdrlnitializeThunk)
    线程开始执行

    展开全文
  • Linux系统进程复习总结  1....进程就是内核对某个运行状态的数据集合的一种总结, 是进行系统资源分配和调度的基本单元 ...Linux内核在创建进程时的函数调用主要是通过上面三个函数执行的, 其中co
  • 进程创建过程

    2015-04-24 16:39:22
    进程创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节。比如,子进程复制父进程所拥有的资源,或者子进程和父进程共享相同的物理页面,拥有自己的地址...
  • Zygote进程创建过程(Android 8.1)

    万次阅读 2019-08-13 16:10:29
    本篇文章的内容中,我将要给大家介绍Android开发中最长见到的一个底层进程——...它的启动过程从init进程启动开始,到Zygote进程真正运行起来,并且进行相关初始化完成为止。 我们来详细分析下Zygote进程的启动过程...
  • 分析Linux内核创建一个新进程过程
  • 通过调试,查看创建进程过程,如图三所示 图一 Fork() 图二 设置断点 图三 dup_task_struct调试中的代码 简析Linux内核创建一个新的进程过程 进程控制块(Processing ...
  • Linux内核创建一个新进程过程 20135224陈实+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 简介: pcb定义:进程所需的各种执行与数据信息头 task_...
  • Linux内核创建进程的全过程

    千次阅读 2016-05-26 20:48:57
    进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性。比如进程的状态、进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程...
  • 有了之前的对进程和线程对象的学习的铺垫后,我们现在可以开始学习windows下的进程创建过程了,我将尝试着从源代码的层次来分析在windows下创建一个进程都要涉及到哪些步骤,都要涉及到哪些数据结构。   1. 相关...
  • [转]进程创建过程

    2013-12-23 11:18:35
    进程创建过程 ------基于Linux0.11源码分析 1. 背景 进程创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节。比如,子进程复制父进程所拥有的...
  • ------------分析Linux内核创建一个新进程过程--------------  这次实验要详细分析Linux内核创建进程过程。那么我们应该先明确,一个进程在Linux内核中是怎样的。那么就是要知道如下的几个知识。  进程...
  • 进程创建和线程创建

    2016-10-22 21:45:02
    创建进程,创建线程,MFC创建线程,UI线程,工作线程
  • 系统允许一个进程创建进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的...
  • Linux1.0 内核源码,fork.c,主要介绍fork系统调用部分是如何创建一个新进程的,以及拷贝父进程几乎所有资源。 unix 系统通过 fork 系统调用创建一个进程,fork.c 的主要任务是为新的进程填写数据结构,相关步骤有:...
  • 此次实验就是了解内核创建一个新进程大致过程。 为了简单,使用fork再用户态创建一个进程。代码如下: 下面是准备工作​​​ cd LinuxKernel rm -rf menu git clone ...
  • 系统允许一个进程创建进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的...
  • 进程创建

    2016-10-07 10:08:11
    进程创建也就有两种方式:一是由操作系统创建,二是由父进程创建。 在系统启动时,操作系统会创建一些进程,他们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。 系统允许一个进程创建进程...
  • 本篇文章通过fork函数的调用,来说明在Linux系统中创建一个新进程需要经历的过程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,794
精华内容 34,717
关键字:

创建进程的大致过程