5、Linux驱动开发:设备-设备注册

news2024/11/30 8:39:41

目录

🍅点击这里查看所有博文

  随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

  想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

  很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

  同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

  既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

  本系列博客所述资料均来自互联网资料,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

字符设备架构

  字符设备的驱动架构,我们可将其分为四个部分,分别是用户层、VFS层、驱动层、物理层。

输入图片说明

用户层

  在Linux的设计理念中,一切皆是文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。

  字符设备也不例外,在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个设备文件和它对应。

root@ubuntu:/home/peng/Desktop/driver/Learning/5_cdev# ll /dev/ttyS*
crw-rw---- 1 root dialout 4, 64 Jul 25 06:30 /dev/ttyUSB0
crw-rw---- 1 root dialout 4, 65 Jul 25 06:30 /dev/ttyUSB1

  以ttyUSB设备举例,我们可以通过标准文件操作的的方式,也就是open、read、write等方式直接访问该设备。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main(void)
{
	int fd;
	fd = open("/dev/ttyUSB0",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return;
	}
	printf("open ok \n ");
}

  除文件接口之外,还可以通过shell的方式,也就是echo、cat等命令对其进行读写操作。这里不做演示。

VFS层

  在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体里面记录了这个文件的所有信息。这里操作的是字符设备驱动,需要注意的信息有设备号、设备类型、设备的结构体。

struct inode {
	// 记录设备的类型
	umode_t			i_mode;
	xxxxxxxxxxxxx
	// 记录文件所对应的设备号
	dev_t			i_rdev;
	xxxxxxxxxxxxx
	union {
		xxxxxxxxxxxxx
		// 记录描述字符设备的结构体
		struct cdev		*i_cdev;
	};
};

  当用户层打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,知道接下来要操作的设备类型(字符设备还是块设备)。

  在Linux操作系统中,每个驱动程序都要分配一个主设备号。在设备号注册章节中提到了怎么注册设备到/proc/devices中,其中就可以通过自动或者手动的方式去申请设备号。

  根据struct inode结构体里面记录的设备号,就可以找到对应的驱动程序。

  前面的操作主要是在打开文件时能找到驱动设备,在文件被打开后。VFS层会给应用层返回一个文件描述符(fd)。

  这个fd是和struct file结构体对应的。上层的应用程序就可以通过fd来找到strut file,然后在由struct file找到操作字符设备的函数接口(file_operations)了。struct file是在打开文件找到驱动设备后,才被赋值的,驱动层部分会讲到。

struct file {
    xxxxxxxxxxxxx
    //记录字符设备的操作函数
    const struct file_operations	*f_op;
    xxxxxxxxxxxxx
    //文件指针偏移值
    loff_t			f_pos;
    xxxxxxxxxxxxx
};

驱动层

  以字符设备为例,在Linux操作系统中每个字符设备都对应一个char_device_struct 结构体,该结构体和主设备号相关联。该结构体中struct cdev成员描述了字符设备所有的信息。

/*fs/char_dev.c*/
static struct char_device_struct {
    struct char_device_struct *next;
    unsigned int major;
    unsigned int baseminor;
    int minorct;
    char name[64];
    struct cdev *cdev;      /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

/* fs.h */
#define CHRDEV_MAJOR_HASH_SIZE 255

  struct cdev结构体所记录的就是设备驱动相关的数据。其中file_operations就是用户层中用户调用接口的具体实现。其他的数据设备号(完整的设备号),以及次设备的个数了解即可。

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops; //接口函数集合
	struct list_head list;//内核链表
	dev_t dev;//设备号
	unsigned int count;//次设备号个数
};

  找到struct cdev结构体后,Linux内核就会将struct cdev结构体所在的内存空间首地记录在struct inode结构体的i_cdev成员中。将struct cdev结构体的中记录的函数操作接口file_operations 地址记录在struct file结构体的f_op成员中。

物理层

  skip

驱动实现

模块挂载

  在注册设备号之后(register_chrdev_region)。我们需要将devno保存到cdev结构体中(cdev_add)。在保存之前还需要对cdev结构体进行初始化(cdev_init)。

static dev_t devno;
static struct cdev cdev;
static int hello_init(void)
{
	int result;
	int error;	
	printk("hello_init \n");
	devno = MKDEV(237,0);	
	result = register_chrdev_region(devno, 1, "hello");
	if(result<0)
	{
		printk("register_chrdev_region fail \n");
		return result;
	}
	cdev_init(&cdev,&hello_ops);
	error = cdev_add(&cdev,devno,1);
	if(error < 0)
	{
		printk("cdev_add fail \n");
		unregister_chrdev_region(devno,1);
		return error;
	}
	return 0;
}
module_init(hello_init);

驱动操作函数

  cdev初始化时,传入的就是设备文件的操作函数。这里就简单实现了一个openclose函数。

  在file_operations结构体的成员中,release 函数指针对应的就是文件的close函数。

  当用户层app调用该设备文件的open函数时,经过内核处理,理应调用到hello_open函数往dmesg中打印"hello_open()\n"。同理,app中调用到close函数时,内核驱动中会调用到hello_release 函数往dmesg中打印"hello_release()\n"

