找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1508

积分

0

好友

198

主题
发表于 2026-2-11 12:59:57 | 查看: 35| 回复: 0

昨天在家搭建环境时发现一个问题,我安装的ubuntu 22.04.5,默认的系统启动时不会出现弹出选择内核版本的菜单,需要修改grup脚本:

# 1. 编辑GRUB默认配置文件
sudo vim /etc/default/grub

# 2. 修改以下参数
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.6.123"

# 3. 关闭GRUB菜单隐藏(方便后续验证)
GRUB_TIMEOUT=5  # 菜单显示5秒,默认可能是0(隐藏)
GRUB_TIMEOUT_STYLE=menu

# 4. 保存退出后,更新GRUB配置
sudo update-grub

做完如上设置就可正常切换到自己手动安装的内核版本,调试驱动安装就都正常了。

终端加载ttynull.ko模块并查看内核日志

今天我走读了 ttynull 驱动源码, 这是 Linux 内核中基于 TTY 子系统实现的空终端驱动。

驱动简介

ttynull.c核心功能是模拟 /dev/ttynull 设备(类似 /dev/null 但适配 TTY 子系统):

  • 写入该设备的数据被直接丢弃,仅返回写入长度;
  • 无实际硬件交互,纯软件层面实现 TTY 子系统的 “空操作”;
  • 注册为控制台设备,可作为系统输出的 “黑洞”;
  • 它是学习 Linux 子系统架构、字符设备驱动的经典入门案例。

完整源码逐行注释

// SPDX-License-Identifier: GPL-2.0  // 内核开源许可证声明(必选)
/*
 * Copyright (C) 2019 Axis Communications AB
 *
 * Based on ttyprintk.c:
 *  Copyright (C) 2010 Samo Pogacnik
 */
// 内核头文件引入
#include <linux/console.h>
#include <linux/module.h>
#include <linux/tty.h>

// TTY端口操作集(本驱动无硬件,为空实现)
static const struct tty_port_operations ttynull_port_ops;
// TTY驱动核心结构体指针(管理驱动生命周期)
static struct tty_driver *ttynull_driver;
// TTY端口结构体(管理设备打开/关闭/挂起等状态)
static struct tty_port ttynull_port;
/**
 * ttynull_open - TTY设备打开函数
 * @tty: 指向当前打开的TTY设备结构体
 * @filp: 指向文件操作结构体(用户空间open对应的内核态结构)
 * 返回值:0表示成功,负数表示错误
 * 功能:复用内核tty_port的通用打开逻辑,无自定义硬件初始化
 */
static int ttynull_open(struct tty_struct *tty, struct file *filp)
{
  return tty_port_open(&ttynull_port, tty, filp);
}

/**
 * ttynull_close - TTY设备关闭函数
 * @tty: 当前TTY设备结构体
 * @filp: 文件操作结构体
 * 功能:复用内核tty_port的通用关闭逻辑,无自定义硬件释放
 */
static void ttynull_close(struct tty_struct *tty, struct file *filp)
{
  tty_port_close(&ttynull_port, tty, filp);
}

/**
 * ttynull_hangup - TTY设备挂起处理函数
 * @tty: 当前TTY设备结构体
 * 功能:处理设备挂起事件(如串口断开),复用内核通用逻辑
 */
static void ttynull_hangup(struct tty_struct *tty)
{
  tty_port_hangup(&ttynull_port);
}

/**
 * ttynull_write - TTY设备写入函数(核心空操作)
 * @tty: 当前TTY设备结构体
 * @buf: 待写入的数据缓冲区(用户空间传入)
 * @count: 待写入的数据长度
 * 返回值:传入的count(模拟“成功写入”,实际数据被丢弃)
 * 功能:不读取buf中的数据,直接返回长度,实现“写入即丢弃”
 */
static ssize_t ttynull_write(struct tty_struct *tty, const u8 *buf,
                 size_t count)
{
  return count;  // 核心:无数据处理,直接返回写入长度
}

/**
 * ttynull_write_room - 告知内核设备可用写入空间
 * @tty: 当前TTY设备结构体
 * 返回值:固定65536(64KB),表示设备有充足写入空间
 * 功能:避免内核因“空间不足”拒绝写入,无实际硬件意义
 */
