Linux 进程创建

一、进程创建的基本原理

Linux进程创建采用**复制-写时复制(Copy-on-Write, COW)**机制,主要依靠以下两个系统调用:

  1. fork()
    创建一个与当前进程几乎完全相同的新进程(子进程),父子进程的地址空间初始时共享,写时分离。

  2. vfork()
    类似于fork,但父进程会被挂起,子进程与父进程共享地址空间,适用于马上exec的场景。

  3. clone()
    更底层、更灵活的接口,可以指定哪些资源与父进程共享(如文件描述符、内存空间等),容器和线程实现常用。

  4. execve()
    用于让当前进程加载并执行新程序,通常与fork/clone配合使用。


二、用户空间API

1. fork()

#include <unistd.h>

pid_t pid = fork();
if (pid == 0) {
    // 子进程代码
} else if (pid > 0) {
    // 父进程代码
} else {
    // 错误处理
}

2. vfork()

pid_t pid = vfork();

vfork更高效,但使用不当可能导致问题,推荐只在马上exec的场景下用。

3. clone()

#define _GNU_SOURCE
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);

用于实现线程、容器等。glibc的pthread_create底层也是用clone实现的。

4. exec族函数

#include <unistd.h>
execl("/bin/ls", "ls", NULL);

exec族函数会用新程序替换当前进程的代码段和数据段,PID不变。


三、内核源码层面(简要)

Linux内核进程创建核心函数是do_fork()(旧版本叫do_fork,新版本叫_do_fork),其主要流程:

  1. 检查资源和参数
  2. 调用copy_process(),复制父进程的task_struct等结构体
  3. 设置写时复制的页表
  4. 分配PID
  5. 把新进程加入调度队列
  6. 返回新进程PID

源码位置:kernel/fork.c


四、进程创建流程图

用户空间
   |
   | fork()/vfork()/clone()
   v
内核空间
   |
   | _do_fork()
   |-> copy_process()
   |-> 分配PID
   |-> 设置资源
   v
新进程
   |
   | execve()(如需加载新程序)
   v
新进程运行新程序

五、进程创建的常见用途

  • shell执行命令:fork+exec
  • 服务端并发处理:fork多个子进程
  • 多线程实现:clone(共享资源)
  • 容器技术:clone(指定命名空间)

六、进程创建的特点

  • PID唯一:每个进程有唯一PID
  • 父子关系:进程有父进程和子进程
  • 资源继承/共享:文件描述符、环境变量等可继承或共享
  • 写时复制:初始共享内存,写入时分离

七、示例代码

fork+exec

#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        execl("/bin/ls", "ls", NULL);
    } else if (pid > 0) {
        // 父进程
        printf("Parent PID: %d, Child PID: %d\n", getpid(), pid);
    }
    return 0;
}

八、内核进程创建实现细节

1. 主要数据结构

  • task_struct
    每个进程都用一个task_struct结构体表示,包含PID、状态、优先级、调度信息、资源指针等。

  • 进程树/链表
    内核用链表和树结构管理所有进程,典型如task_struct中的parentchildrensibling等指针。

2. 创建流程(以fork为例)

源码主要在kernel/fork.c,流程如下:

  1. 系统调用入口
    用户空间调用fork(),内核转入__do_fork()_do_fork()

  2. 参数检查和资源准备
    检查资源限制(如最大进程数)、参数有效性。

  3. 复制进程描述符
    调用copy_process(),复制父进程的task_struct
    其中内存空间采用写时复制(COW),初始时父子进程共享物理页面,只有写入时才分离。

  4. PID分配
    调用alloc_pid()为新进程分配唯一PID。

  5. 进程插入进程链表/树
    把新进程插入内核的进程管理结构中。

  6. 唤醒调度器
    新进程加入可运行队列,等待调度器分配CPU。

  7. 返回PID
    父进程获得新子进程的PID,子进程获得0。

相关源码片段(伪代码简化)


long _do_fork(...) {
    struct task_struct *p;
    p = copy_process(...); // 复制进程描述符
    if (!IS_ERR(p)) {
        wake_up_new_task(p); // 唤醒调度器
        return p->pid;       // 返回新进程PID
    }
    return error;
}

重要函数

  • copy_process()
  • alloc_pid()
  • wake_up_new_task()

3. 写时复制(COW)

  • 父子进程初始共享物理内存页,均为只读。
  • 任何一方写入时,触发页异常,内核复制一份物理页,保证互不影响。

4. clone系统调用

  • 更灵活,可通过参数决定哪些资源共享(如内存、文件描述符、信号处理等)。
  • 线程实现本质是用clone共享资源。

九、进程调度机制

1. 调度器核心

  • 负责决定哪个进程获得CPU、何时切换进程。
  • 现代Linux采用CFS(完全公平调度器),源码在kernel/sched/

2. 主要调度算法

  • CFS(Completely Fair Scheduler)
    用红黑树管理所有可运行进程,每个进程有一个虚拟运行时间(vruntime),调度器优先选择vruntime最小的进程。

  • 实时调度(RT)
    包括SCHED_FIFO、SCHED_RR等,优先级高于普通进程。

3. 关键数据结构

  • struct task_struct:进程描述符
  • struct rq:每个CPU的运行队列
  • 红黑树(struct rb_root):CFS的核心数据结构

