Linux驱动编程 - 字符设备驱动


目录

简介:

一、字符设备驱动框架

1、字符设备驱动入口

2、字符设备驱动加载过程

2.1 申请设备号

2.1.1 分配设备号函数

(1) 静态分配函数

(2) 动态分配函数

(3) 注销设备号

2.1.2 设备号中的主/次设备号

2.1.3 申请设备号示例

 

2.2 注册字符设备

2.2.1 cdev结构体介绍

 

2.2.2 file_operations结构体介绍

 

2.2.3 初始化、注册字符设备

(1) cdev_init

(2) cdev_add

(3) 注销字符设备

2.2.4 注册字符设备示例

 

2.3 自动创建设备节点

2.3.1 class_create创建类

2.3.2 device_create创建设备

2.3.3 自动创建设备节点示例

二、字符设备示例

1、命令测试

 

2、应用程序读写


简介:


Linux内核主要包括三种驱动模型,字符设备驱动、块设备驱动以及网络设备驱动。本文主要介绍字符设备驱动的框架和机制。

一、字符设备驱动框架


Linux底层驱动一般都从入口函数开始分析。

1、字符设备驱动入口


驱动首先实现的就是加载和卸载函数,也是驱动程序的入口函数。

 static int __init xxx_init(void){​}​static void __exit xxx_exit(void){}​module_init(xxx_init);module_exit(xxx_exit);

这段代码实现了通用驱动的 module_init  加载与 module_exit  卸载框架。

2、字符设备驱动加载过程


  • 字符设备驱动加载过程

  • 字符设备驱动卸载过程

2.1 申请设备号


每一类字符设备都有唯一的设备号,其中设备号又分为主设备号次设备号。

  • 主设备号:用于标识设备的类型,如:区分是IIC设备还是SPI设备;

  • 次设备号:用于区分同类型的不同设备,如:区分IIC设备下,具体哪一个设备,是MPU6050还是EEPROM

2.1.1 分配设备号函数

设备号的分配方式有两种,一种是静态分配,另一种是动态分配。

(1) 静态分配函数

int register_chrdev_region(dev_t from, unsigned count, const char *name)

功能:以from设备号开始,连续分配count个同类型的设备号

参数:

  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

  • name:表示设备或者驱动的名称

(2) 动态分配函数

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

功能:从baseminor次设备号开始,连续分配count个同类型的设备号,并自动分配一个主设备号,将主、次组成的设备号信息赋值给*dev

参数:

  • dev:设备号的指针,用于存放分配的设备号,可使用MAJOR和MINOR宏,将主设备号和次设备号分别提取出来;

  • baseminor:次设备号从第几开始分配;

  • count:表示连续次设备号的个数,(同类型的设备有多少个);

  • name:表示设备或者驱动的名称;

两个函数的区别

  • register_chrdev_region:已给定主设备号和次设备号,调用该函数将设备号注册到内核;

  • alloc_chrdev_region:未给定设备的主设备号和次设备号,调用后,主设备号以0来表示,以自动分配,并且将自动分配的设备号,同样加入到子系统中,方便系统追踪系统设备号的使用情况;

(3) 注销设备号

设备号作为一种系统资源,使用完后需要将占用的设备号归还给系统,调用 unregister_chrdev_region 注销

void unregister_chrdev_region(dev_t from, unsigned count)

功能:要注销from主设备号下的连续count个设备

参数:

  • from:表示已知的一个设备号

  • count:表示连续设备编号的个数,(同类型的设备有多少个)

2.1.2 设备号中的主/次设备号

设备号的类型是dev_t ,内核 include/linux/types.h 可以看到dev_t 表示u32类型的数值。

 typedef u32 __kernel_dev_t;​typedef __kernel_dev_t      dev_t;

dev_t 包括主设备号和次设备号,由 MINORBITS 宏定义指定分界线,主设备号占用12bit次设备号占用20bit

头文件 include/linux/kdev_t.h 中定义

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))    //从dev_t中获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))     //从dev_t中获取次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))         //主/次设备号组成dev_t

2.1.3 申请设备号示例

static dev_t devid;				//设备号
static int major;				//主设备号
static int minor;				//次设备号
#define DEV_CNT        1        //设备数量
#define DEV_NAME    "test"      //设备名字/* 创建设备号 */
if (major) {         /* 定义了设备号 */devid = MKDEV(major, 0);        //主设备号major和次设备号0组成dev_t register_chrdev_region(devid, DEV_CNT, DEV_NAME);     //dev_t设备号注册到内核
} else {             /* 没有定义设备号 */alloc_chrdev_region(&devid, 0, DEV_CNT, DEV_NAME);    //申请设备号dev_t major = MAJOR(devid);     //获取dev_t的主设备号minor = MINOR(devid);     //获取dev_t的次设备号
}

