设备与模块
设备类型
主要设备类型
在Linux系统中,设备被抽象为文件,通过文件系统进行管理。根据设备的功能和特性,主要分为以下三种类型:
1. 字符设备(Character Device)
- 定义:以字符流(字节流)的形式传输数据,数据的读取和写入是顺序的、无缓冲的,不支持随机访问。
- 特点:
- 数据传输实时性强,常用于单次少量数据交互的场景。
- 不支持通过文件系统的
lseek
命令随机定位读写位置。
- 常见设备:
- 键盘、鼠标、串口(如
/dev/ttyS0
)、终端(如/dev/tty1
)、打印机等。 - 部分传感器设备(如温度传感器、串口通信设备)。
- 键盘、鼠标、串口(如
- 设备文件标识:
- 在
/dev
目录下,设备文件的类型标识为c
(通过ls -l
命令查看)。 - 示例:
crw-rw-r-- 1 root root 1, 3 May 23 08:00 /dev/null
(c
表示字符设备)。
- 在
2. 块设备(Block Device)
- 定义:以数据块(Block,如4KB为一个块)为单位传输数据,支持随机访问(可通过块地址直接定位数据),通常有数据缓冲机制。
- 特点:
- 适合大批量数据的快速读写,如磁盘存储。
- 支持文件系统的创建(如EXT4、XFS等),数据可以按文件形式组织。
- 常见设备:
- 硬盘(如
/dev/sda
、/dev/nvme0n1
)、固态硬盘(SSD)、U盘、光驱、SD卡等。 - 分区设备(如
/dev/sda1
、/dev/sdb2
)。
- 硬盘(如
- 设备文件标识:
- 在
/dev
目录下,设备文件的类型标识为b
。 - 示例:
brw-rw---- 1 root disk 8, 0 May 23 08:00 /dev/sda
(b
表示块设备)。
- 在
3. 网络设备(Network Device)
- 定义:用于网络通信的设备,通过网络协议(如TCP/IP)传输数据,不直接对应文件系统中的文件,而是通过网络接口进行管理。
- 特点:
- 数据传输基于网络协议,支持双向通信和数据包的收发。
- 通常不通过
/dev
目录下的文件访问,而是通过系统命令(如ifconfig
、ip
)或网络编程接口(如Socket)操作。
- 常见设备:
- 以太网网卡(如
eth0
、enp0s3
)、无线网卡(如wlan0
)、虚拟网络设备(如docker0
、lo
回环接口)。
- 以太网网卡(如
- 设备文件标识:
- 没有对应的常规设备文件,可通过
/sys/class/net/
目录查看网络接口信息。 - 示例:通过
ls /sys/class/net/
命令查看当前系统的网络设备名称。
- 没有对应的常规设备文件,可通过
补充说明:伪设备(虚拟设备)
除上述三类真实硬件设备外,Linux中还有一类伪设备(虚拟设备),用于实现系统功能或模拟硬件行为:
- 示例:
- **
/dev/null
**:空设备,丢弃所有写入的数据,常用于日志重定向(如command > /dev/null
)。 - **
/dev/zero
**:零设备,读取时返回无限的空字节(\0
),用于填充数据或测试。 - **
/dev/loop
**:回环设备,用于将文件模拟为块设备(如挂载ISO镜像)。 - 管道(Pipe)和套接字(Socket):用于进程间通信(IPC),在
/dev
目录下无实体文件,通过内存或特殊文件系统(如tmpfs
)实现。
- **
设备管理的核心概念
- 设备文件与驱动程序:Linux通过设备驱动程序连接硬件设备和操作系统,每个设备文件对应一个驱动程序,用户通过操作设备文件(如读写)触发驱动程序完成硬件交互。
- 主设备号与次设备号:
- 每个设备文件通过主设备号(Major Number)和次设备号(Minor Number)唯一标识。
- 主设备号标识设备类型或驱动程序,次设备号标识同一类型中的具体设备实例(如同一硬盘的不同分区)。
通过将设备抽象为文件,Linux实现了“一切皆文件”的设计哲学,简化了用户对硬件的操作和管理。
模块概念
模块
在 Linux 系统中,模块(Module) 是一种可以动态加载和卸载的代码片段,用于扩展内核功能而无需重新编译整个内核。这种机制使得 Linux 内核具有高度的灵活性和可扩展性,尤其适用于硬件驱动、文件系统、网络协议等需要动态支持的场景。
一、模块的基本概念
1. 模块的作用
- 动态扩展内核功能:例如为新硬件添加驱动程序,或支持新的文件系统(如 NTFS)、网络协议等。
- 减少内核体积:常用功能编译进内核,不常用功能以模块形式存在,按需加载,节省内存和资源。
- 热插拔支持:配合硬件热插拔(如 USB 设备),动态加载/卸载模块,提升系统灵活性。
2. 模块与内核的关系
- 模块运行在内核空间,拥有和内核相同的权限,可以直接访问硬件和内核数据结构。
- 模块依赖于内核提供的符号表(Symbol Table),加载时需与当前内核版本兼容(内核版本、架构必须一致)。
二、模块的分类
根据功能不同,模块主要分为以下几类:
1. 设备驱动模块
- 作用:为硬件设备提供驱动支持,如显卡驱动(NVIDIA/AMD 闭源驱动)、网卡驱动、存储设备驱动等。
- 示例:
nvidia.ko
:NVIDIA 显卡驱动模块。r8168.ko
:Realtek 8168 网卡驱动模块。
2. 文件系统模块
- 作用:支持不同的文件系统格式,使内核能够读写对应格式的存储设备。
- 示例:
ntfs.ko
:NTFS 文件系统支持模块(用于访问 Windows 分区)。exfat.ko
:EXFAT 文件系统支持模块(常用于U盘、SD卡)。
3. 网络协议模块
- 作用:扩展网络功能,如虚拟专用网络(VPN)、网络过滤等。
- 示例:
ip_tables.ko
:iptables 防火墙核心模块。openvpn.ko
:OpenVPN 虚拟网络模块(需配合用户空间程序使用)。
4. 系统功能模块
- 作用:增强内核本身的功能,如内存管理、进程调度等。
- 示例:
overlay.ko
:OverlayFS 分层文件系统模块(常用于 Docker 容器)。dm-mod.ko
:Device Mapper 模块(用于逻辑卷管理 LVM、磁盘加密等)。
三、模块的相关文件与目录
1. 模块文件的位置
- 默认存放路径:
- 已安装的模块存放在
/lib/modules/$(uname -r)/
目录下,按内核版本分类。 - 临时模块可存放在
/tmp
或自定义目录(需确保权限)。
- 已安装的模块存放在
- 文件扩展名:模块文件通常为
.ko
(Kernel Object)格式,本质是 ELF 可执行文件。
2. 关键系统文件
- **
/proc/modules
**:记录当前已加载的模块列表,包含模块名、大小、引用计数等信息。1
2
3# 示例输出
nvidia_drm 65536 2 - Live 0xffffffffc0340000
nvidia_modeset 1392640 2 nvidia_drm, Live 0xffffffffc0040000 - **
/sys/module/
**:以目录形式展示已加载模块的详细信息(如参数、依赖关系),可通过文件读取或写入模块参数。 - **
/etc/modules
**:系统启动时自动加载的模块列表(传统配置文件,部分系统使用/etc/modules-load.d/
目录下的.conf
文件)。
四、模块的管理命令
1. 加载模块(modprobe
/insmod
)
- **
modprobe
**(推荐):- 自动解决模块依赖关系(如先加载依赖模块)。
- 语法:
modprobe [模块名] [参数=值...]
- 示例:加载 NTFS 文件系统模块:
1
sudo modprobe ntfs
- **
insmod
**:- 直接加载模块文件,不处理依赖,需手动指定完整路径。
- 语法:
insmod /path/to/module.ko [参数=值...]
- 示例:
1
sudo insmod /lib/modules/5.15.0-72-generic/kernel/fs/ntfs/ntfs.ko
2. 卸载模块(rmmod
/modprobe -r
)
- **
rmmod
**:- 直接卸载模块,若模块被其他进程或模块依赖,则卸载失败。
- 语法:
rmmod [模块名]
- **
modprobe -r
**(推荐):- 自动递归卸载无依赖的模块(先卸载依赖它的模块)。
- 语法:
modprobe -r [模块名]
- 示例:卸载 NTFS 模块:
1
sudo modprobe -r ntfs
3. 查看模块信息
- **
lsmod
**:列出当前已加载的模块及其依赖关系。1
2
3
4# 示例输出
Module Size Used by
nvidia_drm 65536 2
nvidia_modeset 1392640 1 nvidia_drm - **
modinfo
**:查看模块文件的详细信息(作者、描述、依赖、参数等)。1
2
3
4
5
6
7modinfo ntfs
# 输出示例:
filename: /lib/modules/5.15.0-72-generic/kernel/fs/ntfs/ntfs.ko
description: NTFS filesystem driver
author: Anton Altaparmakov, Richard Russel, Etienne Carriere,
Christoph Hellwig, Paul Mackerras
license: GPL
4. 配置模块自动加载
- 传统方法(
/etc/modules
):
在文件中每行添加一个模块名,系统启动时自动加载。1
2
3
4sudo nano /etc/modules
# 添加内容(示例):
ntfs
overlay - 现代方法(
/etc/modules-load.d/*.conf
):
在/etc/modules-load.d/
目录下创建.conf
文件,每行写入模块名。1
2
3
4sudo nano /etc/modules-load.d/custom-modules.conf
# 添加内容:
ntfs
overlay
五、模块的依赖与符号表
1. 依赖关系
- 模块可能依赖其他模块(如
nvidia_modeset
依赖nvidia_drm
),加载时需按顺序加载。 modprobe
会自动读取/lib/modules/$(uname -r)/modules.dep
文件,处理依赖关系。
2. 符号表(Symbol Table)
- 内核和模块通过符号表共享函数和变量(如内核导出的
printk
函数)。 - 模块加载时,需检查内核符号表是否包含所需符号(版本不兼容会导致加载失败)。
- 可通过
nm
命令查看模块或内核中的符号:1
nm -D /lib/modules/$(uname -r)/kernel/kernel/module.ko | grep printk
六、编译与安装自定义模块
1. 准备工作
- 安装内核开发工具:
1
2
3
4
5# Debian/Ubuntu
sudo apt install linux-headers-$(uname -r) build-essential
# CentOS/RHEL
sudo yum install kernel-devel kernel-headers
2. 编写模块代码(示例:简单 Hello World 模块)
1 |
|
3. 编写 Makefile
1 | obj-m += hello.o |
4. 编译与安装
1 | make |
七、常见问题与注意事项
模块版本兼容性:
- 模块必须与当前内核的版本、架构完全一致(可通过
uname -r
查看内核版本)。 - 升级内核后,旧模块可能无法使用,需重新编译或安装对应版本的模块。
- 模块必须与当前内核的版本、架构完全一致(可通过
权限问题:
- 加载/卸载模块需
root
权限(使用sudo
)。
- 加载/卸载模块需
依赖冲突:
- 若模块依赖已加载的其他模块,需先卸载依赖模块(或使用
modprobe -r
自动处理)。
- 若模块依赖已加载的其他模块,需先卸载依赖模块(或使用
闭源模块的风险:
- 部分硬件厂商提供的闭源模块(如 NVIDIA 驱动)可能与内核更新不兼容,需谨慎处理。
通过模块机制,Linux 内核实现了“核心精简、功能可扩展”的设计目标,用户可根据需求动态调整系统功能,这也是 Linux 生态灵活性和强大适配能力的重要体现。
模块退出和调用
退出和调用必须符合my_init和my_exit。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 naro!