Linux驱动开发 2 - 从零实现内核模块
·
目录
前言
在Linux内核开发中,设备驱动是连接硬件与操作系统的关键桥梁。本文将介绍一个简单的字符设备驱动示例。
一、内核模块代码解析
1.1 模块初始化与退出
#include <linux/module.h>
#include <linux/fs.h>
static int __init demo_init(void)
{
printk("demo init\\n");
register_chrdev(0, "chardev_demo", &demo_fops); // 动态分配主设备号
return 0;
}
static void __exit demo_exit(void)
{
printk("demo exit\\n");
unregister_chrdev(0, "chardev_demo");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
- module_init/exit:定义模块加载/卸载时的入口函数
- register_chrdev:注册字符设备,参数0表示动态分配主设备号
- printk:内核日志输出函数
1.2 文件操作接口实现
static int demo_open(struct inode *inode, struct file *file)
{
printk("demo open\\n");
return 0;
}
static int demo_release(struct inode *inode, struct file *file)
{
printk("demo release\\n");
return 0;
}
static struct file_operations demo_fops = {
.open = demo_open,
.release = demo_release,
.owner = THIS_MODULE,
};
- file_operations:定义设备文件支持的操作
- 当前实现基本的打开/关闭功能,可扩展
read/write/ioctl等方法
二、设备文件创建
2.1 手动创建(不推荐)
# 假设主设备号为250,次设备号为0
sudo mknod /dev/my_device c 250 0
2.2 自动创建(推荐)
在驱动初始化代码中添加:
static struct class *my_class;
static dev_t dev_id;
// 在demo_init中
my_class = class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, dev_id, NULL, "my_device");
- class_create:创建设备类
- device_create:自动在/dev下生成设备节点
- 通过
udev机制动态管理设备文件
三、驱动与设备关联方式
3.1 传统字符设备注册
major = register_chrdev(0, "my_device", &fops);
dev_id = MKDEV(major, 0); // 组合设备号
- 适用于简单设备,需手动或自动分配主设备号
3.2 设备树 + Platform驱动(嵌入式首选)
设备树定义:
my_device@1234 {
compatible = "my-vendor,my-device";
reg = <0x1234 0x100>;
};
驱动匹配:
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "my-vendor,my-device" },
{}
};
3.3 Platform设备 + 驱动(非设备树系统)
// Platform设备注册
static struct platform_device my_device = {
.name = "my-device-name",
};
platform_device_register(&my_device);
// Platform驱动注册
static struct platform_driver my_driver = {
.probe = my_probe,
.driver = { .name = "my-device-name" },
};
platform_driver_register(&my_driver);
四、编译与测试
4.1 Makefile示例
obj-m += char_demo.o
KDIR := /path/to/linux-source
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
4.2 操作步骤
make # 编译模块
sudo insmod demo.ko # 加载模块
dmesg | tail # 查看内核日志
ls /dev/my_device # 验证设备文件
sudo rmmod demo # 卸载模块
五、验证与调试
5.1 关键验证命令
5.2 常见问题排查
- 权限问题:使用
sudo加载模块,检查/dev节点权限 - 版本兼容:确保内核头文件与运行内核版本一致
- 日志追踪:通过
dmesg -w实时监控内核消息
六、完整示例(Misc Dev)
GitHub - soha1991/linux_learn: My linux learning project, including driver, userspace app, etc.
driver code:
#include "linux/device.h"
#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/gfp_types.h"
#include "linux/miscdevice.h"
#include "linux/mutex.h"
#include "linux/pagemap.h"
#include "linux/printk.h"
#include "linux/slab.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#define DEMO_DEV_NAME "demo_device"
#define STATUS_READY 0
#define STATUS_BUSY 1
#define IOCTL_MAGIC 'V'
#define IOCTL_SET_MODE _IOW(IOCTL_MAGIC, 1, int)
#define IOCTL_GET_STATUS _IOR(IOCTL_MAGIC, 1, int)
struct demo_device {
char *dma_buffer; // DMA模拟缓冲区
size_t buffer_size; // 缓冲区大小
int mode; // 工作模式
atomic_t status; // 设备状态
struct mutex lock; // 互斥锁
};
static struct demo_device *vdev;
static int demo_open (struct inode *, struct file *);
static int demo_release (struct inode *, struct file *);
// static int demo_mmap (struct file *, struct vm_area_struct *);
// static long demo_ioctl (struct file *, unsigned int, unsigned long);
static int demo_open (struct inode * inode, struct file * filp)
{
printk("demo open\n");
if (!try_module_get (THIS_MODULE))
{
return -EBUSY;
}
atomic_set(&vdev->status, STATUS_BUSY);
filp->private_data = vdev;
return 0;
}
static int demo_release (struct inode * inode, struct file * filp)
{
printk("demo release\n");
module_put (THIS_MODULE);
atomic_set(&vdev->status, STATUS_READY);
return 0;
}
static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
struct demo_device *dev = (struct demo_device *)filp->private_data;
int ret = 0;
printk("demo_read pos %lld\n",*pos);
mutex_lock (&dev->lock);
if (*pos >= dev->buffer_size)
{
ret = 0;
goto out;
}
if (*pos + count > dev->buffer_size)
{
count = dev->buffer_size - *pos;
}
if (copy_to_user(buf, dev->dma_buffer + *pos, count))
{
ret = -EFAULT;
goto out;
}
*pos += count;
ret = count;
out:
mutex_unlock (&dev->lock);
return ret;
}
static ssize_t demo_write (struct file *filp, const char __user * buf, size_t count, loff_t *pos)
{
struct demo_device *dev = (struct demo_device *)filp->private_data;
int ret = 0;
printk("demo_write pos %lld\n",*pos);
mutex_lock (&dev->lock);
if (*pos >= dev->buffer_size)
{
ret = -ENOSPC;
goto out;
}
if (*pos + count > dev->buffer_size)
{
count = dev->buffer_size - *pos;
}
if (copy_from_user(dev->dma_buffer + *pos, buf, count))
{
ret = -EFAULT;
goto out;
}
*pos += count;
ret = count;
out:
mutex_unlock(&dev->lock);
return ret;
}
static long demo_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
struct demo_device *dev = filp->private_data;
int tmp;
int ret = 0;
switch (cmd)
{
case IOCTL_SET_MODE:
if (copy_from_user(&tmp, (int __user *)arg, sizeof(int)))
{
return -EFAULT;
}
dev->mode = tmp;
break;
case IOCTL_GET_STATUS:
tmp = atomic_read(&dev->status);
if (copy_to_user((int __user *)arg, &tmp, sizeof(int)))
{
return -EFAULT;
}
break;
default:
printk("Not support cmd 0x%x\n", cmd);
ret = -ENOTTY;
}
return ret;
}
static struct file_operations demo_fops = {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
.owner = THIS_MODULE,
};
static struct miscdevice demo_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_DEV_NAME,
.fops = &demo_fops,
.mode = 0666,
};
static int __init demo_init(void)
{
int ret = 0;
printk("demo init\n");
vdev = (struct demo_device *)kmalloc (sizeof(struct demo_device), GFP_KERNEL);
if (!vdev)
return -ENOMEM;
vdev->buffer_size = 8*1024;
vdev->dma_buffer = (char *)kmalloc (vdev->buffer_size, GFP_KERNEL);
if (!vdev->dma_buffer)
{
ret = -ENOMEM;
goto free_dev;
}
mutex_init(&vdev->lock);
atomic_set(&vdev->status, STATUS_READY);
vdev->mode = 0;
ret = misc_register (&demo_device);
if (ret < 0)
{
printk("misc_register err, %d\n", ret);
ret = -EIO;
goto free_buf;
}
free_buf:
kfree(vdev->dma_buffer);
free_dev:
kfree(vdev);
return ret;
}
static void __exit demo_exit(void)
{
misc_deregister (&demo_device);
kfree (vdev->dma_buffer);
kfree (vdev);
printk("demo exit\n");
}
module_init(demo_init)
module_exit(demo_exit)
MODULE_LICENSE("GPL");
App code
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define DEVICE_PATH "/dev/demo_device"
// 如果驱动支持 ioctl,定义命令(需与内核驱动一致)
#define IOCTL_MAGIC 'V'
#define IOCTL_SET_MODE _IOW(IOCTL_MAGIC, 1, int)
#define IOCTL_GET_STATUS _IOR(IOCTL_MAGIC, 1, int)
int main() {
int fd;
char write_buf[100] = "Hello from userspace!";
char read_buf[100] = {0};
// 1. 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
exit(EXIT_FAILURE);
}
printf("Device opened successfully.\n");
// 2. 向设备写入数据
ssize_t ret = write(fd, write_buf, strlen(write_buf));
if (ret < 0) {
perror("Write failed");
close(fd);
exit(EXIT_FAILURE);
}
close(fd);
printf("Wrote %zd bytes: %s\n", ret, write_buf);
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
exit(EXIT_FAILURE);
}
// 3. 从设备读取数据
ret = read(fd, read_buf, sizeof(read_buf));
if (ret < 0) {
perror("Read failed");
close(fd);
exit(EXIT_FAILURE);
}
printf("Read %zd bytes: %s\n", ret, read_buf);
// 4. 可选:测试 ioctl(如果驱动支持)
int mode = 1;
if (ioctl(fd, IOCTL_SET_MODE, &mode) < 0) {
perror("IOCTL failed");
} else {
printf("IOCTL SET MODE succeeded. Value: %d\n", mode);
}
int status;
if (ioctl(fd, IOCTL_GET_STATUS, &status) < 0) {
perror("IOCTL failed");
} else {
printf("IOCTL GET STATUS succeeded. Value: %d\n", status);
}
// 5. 关闭设备
close(fd);
return 0;
}
Makefile
obj-m += misc_drv.o
KDIR := $(shell pwd)/../../
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
build cmd
cd demo_path
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
cd app_path
aarch64-linux-gnu-gcc -static demo_app.c -o demo_app
更多推荐

所有评论(0)