基于Linux的驱动开发:内核模块传参、内核到处符号表、字符设备驱动

本文主要是介绍基于Linux的驱动开发:内核模块传参、内核到处符号表、字符设备驱动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

内核模块传参

        内核模块:

                int a , b;

        安装内核模块时:insmod demo.ko a = 100 b =10;

1.内核模块传参的意义

        在安装内核模块时给内核模块中的变量进行数值传递,这样可以让我们的内核模块向上兼容更为复杂的应用程序,向下适配多种硬件

2.内核模块传参相关API

        1.函数原型:module_param(name, type, perm)

        功能: 声明可以进行内核模块传参的变量

        参数:name : 变量名

                   type:要传参的数值类型---》byte(传递char), hexint(16进制的int)、short 、int、uint、long、ulong、charp(传递字符指针)、bool(0/1 y/n Y/N) 

                   perm:文件权限,通过module_param声明了要传参的变量,那么在sys/module/当前模块/parameters/下会生成一个以当前变量为名的文件,文件的权限为perm指定的权限,文件内容为变量的值

        注:通过modinfo查看您当前内核模块可以进行命令行传参的变量有哪些

        2.函数原型:MODULE_PARM_DESC(_parm, desc)

        功能:添加要传参的变量的描述,这个描述也可以通过modinfo查看

        参数:_parm :传参的变量名

                  desc:添加的描述

        注:给char类型的变量传参时,需要传递对应的ASCII十进制形式

                给字符指针传递字符串时,字符串不可以有空格,若有空格,空格前的被当作参数传递给变量,后面的会被认为时一个不认识的变量

示例代码 

内核导出符号表

 

1.导出符号表的意义

到处符号表可以让不同模块之间实现资源的相互访问 ,比如模块2想要访问模块1的资源,只需将模块1资源的符号表导出给模块2,此时,模块2就可以访问模块1的资源了

2.导出符号表的相关API

EXPORT_SYMBOL(变量名|函数名)

或者

EXPORT_SYMBOL_CPL(变量名|函数名)

3.导出符号表测试实例

3.1编写代码

定义demo1.c,完成函数的定义

#include <linux/init.h>
#include <linux/module.h>
int add(int i,int j)
{return i+j;
}
//生成add的符号表文件
EXPORT_SYMBOL(add);
static int __init mycdev_init(void)
{return 0;
}
static void __exit mycdev_exit(void)
{}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 定义dem2.c,完成调用demo1.c中的函数

#include <linux/init.h>
#include <linux/module.h>extern int add(int i,int j);
static int __init mycdev_init(void)
{printk("调用模块1函数执行结果为:%d",add(3,5));return 0;
}
static void __exit mycdev_exit(void)
{}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

3.2编译

先编译demo1.c将生成的符号表文件Module.symvers复制到demo2的路径下再编译demo2.c

此时通过modinifo查看demo2.ko,显示demo2依赖于demo1

注意 :在高版本的内核中不支持符号表文件直接复制,不然编译报未定义错误

解决办法:在demo2的Makefile中加上demo1符号表的文件路径3.3安装流程

先安装demo1,再安装demo2

3.4卸载

先卸载demo2,再卸载demo1

字符设备驱动

1.字符设备驱动的定义

字符设备是以字节流的形式进行顺序访问的设备,针对字节设备设计的驱动框架叫做字符设备驱动。当前市面上绝大多数的设备都属于字符设备,比如键盘、鼠标、摄像头...

2.字符设备驱动的框架

1.当在内核中注册一个字符设备驱动后会得到驱动对应的设备号,设备号是设备的唯一标识。设备号分为主设备号和次设备号,主设备号(高12位):用来标识一类设备 ,此设备号(低20位):用来标识这一类中的某一个设备。设备号=主设备号<<20 | 次设备号

2.根据设备号可以通过某种方式在文件系统中创建设备文件,相当于通过设备号将设备文件和驱动绑定

3.当设备文件被创建后,应用程序中调用 open() | read() | write() | close()

4.调用以上函数时,驱动中对应的操作应用方法会被回调

5.在驱动的操作方法中完成硬件的控制

3.字符设备驱动的注册和注销

3.1相关API 

头文件:#include <linux/fs.h>

注册字符设备驱动

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

功能:实现字符设备驱动的注册,一次申请256个设备的资源(0-255个此设备号)

参数:major:主设备号

                >0 静态指定主设备号

                =0 动态申请主设备号

           name:注册得到的驱动名字

           fops:操作方法结构体指针,指向操作方法结构体变量

返回值:

        失败返回错误码

        成功:若major>0,则返回申请得到主设备号

                   若major=0,则返回0

操作方法结构体,用于保存和管理驱动的各自操作方法

struct file_operations {

        int (*open) (struct inode *, struct file *);

        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

        int (*release) (struct inode *, struct file *);

};

注销字符设备驱动

void unregister_chrdev(unsigned int major, const char *name)

功能:实现字符设备驱动的注销

参数:major:驱动对应的主设备号

           name:注册时填写的驱动号

返回值:无

3.2字符设备驱动注册实例

