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错误码是一一对应的
cpp/* 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
cppdev1.class = class_create(THIS_MODULE, "test"); //创建类,名字为test if(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"); //创建设备,名字为test if(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
cpp#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 //初始化cdev cdev_init(&dev1.cdev_test, &cdev_test_fops); dev1.cdev_test.owner = THIS_MODULE; //将owner字段指向本模块,防止模块被卸载 //添加cdev ret = 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"); //创建类,名字为test if(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"); //创建设备,名字为test if(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
bashexport 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
cpp#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"; //读取缓冲区buf fd = 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文件就是之后放在开发板上运行的可执行文件
bash/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内联函数
将函数内联展开,以减少函数调用的开销