2.2 注册字符设备


2.2.1 cdev结构体介绍

cdev 定义在 include/linux/cdev.h,用来抽象一个字符设备。

struct cdev{struct kobject kobj;                //表示一个内核对象struct module *owner;	            //值为THIS_MODULE,表示模块const struct file_operations *ops;	//操作集,包括open、read、write等操作接口struct list_head list;              //用于将该设备加入到内核模块链表中dev_t dev;	                        //设备号(包括主设备号和次设备号)unsigned int count;	                //次设备号个数
};
2.2.2 file_operations结构体介绍

file_operations 定义在 include/linux/fs.h,它是文件操作集,包含open、read、write等操作。

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);__poll_t (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);unsigned long mmap_supported_flags;int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,u64);int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
2.2.3 初始化、注册字符设备

(1) cdev_init

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

功能:初始化cdev结构体

参数:

  • cdev:struct cdev结构体字符设备

  • fops:文件操作集

(2) cdev_add

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

功能:添加一个字符设备到kernel

参数:

  • p:cdev结构体指针

  • dev:设备号

  • count:该类型设备的个数

(3) 注销字符设备

void cdev_del(struct cdev *p)

功能:从系统中移除该字符设备驱动

2.2.4 注册字符设备示例

调用 cdev_add 后,"cat /proc/devices" 命令能看到字符设备信息

/* 定义file_operations */
static struct file_operations dev_fops = {.owner = THIS_MODULE,.open = dev_open,.read = dev_read,.write = dev_write,.release = 	dev_release,
};/* 初始化并注册 cdev */struct cdev cdev;cdev.owner = THIS_MODULE;cdev_init(&cdev, &dev_fops);		//初始化字符设备结构,设置file_operationscdev_add(&cdev, devid, DEV_CNT);    //添加至内核,"cat /proc/devices"能查看

2.3 自动创建设备节点


mknod 命令可以手动创建设备节点,简单介绍如下:

$ mknod /dev/test c 252 0        #生成/dev/test,c表示字符设备,主设备号为 252,次设备号为 0

主设备号可以用 "cat /proc/devices" 查看。下面主要介绍自动创建设备节点。

2.3.1 class_create创建类

#define class_create(owner, name)       \

({                      \

    static struct lock_class_key __key; \

    __class_create(owner, name, &__key);    \

})

void class_destroy(struct class *cls)                //注销class

调用class_create后,会在/sys/class/下创建类文件夹,文件夹名由函数的第二个入参决定。

2.3.2 device_create创建设备

struct device *device_create(struct class *class, struct device *parent,

                 dev_t devt, void *drvdata, const char *fmt, ...)

功能:调用device_create,会在/sys/class/xxx类下面创建一个设备,通过udev在/dev/目录下生成设备文件节点,文件名由fmt参数决定。

参数:

  • class:表示设备要创建哪个类下面;
  • parent:父设备, 一般为 NULL, 也就是没有父设备;
  • devt:设备号;
  • drvdata:设备可能会使用的一些数据, 一般为 NULL;
  • fmt:设备名字, 如果设置 fmt=xxx 的话, 就会生成/dev/xxx 这个设备文件;

返回值:创建好的设备

删除设备时调用:

void device_destroy(struct class *class, dev_t devt)        //注销device        


udev工作过程如下:
(1)当内核检测到系统中出现新设备后,内核会通过netlink套接字发送uevent;
(2)设备模块加载时udev获取内核发送的信息,扫描/sys/class/下的设备目录进行规则匹配。从而在/dev/创建设备节点;
 

2.3.3 自动创建设备节点示例

static struct class *class;		/* 类 		*/
static struct device *device;	/* 设备		*//* 4、创建类 */class = class_create(THIS_MODULE, "test");	///sys/class/目录下会创建一个新的文件夹if (IS_ERR(class)) {return PTR_ERR(class);}/* 5、创建设备 */device = device_create(class, NULL, devid, NULL, "test");//dev目录下创建相应的设备节点if (IS_ERR(device)) {return PTR_ERR(device);}

二、字符设备示例


