printk浅析

Realtek 10GbE芯片组解析:低成本高速网络方案 10GbE(万兆以太网)技术通过提升网络带宽,显著改善了数据传输效率,成为现代网络架构的核心组件。其工作原理基于高速串行通信协议,通过优化信号处理和编码技术实现低延迟、高吞吐量。在技术价值上,10GbE不仅满足数据中心和企业网络的高需求,还逐步渗透到消费级市场,推动家庭和小型企业的网络升级。应用场景包括NAS存储、视频编辑工作站和云服务接入等。Realtek最新发布的RTL8127、RTL8159和RTL8261C芯片组,以低功耗和低成本优势,进一步降低了10GbE的普及门槛。这些芯片支持PCIe Gen4 阅读详情

printk浅析


printk的机制

日志等级
    #define KERN_EMERG "<0>" /* 系统不可使用 */
    #define KERN_ALERT "<1>" /* 需要立即采取行动 */
    #define KERN_CRIT "<2>" /* 严重情况 */
    #define KERN_ERR "<3>" /* 错误情况 */
    #define KERN_WARNING "<4>" /* 警告情况 */
    #define KERN_NOTICE "<5>" /* 正常情况, 但是值得注意 */
    #define KERN_INFO "<6>" /* 信息型消息 */
    #define KERN_DEBUG "<7>" /* 调试级别的信息 */
printk内核实现

printk 可以在内核的任意上下文中调用。这个调用从 ./linux/kernel/printk.c 中的 printk 函数开始,它会在使用 va_start 解析可变长度参数之后调用 vprintk(在同一个源文件)。

vprintk 函数执行了许多管理级检查(递归检查),然后获取日志缓冲区的锁(__log_buf)。接下来,它会对输入的字符串进行日志级别检查; 如果发现日志级别信息,那么对应的日志级别就会被设置。最后,vprintk 会获取当前时间(使用函数 cpu_clock)并使用 sprintf(不是标准库版本,而是在 ./linux/lib/vsprintf.c 中实现的内部内核版本)将它转换成一个字符串。这个字符串会被传递给 printk,然后它会被一个管理缓冲边界(emit_log_char)的特殊函数复制到内核日志缓冲区中。这个函数最后将获取和释放执行控制台信号,并将下一条日志消息发送到控制台(在 release_console_sem 中执行)。内核缓冲缓冲区的大小初始值为 4KB,但是最新的内核大小已经升级到 16KB(在不同的体系架构上,这个值最高可以达到 1MB)。

asmlinkage int printk(const char *fmt, ...)
{
    va_list args;
    int r;

/*
 *启动KDB调试
 */
#ifdef CONFIG_KGDB_KDB
    if (unlikely(kdb_trap_printk)) {
        va_start(args, fmt);
        r = vkdb_printf(fmt, args);
        va_end(args);
        return r;
    }
#endif
    va_start(args, fmt);
    r = vprintk(fmt, args);//printk函数的核心步骤
    va_end(args);

    return r;
}


