进程管理专题(一)

视频4.进程原理及系统调用

task

Linux 内核把进程叫做任务 (task), 进程的虚拟地址空间可分为用户虚拟地址空间
核虚拟地址空间
所有进程共享内核虚拟地址空间--每个进程在用户态有独立的虚拟地址空间,但内核态的地址空间是映射到同一块(高地址部分)

C语言标准库中和Linux内核进程的概念中,对于进程和线程有点不同,比较如下表:

C 语言标准库进程 Linux 内核进程
包括多个线程的进程 线程组
只有一个线程的进程 任务或进程
线程 共享用户虚拟地址空间的进程

看完上表后,再来理解下面这句话:
进程有两种特殊的形式:没有用户虚拟地址空间的进程叫内核线程,共享用户虚拟地址空间的进程叫用户线程,共享同一个用户虚拟地址空间的所有用户线程叫线程组

Linux 通过 'ps' 命令用于输出当前系统的进程状态。显示瞬间进程的的状态,并不是动态连续;如果我们想对进程进行实时监控就 'top' 命令。

task_struct

task_struct 结构体在 include\linux\sched.h 中定义
TASK_RUNNING状态
定义: 可运行状态或者可就绪状态,表示进程处于可执行的状态,可能正在执行或等待在就绪队列中。
注意: 在Linux内核中,TASK_RUNNING状态并不严格区分进程是否正在CPU上运行。

TASK_INTERRUPTIBLE状态
定义: 可中断睡眠状态,又称浅睡眠状态。进程进入睡眠状态等待特定条件或资源,条件满足时可被唤醒。
特性: 可通过信号中断睡眠状态,使进程重新进入就绪队列。

TASK_UNINTERRUPTIBLE状态
定义: 不可中断状态,又称深度睡眠状态。进程在睡眠等待时不受信号干扰,不响应任何信号。
查看方式: 可以通过ps命令查看被标记为D状态的进程,即处于不可中断睡眠状态的进程。

__TASK_STOPPED状态
定义: 进程停止运行的状态,通常由于接收到停止信号(如SIGSTOP)。

EXIT_ZOMBIE状态
定义: 僵尸状态,进程已经消亡但在进程表中仍有记录,父进程尚未调用wait()系统调用回收其资源。
注意: 僵尸状态进程占用内核资源,应及时处理以避免资源泄漏。

task_struct中值得注意的: state,pid,tgid,pid_links[PIDTYPE_MAX],real_parent,parent,group_leader,cred,real_cred,comm[TASK_COMM_LEN], prio,static_prio,normal_prio,rt_priority,cpus_ptr,mm,active_mm,fs,files等等等等

进程优先级

image

关于 prio,static_prio,normal_prio,rt_priority 的比较与分析见一下博客:
https://blog.csdn.net/shulianghan/article/details/123786549

系统调用

在内核版本为 6.12.54 中的 fork.c 源码中,关于系统调用 fork,vfork等,最终都是调用kernel_clone(&args),具体解析见 《图解Linux内核(基于6.x) (姜亚华) (Z-Library)》--13.3 创建进程

#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
	struct kernel_clone_args args = {
		.exit_signal = SIGCHLD,
	};

	return kernel_clone(&args);
#else
	/* can not support in nommu mode */
	return -EINVAL;
#endif
}
#endif

#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
	struct kernel_clone_args args = {
		.flags		= CLONE_VFORK | CLONE_VM,
		.exit_signal	= SIGCHLD,
	};

	return kernel_clone(&args);
}
#endif

内核线程

内核线程是一种只存在内核态的进程,它不会在用户态运行,多是一些内核中的服务进程

它是独立运行在内核空间的进程,与普通用户进程的区别在于内核线程没有独立的进程地址空间。task_struct 数据结构里面有一个成员指针 mm 设置为 NULL, 说明它只能运行在内核空间

进程退出

进程主动终止:从main()函数返回,链接程序会自动添加到exit()系统调用;主动调用exit()系统函数。
进程被动终止:进程收到一个自己不能处理的信号;进程收到 SGKILL 等终止信息

SYSCALL_DEFINE1(exit, int, error_code)
{
	do_exit((error_code&0xff)<<8);
}

视频5.调度器及CFS调度器

1.调度:就是按照某种调度的算法设计,从进程的就绪队列当中选取进程分配CPU,主要是协调对CPU等相关资源使用,进程调度的目的是最大限度利用CPU的时间

