Linux中控制台初始化console_init函数的实现

控制台系统初始化console_init

c 复制代码
void __init console_init(void)
{
        initcall_t *call;

        /* Setup the default TTY line discipline. */
        (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

        /*
         * set up the console device so that later boot sequences can
         * inform about problems etc..
         */
#ifdef CONFIG_EARLY_PRINTK
        disable_early_printk();
#endif
#ifdef CONFIG_SERIAL_68360
        /* This is not a console initcall. I know not what it's doing here.
           So I haven't moved it. dwmw2 */
        rs_360_init();
#endif
        call = &__con_initcall_start;
        while (call < &__con_initcall_end) {
                (*call)();
                call++;
        }
}

1. 函数功能概述

console_init 函数负责初始化内核控制台系统,包括注册 TTY 线路规程、处理早期打印、执行控制台驱动初始化等,为内核提供输出和交互能力

2. 代码逐段分析

2.1. 函数定义和变量声明

c 复制代码
void __init console_init(void)
{
        initcall_t *call;
  • void __init:无返回值,__init 表示只在初始化阶段使用
  • initcall_t *call:初始化调用函数指针,用于遍历执行控制台初始化函数

2.2. 注册默认 TTY 线路规程

c 复制代码
        /* Setup the default TTY line discipline. */
        (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
  • 设置默认的 TTY 线路规程
  • tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY):注册 TTY 线路规程
    • N_TTY:线路规程编号,表示规范模式(canonical mode)
    • &tty_ldisc_N_TTY:N_TTY 线路规程的操作结构体指针
  • (void):显式忽略返回值,表示不关心注册结果
  • 作用:为终端设备建立默认的输入处理和行为规则

2.3. 早期打印支持(条件编译)

c 复制代码
#ifdef CONFIG_EARLY_PRINTK
        disable_early_printk();
#endif
  • #ifdef CONFIG_EARLY_PRINTK:条件编译,只在配置了早期打印支持时包含
  • disable_early_printk():禁用早期打印功能
  • 背景:早期打印在内核完全初始化前使用,现在正式控制台已就绪,可以关闭

2.4. 68360 串口特殊处理(条件编译)

c 复制代码
#ifdef CONFIG_SERIAL_68360
        /* This is not a console initcall. I know not what it's doing here.
           So I haven't moved it. dwmw2 */
        rs_360_init();
#endif
  • #ifdef CONFIG_SERIAL_68360:针对 68360 串口的特殊处理
  • rs_360_init():68360 串口初始化函数
  • 作用:历史遗留代码,确保特定硬件的串口正常工作

2.5. 控制台初始化调用遍历

c 复制代码
        call = &__con_initcall_start;
        while (call < &__con_initcall_end) {
                (*call)();
                call++;
        }
}
  • call = &__con_initcall_start:获取控制台初始化调用段的起始地址
  • while (call < &__con_initcall_end):循环直到初始化调用段的结束地址
  • (*call)():执行当前指向的初始化函数
  • call++:移动到下一个初始化函数指针

TTY 线路规程注册tty_register_ldisc

c 复制代码
int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
        unsigned long flags;
        int ret = 0;

        if (disc < N_TTY || disc >= NR_LDISCS)
                return -EINVAL;

        spin_lock_irqsave(&tty_ldisc_lock, flags);
        if (new_ldisc) {
                tty_ldiscs[disc] = *new_ldisc;
                tty_ldiscs[disc].num = disc;
                tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
                tty_ldiscs[disc].refcount = 0;
        } else {
                if(tty_ldiscs[disc].refcount)
                        ret = -EBUSY;
                else
                        tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
        }
        spin_unlock_irqrestore(&tty_ldisc_lock, flags);

        return ret;
}

1. 函数功能概述

tty_register_ldisc 函数负责注册或注销 TTY 线路规程,管理线路规程的全局表,为终端设备提供不同的输入处理和行为模式

2. 代码逐段分析

2.1. 函数定义和变量声明

c 复制代码
int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
        unsigned long flags;
        int ret = 0;
  • int:返回值类型,0表示成功,负数表示错误码
  • int disc:线路规程编号(如 N_TTY=0, N_SLIP=1, N_PPP=3 等)
  • struct tty_ldisc *new_ldisc:要注册的线路规程结构指针,NULL表示注销
  • unsigned long flags:保存中断状态的变量
  • int ret = 0:返回值,初始化为成功

2.2. 参数有效性检查