asmlinkage int vprintk(const char *fmt, va_list args)
{
    int printed_len = 0;
    int current_log_level = default_message_loglevel;//printk函数的默认输出级别
    unsigned long flags;
    int this_cpu;
    char *p;
    size_t plen;
    char special;

    boot_delay_msec();
    printk_delay();

    /* 当我们需要console_sem的持有者的时候 我们会停止这一步 */
    local_irq_save(flags);
    this_cpu = smp_processor_id();//获得cpu号

    /* printk在错误使用下会引发panic */
    if (unlikely(printk_cpu == this_cpu)) {
        /*
         * 如果在这个CPU上printk调用失败了,然后试着得到失败的信息但
         * 需要保证我们不会引发死锁。否则直接返回来避免printk的递归
         * 但是要标记已经发生递归,并在下一个合适的时刻将信息打印出来
         */
        /* oops_in_progress只有在panic函数中才为1 */
        if (!oops_in_progress && !lockdep_recursing(current)) {
            recursion_bug = 1;
            goto out_restore_irqs;
        }
        /*如果在printk运行时,这个CPU崩溃,确信不能死锁,
         *10秒1次初始化锁logbuf_lock和console_sem,
         *留时间给控制台打印完全的oops信息
         */
        zap_locks();
    }

    lockdep_off();
    raw_spin_lock(&logbuf_lock);
    printk_cpu = this_cpu;

    /* 因为第一次printk调用失败 所以这次需要把第一次失败的信息打印出来 */
    if (recursion_bug) {
        recursion_bug = 0;
        strcpy(printk_buf, recursion_bug_msg);
        printed_len = strlen(recursion_bug_msg);
    }
    /*将要输出的字符串按照fmt中的格式编排好,
     *放入printk_buf中,并返回应该输出的字符个数
     */
    printed_len += vscnprintf(printk_buf + printed_len,
                  sizeof(printk_buf) - printed_len, fmt, args);

    p = printk_buf;

    /* 读取日志等级 并且处理printk的特殊前缀 */
    plen = log_prefix(p, &current_log_level, &special);
    if (plen) {
        p += plen;

        switch (special) {
        case 'c': /* Strip <c> KERN_CONT, continue line */
            plen = 0;
            break;
        case 'd': /* Strip <d> KERN_DEFAULT, start new line */
            plen = 0;
        default:
            if (!new_text_line) {
                emit_log_char('\n');
                new_text_line = 1;
            }
        }
    }

    /*
     *拷贝printk_buf数据到环形缓冲区中,如果调用者没有提供合适的日志级别,
     *则插入默认级别;拷贝的过程由函数emit_log_char实现,每次拷贝一个字节
     */
    for (; *p; p++) {
        if (new_text_line) {
            new_text_line = 0;

            if (plen) {
                /* 保存初始的前缀 */
                int i;

                for (i = 0; i < plen; i++)
                    emit_log_char(printk_buf[i]);
                printed_len += plen;
            } else {
                /* 如果不存在添加默认等级 */
                emit_log_char('<');
                emit_log_char(current_log_level + '0');
                emit_log_char('>');
                printed_len += 3;
            }
            /*如果设置了此选项,则在每一条printk信息前都要加上时间参数*/
            if (printk_time) {
                char tbuf[50], *tp;
                unsigned tlen;
                unsigned long long t;
                unsigned long nanosec_rem;

                t = cpu_clock(printk_cpu);
                nanosec_rem = do_div(t, 1000000000);
                tlen = sprintf(tbuf, "[%5lu.%06lu] ",
                        (unsigned long) t,
                        nanosec_rem / 1000);/* 对打印出来的时间进行格式化 */

                for (tp = tbuf; tp < tbuf + tlen; tp++)
                    emit_log_char(*tp);
                printed_len += tlen;
            }

            if (!*p)
                break;
        }

        emit_log_char(*p);
        if (*p == '\n')
            new_text_line = 1;
    }
    /*
    * 在acquire_console_semaphore_for_printk函数的注释中有这样一句话:
    * 此函数被调用时拥有logbuf_lock的自旋锁,并且处于禁止中断状态
    *  在返回时(无论成功get sem)应保证logbuf_lock的自旋锁被释放,但是仍然禁止中断
    */
    if (console_trylock_for_printk(this_cpu))
        /*此函数将log_buf中的内容发送给console,并且唤醒klogd*/
        console_unlock();

    lockdep_on();
out_restore_irqs:
    local_irq_restore(flags);

    return printed_len;
}
emit_log_char函数

环形缓冲区在字面看来就是一个数组 static char __log_buf[__LOG_BUF_LEN];其长度一般为4096大小(内核可修改)。而log_end长度为unsigned long范围,远远大于数组的大小,对于每一个字符的赋值log_end则只管++,在加一之后进行判断,如果log_end的值大于log_start,则表示缓冲区的长度已经达到最大,下一次的写入就将覆盖之前最旧的位置,因此log_start = log_end - log_buf_len,将log_start的位置向后移一位(因为每次只写入一个字符,不可能超过一位)。log_end和log_start通过unsigned long的自然溢出来实现环形的判断,而对其中每一次赋值则不再考虑环形的实现形式。

static void emit_log_char(char c)
{
    LOG_BUF(log_end) = c;
    log_end++;
    if (log_end - log_start > log_buf_len)
        log_start = log_end - log_buf_len;
    if (log_end - con_start > log_buf_len)
        con_start = log_end - log_buf_len;
    if (logged_chars < log_buf_len)
        logged_chars++;
}

控制台console