1.编写代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
unsigned int major;
// 封装操作方法,这些操作方法在应用层进行系统调用时被回调
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
// 定义操作方法结构体对象,保存封装的操作方法
struct file_operations fops = {.open=mycdev_open,.read=mycdev_read,.write=mycdev_write,.release=mycdev_close,
};
//入口函数
static int __init mycdev_init(void)
{// 注册字符设备驱动major = register_chrdev(0, "mychrdev", &fops);if (major < 0){printk("字符设备驱动注册失败\n");return major;}printk("注册字符设备驱动成功major=%d\n", major);return 0;
}
//出口函数
static void __exit mycdev_exit(void)
{//注销字符设备驱动unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

2.编译驱动

3.安装驱动内核模块

可以查看/proc/devices文件,确定驱动是否被注册4.创建设备文件

创建设备文件的命令:mknod /dev/mychrdev c 240 0

解析:mknod:创捷设备文件的命令码

           /dev/mychrdev:创建的设备文件的路径和名字

          c 设备文件类型(字符设备文件) d(块设备文件)

          240:主设备号

          0:次设备号 0-255都可以

5.编写应用程序代码测试是否可以关联驱动

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{char buf[128] = {0};int fd = open("/dev/mychrdev", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");return -1;}printf("打开设备文件成功\n");//调用readread(fd, buf, sizeof(buf));//调用writewrite(fd, buf, sizeof(buf));//调用closeclose(fd);return 0;
}

6.现象

执行应用程序,驱动中的操作方法被回调

4.用户和内核的数据传递

用户空间和内核空间之间无法直接相互访问内存,二者进行数据交互需要使用数据传递函数

4.1 API

头文件 #include <linux/uaccess.h>

函数原型unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

功能:传递内核空间的数据到用户空间

参数:to:用户空间保存数据的buf首地址

           from:内核空间保存数据的buf首地址

           n:传递的数据长度,以字节为单位

返回值:成功返回0,失败返回未拷贝的字节数

函数原型unsigned log copy_from_user(void *to, const void __user *from, unsigned long n)

功能:传递用户空间的数据到内核空间

参数:to:内核空间保存数据的buf首地址

           from:用户空间保存数据的buf首地址

           n:传递的数据长度,以字节为单位

返回值:成功返回0,失败返回未拷贝的字节数

4.2用户和内核数据传递实例

应用程序代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string.h>
int main(int argc, char const *argv[])
{char buf[128] = {0};int fd = open("/dev/mychrdev", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");return -1;}printf("打开设备文件成功\n");fgets(buf,sizeof(buf),stdin);//在终端读一个字符串buf[strlen(buf)-1]='\0';write(fd, buf, sizeof(buf));//将数据传递给内核memset(buf,0,sizeof(buf));//清空数组read(fd, buf, sizeof(buf));//将内核空间数据传递到用户printf("buf:%s\n",buf);close(fd);return 0;
}

驱动程序代码 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include<linux/uaccess.h>
unsigned int major;
char kbuf[128]={};
// 封装操作方法
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);int ret;//拷贝数据到用户空间ret=copy_to_user(ubuf,kbuf,size);if(ret){printk("copy_to_user filed\n");return -EIO;}return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);int ret;//从用户空间拷贝数据到内核空间ret=copy_from_user(kbuf,ubuf,size);if(ret){printk("copy_from_user filed\n");return -EIO;}return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);return 0;
}
// 定义操作方法结构体对象
struct file_operations fops = {.open=mycdev_open,.read=mycdev_read,.write=mycdev_write,.release=mycdev_close,
};
static int __init mycdev_init(void)
{// 注册字符设备驱动major = register_chrdev(0, "mychrdev", &fops);if (major < 0){printk("字符设备驱动注册失败\n");return major;}printk("注册字符设备驱动成功major=%d\n", major);return 0;
}
static void __exit mycdev_exit(void)
{//注销字符设备驱动unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

现象:在终端输入的字符串可以正常的打印出来,说明用户空间和内核空间数据传递成功

5.物理内存映射相关API

驱动控制硬件需要操作硬件的特殊功能寄存器,但是特殊功能寄存器内存物属于理内存,驱动加载到虚拟内存,想要在驱动中操作硬件寄存器,需要将硬件寄存器内存映射为虚拟内存

#include <linux/io.h>

函数原型:void *ioremap(phys_addr_t paddr, unsigned long size)

功能:映射指定大小的物理内存为虚拟内存

参数:paddr:要映射的物理内存首地址

           size:要映射的物理内存大小

返回值:成功,返回映射成功的虚拟内存首地址,失败,返回NULL

函数原型:void iounmap(const void __iomem *addr)

功能:取消物理内存的映射

参数:addr:要取消的虚拟内存首地址

返回值:无

注:有关字符设备驱动的示例代码,目前可查看上一篇文章"编写驱动代码控制LED灯亮灭"http://t.csdnimg.cn/ZBsgG

这篇关于基于Linux的驱动开发:内核模块传参、内核到处符号表、字符设备驱动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/252733

相关文章

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语