深入理解 Linux 内核模块:核心知识、实战解析与高频面试题

devtools/2025/3/3 22:21:56/

1. 什么是 Linux 内核模块?

1.1 定义

Linux 内核模块(Kernel Module)是 可动态加载和卸载的内核代码,它允许开发者在 无需重启系统 的情况下扩展内核功能,例如:

  • 设备驱动(Drivers)
  • 文件系统(File Systems)
  • 网络协议(Network Protocols)
  • 安全增强(Security Extensions)
    在这里插入图片描述

1.2 为什么需要内核模块?

在没有内核模块的情况下,所有功能都需要编译进内核,这会导致:

  1. 大而臃肿:系统包含许多不必要的代码,占用内存。
  2. 不灵活:任何修改都需要重新编译并重启内核,影响系统稳定性。

内核模块的优势
动态加载/卸载,提高灵活性
减少内存占用,按需加载
降低内核维护成本,方便调试


2. 内核模块的基本结构

2.1 一个最简单的内核模块

#include <linux/module.h>   // 内核模块头文件
#include <linux/init.h>     // __init 和 __exit 相关宏MODULE_LICENSE("GPL");  // 指定模块的许可证
MODULE_AUTHOR("Your Name");  // 作者
MODULE_DESCRIPTION("A Simple Linux Kernel Module");  // 模块描述// 模块加载函数
static int __init my_module_init(void)
{printk(KERN_INFO "Hello, Kernel! My module is loaded.\n");return 0;
}// 模块卸载函数
static void __exit my_module_exit(void)
{printk(KERN_INFO "Goodbye, Kernel! My module is removed.\n");
}// 注册模块入口和退出函数
module_init(my_module_init);
module_exit(my_module_exit);

2.2 关键宏解析

  • MODULE_LICENSE("GPL") 规定模块的许可证,否则可能会触发 taint(污染)警告。
  • module_init() 指定模块加载时执行的函数。
  • module_exit() 指定模块卸载时执行的函数。

2.3 编译、加载与卸载

创建 Makefile

obj-m += my_module.o  
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)all:$(MAKE) -C $(KDIR) M=$(PWD) modulesclean:$(MAKE) -C $(KDIR) M=$(PWD) clean

编译:

make

加载模块:

sudo insmod my_module.ko

查看日志:

dmesg | tail -n 10

卸载模块:

sudo rmmod my_module

3. 设备驱动与字符设备

3.1 字符设备基础

字符设备是按字节流方式访问的设备,例如串口、键盘等。

3.2 字符设备驱动框架

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>#define DEVICE_NAME "mychardev"static dev_t dev_num;
static struct cdev my_cdev;static int my_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static int my_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}static struct file_operations fops = {.owner = THIS_MODULE,.open = my_open,.release = my_release,
};static int __init my_module_init(void) {alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);cdev_init(&my_cdev, &fops);cdev_add(&my_cdev, dev_num, 1);printk(KERN_INFO "Char device registered\n");return 0;
}static void __exit my_module_exit(void) {cdev_del(&my_cdev);unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "Char device unregistered\n");
}module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

4. Linux 内核模块面试高频问题

Q1: 你能解释一下 printkdmesg 的作用吗?

解答

  • printk():内核日志打印函数,用于调试。
  • dmesg:查看 printk 输出的命令。

示例

printk(KERN_INFO "This is an informational message.\n");

常见的 KERN_ 级别:

  • KERN_EMERG:紧急情况
  • KERN_ALERT:需要立即采取措施
  • KERN_CRIT:严重错误
  • KERN_ERR:错误信息
  • KERN_WARNING:警告
  • KERN_NOTICE:普通重要消息
  • KERN_INFO:信息
  • KERN_DEBUG:调试信息

Q2: module_init()module_exit() 的作用是什么?

解答

  • module_init():定义内核模块加载时执行的函数。
  • module_exit():定义内核模块卸载时执行的函数。

Q3: 设备号是什么?如何分配?

解答
设备号(Device Number)用于唯一标识设备,包括:

  • 主设备号(Major Number):表示设备类型(如磁盘、串口)。
  • 次设备号(Minor Number):区分同类设备。

