2010-02-09 20:33:00 dog250 阅读数 3574

今天有同事问我在应用程序中怎么打印信息,在内核中有printk,用户空间用什么?我感到这个问题很奇怪,用printf不就可以了吗?他说他的代码在一个so中,我都无语了!他原来是做windows的,在windows中很多都是win32程序,带界面的,不是控制台程序,他大多数通过单步跟踪和断点来了解程序运行时的信息,其实吧,我倒是挺不喜欢单步或者断点的,除了特殊的调试需要,一般我都是通过打印日志来获取信息,linux有强大的日志记录系统,可以给程序一个安心的多的执行环境,日志仅仅是一个旁路的作用,起到记录个审计的作用而不会影响程序的执行,单步和断点就不同了,很多的问题你仅仅通过单步是无法发现的,特别是并发问题,不过在windows上写代码的哥们总是喜欢单步,就像他们总喜欢反汇编一样,这里面的原因就是,第一,windows程序大多数不是控制台,它实际上没有地方打印信息,第二,windows拥有强大的可视化IDE,这就给单步和断点提供了方便。

在linux中,syslogd是用户空间的一个守护进程,所有的需要记录日志的别的进程可以和这个守护进程通信,可以委托这个守护进程帮助记录日志,为何不自己记录呢,那是因为syslogd非常的专业,可以将日志记录到很细的地步,可以看etc/下面的配置文件,既然所有的进程都可以委托syslogd记录日志,那么就涉及到了进程间通信,这就是linux中另外一大块的内容了,这里就不谈了,linux善于使用小的程序组合成大的机制,linux善于分布式的思想而不是集中的,但是为何将日志都委托给一个守护进程呢,这不就集中了吗?其实这难道不是各司其职吗?因此这还是一个边界的问题。syslogd之外还有一个klogd,这两个其实不是一个层次的,syslogd是个总的日志记录器,而klogd仅仅是一个syslogd的客户端罢了,和很多需要记录日志的进程的级别一样,在需要记录日志的进程里,可以调用syslog函数将日志发送给syslogd守护进程,可是内核并不是一个进程,那么它的日志就有必要专门设置一个用户进程负责传给syslogd,这个进程就是klogd,至于klogd如何从内核得到内核日志信息,这就是它自己的事情了,其实内核专门留下了两个日志接口,一个是sys_syslog系统调用(注意不要和syslog函数混淆,syslog系统调用是和内核通信的,而syslog函数是和syslogd通信的),另一个是/proc/kmsg文件,klogd使用的是后者,当然也可以直接使用ksyslog系统调用。这里首先看一下内核日志的一些结构,首先内核日志大多数由printk产生,你也可以认为完全由printk产生,但是我们知道,printk将信息打印到了屏幕,并没有别的什么副作用,实际上并不是仅仅发生了我们看到的事情,实际上printk并不是将信息直接写入了标准输出文件,而仅仅将信息塞入了一个环形缓冲区,,然后至于这个缓冲区的内容什么时候显示到标准输出,那就是另外一个事情了,而内核日志就是存在于这个环形缓冲区,打印到屏幕的仅仅是一个缓冲区副本,真正的数据还在缓冲区保留,只有到了klogd或者别的什么调用syslog系统调用将信息读出时或者缓冲区满了(环形缓冲区首尾相接)的时候才会清除部分数据或者全部数据,刚才说了有两种方式来得到内核日志,一个是/proc/kmsg。一个是系统调用,幸运的是,这两个都是调用了do_syslog内核函数:

int do_syslog(int type, char __user * buf, int len)