static int hello_open(struct inode *inode, struct file *filep)
{
	printk("hello_open()\n");
	return 0;
}
int hello_release (struct inode *inode, struct file *file)
{
	printk("hello_release()\n");
	return 0;
}
static struct file_operations hello_ops = 
{
	.open = hello_open,
	.release = hello_release,
};

模块注销

  模块注销时,需要按照挂载的相反顺序去执行。先删除cdev,后对chrdev进行解注册。

static void hello_exit(void)
{
	printk("hello_exit \n");
	cdev_del(&cdev);
	unregister_chrdev_region(devno,1);
	return;
}
module_exit(hello_exit);

代码验证

  文中编写的驱动模块,由于没有配套的自动关联程序,并不会将设备号自动关联到/dev/目录中的具体设备中。

  我们只需要手动运行下面的命令,即可创建设备驱动文件绑定到主设备号为237,从设备号为0的字符设备中(命令中c代表字符设备)。

root@ubuntu:# mknod /dev/hello_test0 c 237 0

  在用户层编写app(test.c),仅调用open函数打开我们注册的**/dev/hello_test0**文件即可。其他的函数暂时未实现,直接使用则会报错。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main(void)
{
	int fd;
	fd = open("/dev/hello_test0",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return;
	}
	printf("open ok \n");
	close(fd);
	printf("close ok \n");
}

  调用gcc编译app,从app的日志中可以看到打开设备文件是正确的。查看dmesg中驱动的日志,也能看到驱动中的hello_open函数被正常调用。

root@ubuntu:/# gcc ./test.c -o test
root@ubuntu:# ./test 
open ok 
close ok 
root@ubuntu:# dmesg
[ 5964.438242] hello_open()
[ 5964.438331] hello_release()

注意:若前面没有执行mknod函数去创建设备文件。或者mknod时填入的主设备号,次设备号错误。那么这里的open函数调用也是会报错的。

root@ubuntu:# rm -rf /dev/hello 
root@ubuntu:# ./test 
open fail 
: No such file or directory
root@ubuntu:# mknod /dev/hello c 238 0
root@ubuntu:# ./test 
open fail 
: No such device or address

  那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。

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

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

相关文章

12、Kubernetes中KubeProxy实现之iptables和ipvs

目录 一、概述 二、iptables 代理模式 三、iptables案例分析 四、ipvs案例分析 一、概述 iptables和ipvs其实都是依赖的一个共同的Linux内核模块&#xff1a;Netfilter。Netfilter是Linux 2.4.x引入的一个子系统&#xff0c;它作为一个通用的、抽象的框架&#xff0c;提供…

华为智能高校出口安全解决方案(3)

本文承接&#xff1a; https://qiuhualin.blog.csdn.net/article/details/133267254?spm1001.2014.3001.5502 重点讲解华为智能高校出口安全解决方案的攻击防御&安全运维&日志审计的部署流程。 华为智能高校出口安全解决方案&#xff08;3&#xff09; 课程地址攻击防…

git报错:Failed to connect to 127.0.0.1 port 1080

Bug描述 由于在试了网上的这条命令 git config --global http.proxy socks5 127.0.0.1:1080 git config --global https.proxy socks5 127.0.0.1:1080git config --global http.proxy 127.0.0.1:1080 git config --global https.proxy 127.0.0.1:1080Bug描述&#xff1a;Faile…

对负采样(negative sampling)的一些理解

负采样&#xff08;negative sampling&#xff09;通常用于解决在训练神经网络模型时计算softmax的分母过大、难以计算的问题。但在LightGCN模型论文的BPR LOSS中&#xff0c;负采样的概念可能与传统的softmax分母问题不完全一样。 在LightGCN模型中&#xff0c;不同于传统的协…

Spring结合自定义注解实现 AOP 切面功能【详解】

Spring结合自定义注解实现 AOP 切面功能 Spring AOP 注解概述Aspect 快速入门execution 切点表达式 拦截指定类的方法Pointcut("annotation(xx)") 拦截拥有指定注解的方法常用注解1.Before&#xff1a;在切点方法前执行2.After&#xff1a;在切点方法后执行3.Around&…

Python爬虫获取百度图片+重命名+帧差法获取关键帧

&#xff08;清库存&#xff09; 获取图片 重命名 帧差法 爬虫获取图片文件重命名帧差法获取关键帧 爬虫获取图片 # 图片在当前目录下生成import requests import renum 0 numPicture 0 file List []def dowmloadPicture(html, keyword):global num# t 0pic_url re.fin…

【JVM】运行时数据区之 堆——自问自答

Q:堆和栈&#xff0c;在设计上有何用义&#xff1f; 此处我们不说数据结构的概念。 堆本身是一种存储结构&#xff0c;在代码的内存层面来看&#xff0c;无论是c 操作的原生内存&#xff0c;还是Java 背后的JVM&#xff0c;堆的作用都是进行持久存储的。 这个持久存储并不是…