2.如果调度器支持就绪状态切换到执行状态,同时支持执行状态切换到就绪状态,称调度器为抢占式调度器

3.调度类/调度器类 sched_class 结构体如下:

// kernel/sched/sched.h

struct sched_class {

#ifdef CONFIG_UCLAMP_TASK
	int uclamp_enabled;
#endif

	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
	bool (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
	void (*yield_task)   (struct rq *rq);
	bool (*yield_to_task)(struct rq *rq, struct task_struct *p);

	void (*wakeup_preempt)(struct rq *rq, struct task_struct *p, int flags);

	int (*balance)(struct rq *rq, struct task_struct *prev, struct rq_flags *rf);
	struct task_struct *(*pick_task)(struct rq *rq);
	/*
	 * Optional! When implemented pick_next_task() should be equivalent to:
	 *
	 *   next = pick_task();
	 *   if (next) {
	 *       put_prev_task(prev);
	 *       set_next_task_first(next);
	 *   }
	 */
	struct task_struct *(*pick_next_task)(struct rq *rq, struct task_struct *prev);

	void (*put_prev_task)(struct rq *rq, struct task_struct *p, struct task_struct *next);
	void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);

#ifdef CONFIG_SMP
	int  (*select_task_rq)(struct task_struct *p, int task_cpu, int flags);

	void (*migrate_task_rq)(struct task_struct *p, int new_cpu);

	void (*task_woken)(struct rq *this_rq, struct task_struct *task);

	void (*set_cpus_allowed)(struct task_struct *p, struct affinity_context *ctx);

	void (*rq_online)(struct rq *rq);
	void (*rq_offline)(struct rq *rq);

	struct rq *(*find_lock_rq)(struct task_struct *p, struct rq *rq);
#endif

	void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
	void (*task_fork)(struct task_struct *p);
	void (*task_dead)(struct task_struct *p);

	/*
	 * The switched_from() call is allowed to drop rq->lock, therefore we
	 * cannot assume the switched_from/switched_to pair is serialized by
	 * rq->lock. They are however serialized by p->pi_lock.
	 */
	void (*switching_to) (struct rq *this_rq, struct task_struct *task);
	void (*switched_from)(struct rq *this_rq, struct task_struct *task);
	void (*switched_to)  (struct rq *this_rq, struct task_struct *task);
	void (*reweight_task)(struct rq *this_rq, struct task_struct *task,
			      const struct load_weight *lw);
	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
			      int oldprio);

	unsigned int (*get_rr_interval)(struct rq *rq,
					struct task_struct *task);

	void (*update_curr)(struct rq *rq);

#ifdef CONFIG_FAIR_GROUP_SCHED
	void (*task_change_group)(struct task_struct *p);
#endif

#ifdef CONFIG_SCHED_CORE
	int (*task_is_throttled)(struct task_struct *p, int cpu);
#endif
};

4.调度器类可以分为以下五类

extern const struct sched_class stop_sched_class; //停机调度类
extern const struct sched_class dl_sched_class; //限期调度类
extern const struct sched_class rt_sched_class; //实时调度类
extern const struct sched_class fair_sched_class; //公平调度类
extern const struct sched_class idle_sched_class; //空闲调度类

这五种调度类的优先级从高到低依次为:停机调度类,限期调度类,实时调度类,公平调度类,空闲调度类

  • 停机调度类:优先级最高的调度类,停机进程是优先级最高的进程,可以抢占所有其他进程,其他进程不可抢占停机进程
  • 限期调度类:最早使用优先算法,使用红黑树把进程按照绝对截止期限从小到大排序,每次调度时选择绝对截止期限最小的进程
  • 实时调度类:为每个调度优先级维护一个队列
  • 公平调度类:使用完全公平的调度算法,这种算法会引入虚拟运行时间的相关概念--虚拟运行时间=实际运行时间*nice0 对应的权重/进程的权重
  • 空闲调度类:每个CPU上有一个空闲线程,即0号线程。空闲调度类优先级别最低,仅当没有其他进程可以调度的时候,才会调度空闲线程

5.进程优先级

// include\linux\sched\prio.h

#define MAX_RT_PRIO		100
#define MAX_DL_PRIO		0

#define MAX_PRIO		(MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO		(MAX_RT_PRIO + NICE_WIDTH / 2)

【进程分类】

实时进程(Real-Time Process):优先级高,需要立即被执行的进程
普通进程(Normal Process):优先级低,更长执行时间的进程

进程的优先级是用一个0-139的整数直接表示。数字越小,优先级越高,其中优先级0-99留给实时进程,100-139留给普通进程

