Linux 学习笔记——第二章 进程管理和调度(1)

Linux 学习笔记——第二章 进程管理和调度(1)

《深入 Linux 内核架构》阅读笔记。书籍参考的内核版本较老,文章参考的 Linux 内核版本为 5.4.103,并根据新版内核调整了一些代码片段

调度器

调度器是 Linux 内核的一个子系统,它的主要职责有:

  • 内核必须决定为各个进程分配多长时间,何时切换到下一个进程,这又引出了哪个进程是下一个的问题。此类调度策略是平台无关的。
  • 在内核从进程 A 切换到进程 B 时,必须确保进程 B 的执行环境与上一次撤销其处理器资源时完全相同。例如,处理器寄存器的内容和虚拟地址空间的结构必须与此前相同。这项工作与处理器极度相关,不能只用 C 语言实现,还需要汇编代码的帮助。

进程优先级

  • 硬实时进程:有严格的时间限制,某些任务必须在指定的时限内完成。例如飞机的飞行控制命令。Linux 的主流内核不支持硬实时处理。由于 Linux 是针对吞吐量优化,试图尽快地处理常见情形,其实很难实现可保证的响应时间。
  • 软实时进程:硬实时进程的一种弱化形式。例如对 CD 的写入操作。
  • 普通进程:大多数进程是没有特定时间约束的普通进程,但仍然可以根据重要性来分配优先级

抢占式多任务处理

各个进程都分配到一定的时间段可以执行。时间段到期后,内核会从进程收回控制权,让一个不同的进程运行,而不考虑前一进程所执行的上一个任务。被抢占进程的运行时环境,即所有 CPU 寄存器的内容和页表,都会保存起来,因此其执行结果不会丢失。在该进程恢复执行时,其进程环境可以完全恢复。时间片的长度会根据进程重要性(以及因此而分配的优先级)的不同而变化。

Linux 进程管理的结构中还需要另外两种进程状态选项:用户状态和核心态。如果进程想要访问系统数据或功能(后者管理着所有进程之间共享的资源,例如文件系统空间),则必须切换到核心态。切换有两种方法:

  • 通过系统调用。
  • 通过中断,此时切换是自动触发的。处理中断的操作,通常与中断发生时执行的进程无关。

内核的抢占调度模型建立了一个层次结构,用于判断哪些进程状态可以由其他状态抢占。

  • 普通进程总是可能被抢占,甚至是由其他进程抢占。在一个重要进程变为可运行时,例如编辑器接收到了等待已久的键盘输入,调度器可以决定是否立即执行该进程,即使当前进程仍然在正常运行。对于实现良好的交互行为和低系统延迟,这种抢占起到了重要作用。
  • 如果系统处于核心态并正在处理系统调用,那么系统中的其他进程是无法夺取其 CPU 时间的。调度器必须等到系统调用执行结束,才能选择另一个进程执行,但中断可以中止系统调用。(在进行重要的内核操作时,可以停用几乎所有的中断)
  • 中断可以暂停处于用户状态和核心态的进程。中断具有最高优先级,因为在中断触发后需要尽快处理。
  • 在内核 2.5 开发期间,一个称之为内核抢占(kernel preemption)的选项添加到内核。 该选项支持在紧急情况下切换到另一个进程,甚至当前是处于核心态执行系统调用(中断处理期间是不行的)。但该特性的代价是增加内核的复杂度,因为接下来有许多数据结构需要针对并发访问进行保护,即使在单处理器系统上也是如此。

Linux 的调度器

  • O ( 1 ) O(1) O(1) 调度器:在 2.5 系列内核开发期间使用,它可以在常数时间内完成其工作,不依赖于系统上运行的进程数目。
  • 完全公平调度器 (completely fair scheduler):在内核版本 2.6.23 开发期间合并进来,它试图尽可能地模仿理想情况下的公平调度。此外,它不仅可以调度单个进程,还能够处理更一般性的调度实体(scheduling entity)。例如,该调度器分配可用时间时,可以首先在不同用户之间分配,接下来在各个用户的进程之间分配。

“僵尸”进程

进程的常见状态有运行态、就绪态、等待(睡眠)态,一个特殊的进程状态是“僵尸”状态。说这些进程死了,是因为其资源(内存、与外设的连接,等等)已经释放,因此它们无法也决不会再次运行。说它们仍然活着,是因为进程表中仍然有对应的表项。

僵尸是如何产生的?其原因在于 UNIX 操作系统下进程创建和销毁的方式。在两种事件发生时,程序将终止运行:

  • 程序必须由另一个进程或一个用户杀死(通常是通过发送 SIGTERM 或 SIGKILL 信号来完成,这等价于正常地终止进程)。
  • 进程的父进程在子进程终止时必须调用或已经调用 wait4(读做 wait for)系统调用。 这相当于向内核证实父进程已经确认子进程的终结。该系统调用使得内核可以释放为子进程保留的资源。

只有在第一个条件发生(程序终止)而第二个条件不成立的情况下(wait4),才会出现“僵尸”状态。在进程终止之后,其数据尚未从进程表删除之前,进程总是暂时处于“僵尸”状态。

进程表示