集合-Collection

系列文章目录 1.集合-Collection-CSDN博客 文章目录 目录 系列文章目录 文章目录 前言 一 . 集合的继承体系 二 . 什么是Collection? 三 . 常用方法 1.add(Object element): 将指定的元素添加到集合中。 2. remove(Object element): 从集合中移除指定的元素。 3. bo…

国庆day1---消息队列实现进程之间通信方式代码,现象

snd&#xff1a; #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr,"__%d__:",__LINE__);\perror(msg);\ }while(0)typedef struct{ long msgtype; //消息类型char data[1024]; //消息正文 }Msg;#define SIZE sizeof(Msg)-sizeof(long)int main…

HP E1740A 模拟量输入模块

HP&#xff08;惠普&#xff09;E1740A 模拟量输入模块是一种用于数据采集和测量的工控模块&#xff0c;通常用于各种自动化和监测应用中。以下是该模拟量输入模块的一些可能特点和功能&#xff1a; 多通道输入&#xff1a; E1740A 模块通常具有多个模拟量输入通道&#xff0c;…

windows的arp响应

1.原理‘ 2.场景 3.步骤

YOLOv8+swin_transfomerv2

测试环境&#xff1a;cuda11.3 pytorch1.11 rtx3090 wsl2 ubuntu20.04 踩了很多坑&#xff0c;网上很多博主的代码根本跑不通&#xff0c;自己去github仓库复现修改的 网上博主的代码日常出现cpu,gpu混合&#xff0c;或许是人家分布式训练了&#xff0c;哈哈哈 下面上干货…

Android回收视图

本文所有代码均存放于https://github.com/MADMAX110/BitsandPizzas 回收视图是列表视图的一个更高级也更灵活的版本。 回收视图比列表视图更加灵活&#xff0c;所以需要更多设置&#xff0c;回收视图使用一个适配器访问它的数据&#xff0c;不过与列表视图不同&#xff0c;回收…

[RCTF2015]EasySQL 二次注入 regexp指定字段 reverse逆序输出

第一眼没看出来 我以为是伪造管理员 就先去测试管理员账号 去register.php 注册 首先先注册一个自己的账号 我喜欢用admin123 发现里面存在修改密码的内容 那么肯定链接到数据库了 题目又提示是sql 那我们看看能不能修改管理员密码 首先我们猜测闭合 通过用户名 admin…

HTML,CSS,JavaScript知识点

HTML&#xff0c;CSS&#xff0c;JavaScript知识点 HTML篇 HTML是超文本标记语言。文件以.html结尾。 Hello,HTML。常用的工具: 标题: <h1>一级标题</h1><h2>二级标题</h2><h3>三级标题</h3><h4>四级标题</h4>无序列表和有…

YOLOv8+swin_transfomer

测试环境&#xff1a;cuda11.3 pytorch1.11 rtx3090 wsl2 ubuntu20.04 本科在读&#xff0c;中九以上老师或者课题组捞捞我&#xff0c;孩子想读书&#xff0c;求课题组师兄内推qaq 踩了很多坑&#xff0c;网上很多博主的代码根本跑不通&#xff0c;自己去github仓库复现修…

PHP免登录积分商城系统/动力商城/积分商城兑换系统源码Tinkphp

介绍&#xff1a; PHP免登录积分商城系统/动力商城/积分商城兑换系统源码Tinkphp&#xff0c;这个免登录积分商城系统是一种新型的电子商务模式&#xff0c;它通过省去麻烦的注册步骤&#xff0c;让用户能够很快又方便去积分兑换。这种商城系统具有UI干净整洁大方、运行顺畅的…

正点原子嵌入式linux驱动开发——STM32MP1启动详解

STM32单片机是直接将程序下载到内部 Flash中&#xff0c;上电以后直接运行内部 Flash中的程序。 STM32MP157内部没有供用户使用的 Flash&#xff0c;系统都是存放在外部 Flash里面的&#xff0c;比如 EMMC、NAND等&#xff0c;因此 STM32MP157上电以后需要从外部 Flash加载程序…

Mendix中的依赖管理:npm和Maven的应用

序言 在传统java开发项目中&#xff0c;我们可以利用maven来管理jar包依赖&#xff0c;但在mendix项目开发Custom Java Action时&#xff0c;由于目录结构有一些差异&#xff0c;我们需要自行配置。同样的&#xff0c;在mendix项目开发Custom JavaScript Action时&#xff0c;…

调度算法2-适用于交互式系统

一、时间片轮转调度算法(RR) 1.算法思想 Round-Robin 公平、轮流地为各个进程服务&#xff0c;让每个进程在一定时间间隔内都可得到响应 2.算法规则 按照各进程到达就绪队列的顺序&#xff0c;轮流让各个进程执行一个时间片 响应比(等待时间要求服务时间)/要求服务时间 3…