4. 调度流程简述

  1. 进程状态变化
    当进程变为可运行(如唤醒、创建),加入运行队列。

  2. 调度器选择进程
    调度器根据算法(如CFS),选择下一个要运行的进程。

  3. 上下文切换
    保存当前进程状态,恢复新进程状态,切换页表等。

  4. 抢占与优先级
    某些情况下(如新进程优先级更高),当前进程被抢占。

5. 相关源码文件

  • kernel/sched/core.c:调度器核心
  • kernel/sched/fair.c:CFS实现
  • kernel/sched/rt.c:实时调度实现

6. 简单流程图

进程变为可运行
      ↓
加入运行队列(红黑树)
      ↓
调度器定时/事件触发
      ↓
选择vruntime最小进程
      ↓
上下文切换
      ↓
新进程获得CPU

十、补充说明

  • 进程调度与优先级、nice值、cgroup等密切相关。
  • 可用pstophtop等工具查看进程状态和调度信息。
  • sched_setaffinity等API可控制进程CPU亲和性。

十一、Linux进程调度源码具体解析(CFS)

1. 关键结构体

1.1 task_struct

每个进程的描述符,调度相关字段如:

struct task_struct {
    ...
    int prio;             // 动态优先级
    int static_prio;      // 静态优先级
    int normal_prio;      // 普通优先级
    unsigned long policy; // 调度策略(如SCHED_NORMAL, SCHED_FIFO)
    struct sched_entity se; // CFS调度实体
    ...
};
1.2 sched_entity

CFS调度实体,记录调度相关信息:

struct sched_entity {
    struct rb_node run_node;   // 红黑树节点
    u64 vruntime;              // 虚拟运行时间
    ...
};
1.3 cfs_rq

CFS运行队列,红黑树实现:

struct cfs_rq {
    struct rb_root tasks_timeline; // 红黑树根节点
    struct sched_entity *curr;     // 当前正在运行的实体
    ...
};

2. 关键流程函数

2.1 进程加入运行队列

enqueue_task_fair()(kernel/sched/fair.c)

static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) {
    struct cfs_rq *cfs_rq = &rq->cfs;
    struct sched_entity *se = &p->se;
    // 插入红黑树
    __enqueue_entity(cfs_rq, se);
    // 更新队列信息
    ...
}
  • 进程被唤醒或新建时,调用此函数加入CFS队列。
2.2 选择下一个要运行的进程

pick_next_task_fair()(kernel/sched/fair.c)

static struct task_struct *pick_next_task_fair(struct rq *rq) {
    struct cfs_rq *cfs_rq = &rq->cfs;
    struct sched_entity *se = __pick_next_entity(cfs_rq);
    if (!se)
        return NULL;
    return entity_to_task(se);
}
  • 红黑树中vruntime最小的实体被选中。
2.3 虚拟运行时间更新

每次进程获得CPU时,都会按实际运行时间更新vruntime,保持公平。


3. 红黑树调度机制

  • 所有可运行进程按vruntime排序,谁“被调度得最少”谁优先获得CPU。
  • 插入、删除、查找都是红黑树操作,时间复杂度O(log n)。

4. 相关源码文件

  • kernel/sched/core.c:调度框架
  • kernel/sched/fair.c:CFS算法实现
  • include/linux/sched.h:task_struct定义
  • kernel/sched/rt.c:实时调度实现
  • kernel/sched/deadline.c:deadline调度实现

十二、进程调度参数调优

1. 优先级与nice值

  • 普通进程优先级通过nice值调整,范围-20(最高优先级)到19(最低)。
  • nice命令或setpriority系统调用设置:
    nice -n -5 ./myapp
    
    setpriority(PRIO_PROCESS, pid, -5);
    

2. 调度策略

  • SCHED_OTHER(普通CFS)
  • SCHED_FIFO(实时,先到先服务)
  • SCHED_RR(实时,轮转)
  • SCHED_DEADLINE(实时,带截止时间)

更改进程调度策略:

#include <sched.h>
struct sched_param param = { .sched_priority = 10 };
sched_setscheduler(pid, SCHED_FIFO, &param);

3. cgroup和CPU资源限制

  • 用cgroup(控制组)限制某一组进程的CPU份额。
  • 例如,限制某cgroup最多使用20% CPU:
    mkdir /sys/fs/cgroup/cpu/mygroup
    echo 20000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us
    echo 100000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us
    
  • 将进程加入cgroup:
    echo <pid> > /sys/fs/cgroup/cpu/mygroup/cgroup.procs
    

4. CPU亲和性(affinity)

  • 控制进程在哪些CPU上运行,提高cache命中率,减少迁移开销。
  • taskset命令或sched_setaffinity系统调用:
    taskset -c 0,1 ./myapp
    
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);
    sched_setaffinity(pid, sizeof(mask), &mask);
    

5. sysctl参数调优

  • /proc/sys/kernel/sched_min_granularity_ns:最小时间片
  • /proc/sys/kernel/sched_latency_ns:调度周期
  • /proc/sys/kernel/sched_child_runs_first:fork后子进程是否优先运行

例如,调整调度周期:

echo 10000000 > /proc/sys/kernel/sched_latency_ns

创作不易,点赞关注,持续更新!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值