设备类型

主要设备类型

在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/nullc表示字符设备)。

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/sdab表示块设备)。

3. 网络设备(Network Device)

  • 定义:用于网络通信的设备,通过网络协议(如TCP/IP)传输数据,不直接对应文件系统中的文件,而是通过网络接口进行管理。
  • 特点
    • 数据传输基于网络协议,支持双向通信和数据包的收发。
    • 通常不通过/dev目录下的文件访问,而是通过系统命令(如ifconfigip)或网络编程接口(如Socket)操作。
  • 常见设备
    • 以太网网卡(如eth0enp0s3)、无线网卡(如wlan0)、虚拟网络设备(如docker0lo回环接口)。
  • 设备文件标识
    • 没有对应的常规设备文件,可通过/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
    7
    modinfo 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
    4
    sudo nano /etc/modules
    # 添加内容(示例):
    ntfs
    overlay
  • 现代方法(/etc/modules-load.d/*.conf
    /etc/modules-load.d/ 目录下创建 .conf 文件,每行写入模块名。
    1
    2
    3
    4
    sudo 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hello World Module");
MODULE_AUTHOR("Your Name");

static int __init hello_init(void) {
printk(KERN_INFO "Hello, Linux kernel!\n");
return 0;
}

static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, Linux kernel!\n");
}

module_init(hello_init);
module_exit(hello_exit);

3. 编写 Makefile

1
2
3
4
5
6
7
obj-m += hello.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

4. 编译与安装

1
2
3
4
make
sudo insmod hello.ko # 加载模块
dmesg | tail # 查看模块输出的日志(printk 输出到内核日志)
sudo rmmod hello # 卸载模块

七、常见问题与注意事项

  1. 模块版本兼容性

    • 模块必须与当前内核的版本、架构完全一致(可通过 uname -r 查看内核版本)。
    • 升级内核后,旧模块可能无法使用,需重新编译或安装对应版本的模块。
  2. 权限问题

    • 加载/卸载模块需 root 权限(使用 sudo)。
  3. 依赖冲突

    • 若模块依赖已加载的其他模块,需先卸载依赖模块(或使用 modprobe -r 自动处理)。
  4. 闭源模块的风险

    • 部分硬件厂商提供的闭源模块(如 NVIDIA 驱动)可能与内核更新不兼容,需谨慎处理。

通过模块机制,Linux 内核实现了“核心精简、功能可扩展”的设计目标,用户可根据需求动态调整系统功能,这也是 Linux 生态灵活性和强大适配能力的重要体现。

模块退出和调用

退出和调用必须符合my_init和my_exit。