Linux 0.11进程状态追踪实战:手把手教你用fprintk给内核打日志(附Python分析脚本)

张开发
2026/4/16 12:21:34 15 分钟阅读

分享文章

Linux 0.11进程状态追踪实战:手把手教你用fprintk给内核打日志(附Python分析脚本)
Linux 0.11进程状态追踪实战手把手教你用fprintk给内核打日志附Python分析脚本在操作系统内核开发中理解进程的生命周期和状态转换是至关重要的。Linux 0.11作为早期Linux内核版本其简洁的设计和清晰的代码结构使其成为学习操作系统原理的理想选择。本文将带你深入探索如何在Linux 0.11内核中插入日志打印函数实时追踪进程从创建到消亡的完整生命周期。1. 环境准备与基础概念在开始修改内核代码之前我们需要先了解一些基本概念和准备工作。Linux 0.11中的进程状态主要包括以下几种N (新建)进程刚被创建时的状态J (就绪)进程准备好运行等待CPU调度R (运行)进程正在CPU上执行W (等待)进程因等待资源而进入睡眠状态E (退出)进程结束运行为了追踪这些状态变化我们需要在内核的关键调度函数中插入日志打印语句。这里我们使用fprintk函数它是内核中专门用于打印日志的函数。注意在修改内核代码前建议先备份原始文件以便出现问题时可以快速恢复。2. 内核代码修改实战2.1 初始化日志文件首先我们需要在内核初始化时创建日志文件。修改main.c文件位于linux-0.11/init/目录// 在init函数中增加以下语句 (void) open(/var/process.log, O_CREAT|O_TRUNC|O_WRONLY, 0666);这里有几个关键点需要注意文件打开操作必须放在init函数内部不能放在函数之前文件路径设置为/var/process.log使用O_CREAT|O_TRUNC标志确保每次启动都会创建新文件重要提示不要在task[0]中执行文件操作这可能导致系统崩溃。task[0]是系统的第一个进程只负责创建其他进程。2.2 修改fork.c追踪进程创建在fork.c文件中我们需要在进程创建的关键点插入日志打印int copy_process(int nr, long ebp, long edi, long esi, long gs, long none, long ebx, long ecx, long edx, long fs, long es, long ds, long eip, long cs, long eflags, long esp, long ss) { struct task_struct *p; // 分配内存给新进程 p (struct task_struct *) get_free_page(); // 设置进程状态为不可中断 p-state TASK_UNINTERRUPTIBLE; fprintk(3, %ld\tN\t%ld\n, p-pid, jiffies); // ... 其他初始化代码 ... // 最后将进程状态设为就绪 p-state TASK_RUNNING; fprintk(3, %ld\tJ\t%ld\n, p-pid, jiffies); return last_pid; }2.3 修改sched.c追踪进程调度schedule()函数是内核调度的核心我们需要在这里添加多个日志点void schedule(void) { int i, next, c; struct task_struct **p; // 检查信号和闹钟 for(p LAST_TASK; p FIRST_TASK; --p) { if (*p) { if ((*p)-alarm (*p)-alarm jiffies) { (*p)-signal | (1(SIGALRM-1)); (*p)-alarm 0; } if (((*p)-signal ~(_BLOCKABLE (*p)-blocked)) (*p)-state TASK_INTERRUPTIBLE) { (*p)-state TASK_RUNNING; fprintk(3, %d\tJ\t%d\n, (*p)-pid, jiffies); } } } // 调度器主循环 while (1) { c -1; next 0; i NR_TASKS; p task[NR_TASKS]; while (--i) { if (!*--p) continue; if ((*p)-state TASK_RUNNING (*p)-counter c) { c (*p)-counter; next i; } } if (c) break; for(p LAST_TASK; p FIRST_TASK; --p) { if (*p) { (*p)-counter ((*p)-counter 1) (*p)-priority; } } } // 进程切换 if (current-pid ! task[next]-pid) { if (current-state TASK_RUNNING) { fprintk(3, %d\tJ\t%d\n, current-pid, jiffies); } fprintk(3, %d\tR\t%d\n, task[next]-pid, jiffies); } switch_to(next); }3. 进程状态转换的完整追踪3.1 睡眠和唤醒操作除了基本的创建和调度我们还需要追踪进程的睡眠和唤醒操作// 在sys_pause()函数中 int sys_pause(void) { if (current-state ! TASK_INTERRUPTIBLE) { fprintk(3, %d\tW\t%d\n, current-pid, jiffies); } current-state TASK_INTERRUPTIBLE; schedule(); return 0; } // 在sleep_on()函数中 void sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current (init_task.task)) { panic(task[0] trying to sleep); } tmp *p; *p current; current-state TASK_UNINTERRUPTIBLE; fprintk(3, %d\tW\t%d\n, current-pid, jiffies); schedule(); if (tmp) { tmp-state 0; fprintk(3, %d\tJ\t%d\n, tmp-pid, jiffies); } }3.2 进程退出处理在exit.c中我们需要追踪进程的退出int do_exit(long code) { // 释放资源代码... current-state TASK_ZOMBIE; fprintk(3, %ld\tE\t%ld\n, current-pid, jiffies); schedule(); return (-1); }4. 日志分析与可视化收集到日志数据后我们需要一个工具来分析这些数据。下面是一个Python脚本示例可以解析日志文件并生成可视化图表#!/usr/bin/env python # stat_log.py import sys import matplotlib.pyplot as plt def parse_log_file(filename): processes {} with open(filename, r) as f: for line in f: pid, state, time line.strip().split(\t) pid int(pid) time int(time) if pid not in processes: processes[pid] [] processes[pid].append((state, time)) return processes def plot_process_states(processes, pidsNone): if pids is None: pids processes.keys() fig, ax plt.subplots(figsize(12, 6)) for pid in pids: if pid not in processes: continue states processes[pid] prev_time states[0][1] prev_state states[0][0] for state, time in states[1:]: # 绘制水平线段表示状态持续时间 ax.hlines(ypid, xminprev_time, xmaxtime, colorget_state_color(prev_state), linewidth2) prev_time time prev_state state ax.set_yticks(list(pids)) ax.set_yticklabels([fProcess {pid} for pid in pids]) ax.set_xlabel(Time (jiffies)) ax.set_title(Process State Transition Diagram) # 添加图例 from matplotlib.lines import Line2D legend_elements [ Line2D([0], [0], colorget_state_color(N), lw2, labelNew), Line2D([0], [0], colorget_state_color(J), lw2, labelReady), Line2D([0], [0], colorget_state_color(R), lw2, labelRunning), Line2D([0], [0], colorget_state_color(W), lw2, labelWaiting), Line2D([0], [0], colorget_state_color(E), lw2, labelExit) ] ax.legend(handleslegend_elements, locupper right) plt.tight_layout() plt.show() def get_state_color(state): colors { N: blue, J: green, R: red, W: orange, E: black } return colors.get(state, gray) if __name__ __main__: if len(sys.argv) 2: print(Usage: python stat_log.py logfile [pid1 pid2 ...] [-g]) sys.exit(1) filename sys.argv[1] pids [] show_graph False for arg in sys.argv[2:]: if arg -g: show_graph True else: try: pids.append(int(arg)) except ValueError: pass processes parse_log_file(filename) if not pids: pids sorted(processes.keys()) if show_graph: plot_process_states(processes, pids) else: # 文本统计输出 for pid in pids: if pid in processes: print(f\nProcess {pid} state transitions:) for state, time in processes[pid]: print(f{time}\t{state})这个脚本提供了两种输出方式文本统计显示每个进程的状态转换时间点图形化展示用不同颜色的线段表示不同状态持续时间要运行脚本分析日志可以使用以下命令python stat_log.py process.log 1 2 3 -g # 分析进程1、2、3并显示图形 python stat_log.py process.log -g # 分析所有进程并显示图形5. 时间片调整实验Linux 0.11使用简单的时间片轮转调度算法我们可以通过修改时间片大小来观察其对系统性能的影响。5.1 修改时间片参数时间片大小由priority值决定在linux-0.11/include/linux/sched.h中定义#define INIT_TASK \ { 0,15,15, /* state, counter, priority */ \将第二个和第三个值counter和priority从15改为其他值如10或5即可调整时间片大小。5.2 实验结果分析下表展示了不同时间片设置下的调度情况对比时间片大小上下文切换次数平均等待时间吞吐量5高较短中等10中等中等最高15低较长中等从实验结果可以看出小时间片5导致频繁的上下文切换虽然响应速度快但吞吐量不是最优中等时间片10在上下文切换开销和CPU利用率之间取得平衡吞吐量最高大时间片15减少了上下文切换但可能导致交互式进程响应变慢在实际系统调优中需要根据工作负载特性选择合适的时间片大小。对于混合型负载既有CPU密集型又有IO密集型进程中等大小的时间片通常能提供最佳的整体性能。

更多文章