02-编写单个字节设备模块的驱动套路

 

目录

 

1.单字节设备和多字节设备的区别

2.单字节设备(LED设备驱动)的驱动套路

2.1 头文件

2.2 定义设备驱动相关的变量

2.3 编写file operations 相关操作的函数

2.3.1 open函数

2.3.2 release函数

2.3.3 write函数

2.3.4 read函数

2.3.5 填充file operations结构体

2.4 模块的初始化和卸载

2.4.1 模块的初始化

2.4.2 模块的卸载

2.5 声明

2.6完整代码如下:

3.Makefile编写

4.测试验证

4.1 测试文件编写led_test.c

5.一张图总结


1.单字节设备和多字节设备的区别

对于Linux设备而言,一共有3种设备,即字符设备、块设备和网络设备。

首先我们先来说说单个字节设备属于哪一类的设备,就字符设备而言,有分为单字节和多字节设备。顾名思义:单字节设备:就是最大只能对设备进行读取或者写入一个字节的数据,常见的有:led设备、按键设备,这种设备只会呈现出两种状态。而多字节设备:可以写入或者读取多个字节的,常见的有存储设备。

今天的话,主要是针对单字节设备(以LED设备为例)进行分析。

先说明一下开发环境:

Ubuntu 16.04

不涉及任何硬件的相关操作(如果有读者,想要移植到arm-linux开发板上运行,则需要在对应的函数上增加相关硬件操作,以及修改Makefile的编译器即可)

按照上一篇文章的套路,继续来分析一下:

2.单字节设备(LED设备驱动)的驱动套路

2.1 头文件

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/cdev.h> //使用的是cdev
#include <linux/uaccess.h> //copy_to_user copy_from_user
#include <linux/slab.h>
//如果要涉及硬件,请包含相关头文件

2.2 定义设备驱动相关的变量

#define LED_MAJOR       0 /*采用动态的方式创建*/
#define DEVICE_NAME     "my_led" //设备的名称


static int led_major = LED_MAJOR;

/*led设备结构体*/
typedef struct LED_CDEV
{
    struct cdev cdev;//定义一个cdev结构体
    unsigned char led_value;/*led 的值,属于私有数据*/
}led_cdev_t;

static led_cdev_t *g_led_cdv;

2.3 编写file operations 相关操作的函数

2.3.1 open函数

/*打开设备*/
static int led_dev_open(struct inode *inode,struct file *filep)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    filep->private_data = g_led_cdv;
    //如果涉及到硬件操作,在这里申请gpio资源即可
    return 0;
}

2.3.2 release函数

/*文件释放*/
static int led_dev_release(struct inode *inode,struct file *filep)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    //如果涉及到硬件相关操作,在这里释放掉gpio资源即可
    return 0;
}

2.3.3 write函数

/*写函数*/
static ssize_t led_status_write(struct file *filep,const char __user *buf,
                                size_t size,loff_t *ppos)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    int ret = 0;
    led_cdev_t *dev = filep->private_data;
    
    if(copy_from_user(&dev->led_value,buf,1))//从用户空间拷贝数据到内核空间
    {
        ret = -EFAULT;
    }
    else
    {
        printk(KERN_INFO"write:dev->led_value=%d\r\n",dev->led_value);
        //涉及到硬件,直接在这里操作即可
        ret = 1;
    }
    return ret;
}

使用copy_from_user()可以向内核传递一个数据块,如果只需要传递一个char、short、或者int类型的数据,其实也可以使用get_user()函数

它们函数原型如下所示:

/*
    to   :  内核空间缓冲区地址
    from :  用户空间地址
    n    :  数据字节数
    返回值: 不能被复制的字节数,返回 0 表示全部复制成功

*/
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);


/*
    x  : 内核空间的数据
    p  : 用户空间的指针
    获取成功,返回 0,否则返回-EFAULT
*/
int get_user(x, p);

2.3.4 read函数

static ssize_t led_status_read(struct file *filep,char __user *buf,size_t size,
                              loff_t *ppos)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    int ret = 0;
    led_cdev_t *dev = filep->private_data;/*获得设备结构体指针*/
    
   
    if(copy_to_user(buf,&dev->led_value,1)) /*内核空间到用户空间拷贝*/
    {
        ret = -EFAULT;
    }
    else
    {
        printk(KERN_INFO"read:dev->led_value=%d\r\n",dev->led_value);
        //涉及到硬件的操作,直接在这里操作即可
        ret = 1;/*返回具体的个数*/
    }
    return ret;
}

