一、加载内核源码
1. 安装系统内核开发包
sudo apt-get update
sudo apt-get install linux-headers-6.8.0-60-generic
2.加载版本内核(我用的6.8版本)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.8.tar.xz
tar -xvf linux-6.8.tar.xz
3. 编译内核
cd linux-6.8
cp /boot/config-6.8.0-60-generic .config
make defconfig
make oldconfig # 按回车接受默认值
make prepare && make modules_prepare
####当然可能会遇见的错误的解决办法:
sudo apt-get update
sudo apt-get install build-essential flex bison libssl-dev libncurses5-dev
sudo apt-get install libelf-dev
sudo apt-get install gcc-12 g++-12
二、实战案例
1.内核源码目录结构规划
我们要在内核中添加一个简单的my_driver的驱动,目录结构如下:
drivers/
└── my_driver/ # 主驱动目录
├── include/ # 头文件子目录
│ └── my_driver.h
├── src/ # 源代码子目录
│ ├── my_driver_core.c
│ └── my_driver_device.c
| └── Makefile
├── Kconfig # 驱动配置文件
└── Makefile # 驱动编译文件
2. 创建驱动代码与目录
步骤 1:进入内核源码目录
cd /home/jerry/Documents/test/linux-6.8 # 替换为你的内核源码路径
步骤 2:创建目录结构
mkdir -p drivers/my_driver/{include,src}
步骤 3:编写头文件: my_driver.h
// drivers/my_driver/include/my_driver.h
#ifndef MY_DRIVER_H
#define MY_DRIVER_H
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define MY_DRIVER_NAME "my_driver"
#define MY_DRIVER_DEVICE_COUNT 1
#define MY_DRIVER_DEVICE_MINOR 0
int my_driver_init(void);
void my_driver_exit(void);
int my_driver_device_init(void);
void my_driver_device_exit(void); // 新增声明
#endif /* MY_DRIVER_H */
步骤 4:编写核心驱动代码: my_driver_core.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include "../include/my_driver.h"
static dev_t my_driver_devt; //设备号(主设备号 + 次设备号)
static struct cdev my_driver_cdev; //字符设备结构,用于注册设备操作函数
static struct class *my_driver_class; //设备类指针,用于自动创建设备节点
//设备打开函数
static int my_driver_open(struct inode *inode,struct file *file){
printk(KERN_INFO "my_driver: device opened\n");
return 0;
}
// 设备释放函数
static int my_driver_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "my_driver: device released\n");
return 0;
}
//文件操作表
static const struct file_operations my_driver_fops = {
.open = my_driver_open,
.release = my_driver_release,
};
//驱动初始化函数
int my_driver_init(void){
int ret;
//让内核中台分配一个主设备号分配设备号
/*
输出参数,存储分配的设备号
起始次设备号
设备数量
设备名
*/
ret = alloc_chrdev_region(&my_driver_devt,MY_DRIVER_DEVICE_MINOR,MY_DRIVER_DEVICE_COUNT,MY_DRIVER_NAME);
if (ret < 0) {
printk(KERN_ERR "my_driver: failed to allocate device number\n");
return ret;
}
//初始化字符设备
cdev_init(&my_driver_cdev,&my_driver_fops);
my_driver_cdev.owner = THIS_MODULE;
//注册字符设备
ret = cdev_add(&my_driver_cdev,my_driver_devt,MY_DRIVER_DEVICE_COUNT);
if (ret < 0) {
unregister_chrdev_region(my_driver_devt, MY_DRIVER_DEVICE_COUNT);
printk(KERN_ERR "my_driver: failed to add cdev\n");
return ret;
}
// 创建设备类(修改此处:移除 THIS_MODULE 参数)
my_driver_class = class_create(MY_DRIVER_NAME);
if (IS_ERR(my_driver_class)) {
cdev_del(&my_driver_cdev);
unregister_chrdev_region(my_driver_devt, MY_DRIVER_DEVICE_COUNT);
printk(KERN_ERR "my_driver: failed to create class\n");
return PTR_ERR(my_driver_class);
}
//创建设备节点
device_create(my_driver_class,NULL,my_driver_devt,NULL,MY_DRIVER_NAME);
printk(KERN_INFO "my_driver: initialized successfully\n");
return 0;
}
EXPORT_SYMBOL(my_driver_init);
//驱动退出函数
void my_driver_exit(void){
//销毁设备节点
device_destroy(my_driver_class,my_driver_devt);
//销毁设备类
class_destroy(my_driver_class);
//销毁字符设备
cdev_del(&my_driver_cdev);
//释放设备号
unregister_chrdev_region(my_driver_devt,MY_DRIVER_DEVICE_COUNT);
printk(KERN_INFO "my_driver: unloaded\n");
}
EXPORT_SYMBOL(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jerry");
MODULE_DESCRIPTION("My Driver Example");
步骤 5:编写设备相关代码: my_driver_device.c
// drivers/my_driver/src/my_driver_device.c
#include <linux/module.h>
#include "../include/my_driver.h"
// 设备初始化函数
int my_driver_device_init(void) {
printk(KERN_INFO "my_driver: device module initialized\n");
return my_driver_init();
}
// 设备退出函数
void my_driver_device_exit(void) {
my_driver_exit();
printk(KERN_INFO "my_driver: device module unloaded\n");
}
// 模块入口/出口
module_init(my_driver_device_init);
module_exit(my_driver_device_exit);
// 模块信息(严格遵循格式)
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My Driver Device Module");
MODULE_AUTHOR("Your Name");
MODULE_SOFTDEP("pre: my_driver_core"); // Optional soft dependency
3. 修改 Kconfig 和 Makefile 文件
3.1 驱动目录下的 Kconfig 文件
# drivers/my_driver/Kconfig
config MY_DRIVER
tristate "My Driver Example"
help
This is a simple example driver to demonstrate adding a new directory
to the kernel source tree. It provides a basic character device.
config MY_DRIVER_DEBUG
bool "My Driver Debug Mode"
default n
depends on MY_DRIVER
help
Enable debug messages for my driver.
3.2 驱动目录下的 Makefile 文件
# drivers/my_driver/Makefile
obj-$(CONFIG_MY_DRIVER) += src/
src-y += my_driver_core.o
src-y += my_driver_device.o
# 子目录编译规则
src-$(CONFIG_MY_DRIVER) += include/
3.3 上层目录的 Kconfig 和 Makefile 修改
修改 drivers/Kconfig(添加菜单入口)
# drivers/Kconfig
source "drivers/my_driver/Kconfig"
修改 drivers/Makefile(添加编译规则)
# drivers/Makefile
obj-y += my_driver/
4. 清除旧配置并重新生成
cd /home/jerry/Documents/test/linux-6.8
make mrproper # 清除所有编译文件和配置
make defconfig # 生成默认配置
5. 重新执行 menuconfig
make menuconfig
验证配置是否生效
在menuconfig
界面中,应能找到:
Device Drivers --->
[*] My Driver Example --->
[ ] My Driver Debug Mode # 可选勾选
光标移到新模块,然后选择:
<*>
(内置):按Y
,驱动会编译进内核镜像,系统启动后自动加载。适合基础、必选的驱动。<M>
(模块):按M
,驱动编译为独立.ko
文件,需手动insmod
加载、rmmod
卸载。灵活度高,方便调试。< >
(不编译):按N
,驱动不会参与编译,适合暂时不用的功能。
选好后,按 <Enter>
确认,若有子选项(比如之前定义的 MY_DRIVER_DEBUG
),可进入子菜单进一步配置。
根据编译方式,执行不同命令:
驱动内置(
Y
):直接编译整个内核make -j$(nproc) # 用所有 CPU 核心加速编译,nproc 显示核心数 sudo make modules_install # 安装内核模块(若有其他模块) sudo make install # 安装内核、更新引导(不同系统可能需额外操作,如更新 grub)
编译完成后,重启系统(
sudo reboot
),新内核启动后驱动随内核加载。驱动编译为模块(
M
):单独编译模块make -j$(nproc) modules # 只编译模块,生成 .ko 文件
编译出的
my_driver.ko
(路径一般在drivers/my_driver/src/
或对应模块目录),可用以下命令管理:sudo insmod drivers/my_driver/src/my_driver.ko # 加载模块 lsmod | grep my_driver # 查看是否加载成功 sudo rmmod my_driver # 卸载模块