diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index 1dcffed54354e5166615ea7f1e6353c0e8372050..0c87d940374a4e8a3188f9e733c70160cc2eb8af 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -7,7 +7,7 @@ RUN yum install python2 python2-devel gcc gcc-c++ wget libyaml-devel -y && \ python2 get-pip.py RUN pip install --upgrade setuptools && \ pip install --global-option='--with-libyaml' pyyaml && \ - pip install six sh coloredlogs future fire jinja2 docopt && \ + pip install six sh coloredlogs future fire docopt && \ yum install python2-lxml python2-pygments python2-six -y && \ yum install systemd git make bison flex \ gcc-plugin-devel \ diff --git a/Dockerfile.x86_64 b/Dockerfile.x86_64 index 26e42c15cf252e38760bd34160656df269bf27a3..f3ffe1c943d385a78fb655df5755815ae11e7b83 100644 --- a/Dockerfile.x86_64 +++ b/Dockerfile.x86_64 @@ -7,7 +7,7 @@ RUN yum install python2 python2-devel gcc gcc-c++ wget libyaml-devel -y && \ python2 get-pip.py RUN pip install --upgrade setuptools && \ pip install --global-option='--with-libyaml' pyyaml && \ - pip install six sh coloredlogs future fire jinja2 docopt && \ + pip install six sh coloredlogs future fire docopt && \ yum install make bison flex \ gcc-plugin-devel \ elfutils-libelf-devel openssl openssl-devel \ diff --git a/NOTICE b/NOTICE index 02f476bbd7b81c59799a51884bc680bf3d174c8c..e8ffa5823ccb110558b9156611d0c11f4b0adec3 100644 --- a/NOTICE +++ b/NOTICE @@ -33,7 +33,3 @@ Licensed under the MIT License coloredlogs Copyright (c) 2020 Peter Odding Licensed under the MIT License ----------------------------------- -jinja -Copyright 2007 Pallets -Licensed under the BSD-3-Clause License \ No newline at end of file diff --git a/README.md b/README.md index 7a0121cd1b14b78ba6b7961f0720f3a57e8cd555..67766836bc659883d9b95d898952d13c8c9a1b00 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ Plugsched currently supports Anolis OS 7.9 ANCK by default, and other OS need to 2. Create a temporary working directory and download the source code of the kernel. ```shell -# mkdir /tmp/work # uname -r 4.19.91-25.2.an7.x86_64 # cd /tmp/work diff --git a/README_zh.md b/README_zh.md index e82882449813f154f6a1b05ca383aae179b9b1cc..8e7bb57b7aa342fef4e55d9162a6f496fb5243ea 100644 --- a/README_zh.md +++ b/README_zh.md @@ -12,11 +12,17 @@ plugsched 是 Linux 内核调度器子系统热升级的 SDK,它可以实现 对于函数而言,它对外呈现了一些接口函数。通过替换内核中的这些函数,内核就可以绕过原有的执行逻辑进入新的调度器模块中执行,即可完成函数的升级。在模块中的函数要么是接口函数,要么是内部函数,其它函数都是外部函数。 -对于数据,调度器模块重新初始化私有数据,并从前一个调度器继承共享数据。大多数重要数据(运行队列状态和调度类状态)通过状态重建技术重新初始化,而不仅仅是重置内存,它们因此自动变成了 private 数据。为了灵活性, plugsched 允许用户手动将其余部分数据定义为私有数据。然而,手动定义的私有数据只会被清零。因此,为了简单起见,默认情况下,其它数据是共享数据。 +对于数据而言,存在两个问题:是否可以修改结构体,数据如何初始化。 -对于数据而言,用户不仅想知道数据是如何初始化的,还想知道是否可以修改数据本身或者它的语义。Plugsched 没有对全局变量和局部变量设置严格的规则,因此用户可以修改数据本身或它们的语义。但是结构体不同。首先,plugsched 将只被调度器访问的结构体成员分类为内部成员,其他为非内部成员。调度器模块允许修改内部成员的语义,禁止修改非内部成员的语义。如果结构体所有成员都是内部成员,则调度器模块允许修改整个结构体。但是,我们建议使用结构体中的保留字段,而不是修改现有成员。 +**是否可以修改结构体** +结构体**不允许**修改。包括不允许改变长度、不能挪用结构体成员为它用。背后的原因是,这些操作改变了结构体成员的语义,这种行为非常危险。如果用户有往结构体中增加成员的需求,请使用结构体中的保留字段,或者参见[Mempool](#Advanced-Features-zh.md#mempool)。 -比如,修改 rq->lock 的状态会修改数据本身,而用 rq->lock 存其他数据,是修改了它的语义。而缩短 rq 结构体的大小,相当于修改了 rq 中的一些成员。但是由于 rq->lock 被其它子系统使用,因此它是一个非内部成员,所以不允许修改 rq->lock 或 rq 结构体。 +**数据如何初始化** +数据的初始化目前有两种手段 +- 重构数据:**调度器状态重建**技术首先自动清理掉旧调度器的状态,然后自动初始化新调度器的状态。它主要覆盖调度队列、负载、统计相关的状态信息。该技术充分考虑了新旧调度器设计上的不同,而能自动解决它们设计上的差异。 +- 继承升级前的数据:除了重构的数据之外,其余数据均自动地从之前的调度器中继承而来,即保持数据跟之前一致。 + +继承的数据也可以理解为多个调度器间共享的数据,而重构的数据则可以理解为每个调度器各自拥有的数据(因为切换时数据被drop然后重新初始化)。 ### 边界提取 调度器本身并不是模块,因此需要明确调度器的边界才能将它模块化,边界划分程序根据边界配置信息从内核源代码中将调度器模块的代码提取出来。边界配置信息主要包含代码文件、接口函数等信息。最终将边界内的代码提取到独立的目录中,主要分为以下过程: @@ -58,7 +64,6 @@ plugsched 是 Linux 内核调度器子系统热升级的 SDK,它可以实现 2. 创建临时工作目录,下载系统内核的 SRPM 包: ```shell -# mkdir /tmp/work # uname -r 4.19.91-25.2.an7.x86_64 # cd /tmp/work @@ -225,7 +230,7 @@ kpatch 是函数粒度的热升级,plugsched 是子系统范围的热升级, **Q:可以修改调度器边界之外的函数吗?** -可以,我们提供了 [sidecar](./docs/Advanced-Features.md) 机制可以同时修改边界之外的函数。比如,有些 hotfix 既修改了调度器,又修改了 cpuacct 中的内容,可以使用 sidecar 机制升级 cpuacct 中的内容。 +可以,我们提供了 [sidecar](./docs/Advanced-Features-zh.md) 机制可以同时修改边界之外的函数。比如,有些 hotfix 既修改了调度器,又修改了 cpuacct 中的内容,可以使用 sidecar 机制升级 cpuacct 中的内容。 ## Supported Architectures - [X] x86-64 diff --git a/docs/Advanced-Features-zh.md b/docs/Advanced-Features-zh.md new file mode 100644 index 0000000000000000000000000000000000000000..f147900880b363f0781db8d73209a07785017ee4 --- /dev/null +++ b/docs/Advanced-Features-zh.md @@ -0,0 +1,296 @@ +# Sidecar +由于 Linux 内核调度与其他子系统紧耦合,因此修改调度器往往也要少量修改其他子系统。比如, + +- 如果想对组调度(Group Scheduling)做一些统计工作,可能需要同时修改 cpuacct.c。 +- 如果想修改内核线程的调度策略,你可能也会修改 kthread.c。 +- 如果想修改 CPU 亲和性相关策略,可能还需要修改 cpuset.c。 + +Sidecar 复用了很多 plugsched 的基础设施,因此 sidecar 用起来几乎跟 plugsched 的核心功能一样简单。 + +### How it works +sidecar 的开发只需要在一个文件里进行 `kernel/sched/mod/sidecar.c` 。 + +开发好这个文件之后,就可以触发标准的编译和打包。参考 [Quick Start](../README_zh.md#quick-start)。Plugsched 便会去编译这个文件,然后把编译结果链接到ko里。 + +接下来,在安装调度器模块时,类似于 [编译及安装调度器](../README_zh.md#编译及安装调度器) 中提到的功能,都会为 sidecar 函数也执行一遍。(除调度器状态重建外) + +### 例子 +这个例子也可以在`/path/to/plugsched/examples/`中找到。 +1. 登陆云服务器后,先安装一些必要的基础软件包 +2. 创建临时工作目录,下载系统内核的 SRPM 包 +3. 启动并进入容器 +4. 解压内核源代码 +5. 进行边界划分与提取 +步骤1~5 详细操作参考[Quick Start](../README_zh.md#quick-start). + +6. 测试 sidecar 功能 + +把下面这行粘贴进文件 `kernel/sched/mod/export_jump_sidecar.h` + + EXPORT_SIDECAR(name_to_int, fs/proc/util.c, unsigned, const struct qstr *) + +把下面这段粘贴进文件 `kernel/sched/mod/sidecar.c` +```c +/** + * Copyright 2019-2022 Alibaba Group Holding Limited. + * SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + */ + +/* + * Copied from ./fs/proc/util.c and did a little modification to it + */ +#include + +unsigned name_to_int(const struct qstr *qstr) +{ +  const char *name = qstr->name; +  int len = qstr->len; +  unsigned n = 0; +  trace_printk("%s\n", name);      /* !! We added this line !! */ + +  if (len > 1 && *name == '0') + goto out; +  do { + unsigned c = *name++ - '0'; + if (c > 9) +  goto out; + if (n >= (~0U-9)/10) +  goto out; + n *= 10; + n += c; + } while (--len > 0); +  return n; +  out: +  return ~0U; +} +``` + +7. 打包生成 rpm 包 + +``` shell +# plugsched-cli build /tmp/work/scheduler +``` + +8. 将生成的 rpm 包拷贝到宿主机,退出容器,安装调度器 +``` shell +rpm -ivh /tmp/work/scheduler-xxx-4.19.91-25.2.an7.yyy.x86_64.rpm +``` +# Mempool + +### 为什么需要mempool + +plugsched的用户很可能会有修改结构体的需求。例如,在结构体中增加、删除、替换一个成员。但plugsched并不支持修改结构体大小、布局。 + +**删除** +如果用户希望删除一个成员,则应该只删除它被引用的地方代码,而不应该删除结构体中的成员定义,以保持结构体大小不变。 + +**替换** +我们不建议替换成员,建议删除并增加新成员。以降低误用的风险。 + +**增加** +如果用户希望增加一个成员,则应该保证内核源代码中该结构体中至少有一个**保留字段**,例如 +```c +struct my_struct { + int field1; + u64 reserved1; + u64 reserved2; +} +``` +用户便可以使用reserved1或reserved2存放新增加的成员,以保持结构体大小不变。如果用户需要增加不止2个成员怎么办?这就是mempool引入的原因。 + +### How it works +```plain +用户定义要在哪个父亲结构体(task_struct或sched_entity等)中, +以指针的形式嵌入什么新的子结构体, + ↓ +加载模块 + ↓ +按需统计对象的数量。(O(1)复杂度) <------------------------------------+ + ↓ | +该数量乘以一个系数再乘以sizeof(子结构体),分配mempool。 | + ↓ | +stop_machine | + ↓ Y | +重新统计数量,数量超出预留内存?---------------------------------------+ + ↓ N +利用所有CPU,遍历所有对象 (O(N/CPU)复杂度,N是对象数量,例如进程数量) +将mempool分成N块,每块的大小为sizeof(子结构体) + ↓ +加载完成 + +进程创建 进程销毁 + ↓ ↓ +kmalloc分配新的 如果是从mempool中分配出来的,则还给mempool → 如果mempool还完则释放mempool +子结构体 如果是从kmalloc出来的,则直接kfree + +``` + +### 例子 +我们以task\_struct为例,假如用户希望在task\_struct增加一个enhance\_task结构。 + +注意,`nr_threads + nr_cpu_ids`,表示普通线程数量 + idle线程数量 + +注意文件 kernel/sched/mod/mempool.h,该文件其实是完全空白的,其中只包括一些Example,并且被注释掉了。 +而下面的diff,则参照着注释,将mempool.h填充上了。 + +```diff +diff --git a/include/linux/sched.h b/include/linux/sched.h +index 8055f43d8..9a31143f9 100644 +--- a/include/linux/sched.h ++++ b/include/linux/sched.h +@@ -625,6 +625,12 @@ struct wake_q_node { + struct wake_q_node *next; + }; + ++struct enhance_task { ++ u64 val1; ++ u64 val2; ++ u64 val3; ++}; ++ + struct task_struct { + #ifdef CONFIG_THREAD_INFO_IN_TASK + /* +@@ -1268,7 +1274,7 @@ struct task_struct { + + struct callback_head mce_kill_me; + #endif +- CK_HOTFIX_RESERVE(1) ++ struct enhance_task *et; + CK_HOTFIX_RESERVE(2) + CK_HOTFIX_RESERVE(3) + CK_HOTFIX_RESERVE(4) +diff --git a/kernel/sched/mod/Makefile b/kernel/sched/mod/Makefile +index 3e4d48a77..d11652cea 100644 +--- a/kernel/sched/mod/Makefile ++++ b/kernel/sched/mod/Makefile +@@ -41,1 +41,1 @@ obj-stubs := $(patsubst %.o,%.stub.o,$(objs-y)) +-ccflags-n += -DSCHEDMOD_MEMPOOL ++ccflags-y += -DSCHEDMOD_MEMPOOL +diff --git a/kernel/sched/mod/core.c b/kernel/sched/mod/core.c +index f3376073c..99fda425e 100644 +--- a/kernel/sched/mod/core.c ++++ b/kernel/sched/mod/core.c +@@ -1999,6 +1999,8 @@ int wake_up_state(struct task_struct *p, unsigned int state) + static void __sched_fork(unsigned long clone_flags, struct task_struct *p) + { + p->on_rq = 0; ++ p->et = kzalloc(sizeof(struct enhance_task), GFP_KERNEL); ++ p->et->val1 = p->comm[0]; + + p->se.on_rq = 0; + p->se.exec_start = 0; +diff --git a/kernel/sched/mod/export_jump_sidecar.h b/kernel/sched/mod/export_jump_sidecar.h +index 6cdadeedd..04601eac6 100644 +--- a/kernel/sched/mod/export_jump_sidecar.h ++++ b/kernel/sched/mod/export_jump_sidecar.h +@@ -6,3 +6,4 @@ + * from scheduler functions. Sidecar helps to do this. + * See /path/to/plugsched/examples/export_jump_sidecar.h for usage examples. + */ ++EXPORT_SIDECAR(release_thread, arch/x86/kernel/process_64.c, void, struct task_struct *) +diff --git a/kernel/sched/mod/mempool.h b/kernel/sched/mod/mempool.h +index 24d18d374..92c080e04 100644 +--- a/kernel/sched/mod/mempool.h ++++ b/kernel/sched/mod/mempool.h +@@ -103,6 +103,12 @@ static int recheck_mempool_##name(void) \ + * nr_cpu_ids); // we alloc nr_cpu_ids objects before stop_machine + */ + ++DEFINE_RESERVE(task_struct, // struct task_struct ++ et, // struct task_struct's et field ++ et, // name the mempool as et ++ nr_threads + nr_cpu_ids, // we need exactly nr_threads + nr_cpu_ids objects, ++ (nr_threads + nr_cpu_ids)*2) // we addloc twice as many before stop_machine ++ + static int sched_mempools_create(void) + { + int err; +@@ -116,6 +122,9 @@ static int sched_mempools_create(void) + * return err; + */ + ++ if ((err = create_mempool_et())) ++ return err; ++ + return 0; + } + +@@ -126,6 +135,8 @@ static void sched_mempools_destroy(void) + * simple_mempool_destroy(se_smp); + * simple_mempool_destroy(rq_smp); + */ ++ ++ simple_mempool_destroy(et_smp); + } + + static int recheck_smps(void) +@@ -141,6 +152,8 @@ static int recheck_smps(void) + * return err; + */ + ++ if ((err = recheck_mempool_et())) ++ return err; + return 0; + } + +@@ -161,7 +174,16 @@ static void sched_alloc_extrapad(void) + + * for_each_process_thread(p, t) + * t->se.statistics.bvt = alloc_se_reserve(); +- ++ */ ++ int cpu; ++ struct task_struct *p, *t; ++ for_each_process_thread(p, t) { ++ t->et = alloc_et_reserve(); ++ t->et->val1 = t->comm[0]; ++ } ++ for_each_possible_cpu(cpu) ++ idle_task(cpu)->et = alloc_et_reserve(); ++ /* + * list_for_each_entry_rcu(tg, &task_groups, list) { + * if (tg == &root_task_group || task_group_is_autogroup(tg)) + * continue; +@@ -185,9 +207,14 @@ static void sched_free_extrapad(void) + * release_se_reserve(&idle_task(cpu)->se.statistics); + * release_rq_reserve(cpu_rq(cpu)); + * } +- * for_each_process_thread (p, t) +- * release_se_reserve(&t->se.statistics); +- ++ */ ++ int cpu; ++ struct task_struct *p, *t; ++ for_each_process_thread (p, t) ++ release_et_reserve(t); ++ for_each_possible_cpu(cpu) ++ release_et_reserve(idle_task(cpu)); ++ /* + * list_for_each_entry_rcu(tg, &task_groups, list) { + * if (tg == &root_task_group || task_group_is_autogroup(tg)) + * continue; +diff --git a/kernel/sched/mod/sidecar.c b/kernel/sched/mod/sidecar.c +index 21c45a6b2..eabc74410 100644 +--- a/kernel/sched/mod/sidecar.c ++++ b/kernel/sched/mod/sidecar.c +@@ -8,3 +8,18 @@ + * from scheduler functions. Sidecar helps to do this. + * See /path/to/plugsched/examples/sidecar.c for usage examples. + */ ++#include "sched.h" ++ ++#define MCOUNT_SIZE 5 ++#define ORIG_FUNC(f) (*(typeof(&(f)))((unsigned long)__vmlinux__##f + MCOUNT_SIZE)) ++ ++extern void release_et_reserve(struct task_struct*); ++extern void __vmlinux__release_thread(struct task_struct*); ++ ++void release_thread(struct task_struct *dead_task) ++{ ++ printk("%s %d\n", dead_task->comm, dead_task->et->val1); ++ release_et_reserve(dead_task); ++ ++ ORIG_FUNC(release_thread)(dead_task); ++} +``` diff --git a/docs/Education-zh.md b/docs/Education-zh.md new file mode 100644 index 0000000000000000000000000000000000000000..310bc21eebad76ecf7c8a16d45ad2afac2e8927a --- /dev/null +++ b/docs/Education-zh.md @@ -0,0 +1,58 @@ +# 教学功能 + +本节文档适合于开设了操作系统课程的高校师生阅读。plugsched适用于操作系统的 **进程管理** 或 **调度器** 相关章节教学演示、课后实验、课程设计的目的。 + +--- + +## 背景 + +**操作系统** + +操作系统是软件工程大类的传统必修课程,以及许多其他相关专业的选修课程。对于计算机相关人才的培养是必不可少的知识领域,该课程受到许多高校教授的重视。例如著名的Minix系统就是操作系统领域泰斗Tanenbaum教授研发来用于教学目的的操作系统,现在广为人知的Linux系统,则直接受其启发而诞生。 + +操作系统课程同时也是学习难度较高的一门课程,很多高校缺乏让学生上手的实验作业,很多学生对操作系统的理解停留于教科书表面,对关键知识点缺乏形象理解。 + +这些问题的根源,除了操作系统代码复杂这一根因之外,还有各种外部的困难性。例如开发测试环境搭建上手困难,调试方法复杂,问题排查困难等、Linux内核很重型编译要花很长时间。 + +**进程管理和调度器** + +进程管理和调度器是操作系统课程的核心章节之一,许多学生对操作系统的最初认识,常常是从FIFO、SJF等传统调度器模型开始的。但是常常浅尝辄止,而对实际生产应用中的调度器没有感受。 + +--- + +## plugsched用于教学目的 + +**plugsched带来的教学便利** + +plugsched基于Linux操作系统,在进程管理和调度器这一个子领域,为操作系统教学打开了一个便捷入口。使用它,师生可以获得这些便利性: + +- 依托成熟的OpenAnolis系统,不需要所有工作都from sctrach。 +- 容器化开发环境,环境准备、编译一键完成。 +- 开发工作聚焦于调度器,工作目录文件数量少,不受其他子系统影响。 +- 开发并**随意增加调试输出**,**半分钟编译** 完成后,直接安装rpm包不需要重启即可生效 **开发快速** + +无论是课堂演示、课程设计、课后实验等,都可以以很低的门槛入门进行。教师也很容易量化作业结果。 + +**plugsched用于进程管理各个功能** + +plugsched方便演示和实验进程管理和调度器的不限于以下的各个知识点的理解和hacking:(下面用task统称进程和线) + +- task的PCB +- task队列管理 +- 核间通信 +- 核间同步 +- task状态转移和生命周期管理 +- CPU负载均衡 +- ... + +--- + +## Getting Started + +推荐师生按照以下流程上手plugsched: + +- 学习README,简单了解plugsched的设计 +- 重点学习README中的QuickStart章节,实验上手plugsched的使用 +- 学习examples目录下的例子 + +完成这些学习之后,就可以开始尝试自己hack调度器。 \ No newline at end of file diff --git a/src/mempool.h b/src/mempool.h index 55c844766e91c10385175c90ba3929f883e754c5..7db3a96565b1371659d1b2f3160bccd6a091b2da 100644 --- a/src/mempool.h +++ b/src/mempool.h @@ -65,7 +65,7 @@ static inline struct simple_mempool *simple_mempool_create(int obj_num, int obj_ return smpool; } -static inline void simple_mempool_destory(struct simple_mempool *smpool) +static inline void simple_mempool_destroy(struct simple_mempool *smpool) { vfree(smpool->vstart); kfree(smpool); @@ -132,7 +132,7 @@ static void *simple_percpu_mempool_alloc(struct simple_percpu_mempool *psmpool) return (void *)ret; } -static void simple_percpu_mempool_destory(struct simple_percpu_mempool *psmpool) +static void simple_percpu_mempool_destroy(struct simple_percpu_mempool *psmpool) { int i; @@ -265,9 +265,9 @@ static void sched_mempools_destroy(void) { /* * Examples of mempools destroy - * simple_mempool_destory(se_smp); - * simple_mempool_destory(rq_smp); - * simple_percpu_mempool_destory(percpu_var_smp); + * simple_mempool_destroy(se_smp); + * simple_mempool_destroy(rq_smp); + * simple_percpu_mempool_destroy(percpu_var_smp); */ }