内核打印函数介绍 printk、、pr_xxx()API、dev_xxx()

内核打印函数介绍

2016年08月20日 20:01:20 那颗流星的秘密 阅读数:7000 标签: 驱动 更多

个人分类: 驱动开发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mike8825/article/details/52262774

       在驱动中,有很多打印信息,有些是默认不能打开的,如dev_dbg或dev_vdbg等,为了方便调试,需要将这些打印信息全部打印出来,可通过如下步骤来打开开关。

我们先来看printk的打印控制

  1. asmlinkage int printk(const char *fmt, ...)

  2. {

  3. r = vprintk_emit(0, -1, NULL, 0, fmt, args);

  4. }

  5.  
  6. asmlinkage int vprintk_emit(int facility, int level,const char *dict, size_t dictlen,const char *fmt, va_list args)

  7. {

  8.  
  9. console_unlock();

  10. }

  11.  
  12. void console_unlock(void)

  13. {

  14.  
  15. call_console_drivers(level, text, len);

  16. }

  17.  
  18. static void call_console_drivers(int level, const char *text, size_t len)

  19. {

  20. struct console *con;

  21.  
  22. trace_console(text, len);

  23.  
  24. if (level >= console_loglevel && !ignore_loglevel)

  25. return;

  26. if (!console_drivers)

  27. return;

  28.  
  29. for_each_console(con) {

  30. if (exclusive_console && con != exclusive_console)

  31. continue;

  32. if (!(con->flags & CON_ENABLED))

  33. continue;

  34. if (!con->write)

  35. continue;

  36. if (!cpu_online(smp_processor_id()) &&

  37. !(con->flags & CON_ANYTIME))

  38. continue;

  39. con->write(con, text, len);

  40. }

  41. }

只有高于控制台的的信息才能打印出来,这里可以修改控制台的打印级别(

 

echo 8 > /proc/sys/kernel/printk或者在rc脚本中write n1 n2 n3 n4到printk节点,如

device/qcom/common/rootdir/etc/init.qcom.rc   write /proc/sys/kernel/printk "6 6 1 7")

或者忽略这一限制(在cmdlinx中加入ignore_loglevel)

 

 
  1. kernel/kernel/sysctl.c

  2. static struct ctl_table kern_table[] = {

  3. #if defined CONFIG_PRINTK

  4. {

  5. .procname = "printk",

  6. .data = &console_loglevel,

  7. .maxlen = 4*sizeof(int),

  8. .mode = 0644,

  9. .proc_handler = proc_dointvec,

  10. },

  11. }

  12. #define console_loglevel (console_printk[0])

  13. int console_printk[4] = {

  14. DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */

  15. DEFAULT_MESSAGE_LOGLEVEL, /* default_message_loglevel */

  16. MINIMUM_CONSOLE_LOGLEVEL, /* minimum_console_loglevel */

  17. DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */

  18. };

 

 

 
  1. static int __init ignore_loglevel_setup(char *str)

  2. {

  3. ignore_loglevel = 1;

  4. printk(KERN_INFO "debug: ignoring loglevel setting.\n");

  5.  
  6. return 0;

  7. }

  8.  
  9. early_param("ignore_loglevel", ignore_loglevel_setup);

printk的参数控制

 

 

 
  1. #define KERN_EMERG "<0>"

  2. #define KERN_ALERT "<1>"

  3. #define KERN_CRIT "<2>"

  4. #define KERN_ERR "<3>"

  5. #define KERN_WARNING "<4>"

  6. #define KERN_NOTICE "<5>"

  7. #define KERN_INFO "<6>"

  8. #define KERN_DEBUG "<7>"

如printk (KERN_NOTICE "floppy disk write protected\n");
此外还有很多printk函数的变形,如pr_xxx,dev_xxx函数,

 

对应pr_xxx()API的好处是,可以在文件最开头通过pr_fmt定义一个打印格式,比如在kernel/watchdog.c的最开头通过如下定义保证以后watchdog.c调用的

所以pr_xxx()打印的信息都自动带有" NMI watchdog: "的前缀。(linux/include/printk.h)

#define pr_fmt(fmt)  " NMI watchdog: " fmt

 

#ifndef pr_fmt
#define pr_fmt(fmt) fmt
#endif

#define pr_emerg(fmt, ...) \
    printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...) \
    printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...) \
    printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...) \
    printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...) \
    printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...) \
    printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...) \
    printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
