一、进程创建的基本原理
Linux进程创建采用**复制-写时复制(Copy-on-Write, COW)**机制,主要依靠以下两个系统调用:
-
fork()
创建一个与当前进程几乎完全相同的新进程(子进程),父子进程的地址空间初始时共享,写时分离。 -
vfork()
类似于fork,但父进程会被挂起,子进程与父进程共享地址空间,适用于马上exec的场景。 -
clone()
更底层、更灵活的接口,可以指定哪些资源与父进程共享(如文件描述符、内存空间等),容器和线程实现常用。 -
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),其主要流程:
- 检查资源和参数
- 调用
copy_process(),复制父进程的task_struct等结构体 - 设置写时复制的页表
- 分配PID
- 把新进程加入调度队列
- 返回新进程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中的parent、children、sibling等指针。
2. 创建流程(以fork为例)
源码主要在kernel/fork.c,流程如下:
-
系统调用入口
用户空间调用fork(),内核转入__do_fork()或_do_fork()。 -
参数检查和资源准备
检查资源限制(如最大进程数)、参数有效性。 -
复制进程描述符
调用copy_process(),复制父进程的task_struct。
其中内存空间采用写时复制(COW),初始时父子进程共享物理页面,只有写入时才分离。 -
PID分配
调用alloc_pid()为新进程分配唯一PID。 -
进程插入进程链表/树
把新进程插入内核的进程管理结构中。 -
唤醒调度器
新进程加入可运行队列,等待调度器分配CPU。 -
返回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. 调度流程简述
-
进程状态变化
当进程变为可运行(如唤醒、创建),加入运行队列。 -
调度器选择进程
调度器根据算法(如CFS),选择下一个要运行的进程。 -
上下文切换
保存当前进程状态,恢复新进程状态,切换页表等。 -
抢占与优先级
某些情况下(如新进程优先级更高),当前进程被抢占。
5. 相关源码文件
kernel/sched/core.c:调度器核心kernel/sched/fair.c:CFS实现kernel/sched/rt.c:实时调度实现
6. 简单流程图
进程变为可运行
↓
加入运行队列(红黑树)
↓
调度器定时/事件触发
↓
选择vruntime最小进程
↓
上下文切换
↓
新进程获得CPU
十、补充说明
- 进程调度与优先级、nice值、cgroup等密切相关。
- 可用
ps,top,htop等工具查看进程状态和调度信息。 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, ¶m);
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
创作不易,点赞关注,持续更新!!!

3003

被折叠的 条评论
为什么被折叠?



