前言
实验课要求要编写字串符类型的Linux内核驱动。在这里记录下相关流程。
源码
先给实验编写出来的Linux内核模块源码
vser.c 文件:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
static struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
kfifo_to_user(&vsfifo, buf, count, &copied);
return copied;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
kfifo_from_user(&vsfifo, buf, count, &copied);
return copied;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vsdev, &vser_ops);
vsdev.owner = THIS_MODULE;
ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
return 0;
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
cdev_del(&vsdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");
Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/farsight/fs4412/linux-3.14.25-fs4412
ROOTFS ?= /nfs/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
obj-m := vser.o
endif
将两个文件放在一起。
编译
在两个文件所在的目录 执行make命令。如果没有报错,当前文件夹下面会生成vser.ko文件
加载驱动模块
执行lnsmode vser.ko加载内核模块。然后执行 lsmod | grep vser 查看是否加载成功。

创建设备节点
我们刚刚写的内核模块默认不会自动创建设备节点,我们需要手动创建下设备树节点。
sudo mknod /dev/vser c 256 0
sudo chmod 666 /dev/vser
可以看到 此时 /dev 目录下面多出了一个 vser文件(Linux下一切皆文件)

测试内核模块
执行下述命令
写入数据命令:
echo "hello linux" > /dev/vser
读取数据命令
cat /dev/vser
可以看到执行完上述命令 终端输出了 hello linux字样。输出了则代字串符类型的内核模块加载成功。

移除内核模块
别忘记移除内核模块
sudo rmmod vser
后记:一个小小的改进
刚刚的实验用的代码需要我们手动创建设备节点,但是实际上是可以自动化创建设备节点。
下面是改进版本的vser.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/device.h>
#include <linux/version.h>
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo, char, 32);
static struct class *vser_class;
static struct device *vser_device;
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
int ret;
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
if (ret)
return ret;
return copied;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
int ret;
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
if (ret)
return ret;
return copied;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vsdev, &vser_ops);
vsdev.owner = THIS_MODULE;
ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
vser_class = class_create(VSER_DEV_NAME);
#else
vser_class = class_create(THIS_MODULE, VSER_DEV_NAME);
#endif
if (IS_ERR(vser_class)) {
ret = PTR_ERR(vser_class);
goto class_err;
}
vser_device = device_create(vser_class, NULL, dev, NULL, VSER_DEV_NAME);
if (IS_ERR(vser_device)) {
ret = PTR_ERR(vser_device);
goto device_err;
}
printk(KERN_INFO "vser: module loaded, /dev/%s created\n", VSER_DEV_NAME);
return 0;
device_err:
class_destroy(vser_class);
class_err:
cdev_del(&vsdev);
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
device_destroy(vser_class, dev);
class_destroy(vser_class);
cdev_del(&vsdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
printk(KERN_INFO "vser: module unloaded, /dev/%s removed\n", VSER_DEV_NAME);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");
替换文件后,清理垃圾文件并重新编译
make clean
make
然后,重新加载模块,但是这个版本不需要我们手动创建设备节点。加载模块后/dev 目录下已经自动生成vser文件了。