前面已经说明了printk的实现机制,而printk最终是要打印到控制台上,
所以我们来看一下在linux下控制台是如何实现打印信息的。

console的初始化

struct console_cmdline
{
    char    name[8];            /* 设备名称     */
    int index;              /* 可用的次设备号      */
    char    *options;           /* 设备的一些选项   */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    char    *brl_options;           /* Options for braille driver */
#endif
};

/*
 *通过传入的str来提取console_cmdline中的一些信息 并存放到buf中
 */

static int __init console_setup(char *str)
{
    char buf[sizeof(console_cmdline[0].name) + 4]; /*即得到options */
    char *s, *options, *brl_options = NULL;
    int idx;

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    if (!memcmp(str, "brl,", 4)) {
        brl_options = "";
        str += 4;
    } else if (!memcmp(str, "brl=", 4)) {
        brl_options = str + 4;
        str = strchr(brl_options, ',');
        if (!str) {
            printk(KERN_ERR "need port name after brl=\n");
            return 1;
        }
        *(str++) = 0;
    }
#endif

    /*
     * Decode str into name, index, options.
     */
    if (str[0] >= '0' && str[0] <= '9') {
        strcpy(buf, "ttyS");
        strncpy(buf + 4, str, sizeof(buf) - 5);
    } else {
        strncpy(buf, str, sizeof(buf) - 1);
    }
    buf[sizeof(buf) - 1] = 0;
    if ((options = strchr(str, ',')) != NULL)
        *(options++) = 0;
#ifdef __sparc__
    if (!strcmp(str, "ttya"))
        strcpy(buf, "ttyS0");
    if (!strcmp(str, "ttyb"))
        strcpy(buf, "ttyS1");
#endif
    for (s = buf; *s; s++)
        if ((*s >= '0' && *s <= '9') || *s == ',')
            break;
    idx = simple_strtoul(s, NULL, 10);
    *s = 0;

    __add_preferred_console(buf, idx, options, brl_options);
    console_set_on_cmdline = 1;
    return 1;
}
static int __add_preferred_console(char *name, int idx, char *options,
                   char *brl_options)
{
    struct console_cmdline *c;
    int i;

    /*
     *  查看这个tty是否已登记, and
     *  if we have a slot free.
     */
    for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
        if (strcmp(console_cmdline[i].name, name) == 0 &&
              console_cmdline[i].index == idx) {
                if (!brl_options)
                    selected_console = i;//确定所选的终端号 默认为-1
                return 0;
        }
    if (i == MAX_CMDLINECONSOLES)//所选终端号无对应终端 报错
        return -E2BIG;
    if (!brl_options)
        selected_console = i;
    c = &console_cmdline[i];//将所选的终端的对象进行初始化
    strlcpy(c->name, name, sizeof(c->name));
    c->options = options;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    c->brl_options = brl_options;
#endif
    c->index = idx;
    return 0;
}
printk对console的选择

printk的代码中包含了对console的选择
以下为调用关系
printk -> vprintk -> console_unlock -> call_console_drivers

