linux 子系统框架

Linux 子系统介绍

常见子系统

常见的子系统主要可以归为一下几类:

1、Misc 杂项设备
2、常见的子系统有IIO 子系统
3、Input 输入子系统
4、RTC 子系统
5、显示子系统
6、MTD 子系统
7、V4L2 与 ASoC 子系统

这里由于难易程度不一样,我们从简单的类型开始介绍

Misc 杂项设备

在linux系统中,每个字符设备都会有自己的设备号 (如串口设备、输入设备),他们被称为标准设备,但是在开发的过程中经常会有一些模块难以归类到标准设备中,而这些设备就是杂项设备

Misc 结构体

这里有两个核心的结构体

struct miscdevice是描述设备信息的,
struct file_operations是表述该设备的操作的,当linux用户层执行某些操作的时候,内核会调用这里绑定的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 用来描述设备的信息,具体的初始化会在probe函数里面进行, 一般定义在总线的dev结构体里面
static struct miscdevice xxx_miscdev = {
.minor = MISC_DYNAMIC_MINOR, // 让内核自动分配次设备号
.name = "xxx_device", // 生成的设备节点名称,例如 /dev/xxx_device
.fops = &xxx_fops, // 绑定刚才写好的操作函数
};

// 用来执行设备的具体操作
static const struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
.open = xxx_open,
.unlocked_ioctl = xxx_ioctl,
.release = xxx_release,
};

struct file_operations结构体中对应的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//当应用层调用 open() 时,调用这个函数
static int xxx_open(struct inode *inode, struct file *file) {
/*
container_of可以反向推出父指针
file->private_data:这个一般是固定的,他是包含了 用户定义的dev结构体中struct miscdevice数据的地址
struct xxx_dev:户定义的dev结构体,也是要反推的那个结构体
miscdev:户定义的dev结构体中struct miscdevice的变量在dev结构体中的变量名
*/
struct xxx_dev *dev = container_of(file->private_data, struct xxx_dev, miscdev);
file->private_data = dev; //将设备结构体给到file->private_data变量,方便我们后续直接获取dev

// 可以在这里进行硬件的唤醒、上电等初始化操作,可以根据操作手册
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 应用层调用write时调用
static ssize_t xxx_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
struct xxx_dev *dev = file->private_data;

// 自己定义一个接收的数组
char data_buf[64];
// 限制写入长度,防止数组越界
if (count > sizeof(data_buf)) {
count = sizeof(data_buf);
}

// 将用户空间的数据拷贝到内核空间
if (copy_from_user(data_buf, buf, count)) {
return -EFAULT;
}

// 解析 data_buf 中的数据,例如通过 SPI/I2C 发送自定义的数据帧到硬件, reg是要写入的寄存器地址
// i2c写入
i2c_smbus_write_byte_data(dev->i2c, reg, data_buf[0]);

// spi写入
spi_write_reg(dev->spi, reg, data_buf[0]);

return count;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 应用层调用ioctl时调用
static long xxx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
struct xxx_dev *dev = file->private_data;
// 最常用的接口,用于向硬件发送控制指令
switch (cmd) {
case 0x01: // 宏定义:例如 MPU6050_RESET
// 执行硬件复位操作
break;
case 0x02: // 宏定义:例如 MPU6050_WAKEUP
// 执行唤醒操作
break;
default:
return -EINVAL; // 无效的命令参数
}
return 0;
}
1
2
3
4
5
// 应用成调用close时调用
static int xxx_release(struct inode *inode, struct file *file) {
// 硬件的休眠、断电操作
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static ssize_t xxx_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){
struct xxx_dev *dev = file->private_data; //获取dev结构体

// 这里用来操作i2c 或者 spi总线进行具体的读取操作
// i2c读取
u8 valueA = i2c_smbus_read_byte_data(dev->i2c, reg);

// spi读取
u8 valueB = 0;
int ret = spi_read_reg(dev->spi, reg, &valueB);
if (ret < 0) {
return ret;
}

char data[64];
int len = snprintf(data, sizeof(data), "valueA: %d, valueB: %d\n", valueA, valueB);

//将读取到的数据传递到用户层
if(copy_to_user(buf, data, len)){
return -EFAULT;
}
return len;
}

probe函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int xxx_probe(/*参数内容根据总结结构填写*/)
{
// 获取根据总线类型获取对应的dev结构体指针

//出很适合杂项结构体
dev->miscdev.minor = MISC_DYNAMIC_MINOR; // 自动获取设备号
dev->miscdev.name = devm_kasprintf(&client->dev, GFP_KERNEL, "xxx-%d-%02X", client->adapter->nr, client->addr);
dev->miscdev.fops = &xxx_fops;

// 其他初始化代码

// 注册杂项设备
ret = misc_register(&dev->miscdev);
if(ret < 0){
printk("xxx 设备注册失败!\n");
return ret;
}

// 其他初始化代码
}

remove函数

1
2
3
4
5
6
7
8
9
10
11
12
static int xxx_remove(/*参数内容根据总结结构填写*/){
// 获取根据总线类型获取对应的dev结构体指针

// 其他代码

// 取消注册杂项设备
misc_deregister(&dev->miscdev);
printk("xxx 设备正在被移除!正在执行 remove 函数...\n");

// 其他代码
return 0;
}

总结

其实从代码结构来看SPI 和 I2C驱动的主体结构体是相互对应,主要是它们所操作的对应的硬件的不同,需要构建相应的硬件操作对象(为了解耦)。这里是一个复杂点,关于操作的硬件对象我们在下一章节介绍


linux 子系统框架
https://codechangeworld.pages.dev/2026/08/10/子系统Misc/
作者
子非鱼
发布于
2026年8月10日
许可协议