linux字符设备概念(二)

文章介绍了作者为字符设备驱动创建的一个通用模板,包括设备打开、关闭、读写函数,以及设备注册和注销的过程。模板简化了led、key、beep等驱动的开发,通过insmod加载模块后会在/dev下自动生成设备文件,并在卸载时自动删除。测试代码展示了如何与驱动交互。
摘要由CSDN通过智能技术生成

 

最近学习字符设备驱动,其大致的框架与流程都基本搞懂了,为了方便以后代码重用,写了一个较为完善的模板,
以后如果需要写如:led、key、beep等的字符设备驱动,就不需要从O开始,可以直接用来修改调试,
代码中有比较清晰的注释,以及错误处理与回收机制,并且经过了初步测试,是通过的

该模板的作用:编译生成.ko文件后,通过insmod *.ko加载模块,会自动在/dev目录下生成相应的设备文件,模板中设备文件名称为firdev,
同样,卸载模块时会自动将其删除,这主要是device与class这两个结构体的功劳,代码中都有注释...

// 驱动模板
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>

#define CHRDEV_CNT                1
#define CHRDEV_NAME                "firdev"

/* 自定义字符设备结构体 */
struct chr_dev {
        dev_t devnum;                        //设备号
        struct cdev *pcdev;                //cdev
        struct class *class;                //类
        struct device *device;                //设备
};
struct         chr_dev firdev;
char         kernel_buf[512];
/*
  * @brief  文件打开函数
  * @param  inode : 传递给驱动的inode
                        file  : 要打开的设备文件       
  * @retval 0 成功, 其他 失败
  */
static int chrdev_open(struct inode *inode, struct file *file)
{
        filp->private_data = &firdev;                 //设置私有数据
        printk(KERN_INFO "chrdev_open\n");
        return 0;
}
/*
  * @brief  文件关闭函数
  * @param  inode : 传递给驱动的inode
                        file  : 要关闭的设备文件       
  * @retval 0 成功, 其他 失败
  */
static int chrdev_release(struct inode *inode, struct file *file)
{
        printk(KERN_INFO "chrdev_release\n");
        return 0;
}
/*
  * @brief  读函数, 将内核中的数据拷贝到应用层
  * @param  file: 要打开的设备文件
                        buf : 返回给用户空间的数据缓冲区
                        cnt : 要读取的数据长度
                        offt: 相对于文件首地址的偏移       
  * @retval 读取的字节数, 负值表示读取失败
  */
ssize_t chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
        int ret = -1;
        printk(KERN_INFO "chrdev_read\n");
        //一定要用如下拷贝函数, 不信你试试别的
        ret = copy_to_user(ubuf, kernel_buf, count);
        if (ret) {
                printk(KERN_ERR "copy_to_user fail\n");
                return -EINVAL;
        }
        printk(KERN_INFO "copy_to_user success...:%s\n", kernel_buf);
       
        return 0;
}
/*
  * @brief  写函数, 将应用层传递过来的数据复制到内核中
  * @param  filp: 打开的文件描述符
                        buf : 要写给设备写入的数据
                        cnt : 要写入的数据长度
                        offt: 相对于文件首地址的偏移       
  * @retval 写入的字节数, 负值表示写入失败
  */
static ssize_t chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
        int ret = -1;
       
        printk(KERN_INFO "chrdev_write\n");
        //一定要用如下拷贝函数, 不信你试试别的
        ret = copy_from_user(kernel_buf, ubuf, count);
       
        if (ret) {
                printk(KERN_ERR "copy_from_user fail\n");
                return -EINVAL;
        }
        printk(KERN_INFO "copy_from_user success...:%s\n", kernel_buf);
       
        return 0;
}

//文件操作结构体, 外部操作此模块的接口, 需要我们填充
//.owner:指向拥有这个结构的模块的指针,用来在它的操作还在被使用时阻止模块被卸载
static struct file_operations chrdev_fops = {
        .owner                 = THIS_MODULE,
        //应用层间接调用的就是如下接口
        .open                = chrdev_open,                //打开设备时调用
        .release        = chrdev_release,        //关闭设备时调用
        .write                 = chrdev_write,                //写设备操作
        .read                = chrdev_read,                //读设备操作
};

static int __init chrdev_init(void)
{
        int ret;
        // 1.分配设备号
        ret = alloc_chrdev_region(&firdev.devnum, 0, CHRDEV_CNT, CHRDEV_NAME);
        if(ret < 0) {
                printk(KERN_ERR "alloc_chrdev_region fail\n");
                goto tag1; //如果执行到这里, 可以直接返回
        }
        printk(KERN_INFO "major = %d, minor = %d.\n", MAJOR(firdev.devnum), MINOR(firdev.devnum));
       
        // 2.初始化cdev, 并添加到系统
        // cdev绑定file_operations与dev_t, 添加进系统从而产生了联系
        firdev.pcdev = cdev_alloc();       
        //firdev.pcdev->owner = THIS_MODULE;
        //firdev.pcdev->ops = &chrdev_fops;                //将cdev和file_operations进行绑定
        cdev_init(firdev.pcdev, &chrdev_fops);//这条语句可代替上面两条语句
        ret = cdev_add(firdev.pcdev, firdev.devnum, CHRDEV_CNT);//将cdev结构体加入到系统中去
        if (ret) {
                printk(KERN_ERR "Unable to cdev_add\n");
                goto tag2;//如果执行到这里, 说明前面设备号分配成功了, 需要释放掉
        }
        printk(KERN_INFO "cdev_add success\n");
       
        // 3.创建类
        // 注册字符设备驱动完成后, 添加设备类的操作, 让内核帮我们发信息
        // 给udev,让udev自动创建和删除设备文件
        firdev.class = class_create(THIS_MODULE, CHRDEV_NAME);
        if (IS_ERR(firdev.class)) {
                goto tag3;//如果执行到这里, 说明设备号与cdev都分配成功了, 需要释放掉
        }
       
        // 4.创建设备
        // 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
        // 所以我们这里要的文件名是/dev/firdev
        firdev.device = device_create(firdev.class, NULL, firdev.devnum, NULL, CHRDEV_NAME);
        if (IS_ERR(firdev.device)) {
                goto tag3;
        }
        return 0;
       
tag3:
        cdev_del(firdev.pcdev);
tag2:
        unregister_chrdev_region(firdev.devnum, CHRDEV_CNT);
tag1:       
        return -EINVAL;
}

//模块卸载函数, 注销要跟创建时倒着来
static void __exit chrdev_exit(void)
{
        // 销毁设备,即把创建的设备文件删掉
        device_destroy(firdev.class, firdev.devnum);
        // 销毁类,释放资源
        class_destroy(firdev.class);
       
        // 注销字符设备驱动结构
        cdev_del(firdev.pcdev);
        // 然后注销申请到的设备号
        unregister_chrdev_region(firdev.devnum, CHRDEV_CNT);
}

//模块加载与卸载时会调用如下接口
module_init(chrdev_init);
module_exit(chrdev_exit);

//下面这些都是跟模块相关, 需要加上才能编译
MODULE_LICENSE("GPL");                                // 模块许可证
MODULE_AUTHOR("author");                        // 模块作者
MODULE_DESCRIPTION("description");        // 模块信息
MODULE_ALIAS("alias");                                // 模块别名


//  测试代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FILE "/dev/firdev"

int main(void)
{
        int fd = -1;
        int i = 0;
        char buf[64];
        fd = open(FILE, O_RDWR);
        if (fd < 0) {
                printf("open %s fail:%d\n",  FILE, fd);
                return -1;
        }
        printf("open %s ok\n", FILE);
       
        while(1) {
                memset(buf, 0, sizeof(buf));
                printf("input>>");
                scanf("%s", buf);
               
                if(strstr(buf, "write")) {
                        char *p = strstr(buf, ":");
                        write(fd, p+1, strlen(p));
                        printf("len:%d\n", strlen(p));
                }
                else if(!strcmp(buf, "read")) {
                        memset(buf, 0, sizeof(buf));
                        read(fd, buf, sizeof(buf));
                        printf("read str:%s\n", buf);
                }
                else if(!strcmp(buf, "clear"))
                        write(fd, 0, 1);
                else if(!strcmp(buf, "quit"))
                        break;
        }
}


//  测试结果
将驱动模板编译生成.ko文件,我的是first_drv.ko,拷贝至开发板
然后执行:insmod first_drv.ko
root@ALIENTEK-IMX6U:/mnt/ttt# insmod first_drv.ko
major = 249, minor = 0.
cdev_add success

说明模块加载成功,cat /proc/devices可看到firdev驱动,并且ls /dev可看到firdev目录,这就是自动生成的设备文件
然后测试代码测试,如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

newzhpfree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值