void console_unlock(void)
{
    unsigned long flags;
    unsigned _con_start, _log_end;
    unsigned wake_klogd = 0, retry = 0;

    /* 如果当前终端已被挂起 释放锁 并退出 */
    if (console_suspended) {
        up(&console_sem);
        return;
    }

    console_may_schedule = 0;/* console需要调度 */

again:
    for ( ; ; ) {
        raw_spin_lock_irqsave(&logbuf_lock, flags);
        wake_klogd |= log_start - log_end;/* 如果之间有数据需要打印,则添加标记 */
        if (con_start == log_end)
            break;          /* 没有数据需要打印 */
        _con_start = con_start;
        _log_end = log_end;
        con_start = log_end;        /* “清空”缓存 */
        raw_spin_unlock(&logbuf_lock);
        stop_critical_timings();    /* don't trace print latency */
        call_console_drivers(_con_start, _log_end);/* 将数据长度传入终端设备 */
        start_critical_timings();
        local_irq_restore(flags);
    }
    console_locked = 0;

    /* 将数据输出到所有的终端设备 */
    if (unlikely(exclusive_console))
        exclusive_console = NULL;

    raw_spin_unlock(&logbuf_lock);

    up(&console_sem);

    /*
     * 有人可能又将缓存区填入了数据, 所以检查是否需要重新flush
     * 万一我们没有获得console_sem锁,锁会有新的持有者
     * 他们会调用console_unlock 并flush缓存区 不需要担心
     */
    raw_spin_lock(&logbuf_lock);
    if (con_start != log_end)/* 有人填充了数据 */
        retry = 1;
    raw_spin_unlock_irqrestore(&logbuf_lock, flags);

    if (retry && console_trylock())/* 又有新的数据需要打印 */
        goto again;

    if (wake_klogd)/* 如果有等待的klogd 那么唤醒他 */
        wake_up_klogd();
}
static void call_console_drivers(unsigned start, unsigned end)
{
    unsigned cur_index, start_print;
    static int msg_level = -1;/* 日志等级 */

    BUG_ON(((int)(start - end)) > 0);

    cur_index = start;
    start_print = start;
    while (cur_index != end) {
        if (msg_level < 0 && ((end - cur_index) > 2)) {
            /* 判断日志等级 */
            cur_index += log_prefix(&LOG_BUF(cur_index), &msg_level, NULL);
            start_print = cur_index;
        }
        while (cur_index != end) {
            /* 这一段操作之将日志相关信息解析 */
            char c = LOG_BUF(cur_index);/* 将缓存中的字符一个一个赋给c */

            cur_index++;
            if (c == '\n') {
                if (msg_level < 0) {
                    msg_level = default_message_loglevel;
                }
                _call_console_drivers(start_print, cur_index, msg_level);
                msg_level = -1;
                start_print = cur_index;
                break;
            }
        }
    }
    _call_console_drivers(start_print, end, msg_level);/* 传入打印数据 */
}
/*
 * 从start到end一个字符一个字符输出
 */
static void _call_console_drivers(unsigned start,
                unsigned end, int msg_log_level)
{
    /* 如果当前日志等级高于设置打印的级别 则忽略掉 */
    if ((msg_log_level < console_loglevel || ignore_loglevel) &&
            console_drivers && start != end) {
        if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
            /* 如果start低于end 则如下方式打印 */
            __call_console_drivers(start & LOG_BUF_MASK,
                        log_buf_len);
            __call_console_drivers(0, end & LOG_BUF_MASK);
        } else {
            __call_console_drivers(start, end);/* 正常区间打印 */
        }
    }
}
static void __call_console_drivers(unsigned start, unsigned end)
{
    struct console *con;

    /* 遍历终端的链表 把信息输出到每个终端上 */
    for_each_console(con) {
        if (exclusive_console && con != exclusive_console)/* 只输出选中的终端 */
            continue;
        if ((con->flags & CON_ENABLED) && con->write &&
                (cpu_online(smp_processor_id()) ||
                (con->flags & CON_ANYTIME)))
            con->write(con, &LOG_BUF(start), end - start);/*调用每个终端的write接口*/
    }
}

用户空间访问内核日志

用户空间访问和控制内核日志有两个接口:
(1)通过glibc的klogctl函数接口调用内核的syslog系统调用 (2)通过fs/proc/kmsg.c内核模块中导出的procfs接口:/proc/kmsg文件。 他们其实最终都调用了/kernel/printk.c中的do_syslog函数,实现对__log_buf的访问及相关变量的修改。但值得注意的是:从/proc/kmsg中获取数据,那么 __log_buf中被读取过的数据就不再保留(也就是会修改log_start指针), 然而 syslog 系统调用返回日志数据并保留数据给其他进程。读取/proc文件是 klogd的默认做法。dmesg命令可用来查看缓存的内容并保留它,其实它是将__log_buf中的所有内容返回给stdout,并不管它是否已经被读取过

图像处理基础 || (一) python 读取图像的常用方式 图像的读取与保存是图像处理或计算机视觉领域中最基本的操作,python中有众多的库支持图像的读取、显示与存储,常用的库包括 matplotlib、skimage 和 Pillow 库。 阅读详情

相关推荐

A B C三个气象站的小波分析结果解读

在小波分析的过程中,信号的不同频率分量会在不同的尺度上显现,而纵轴上的数值表示信号在相应尺度上的强度或能量。2. **小波方差的横轴(水平轴)**:横轴上的尺度表示用于分析的小波函数的尺度。1. **共同趋势:** 在所有三个点的小波方差图中,随着尺度的增加,方差逐渐增大,尤其是在较大的时间尺度上。3. **尺度30的特殊性:** 在所有点的小波方差图中,尺度30似乎是一个特殊的点,对应着一个较长的时间尺度上的波动或变化。2. **方差的大小:** 方差的大小反映了在相应尺度下信号的波动强度。