static unsigned int ttynull_write_room(struct tty_struct *tty)
{
  return 65536;
}

/**
 * ttynull_ops - TTY设备操作集
 * 功能:封装驱动实现的核心操作,未实现的接口由内核提供默认空实现
 * 注:TTY子系统的核心抽象,所有TTY驱动都需实现该结构体的关键成员
 */
static const struct tty_operations ttynull_ops = {
    .open = ttynull_open,        // 设备打开接口
    .close = ttynull_close,      // 设备关闭接口
    .hangup = ttynull_hangup,    // 设备挂起接口
    .write = ttynull_write,      // 设备写入接口
    .write_room = ttynull_write_room,  // 写入空间查询接口
};

/**
 * ttynull_device - 控制台与TTY驱动的关联函数
 * @c: 控制台结构体
 * @index: 设备索引(输出参数)
 * 返回值:ttynull_driver(当前TTY驱动指针)
 * 功能:将控制台设备关联到ttynull驱动,仅支持1个设备实例(索引0)
 */
static struct tty_driver *ttynull_device(struct console *c, int *index)
{
    *index = 0;          // 仅支持1个设备实例(/dev/ttynull)
    return ttynull_driver;
}

/**
 * ttynull_console - 控制台设备结构体
 * 功能:注册ttynull为控制台类型设备,支持系统级输出(最终被丢弃)
 */
static struct console ttynull_console = {
    .name = "ttynull",           // 控制台名称(对应/dev/ttynull)
    .device = ttynull_device,    // 控制台与TTY驱动的关联函数
};

/**
 * ttynull_init - 模块初始化函数(驱动加载入口)
 * 返回值:0表示成功,负数表示错误
 * 功能:完成TTY驱动的分配、配置、注册,以及控制台注册
 * 流程:分配驱动→初始化端口→配置驱动→注册驱动→注册控制台
 */
static int __init ttynull_init(void)
{
  struct tty_driver *driver;
  int ret;
  // 1. 分配TTY驱动结构体
  // 参数1:设备实例数(1个);参数2:驱动属性
  // TTY_DRIVER_RESET_TERMIOS:打开时重置终端属性
  // TTY_DRIVER_REAL_RAW:原始模式(无字符处理)
  // TTY_DRIVER_UNNUMBERED_NODE:不生成编号节点(仅/dev/ttynull,无ttynull1/2)
  driver = tty_alloc_driver(1,
    TTY_DRIVER_RESET_TERMIOS |
    TTY_DRIVER_REAL_RAW |
    TTY_DRIVER_UNNUMBERED_NODE);
  if (IS_ERR(driver))  // 检查分配是否失败(内核分配失败返回错误码指针)
    return PTR_ERR(driver);  // 转换错误码并返回
  // 2. 初始化TTY端口(关联空操作集)
  tty_port_init(&ttynull_port);
  ttynull_port.ops = &ttynull_port_ops;
  // 3. 配置TTY驱动核心属性
  driver->driver_name = "ttynull";  // 内核态驱动名称(标识用)
  driver->name = "ttynull";         // 用户态设备名称(/dev/ttynull)
  driver->type = TTY_DRIVER_TYPE_CONSOLE;  // 驱动类型:控制台
  driver->init_termios = tty_std_termios;  // 初始化终端属性为内核默认值
  // 配置输出模式:换行符转换等(符合TTY规范)
  driver->init_termios.c_oflag = OPOST | OCRNL | ONOCR | ONLRET;
  tty_set_operations(driver, &ttynull_ops);  // 绑定驱动操作集
  tty_port_link_device(&ttynull_port, driver, 0);  // 关联端口与驱动
  // 4. 注册TTY驱动到内核
  ret = tty_register_driver(driver);
  if (ret < 0) 
  {  // 注册失败:释放已分配的资源(内核编码规范:失败必释放)
    tty_driver_kref_put(driver);    // 释放驱动结构体
    tty_port_destroy(&ttynull_port); // 销毁TTY端口
    return ret;
  }
  // 5. 保存驱动指针 + 注册控制台
  ttynull_driver = driver;
  register_console(&ttynull_console);  // 将ttynull注册为控制台设备
  return 0;  // 初始化成功
}
/**
 * ttynull_exit - 模块退出函数(驱动卸载入口)
 * 功能:释放所有资源,与初始化流程相反
 * 流程:注销控制台→注销驱动→释放驱动→销毁端口
 */
