Linux内核15-内核如何创建进程

简介: Linux内核15-内核如何创建进程

1. _do_fork()函数


不论是clone()、fork()还是vfork(),它们最核心的部分还是调用_do_fork()(一个与体系无关的函数),完成创建进程的工作。它具有如下参数:

早期版本中是调用do_fork()函数。其实,_do_forkdo_fork在进程的复制的时候并没有太大的区别, 他们就只是在进程tls复制的过程中实现有细微差别

下面是_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,
          unsigned long tls)
{
    struct task_struct *p;
    int trace = 0;
    long nr;
    /*
     * 当从kernel_thread调用或CLONE_UNTRACED被设置时,不需要报告event
     * 否则,报告使用哪种fork类型的event
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;
        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }
    /* 拷贝进程描述符 */
    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
    /*
     * 在唤醒新线程之前,先检查指针是否合法
     * 因为如果线程创建后立即退出的话,线程指针可能会非法
     */
    if (!IS_ERR(p)) {
        struct completion vfork;
        struct pid *pid;
        trace_sched_process_fork(current, p);
        /* 分配PID */
        pid = get_task_pid(p, PIDTYPE_PID);
        nr = pid_vnr(pid);
        /* 把新进程PID写入到父进程的用户态变量中。*/
        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);
        /* 如果实现的是vfork调用,则完成completion机制,确保父进程后续运行 */
        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }
        /* 唤醒新进程 */
        wake_up_new_task(p);
        /* fork过程完成,子进程开始运行,并告知ptracer */
        if (unlikely(trace))
            ptrace_event_pid(trace, pid);
        /*
         * 如果实现的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;
}

2. 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,
                    unsigned long tls,
                    int node)
{
    // 1. 检查参数clone_flags,一些标志组合是否合理
    // ...
    // 2. 其它的安全检查
    retval = security_task_create(clone_flags);
    // 3. 分配一个新的task_struct结构,与父进程完全相同,只是stack不同
    p = dup_task_struct(current, node);
    // 4. 检查该用户的进程数是否超过限制
    if (atomic_read(&p->real_cred->user->processes) >=
            task_rlimit(p, RLIMIT_NPROC)) {
        // 检查该用户是否具有相关权限,不一定是root
        if (p->real_cred->user != INIT_USER &&
            // ...
    }
    // 5. 检查进程数量是否超过max_threads,后者取决于内存的大小
    if (nr_threads >= max_threads)
    // ...
    // 6. 进程描述符相关变量初始化
    // 7. 完成调度器相关数据结构的初始化
    retval = sched_fork(clone_flags, p);
    // 8. 拷贝所有的进程信息
    shm_init_task(p);
    retval = copy_semundo(clone_flags, p);
    retval = copy_files(clone_flags, p);
    retval = copy_fs(clone_flags, p);
    retval = copy_sighand(clone_flags, p);
    retval = copy_signal(clone_flags, p);
    retval = copy_mm(clone_flags, p);
    retval = copy_namespaces(clone_flags, p);
    retval = copy_io(clone_flags, p);
    // 9. 设置子进程的pid
    retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
    // ...
    // 10. 设置子进程的PID
    p->pid = pid_nr(pid);
    // 11. 根据是创建线程还是进程设置线程组组长、进程组组长等等信息
    // ...
    // 12. 将pid加入PIDTYPE_PID这个散列表
    if (likely(p->pid)) {
        // ..
        attach_pid(p, PIDTYPE_PID);
        nr_threads++;
    }
    // 释放资源,善后处理
    return p;
err:
    // 错误处理
}

现在,我们已经有了一个可运行的子进程,但是,实际上还没有运行。至于何时运行取决于调度器。在未来的某个进程切换时间点上,调度器把子进程描述符中的thread成员中的值加载到CPU上,赋予子进程CPU的使用权。esp寄存器加载thread.esp的值(也就是获取了子进程的内核态栈的地址),eip寄存器加载ret_from_fork()函数的返回地址(子进程执行的下一条指令)。ret_from_fork()是一个汇编函数,它调用schedule_tail(),继而调用finish_task_switch()函数,完成进程切换,然后把栈上的值加载到寄存器中,强迫CPU进入用户模式。基本上,新进程的执行恰恰在fork()、vfork()或clone()系统调用结束之时。该系统调用的返回值保存在寄存器eax中:对于子进程是0,对于父进程来说就是子进程的PID。

要想理解这一点,应该要重点理解一下copy_thread_tls()函数,其与早期的copy_thread()函数非常类似,只是在末尾添加了向子线程添加tls的内容。

在copy_thread_tls()函数中,我们可以看到这样的代码:

childregs->ax = 0;
p->thread.ip = (unsigned long) ret_from_fork;

这就是为什么子进程为什么返回PID=0的原因。

总结

  1. 这个函数看似很复杂,其实就是根据用户态传递过来的控制参数,实现进程的4要素:执行代码、私有堆栈空间、进程控制块(task_struct)和独立的内存空间。当然了,线程只是拷贝父进程的即可。
  2. 创建完进程的4要素之后,把新进程的最开始执行的指令设置到eip寄存器即可。然后就是等待内核调度。当轮到新进程使用CPU的时候,就从eip寄存器开始执行。


相关文章
|
19天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
45 1
|
8天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
59 13
|
15天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
19天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
19天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
20天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
20天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
23天前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
34 3
|
25天前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
40 6
|
23天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####