weixin_44259522的博客 861

printk_defered打印详细解析

在linux源码中有一种特殊的打印函数,用户sched_flow过程中,此函数就是printk_defered,pirntk_defered源码如下所示: int printk_deferred(const char *fmt, ...) { va_list args; int r; va_start(args, fmt); r = vprintk_deferred(fmt, args...

jj244212647的博客 2251

从零开始使用matlab实现EKF-SLAM算法(含完整代码)

扩展卡尔曼滤波器同步定位与建图 (EKF-SLAM) 算法建立在 EKF 的基础上,集成了 EKF 的功能,可解决机器人技术中最具挑战性的问题之一:同时定位与建图 (SLAM)。 EKF-SLAM采用EKF方法来同时估计机器人的状态和周围环境的布局,而无需事先了解位置。 这种双重功能对于在未知环境中运行的自主系统至关重要,使它们能够在绘制区域地图时有效导航。 EKF-SLAM 算法一直是自动驾驶车辆和机器人系统开发的基石,为处理现实世界环境中固有的不确定性提供了一个强大的框架。

mjmmm的博客 6636

Linux内核printk实现

基于MTK 6595分析,内核版本3.10.5 1 Printk函数分析 内核为Printk维护一个环形缓冲区,其大小为: #define __LOG_BUF_LEN (1 大小可以通过CONFIG_LOG_BUF_SHIFT去控制 1.1函数原型如下: asmlinkage int printk(const char *fmt, ...) {          va_list

bin_linux96的专栏 7403

Cadence SKILL 语言实战指南:从函数列表到高效自动化脚本

本文是Cadence SKILL语言实战指南,旨在帮助芯片设计工程师从掌握核心函数列表出发,高效构建自动化脚本。文章详细解析了数据处理、数据库访问、流程控制及错误处理四类关键函数,并通过版图层批量修改与参数化单元生成两大实战场景,演示如何将函数组合成解决实际问题的自动化方案,从而显著提升Virtuoso环境下的工作效率。

beta5的博客 706

手把手教你配置MAX96712解串器:从I2C地址分配到PWM帧同步

本文详细解析了MAX96712解串器在自动驾驶视觉链路中的核心配置流程。从I2C地址分配、GMSL2数据流映射到实现多摄像头精准同步的PWM帧同步触发,手把手指导工程师构建高可靠性的图像传输系统,并提供了实用的Linux调试技巧与问题排查思路。

weixin_29297127的博客 927

printk导致系统崩溃

具体实例如下:   printk(KERN_ERR "random addess s is: %s\n",(char *)0X00000333);   输出为 : random addess s is: (null)   printk(KERN_ERR "random addess s is: %s\n",(char *)0X000003333);   却会导致系统崩溃   通过分析pr

李子的备忘录 4501

AI测试入门:如何测试AI对话模型/智能体?「详细介绍」

作为测试人员,我们需要掌握特定的技能和方法来有效地测试这些复杂的AI对话模型和智能体。传统的软件测试方法在这里可能不再完全适用,我们需要开发新的测试策略和技术来应对AI系统的独特特性。在这篇文章中,我们将深入探讨如何测试AI对话模型和智能体。

blues_C的博客 6864

printk中时间戳的来源

inux通过printk打印log时,一般会带上时间戳,这个时间戳的来源是什么?精度是多少?

weixin_40889165的博客 2690

SAP SmartForms字体 :从OTF缓存溢出到完美解决

精准诊断:遇到错误,首先区分是图片过大还是字体嵌入导致。删除图片测试是最快的判别方法。字体上传规范始终从SE73入口上传用于表单打印的字体。强烈建议应用SAP Note 1301018,确保上传时勾选“不替换其他字体”,避免系统自动替换引发的意外问题。如需为同一字体族上传粗体、斜体等变体,请在SE73初始屏幕上勾选对应的属性框(如BOLD),然后分别上传对应的字体文件(如上传过程中,系统会提示输入RTF控制序列信息,这是确保打印预览正常显示的关键步骤,必须正确填写。方案选型快速止血:采用方案一(

Pegasus666的博客 296

【2025年华为秋招(AI)-11月19日-第一题(100分)- 选择题】(题目+思路+Java&C++&Python解析+在线测试)

本文摘要: 本文包含15道单选题和5道多选题,涵盖机器学习、深度学习、概率统计和数值计算等多个领域。单选题涉及Transformer的Softmax层作用、最小二乘法拟合、概率密度计算、注意力机制、反向传播问题、ROC曲线用途、截断误差来源等知识点。多选题考察大模型训练技术、BERT/GPT比较、强化学习对齐方法、CNN常见层以及PCA特性等内容。题目设计注重理论与实践结合,既包含数学推导,又关注前沿技术应用,全面检验读者对机器学习核心概念的理解能力。正确答案均附有详细解析,帮助理解相关技术原理。

m0_73659489的博客 147

死锁问题----打印此刻系统中所有的task

一、task_struct的遍历 进程,线程和内核线程的基本属性都是由struct task_struct结构体定义的。 设备上运行的所有task都是通过双链表连成一串,其中第一个task就是init_task,最终又指向会init_task。因此可以通过首个task(init_task)通过双向链表(tasks)遍历所有task,最终回到init_task方式遍历所有的task。 首进程init_task在内核启动的时候静态赋值创建。(/in...

sydyh43的博客 495

[Dify]-进阶5- 使用 Dify 构建多轮对话的客服机器人

本文介绍了如何利用Dify平台构建智能客服系统,重点阐述了ChatFlow框架的应用。系统支持多轮对话、业务接口集成和话术策略管理,具备订单查询、退货申请等核心功能。通过Classifier节点实现意图识别,借助Router节点分流处理流程,并结合API插件实现业务数据对接。文章还提供了部署调试、会话优化和监控分析的具体方法,强调模块化设计和插件化集成的重要性,为构建功能完善的智能客服系统提供了完整解决方案。

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 1662

内核日志及printk结构浅析

一、printk概述     对于做Linux内核开发的人来说,printk实在是再熟悉不过了。内核启动时显示的各种信息大部分都是通过她来实现的,在做内核驱动调试的时候大部分时候使用她就足矣。她之所以用得如此广泛,一个是由于她使用方便,还有一个重要的原因是她的健壮性。它使用范围很广,几乎是内核的任何地方都能调用它。你既可以在中断上下文、进程上下中调用她,也可以在任何持有锁时调用她,更可以

tangkegaga的专栏 9986

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

浅析linux printk的实现

printk函数是我们调试linux内核必备的打印接口, printk定义在/kernel/路径下 声明在/linux/kernel.h中,如果在某个文件中使用printk时编译通不过,通常都是未包含这个头文件(当然还有语法错误,呵呵~) /**  * printk - print a kernel message  * @fmt: format string  *

jeffreyst的专栏 2948

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

浅析linux内核printk的原理

通常,在调试linux驱动时,我们常用到printk函数来输出一些关键信息,来作为调试手段。但不知道大家有没有想过,内核中的printk是如何打印到我们的串口控制台的呢?

DSP小胖的博客 2737

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

[linux-内核]内核日志及printk结构浅析

这段时间复习了一下内核调试系统,注意看了一下printk的实现以及内核日志的相关知识,这里做一下总结。 1、问题的引出: 做DPDK项目时,调试rte_kni.ko时,发现printk并不会向我们想想的那样把log信息显示在我们的终端上。有人总结了三个原因: 原因1:printk()有一个控制日志级别的字段,如果该字段的日记级别高于console默认的日志级别那么才会打印出来(数值越小日志级别越高,分为从0-7共计8个日志级别)。有一种简单的改变当前终端的日志级别的方法,echo 8 > /sy

u014426028的博客 1232

linux内核日志及printk结构浅析

转载:http://blog.chinaunix.net/uid-20543672-id-3211832.html  这段时间复习了一下内核调试系统,注意看了一下printk的实现以及内核日志的相关知识,这里做一下总结。 一、printk概述     对于做Linux内核开发的人来说,printk实在是再熟悉不过了。内核启动时显示的各种信息大部分都是通过她来实

rookie的专栏 755
上一篇: Kmap
下一篇: 安装DPDK
code__L
博客等级 码龄10年 3粉丝 8原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值