写一个内核驱动示例

简单的Linux内核驱动示例

下面是一个基本的Linux字符设备驱动示例,它创建了一个简单的 /dev/hello 设备节点,可以实现基本的读写操作。

示例代码:hello.c

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEVICE_NAME "hello"
#define CLASS_NAME "hello"

static int major;
static struct class *hello_class = NULL;
static struct cdev hello_cdev;

// 设备缓冲区
static char msg[100] = {0};
static int msg_len = 0;

// 打开设备
static int hello_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Hello device opened\n");
return 0;
}

// 释放设备
static int hello_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Hello device closed\n");
return 0;
}

// 读取设备
static ssize_t hello_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
int retval = 0;

if (*off > 0)
return 0;

if (copy_to_user(buf, msg, msg_len)) {
retval = -EFAULT;
goto out;
}

*off += msg_len;
retval = msg_len;

out:
return retval;
}

// 写入设备
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
if (len > sizeof(msg) - 1)
return -EINVAL;

if (copy_from_user(msg, buf, len))
return -EFAULT;

msg_len = len;
msg[msg_len] = '\0'; // 确保字符串以null结尾

printk(KERN_INFO "Received %zu characters from user\n", len);
return len;
}

// 文件操作结构体
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};

// 模块初始化
static int __init hello_init(void)
{
dev_t dev = 0;
int ret;

// 动态分配主设备号
ret = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Failed to allocate char device region\n");
return ret;
}

major = MAJOR(dev);
printk(KERN_INFO "Hello module loaded with major number %d\n", major);

// 初始化cdev结构并添加到内核
cdev_init(&hello_cdev, &hello_fops);
hello_cdev.owner = THIS_MODULE;

ret = cdev_add(&hello_cdev, dev, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add cdev\n");
unregister_chrdev_region(dev, 1);
return ret;
}

// 在/sys/class下创建设备类
hello_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(hello_class)) {
printk(KERN_ERR "Failed to create device class\n");
cdev_del(&hello_cdev);
unregister_chrdev_region(dev, 1);
return PTR_ERR(hello_class);
}

// 在/dev下创建设备节点
device_create(hello_class, NULL, dev, NULL, DEVICE_NAME);

return 0;
}

// 模块退出
static void __exit hello_exit(void)
{
dev_t dev = MKDEV(major, 0);

// 删除设备节点和类
device_destroy(hello_class, dev);
class_destroy(hello_class);

// 删除cdev并释放设备号
cdev_del(&hello_cdev);
unregister_chrdev_region(dev, 1);

printk(KERN_INFO "Hello module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Linux char driver");
MODULE_VERSION("0.1");

配套的Makefile

1
2
3
4
5
6
7
8
9
obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

使用说明

  1. 将上述代码保存为 hello.cMakefile
  2. 编译模块:
    1
    make
  3. 加载模块:
    1
    sudo insmod hello.ko
  4. 检查设备:
    1
    2
    dmesg | tail  # 查看内核日志
    ls -l /dev/hello # 检查设备节点
  5. 测试设备:
    1
    2
    echo "test" > /dev/hello
    cat /dev/hello
  6. 卸载模块:
    1
    sudo rmmod hello

功能说明

  • 创建一个 /dev/hello 字符设备
  • 支持基本的 open, release, read, write 操作
  • 写入设备的数据会被存储,并在读取时返回
  • 使用动态设备号分配
  • 自动创建设备节点

这只是一个基础示例,实际的内核驱动开发需要考虑更多的错误处理、同步机制和安全问题。