驱动:字符设备驱动注册、读写实操
准备基础的文件
Makefile
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
# KERN_VER = $(shell uname -r)
# KERN_DIR = /lib/modules/$(KERN_VER)/build # 开发板的linux内核的源码树目录
KERN_DIR = /home/aston/workspace/driver/kernelobj-m += module_test.o #要将module_test.c文件编译成一个模块 all:make -C $(KERN_DIR) M=`pwd` modules cp:cp *.ko /home/aston/workspace/nfsdir.PHONY: clean
clean:make -C $(KERN_DIR) M=`pwd` modules clean
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit// 模块安装函数
static int __init chrdev_init(void)
{ printk(KERN_INFO "chrdev_init helloworld init\n");return 0;
}// 模块下载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");
}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
添加注册
module_test.c文件当中添加file_operations结构体实体
// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,
};
补全需要注册的函数
// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}
在chrdev_init里面调用register_chrdev注册
int ret = 0;//注册字符设备驱动ret = register_chrdev(MYMAJOR, MYNAME, &my_fops);if(ret){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "chrdev_init helloworld successs...\n");
修改后的module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>#define MYMAJOR 200
#define MYNAME "mychartest"// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,};// 模块安装函数
static int __init chrdev_init(void)
{ int ret = 0;printk(KERN_INFO "chrdev_init helloworld init\n");//注册字符设备驱动ret = register_chrdev(MYMAJOR, MYNAME, &my_fops);if(ret){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "chrdev_init helloworld successs...\n");return 0;
}// 模块下载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");
}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
编译后安装测试
执行make 进行编译
将文件cp到nfs目录
设备里面进行安装测试,查看日志,检测是否安装正确
注销测试
在chrdev_exit 注销字符设备驱动
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");//注销字符设备驱动unregister_chrdev(MYMAJOR, MYNAME);
}
然后重新编译安装
先进行了卸载,但是安装失败
查看cat /proc/devices,发现还是存在
cat /proc/devices
设备重启,就恢复正常,因为我们进行了注册没有进行注销,所有释放是失败的
重启设备后,200没有了
重新安装,重新卸载就可以了
内核自动分配主设备号
int mymajor;//全局变量static int __init chrdev_init(void)
{ printk(KERN_INFO "chrdev_init helloworld init\n");//注册字符设备驱动//将第一个参数填写0 成功:返回主设备号 失败:返回负数mymajor = register_chrdev(0, MYNAME, &my_fops);if(mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n",mymajor);return 0;
}
应用程序调用驱动
驱动设备文件的创建
使用ls -l命令查看设备文件的设备号
ls -l /dev/ttyS0
使用 mknod 创建设备文件
mknod /dev/设备名 [c|b] 主设备号 次设备号
编写应用程序调用驱动
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define FILE "/dev/mychartest"int main(void)
{int fd = -1;fd = open(FILE, O_RDWR);if(fd < 0){printf("open %s error.\n", FILE);return -1;}printf("open %s seccess...]n", FILE);close(fd);return 0;
}
添加读写接口
复习一下
先安装驱动
insmod module_test.ko
查看
cat /proc/devices
再创建mknod 创建设备文件
mknod /dev/mychartest c 250 0
最后应用层调用
apptest.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define FILE "/dev/mychartest"int main(void)
{int fd = -1;int i = 1000;char buf[100];fd = open(FILE, O_RDWR);if(fd < 0){printf("open %s error.\n", FILE);return -1;}printf("open %s seccess...]\n", FILE);// 写入数据ssize_t written = write(fd, "123456", 6);if (written != 6) {perror("write failed");close(fd);return -1;}lseek(fd, 0, SEEK_SET);ssize_t read_bytes = read(fd, buf, sizeof(buf) - 1); // 预留1字节给终止符if (read_bytes < 0) { perror("read failed");close(fd);return -1;}buf[read_bytes] = '\0'; // 手动添加字符串终止符printf("buf: %s\n", buf);while(i--)close(fd);return 0;
}
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>#define BUFFER_SIZE 100
#define MYMAJOR 200
#define MYNAME "mychartest"int mymajor;
static char buffer[BUFFER_SIZE];// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");size_t bytes_to_read = min(count, (size_t)(BUFFER_SIZE - *f_pos));if (copy_to_user(buf, buffer + *f_pos, bytes_to_read)) {return -EFAULT;}*f_pos += bytes_to_read;return bytes_to_read;
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");size_t bytes_to_write = min(count, (size_t)(BUFFER_SIZE - *f_pos));if (copy_from_user(buffer + *f_pos, buf, bytes_to_write)) {return -EFAULT;}*f_pos += bytes_to_write;return bytes_to_write;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,.write = my_write,.read = my_read,};// 模块安装函数
static int __init chrdev_init(void)
{ printk(KERN_INFO "chrdev_init helloworld init\n");//注册字符设备驱动//将第一个参数填写0 成功:返回主设备号 失败:返回负数mymajor = register_chrdev(0, MYNAME, &my_fops);if(mymajor < 0){printk(KERN_ERR "register_chrdev fail\n");return -EINVAL;}printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n",mymajor);return 0;
}// 模块下载函数
static void __exit chrdev_exit(void)
{printk(KERN_INFO "chrdev_exit helloworld exit\n");//注销字符设备驱动unregister_chrdev(MYMAJOR, MYNAME);
}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
copy_from_user 和 copy_to_user 是 Linux 内核编程中用于在内核空间和用户空间之间安全传输数据的关键函数
copy_from_user
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
作用:从用户空间(from)复制数据到内核空间(to)。
参数:
to:内核空间目标地址(指针)。
from:用户空间源地址(需用 __user 标记)。
n:要复制的字节数。
返回值:
0:成功复制全部数据。
非零值:未成功复制的字节数(表示部分或全部复制失败)。
copy_to_user
作用:从内核空间(from)复制数据到用户空间(to)。
参数:
to:用户空间目标地址(需用 __user 标记)。
from:内核空间源地址(指针)。
n:要复制的字节数。
返回值:
0:成功复制全部数据。
非零值:未成功复制的字节数。
为什么需要专用函数?
内核与用户空间隔离:
内核空间和用户空间有独立的内存地址空间,不能直接通过指针访问。
用户空间指针可能无效(如空指针、已释放的内存),直接访问会导致内核崩溃。
安全机制:
copy_*_user 会检查用户空间指针的有效性,并在访问前将进程切换到用户模式。
若用户空间指针无效,函数会返回错误而非崩溃。