背景:
GNU time 的基本功能
- 测量程序执行时间和系统资源使用情况
- 提供比 shell 内建 time 更详细的统计信息
考虑引入GNU time 来对内核调度子系统的性能进行测试,方便实现 PELT 跨核负载均衡。
预期效果:
实际执行的时候发生了Panic,原因是 preempt_count > 0
具体复现方法:
- 在 DragonOS 中通过 dadk 引入GNU time
{
"name": "gnu time",
"version": "1.9",
"description": "gnu time",
"rust_target": null,
"task_type": {
"BuildFromSource": {
"Archive": {
"url": "https://ftp.gnu.org/gnu/time/time-1.9.tar.gz"
}
}
},
"depends": [],
"build": {
"build_command": "./configure CC=x86_64-linux-musl-gcc CFLAGS=-static && make -j $(nproc) && DESTDIR=$DADK_CURRENT_BUILD_DIR make install"
},
"clean": {
"clean_command": "make clean"
},
"install": {
"in_dragonos_path": "/"
},
"build_once": true,
"install_once": true,
"target_arch": ["x86_64"]
}
- 编译启动后,执行
time ls
,或者其他自定义的测试程序/指令
- 触发 panic:
e[41m[ ERROR ] e[0m(src/lib.rs:117) Kernel Panic Occurred.
Location:
File: src/sched/mod.rs
Line: 823, Column: 5
Message:
assertion `left == right` failed
left: 1
right: 0
由于 preempt_count 相关的上下文无法通过 log 打印日志debug,暂时无法定位问题出在哪里,或许需要比较熟悉调度的同学一起看一下
1 个赞
由于 锁追踪 相关的 lockdep 是一个比较复杂的内核模块,并没有成熟的可移植的针对内核的工具(至少我目前了解到的情况),所以我采取修改 GNU time 源码通过打日志的方式来排查是哪一步触发了 panic 。据目前的排查情况来看,问题有可能出现在 signal 这一块。见下图:
圈出来的部分,如果该函数中有四个 signal 函数调用,如果不注释掉前面两个调用,程序无法执行到打印日志 “signal” 就 panic 了,注释掉之后,又会在后面的 signal 调用中触发 panic 而不会打印出 “signal 2”。
试了一下,发现好像是父进程在等待子进程终止这个过程结束的时候,获取的rd_children没有drop掉
drop掉之后得到下面的结果
1 个赞
这个问题是因为子进程 do_execve() 的时候需要加载可执行文件,但是 ls 并不在当前的 /bin 目录下,而是在 /usr/local/bin/ls,所以发生了非法的访问
目前在注释掉 do_wait() 中的一行 unsafe { ProcessManager::release(*pid) };
(否则执行完release函数后发生了一个很奇怪的阻塞)后, time
基本可以正常使用。
注释后:
注释前:
相关日志代码片段:
// 截取自 do_wait()
if state.is_exited() {
kwo.ret_status = state.exit_code().unwrap() as i32;
log::debug!("Child process {:?} has exited with status: {}", pid, kwo.ret_status);
drop(pcb);
log::debug!("Dropped PCB");
unsafe { ProcessManager::release(*pid) };
log::debug!("Released process");
drop(irq_guard);
log::debug!("Dropped IRQ guard");
log::debug!("do_wait return");
return Ok((*pid).into());
}
pub unsafe fn release(pid: Pid) {
let pcb = ProcessManager::find(pid);
if pcb.is_some() {
log::debug!("have find {:?}, ready to release", pid);
// let pcb = pcb.unwrap();
// 判断该pcb是否在全局没有任何引用
// TODO: 当前,pcb的Arc指针存在泄露问题,引用计数不正确,打算在接下来实现debug专用的Arc,方便调试,然后解决这个bug。
// 因此目前暂时注释掉,使得能跑
// if Arc::strong_count(&pcb) <= 2 {
// drop(pcb);
// ALL_PROCESS.lock().as_mut().unwrap().remove(&pid);
// } else {
// // 如果不为1就panic
// let msg = format!("pcb '{:?}' is still referenced, strong count={}",pcb.pid(), Arc::strong_count(&pcb));
// error!("{}", msg);
// panic!()
// }
log::debug!("ready to remove process {:?} from ALL_PROCESS", pid);
ALL_PROCESS.lock_irqsave().as_mut().unwrap().remove(&pid);
log::debug!("has remove process {:?} from ALL_PROCESS", pid);
} else {
log::debug!("didn't find {:?}", pid);
}
}
time
主要关注执行的总时间,无法直接提供关于进程在哪些核心上运行的信息。因此,单独使用 time
无法精确地测试进程是否进行了跨核调度。它只能给出整体的执行时间,而无法揭示进程在各个 CPU 核心之间的调度过程。
如果目标是测试进程的执行时间,并且关心的是跨核调度对总执行时间的影响(例如,通过增加多核间的迁移或缓存失效),time
可以提供一些线索,但它不会告诉我们跨核调度是否发生。我们需要通过其他方法来更好地控制和观察这一过程,例如:
- 使用
taskset
:指定进程运行在哪些核心上,这样可以排除跨核调度对性能的影响,或强制进程跨核运行来测试其影响。
- 监控工具:使用诸如
top
、htop
或 perf
等工具来观察进程是否被调度到多个核心。
所以如果要测试多核调度性能或许需要内核先支持 CPU 亲和性并实现 sched_setaffinity
系统调用,从而为 taskset
的引入提供支持。
【任务发布】实现 sched_setaffinity 系统调用 和 CPU 亲和性 · Issue #1035 · DragonOS-Community/DragonOS
另外, 虽然 time
和 taskset
可以给出基本的性能指标,但它们没有办法提供多核负载均衡的详细分析。
而支持 top
、htop
则需要先支持通过 /proc
文件系统来访问进程信息(如 /proc/[pid]/stat
)和系统资源使用情况(如 /proc/meminfo
和 /proc/stat
);perf
需要内核的 perf_events
子系统支持。
【任务发布】支持通过 /proc 来访问进程信息和系统资源使用情况 · Issue #1036 · DragonOS-Community/DragonOS
1 个赞