正点原子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错误码是一一对应的

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
cpp 复制代码
    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;
    }



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

bash 复制代码
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

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内联函数

将函数内联展开,以减少函数调用的开销

相关推荐
如果是君3 小时前
【git使用】ubuntu下利用git工具提交一个工程
linux·git·ubuntu
橘颂TA3 小时前
【Linux】 层层递进,抽丝剥茧:调度队列、命令行参数、环境变量
linux·运维·服务器·c/c++
蒙奇D索大3 小时前
【计算机网络】408计算机网络高分指南:物理层编码与调制技术精讲
java·前端·学习·计算机网络
姝孟3 小时前
C++学习——类与对象详细知识点总结
c++·笔记·学习
im_AMBER3 小时前
Leetcode 35
笔记·学习·算法·leetcode
X_szxj3 小时前
Volatility2在kali安装
linux·运维·服务器
像风一样!3 小时前
NFS文件存储
linux·服务器·网络·nfs文件存储
MOYIXIAOWEIWEI3 小时前
rocky 9.5系统安装zabbix监控实现邮件告警
ubuntu·zabbix·rocky
大聪明-PLUS3 小时前
Linux 中的 DNS 工作原理(一):从 getaddrinfo 到 resolv.conf
linux·嵌入式·arm·smarc