#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>static dev_t devid;						/* 设备号	*/
static int major;						/* 主设备号	*/
static int minor;						/* 次设备号	*/
static struct cdev cdev;				/* cdev		*/
static struct class *class;				/* 类 		*/
static struct device *device;			/* 设备		*/#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"cdev_test"		/* 名字 */static int chardev_open(struct inode *inode, struct file *filp)
{//filp->private_data = &cdev_data;  /* 设置私有数据 */printk("open...\n");return 0;
}static ssize_t chardev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{printk("read...\n");return 0;
}static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{printk("write...\n");return cnt;
}static int chardev_release(struct inode *inode, struct file *filp)
{printk("release...\n");return 0;
}/* 设备操作函数 */
static struct file_operations chardev_fops = {.owner = THIS_MODULE,.open = chardev_open,.read = chardev_read,.write = chardev_write,.release = 	chardev_release,
};/* 入口函数 */
static int __init chardev_init(void)
{
/* 注册字符设备驱动 *//* 1、创建设备号 */if (major) { /* 定义了设备号 */devid = MKDEV(major, 0);register_chrdev_region(devid, CDEV_CNT, CDEV_NAME);} else { /* 没有定义设备号 */alloc_chrdev_region(&devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */major = MAJOR(devid); /* 获取分配号的主设备号 */minor = MINOR(devid); /* 获取分配号的次设备号 */}printk("major=%d, minor=%d\r\n", major, minor);/* 2、初始化 cdev */cdev.owner = THIS_MODULE;cdev_init(&cdev, &chardev_fops);				//file_operations/* 3、添加一个 cdev */cdev_add(&cdev, devid, CDEV_CNT);/* 4、创建类 */class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹if (IS_ERR(class)) {return PTR_ERR(class);}/* 5、创建设备 */device = device_create(class, NULL, devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点if (IS_ERR(device)) {return PTR_ERR(device);}return 0;
}/* 出口函数 */
static void __exit chardev_exit(void)
{/* 注销字符设备驱动 */cdev_del(&cdev); /* 删除 cdev */unregister_chrdev_region(devid, CDEV_CNT); /* 注销 */device_destroy(class, devid);class_destroy(class);
}module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("donga");

本示例read、write函数只打印信息演示作用。在实际使用中,read函数通常会调用 copy_to_user 将内核空间的数据拷贝到用户空间,write函数则调用  copy_from_user 将用户空间数据拷贝到内核空间,再做处理。

1、命令测试


$ insmod chardev_drv.ko        #加载驱动,生成/dev/cdev_test设备节点

$ cat /proc/devices                 #查看字符设备

$ cat  /dev/cdev_test                   #读

$ echo "1" > /dev/cdev_test        #写

/ # cat /dev/cdev_test                    #读,过程:open->read->release
open...
read...
release...

/ # echo "1" > /dev/cdev_test        #写,过程:open->write->release
open...
write...
release...

2、应用程序读写


#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"int main(int argc, char *argv[])
{int fd, retvalue;char *filename;if(argc != 2){printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);    //打开设备文件if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}write(fd, NULL, 0);       //写read(fd, NULL, 0);        //读retvalue = close(fd);     //关闭文件if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

执行过程:


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/51845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手摸手教你撕碎西门子S7通讯协议06--S7Read读取short数据

1、S7通讯回顾 - &#xff08;1&#xff09;建立TCP连接 Socket.Connect-》已实现 - &#xff08;2&#xff09;发送访问请求 COTP-》已实现 - &#xff08;3&#xff09;交换通信信息 Setup Communication-》已实现 - &#xff08;4&#xff09;执行相关操作 …

如何使用rdma-core来实现RDMA操作

rdma-core 是一个开源项目&#xff0c;为远程直接内存访问&#xff08;RDMA&#xff09;提供用户空间的支持。它包括 RDMA 设备的驱动程序、库和工具&#xff0c;旨在简化 RDMA 应用的开发和部署。 基础知识参考博文&#xff1a; 一文带你了解什么是RDMA RDMA 高性能架构基本…

Langchain--如何使用大模型 2.0

【&#x1f34a;易编橙终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 Langch…

【已解决】嵌入式linux mobaxterm unable to open connection to comx 串口正常连接,但终端无法输入

1.点击Session重新选择串口&#xff0c;注意看看串口是不是连接到虚拟机&#xff0c;导致串口被占用。 2.选择PC机与开发板连接的串口&#xff0c;不知道的话可以打开设备管理器看看&#xff0c;选择正确的波特率&#xff0c;一般是115200。 3.关键一步&#xff1a;选择后别急…

【计算机网络原理】网络层IP协议的总结和数据链路层以太网协议的总结.

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

MobaXterm tmux 配置妥当

一、事出有因 缘由&#xff1a;接上篇文章&#xff0c;用Docker搭建pwn环境后&#xff0c;用之前学过的多窗口tmux进行调试程序&#xff0c;但是鼠标滚动的效果不按预期上下翻屏。全网搜索很难找到有效解决办法&#xff0c;最后还是找到了一篇英文文章&#xff0c;解决了&…

正点原子imx6ull-mini-Linux设备树下的LED驱动实验(4)

1&#xff1a;修改设备树文件 在根节点“/”下创建一个名为“alphaled”的子节点&#xff0c;打开 imx6ull-alientek-emmc.dts 文件&#xff0c; 在根节点“/”最后面输入如下所示内容 alphaled {#address-cells <1>;#size-cells <1>;compatible "atkalp…

25.惰性队列

介绍 消费者由于各种原因而致使长时间不能消费消息造成堆积。比如有一百万条消息发送到mq中&#xff0c;消费者这时宕机了不能消费消息&#xff0c;造成了消息堆积。惰性队列就有必要了。 正常情况下&#xff0c;消息保存在内存中。消费者从内存中读取消息消费&#xff0c;速…

游戏制作中没想明白的事情

当一个备忘录&#xff0c;有的是还没有时间去深入研究&#xff0c;或者没有从头了解 什么是建模绑定&#xff1f;为什么人物建模&#xff0c;初始化都是双手打开的&#xff1f;平着放武器&#xff0c;但运行的时候武器会自动竖起来&#xff0c;这是怎么做到的&#xff1f; 思…

KamaCoder 100. 岛屿的最大面积 + Leetcode 695. Max Area of Island

题目描述 给定一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的矩阵&#xff0c;计算岛屿的最大面积。岛屿面积的计算方式为组成岛屿的陆地的总数。岛屿由水平方向或垂直方向上相邻的陆地连接而成&#xff0c;并且四周都是水域。你可以假设矩阵外均…

XYCTF2024 WP

Pwn&#xff1a; hello_world(签到)&#xff1a; 这里的printf没有格式化字符串漏洞&#xff0c;但是我们依旧可以填充栈来利用printf泄露栈上信息 根据我们能填充的字节数来看&#xff0c;我们无法泄露出libc_start_main128的地址&#xff0c;但是可以泄露libc_start_call_m…

一款免费且功能强大的硬件检测工具,绿色小巧免安装!

HWiNFO是一款免费功能强大且广泛使用的硬件信息检测和监控工具&#xff0c;适用于Windows系统。它能够提供详细的硬件信息&#xff0c;包括CPU、主板、内存、硬盘、显卡等组件的详细规格和性能数据。此外&#xff0c;HWiNFO还支持实时监控硬件状态&#xff0c;如温度、电压和风…

【React Hooks原理 - useTransition】

概述 在上一篇中我们介绍了useDeferredValue的基本原理&#xff0c;本文主要介绍一下useTransition这个Hook&#xff0c;之所以在这里提到useDeferredValue&#xff0c;是因为这两个Hook都是在React18引入的进行渲染优化的Hooks&#xff0c;在某些功能上是重叠的&#xff0c;主…

面试面到自闭,字节软件测试岗五轮面试,四个小时灵魂拷问...

准备过程 我自己是本科毕业后在老东家干了两年多&#xff0c;老东家算是一家”小公司”(毕竟这年头没有 BAT 或 TMD 的 title 都不好意思报出身)&#xff0c;毕业这两年多我也没有在大厂待过&#xff0c;因此找坑的时候是非常非常虚的。迫于心慌&#xff0c;我好好思考了一阵来…

Android 性能优化(二):LeakCanary【用于分析代码是否存在内存泄漏】程序无响应

目录 1&#xff09;内存相关的五种常见问题 2&#xff09;内存溢出和内存泄漏 3&#xff09;LeakCanary是什么? 4&#xff09;LeakCanary如何使用&#xff0c;如何分析&#xff1f; 5&#xff09;LeakCanary监测的内容 提问&#xff1a;程序有时候很卡&#xff0c;经常会出现…

前端开发:Vue2.0桌面组件库-Element

引入Element的步骤&#xff1a; 1.在vscode终端中执行命令&#xff08;需要联网&#xff09; 下载成功 2.在main.js中导入element.ui组件库。 同上&#xff0c;自定义的组件需要先在根组件中引入。 3.访问官网&#xff0c;复制调整代码

变阻器的主要特性和参数有哪些?

变阻器的主要特性和参数有很多&#xff0c;下面将详细介绍几个重要的特性和参数&#xff1a; 1. 电阻范围&#xff1a;滑动变阻器的电阻范围是指其最大电阻值和最小电阻值之间的范围&#xff0c;这个范围通常由制造商指定&#xff0c;用户在选择变阻器时需要根据实际需求选择合…

基于 SSM 的汽车租赁系统

基于 SSM 的电器网上订购系统 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Spring、JSP、MyBatis 工具&#xff1a;MyEclipse/IDEA、Tomcat 引言 汽车租赁是在约定时间内&#xff0c;租赁经营人将租赁汽车&#xff08;包括载货汽车和载客汽车&#x…

AFSim 仿真系统--子系统几何考虑

子系统几何考虑 概述 由于WSF试图表示以多种方式运行的子系统&#xff08;传感器&#xff0c;武器或通信&#xff09;&#xff0c;因此它提供的定义属性的机制&#xff0c;如几何限制&#xff0c;可能相当令人生畏。本文档提供了关于这些机制如何运作以及如何定义行为类似于真实…