{

unsigned long i, j, limit, count;

int do_clear = 0;

char c;

int error = 0;

error = security_syslog(type);

if (error)

return error;

switch (type) {

...

case 2:

...

error = wait_event_interruptible(log_wait, (log_start - log_end)); //如果没有数据,那么就睡眠等待。

if (error)

goto out;

i = 0;

spin_lock_irq(&logbuf_lock); //如果要往外读数据,不希望在读的过程中数据发生变化,因此必须锁住环形数据缓冲区

while (!error && (log_start != log_end) && i < len) { //这里才是内核环形缓冲区真正消耗的地方。

c = LOG_BUF(log_start); //得到一个字符

log_start++; //只有在这里才逻辑上清除环形缓冲区的数据

spin_unlock_irq(&logbuf_lock);

error = __put_user(c,buf);

buf++;

i++;

spin_lock_irq(&logbuf_lock);

}

spin_unlock_irq(&logbuf_lock);

...

}

我们看一下printk调用的vprintk内核函数:

asmlinkage int vprintk(const char *fmt, va_list args)

{

unsigned long flags;

int printed_len;

char *p;

static char printk_buf[1024];

static int log_level_unknown = 1;

...

spin_lock_irqsave(&logbuf_lock, flags); //这个logbuf_lock自旋锁其实很重要,确保一次内核信息写的过程不会有别的写过程来捣乱,否则很多信息会揉在一起,比如hello和 world会变为hwellorold。

printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);

for (p = printk_buf; *p; p++) {

if (log_level_unknown) {

if (p[0] != '<' || p[1] < '' || p[1] > '7' || p[2] != '>') {

emit_log_char('<');

emit_log_char(default_message_loglevel + '');

emit_log_char('>');

}

log_level_unknown = 0;

}

emit_log_char(*p);

if (*p == '/n')

log_level_unknown = 1;

}

if (!cpu_online(smp_processor_id()) && system_state != SYSTEM_RUNNING) {

spin_unlock_irqrestore(&logbuf_lock, flags);

goto out;

}

if (!down_trylock(&console_sem)) { //如果现在可以得到设备的信号量,那么马上将数据显示到终端

console_locked = 1;

spin_unlock_irqrestore(&logbuf_lock, flags);

console_may_schedule = 0;

release_console_sem(); //该函数具体显示数据

} else {

spin_unlock_irqrestore(&logbuf_lock, flags);//本次的内核信息已经写入了内核环形缓冲,为了不阻碍别人写,释放掉自旋锁

}

out:

return printed_len;

}

内核的环形缓冲有两个用途,一个是直接打印在屏幕上,还有一个就是可能用户空间的内核日志程序正在运行,因此这个函数完成了两个作用:

void release_console_sem(void)

{

unsigned long flags;

unsigned long _con_start, _log_end;

unsigned long wake_klogd = 0;

for ( ; ; ) { //打印向屏幕,注意,打印的时候也要确保logbuf_lock被拥有,因为不想在打印过程中发生缓冲区变化的事情

spin_lock_irqsave(&logbuf_lock, flags);

wake_klogd |= log_start - log_end; //确保有数据,这个变量还作为是否唤醒do_syslog的睡眠队列的依据。

if (con_start == log_end)

break; /* Nothing to print */

_con_start = con_start;

_log_end = log_end;

con_start = log_end; //注意,log_end和log_start并没有变化,而是副本变化了,这是因为下面还要用到内核缓冲,不能因为写到了屏幕,信息就消耗了。

spin_unlock_irqrestore(&logbuf_lock, flags);

call_console_drivers(_con_start, _log_end);

}

console_locked = 0;

console_may_schedule = 0;

up(&console_sem);

spin_unlock_irqrestore(&logbuf_lock, flags);

if (wake_klogd && !oops_in_progress && waitqueue_active(&log_wait)) //如果内核唤醒缓冲中有数据,那么就唤醒睡眠队列

wake_up_interruptible(&log_wait);

}

最后我们看一下procfs导出的kmsg接口的read函数,其实也是调用了do_syslog函数:

static ssize_t kmsg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)

{

if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))

return -EAGAIN;

return do_syslog(2, buf, count);

}

