首页 > 系统相关 >Linux驱动开发之杂项设备注册和Linux2.6设备注册

Linux驱动开发之杂项设备注册和Linux2.6设备注册

时间:2023-12-26 16:05:08浏览次数:259  
标签:struct dev cdev 注册 Linux Linux2.6 bp 设备

Linux设备驱动开发中,需要向内核正确注册设备,才能创建设备节点,以供应用层访问。本文将详细介绍Linux下的两种设备注册方法:杂项设备注册Linux 2.6新方法注册

一、杂项设备注册

杂项设备注册简介

杂项设备注册是Linux驱动开发中的一种设备注册方式。在Linux系统中,杂项设备是一类没有明确分类的设备,它们不属于字符设备、块设备或网络设备等特定类型。杂项设备可以包括各种不同类型的设备,如传感器、LED灯、温度计等。

杂项设备注册的目的是将这些杂项设备描述成设备文件,以便通过文件操作来控制和访问设备。通过设备文件,应用程序可以打开、关闭、读取和写入杂项设备,实现对设备的控制和数据交互。

在杂项设备注册中,使用struct miscdevice结构体来描述设备的属性和设备文件的名字。该结构体包含了主设备号、次设备号、设备文件名和文件操作集合等信息。通过调用misc_register函数,将杂项设备的核心结构体注册到内核中,完成设备的注册过程。

杂项设备注册的好处是它可以自动生成设备文件,无需手动创建。系统会根据设备的主设备号和次设备号,在/dev目录下自动创建对应的设备文件。这样,应用程序可以直接通过设备文件来访问和控制杂项设备,简化了设备的管理和使用。

杂项设备注册特点:

  • 主设备号固定为10,次设备号自动分配
  • 注册后在/dev目录自动创建设备节点
  • 每个主机最多256个杂项设备

杂项设备注册相关API

misc_register()

功能
    向内核注册一个杂项设备
头文件
    Linux/miscdevice.h
原型
    int misc_register(struct miscdevice * misc)
参数
    struct miscdevice * misc:杂项设备注册的核心结构体
返回值
    成功    0
    失败    负数
    
struct miscdevice  {//杂项注册核心结构体
         int minor;         //次设备号
         const char *name;  //设备的名字 会出现在  /dev/目录下
         const structfile_operations *fops;  操作集合结构体
         //以下内容可以不管
         struct list_head list;
         struct device *parent;
         struct device*this_device;
         const char *nodename;
         umode_t mode;
};
+++++++++++++++++++++++++++++++++++++++++++++++++++++
struct file_operations {//操作集合结构体,使用时需要实现内核编程接口:open/close/read/write等。
         structmodule *owner;                  //固定填写  THIS_MODULE
         int(*open) (struct inode *, struct file *);   //内核层的open函数
         int(*release) (struct inode *, struct file *); //内核层的close函数
   ……………………………………..

};

misc_deregister()

功能    
    取消注册的杂项设备
头文件
    Linux/miscdevice.h
原型
    int misc_deregister(struct miscdevice*misc)
参数
     struct miscdevice *misc:杂项设备的核心结构体
返回值
    成功    0
    失败    负数                                     

杂项设备注册相关例程

例程简介

这个例程的作用是实现一个蜂鸣器的控制功能。它包括了一个内核模块和一个用户态程序。

内核模块部分:

  • 在模块初始化函数bp_init中,首先进行了地址映射,将控制寄存器和数据寄存器映射到内核空间。
  • 然后设置了蜂鸣器的相关寄存器,包括将控制寄存器的低四位设置为1,将数据寄存器的最低位设置为0。
  • 接下来,通过misc_register函数向内核注册了一个杂项设备。设置了该设备的打开和关闭函数,并指定了设备文件名和次设备号。
  • 在模块退出函数bp_exit中,调用misc_deregister函数取消注册杂项设备。

用户态程序部分:

  • main函数中,通过open函数打开设备文件/dev/bp_dev,并指定了读写权限。
  • 然后进入一个循环,每隔一秒钟打开蜂鸣器并输出提示信息,再关闭蜂鸣器并输出提示信息。
  • 循环会一直执行,直到程序被手动终止。

源码分享