分配方式

dev_t dev_num;
alloc_chrdev_region(&dev_num, 0, 1, "my_device");

或者使用 register_chrdev() 动态注册。


Q4: 你能解释一下 file_operations 结构体的作用吗?

解答
file_operations 结构体用于定义设备驱动的操作,例如:

struct file_operations fops = {.open = my_open,.release = my_release,.read = my_read,.write = my_write,
};

它的作用类似于 系统调用接口,用于应用层和内核交互


5. 结论

这篇文章介绍了 Linux 内核模块 的基本概念、代码示例、常见面试题及其解答。
对于初学者,建议从 加载和卸载模块 开始练习;对于高阶工程师,建议研究 字符设备驱动面试考点,以深入理解 Linux 内核模块的运作机制。

欢迎留言交流!😊加粗样式


http://www.ppmy.cn/devtools/164295.html

相关文章

提升 Spring Boot 系统性能:高效处理实时数据流的 BufferTrigger 使用详解

提升 Spring Boot 系统性能&#xff1a;高效处理实时数据流的 BufferTrigger 使用详解 在现代应用中&#xff0c;特别是像社交平台、金融系统等高并发场景下&#xff0c;如何高效地处理大量实时数据成为了系统设计的一个关键问题。BufferTrigger 是由快手开源的一个工具&#…

React核心知识及使用场景

React是一个用于构建用户界面的JavaScript库,尤其适合构建单页面应用(SPA)。它基于组件化的开发思想,主要特点是通过虚拟DOM来提高渲染效率。以下是React的核心知识和使用场景: 一. 核心知识 组件化: 类组件和函数组件:React的组件分为类组件和函数组件。类组件通过继承…

3.jvm的执行流程

自上向下 ​编译&#xff1a;.java → .class。 ​加载&#xff1a;类加载器加载字节码到方法区&#xff0c;生成Class对象。 ​内存分配&#xff1a;对象实例存入堆&#xff0c;方法调用栈帧入虚拟机栈。 ​执行&#xff1a;解释器或JIT执行字节码&#xff0c;本地方法通过JNI…

零知识证明在区块链加密货币中的应用分析

目录 1引言 2 研究内容 2.1 零知识认证算法模型 2.2 区块链中的加密货币算法 2.2.1 区块链中的零知识证明算法 2.2.2 算法设计 2.2.3 算法的实现 2.3 方案的安全性分析 2.4 方案的效率分析 3 结论 1引言 随着信息技术的迅速发展&#xff0c;数据隐私和安全性…

C++入门(2)

1.变量和常量 1.1变量的创建 我们把经常变化的值称为变量&#xff0c;不变的值称为常量。 data_type name;| || | 数据类型 变量名 1 int age; //整型变量 2 char ch; //字符变量 3 double weight; //浮点型变量 变量的命名规则遵循以下原则&#x…

Is Noise Conditioning Necessary for Denoising Generative Models?论文阅读笔记

很吸引人的一个标题&#xff0c;很吸引人的一个作者&#xff0c;来读一读明神的新作&#xff0c;讲的是怎么把去噪领域的一些有意思的思想&#xff0c;特别是blind denoising和noise-level estimation的思想&#xff0c;应用到denoising diffusion模型中&#xff0c;从而去掉de…

4-1.jvm的类加载

JVM的类加载机制是将字节码文件&#xff08;.class&#xff09;动态加载到内存&#xff0c;并进行验证、准备、解析和初始化的过程&#xff0c;最终生成可被虚拟机直接使用的类对象。&#xff1a; 一、类加载的五大阶段 ​加载&#xff08;Loading&#xff09;​​ ​任务&a…

Android双屏异显副屏实现PIP效果小窗口同步显示主屏播放画面

在KTV应用开发中一个常见的场景需求就是一台设备要接多个显示屏&#xff0c;其中一个主屏一般都是触摸屏&#xff0c;通过VGA线连接&#xff0c;支持手点击操作点歌切歌等。另外还会有多个副屏&#xff0c;一般都是电视机&#xff0c;通过HDMI线连接。 有一个特点就是所有电视…