1. 项目概述
作为一名长期在Linux环境下工作的开发者,我经常需要将自己开发的驱动程序或工具打包成deb格式,方便在不同机器上部署。deb是Debian系列Linux发行版(如Ubuntu)的标准软件包格式,它不仅能自动处理依赖关系,还能通过apt/dpkg进行集中管理,比手动复制文件要规范得多。
最近我在为一个字符设备驱动项目打包时,踩了不少坑,特别是内核模块与特定内核版本的绑定问题。本文将详细介绍如何从零开始创建一个规范的deb安装包,重点解决以下几个问题:
- 如何正确组织deb包的文件结构
- 如何编写control文件确保内核版本匹配
- 安装/卸载脚本的关键处理逻辑
- 实际构建和验证的完整流程
这个教程适合有一定Linux基础,需要分发自己开发的驱动程序或工具的开发者和系统管理员。即使你之前没有打包经验,按照本文步骤操作也能完成一个可用的deb包。
2. 工具准备与环境检查
2.1 安装必备工具链
在开始之前,我们需要确保系统已安装deb包构建工具链。在Ubuntu/Debian上运行:
bash复制sudo apt update
sudo apt install -y build-essential dh-make debhelper devscripts
这些工具的作用分别是:
build-essential:提供gcc、make等基础编译工具dh-make:debhelper工具集,简化deb包创建debhelper:提供deb构建的辅助脚本devscripts:包含debuild等实用工具
提示:如果是生产环境,建议固定这些工具的版本,避免不同版本间的行为差异。可以用
apt install -y build-essential=12.8ubuntu1.1这样的格式指定版本。
2.2 确认内核版本
内核模块必须与特定内核版本匹配,因此我们需要先确认目标系统的内核版本:
bash复制uname -a
# 示例输出:Linux mypc 5.15.0-136-generic #153-Ubuntu SMP Tue Jan 16 18:15:12 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
关键信息是5.15.0-136-generic,这将是后续步骤中需要多次使用的内核版本号。如果你的驱动需要兼容多个内核版本,需要为每个版本单独构建deb包。
3. 创建deb包目录结构
3.1 基础目录布局
deb包本质上是一个具有特定结构的文件归档。我们首先创建基本目录:
bash复制mkdir -p driver_deb
mkdir -p driver_deb/DEBIAN
mkdir -p driver_deb/lib/modules/$(uname -r)/kernel/drivers/misc/
目录结构说明:
driver_deb/:deb包根目录DEBIAN/:存放控制文件和脚本(必须大写)lib/modules/$(uname -r)/kernel/drivers/misc/:内核模块的标准安装路径
注意:
misc目录是内核为杂项驱动保留的标准位置。如果你的驱动有明确分类(如网络、USB等),应该放到对应的drivers/net/或drivers/usb/子目录下。
3.2 放置驱动文件
将编译好的内核模块(.ko文件)复制到目标位置:
bash复制cp ~/project/linux_driver_study/ubuntu/hello_driver/hello_driver.ko \
./driver_deb/lib/modules/5.15.0-136-generic/kernel/drivers/misc/
这里有几个关键点:
- 路径中的内核版本必须与
uname -r完全一致 - 文件权限应保持为644(默认umask创建即可)
- 确保.ko文件是针对该内核版本编译的
4. 编写DEBIAN/control文件
4.1 control文件基本结构
control文件是deb包的核心元数据文件,位于DEBIAN/control。创建一个新文件并填入以下内容:
plaintext复制Package: driver-kmod
Version: 1.0.0
Section: kernel
Priority: optional
Architecture: amd64
Depends: linux-image-5.15.0-136-generic
Maintainer: yourname@example.com
Description: Pre-built kernel module for MyDevice (hello_driver.ko)
This package installs the pre-compiled kernel module (hello_driver.ko) for the MyDevice hardware. It is built specifically for kernel version 5.15.0-136-generic.
**WARNING:** This driver will stop working if the kernel is upgraded! A new package built against the new kernel version is required.
4.2 关键字段详解
- Package:包名,建议使用
-kmod后缀表示内核模块 - Version:遵循语义化版本规范,如1.0.0
- Depends:最重要的字段,必须精确指定依赖的内核镜像包
- Architecture:必须与模块编译目标架构一致(通过
dpkg --print-architecture查看)
经验之谈:在Depends字段中,除了内核版本,还可以添加其他依赖如
firmware-modules等。但要注意,过多的依赖会增加安装复杂度。
4.3 内核版本处理技巧
为了确保驱动与内核版本严格匹配,我推荐以下做法:
-
在Makefile中自动获取当前内核版本:
makefile复制KVER ?= $(shell uname -r) -
在control文件中使用变量替换(需要配合dh_make使用):
plaintext复制
Depends: linux-image-${kernel:Version} -
或者使用postinst脚本进行运行时检查:
bash复制CURRENT_KVER=$(uname -r) if [ "$CURRENT_KVER" != "5.15.0-136-generic" ]; then echo "ERROR: Kernel version mismatch!" >&2 exit 1 fi
5. 安装与卸载脚本
5.1 postinst安装后脚本
DEBIAN/postinst在包安装完成后执行,主要用于更新模块依赖:
bash复制#!/bin/sh
set -e
# 更新模块依赖关系
/sbin/depmod -a || true
# 可选:自动加载模块(通常不建议)
# /sbin/modprobe hello_driver > /dev/null 2>&1 || true
exit 0
重要提示:
depmod -a是必须的,它生成modules.dep文件让modprobe能解析模块依赖。|| true确保即使出错也不会导致安装失败。
5.2 prerm卸载前脚本
DEBIAN/prerm在卸载前执行,尝试移除已加载的模块:
bash复制#!/bin/sh
set -e
# 检查并卸载模块
if /sbin/lsmod | grep -q '^hello_driver\s'; then
/sbin/rmmod hello_driver || true
fi
exit 0
5.3 postrm卸载后脚本
DEBIAN/postrm在文件删除后执行,清理模块依赖:
bash复制#!/bin/sh
set -e
# 再次更新模块依赖
/sbin/depmod -a || true
exit 0
5.4 设置脚本权限
所有脚本必须具有可执行权限:
bash复制chmod 0755 driver_deb/DEBIAN/postinst
chmod 0755 driver_deb/DEBIAN/postrm
chmod 0755 driver_deb/DEBIAN/prerm
常见问题:如果忘记设置权限,dpkg会静默忽略这些脚本,导致模块未正确注册或卸载。
6. 构建deb包
6.1 使用dpkg-deb构建
完成所有文件准备后,执行构建命令:
bash复制dpkg-deb --build driver_deb/
成功后会生成driver_deb.deb文件。如果想指定输出文件名:
bash复制dpkg-deb --build driver_deb/ mydriver-1.0.0_amd64.deb
6.2 检查包内容
可以使用以下命令检查deb包内容:
bash复制dpkg -c driver_deb.deb # 查看包含的文件
dpkg -I driver_deb.deb # 查看包信息
6.3 高级构建选项
对于更复杂的项目,建议使用debuild工具链:
bash复制cd driver_deb/
debuild -us -uc # 不签名
这会生成符合Debian政策的完整构建,包括changelog等元数据。
7. 安装与验证
7.1 安装deb包
推荐使用apt安装以自动解决依赖:
bash复制sudo apt install ./driver_deb.deb
或者使用dpkg(不自动解决依赖):
bash复制sudo dpkg -i driver_deb.deb
7.2 验证安装
检查文件是否安装到正确位置:
bash复制ls -l /lib/modules/5.15.0-136-generic/kernel/drivers/misc/hello_driver.ko
手动加载测试:
bash复制sudo insmod /lib/modules/5.15.0-136-generic/kernel/drivers/misc/hello_driver.ko
lsmod | grep hello_driver
sudo rmmod hello_driver
7.3 检查包状态
查看已安装的包:
bash复制dpkg -l | grep driver-kmod
8. 卸载与清理
8.1 完全卸载
使用apt或dpkg卸载:
bash复制sudo apt remove driver-kmod
# 或
sudo dpkg -P driver-kmod
8.2 验证卸载
确认文件已移除:
bash复制ls /lib/modules/5.15.0-136-generic/kernel/drivers/misc/hello_driver.ko # 应该不存在
检查模块是否已卸载:
bash复制lsmod | grep hello_driver # 应该无输出
9. 高级主题与问题排查
9.1 多架构支持
如果你的驱动需要支持多种架构(如amd64和arm64),需要:
- 为每个架构单独构建.ko文件
- 创建不同的deb包,Architecture字段分别设为amd64和arm64
- 在仓库中按架构组织包
9.2 DKMS集成
为了避免每次内核升级都重新打包,可以考虑使用DKMS(Dynamic Kernel Module Support)。需要:
- 创建dkms.conf文件
- 在postinst中注册DKMS模块
- 在prerm中注销DKMS模块
9.3 常见问题排查
问题1:安装后模块无法加载,dmesg显示"Invalid module format"
解决:
- 确认编译用的内核头文件与目标系统内核版本完全一致
- 检查Depends字段是否指定了正确内核版本
- 运行
modinfo hello_driver.ko查看vermagic是否匹配
问题2:卸载后模块仍显示在lsmod中
解决:
- 确保prerm脚本正确卸载模块
- 检查是否有其他进程持有模块引用
- 尝试手动
rmmod并检查dmesg输出
问题3:apt安装时报依赖错误
解决:
- 运行
sudo apt -f install修复依赖 - 检查control文件的Depends字段格式是否正确
- 确认依赖包在仓库中可用
10. 实际项目建议
根据我在多个驱动项目中的经验,以下建议可能对你有帮助:
-
版本管理:将deb构建脚本与驱动源码一起纳入版本控制,为每个内核版本创建分支
-
自动化构建:使用CI工具(如GitHub Actions)在代码提交时自动构建deb包
-
签名包:生产环境应对deb包进行GPG签名,增加安全性
-
仓库管理:设置私有APT仓库集中管理内部开发的deb包
-
测试策略:在虚拟机中测试不同内核版本下的安装/卸载流程
-
文档记录:在README中明确记录:
- 支持的内核版本
- 已知兼容性问题
- 升级注意事项