这里我觉得有一个问题,就是在printk的时候,如果我们恰好可以得到设备的信号量,那么就必须等到信息完全显示到设备上时调用栈才可以返回,也就是说,最好不要在内核大量调用printk函数,另外,在多处理器上这也是很不利的,以前的处理器少,因此问题不大,现在都是多处理器了,性能就可能受printk的影响了,我觉得可以为每一个cpu准备一个环形缓冲区,然后在拼接这些缓冲区的时候进行控制,比如在用户空间读每个cpu的缓冲区,只不过将它们作为一个整体去理解,这个我感觉可以用文件系统实现。在任何程序中频繁调用打印都是不妙的,因为打印涉及设备操作,想想看,外设相比cpu多么慢,而且对于机器中的唯一设备,而且是每个进程几乎都在用的设备,我们花在打架上的时间又是何等的长。

回归同事问我的那个问题,在linux上怎么打印信息,其实,只要你不关闭,每个程序都会默认打开0,1,2三个文件描述符,这三个描述符在start_kernel中调用的kernel_init进程(最终成为/sbin/init)中被初始化,也就是在下面的代码:

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

printk(KERN_WARNING "Warning: unable to open an initial console./n");

(void) sys_dup(0);

(void) sys_dup(0);

在这里,sys_open打开了/dev/console,将其作为标准输出,其实也就是我们的终端,当前终端,在sys_open中会自动将文件描述符0分配给这个终端文件,然后紧接着的sys_dup相当于复制了这个终端文件描述符代表的文件给了新的描述符1和2,这样的话实际上0,1,2代表的是同一个东西,都是指当前的终端,这也就是为什么你往标准错误里面写东西也不会错的道理,实际上标准输入,输出,错误都是给用户进程用的,对于内核,它们是一个东西。因为linux进程都是fork出来的,根源就是这个kernel_init进程,于是每个进程都会默认复制父进程的0,1,2文件描述符(如果父进程没有关闭它们的话)。

2016-11-01 10:27:32 zhou5712 阅读数 347

最近学习linux内核的日志打印,找到几篇好文章,先保存下来。

(1)内核日志:API 及实现(从内核到用户空间的日志):http://www.ibm.com/developerworks/cn/linux/l-kernel-logging-apis/index.html


(2)内核日志及printk结构浅析 :http://blog.chinaunix.net/uid-26993600-id-3252420.html


2014-06-28 14:32:45 piaomiaoju 阅读数 3230

Linux内核日志开关

1、让pr_debug能输出

--- a/kernel/printk/printk.c

+++ b/kernel/printk/printk.c

@@ -59,7 +59,7 @@

/* We show everything that is MOREimportant than this.. */

#define MINIMUM_CONSOLE_LOGLEVEL 1 /*Minimum loglevel we let people use */

-#define DEFAULT_CONSOLE_LOGLEVEL 7 /*anything MORE serious than KERN_DEBUG */

+#define DEFAULT_CONSOLE_LOGLEVEL 8 /*anything MORE serious than KERN_DEBUG */