#define pr_cont(fmt, ...) \
    printk(KERN_CONT fmt, ##__VA_ARGS__)

/* pr_devel() should produce zero code unless DEBUG is defined */
#ifdef DEBUG
#define pr_devel(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_devel(fmt, ...) \
    no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

#include <linux/dynamic_debug.h>

/* If you are writing a driver, please use dev_dbg instead */
#if defined(CONFIG_DYNAMIC_DEBUG)
/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
    dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
    printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
    no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

 

使用dev_xxx()族API打印的时候,设备名称对自动加到打印信息的前头。 (drivers/base/core.c)

static int __dev_printk(const char *level, const struct device *dev,
			struct va_format *vaf)
{
	if (!dev)
		return printk("%s(NULL device *): %pV", level, vaf);

	return dev_printk_emit(level[1] - '0', dev,
			       "%s %s: %pV",
			       dev_driver_string(dev), dev_name(dev), vaf);
}

int dev_printk(const char *level, const struct device *dev,
	       const char *fmt, ...)
{
	struct va_format vaf;
	va_list args;
	int r;

	va_start(args, fmt);

	vaf.fmt = fmt;
	vaf.va = &args;

	r = __dev_printk(level, dev, &vaf);

	va_end(args);

	return r;
}
EXPORT_SYMBOL(dev_printk);

#define define_dev_printk_level(func, kern_level)		\
int func(const struct device *dev, const char *fmt, ...)	\
{								\
	struct va_format vaf;					\
	va_list args;						\
	int r;							\
								\
	va_start(args, fmt);					\
								\
	vaf.fmt = fmt;						\
	vaf.va = &args;						\
								\
	r = __dev_printk(kern_level, dev, &vaf);		\
								\
	va_end(args);						\
								\
	return r;						\
}								\
EXPORT_SYMBOL(func);

define_dev_printk_level(dev_emerg, KERN_EMERG);
define_dev_printk_level(dev_alert, KERN_ALERT);
define_dev_printk_level(dev_crit, KERN_CRIT);
define_dev_printk_level(dev_err, KERN_ERR);
define_dev_printk_level(dev_warn, KERN_WARNING);
define_dev_printk_level(dev_notice, KERN_NOTICE);
define_dev_printk_level(_dev_info, KERN_INFO);

 

 

此外,打印中还经常看到pr_debug函数,可以动态的打印调试信息(http://blog.csdn.net/weiqifa0/article/details/44038907

如要打开该函数,只需在内核中配置CONFIG_DYNAMIC_DEBUG,并且将debugfs挂载到某个文件夹mount -t debugfs none /sys/kernel/debug/,

就可以通过echo -n "file xxxxxx.c +p" > /sys/kernel/debug/dynamic_debug/control来打开调试信息啦。

 

#if defined(CONFIG_DYNAMIC_DEBUG)
/* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
#define pr_debug(fmt, ...) \
	dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
	printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
	no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

此外,dev_bdb一般是没打印出来的。

在linux/device.h文件中:

 

#ifdef DEBUG
#define dev_dbg(dev, format, arg...)        \
    dev_printk(KERN_DEBUG , dev , format , ## arg)
#else
static inline int __attribute__ ((format (printf, 2, 3)))
dev_dbg(struct device * dev, const char * fmt, ...)
{
    return 0;
}
#endif

那我们在包含该头文件之前,需要声明一下DEBUG,并修改控制台的打印级别,就可以打印出来了。

#define DEBUG
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/platform_device.h>

 

下面的程序printk_test.c用来测试上面的打印函数,如下

 

#define pr_fmt(fmt) KBUILD_MODNAME  "  pr_test::::::" fmt
#define DEBUG
#define VERBOSE_DEBUG
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/delay.h>
static struct miscdevice dev;

#define GTP_INFO(fmt,arg...)   printk("<<-GTP-INFO->> "fmt"\n",##arg)
#define DEVICE_NAME "printk"
static int printk_test(struct device *dev)
{
	printk(KERN_INFO KBUILD_MODNAME "  %d\n",__LINE__);
	dev_emerg(dev,"dev_emerg %d\n",__LINE__);
	dev_alert(dev,"dev_alert %d\n",__LINE__);
	dev_crit(dev,"dev_crit %d\n",__LINE__);
	dev_err(dev,"dev_err %d\n",__LINE__);
	dev_warn(dev,"dev_warn %d\n",__LINE__);
	dev_notice(dev,"dev_notice %d\n",__LINE__);
	dev_info(dev,"dev_info %d\n",__LINE__);
	_dev_info(dev,"_dev_info %d\n",__LINE__);

	pr_emerg("pr_emerg %d\n",__LINE__);
	pr_alert("pr_alert %d\n",__LINE__);
	pr_crit("pr_crit %d\n",__LINE__);
	pr_err("pr_err %d\n",__LINE__);
	pr_warning("pr_warning %d\n",__LINE__);
	pr_warn("pr_warn %d\n",__LINE__);
	pr_notice("pr_notice %d\n",__LINE__);
	pr_info("pr_info %d\n",__LINE__);
	pr_cont("pr_cont %d\n",__LINE__);
	GTP_INFO("%d\n",__LINE__);
	msleep(8000);
	pr_debug("pr_debug\n");

	dev_dbg(dev,"dev_dbg %d\n",__LINE__);
	dev_vdbg(dev,"dev_vdbg %d\n",__LINE__);
	return 0;
}


static struct miscdevice dev = {
	.minor          = MISC_DYNAMIC_MINOR,
	.name           = DEVICE_NAME,
};

static int __init printk_init(void)
{
	int ret;
	printk("%s\n",__func__);
	ret = misc_register(&dev);
	ret = printk_test(dev.this_device);
	return ret;
}

static void __exit printk_exit(void)
{
	misc_deregister(&dev);
	printk("%s\n",__func__);
}

module_init(printk_init);
module_exit(printk_exit);
MODULE_LICENSE("GPL");

 

编译并安装驱动,修改打印级别echo 8 >/proc/sys/kernel/printk,打印信息为


这里的pr_debug函数也打印出来了,因为把DEBUG的声明放在CONFIG_DYNAMIC_DEBUG声明之前,pr_debug走的是printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__),

如果没有声明DEBUG,可通过echo -n "file printk_test.c +p" > /sys/kernel/debug/dynamic_debug/control来打开调试信息。

<think>好的,用户想了解 Linux 内核中的 EXPORT_SYMBOL 函数的使用说明和示例。首先,我需要回忆一下相关知识,并结合提供的引用内容来组织回答。 首先,用户提到的引用[1]和[2]已经解释了EXPORT_SYMBOL的基本作用,即导出符号供其他模块使用。需要先介绍它的基本概念和用途,然后说明其工作原理,比如符号表管理和模块加载过程。引用[4]提到宏定义展开和链接脚本,这部分可能需要稍微深入一点,但用户可能更关注用法,所以可以简要提及。 接下来,用户需要使用说明,应该分步骤讲解如何在代码中使用EXPORT_SYMBOL,比如定义函数、导出符号、其他模块声明并使用。同时,要给出示例代码,像引用[1]中的示例,但可能需要更详细,比如两个模块的代码示例,一个导出,一个使用。 注意事项部分需要包括作用域、依赖关系、版本兼容性等,引用[2]和[4]可能有相关内容,比如符号表管理和版本问题。此外,要提到GPL协议的问题,因为内核模块如果使用EXPORT_SYMBOL_GPL的话需要注意许可证兼容性。 然后生成相关问题,要围绕EXPORT_SYMBOL的相关知识点,比如原理、与GPL版本的区别、调试方法、应用场景等。 需要注意的是,回答要符合用户的要求,结构清晰,分点说明,同时正确使用LaTeX格式,比如变量名用$...$包裹,代码块用三个反引号标注语言。另外,引用标识要正确添加到对应的引用内容后面,比如在提到导出符号时引用[1],在原理部分引用[2]和[4]。 最后,检查是否有遗漏的信息,比如是否覆盖了使用步骤、示例、注意事项,以及相关问题是否相关。确保语言口语化,避免使用专业术语过多,保持自然的中文表达。</think>### EXPORT_SYMBOL 函数介绍及用法 #### 1. 核心功能 `EXPORT_SYMBOL` 是 Linux 内核提供的宏,用于将模块中的**函数或变量**导出到内核符号表,使其他内核模块能跨模块调用这些符号[^1]。这是实现模块间动态交互的基础机制。 #### 2. 工作原理 - **符号表管理**:导出的符号会被记录在`__ksymtab`段中,形成全局可见的符号表[^4] - **模块依赖**:加载模块时,内核通过符号表解析跨模块调用关系[^2] - **地址绑定**:编译时生成未解析的符号引用,运行时通过符号表完成地址绑定[^4] #### 3. 使用步骤 1. **定义待导出函数** ```c // module_a.c void my_exported_func(void) { printk("Exported function called\n"); } EXPORT_SYMBOL(my_exported_func); // 关键导出语句 ``` 2. **外部模块调用** ```c // module_b.c extern void my_exported_func(void); // 声明外部符号 static int __init mod_init(void) { my_exported_func(); // 跨模块调用 return 0; } ``` #### 4. 完整示例 **导出模块 (export_demo.c):** ```c #include <linux/init.h> #include <linux/module.h> int shared_var = 100; EXPORT_SYMBOL(shared_var); // 导出变量 void demo_func(void) { printk(KERN_INFO "Demo function executed\n"); } EXPORT_SYMBOL(demo_func); // 导出函数 MODULE_LICENSE("GPL"); ``` **调用模块 (use_demo.c):** ```c #include <linux/init.h> #include <linux/module.h> extern int shared_var; // 导入变量 extern void demo_func();// 导入函数 static int __init mod_init(void) { printk("Shared value: %d\n", shared_var); demo_func(); return 0; } module_init(mod_init); MODULE_LICENSE("GPL"); ``` #### 5. 关键注意事项 1. **符号可见性**:导出的符号必须是非静态(non-static)的 2. **依赖顺序**:被调用模块需要先于调用模块加载 3. **版本控制**:使用`EXPORT_SYMBOL_GPL`限制仅GPL兼容模块可用[^4] 4. **命名冲突**:避免与内核已有符号重名 5. **内存安全**:确保导出符号的生命周期覆盖所有调用者 #### 6. 调试技巧 - 查看符号表:`cat /proc/kallsyms | grep <symbol>` - 模块依赖检查:`modinfo <module_name>` - 动态加载测试:`insmod`/`rmmod`顺序验证
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值