Linux 内核涉及进程和程序的所有算法都围绕一个名为 task_struct 的数据结构建立,该结构定义在 include/linux/sched.h 中。这是系统中主要的一个结构,该结构的内容可以分解为各个部分,每个部分表示进程的一个特定方面。

  • 状态和执行信息,如待决信号、使用的二进制格式(和其他系统二进制格式的任何仿真信息)、进程 ID 号(pid)、到父进程及其他有关进程的指针、优先级和程序执行有关的时间信息(例如 CPU 时间)。
  • 有关已经分配的虚拟内存的信息。
  • 进程身份凭据,如用户 ID、组 ID 以及权限等。可使用系统调用查询(或修改)这些数据。
  • 使用的文件包含程序代码的二进制文件,以及进程所处理的所有文件的文件系统信息,这些都必须保存下来。
  • 线程信息记录该进程特定于 CPU 的运行时间数据(该结构的其余字段与所使用的硬件无关)。
  • 在与其他应用程序协作时所需的进程间通信有关的信息。
  • 该进程所用的信号处理程序,用于响应到来的信号。
进程的当前状态
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long			state;

state 指定了进程的当前状态,可使用下列值:

  • TASK_RUNNING 意味着进程处于可运行状态。这并不意味着已经实际分配了 CPU。进程可能会
    一直等到调度器选中它。该状态确保进程可以立即运行,而无需等待外部事件。
  • TASK_INTERRUPTIBLE 是针对等待某事件或其他资源的睡眠进程设置的。在内核发送信号给该进程表明事件已经发生时,进程状态变为 TASK_RUNNING,它只要调度器选中该进程即可恢复执行。
  • TASK_UNINTERRUPTIBLE 用于因内核指示而停用的睡眠进程。它们不能由外部信号唤醒,只能由内核亲自唤醒。
  • TASK_STOPPED 表示进程特意停止运行,例如,由调试器暂停。
  • TASK_TRACED 本来不是进程状态,用于从停止的进程中,将当前被调试的那些(使用 ptrace 机制)与常规的进程区分开来。

下列常量既可以用于 task_struct 的进程状态字段,也可以用于 exit_state 字段,后者明确地用于退出进程。

  • EXIT_ZOMBIE 僵尸状态。
  • EXIT_DEAD 状态则是指 wait 系统调用已经发出,而进程完全从系统移除之前的状态。只有多个线程对同一个进程发出 wait 调用时,该状态才有意义。
资源限制机制

Linux 提供资源限制机制,对进程使用系统资源施加某些限制。该机制利用了 task_struct 中的 signal 字段拥有的 rlim 数组,数组元素类型为 struct rlimit,其代码位于 usr/include/linux/resource.h 中。

struct rlimit {
	__kernel_ulong_t	rlim_cur;
	__kernel_ulong_t	rlim_max;
};

rlim_cur 是进程当前的资源限制,也称之为软限制(soft limit)。rlim_max 是该限制的最大容许值,因此也称之为硬限制(hard limit)。系统调用 setrlimit 来增减当前限制,但不能超出 rlim_max 指定的值。getrlimits 用于检查当前限制。

init 进程的限制在系统启动时生效, 定义在 include/asm-generic/resource.h 中。

/*
 * boot-time rlimit defaults for the init task:
 */
#define INIT_RLIMITS							\
{									\
	[RLIMIT_CPU]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_FSIZE]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_DATA]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_STACK]		= {       _STK_LIM,  RLIM_INFINITY },	\
	[RLIMIT_CORE]		= {              0,  RLIM_INFINITY },	\
	[RLIMIT_RSS]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_NPROC]		= {              0,              0 },	\
	[RLIMIT_NOFILE]		= {   INR_OPEN_CUR,   INR_OPEN_MAX },	\
	[RLIMIT_MEMLOCK]	= {    MLOCK_LIMIT,    MLOCK_LIMIT },	\
	[RLIMIT_AS]			= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_LOCKS]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
	[RLIMIT_SIGPENDING]	= { 		0,	       0 },	\
	[RLIMIT_MSGQUEUE]	= {   MQ_BYTES_MAX,   MQ_BYTES_MAX },	\
	[RLIMIT_NICE]		= { 0, 0 },				\
	[RLIMIT_RTPRIO]		= { 0, 0 },				\
	[RLIMIT_RTTIME]		= {  RLIM_INFINITY,  RLIM_INFINITY },	\
}

#endif

内核在 proc 文件系统中对每个进程都包含了对应的一个文件,这样就可以查看当前的 rlimit 值:

$ cat /proc/3/limits 
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             23727                23727                processes 
Max open files            1024                 4096                 files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       23727                23727                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

生成该文件的代码位于 /fs/proc/base.c 的 proc_pid_limits 函数中:

/* Display limits for a process */
static int proc_pid_limits(struct seq_file *m, struct pid_namespace *ns,
			   struct pid *pid, struct task_struct *task)
{
	unsigned int i;
	unsigned long flags;

	struct rlimit rlim[RLIM_NLIMITS];

	if (!lock_task_sighand(task, &flags))
		return 0;
	memcpy(rlim, task->signal->rlim, sizeof(struct rlimit) * RLIM_NLIMITS);
	unlock_task_sighand(task, &flags);

	/*
	 * print the file header
	 */
	seq_puts(m, "Limit                     "
		"Soft Limit           "
		"Hard Limit           "
		"Units     \n");

	for (i = 0; i < RLIM_NLIMITS; i++) {
		if (rlim[i].rlim_cur == RLIM_INFINITY)
			seq_printf(m, "%-25s %-20s ",
				   lnames[i].name, "unlimited");
		else
			seq_printf(m, "%-25s %-20lu ",
				   lnames[i].name, rlim[i].rlim_cur);

		if (rlim[i].rlim_max == RLIM_INFINITY)
			seq_printf(m, "%-20s ", "unlimited");
		else
			seq_printf(m, "%-20lu ", rlim[i].rlim_max);

		if (lnames[i].unit)
			seq_printf(m, "%-10s\n", lnames[i].unit);
		else
			seq_putc(m, '\n');
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值