【问题标题】:System hangs when running two different LKMs to get battery statistics every second运行两个不同的 LKM 以每秒获取电池统计信息时系统挂起
【发布时间】:2015-04-26 18:22:40
【问题描述】:

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

你可能会想我为什么要做这些事情。这是我的一部分学者的答案。另外,我觉得这很有趣。

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

  1. 可以按照最佳答案here 中给出的方式获取参数,并找到更多参数here。使用这两个链接,我的任务很容易开始。
  2. here 获取定期执行LKM 功能的代码。已将其修改为每秒运行一次。

对于这两个 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 分钟的视频)。如果您希望将其作为图像查看,请在下面附上:

我想知道:

  • 是否有更好和/或更简单的方法来实现我正在尝试的目标?或
  • LKM 中的什么问题导致 CPU 阻塞?

提前致谢。

【问题讨论】:

  • 顺便说一句,您可以在 VM 中进行初始的高风险测试吗?如果您导致虚拟机“爆炸”,它几乎不会像对您的实际系统造成这种伤害那样有害。
  • 最初尝试使用 VM,但电池信息无法通过 VM 管理器(虚拟机)提供给 VM 操作系统。
  • 安装 VB 来宾附加组件后,电池信息甚至不可用?
  • 我得到了我的工作 LKM :)

标签: c linux-kernel batterymanager


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢 Krzysztof Adamski 的这些观点。我会尝试使用替代方案。 - bat_stat 函数几乎没有修改,我错过了在另一个文件中更改它。感谢那。 - 我已经分析了power_supply_get_by_name() 的输出以确保每次都是安全的。我会检查是否可以使用任何条件。由于我没有使用单个模块,因此我将它们拆分并开始用作不同的模块。
  • 我删除了计时器 LKM 并使用kthread 重复了电池统计分析器,并在msleep 的帮助下应用了无限循环以在执行中施加延迟。我还对(无限)循环应用了一个小逻辑,这样当模块从内核中删除时线程将终止。感谢 Krzysztof Adamski 的提示!
  • 我认为您实际上展示了另一种获得解决方案的方法。后来它击中了我……The best answer
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 1970-01-01
  • 2014-02-12
相关资源
最近更新 更多