Linux内核|字符设备

news2024/9/27 12:14:38

Linux内核是怎么设计字符设备的

Linux哲学

一切皆文件

如何把字符设备抽象成文件
复习文件描述符本质

open()函数,在文件系统中找到指定文件的操作接口,绑定到进程task_srtuct->files_struct->fd_array[]->file_operations

在这里插入图片描述

在这里插入图片描述

思路

把底层寄存器配置操作放在文件操作接口里面,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件来设置底层寄存器

硬件层原理
基本接口实现
  • 查原理图,数据手册,确定底层需要配置的寄存器

  • 类似裸机开发

  • 实现一个文件的底层操作接口,这是文件的基本特征

    struct file_operations 
    

    ebf-buster-linux/include/linux/fs.h

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_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_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;
  • 几乎所有成员都是函数指针,用来实现文件的具体操作
驱动层原理

把file_operations文件操作接口注册到内核,内核通过主次设备号来登记记录它

  • 构造驱动基本对象:struct cdev,里面记录具体的file_operations

    cdev_init()
    
  • 两个hash表

    • chrdevs:登记设备号

      __register_chrdev_region()
      
    • cdev_map->probe:保存驱动基本对象struct cdev

      cdev_add()
      
文件系统层原理

mknod指令+主从设备号

  • 构建一个新的设备文件

  • 通过主次设备号在cdev_map中找到cdev->file_operations

  • 把cdev->file_operations绑定到新的设备文件中

到这里,应用程序就可以使用open()、write()、read()等函数来控制设备文件了

设备号的组成与哈希表

设备号

ebf-buster-linux/include/linux/kdev_t.h

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))  //主
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

理论取值范围
主设备号:2^12=1024*4=4k
次设备号:2^20=1024*1024=1M
  • 已注册的设备号可以使用cat /proc/devices查看

  • 内核是希望一个设备驱动(file_operation)可以独自占有一个主设备号和多个次设备号,而通常一个设备文件绑定一个主设备号和一个次设备号,所以设备驱动与设备文件是一对一或者一对多的关系。

hash table

哈希表、散列表
在这里插入图片描述

  • 数组的优缺点:查找快,增删元素效率低,容量固定

  • 链表的优缺点:查找慢,增删元素效率高,容量不限

  • 哈希表:数组+链表

    • 以主设备号为编号,使用哈希函数f(major)=major%255来计算数组下标
    • 主设备号冲突(如0、255),则以次设备号为比较值来排序链表节点。

哈希函数的设计目标:链表节点尽量平均分布在各个数组元素中,提高查询效率

从源码看如何管理设备号

关键数据结构梳理

ebf-buster-linux/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];
__register_chrdev_region函数分析

ebf-buster-linux/fs/char_dev.c

保存新注册的设备号到chrdevs哈希表中,防止设备号冲突

分析结论:

  • 主设备号为0,动态分配设备号:

    • 优先使用:255~234
  • 其次使用:511~384

  • 主设备号最大为512

从源码看如何保存file_operation接口

关键数据结构梳理

kernel/ebf-buster-linux/include/linux/cdev.h

字符设备管理对象

struct cdev {
	//内核驱动基本对象
    struct kobject kobj;
	//相关内核模块
    struct module *owner;
	//设备驱动接口
    const struct file_operations *ops;
	//链表节点
    struct list_head list;
	//设备号
    dev_t dev;
	//次设备号的数量
    unsigned int count;

} __randomize_layout;

ebf-buster-linux/fs/char_dev.c

哈希表probes

struct kobj_map {
	struct probe {
		//指向下一个链表节点
		struct probe *next;
		//设备号
		dev_t dev;
		//次设备号的数量
		unsigned long range;
		struct module *owner;
		kobj_probe_t *get;
		int (*lock)(dev_t, void *);
		//空指针,内核常用技巧
		void *data;
	} *probes[255];
	struct mutex *lock;
};
cdev_init函数分析

ebf-buster-linux/fs/char_dev.c

保存file_operation到cdev中

cdev_add函数分析

ebf-buster-linux/fs/char_dev.c

根据哈希函数保存cdev到probes哈希表中,方便内核查找file_operation使用

如何创建一个设备文件

mknod引入

创建指定类型的特殊文件

mknod --help

用法:mknod [选项]... 名称 类型 [主设备号 次设备号]
Create the special file NAME of the given TYPE.
...
当类型为"p"时可不指定主设备号和次设备号,否则它们是必须指定的。
如果主设备号和次设备号以"0x"或"0X"开头,它们会被视作十六进制数来
解析;如果以"0"开头,则被视作八进制数;其余情况下被视作十进制数。
可用的类型包括:

  b      创建(有缓冲的)区块特殊文件
  c, u   创建(没有缓冲的)字符特殊文件
  p      创建先进先出(FIFO)特殊文件

如:

mkmod /dev/test c 2 0

原理分析

![2023-10-09T04:03:54.png][4]

init_special_inode函数分析

ebf-buster-linux/fs/inode.c

判断文件的inode类型,如果是字符设备类型,则把def_chr_fops作为该文件的操作接口,并把设备号记录在inode->i_rdev。

要点

inode上的file_operation并不是自己构造的file_operation,而是字符设备通用的def_chr_fops,那么自己构建的file_operation等在应用程序调用open函数之后,才会绑定在文件上。

open函数如何查找file_operation接口

![2023-10-09T04:11:23.png][5]

  • get_unused_fd_flags

    • 为本次操作分配一个未使用过的文件描述符
  • do_file_open

    • 生成一个空白struct file结构体
    • 从文件系统中查找到文件对应的inode
  • do_dentry_open

static int do_dentry_open(struct file *f,
			  struct inode *inode,
			  int (*open)(struct inode *, struct file *))
{
	...
	/*把inode的i_fop赋值给struct file的f_op*/
	f->f_op = fops_get(inode->i_fop);
	...
	if (!open)
		open = f->f_op->open;
	if (open) {
		error = open(inode, f);
		if (error)
			goto cleanup_all;
	}
	...
}
  • def_chr_fops->chrdev_open

    ​ ebf-buster-linux/fs/char_dev.c

static int chrdev_open(struct inode *inode, struct file *filp)
{
	const struct file_operations *fops;
	struct cdev *p;
	struct cdev *new = NULL;
	...
	struct kobject *kobj;
	int idx;
	/*从内核哈希表cdev_map中,根据设备号查找自己注册的sturct cdev,获取cdev中的file_operation接口*/
	kobj = kobj_lookup(cdev_map, inode>i_rdev,&idx);
	new = container_of(kobj, struct cdev, kobj);
	...
	inode->i_cdev = p = new;
	...
	fops = fops_get(p->ops);
	...
	/*把cdev中的file_operation接口赋值给struct file的f_op*/
	replace_fops(filp, fops);
	
	/*调用自己实现的file_operation接口中的open函数*/
	if (filp->f_op->open) {
		ret = filp->f_op->open(inode, filp);
		if (ret)
			goto out_cdev_put;
	}
	...
}

led字符设备驱动实验

驱动模块= 内核模块(.ko)+ 驱动接口(file_operations)

  • 在内核模块入口函数里获取gpio相关寄存器并初始化

  • 构造file_operations接口,并注册到内核

  • 创建设备文件,绑定自定义file_operations接口

  • 应用程序echo通过写设备文件控制硬件led

驱动模块初始化
地址映射

GPIO寄存器物理地址和虚拟地址映射

ebf-buster-linux/arch/arm/include/asm/io.h

void __iomem *ioremap(resource_size_t res_cookie, size_t size)

参数:

  • res_cookie:物理地址

  • size:映射长度

返回值:

  • void * 类型的指针,指向被映射的虚拟地址
  • __iomem 主要是用于编译器的检查地址在内核空间的有效性
虚拟地址读写
readl()/ writel()	//过时

void iowrite32(u32 b, void __iomem *addr)   //写入一个双字(32bit)

unsigned int ioread32(void __iomem *addr)   //读取一个双字(32bit)

检查cpu大小端,调整字节序,以提高驱动的可移植性

自定义led的file_operations接口
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = led_release,
};
  • owner:设置驱动接口关联的内核模块,防止驱动程序运行时内核模块被卸载
  • release:文件引用数为0时调用
拷贝数据

include/linux/uaccess.h

copy_from_user(void *to, const void __user *from, unsigned long n)

参数:

  • *to:将数据拷贝到内核的地址

  • *from 需要拷贝数据的用户空间地址

  • n 拷贝数据的长度(字节)

返回值:

失败:没有被拷贝的字节数

成功:0

register_chrdev函数

ebf-buster-linux/include/linux/fs.h

注册设备号函数到内核

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}
__register_chrdev函数

kernel/ebf-buster-linux/fs/char_dev.c

int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, baseminor, count, name);
...
	cdev = cdev_alloc();
...
	cdev->owner = fops->owner;
	cdev->ops = fops;
...
	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
...
}
  • 次设备号为0,次设备号数量为256

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

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

相关文章

synchronized的介绍

1.synchronized的介绍和作用 synchronized是Java编程语言中的一个关键字&#xff0c;用于实现线程同步。在多线程编程中&#xff0c;多个线程可能同时访问共享资源&#xff0c;而这可能导致数据不一致或其他问题。为了避免这些问题&#xff0c;可以使用 synchronized 关键字来…

Android Launcher3各启动场景源码分析

文章目录 一、概述二、开机启动Launcher2.1、开机启动Launcher流程图2.2、开机启动流程源码分析 三、短压Home键启动Launcher3.1、短压Home键启动Launcher流程图3.2、短压Home键启动Launcher源码分析 四、Launcher异常崩溃后的自启动4.1、Launcher异常崩溃后的自启动流程图4.2、…

刷题总结1.19

这句话是不正确的。当对链接队列进行出队操作时&#xff0c;front指针会发生变化。 链接队列是一种基于链表实现的队列数据结构。队列的特点是先进先出&#xff0c;即首先进队的元素将首先出队。在出队操作中&#xff0c;我们需要移动front指针&#xff0c;将其指向下一个元素…

什么是兼容性测试?有哪些作用?

兼容性测试是软件测试中至关重要的一个方面&#xff0c;它主要关注确保应用程序在不同环境和平台上的正常运行&#xff0c;以提供一致、流畅的用户体验。本文将介绍什么是兼容性测试以及它在软件开发生命周期中的作用。 什么是兼容性测试? 兼容性测试是一种确保软件在各种操作…

CodeGeex全能的智能编程助手

大家好我是在看&#xff0c;记录普通人学习探索AI之路。 一、介绍 CodeGeeX&#xff0c;一款由清华大学知识工程实验室研发的基于大型模型的全能智能编程辅助工具&#xff0c;能够实现包括代码生成与补全、自动注释添加、代码翻译以及智能问答等多种功能。经过对包含前后端工…

入门设计者不容错过!5款网页原型设计工具推荐!

即时设计 即时设计是一种支持团队合作的原型设计工具&#xff0c;不限于设备和人群的使用&#xff0c;浏览器可以打开和使用。在即时设计中&#xff0c;您可以从0到1创建一个Web页面原型&#xff0c;具有钢笔、矩形、矢量编辑、轮廓、文本、色彩填充等设计功能&#xff0c;足以…

鸿蒙原生应用/元服务实战-AGC团队账户

多人及内外结合去开发运营鸿蒙原生应用元服务时&#xff0c;需要用到团队账户&#xff0c;AGC提供了强大的团队角色与权限分工能力。 团队帐号是开发者联盟为实名开发者提供的多个成员帐号登录与权限管理服务。当前团队帐号支持成员参与应用市场&#xff08;付费推广、应用内付…

openGauss:准备知识1【IP地址/SSH协议/PuTTY安装和使用】

最近研究在openEuler 22.03 LTS上使用openGauss数据库。如果想要远端访问服务器&#xff0c;那么就先要了解IP地址、SSH协议等内容。 IP代表“Internet Protocol”&#xff0c;是一种网络协议&#xff0c;它定义了计算机在网络上的地址和数据传输方式。简言之&#xff0c;可以…

Unity XR 设置VR设备手柄按键按下事件

一、Unity设置 1、导入XR Interaction Toolkit插件&#xff0c;导入示例资源&#xff08;如下图&#xff09;。 2、设置新版XR输入事件 ①打开XRI Default Input Action 面板。 ②设置左手柄上的按键就点击Action Maps 列表下的 XRI LeftHand Interaction选项&#xff0c;设置…

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比(Matlab)

基于HFSS的微带线特性阻抗仿真-与基于FDTD的计算电磁学方法对比&#xff08;Matlab&#xff09; 工程下载&#xff1a; HFSS的微带线特性阻抗仿真工程文件&#xff08;注意版本&#xff1a;HFSS2023R2&#xff09;&#xff1a; https://download.csdn.net/download/weixin_445…

npm pnpm yarn 报错或常见问题处理集锦

各种卡死&#xff0c;报错问题处理汇总 1. npm 安装 卡死了怎么办&#xff0c;npm # 切换源 npm config set registry https://registry.npmmirror.com # 查看源 npm config get registry2. pnpm安装 卡死了怎么办 方法1&#xff1a;切换源 npx pnpm config set registry h…

从QObject类及非QObject类实现多继承,需把QObject放在继承链最前面

在开发中&#xff0c;有时需要实现多继承&#xff0c;如下定义了一个抽象类作为接口&#xff1a; // 接收CAN数据接口类#ifndef _RECVCANDATA_INTERFACE_H #define _RECVCANDATA_INTERFACE_H#include"cansocketlinux.h" class CRecvCanDataInterface {public: // vi…

C++ 设计模式之备忘录模式

【声明】本题目来源于卡码网&#xff08;题目页面 (kamacoder.com)&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】 -- 什么是备忘录模式 &#xff08;第17种模式&#xff09; 备忘录模式&#xff08;Meme…

KubeSphere 核心实战之一【在kubesphere平台上部署mysql】(实操篇 1/4)

文章目录 1、登录kubesphere平台2、kubesphere部署应用分析2.1、工作负载2.2、服务2.3、应用路由2.4、任务2.5、存储与配置2.6、部署应用三要素 3、部署mysql3.1、mysql容器启动实例3.2、mysql部署分析3.3、创建mysql的配置3.4、创建mysql的数据卷pvc3.5、创建mysql工作负载3.6…

力扣第236题——二叉树的最近公共祖先 (C语言题解)

题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以…

C#MQTT编程07--MQTT服务器和客户端(wpf版)

1、前言 上篇完成了winform版的mqtt服务器和客户端&#xff0c;实现了订阅和发布&#xff0c;效果666&#xff0c;长这样 这节要做的wpf版&#xff0c;长这样&#xff0c;效果也是帅BBBB帅&#xff0c;wpf技术是cs程序软件的福音。 wpf的基础知识和案例项目可以看我的另一个专…

定义域【高数笔记】

【定义域】 1&#xff0c;{知识点} 对于一个函数&#xff0c;f(x)&#xff0c;"f"是起到两个作用&#xff0c;第一&#xff0c;是对自变量的范围的约束&#xff0c;第二&#xff0c;是对运算的约束&#xff0c;同一个"f" 就有同一个约束效果 2&#xff0c;…

TPU编程竞赛系列|第八届集创赛“算能杯“报名开启!

近日&#xff0c;第八届全国大学生集成电路创新创业大赛正式开幕&#xff0c;"算能杯"以 基于TPU处理器的边缘计算系统设计 为赛题&#xff0c;围绕算能提供的多款TPU硬件&#xff0c;展开软硬件协同设计&#xff0c;创新开发算法及探索新兴应用。我们诚邀全国高校的…

表的增删改查 进阶(二)

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;MySql&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 3.新增 4.查询 聚合查询 聚合函数 GROUP BY子句 HA…

php反序列化之pop链构造(基于重庆橙子科技靶场)

常见魔术方法的触发 __construct() //创建类对象时调用 __destruct() //对象被销毁时触发 __call() //在对象中调用不可访问的方法时触发 __callStatic() //在静态方式中调用不可访问的方法时触发 __get() //调用类中不存在变量时触发&#xff08;找有连续箭头的…