Linux进程调度响应时间测试方法

最好准备两个不同的内核版本,我们对目标版本操作时,可以先重启进入另一个版本,以免出现问题。

1 修改内核代码

测试原理是计算进程就绪后加入到就绪队列(enqueue_task()函数),到该进程开始运行的时间差。

  • 打开include/linux/sched.h,在task_struct结构体定义中,添加以下代码:
u64    enqueue_time;

enqueue_time记录进入就绪队列的时刻。
注意,不能添加到定义的最前和最后面,我加在了stime定义的后面。

  • 打开kernel/sched/core.c,添加以下两行头文件:
#include <linux/time.h>
#include <linux/timekeeping.h>
  • 同一文件内再找到enqueue_task()函数定义,插入以下代码(插入位置我觉得都可以,我放在了倒数第二有效行):
p->enqueue_time = (u64)ktime_get();
  • 重新编译安装:
sudo make -j8
sudo make modules_install
sudo make install
  • 重启进入目标内核版本。

2 动态添加内核模块

  • 在内核代码目录/home/linux/linux-5.6.19/drivers/中创建linux_test文件夹。
  • linux_test文件夹中创建test.c文件:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/kthread.h>

#include <linux/pid.h>
#include <linux/time.h>
#include <linux/timekeeping.h>

MODULE_LICENSE("GPL");

#define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
static struct task_struct *task = NULL;

int my_kernel_thread(void *arg)
{
    struct sched_param param;
    u64 etime,use_time;
    u64 count=0,sum=0,max_time=0;
    //struct pid * kpid=find_get_pid(current->pid);//获取当前进程的描述符信息
    //struct task_struct * task=pid_task(kpid, PIDTYPE_PID); //获取进程的任务描述符信息

    param.sched_priority = 99;                             //设置字段sched_priority的值
    sched_setscheduler(task, SCHED_RR, &param);            //调用函数改变进程的调度策略

    while(1){
       if(kthread_should_stop())break;

       etime = task->enqueue_time;
       if(etime && count<1000){
            use_time = (u64)ktime_get() - etime;
            //printk("use_time is:%llu\n", now_time - etime); 
            task->enqueue_time = 0;
            ++count;
            sum += use_time;
            if(max_time < use_time){
                max_time = use_time;
            }
       }
       ssleep(0.01);
    }
    printk("count is:%llu\n", count); 
    printk("avg_use_time is:%llu\n", sum/count); 
    printk("max_use_time is:%llu\n", max_time); 
    return 0;
}

static int __init test_init(void)
{ 
    printk("into test_init.\n");

    task = kthread_run(my_kernel_thread,NULL,"test task"); //创建新进程

    //struct pid * kpid=find_get_pid(result/*current->pid*/);  //获取当前进程的描述符信息
    //struct task_struct * task=pid_task(kpid, PIDTYPE_PID);   //获取进程的任务描述符信息
    //printk("the state of the task is:%d\n", task->state);    //显示任务当前所处的状态
    //printk("the pid of the task is:%d\n", task->pid);        //显示任务的进程号
    //printk("the tgid of the task is:%d\n", task->tgid);      //显示任务的线程组号

    //printk("the pid of current thread is:%d\n", current->pid); //显示当前进程的进程号
    
    printk("out test_init.\n");
    return 0;
}

static void __exit test_exit(void)
{
    if(task){
        kthread_stop(task);
        task = NULL;
    }
    printk("test exit!\n");
}

module_init(test_init);

module_exit(test_exit);
  • 新创建的线程中,每次循环开始都会先判定task->enqueue_time的值:若为0,则表示距离上一次循环,线程一直在运行没有挂起,不计入响应时间;若不为0,则表示距离上一次循环,线程有挂起过,可以计入响应时间,且要将enqueue_time置0。
  • 循环体每执行一次都会挂起0.01秒,有效循环共1000次,也就是计算1000次响应时间的平均值和最大值。
  • 同目录创建Makefile
obj-m := test.o
  • 同目录执行以下命令编译:
make -C /home/linux/linux-5.6.19 M=/home/linux/linux-5.6.19/drivers/linux_test/  modules
  • 同目录执行以下命令安装插入模块:
sudo insmod test.ko
  • 等待大约10秒,同目录执行以下命令删除模块:
sudo rmmod test.ko
  • 同目录执行以下命令查看输出:
sudo dmesg
(sudo dmesg | tail -10)
  • 结果如下,时间单位纳秒ns:
$ sudo dmesg | tail -10
[ 2877.339786] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
[ 2877.340443] IPv6: ADDRCONF(NETDEV_CHANGE): ens33: link becomes ready
[ 4072.016473] test: loading out-of-tree module taints kernel.
[ 4072.016566] test: module verification failed: signature and/or required key missing - tainting kernel
[ 4072.017088] into test_init.
[ 4072.019076] out test_init.
[ 4091.297209] count is:1000
[ 4091.297210] avg_use_time is:15679
[ 4091.297210] max_use_time is:275061
[ 4091.297224] test exit!

注意,若修改模块,再次插入需要先删除原模块。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