6.内核调度策略

linux内核提供一些调度策略供用户应用程序来选择调度器,linux内核调度策略部分源码如下:

// include/uapi/linux/sched.h

/*
 * Scheduling policies
 */
#define SCHED_NORMAL		0
#define SCHED_FIFO		1
#define SCHED_RR		2
#define SCHED_BATCH		3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE		5
#define SCHED_DEADLINE		6
#define SCHED_EXT		7

/* Can be ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */
#define SCHED_RESET_ON_FORK     0x40000000
  • SCHED_NORMAL:普通进程调度策略,使 task 选择 CFS 调度器来调度运行
  • SCHED_FIFO:实时进程调度策略,先进先出调度没有时间片,没有更高优先级的状态下,只有等待主动让出 CPU
  • SCHED_RR:实时进程调度策略,时间片轮转,进程使用完时间片之后加入优先级对应运行队列当中的尾部,把 CPU 让给同等优先级的其他进程
  • SCHED_BATCH:普通进程调度策略,批量处理,使task选择 CFS 调度器来调度运行
  • SCHED_IDLE:普通进程调度策略,使 task 以最低优先级选择 CFS 调度器来调度运行
  • SCHED_DEADLINE:限期进程调度策略,使 task 选择 Deadline 调度器来调度运行
  • SCHED_EXT:扩展进程调度策略,用于支持自定义或实验性的调度器,使 task 可以根据特定需求选择扩展调度器来运行

补充:CFS(Completely Fair Scheduler)是 Linux 内核 2.6.23 之后默认的普通进程调度器,专为 SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE 这类非实时任务设计。它的核心目标是让所有进程尽可能公平地分享 CPU 时间,而不是传统意义上的“时间片”轮转。CFS 是一个“按比例分配 CPU 时间的红黑树调度器”,完全抛弃固定时间片,用虚拟运行时间(vruntime)衡量谁最“欠” CPU,就让谁运行。

补充:其中 stop 调度器和 DLE-task 调度器,仅在内核中使用,用户没有办法进行选择使用

7.CFS调度器类在linux内核源码如下:

//  kernel\sched\fair.c


const struct sched_class fair_sched_class;

/**************************************************************
 * CFS operations on generic schedulable entities:
 */


/*
 * All the scheduling class methods:
 */
DEFINE_SCHED_CLASS(fair) = {

	.enqueue_task		= enqueue_task_fair,
	.dequeue_task		= dequeue_task_fair,
	.yield_task		= yield_task_fair,
	.yield_to_task		= yield_to_task_fair,

	.wakeup_preempt		= check_preempt_wakeup_fair,

	.pick_task		= pick_task_fair,
	.pick_next_task		= __pick_next_task_fair,
	.put_prev_task		= put_prev_task_fair,
	.set_next_task          = set_next_task_fair,

#ifdef CONFIG_SMP
	.balance		= balance_fair,
	.select_task_rq		= select_task_rq_fair,
	.migrate_task_rq	= migrate_task_rq_fair,

	.rq_online		= rq_online_fair,
	.rq_offline		= rq_offline_fair,

	.task_dead		= task_dead_fair,
	.set_cpus_allowed	= set_cpus_allowed_fair,
#endif

	.task_tick		= task_tick_fair,
	.task_fork		= task_fork_fair,

	.reweight_task		= reweight_task_fair,
	.prio_changed		= prio_changed_fair,
	.switched_from		= switched_from_fair,
	.switched_to		= switched_to_fair,

	.get_rr_interval	= get_rr_interval_fair,

	.update_curr		= update_curr_fair,

#ifdef CONFIG_FAIR_GROUP_SCHED
	.task_change_group	= task_change_group_fair,
#endif

#ifdef CONFIG_SCHED_CORE
	.task_is_throttled	= task_is_throttled_fair,
#endif

#ifdef CONFIG_UCLAMP_TASK
	.uclamp_enabled		= 1,
#endif
};
  • enqueue_task_fair:当任务进入可运行状态时,用此函数将调度实体存放到红黑树,完成入队操作
  • dequeue_task_fair:当任务退出可运行状态时,用此函数将调度实体从红黑树中移除,完成出队操作

8.CFS调度器就绪队列内核源码如下

