对 linux驱动 及 字符型设备驱动 的理解

=========================================================================

以下是自我的理解

=========================================================================

要理解设备驱动开发,首先要理清几个大思路:

  1. 设备驱动是内核的一部分,而内核的套路就是先注册才能使用,因此就有注册接口。
  2. 向内核注册之后,也要能被用户所使用。而要被使用,就得提供接口出来给用户。
  3. 同样也要提供使用方式。
  4. 内核就作为一个中间层,即接收设备驱动的注册,又提供公共接口给用户。完美的中间层。
  5. 使用接口一般就是/dev目录下的设备节点文件。
  6. 使用方式就是file_operations结构体中的操作函数(即ops操作函数)。

=========================================================================

在理解了这几个思路之后,剩下的就是满足内核的具体套路(以字符型设备驱动为例):

(1)首先是注册进内核。

  1. 既然是字符型设备,那必然有对应的结构体描述:“struct cdev”。
  2. 驱动的所有内容,都是围绕着这个结构体在进行。
  3. 既然是结构体,那就有相应的初始化。
  4. 调用cdev_init()函数可以初始化cdev结构体
  5. cdev 结构体的初始化主要是包括 ops 操作函数 和 owner 拥有者 的初始化。

        ===============================

  1. 同样,既然是设备,想要编写驱动,按内核套路来说,就需要有一个设备号去供内核识别。
  2. 而获取这个设备号,有两种方式:动态分配获取(调用alloc_chrdev_region() 函数) 和 静态分配获取(调用register_chrdev_region() 函数,需要自定义主设备号)。

        ===============================

  1. 有了 设备号 和 描述结构体 后,就要将这两者关联起来,并注册进内核成为内核一部分。
  2. 可以调用 cdev_add()函数,将设备号 和 描述结构体 进行关联并注册。

(2)然后提供出用户接口 及 使用方式 给用户使用。

  1. 使用方式就是上面所提到的 file_operations 结构体成员。
  2. 而使用接口这一部分其实与文件系统强关联。
  3. 有提供两种方式可以满足目的,mknod手动创建 和 udev的自动创建。
  4.  对于udev自动创建,内核的套路就是以下描述。
  5.  调用 class_create() 函数创建一个设备类,在 /sys/class下生成设备目录。
  6.  调用 device_create() 函数在 /dev/ 目录和 /sys/class/xxx 目录下生成设备文件接口 xxx。

(3)至此,驱动的两大要求基本完成。

=========================================================================

以下是一些具体的描述

=========================================================================

驱动中对 udev 自动创建设备节点)的主要做法是(以字符驱动为例)在驱动初始化代码里:

  • 调用 register_chrdev_region() 或 alloc_chrdev_region() 来静态或者动态获取设备号 ->
  • 调用 cdev_init() 初始化设备号为cdev结构,并建立cdev与file_operations之间的连接 ->
  • 调用 cdev_add() 向系统添加一个cdev,以完成字符设备注册 ->
  • 调用 class_create() 为该设备创建一个 class,在/sys/class/下生成设备信息 ->
  • 调用 device_create() 创建对应设备

当然,也可以利用mknod命令手动创建设备节点。

=========================================================================

  • 内核中定义了 struct class 结构体,一个 struct class 结构体类型的变量对应一个驱动类。
  • 内核同时提供了 class_create() 函数,可以用它来创建一个类(class)。
  • 这个类(class)存放在 sysfs 下面,一旦调用class_create() 函数创建好了这个类,再调用 device_create() 函数,就会在 /sys/class/ 下生成设备信息目录。
  • 这样在启动加载模块的时候,用户空间中的 udev 会自动响应 device_create() 函数,去 /sysfs 下寻找对应的类,从而创建设备节点。

