柿子要挑软的捏-先探探“Linux字符设备驱动”的底 -- 1

学习目标:

掌握设备号的作用和申请、注册和注销的方法。


学习内容:

        为什么说“字符设备驱动”是软柿子?这是我从一本书上看到的,经过提炼后总结的。原话是“在Linux设备驱动程序的家族中,字符设备驱动程序是较为简单的驱动程序,同时也是 应用非常广泛的驱动程序。所以学习字符设备驱动程序,对构建Linux设备驱动程序的知识结构非常重要。”

        那么,我们先说说要完成一个字符设备驱动程序,至少需要哪些要素呢?

一、设备号

        Linux 规定每一个字符设备或者块设备都必须有一个专属设备号。一个设备号主设备号次设备号组成。

  • 主设备号用来表示某一类特定驱动程序;
  • 次设备号用来表示该驱动下的各个设备。

        例如,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么 ,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备驱动程序,次设备号 分别为1和2。这里,次设备号就分别表示两个LED灯。

        因此,开发字符设备驱动程序,申请设备号是第一步,只有有了设备号,才能向系统注册设备。

1、设备号的类型

        Linux中使用 dev_t 数据类型表示设备号。dev_t 的定义在内核源码的 include/linux/types.h 文件里。设备号是一个32位的数据类型(unsigned int),其中高12位为主设备号,低20位为次设备号。

#define MINORBITS       20
#define MINORMASK       ((1U << MINORBITS) - 1)

#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
  1. MINORBITS 表示次设备号位数。
  2. MINORMASK 用于计算次设备号使用。
  3. MAJOR 表示从dev_t中获取主设备号,本质是将dev_t右移20位。
  4. MINOR 表示从dev_t中获取次设备号,本质是取低20位的值。
  5. MKDEV 用于将主设备号和次设备号组成dev_t类型的设备号。

2、设备号分配

        在内核中,提供了动态分配设备号和静态分配设备号的函数,声明在include/linux/fs.h 里面。定义在fs/char_dev.c 中

功能函数参数
静态分配设备号函数register_chrdev_region设备号起始值,次设备号数量,设备的名称
动态分配设备号函数alloc_chrdev_region保存自动申请到的设备号,次设备号的起始值,要申请的数量,设备名称
设备号释放函数unregister_chrdev_region要释放的设备号,释放的设备号数量

二、举例

1、例程:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>

#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

// 这里是驱动传参
static int major = 0;
static int minor = 0;

module_param(major, int, S_IRUGO);
module_param(minor, int, S_IRUGO);

// 设备号变量
dev_t dev_num;

// 驱动入口函数
static int module_dev_t_init(void)
{
       int ret = 0;

       if(major)       // 如果有主设备号,则进行静态设备注册
       {
               printk("major=%d!\n", major);
               printk("minor=%d!\n", minor);

               // 合成设备号
               dev_num = MKDEV(major, minor);
               // 注册设备号
               ret = register_chrdev_region(dev_num, 1, "test1");
               if(ret < 0)
               {
                       printk("register_chrdev_region failed!!\n");
                       return -1;
               }
               printk("register_chrdev_region success!!\n");
       }
       else            // 如果没有主设备号,则进行动态设备注册
       {
               ret = alloc_chrdev_region(&dev_num, 0, 1, "test2");
               if(ret < 0)
               {
                       printk("alloc_chrdev_region failed!!\n");
                       return -1;
               }
               printk("alloc_chrdev_region success!!\n");

               major = MAJOR(dev_num);
               minor = MINOR(dev_num);
               printk("Major=%d, Minor=%d\n", major, minor);
       }

       return 0;
}

// 驱动卸载函数
static void module_dev_t_exit(void)
{
       unregister_chrdev_region(dev_num, 1);  // 注销设备号
       printk("dev_t test module bye!\n");
}

module_init(module_dev_t_init);
module_exit(module_dev_t_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BF");
MODULE_VERSION("V1");

这个例子我是在ubuntu 的虚拟机上进行实验的,Makefile 如下 

KERNEL_DIR=/lib/modules/5.15.0-69-generic/build

obj-m := dev_t.o

all:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

.PHONY:clean
clean:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
~                                                    

编译完成之后,使用insmod加载模块,查看dmesg 出现了两种状况,看图:

2、遇到问题:

1、loading out-of-tree module taints kernel.

        这种错误可以不用理会,不影响驱动程序使用,但是看着就是不爽,就像编译过程中的warning 一样。
问题原因1:
        因为驱动用到了设备树,编译驱动的linux内核与insmod模块的linux的内核设备树不相同导致的。
解决方法:把驱动使用当前linux设备树重新编译一下,可以解决。

问题原因2:
        没有把驱动模块编译到 Kconfig 文件中,即 make menuconfig 的配置选项中没有此驱动。
解决方法:
        把驱动信息加入到Kconfig树中,普可以通过配置内核来决定哪些驱动需要加载,系统也就不会再报loading out-of-tree module taints kernel的错误了。

2、module verification failed: signature and/or required key missing - tainting kernel

问题原因:
        这是加载驱动程序时驱动签名或需要的密钥找不到,导致驱动module认证失败。
解决方法:
        方式一、重新配置内核
        方式二、修改驱动Makefile文件,在第一行增加以下语句:CONFIG_MODULE_SIG=n

        因此,我的Makefile修改为

CONFIG_MODULE_SIG=n
KERNEL_DIR=/lib/modules/5.15.0-69-generic/build

obj-m := dev_t.o

all:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

.PHONY:clean
clean:
        $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

        再次编译后,加载后,没有出现刚才的提示。

3、例程验证

        1、动态

        加载成功后,怎么确认设备注册成功?

        在系统中使用命令 cat /proc/devices 查看设备名称、设备号

        可以看到和dmesg中打印的一样的设备号 236 名称是 test2,说明设备注册没问题。
rmmod 之后就查询不到,说明设备卸载成功。

        到这里,我们可以从test2 设备名称以及dmesg 中打印的申请设备号的函数名:alloc_chrdev_region 确认,我们这次加载使用的是 动态申请设备号

2、静态       

        我们再来试试静态申请设备号,我们程序中判断是否给驱动程序传递了 主设备号 这个参数,刚才我们没有给驱动传递。那现在我们传递一个试试。

        注意,注意,注意!重要的事情说三遍!“静态申请设备号,要自己查询一下,使用没有被申请的设备号”

        通过场次 cat /proc/devcies 我们就用一个没有被申请的 222 号设备号。还记开头说的设备号是由 主设备号左移20位 再和 次设备号 位或 得到的。那我们可以直接传递 主设备号为 222,次设备号为0。命令如下:        

sudo insmod dev_t.ko major=222 minor=0

        查看dmesg 和 cat /proc/devcies 验证一下。

三、软柿子十八捏,第一捏.1,捏完 :)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值