/* CFS-related fields in a runqueue */
struct cfs_rq {
	struct load_weight	load;
	unsigned int		nr_running;
	unsigned int		h_nr_queued;       /* SCHED_{NORMAL,BATCH,IDLE} */
	unsigned int		h_nr_runnable;     /* SCHED_{NORMAL,BATCH,IDLE} */
	unsigned int		idle_nr_running;   /* SCHED_IDLE */
	unsigned int		idle_h_nr_running; /* SCHED_IDLE */
	unsigned int		h_nr_delayed;

	s64			avg_vruntime;
	u64			avg_load;

	u64			min_vruntime;
#ifdef CONFIG_SCHED_CORE
	unsigned int		forceidle_seq;
	u64			min_vruntime_fi;
#endif

	struct rb_root_cached	tasks_timeline;

	/*
	 * 'curr' points to currently running entity on this cfs_rq.
	 * It is set to NULL otherwise (i.e when none are currently running).
	 */
	struct sched_entity	*curr;
	struct sched_entity	*next;

#ifdef CONFIG_SMP
	/*
	 * CFS load tracking
	 */
	struct sched_avg	avg;
#ifndef CONFIG_64BIT
	u64			last_update_time_copy;
#endif
	struct {
		raw_spinlock_t	lock ____cacheline_aligned;
		int		nr;
		unsigned long	load_avg;
		unsigned long	util_avg;
		unsigned long	runnable_avg;
	} removed;

#ifdef CONFIG_FAIR_GROUP_SCHED
	u64			last_update_tg_load_avg;
	unsigned long		tg_load_avg_contrib;
	long			propagate;
	long			prop_runnable_sum;

	/*
	 *   h_load = weight * f(tg)
	 *
	 * Where f(tg) is the recursive weight fraction assigned to
	 * this group.
	 */
	unsigned long		h_load;
	u64			last_h_load_update;
	struct sched_entity	*h_load_next;
#endif /* CONFIG_FAIR_GROUP_SCHED */
#endif /* CONFIG_SMP */

#ifdef CONFIG_FAIR_GROUP_SCHED
	struct rq		*rq;	/* CPU runqueue to which this cfs_rq is attached */

	/*
	 * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
	 * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
	 * (like users, containers etc.)
	 *
	 * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a CPU.
	 * This list is used during load balance.
	 */
	int			on_list;
	struct list_head	leaf_cfs_rq_list;
	struct task_group	*tg;	/* group that "owns" this runqueue */

	/* Locally cached copy of our task_group's idle value */
	int			idle;

#ifdef CONFIG_CFS_BANDWIDTH
	int			runtime_enabled;
	s64			runtime_remaining;

	u64			throttled_pelt_idle;
#ifndef CONFIG_64BIT
	u64                     throttled_pelt_idle_copy;
#endif
	u64			throttled_clock;
	u64			throttled_clock_pelt;
	u64			throttled_clock_pelt_time;
	u64			throttled_clock_self;
	u64			throttled_clock_self_time;
	int			throttled;
	int			throttle_count;
	struct list_head	throttled_list;
	struct list_head	throttled_csd_list;
#endif /* CONFIG_CFS_BANDWIDTH */
#endif /* CONFIG_FAIR_GROUP_SCHED */
};

cfs_rq:跟踪就绪队列信息以及管理就绪态调度实体,并维护一只按照虚拟时间排序的红黑树

视频6.实时调度类及 SMP 和 NUMA

1.Linux进程可分为两大类:实时进程和普通进程。实时进程与普通进程根本不同之处,如果系统中有一个实时进程可执行,那么调度器总是会选择它,除非另有一个优先级更高的实时进程

  • SCHED_FIFO:没有时间,在被调度器选择之后,可以运行任意长时间
  • SCHED_RR:有时间片,其值在进程运行时会减少

2.实时调度实体 sched_rt_entity 内核源码

// include\linux\sched.h

struct sched_rt_entity {
	struct list_head		run_list; //专门用于加入到优先级队列当中
	unsigned long			timeout;  //设置时间超时
	unsigned long			watchdog_stamp;  //用于记录jiffies值
	unsigned int			time_slice;  //时间片
	unsigned short			on_rq;  
	unsigned short			on_list;

	struct sched_rt_entity		*back;  //临时用于从上往下连接RT调度实体使用
#ifdef CONFIG_RT_GROUP_SCHED
	struct sched_rt_entity		*parent;  //指向父RT调度实体
	/* rq on which this entity is (to be) queued: */
	struct rt_rq			*rt_rq;
	/* rq "owned" by this entity/group: */
	struct rt_rq			*my_q;  //RT调度实体所拥有的实时运行队列,用于管理子任务
#endif
} __randomize_layout;