使用copy_to_user()可以向用户空间传递一个数据块,如果只需要传递一个char、short、或者int类型的数据,其实也可以使用put_user()函数

它们函数原型如下所示:

/*
    to   :  内核空间缓冲区地址
    from :  用户空间地址
    n    :  数据字节数
    返回值: 不能被复制的字节数,返回 0 表示全部复制成功

*/
static inline unsigned long __must_check copy_to_user(void *to, const void __user *from, unsigned long n);


/*
    x   : 内核空间的数据
    p   : 用户空间的指针。
    ret : 传递成功,返回 0,否则返回-EFAULT
*/
int put_user(x,p);

2.3.5 填充file operations结构体

/*填充file operations 结构体*/
static const struct file_operations led_dev_fops = 
{
    .owner      = THIS_MODULE,
    .open       = led_dev_open,
    .release    = led_dev_release,
    .read       = led_status_read,
    .write      = led_status_write
};

2.4 模块的初始化和卸载

2.4.1 模块的初始化

/*设备驱动模块加载函数*/
static int __init led_module_init(void)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    
    int result = 0;
    dev_t dev_no = MKDEV(led_major,0);
    
    /*1.申请设备号*/
    if(led_major)
    {
        result = register_chrdev_region(dev_no,1,DEVICE_NAME);
    }
    else
    {
        /*动态申请设备号*/
        result = alloc_chrdev_region(&dev_no,0,1,DEVICE_NAME);
        led_major = MAJOR(dev_no);
    }
    
    if(result < 0)
    {
        return result;
    }
    
    /*2.动态申请设备结构体的内存*/
    g_led_cdv = kmalloc(sizeof(led_cdev_t),GFP_KERNEL);
    
    if(!g_led_cdv)
    {
        /*申请失败*/
        result = -ENOMEM;
        goto fail_malloc;
    }
    /*初始化并注册cdev*/
    cdev_init(&g_led_cdv->cdev,&led_dev_fops);/*初始化*/
    result = cdev_add(&g_led_cdv->cdev,dev_no,1);/*注册*/
    if(result)
    {
        printk(KERN_INFO"Error to add led cdev");
    }
    return 0;
    
    fail_malloc:
    unregister_chrdev_region(dev_no,1);
    return result;
}

module_init(led_module_init);

2.4.2 模块的卸载

需要按照相反的顺序进行模块的卸载

cdev_add                 =====> cdev_del

kmalloc                     =====> kfree

alloc_chrdev_region =====> unregister_chrdev_region

static void __exit led_module_exit(void)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    /*按照相反的顺序卸载*/
    cdev_del(&g_led_cdv->cdev);
    kfree(g_led_cdv);
    unregister_chrdev_region(MKDEV(led_major,0),1);/*释放设备号*/
}

module_exit(led_module_exit);

2.5 声明

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("this is a led drivers!");

2.6完整代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/uaccess.h> //copy_to_user copy_from_user
#include <linux/slab.h>



#define LED_MAJOR       0 /*采用动态的方式创建*/
#define DEVICE_NAME     "my_led"


static int led_major = LED_MAJOR;

/*led设备结构体*/
typedef struct LED_CDEV
{
    struct cdev cdev;
    unsigned char led_value;/*led*/
}led_cdev_t;

static led_cdev_t *g_led_cdv;
//1.file operations 函数相关实现
/*1.1 打开设备*/
static int led_dev_open(struct inode *inode,struct file *filep)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    filep->private_data = g_led_cdv;
    return 0;
}
/*1.2 文件释放*/
static int led_dev_release(struct inode *inode,struct file *filep)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    return 0;
}
/*1.3 读函数*/
static ssize_t led_status_read(struct file *filep,char __user *buf,size_t size,
                               loff_t *ppos)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    int ret = 0;
    led_cdev_t *dev = filep->private_data;/*获得设备结构体指针*/
    
    /*内核空间到用户空间拷贝*/
    if(copy_to_user(buf,&dev->led_value,1))
    {
        ret = -EFAULT;
    }
    else
    {
        printk(KERN_INFO"read:dev->led_value=%d\r\n",dev->led_value);
        ret = 1;/*返回具体的个数*/
    }
    return ret;
}
/*1.4 写函数*/
static ssize_t led_status_write(struct file *filep,const char __user *buf,
                                size_t size,loff_t *ppos)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    int ret = 0;
    led_cdev_t *dev = filep->private_data;
    
    if(copy_from_user(&dev->led_value,buf,1))
    {
        ret = -EFAULT;
    }
    else
    {
        printk(KERN_INFO"write:dev->led_value=%d\r\n",dev->led_value);
        ret = 1;
    }
    return ret;
}
/*1.5 填充file operations 结构体*/
static const struct file_operations led_dev_fops = 
{
    .owner      = THIS_MODULE,
    .open       = led_dev_open,
    .release    = led_dev_release,
    .read       = led_status_read,
    .write      = led_status_write
};

/*2 注册和卸载*/
/*2.1 设备驱动模块加载函数*/
static int __init led_module_init(void)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    
    int result = 0;
    dev_t dev_no = MKDEV(led_major,0);//MKDEV是将主设备号和次设备号转换成dev_t类型的一个内核函数。
    
    /*1.申请设备号*/
    if(led_major)
    {
        result = register_chrdev_region(dev_no,1,DEVICE_NAME);
    }
    else
    {
        /*动态申请设备号*/
        result = alloc_chrdev_region(&dev_no,0,1,DEVICE_NAME);
        led_major = MAJOR(dev_no);
    }
    
    if(result < 0)
    {
        return result;
    }
    
    /*2.动态申请设备结构体的内存*/
    g_led_cdv = kmalloc(sizeof(led_cdev_t),GFP_KERNEL);
    
    if(!g_led_cdv)
    {
        /*申请失败*/
        result = -ENOMEM;
        goto fail_malloc;
    }
    /*初始化并注册cdev*/
    cdev_init(&g_led_cdv->cdev,&led_dev_fops);/*初始化*/
    result = cdev_add(&g_led_cdv->cdev,dev_no,1);/*注册*/
    if(result)
    {
        printk(KERN_INFO"Error to add led cdev");
    }
    return 0;
    
    fail_malloc:
    unregister_chrdev_region(dev_no,1);
    return result;
}

/*2.2 模块卸载函数*/
static void __exit led_module_exit(void)
{
    printk(KERN_INFO"%s\r\n",__FUNCTION__);
    /*按照相反的顺序卸载*/
    cdev_del(&g_led_cdv->cdev);
    kfree(g_led_cdv);
    unregister_chrdev_region(MKDEV(led_major,0),1);/*释放设备号*/
}

/*2.3 卸载和加载*/
module_init(led_module_init);
module_exit(led_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wxb");
MODULE_DESCRIPTION("this is a led drivers!");

3.Makefile编写

KERN_VER = $(shell uname -r)
#Linux
KERN_DIR = /lib/modules/$(KERN_VER)/build  

#ARM-Linux 的源码路径
#KERN_DIR = /home/wxb/study/arm_linux/linux-2.6.35.3  
obj-m += led.o

all:
	make -C $(KERN_DIR) M=`pwd` modules
clean:
	rm -r -f *.ko *.mod.c *.mod.o *.o *.order *.symvers
.PHONY:clean

4.测试验证

4.1 测试文件编写led_test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>

#define DEV_NAME "/dev/my_led"

int main(int argc, char *argv[])
{
    int i;
    int fd = 0;
    unsigned char ch = 1;
    
    fd = open (DEV_NAME, O_RDWR);
    if (fd < 0) 
    {
        perror("Open "DEV_NAME" Failed!\n");
        exit(1);
    }
    /*先写入一个字节数据*/
    i = write(fd, &ch, 1);
    if (!i) 
    {
        perror("write "DEV_NAME" Failed!\n");
        exit(1);
    }
    /*然后再读出来*/
    i = read(fd, &ch, 1);
    if (!i) 
    {
        perror("read "DEV_NAME" Failed!\n");
        exit(1);
    }
    else
    {
        printf("read data is %d\r\n",ch);
    }
    /*关闭文件*/
    close(fd);
    return 0;
}

编译得到可执行文件

gcc led_test.c -o led_test

使用make生成模块

make

然后查看设备号

cat /proc/devices

根据该设备的设备号创建一个设备节点

mknod /dev/my_led c 243 0    # c代表char,243与查看的主设备号要一致,0代表第1个从设备

创建完节点之后,我们可以在/dev/的目录下发现该设备

ls /dev/

最后执行命令

./led_test

可得到如下打印信息。

5.一张图总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值