=========================================================================

  • 内核中也提供了另一个字符型设备的注册函数
  • int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)
  • 第一个参数 major 为主设备号,0代表动态分配;第二个参数  name 是设备的名字;第三个参数 fops 为文件操作指针。
  • 此函数 返回0 表示成功。返回-EINVAL 表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回-EBUSY 表示所申请的主设备号正在被其它设备驱动程序使用。
  • 如果是动态分配主设备号 成功,此函数将 返回所分配的主设备号
  • 如果register_chrdev操作成功,设备名就会出现在 /proc/devices文件里。
  • 在register_chrdev函数中,不只是注册了设备号,还做了 cdev 的初始化以及 cdev 的注册。(简单来说:register_chrdev() 中包含了cdev_init() 和 cdev_add() 所要做的事情)
  • module在被加载时,udev 就会自动在/dev下创建设备节点文件。
  • 例如:register_chrdev(MEM_MAJOR,"mem",&memory_fops)  向内核注册一个字符设备,在完成注册后,/proc/devices 中就能看到:/mem

=========================================================================

注意

  • 从linux内核2.6的某个版本之后,devfs不复存在,udev成为devfs的替代。相比devfs,udev有很多优势。udev属于应用层,不要试图在内核的配置选项里找到它。
  • 加入对udev的支持很简单,以作者所写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备。
  • 注意,在2.6较早内核版本中,device_create(…)函数名称不同,叫class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称不同,而且里面的参数设置也有一些变化。

  • struct class和device_create(…) 以及device_create(…)都定义在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。

  • 在2.6.26.6内核版本中,struct class定义在头文件include/linux/device.h中。

  • (手动创建设备节点)在成功的向系统注册了设备驱动程序后(调用register_chrdev()成功后),就可以用mknod命令来把设备在/dev目录下映射为一个设备节点文件。其它程序使用这个设备的时候,只要对在/dev目录下的此设备节点文件进行操作就行了。

  • register_chrdev()是向系统注册设备。注册设备时包括分配设备号,为设备分配资源等,添加设备等;cdev_add()主要是向系统添加设备,在register_chrdev()的时候会调用到该函数。

=========================================================================

举例

  • class_create() class_destroy() 使用示例

(1)通过class_create()、class_destroy()去注册和注销 /sys/class/test_class

(2)class_create动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。

(3)创建的逻辑类位于/sys/class/,文件夹里面的内容仍然为空。

/*
 * test-debug-scr.c
 *
 * Copyright (C) 2012 - 2021 Reuuimlla Limited
 * 
 * Adapt to support xxx
 */

#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/errno.h>
#include <linux/vmalloc.h>
#include <linux/device.h>

/*-------------------------------------------------------------------------------*/
/*    DEFINITION                                         */
/*-------------------------------------------------------------------------------*/

struct class *mem_class;

/
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
// 参数:  
//        owner, 拥有者。一般赋值为THIS_MODULE。12     
//        name, 创建的逻辑类的名称。13     
static int __init class_create_init(void)
{
    mem_class = class_create(THIS_MODULE, "test_class");
    if (mem_class==NULL)
    {
        printk("--create class failed!--\n");
        return -1;
    }
    
    printk("--create class ok!--\n");
    return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit class_create_destroy(void)
{
    if (mem_class != NULL) 
    {
       class_destroy(mem_class);
       mem_class = NULL;
    }
    return;
}

module_init(class_create_init);
module_exit(class_create_destroy);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test-debug-scr generic test driver");
MODULE_AUTHOR("xxxx");

编译并insmod后,在 /sys/clas/ 目录下就会生成 “ test_class ”目录,但因为只是创建了设备类,并没有注册设备且分配设备设备好,因此该目录仍然为空。

  • device_create() 使用示例

(1)通过alloc_chrdev_region() 动态分配主设备和从设备号

(2)通过 MAJOR(),MINOR()分配设备号,通过MKDEV()将设备号转换为dev_t类型。

(3)通过 cdev_init(),cdev_add()注册字符设备。

(4)通过class_create() 和 class_destroy()去注册和注销 /sys/class/test_class

(5)class_create动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。

(6)通过 device_create()在/dev/目录和/sys/class/test目录下分别创建设备文件 test_class

(7)创建的逻辑类位于/sys/class/,文件夹里面已经有分配好的设备号和设备名。

/*
 * test-debug-scr.c
 *
 * Copyright (C) 2012 - 2021 Reuuimlla Limited
 * 
 * Adapt to support xxx
 */

#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#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/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>

/*-------------------------------------------------------------------------------*/
/*    DEFINITION                                         */
/*-------------------------------------------------------------------------------*/

/*主设备和从设备号变量*/
static int test_major = 0;
static int test_minor = 0;

/*设备类别和设备变量*/
static struct cdev cdev;
static struct class *test_class = NULL;
static struct device *test_class_dev = NULL;

#define TEST_DEVICE_CNT      1
#define TEST_DEVICE_NAME     "test_class"

dev_t dev = 0;

/* 打开设备方法,空实现*/
static int test_open(struct inode *inode, struct file *filp) {
    return 0;
}

/* 关闭设备方法,空实现*/
static int test_release(struct inode *inode, struct file *filp) {
    return 0;
}

/* 读取设备数据,空实现*/
static ssize_t test_read(struct file *file, char __user *buffer,size_t count, loff_t *f_pos) 
{
    return 0;
}

/* 写入设备数据,空实现*/
static ssize_t test_write(struct file *file, const char __user *buffer,size_t count, loff_t *f_pos) 
{
    return 0;
}

/* 设备文件操作列表 */
static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .open = test_open,
    .release = test_release,
    .read = test_read,
    .write = test_write, 
};