3.实时调度类 rt_sched_class 数据结构

DEFINE_SCHED_CLASS(rt) = {

	.enqueue_task		= enqueue_task_rt,  //将一个task存放到就绪队列或者尾部
	.dequeue_task		= dequeue_task_rt,  //将一个task从就绪队列的末尾
	.yield_task		= yield_task_rt,  //主动放弃执行

	.wakeup_preempt		= wakeup_preempt_rt,

	.pick_task		= pick_task_rt,  //核心调度器选择就绪队列当中的那个哪个任务将要被调度, prev 是将要被调度出的任务,返回值是将要被调度的任务
	.put_prev_task		= put_prev_task_rt,  //当一个任务将要被调出出时执行
	.set_next_task          = set_next_task_rt,  

#ifdef CONFIG_SMP
	.balance		= balance_rt,
	.select_task_rq		= select_task_rq_rt,
	.set_cpus_allowed       = set_cpus_allowed_common,
	.rq_online              = rq_online_rt,
	.rq_offline             = rq_offline_rt,
	.task_woken		= task_woken_rt,
	.switched_from		= switched_from_rt,
	.find_lock_rq		= find_lock_lowest_rq,
#endif

	.task_tick		= task_tick_rt,

	.get_rr_interval	= get_rr_interval_rt,

	.prio_changed		= prio_changed_rt,
	.switched_to		= switched_to_rt,

	.update_curr		= update_curr_rt,

#ifdef CONFIG_SCHED_CORE
	.task_is_throttled	= task_is_throttled_rt,
#endif

#ifdef CONFIG_UCLAMP_TASK
	.uclamp_enabled		= 1,
#endif
};

其中宏 DEFINE_SCHED_CLASS(name) 展开如下:

#define DEFINE_SCHED_CLASS(name) \
const struct sched_class name##_sched_class \
	__aligned(__alignof__(struct sched_class)) \
	__section("__" #name "_sched_class")

A:实时调度类操作核心函数 enqueue_task_rt -> 插入进程:

/*
 * Adding/removing a task to/from a priority array:
 */

//更新调度信息,将调度实体插入到相应优先级队列的末尾
static void
enqueue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
	struct sched_rt_entity *rt_se = &p->rt;

	if (flags & ENQUEUE_WAKEUP)
		rt_se->timeout = 0;

	check_schedstat_required();
	update_stats_wait_start_rt(rt_rq_of_se(rt_se), rt_se);

	enqueue_rt_entity(rt_se, flags);

	if (!task_current(rq, p) && p->nr_cpus_allowed > 1)
		enqueue_pushable_task(rq, p);
}

B:实时调度类操作核心函数 pick_next_rt_entity -> 选择进程,实时调度类会选择最高优先级的实时进程来运行:

static struct sched_rt_entity *pick_next_rt_entity(struct rt_rq *rt_rq)
{
	struct rt_prio_array *array = &rt_rq->active;
	struct sched_rt_entity *next = NULL;
	struct list_head *queue;
	int idx;
    
    //首先找到一个可用的实体
	idx = sched_find_first_bit(array->bitmap);  
	BUG_ON(idx >= MAX_RT_PRIO);

    //从链表组中找到对应的链表
	queue = array->queue + idx;  
	if (SCHED_WARN_ON(list_empty(queue)))
		return NULL;
	next = list_entry(queue->next, struct sched_rt_entity, run_list);

	return next;  //返回找到的运行实体
}

C:实时调度类操作核心函数 dequeue_task_rt -> 删除进程:

static bool dequeue_task_rt(struct rq *rq, struct task_struct *p, int flags)
{
	struct sched_rt_entity *rt_se = &p->rt;

	update_curr_rt(rq);  //更新调度数据信息等
	dequeue_rt_entity(rt_se, flags); //将rt_se从运行队列中删除,然后添加到队列尾部
    
    
	dequeue_pushable_task(rq, p);

	return true;
}

5.SMP服务器CPU利用率最好的情况下是2至4个CPU

6.从应用层系统架构,目前商用服务器大体分为三类:SMP,NUMA,MPP

  • NUMA优势:一台物理服务器内部继承多个CPU,使系统具有较高事务处理能力。NUMA架构适合OLTP事务处理环境
  • SMP优势:当前使用的OTLP程序当中,用户访问

视频7.进程优先级与调度策略

posted @ 2025-10-21 16:28  爱吃鸡魔人zf  阅读(4)  评论(0)    收藏  举报