4.10号驱动

本文详细介绍了内核模块编程中的参数传递方法,包括命令行参数、变量描述和数组传递,导出符号表的重要性,以及用户空间与内核空间的数据传输。此外,还探讨了自动创建设备节点的udev机制,通过class_create和device_create接口实现内核驱动的简化部署。
摘要由CSDN通过智能技术生成

1. 内核模块传参

1.1 引入

1. 在使用insmod安装驱动时,可以在外部通过传递参数改变变量的值,而不用修改驱动文件

2. 部分厂商只会提供对应的驱动.ko文件,驱动工程师根据.ko修改对应变量的值

1.2 接收命令行传递参数

module_param(name, type, perm)  
 函数功能:接收命令行传递参数
 参数:
     name:变量名字
     type:传递参数类型
        *  byte, hexint, short, ushort, int, uint, long, ulong
        *  charp: a character pointer
    perm:修饰变量的权限

 1.3 变量添加描述

MODULE_PARM_DESC(_parm, desc)
函数功能:对变量进行描述
参数:
    parm:变量名
    desc:对变量的描述

1.4 传递数组

module_param_array(name, type, nump, perm)
函数功能:传递数组
参数:
     name:数组的名字
     type:传递参数类型
     nump:数组的长度 传递的需要是一个地址
     perm:修饰数组名字的权限

2. 导出符号表

2.1 引入机制

在应用层,每个进程都有各自独立的用户空间,A进程不可以直接调用B进程的函数。但在内核层,内核共享3-4G内核空间,A进程只要知道B进程的函数地址就可以直接调用B中的函数。

2.2 导出符号表的好处

驱动工程师可以调用内核任意导出符号表的函数

解决代码冗余问题

2.3 如何导出符号表

EXPORT_SYMBOL_GPL(name)
函数功能:导出对应函数符号表
参数:
    name:函数名

3. 通用Makefile

4. 应用层和内核层的区别

                                           应用层                                           内核层

入口                                main函数入口                               入口函数

出口                                return 0                                         出口函数

许可证                             无                                                  有

指定入口地址                  无                                                  需要指定

指定出口地址                  无                                                   需要指定

编译                                gcc                                                 使用Makefile

执行程序                        ./可执行程序                                    安装/卸载

打印方法                        printf                                                printk

内核模块传参                argv/argc                                          借助宏函数

导出符号表                     无                                                     可以导出

5. 字符设备驱动

5.1 字符设备驱动API

 注册字符设备驱动
#include <linux/fs.h>
int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
函数功能:注册字符设备驱动,编写在入口函数中
参数:
    major:主设备号
    major = 0:动态分配主设备号 系统自动分配主设备号的值
    major > 0:静态分配主设备号 需要注意不能和/proc/devices主设备号重复
    name:字符设备驱动名字
    fops:操作方法结构体
返回值:
        major = 0:成功返回主设备号的值,否则失败
        major > 0:成功返回0,失败返回错误码

注销字符设备驱动
 void unregister_chrdev(unsigned int major, const char *name)
 函数功能:注销字符设备驱动
 参数:
     major:注册成功的主设备号的值
     name:字符设备驱动的名字
 返回值:无

6. 用户空间和内核空间数据传输

用户空间到内核空间(写)

#include <linux/uaccess.h>
int copy_from_user(void *to, const void *from,unsigned long n)
函数功能:将用户空间的数据,拷贝到内核空间,需要写在write函数中
参数:
    to:内核空间首地址
    from:用户空间首地址
    n: 拷贝字节大小
返回值:
    成功返回0,失败返回未拷贝的字节数

内核空间向用户空间(读)

int copy_to_user(void *to, const void *from,unsigned long n)
函数功能:将内核空间的数据,拷贝到用户空间,需要写在read函数中
参数:
    to:用户空间首地址
    from:内核空间首地址
    n: 拷贝字节大小
返回值:
    成功返回0,失败返回未拷贝的字节数

7. 地址映射

7.1 映射概念

物理地址 ===> 硬件层 ===> 芯片手册可以查看到的地址

虚拟地址 ===> 内核层 ===> 将物理地址映射为虚拟地址

如何将物理地址映射为虚拟地址 ===> mmu 内存管理单元

在arm课程中,所有操作的地址都属于物理地址

在驱动课程中,所有操作的地址都属于虚拟地址

所以需要将物理地址映射为虚拟地址

7.2 映射API接口

#include <linux/io.h>
void  *ioremap(unsigned long port, unsigned long size)
函数功能:将物理地址映射为虚拟地址
参数:
    port:物理地址
    size:映射的大小
返回值:
    成功返回映射虚拟地址首地址
    失败返回NULL

void iounmap(volatile void *addr)
函数功能:取消地址映射
参数:
    addr:映射成功之后虚拟地址首地址
返回值:无

8. 自动创建设备节点

8.1 引入目的

每次驱动工程师安装驱动时,都需要通过mknod命令创建设备节点 ==> 手动

需要根据创建字符设备驱动成功之后,返回的主设备号,根据主设备号,在手动创建设备节点(设备号 + 设备节点名字)

linux操作系统启动成功之后,执行的第一个脚本文件为rcS脚本文件

驱动工程师,直接将mknod自动创建设备节点,写入到rcS脚本中

8.2 机制

udev(mdev)机制:在2.6内核版本中引入,引入udev自动创建设备节点,在用户空间实现

在内核层,先使用class_create向上层提交目录信息,再使用device_create向上层提交设备信息,在用户空间udev自动创建设备节点

8.3 接口函数

#include <linux/device.h>
struct class * class_create(owner, name)
函数功能:向上层提交目录信息
参数:
    owner:THIS_MODULE
    name:目录信息的名字
返回值:
    成功返回struct class *结构体指针首地址
    失败判断是否在4K空间
例子:
struct class *cls;
cls = class_create(THIS_MODULE, "myled");
if(IS_ERR(cls))
{
    return PTR_ERR(cls);
}
void class_destroy(struct class *cls) //取消向上层提交目录信息

struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...)
函数功能:向上层提交设备节点信息
参数:
    class:目录句柄指针
    parent:NULL
    devt : 设备号
        MAJOR(dev)  : 根据设备号获取主设备号的值
        MINOR(dev)  : 根据设备号获取次设备号的值
        MKDEV(ma,mi)  :根据主设备号和次设备合成设备号     
  drvdata:NULL
  fmt:设备节点名字
返回值:
    成功返回struct device *结构体指针首地址
    失败判断是否在4K空间

void device_destroy(struct class *class, dev_t devt) //取消向上层提交设备节点信息

面试题

如何对驱动程序进行编译

指定驱动文件名称,指定架构,使用obj-m 对.o文件进行编译

内核模块传参有什么好处

在使用insmod的时候可以直接使用外部传参修改驱动文件中的变量值,而不用改变驱动文件,非常方便

如何给内核模块int型变量传递参数

使用module_param(name,type,perm)函数

用户空间和内核空间如何完成数据传输

用户向内核传数据是write操作,驱动层使用copy_from_user函数

内核向用户传数据是read操作,驱动层使用copy_to_user函数

自动创建设备节点机制以及实现

hotplug监听来自目录的提交申请,当有申请出现时,通知udev

udev(mdev)机制:在2.6内核版本中引入,引入udev自动创建设备节点,在用户空间实现

在内核层,先使用class_create向上层提交目录信息,再使用device_create向上层提交设备信息,在用户空间udev自动创建设备节点

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值