1. 实验环境
在实验本节代码前,准备环境一个ubuntu物理机或者虚拟机,下载并使能交叉编译工具链,然后编译好内核源码,参考《编译内核镜像并在云实验室开发板运行》。
2. 使用mutex保护的字符驱动示例
在你要写代码的目录下用touch新建两个文件,一个源码文件,一个Makefile。如下:
~/linux_driver/04_mutex$ touch 04_virchardev_mutex.c
~/linux_driver/04_mutex$ touch Makefile
~/linux_driver/04_mutex$ vim 04_virchardev_mutex.c
将下面的代码复制到04_virchardev_mutex.c, 如下:
/*
* Copyright 2025 NXP
* SPDX-License-Identifier: GPL-2.0+
*/
1 #include <linux/module.h>
2 #include <linux/types.h>
3 #include <linux/init.h>
4 #include <linux/cdev.h>
5
6 MODULE_LICENSE("GPL");
7 MODULE_AUTHOR("Cloud Lab");
8
9 #define BUF_LEN 0x1000
10 #define VIRTCHARDEV_NAME "virtdev"
11
12 struct virtchardev
13 {
14 dev_t virtdev_id;
15 struct cdev virtdev_cdev;
16 struct class *class;
17 struct device *device;
18 int virtdev_major;
19 int virtdev_minor;
20 struct mutex lock;
21
22 struct cdev cdev;
23 unsigned char mem[BUF_LEN];
24
25 };
26
27 struct virtchardev *virtdev;
28
29
30 int virtdev_open(struct inode *inode, struct file *filp)
31 {
32 filp->private_data = virtdev;
33
34 return 0;
35 }
36
37 int virtdev_release(struct inode *inode, struct file *filp)
38 {
39 return 0;
40 }
41
42 static ssize_t virtdev_read(struct file *filp, char __user *buf,
43 size_t size, loff_t *ppos)
44 {
45 unsigned long p = *ppos;
46 unsigned int count = size;
47 int ret = 0;
48 struct virtchardev *dev = filp->private_data;
49
50 if (p >= BUF_LEN)
51 {
52 printk("*ppos = %d\n", p);
53 return count ? -ENXIO : 0;
54 }
55
56 if (count > BUF_LEN - p)
57 {
58 count = BUF_LEN - p;
59 }
60 mutex_lock(&dev->lock);
61 if (copy_to_user(buf, (void*)(dev->mem + p), count))
62 {
63 ret = - EFAULT;
64 }
65 else
66 {
67 *ppos += count;
68 ret = count;
69
70 printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
71 }
72 mutex_unlock(&dev->lock);
73 return ret;
74 }
75
76 static ssize_t virtdev_write(struct file *filp, const char __user *buf,
77 size_t size, loff_t *ppos)
78 {
79 unsigned long p = *ppos;
80 unsigned int count = size;
81 int ret = 0;
82 void *kbuf = NULL;
83 struct virtchardev *dev = filp->private_data;
84
85 kbuf = kvmalloc(size, GFP_KERNEL);
86 memset(kbuf, 0, count);
87 if (copy_from_user(kbuf, buf, count) != 0){
88 ret = - EFAULT;
89 }
90 mutex_lock(&dev->lock);
91 strscpy(dev->mem, kbuf, (count > BUF_LEN ? BUF_LEN : count));
92 ret = count;
93 printk("write bytes: %zu\n", count);
94 mutex_unlock(&dev->lock);
95 return ret;
96 }
97
98 static const struct file_operations virtdev_fops =
99 {
100 .owner = THIS_MODULE,
101 .read = virtdev_read,
102 .write = virtdev_write,
103 .open = virtdev_open,
104 .release = virtdev_release,
105 };
106
107
108 int virtdev_init(void)
109 {
110 virtdev = kmalloc(sizeof(struct virtchardev), GFP_KERNEL);
111 memset(virtdev, 0, sizeof(struct virtchardev));
112
113 mutex_init(&virtdev->lock);
114 alloc_chrdev_region(&virtdev->virtdev_id, 0, 1,
115 VIRTCHARDEV_NAME );
116 virtdev->virtdev_major = MAJOR(virtdev->virtdev_id);
117
118 cdev_init(&virtdev->virtdev_cdev, &virtdev_fops);
119
120 virtdev->virtdev_cdev.owner = THIS_MODULE;
121 virtdev->virtdev_cdev.ops = &virtdev_fops;
122
123 cdev_add(&virtdev->virtdev_cdev, virtdev->virtdev_id, 1);
124
125 virtdev->class = class_create(THIS_MODULE, VIRTCHARDEV_NAME);
126
127 device_create(virtdev->class, NULL, virtdev->virtdev_id,
128 NULL, VIRTCHARDEV_NAME);
129
130
131 return 0;
132 }
133
134 void virtdev_exit(void)
135 {
136 cdev_del(&virtdev->virtdev_cdev);
137 unregister_chrdev_region(virtdev->virtdev_id, 1);
138
139 device_destroy(virtdev->class, virtdev->virtdev_id);
140 class_destroy(virtdev->class);
141
142 kfree(virtdev);
143
144 }
145
146 module_init(virtdev_init);
147 module_exit(virtdev_exit);
3. 编译
~/linux_driver/04_mutex$ export CROSS_COMPILE=~/toolchain/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
~/linux_driver/04_mutex$ export ARCH=arm64
~/linux_driver/04_mutex$ make
编译完成后当前目录会有如下文件:
04_virchardev_mutex.c 04_virchardev_mutex.mod 04_virchardev_mutex.mod.o Makefile Module.symvers 04_virchardev_mutex.ko 04_virchardev_mutex.mod.c 04_virchardev_mutex.o modules.order
4. 替换云实验室开发板镜像并重新启动
根据《编译内核镜像并在云实验室开发板运行》替换dtb和image,这里仍然需要这一步,因为内核模块在加载时需要和内核编译版本相匹配。
上传并替换云实验室板子的dtb和Image,依次点击用户网页端下面1,2,3的位置,在点击位置2时,选择TFTP,点击3后选择文件~/kernel_build/linux-imx/arch/arm64/boot/dts/freescale/imx8mp-evk.dtb和~/kernel_build/linux-imx/arch/arm64/boot/Image。
上传成功后,点击下方PowerReset EVK按钮,重启开发板。
至此,开发板开始运行自己修改编译的linux镜像。
5. 上传自己编译的字符驱动模块
在3中编译出了关于mutex应用的内核驱动程序04_virchardev_mutex.ko,通过一次点击下图中的1,2,3位置,然后切换到本地对应的文件目录上传。
6. 运行字符驱动模块
执行insmod加载模块,然后cat读取/dev/virtdev是空的,写入welcome(echo welcome > /dev/virtdev)后,再次读取,会显示刚刚写入的内容,log如下: