以下是分析Linux内核调度器源码之初始化的完整攻略:
一、准备工作
1.1 确认内核版本
在开始分析内核调度器源码之前,先要确认自己使用的内核版本。可以通过以下命令查看:
uname -r
1.2 获取内核源码
从官方网站或者镜像站点下载对应内核版本的源码包,解压后存放在合适的位置。也可以通过以下命令获取内核源码:
git clone https://github.com/torvalds/linux.git
1.3 安装内核调试符号
通过以下命令安装内核调试符号,方便在调试时查看源代码:
sudo apt-get install linux-image-$(uname -r)-dbgsym
二、分析内核调度器源码
内核调度器源码主要位于kernel/sched/
目录下,其中init_task.c
是调度器初始化的入口文件,我们从这个文件开始分析。
2.1 了解任务结构体
任务结构体task_struct
是内核中描述进程/线程的数据结构,定义在include/linux/sched.h
文件中,其中包含各种成员变量,比如进程ID、父进程ID、线程状态等等。
struct task_struct {
…… // 其它成员
pid_t pid; // 进程ID
pid_t tgid; // 线程组ID
…… // 其它成员
};
2.2 了解调度器初始化过程
具体来说,调度器初始化过程包括以下几个部分:
- 初始化进程0的
task_struct
结构体。 - 初始化进程0的
thread_info
结构体。 - 初始化内核线程的
task_struct
结构体和thread_info
结构体。 - 初始化进程1以及
swapper
线程的task_struct
结构体。
其中,swapper
线程是内核中专门用来做调度的进程,它的负责空转和协作调度器进行操作系统级别的任务调度。
2.3 分析源码细节
以上是调度器初始化过程的大致概述,接下来分析具体的源代码,请见以下两个示例:
示例1:初始化进程0的task_struct
结构体。
进程0是系统中唯一一个运行在内核模式下的进程,它在内核初始化过程中被创建并且不会结束。在init_task
中完成它的初始化过程。
// 在arch/x86/kernel/head_64.S中,定义了进程0(swapper)的task_union和其地址
// 进程0的task_struct定义在include/linux/sched.h中
// 我们可以在init_task.c中发现的代码如下:
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
struct task_struct init_task = INIT_TASK(init_task);
从代码中可以看出,进程0的task_struct
结构体由INIT_TASK(init_task)
定义,INIT_TASK
宏展开后如下:
/* include/linux/sched.h */
#define INIT_TASK(tsk) { \
.state = 0, \
.stack = init_thread_union.stack, \
.usage = ATOMIC_INIT(2), \
.flags = PF_KTHREAD, \
.prio = MAX_PRIO - 20, \
.static_prio = MAX_PRIO - 20, \
.normal_prio = MAX_PRIO - 20, \
.rt_priority = MAX_RT_PRIO - 1, \
.pid = 0, \
.tgid = 0, \
.stack_canary = STACK_CANARY_INIT, \
.task_file = INIT_FILES, \
.tasks = LIST_HEAD_INIT(init_task.tasks), \
.ptraced = LIST_HEAD_INIT(init_task.ptraced), \
.children = LIST_HEAD_INIT(init_task.children), \
.sibling = LIST_HEAD_INIT(init_task.sibling), \
.group_leader = &init_task, \
.se = { \
.group_node = LIST_HEAD_INIT(init_task.se.group_node), \
.on_rq = 0, \
.load = { { 0, 0 } }, \
.runnable_weight= WMULT_CONST(TASK_UNMAPPED_BASE), \
.sum_exec_runtime = 0, \
.prev_sum_exec_runtime = 0, \
.last_wakeup = 0, \
.last_wakeup_time = 0, \
.nr_migrations = 0, \
.statistics = { \
[0] = { .max = 0, .sum = 0, .count = 0 }, \
[1] = { .max = 0, .sum = 0, .count = 0 }, \
[2] = { .max = 0, .sum = 0, .count = 0 }, \
[3] = { .max = 0, .sum = 0, .count = 0 }, \
}, \
.wakeup_preempt_start = 0, \
.wakeup_preempt_end = 0, \
.wakeup_flags = 0, \
.load_avg = { INIT_AVG, INIT_AVG, INIT_TASK_LOAD }, \
.se_nr_migrations = 0, \
.nr_failed_migrations = 0, \
.h_load = { [0 ... H_LOADIDX_MAX - 1] = WMULT_CONST(TASK_UNMAPPED_BASE), }, \
.h_nr_running = 0, \
.h_nr_scan = 0, \
}, \
.sched = { \
.next = LIST_HEAD_INIT(init_task.sched.next), \
.prev = LIST_HEAD_INIT(init_task.sched.prev), \
.group_node = LIST_HEAD_INIT(init_task.sched.group_node), \
.on_rq = 0, \
.on_lock = 0, \
.prio = MAX_PRIO - 20, \
.static_prio = MAX_PRIO - 20, \
.normal_prio = MAX_PRIO - 20, \
.rt_priority = MAX_RT_PRIO - 1, \
.policy = SCHED_NORMAL, \
.nr_cpus_allowed= NR_CPUS, \
.cpus_allowed = CPU_MASK_ALL, \
.cpus_ptr = &cpu_possible_mask, \
.nr_cpus = NR_CPUS, \
.load = { { 0, 0 } }, \
.se = &init_task.se, \
.timestamp = 0, \
}, \
.thread = { \
.sp = init_thread_union.stack + THREAD_SIZE, \
.tp_value = 0, \
.flags = SP_DEFAULT, \
.pstate = PSR_MODE_EL0t, \
.addr_limit = KERNEL_DS, \
.sve_max_vl = SVE_VL_LEN_MASK, \
#ifdef CONFIG_ARM64_SVE \
.sve_state = {}, \
#endif \
}, \
.ptrace = { \
.test_thread = current, \
}, \
.journal_info = NULL, \
.audit_context = NULL, \
.pi_lock = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock), \
.pi_waiters = LIST_HEAD_INIT(init_task.pi_waiters), \
.pi_top_task = &init_task, \
INIT_CGROUP_SCHED(init_task), \
INIT_TRACE_IRQFLAGS, \
INIT_PERF_EVENTS(init_task), \
INIT_PERF_EVENTS_LIST, \
INIT_TASK_RCU_PREEMPT(init_task) \
}
示例2:初始化进程0的thread_info
结构体。
下面是thread_info
结构体的定义,定义在include/linux/sched.h
文件中,task_struct
结构体中包含一个thread_info
结构体的指针。
struct thread_info {
struct task_struct *task;
struct exec_domain *exec_domain;
__u32 flags;
__u32 status;
__u32 cpu;
int preempt_count;
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
struct vtime vtime;
#endif
#ifdef CONFIG_DEBUG_STACK_USAGE
unsigned long *stack_canary;
#endif
};
那么,thread_info
结构体如何初始化呢?我们在init_task
中找到相应的代码,如下:
#define INIT_THREAD_INFO(var) { \
.task = &(var), \
.exec_domain = &default_exec_domain, \
.flags = 0, \
.status = 0, \
.cpu = 0, \
.preempt_count = 0, \
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
.vtime = {.state = VTIME_INACTIVE},
#endif
#ifdef CONFIG_DEBUG_STACK_USAGE
.stack_canary = &init_thread_union.stack[THREAD_SIZE/sizeof(long)-1],
#endif
}
由此可以看出,thread_info
结构体中的各个成员变量都在这里得到了初始化。其中,task
指向的是它所属的task_struct
结构体,stack_canary
用于检测栈溢出。
三、总结
通过以上的分析过程,我们可以清楚地了解到Linux内核调度器的初始化过程和相关的源代码细节,为我们理解进程调度机制提供了帮助。
上述示例只是其中的一部分,实际上Linux内核调度器的源码比较复杂,要想完全掌握它的原理,需要不断去阅读内核代码和了解相关原理。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:分析Linux内核调度器源码之初始化 - Python技术站