正点原子RK3568学习日志19- Linux错误处理 字符驱动框架完全体
1. Linux错误处理
内核错误码保存在errno-base.h文件
使用goto语句处理的时候,应该遵循“先进后出”的原则
用了class_create()和device_create()函数,必须使用IS_ERR()函数判断返回的指针是否是有效的,如果是无效的,需要调用PTR_ERR()函数将无效指针转换为错误码,并进行错误码的返回对于任何一个指针来说,必然存在三种情况,一种是合法指针,一种是NULL(也就是空指针),一种是错误指针(也就是无效指针)
错误指针已经指向了64位系统内核空间的最后一页0xfffffffffffff000~0xffffffffffffffff,指针落在这段地址之内,说明是错误的无效指针
错误返回1 IS_ERR 同时该函数返回的错误地址对应一个linux的错误码
如果想知道这个指针是哪个错误码,使用PTR_ERR函数转化。0xfffffffffffff000~0xffffffffffffffff这段地址和Linux错误码是一一对应的
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ #ifndef _ASM_GENERIC_ERRNO_BASE_H #define _ASM_GENERIC_ERRNO_BASE_H#define EPERM 1 /* Operation not permitted */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Argument list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ #define ECHILD 10 /* No child processes */ #define EAGAIN 11 /* Try again */ #define ENOMEM 12 /* Out of memory */ #define EACCES 13 /* Permission denied */ #define EFAULT 14 /* Bad address */ #define ENOTBLK 15 /* Block device required */ #define EBUSY 16 /* Device or resource busy */ #define EEXIST 17 /* File exists */ #define EXDEV 18 /* Cross-device link */ #define ENODEV 19 /* No such device */ #define ENOTDIR 20 /* Not a directory */ #define EISDIR 21 /* Is a directory */ #define EINVAL 22 /* Invalid argument */ #define ENFILE 23 /* File table overflow */ #define EMFILE 24 /* Too many open files */ #define ENOTTY 25 /* Not a typewriter */ #define ETXTBSY 26 /* Text file busy */ #define EFBIG 27 /* File too large */ #define ENOSPC 28 /* No space left on device */ #define ESPIPE 29 /* Illegal seek */ #define EROFS 30 /* Read-only file system */ #define EMLINK 31 /* Too many links */ #define EPIPE 32 /* Broken pipe */ #define EDOM 33 /* Math argument out of domain of func */ #define ERANGE 34 /* Math result not representable */ #endif
dev1.class = class_create(THIS_MODULE, "test"); //创建类,名字为testif(IS_ERR(dev1.class)){ret = PTR_ERR(dev1.class);goto err_class_create;}dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test"); //创建设备,名字为testif(IS_ERR(dev1.device)){ret = PTR_ERR(dev1.device);goto err_device_create;}err_device_create:class_destroy(dev1.class); //删除类 err_class_create:cdev_del(&dev1.cdev_test); //删除cdev err_chr_add:unregister_chrdev_region(dev1.dev_num, 1); //注销设备号 err_chrdev:return ret;
2.实验:12_err Linux错误处理实验
err.c
#include <linux/init.h> //初始化头文件 #include <linux/module.h> //基本模块文件 #include <linux/kdev_t.h> //包含设备号相关宏和函数 #include <linux/cdev.h> //字符设备结构体头文件cdev #include <linux/fs.h> //注册设备节点的文件结构体 #include <linux/uaccess.h> //用户空间和内核空间数据传输头文件struct device_test{dev_t dev_num;int major;int minor;struct cdev cdev_test;struct class *class;struct device *device;char kbuf[32];};struct device_test dev1;//定义设备结构体变量dev1//open函数实现,在那个file结构体找 static int cdev_test_open(struct inode *inode, struct file *file) {file->private_data = &dev1; //将设备结构体变量地址赋值给file的private_data字段printk("THis is cdev_test_open\r\n");return 0; }//read函数,从设备读取数据 static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) {struct device_test *test_dev = (struct device_test *)file->private_data; //获取设备结构体变量地址if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0){printk("copy_to_user is error\n");return -1;}printk("This is cdev_test_read\n");return 0; }//write函数,向设备写入数据 static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) {struct device_test *test_dev = (struct device_test *)file->private_data; //获取设备结构体变量地址if (copy_from_user(test_dev->kbuf, buf, size) != 0){printk("copy_from_user is error\n");return -1;}printk("This is cdev_test_write\n");printk("kbuf = %s\r\n", test_dev->kbuf);return 0; }static int cdev_test_release (struct inode *inode, struct file *file) {printk("This is cdev_test_release\n");return 0; }//设备操作函数 static struct file_operations cdev_test_fops = { //file_operations结构体.owner = THIS_MODULE, //指向本模块.open = cdev_test_open, //指向cdev_test_open函数.read = cdev_test_read, //指向cdev_test_read函数.write = cdev_test_write, //指向cdev_test_write函数.release = cdev_test_release, //指向cdev_test_release函数 };static int __init chr_fops_init(void) {//注册字符驱动int ret; //1.创建设备号//动态申请设备号ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); if(ret < 0) //申请失败{goto err_chrdev;}printk("alloc_chrdev_region is ok\n"); dev1.major = MAJOR(dev1.dev_num); //获取主设备号dev1.minor = MINOR(dev1.dev_num); //获取次设备号printk("major = %d\n", dev1.major); //打印设备号printk("minor = %d\n", dev1.minor); //2.注册字符设备cdev //初始化cdevcdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; //将owner字段指向本模块,防止模块被卸载//添加cdevret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1); //注册字符设备到内核if (ret < 0) //注册失败{goto err_chr_add;}printk("cdev_add is ok\n");//3.创建设备节点//创建类和设备节点dev1.class = class_create(THIS_MODULE, "test"); //创建类,名字为testif(IS_ERR(dev1.class)){ret = PTR_ERR(dev1.class);goto err_class_create;}dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test"); //创建设备,名字为testif(IS_ERR(dev1.device)){ret = PTR_ERR(dev1.device);goto err_device_create;}return 0;err_device_create:class_destroy(dev1.class); //删除类 err_class_create:cdev_del(&dev1.cdev_test); //删除cdev err_chr_add:unregister_chrdev_region(dev1.dev_num, 1); //注销设备号 err_chrdev:return ret;}static void __exit chr_fops_exit(void) {device_destroy(dev1.class, dev1.dev_num); //删除设备节点class_destroy(dev1.class); //删除类 cdev_del(&dev1.cdev_test); //注销字符设备 unregister_chrdev_region(dev1.dev_num, 1); //释放设备号 }module_init(chr_fops_init); module_exit(chr_fops_exit); MODULE_LICENSE("GPL v2"); //声明模块许可证 MODULE_AUTHOR("AFANFAN"); //声明模块作者
Makefile
export ARCH=arm64#设置平台架构 export CROSS_COMPILE=/opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-#交叉编译器前缀 obj-m += err.o #此处要和你的驱动源文件同名 KDIR :=/home/alientek/rk3568_linux5.10_sdk/kernel #这里是你的内核目录 PWD ?= $(shell pwd) all:make -C $(KDIR) M=$(PWD) modules #make操作 clean:make -C $(KDIR) M=$(PWD) clean #make clean操作
app.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>int main(int argc, char *argv[]) {int fd; //文件描述符char buf1[32] = "nihao,afanfan"; //读取缓冲区buffd = open("dev/test", O_RDWR); //以读写方式打开设备文件if(fd < 0) //打开失败{perror("open file error\n");return -1;}write(fd, buf1, sizeof(buf1)); //将buf2缓冲区的数据写入到dev/test设备文件中close(fd); //关闭设备文件 对取消文件描述符到文件的映射return 0; }///opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-gcc -o app app.c -static
驱动编译成驱动模块ko
使用命令“make”进行驱动的编译,编译完生成 err.ko目标文件
编译测试程序app
生成的app文件就是之后放在开发板上运行的可执行文件
/opt/atk-dlrk3568-5_10_sdk-toolchain/bin/aarch64-buildroot-linux-gnu-gcc -o app app.c -static运行驱动编译成模块insmod
开发板启动之后, insmod err.ko
运行测试程序 ./app
3.问题:
1.perror
perror是 C 语言标准库中的一个函数,用于打印系统错误信息。它的名字是 "print error" 的缩写。当系统调用或库函数失败时,它会设置一个全局变量 errno来指示具体的错误原因,而 perror就是用来将这个错误代码转换成可读的错误描述信息
2.正确的 chr_fops_exit 函数正确的顺序应该是您初始化顺序的严格逆序。
初始化顺序是:
alloc_chrdev_region (申请设备号)
cdev_init / cdev_add (注册字符设备)
class_create (创建类)
device_create (创建设备节点)
因此,正确的释放顺序应该是:
device_destroy (销毁设备节点)
class_destroy (销毁类)
cdev_del (注销字符设备)
unregister_chrdev_region (释放设备号)
3.inline内联函数将函数内联展开,以减少函数调用的开销