/
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init class_create_init(void)
{
    int err = -1;
    dev_t devno = 0;

    /* 通过register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号*/
	if (test_major) 
    {
		devno = MKDEV(test_major, test_minor);/* 将主设备号和次设备号转换成dev_t类型 */
		register_chrdev_region(devno, TEST_DEVICE_CNT, TEST_DEVICE_NAME);
	} 
    else 
    {
        err = alloc_chrdev_region(&devno, 0, TEST_DEVICE_CNT, TEST_DEVICE_NAME);
        if(err < 0) {
    	    printk("Failed to alloc char dev region.\n");
    	    return err;
        }
        
        // 保存并打印一下主设备号与从设备号
        test_major = MAJOR(devno);
        test_minor = MINOR(devno);
        printk("--test_major: %d ; test_minor:%d--\n", test_major, test_minor);
	}

    /* 注册字符设备 */
    cdev_init (&cdev, &test_fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &test_fops;        
    int error = cdev_add (&cdev, devno, 1);
    if(error)
        printk("Err %d adding test_setup_cdev", error);  

    /* 在/sys/class/目录下创建设备类 test_class 目录 */
    test_class = class_create(THIS_MODULE, "test_class");
    if(IS_ERR(test_class)) 
    {
        printk("Err: failed in creating class.\n");
        return -1; 
    }       

    /* 在/dev/目录和/sys/class/test目录下分别创建设备文件test_class */
    test_class_dev = device_create(test_class, NULL, devno, NULL, "test_class");
    if(IS_ERR(test_class_dev)) 
    {
    	printk("Err: failed in creating device---\n");
    	return -1; 
    }

    printk("---Succedded to initialize test device---\n");  
    return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit class_create_destroy(void)
{
    dev_t devno = MKDEV (test_major, test_minor);

    cdev_del(&cdev);
    device_destroy(test_class, devno);
    class_destroy(test_class);
    unregister_chrdev_region (devno, TEST_DEVICE_NAME);
    printk ("Char driver test_exit.\n");
}

module_init(class_create_init);
module_exit(class_create_destroy);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("test-debug-scr generic test driver");
MODULE_AUTHOR("xxxx");

编译并insmod后:

在 /sys/clas/ 目录下就会生成 test_class 目录

并在/dev目录下生成 test_class 设备节点

如图所示:

lsmod后可以看到 “ test_debug ” 模块已经加载进内核。

在 /sys/class 目录下生成了 test_class 目录,且test_class目录下有关于设备的各类文件。

查看 /sys/class/test_class/目录下的uevent 文件内容,有主设备号,次设备号 ,及设备节点名。与代码中注册的基本一致。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值