在Linux内核开发中,几乎所有的日志、信息的打印都是通过 printk() 函数实现的。 

printk 首先会将所有来自程序的信息都放到一个缓冲区中,然后各个“监控程序”再根据自己的需要将这些信息读取出来。如console会将日志信息直接显示在屏幕上等。Linux内核中的日志缓冲区本质上就是一个环形FIFO。 

既然有缓冲区,那就意味着它里面的数据有被覆盖的风险。而事实上也确实如此,当一个内核或其程序太过庞大而又不对打印信息加以控制的话,很容易就出现老旧的日志信息尚未被读取走就被新生日志给覆盖掉了。

为了避免这种情况的发生,我们一般都会为内核程序中的打印按重要性划分等级并添加打印控制开关。这确实是一个很有效的控制手段。 

但有时确实是会有出现了某些很严重的问题而不得不持续打印相关提示信息的情况。为了避免内核在短时间内产生大量重复信息而将其它有用信息覆盖掉的情况,我们可以在编写相应代码时人为加个延时打印的判断机制上去。这虽然很有效,但也难以掩盖它会给我们的编程带来额外的工作量的事实。 

所幸,Linux内核就有这么一套机制,我们仅需调用一个接口,并根据接口的返回值来决定是否持续打印我们的信息。这个接口的内部会自动帮我们判断我们即将要打印的信息是否过于频繁。以下直接贴出使用代码:

if(printk_ratelimit())
{
    printk(KERNEL_ERR "xxx driver broken!!!\n");
}

是的,就是这么简单。这个 printk_ratelimit() 函数是定义在 <linux/printk.h>上的。 

这个函数的机理是会判断我们的打印的频率的,即它会限制这条打印在指定时间间隔内最多只能出现指定次数条。 

这个限制条件是可以动态修改的,它们被定义在 /proc 目录下的两个文件中:

/proc/sys/kernel/printk_ratelimit
/proc/sys/kernel/printk_ratelimit_burst

printk_ratelimit 表示时间间隔,printk_ratelimit_burst 表示频次间隔。默认情况下 printk_ratelimit 的值是 5 ,printk_ratelimit_burst 的值是 10。即指定的打印在每5秒的时间里最多只能打印10次。我们可以随时更改这两个文件的值来控制打印频率。 

前面说了这么多,不如直接上一段实测代码来感受一下:

int i = 0;
    int ret;
    for(; i < 100; i++)
    {
        ret = printk_ratelimit();
        printk("ret:%d,i:%d\n", ret, i);
        if(ret)
        {
            printk("\t>>> ok <<<\n");
        }
        msleep(100);
    }

当 /proc/sys/kernel/printk_ratelimit 的值为 1 且 /proc/sys/kernel/printk_ratelimit_burst 的值为 2 时,以上代码的打印结果将是在 i 的值为 0,1、11,12、22,23、33,34、44,45、55,56、66,67、77,78、88,89、99 的时候会打印一条 ok 字样的日志。有兴趣的同学可以自行尝试一下。 

BTW,其实在 printk.h 中是有给出建议让我们不要用 printk_ratelimit() 来作为限制打印的,给出的原因是因为这个函数并不会判断打印的内容,如果有多条打印都调用了这个函数的话,那么它们是会共享系统中的频次条件的。以下是这个建议的原版:

android selinux打印 linux内核打印_头文件

如果不太理解的话,我们就实际写一个代码来体验一下就是了,还是上面的代码,只不过再额外加多一次判断:

int i = 0;
    int ret;
    for(; i < 100; i++)
    {
        ret = printk_ratelimit();
        printk("ret:%d,i:%d\n", ret, i);
        if(ret)
        {
            printk("\t>>> ok <<<\n");
        }

        ret = printk_ratelimit();
        printk("ret2:%d\n", ret);
        if(ret)
        {
            printk("\t\t>>> ko <<<\n");
        }
        
        msleep(100);
    }

上面这段代码原本我们期望的结果是当 i 的值为 0,1 时各打印一次 ok 及 ko 字样的日志。但真实情况却是 i 只在 0 的时候打印了一次 ok 及 ko 字样,而在 i 为 1 时并没有任何打印!这也充分印证了官方说明文档中的“共享频次”的意思。如果要让系统按内容来限制频次,则可以使用 printk_ratelimited() 宏定义来实现。这个宏定义同样位于 printk.h 头文件中,它的原型如下图所示:

android selinux打印 linux内核打印_i++_02

  

首先它是一个宏定义,而 printk_ratelimit() 却是一个函数。 

其次,这个宏定义就是用来替换 printk() 进行打印的。 

最后,不用想也知道 printk_ratelimited() 宏定义因为要根据内容来区分频次,它肯定比 printk_ratelimit() 函数需要消耗更多的系统资源。 

话不多说,直接改写我们上面的代码:

#include <linux/ratelimit.h>

    int i = 0;
    int ret;

    for(i = 0; i < 100; i++)
    {
        printk("i:%d\n", i);

        printk_ratelimited("\t>>> ok <<<\n");
        printk_ratelimited("\t\t>>> ko <<<\n");
        
        msleep(100);
    }

不再需要判断,直接用 printk_ratelimited 替换掉 printk 即可。 

另外,使用这个宏定义需要我们额外引入 ratelimit.h 头文件。 

最后,printk_ratelimited 宏定义关于时间与频率的限制是定义在 ratelimit.h 头文件中的,以宏的形式来定义,如下图所示:

android selinux打印 linux内核打印_i++_03

 

默认情况下也是每 5 秒指定内容的打印信息最多只能出现 10 次。