static void __exit ttynull_exit(void)
{
  unregister_console(&ttynull_console);  // 注销控制台
  tty_unregister_driver(ttynull_driver); // 注销TTY驱动
  tty_driver_kref_put(ttynull_driver);   // 释放驱动结构体
  tty_port_destroy(&ttynull_port);       // 销毁TTY端口
}

// 模块加载/卸载入口(内核模块标准声明)
module_init(ttynull_init);
module_exit(ttynull_exit);
// 模块许可证声明(必须为GPL,否则内核拒绝加载)
MODULE_LICENSE("GPL v2");

核心模块拆解

[1]. 头文件与全局变量

TTY子系统相关变量与头文件对照表

[2]. TTY 操作集(tty_operations)

它是 TTY子系统 的核心抽象接口,所有 TTY 驱动都需实现关键成员;
本驱动仅实现必要的 5 个接口,其余接口(如read)由内核提供默认空实现(返回-ENODEV);
write 是核心:通过 “返回长度不处理数据” 实现空驱动的核心功能。

[3]. 模块初始化与退出

初始化流程(ttynull_init)图如下所示:

TTY驱动初始化与注册流程图

说明:注册失败时需逐层释放资源(驱动→端口),符合内核 “申请失败必释放” 的编码规范。

退出流程(ttynull_exit):
与初始化流程完全相反:先注销高层接口(控制台),再释放底层资源(驱动→端口),避免资源泄漏。

[4]. 控制台注册

ttynull_console 将驱动注册为控制台设备,支持系统通过 printk 等接口输出数据;
ttynull_device 实现控制台与 TTY 驱动的关联,确保系统输出能路由到 ttynull 驱动。

关键知识点总结

[1]. TTY 子系统核心概念

  • tty_driver: TTY 驱动的核心抽象,描述驱动名称、类型、操作集等
  • tty_port: 管理 TTY 设备的状态(打开 / 关闭 / 挂起),解耦驱动与硬件
  • tty_operations: 驱动需实现的操作接口,是 TTY 子系统与驱动的交互层
  • console: 系统控制台抽象,支持内核 / 应用输出到 TTY 设备

[2]. 空驱动设计的巧妙之处

  • 复用内核逻辑: 无硬件交互时,直接复用 tty_port 提供的通用接口(open/close/hangup);
  • 最小化实现: 仅实现核心的 write 接口,其余接口用默认实现;
  • 无资源无处理: ttynull_port_ops 为空实现,因无硬件需管理。

[3]. 内核编码规范

  • 必须声明 MODULE_LICENSE("GPL"),否则内核标记为 “tainted” 并拒绝加载;
  • 资源申请失败时必须释放已申请的资源,避免内存泄漏;
  • 函数命名遵循 “驱动名 + 功能”(如 ttynull_write),增强可读性;
  • 返回值规范:ssize_t 类型返回长度,错误时返回负的 errno(如 PTR_ERR(driver))。

学习价值

  1. 理解 TTY 子系统的分层架构:驱动层(tty_driver)→ 端口层(tty_port)→ 硬件层;
  2. 掌握内核字符设备驱动的极简实现范式:无硬件时如何复用内核通用逻辑;
  3. 熟悉内核模块的资源管理规范:申请 / 释放的对称式设计;
  4. 为学习复杂 TTY 驱动(如串口驱动、虚拟终端驱动)打下基础。

在系统与内核驱动的学习道路上,持续的实践与源码走读是关键。希望这份详细的 ttynull 驱动源码分析,能帮助你更好地理解 Linux 内核的工作机制。本文及更多深度技术内容也可以在 云栈社区 找到。




上一篇:意法半导体发布Stellar P3E汽车MCU,集成自研NPU实现30倍AI性能提升
下一篇:10个提升研发效率的AI Agent Skills:从设计到部署全覆盖
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 15:35 , Processed in 0.415534 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表