运行两个不同的LKM时,系统会挂起,以便每秒获取电池统计信息

时间:2015-04-23 17:17:46

标签: c linux-kernel batterymanager

我正在开发一个Linux内核模块(LKM),用于分析笔记本电脑的电池参数,并在每个给定的时间间隔内写入内核日志文件(/var/log/kern.log)。使用日志文件中的数据,我将绘制一个实时图表。

你可能会想到我为什么要这样做。这是我学术界的一部分就是答案。另外,我发现这个非常有趣。

我对这项任务的整个分析过程很简单,直到最近。我遵循的步骤是:

  1. 可以在最佳答案here中提取参数,并找到更多参数here。使用这两个链接,我的任务开始很简单。
  2. here获取了定期执行LKM功能的代码。这被修改为每秒运行一次。
  3. 有了这两个LKM,我误解了我的任务是小菜一碟,因为剩下的唯一任务是将这两个LKM组合在一起并将所需的参数数据写入日志文件。

    我使用了两个LKM的代码并制作了一个LKM,并将其插入到我的Ubuntu 14.04 64位操作系统内核中。系统立即冻结让我感到震惊。在这一点上我一无所知。

    在此之后,我减少了电池分析仪LKM和定时器LKM的代码,这些代码最不需要为我想要的输出运行。我发现减少的代码运行得很舒服。

    后来我迁移了代码,将电池参数分析到一个函数并导出它(使用EXPORT_SYMBOL(<function_name>),从定时器方法中调用。我当然可以肯定,因为两个LKM都是很酷。

    我想通过以下LKM代码和视频向您展示我机器中发生的事情的真实情况。

    bat_stat_analyzer.c - LKM获取电池参数并写入内核日志文件。

    #include <linux/module.h>
    #include <linux/version.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/kdev_t.h>
    #include <linux/fs.h>
    #include <linux/power_supply.h>
    
    static int result = 0;
    static struct power_supply *psy;
    static void bat_stat(void);
    
    EXPORT_SYMBOL(bat_stat);
    
    static void bat_stat(void) {
      union power_supply_propval value;
      int charge_status, voltage_now, current_now, capacity;
    
      psy = power_supply_get_by_name("BAT1");
      result = psy->get_property(psy,POWER_SUPPLY_PROP_STATUS, &value);
      charge_status = (!result) ? value.intval : -1;
      result = psy->get_property(psy,POWER_SUPPLY_PROP_VOLTAGE_NOW, &value);
      voltage_now = (!result) ? value.intval : -1;
      result = psy->get_property(psy,POWER_SUPPLY_PROP_CURRENT_NOW, &value);
      current_now = (!result) ? value.intval : -1;
      result = psy->get_property(psy,POWER_SUPPLY_PROP_CAPACITY, &value);
      capacity = (!result) ? value.intval : -1;
    
      printk(KERN_INFO "%s:%d,%d,%d,%d\n",
        __func__, charge_status, voltage_now, current_now, capacity);
    }
    
    static int __init bat_stat_init(void) /* Constructor */
    {
      bat_stat();
      return 0;
    }
    
    static void __exit bat_stat_exit(void) /* Destructor */
    {
      printk(KERN_INFO "Good bye\n");
    }
    
    module_init(bat_stat_init);
    module_exit(bat_stat_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Sriram Kumar <sriramhearing_at_gmail_dot_com>");
    MODULE_DESCRIPTION("First Battery Analyzer");
    

    bat_stat_repeater.c - LKM重复调用函数

    #include <linux/module.h>   /* Needed by all modules */
    #include <linux/kernel.h>   /* Needed for KERN_INFO */
    #include <linux/init.h>     /* Needed for the macros */
    #include <linux/jiffies.h>
    #include <linux/time.h>
    #include <linux/hrtimer.h>
    
    static unsigned long period_ms;
    static unsigned long period_ns;
    static ktime_t ktime_period_ns;
    static struct hrtimer my_hrtimer;
    extern int bat_stat(void);
    
    
    //~ static void bat_stat_repeat(unsigned long data)
    static enum hrtimer_restart bat_stat_repeat(struct hrtimer *timer)
    {
      unsigned long tjnow;
      ktime_t kt_now;
      bat_stat();
      printk(KERN_INFO "Repeating...\n");
    
      tjnow = jiffies;
      kt_now = hrtimer_cb_get_time(&my_hrtimer);
      hrtimer_forward(&my_hrtimer, kt_now, ktime_period_ns);
      return HRTIMER_RESTART;
    }
    
    static int __init bat_stat_init(void)
    {
      struct timespec tp_hr_res;
      period_ms = 1000;
      hrtimer_get_res(CLOCK_MONOTONIC, &tp_hr_res);
    
      hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
      my_hrtimer.function = &bat_stat_repeat;
      period_ns = period_ms*( (unsigned long)1E6L );
      ktime_period_ns = ktime_set(0,period_ns);
      hrtimer_start(&my_hrtimer, ktime_period_ns, HRTIMER_MODE_REL);
    
      return 0;
    }
    
    static void __exit bat_stat_exit(void)
    {
      int ret_cancel = 0;
      while( hrtimer_callback_running(&my_hrtimer) ) {
        ret_cancel++;
      }
      if (ret_cancel != 0) {
        printk(KERN_INFO " testjiffy Waited for hrtimer callback to finish (%d)\n", ret_cancel);
      }
      if (hrtimer_active(&my_hrtimer) != 0) {
        ret_cancel = hrtimer_cancel(&my_hrtimer);
        printk(KERN_INFO " testjiffy active hrtimer cancelled: %d\n", ret_cancel);
      }
      if (hrtimer_is_queued(&my_hrtimer) != 0) {
        ret_cancel = hrtimer_cancel(&my_hrtimer);
        printk(KERN_INFO " testjiffy queued hrtimer cancelled: %d\n", ret_cancel);
      }
      printk(KERN_INFO "Exit testjiffy\n");
    }
    
    module_init(bat_stat_init);
    module_exit(bat_stat_exit);
    
    MODULE_LICENSE("GPL");
    

    我使用的 Makefile 如下所示,因为它用于编译两个LKM。

    obj-m := <file_name>.o
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
    

    我使用这两个LKM获得的输出可以在此YouTube link上查看(仅需2分钟视频)。如果您希望将其视为图像,请在下面附上:

    Screenshot of battery analyzer output. System getting halted.

    我想知道:

    • 有没有更好和/或更简单的方法来实现我的目标?或
    • 我的LKM中的问题是什么让我的CPU窒息?

    提前致谢。

1 个答案:

答案 0 :(得分:2)

由于使用hrtimers,您遇到了问题。此机制设计用于高精度,在hardirq上下文中调用回调(禁用IRQ),因此您的回调函数必须是原子的。然而,您从回调中调用的函数不是原子的,可能会睡眠(因为互斥)。普通计时器有类似的问题,所以为了解决这个问题,你应该使用其他方法或重复任务,例如工作队列。

代码中的其他一些小问题:

  • 两个模块中的batt_stat函数声明不同
  • 你没有检查第一个模块中power_supply_get_by_name()的输出

另外,我真的没有理由将它拆分为两个内核模块,你应该只使用一个。