c 复制代码
        if (disc < N_TTY || disc >= NR_LDISCS)
                return -EINVAL;
  • disc < N_TTY:检查编号是否小于最小有效值(N_TTY=0)
  • disc >= NR_LDISCS:检查编号是否超过最大支持数量
  • NR_LDISCS:最大线路规程数
  • return -EINVAL:如果编号无效,返回"无效参数"错误

2.3. 获取保护锁

c 复制代码
        spin_lock_irqsave(&tty_ldisc_lock, flags);
  • spin_lock_irqsave(&tty_ldisc_lock, flags):获取线路规程锁并保存中断状态
  • tty_ldisc_lock:保护线路规程表的自旋锁
  • flags:保存当前中断启用状态,解锁时恢复
  • 作用:防止多CPU并发修改线路规程表

2.4. 注册新线路规程分支

c 复制代码
        if (new_ldisc) {
                tty_ldiscs[disc] = *new_ldisc;
                tty_ldiscs[disc].num = disc;
                tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
                tty_ldiscs[disc].refcount = 0;
  • if (new_ldisc):如果传入非空指针,执行注册操作

注册步骤:

  • tty_ldiscs[disc] = *new_ldisc:将传入的线路规程结构复制到全局数组
  • tty_ldiscs[disc].num = disc:设置线路规程编号
  • tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED:设置"已定义"标志位
  • tty_ldiscs[disc].refcount = 0:初始化引用计数为0

2.5. 注销线路规程分支

c 复制代码
        } else {
                if(tty_ldiscs[disc].refcount)
                        ret = -EBUSY;
                else
                        tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
        }
  • else:如果传入NULL指针,执行注销操作

注销逻辑:

  • if(tty_ldiscs[disc].refcount):检查是否有活跃引用
  • ret = -EBUSY:如果有活跃引用,返回"设备忙"错误
  • tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED:如果没有引用,清除"已定义"标志

2.6. 释放锁并返回

c 复制代码
        spin_unlock_irqrestore(&tty_ldisc_lock, flags);

        return ret;
}
  • spin_unlock_irqrestore(&tty_ldisc_lock, flags):释放锁并恢复中断状态
  • return ret:返回操作结果(0成功,负数错误)

内核紧急情况处理panic

c 复制代码
NORET_TYPE void panic(const char * fmt, ...)
        __attribute__ ((NORET_AND format (printf, 1, 2)));

1. 代码逐段分析

1.1. 返回值类型声明

c 复制代码
NORET_TYPE void panic(const char * fmt, ...)
  • NORET_TYPE void:特殊的返回值类型声明
  • panic:函数名,表示内核恐慌/紧急状态
  • const char * fmt:格式化字符串参数,不可修改的字符串指针
  • ...:可变参数列表,表示可以接受多个参数

1.2. 函数属性声明

c 复制代码
        __attribute__ ((NORET_AND format (printf, 1, 2)));
  • __attribute__:GCC 编译器扩展,用于指定函数属性
  • ((NORET_AND format (printf, 1, 2))):包含两个属性的复合属性

NORET_TYPE 宏定义

c 复制代码
// 在 Linux 内核中的典型定义
#define NORET_TYPE    /* */
#define NORET_AND     noreturn,

noreturn 属性含义:

  • 告诉编译器这个函数永远不会返回
  • 函数会终止程序执行或无限循环
  • 编译器可以据此优化代码和警告

format 属性详解

c 复制代码
format (printf, 1, 2)
  • printf:指定格式字符串风格与 printf 相同
  • 1:格式字符串参数的位置(第1个参数)
  • 2:可变参数开始的位置(从第2个参数开始)
相关推荐
望获linux9 小时前
【实时Linux实战系列】FPGA 与实时 Linux 的协同设计
大数据·linux·服务器·网络·数据库·fpga开发·操作系统
武文斌779 小时前
复习总结最终版:计算机网络
linux·开发语言·学习·计算机网络
Mr.45679 小时前
Linux CentOS 7 安装配置HAProxy完整指南:实现高可用负载均衡
linux·centos·负载均衡
小糖学代码11 小时前
Linux:11.线程概念与控制
linux·服务器·c语言·开发语言·c++
YouEmbedded14 小时前
解码Linux文件IO目录检索与文件属性
linux·文件属性·文件io·目录检索
大聪明-PLUS18 小时前
关于新的 Linux 内核接口 gpio uapi 的说明
linux·嵌入式·arm·smarc
玉树临风江流儿18 小时前
Linux驱动开发总结速记
linux·运维·驱动开发
cccyi718 小时前
Linux 进程信号机制详解
linux·signal·volatile
gd632137419 小时前
银河麒麟 aarch64 linux 里面的 qt 怎么安装kit
linux·服务器·qt