/*驱动层相关源码*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/io.h>

void __iomem *GPD0_CON = NULL; //控制寄存器
void __iomem *GPD0_DAT = NULL; //数据寄存器

struct file_operations bp_fops;
struct miscdevice bp_misc;

//开启蜂鸣器函数
int bp_open (struct inode *inode, struct file *file){
printk("蜂鸣器开启\n");
*((unsigned int *)GPD0_DAT) |= 0x1;
return 0;
}

//蜂鸣器关闭函数
int bp_close (struct inode *inode, struct file *file){
printk("蜂鸣器关闭\n");
*((unsigned int *)GPD0_DAT) &= ~(0x1);
return 0;
}

static int __init bp_init(void){
printk("加载蜂鸣器模块\n");

//地址映射
GPD0_CON = ioremap(0x114000a0, 2);
GPD0_DAT = ioremap(0x114000a4, 1);

//寄存器设置
*((unsigned int *)GPD0_CON) &= ~(0xf);
*((unsigned int *)GPD0_CON) |= (0x1);
*((unsigned int *)GPD0_DAT) &= (0x1);

//向内核注册一个杂项设备
bp_fops.open = bp_open;	//打开蜂鸣器
bp_fops.release = bp_close;	//关闭蜂鸣器
bp_fops.owner = THIS_MODULE;	//模块所有者

bp_misc.minor = 255;	//系统分配次设备号
bp_misc.name = "bp_dev";	//设备文件名
bp_misc.fops = &bp_fops;	//文件操作集合
misc_register(&bp_misc);

return 0;
}

static void __exit bp_exit(void){
printk("卸载蜂鸣器模块\n");
misc_deregister(&bp_misc);

}

module_init(bp_init);
module_exit(bp_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYY");
/*用户层相关源码*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(){

int bp_fd  =0;
while(1){
	bp_fd = open("/dev/bp_dev", O_RDWR);
	if(bp_fd < 0){
		printf("打开bp_dev失败\n");
		perror("open");
	}
	printf("开启蜂鸣器\n");
	sleep(1);
	close(bp_fd);
	printf("关闭蜂鸣器\n");
	sleep(1);
}
return 0;
}

二、Linux 2.6设备注册

Linux2.6设备注册简介

Linux 2.6内核引入了一种更灵活的设备注册机制,提供了设备模型来管理和注册设备。这种设备注册方式相对于旧版本的Linux内核更加面向对象,具有更好的可维护性和扩展性,也是当前比较最流行的设备注册方法。

Linux 2.6设备注册特点

  • 提供更灵活的设备注册机制,主次设备号不再限定
  • 注册后需要自行创建设备节点
  • 支持模块化和多实例设计
  • 可以灵活配置主次设备号
  • 支持多实例和模块化设计
  • 更好地处理多个设备的情况

Linux2.6设备注册流程

  • 申请设备号(alloc_chrdev_region) 设备号由主设备号和次设备号组成,用于唯一标识一个设备。可以使用alloc_chrdev_region函数动态申请字符设备号,或者使register_chrdev_region函数静态注册一个已知的设备号。
  • 初始化cdev结构体(cdev_init) cdev结构体表示字符设备。开发者需要初始化这个结构体,并将它与设备的文件操作函数关联起来。这可以通过cdev_initcdev_add函数来完成。
  • 注册cdev结构体(cdev_add) cdev_add函数用于将cdev结构体添加到系统中,使得内核可以管理和操作这个设备。
  • 创建设备类(class_create) 使用class_create函数创建一个设备类,这个类将出现在/sys/class目录下。
  • 创建设备节点(device_create) device_create函数自动创建设备节点(通常位于/dev目录下),而不是手动使用mknod命令。

Linux2.6设备注册相关函数

alloc_chrdev_region()

功能
    向内核申请设备号
头文件
    Linux/fs.h
原型
    int alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name)
参数
    dev_t *dev,         用来存放获取到的设备号的变量
    unsigned baseminor,  次设备号的起始位置 
    unsigned count,      要申请的设备号的数量 
    constchar *name     设备的名字
返回值
    成功    0
    失败    非零

unregister_chrdev_region()

功能
    释放设备号
头文件
    Linux/fs.h
原型
    void unregister_chrdev_region(dev_t from, unsigned count)
参数
    dev_t from,      要释放的设备号的起始位置 
    unsigned count   要释放的设备号的个数
返回值
    无

cdev_init()

功能
    初始化linux2.6的核心结构体
头文件
    Linux/cdev.h
原型
    void cdev_init(struct cdev *cdev, conststruct file_operations *fops);
参数
    struct cdev *cdev,               linux2.6的核心结构体
    const struct file_operations *fops 操作集合结构体
返回值
    无

cdev_add()

功能
    注册一个linux2.6的核心结构体
头文件
    Linux/cdev.h
原型
    int cdev_add(struct cdev *, dev_t,unsigned);
参数
    struct cdev *cdev,      linux2.6的核心结构体
    dev_t dev,                        设备号unsigned   count      
        向内核注册的linux2.6的核心结构体的个数  一般填1 
返回值
    成功    0
    失败    错误码

cdev_del()

功能
    取消linux2.6核心结构体的注册
头文件
    Linux/cdev.h
原型
    void cdev_del(struct cdev *p)
参数
    struct cdev *p:linux2.6的核心结构体指针
返回值
    无

class_create()

功能
   创建类结构体
头文件
     Linux/device.h
原型
     struct class *class_create( struct module*owner, const char *name)
参数
     struct module *owner,    :THIS_MODULE
     const char *name        :类结构体的名字  
返回值
    成功    类结构体指针
    失败    NULL                                    

device_create()

功能
    自动生成设备文件
头文件
    Linux/device.h
原型
    struct device *device_create(struct class*class, struct device *parent,
                               dev_t devt, void *drvdata, const char*fmt, ...)
参数
    struct class *class,      类结构体的名字
    struct device *parent,   NULL
    dev_t devt,                        设备号
    void*drvdata,          传给设备的私有数据  NULL
    const char *fmt,...       设备的名字  会出现在/dev/目录下 
返回值
    成功    设备的结构体指针
    失败    NULL               

device_destroy()

功能
    销毁设备文件
头文件
    Linux/device.h
原型
    void device_destroy(struct class *class,dev_t devt)
参数
    struct class *class,   类结构体
    dev_t devt                  设备号
返回值
    无

class_destroy()

功能
    销毁类结构体
头文件
    Linux/device.h
原型
    void class_destroy(struct class *cls)
参数
   struct class *cls:类结构体 
返回值
    无

Linux2.6设备注册相关例程

例程简介

这个Linux内核模块的例程实现了一个简单的 LED 流水灯控制。

  1. Linux内核模块部分:
  • 定义了一个字符设备驱动,设备名为"myled"。这个驱动通过myopenmyclose函数控制LED灯的开关,并实现了流水灯效果。
  • myled_init函数中,首先通过alloc_chrdev_region函数申请设备号,然后初始化cdev结构体并通过cdev_add函数将其添加到系统中。接着创建一个类结构体,并在该类下创建设备文件。
  • myled_exit函数中,注销设备文件,注销类结构体,取消cdev的注册,并释放设备号。
  1. 用户空间程序部分:
  • 这个程序通过打开"/dev/myled"设备文件来控制LED灯的开关。每次打开设备文件时,LED灯会按照流水灯的效果亮起,每次关闭设备文件时,LED灯会熄灭。
  • 程序会无限循环打开和关闭设备文件,从而实现LED灯的不断闪烁。

源码分享

/*驱动层源码*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>

dev_t mydev;
int count;
struct cdev mycdev;
struct file_operations myfops;
struct class *myclass = NULL;

//open函数实现,开灯,流水灯效果
int myopen (struct inode *inode, struct file *file){
if(count == 4)
count = 0;
gpio_set_value(EXYNOS4X12_GPM4(count), 0);
gpio_set_value(EXYNOS4_GPD0(0), 1);
count++;
printk("开灯%d\n", count);
return 0;
}

//close函数实现,关灯,流水灯效果
int myclose (struct inode *inode, struct file *file){
gpio_set_value(EXYNOS4X12_GPM4(count - 1), 1);
gpio_set_value(EXYNOS4_GPD0(0), 0);
printk("关灯%d\n", count - 1);
return 0;
}

static int __init myled_init(void){
//申请设备号

alloc_chrdev_region(&mydev, 0, 1, "myled");
printk("申请到的设备号为 %d\n", mydev);
printk("主设备号 %d\n", MAJOR(mydev));
printk("次设备号 %d\n", MINOR(mydev));

//初始化Linux2.6的核心结构体
myfops.owner = THIS_MODULE;
myfops.open = myopen;
myfops.release = myclose;
cdev_init(&mycdev, &myfops);

//向内核注册一个Linux2.6核心结构体
cdev_add(&mycdev, mydev, 1);

//创建一个类结构体
myclass = class_create(THIS_MODULE, "myled");
if(myclass == NULL){
	printk("创建类结构体失败\n");
	return -1;
}

//创建设备文件
device_create(myclass, NULL, mydev, NULL, "myled");
return 0;
}

static void __exit myled_exit(void){
//注销设备文件
device_destroy(myclass, mydev);

//注销类结构体
class_destroy(myclass);

//取消Linux2.6注册
cdev_del(&mycdev);

//释放设备号
unregister_chrdev_region(mydev, 1);
}

module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYY");

/*用户层源码*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(){

int led_fd = 0;
while(1){
	led_fd = open("/dev/myled", O_RDWR);
	if(led_fd < 0){
		printf("打开myled失败\n");
		perror("open");
	}
	printf("开灯\n");
	sleep(1);
	close(led_fd);
	printf("关灯\n");
	sleep(1);
}
return 0;
}
//申请设备号 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

//初始化注册cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);

//创建设备节点
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt,..);

三、选择建议

  • 简单的字符设备推荐使用杂项设备注册
  • 需要灵活控制主次设备号的复杂设备使用Linux 2.6方法
  • 在模块化设计方面,Linux 2.6方式更优秀

标签:struct,dev,cdev,注册,Linux,Linux2.6,bp,设备
From: https://blog.51cto.com/u_16158769/8984009

相关文章

  • Linux使用PM2守护进程
    PM2:Node.js应用的进程管理工具Node.js是一个强大的服务器端JavaScript运行时,而在实际部署和管理Node.js应用时,需要一种有效的进程管理工具。PM2(ProcessManager2)正是为此而生,它提供了一套全面的功能,使得在生产环境中轻松管理Node.js进程成为可能。安装PM2首先,确保......
  • Linux 操作命令
    路是脚踏出来的,历史是人写出来的。人的每一步行动都在书写自己的历史。Linux基础命令open:打开文件操作,如环境配置文件。open~/.zshrcvi:vi(visualinterface),linux中最经典的文本编辑器vim(viimproved)是vi发展出来的一个文本编辑器,支持代码补全、编译、错误跳转......
  • leetcode 1633. 各赛事的用户注册率
    https://leetcode.cn/problems/percentage-of-users-attended-a-contest/?envType=study-plan-v2&envId=sql-free-50聚合函数分组后计算的是一组内的数据,分组前我们认为所有数据是一组本题注意还需要嵌套语句selectcontest_id,round(count(user_id)/(selectcoun......
  • Linux 配置.Net 7.0 运行环境
    运行命令dotnet--info,看看是不是成功安装了7.0.0第二步:配置守护进程1,在服务器根目录(/)下创建一个名/www/myweb的目录,用来存放我们的发布文件2,最好先把asp.netcore7测试项目发布后,上传到上面的目录中3,为使每次服务器重启能自动启动我们的应用程序和监测应用程序的运行状态,......
  • linux虚拟机固定ip
    1、查看宿主机IP信息在windows宿主机上,键盘输入win+r,输出cmd,打开终端命令行:输入ipconfig/all,查看宿主机IP信息: 2、修改Linux虚拟机的配置文件Linux虚拟机上打开网络配置文件:cd/etc/sysconfig/network-scripts/viifcfg-ens33 修改配置文件,输入字母 i ,进入编辑模式,做如下修改......
  • Linux CentOS7安装chrome和chromedriver,用于Selenium爬虫(java代码演示)
    ......
  • Linux OpenGL(3) —— 一个三角形
    绘制图形的大致流程图中,浅蓝色方格是整个过程中的重要对象。准备顶点坐标,创建VAO,并将坐标存入VBOGLfloatvertices[]={//顶点位置 -0.5,-0.5,0, 0.5,-0.5,0, 0,0.5,0 };unsignedintVAO;......
  • 如何应对Linux 内核崩溃
    如何应对Linux内核崩溃kdump是一种用于获取Linux内核崩溃转储的方法,而要找到关于其使用和内部结构的解释性文档可能有一些挑战。在这篇文章中,我将深入探讨kdump的基本用法以及kdump/kexec在内核中的实现。首先,让我们了解kexec。kexec是一个Linux内核到内核的引导加载程......
  • linux常用命令(笔记)
    1、telnet进去后怎么退出:telnet10.102.5.11922查看ip,端口通讯状况退出的话:Ctrl+]然后输入q退出,如下图:2、linux下查看tomcat版本curl172.16.45.231:8080|grep"ApacheTomcat"curlIP:端口|grep"ApacheTomcat"3、关于CPU命令——查看CPU型号cat/......
  • linux下服务器ping不通公网域名(不定时更新)
    1、服务器开通的公网访问,但是ping不通域名,可配置hosts重试保存后,重试,OK2、服务器公网IP也ping不通,修改网卡配置,配置DNS重启网卡后,问题解决......