int console_printk[4] = {

DEFAULT_CONSOLE_LOGLEVEL, /* console_loglevel */

2、让模块内代码都能输出

--- a/drivers/of/Makefile

+++ b/drivers/of/Makefile

EXTRA_CFLAGS += -DDEBUG

3、让每个文件输出

--- a/drivers/of/Makefile

+++ b/drivers/of/Makefile

文件include之前加define DEBUG

或者Makefile里面增加

pr_debug()

    Some files call pr_debug(), which is ordinarily an empty macro that discards
    its arguments at compile time.  To enable debugging output, build the
    appropriate file with -DDEBUG by adding

      CFLAGS_[filename].o := -DDEBUG

    to the makefile.

    For example, to see all attempts to spawn a usermode helper (such as
    /sbin/hotplug), add to lib/Makefile the line:

        CFLAGS_kobject_uevent.o := -DDEBUG

    Then boot the new kernel, do something that spawns a usermode helper, and
    use the "dmesg" command to view the pr_debug() output.

4、内核打印控制

dlxld,,lu,这几个都是输出32位的
hdhxhu,这几个都是输出16位数据的,
hhdhhxhhu,这几个都是输出8位的,
lldllllullx,这几个都是输出64位的,


2017-04-19 08:41:45 zhaixuebuluo 阅读数 61
嵌入式软件调试技术专题(3):Linux内核日志与信息打印—2008人已学习
课程介绍    
201704190800191189.jpg
    Linux内核、驱动开发中的printk打印技巧、日志系统、函数调用栈、动态调试、strace命令、内核转储、使用proc文件系统查看内核信息等查看Linux内核日志及打印信息的各种工具和方法。
课程收益
    掌握Linux内核日志系统,软件调试过程中,查看Linux内核信息的各种工具和方法
讲师介绍
    王利涛更多讲师课程
    6年嵌入式开发经验,在多家半导体公司从事芯片测试、验证、Linux驱动开发都工作。熟悉芯片设计流程、熟悉产品平台方案开发流程。
课程大纲
    1.内核实验环境介绍  17:29
    2.printk打印数据格式与打印等级  20:55
    3.printk实现机制分析(一):控制台、终端和串口之间的关系  25:07
    4.printk实现机制分析(二):内核实现与日志系统  30:36
    5.printk实现机制分析三):printk打印配置  17:12
    6.打印函数调用栈  15:35
    7.动态调试  31:53
    8.strace命令  29:26
    9.内核转储  11:56
    10.使用proc文件系统与内核交互  24:51
大家可以点击【查看详情】查看我的课程
2017-03-09 15:51:36 ytfdhb 阅读数 379

        只有对linux内核正确配置后,才能进行编译,配置不当的内核,很有可能会编译出错,或者不能正确运行。

        进入linux内核源码的项层目录,输入make menuconfi命令,可以进入内核配置界面,如图1所示

图1linux内核配置界面

        基于Ncurses的linux内核配置界面不支持鼠标操作,只能用键盘操作,基本的操作方法是:

  >>  能过键盘的方向键移动光标,选中子菜单或者菜单项高亮

 >>  按TAB键实现光标在菜单区和功能区切换

 >> 子菜单或者选项高亮,将光标移功能区选中<Select>回车

      *  如果是子菜单,按回车进入子菜单

     *  如果是菜单选项,按空格可以改变选项的值:

        --> 对于bool型选项,[*]表示选中,[ ]表示未选中

       --> 对于tristate型选项,<*>表示静态编译,<M>表示编译为模块,<>表示未选中

    * 对于int、hex和string类型的选项,按回车进入编辑菜单

 >> 连按两次ESC或者选中<EXIT>回车,将退回到上一级菜单

 >> 按斜线(/)可启动搜索功能,填入关键字后可搜索全部菜单内容

          配置完毕,将光标移动到配置界面末尾,选中“Save an Altemate ConfigurationFile”后回车,保存当前内核配置,默认配置文件名为.config。

图2 保存配置文件

图3 保存内核配置为.config文件

         保存完毕,选择ESC退出内核配置,回到终端命令行。

         如果防止配置错误,可以把配置文件命名为其它文件名,例如.config-bk等,但该配置文件不会被Makefile文件使用,Makefile默认使用文件名为.config的配置文件,所以重新命名配置文件通常在保留或才备份内核配置信息时使用。

         如果没有选中“Save an Altemate Configuration File”,在连续按两次ESC或选择Exit退出内核界面,将会出现提示保存的窗口。选择<Yes>后回车,内核配置将全保存为.config文件。

图4 保存内核配置提示信息


最后,再吼一下俺的口号:

每天进步一点点,开心多一点^_^

--2017年3月9日 15:59:50


   

[LINUX]linux系统日志

阅读数 3795

没有更多推荐了,返回首页