当前位置: 首页 > news >正文

Linux驱动开发基础(Hello驱动)

所学内容来自百问网

目录

1. 文件在内核中的表示

2. 打开字符设备节点时,内核中也有对应的struct file

3. 编写驱动程序步骤

4. 相关知识点

4.1 涉及函数解析

4.2 module_init/module_exit的实现

4.3 register_chrdev的内部实现

4.4 class_destroy/device_create 浅析

5. 示例代码

5.1 驱动代码

5.2 应用代码

5.3 Makefile

5.4 效果


1. 文件在内核中的表示

APP 打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于APP 的每一个文件句柄,在内核里面都有一个“struct file”与之对应。

使用open打开文件时,传入的flags、mode等参数会被记录在内核中对应的struct file结构体里(f_flags、f_mode):

int open(const char *pathname, int flags, mode_t mode); 

去读写文件时,文件的当前偏移地址也会保存在 struct file 结构体的 f_pos 成员里。

2. 打开字符设备节点时,内核中也有对应的struct file

注意这个结构体中的结构体:struct file_operations *f_op,这是由驱 动程序提供的。

结构体struct file_operations的定义如下:

3. 编写驱动程序步骤

1.确定主设备号,也可以让内核分配

2.定义自己的file_operations结构体

3.实现对应的drv_open/drv_read/drv_write 等函数,填入file_operations结构体

4.把file_operations 结构体告诉内核:register_chrdev

5.得有一个入口函数:安装驱动程序时,就会去调用这个入口函数

6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev

7.其他完善:提供设备信息,自动创建设备节点:class_create, device_create

4. 相关知识点

4.1 涉及函数解析

  • 将数据从内核空间复制到用户空间

    • unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

    • void __user *to:指向用户空间的指针,表示数据复制的目标地址

    • const void *from:指向内核空间的指针,表示数据复制的源地址

    • unsigned long n:要复制的字节数

    • 返回值:函数返回未能复制的字节数。如果返回值为0,则表示全部复制成功

  • 将数据从用户空间复制到内核空间

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

    • void *to:指向内核空间的指针,表示数据复制的目标地址

    • const void __user *from:指向用户空间的指针,表示数据复制的源地址

    • unsigned long n:要复制的字节数

    • 返回值:函数返回未能复制的字节数。如果返回值为0,则表示全部复制成功

  • 从文件中读取数据到用户空间缓冲区

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

    • struct file *:指向file结构体的指针,代表要读取的文件

    • char __user *:指向用户空间缓冲区的指针,用于存储从文件中读取的数据

    • size_t:要读取的字节数

    • loff_t *:指向文件中的偏移量的指针,表示从文件的哪个位置开始读取

    • 返回值:返回实际读取的字节数。如果读取失败,返回负值

  • 将用户空间缓冲区的数据写入文件

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

    • struct file *:指向file结构体的指针,代表要写入的文件

    • const char __user *:指向用户空间缓冲区的指针,包含要写入文件的数据

    • size_t:要写入的字节数

    • loff_t *:指向文件中的偏移量的指针,表示从文件的哪个位置开始写入

    • 返回值:返回实际写入的字节数。如果写入失败,返回负值

  • 打开文件,并初始化file结构体

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

    • struct inode *:指向inode结构体的指针,代表要打开的文件的元数据

    • struct file *:指向file结构体的指针,用于存储打开文件的相关信息

    • 返回值:如果打开成功,返回0;如果失败,返回负值

  • 释放文件资源,包括关闭文件、释放内存等

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

    • struct inode *:指向inode结构体的指针,代表要释放的文件的元数据

    • struct file *:指向file结构体的指针,包含要释放的文件的相关信息

    • 返回值:如果释放成功,返回0;如果失败,返回负值

4.2 module_init/module_exit的实现

一个驱动程序有入口函数、出口函数,代码如下:

module_init(hello_init); 
module_exit(hello_exit); 

驱动程序可以被编进内核里,也可以被编译为ko文件后手工加载。对于这 两种形式,“module_init/module_exit”这2个宏是不一样的。在内核文件 “include\linux\module.h”中可以看到这2个宏:

module_init(initfn) 宏用于声明模块初始化函数。当模块被加载到内核时,initfn函数会被自动调用。这个函数通常用于执行模块所需的任何初始化任务,比如注册设备、分配内存、初始化数据结构等。

module_exit(exitfn)宏用于声明模块退出函数。当模块从内核中卸载时,exitfn函数会被自动调用。这个函数通常用于执行模块卸载前的任何清理任务,比如注销设备、释放内存、清理数据结构等。

4.3 register_chrdev的内部实现

register_chrdev函数源码如下:

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函数,这个函数的代码精简如下:

01 int __register_chrdev(unsigned int major, unsigned int baseminor, 
02                       unsigned int count, const char *name, 
03                       const struct file_operations *fops) 
04 { 
05     struct char_device_struct *cd; 
06     struct cdev *cdev; 
07     int err = -ENOMEM; 
08  
09     cd = __register_chrdev_region(major, baseminor, count, name); 
10  
11     cdev = cdev_alloc(); 
12  
13     cdev->owner = fops->owner; 
14     cdev->ops = fops; 
15     kobject_set_name(&cdev->kobj, "%s", name); 
16  
17     err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); 
18 } 

这个函数主要的代码是第09行、第11~15行、第17行。

第09行,调用__register_chrdev_region函数来“注册字符设备的区域”, 它仅仅是查看设备号(major, baseminor)到(major, baseminor+count-1) 有没有被占用,如果未被占用的话,就使用这块区域。

内核中存在着一个chrdevs数组:

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]; 

去访问它的时候,并不是直接使用主设备号major来确定数组项,而是使用如下函数来确定数组项:

/* index in the above */ 
static inline int major_to_index(unsigned major) 
{ return major % CHRDEV_MAJOR_HASH_SIZE; 
} 

上述代码中,CHRDEV_MAJOR_HASH_SIZE等于255。比如主设备号1、256, 都会使用chardevs[1]。chardevs[1]是一个链表,链表里有多个 char_device_struct结构体,某个结构体表示主设备号为1的设备,某个结构 体表示主设备号为256的设备。

chardevs的结构图如图1.6所示:

由此可见:

1.chrdevs[i]数组项是一个链表头

链表里每一个元素都是一个char_device_struct结构体,每个元素表示 一个驱动程序。

char_device_struct结构体内容如下:

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 */ 
} 

它指定了主设备号major、次设备号baseminor、个数minorct,在cdev 中含有file_operations结构体。

char_device_struct结构体的含义是:主次设备号为(major, baseminor)、(major, baseminor+1)、(major, baseminor+2)、(major, baseminor+ minorct-1)的这些设备,都使用同一个file_operations来操作。

2.在图1.6中,chardevs[1]中有3个驱动程序

第1个char_device_struct结构体对应主次设备号(1, 0)、(1, 1),这 是第1个驱动程序。

第2个char_device_struct结构体对应主次设备号(1, 2)、(1, 2)、……、 (1, 11),这是第2个驱动程序。

第3个char_device_struct结构体对应主次设备号(256, 0),这是第3 个驱动程序。

第11~15行分配一个cdev结构体,并设置它:它含有file_operations 结构体。

第17行调用cdev_add把cdev结构体注册进内核里,cdev_add函数代码 如下:

01 int cdev_add(struct cdev *p, dev_t dev, unsigned count) 
02 { 
03     int error; 
04  
05     p->dev = dev; 
06     p->count = count; 
07  
08     error = kobj_map(cdev_map, dev, count, NULL, 
09              exact_match, exact_lock, p); 
10     if (error)  
11         return error; 
12  
13     kobject_get(p->kobj.parent); 
14  
15     return 0; 
16 } 

这个函数涉及kobj的操作,这是一个通用的链表操作函数。它的作用是: 把cdev结构体放入cdev_map链表中,对应的索引值是“dev”到“dev+count 1”。以后可以从cdev_map链表中快速地使用索引值取出对应的cdev。

比如执行以下代码:

err = cdev_add(cdev, MKDEV(1, 2), 10);

其中的MKDEV(1,2)构造出一个整数“1<<8 | 2”,即0x102;上述代码将 cdev放入cdev_map链表中,对应的索引值是0x102到0x10c(即0x102+10)。 以后根据这10个数值(0x102、0x103、0x104、……、0x10c)中任意一个,都 可以快速地从cdev_map链表中取出cdev结构体。

APP打开某个字符设备节点时,进入内核。在内核里根据字符设备节点的主、 次设备号,计算出一个数值(major<<8 | minor,即inode->i_rdev),然后使 用这个数值从cdev_map中快速得到cdev,再从cdev中得到file_operations 结构体。

关键函数如下:

在打开文件的过程中,可以看到并未涉及chrdevs,都是使用cdev_map。 所以可以看到在chrdevs的定义中看到如下注释:

4.4 class_destroy/device_create 浅析

驱动程序的核心是 file_operations 结构体:分配、设置、注册它。 “class_destroy/device_create”函数知识起一些辅助作用:在/sys目录下 创建一些目录、文件,这样Linux系统中的APP(比如udev、mdev)就可以根据 这些目录或文件来创建设备节点。

以下代码将会在“/sys/class”目录下创建一个子目录“hello_class”

hello_class = class_create(THIS_MODULE, "hello_class"); 

以下代码将会在“/sys/class/hello_class”目录下创建一个文件 “hello”:

device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); 

5. 示例代码

5.1 驱动代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
​
//1.确定主设备号,也可以让内核分配
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a,b) (a < b ? a : b)
//3.实现对应的drv_open/drv_read/drv_write 等函数,填入file_operations结构体
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int ret;printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);ret = copy_to_user(buf, kernel_buf, MIN(1024,size));return MIN(1024,size);
}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int ret;printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);ret = copy_from_user(kernel_buf, buf, MIN(1024,size));return MIN(1024,size);
}
static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);return 0;
}
static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);return 0;
}
​
//2.定义自己的file_operations结构体
static struct file_operations hello_drv = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,
};
​
//4.把file_operations 结构体告诉内核:register_chrdev
//5.得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init hello_init(void)
{int err;major = register_chrdev(0,"hello",&hello_drv);
​hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if(IS_ERR(hello_class)){unregister_chrdev(major,"hello");return -1;}
​device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");return 0;
}
//6.有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "hello");
}
​
//7.其他完善:提供设备信息,自动创建设备节点:class_create,  device_create
module_init(hello_init);
module_exit(hello_exit);
// 声明许可证类型
MODULE_LICENSE("GPL");

5.2 应用代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
​
/*./hello_drv_test -w abc./hello_drv_test -r
*/
int main(int argc,char **argv)
{int fd;char buf[1024];int len;
​// 判断参数if(argc < 2){printf("Usage: %s -w <string>\n",argv[0]);printf("       %s -r\n",argv[0]);return -1;}
​// 打开文件fd = open("/dev/hello",O_RDWR);if(fd == -1){printf("can not open file /dev/hello\n");return -1;}
​// 写文件或读文件if((strcmp(argv[1],"-w") == 0) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd,argv[2],len);}else{len = read(fd,buf,1024);buf[1023] = '\0';printf("APP read : %s\n",buf);}
​close(fd);return 0;
}

5.3 Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册
​
// 此处使用的是IMX6U_6ULL开发板
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
​
all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 
​
clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_drv_test
​
obj-m   += hello_drv.o

5.4 效果

linux:

开发板:

装载驱动

查看驱动是否转载成功

执行程序

相关文章:

Linux驱动开发基础(Hello驱动)

所学内容来自百问网 目录 1. 文件在内核中的表示 2. 打开字符设备节点时&#xff0c;内核中也有对应的struct file 3. 编写驱动程序步骤 4. 相关知识点 4.1 涉及函数解析 4.2 module_init/module_exit的实现 4.3 register_chrdev的内部实现 4.4 class_destroy/device_…...

centos7安装 ES集群 elasticsearch

这里写自定义目录标题 编写启动脚本 elasticsearch.sh启动可能报错&#xff1a;elasticsearch 7.10启动报错 bootstrap checks failed解决方法问题原因&#xff1a;注意 退出xshell&#xff0c;重新登录&#xff1a; 上面两个配置项改完后&#xff0c;ES启动用户(es 或root) **…...

互联网应用主流框架整合【Redis数据结构及常用命令】

在大部分情况下我们使用Redis只是执行一些简单的命令操作&#xff0c;通常无需区分是否是在一个连接池里的同一个链接去执行&#xff0c;如果需要执行多条命令&#xff0c;需要保证命令在同一个链接里完成&#xff0c;则采用SessionCallback接口操作即可 Redis数据结构-字符串…...

GORM 自动迁移与命名策略

在现代软件开发中&#xff0c;数据库结构的维护和迁移是常见的挑战之一。GORM&#xff0c;作为 Go 语言中强大的 ORM 库&#xff0c;提供了自动迁移功能&#xff0c;帮助开发者轻松地管理数据库表结构的变更。此外&#xff0c;GORM 还允许开发者通过命名策略&#xff08;Naming…...

python社会科学问题研究的计算实验

实验十五&#xff1a;社会科学问题研究的计算实践 1.实验目标及要求 &#xff08;1&#xff09;掌握网络视角 &#xff08;2&#xff09;掌握社会网络基础内容 &#xff08;3&#xff09;掌握友谊悖论 2.实验主要内容 随机生成一次符合社会网络特征的网络&#xff0c;通过计…...

Element Plus 发布 2.8.0

功能特性 组件更新 [color-picker] alpha-slider a11y (#14245 by tolking)添加 mention 组件 (#17586 by Fuphoenixes)[tree-v2] 添加 scrollTo 方法 (#14050 by kaine0923)[drawer] 添加 append-to 属性 (#17761 by tolking)[table] tree children 添加严格检查 (#13519 by t…...

解释区块链技术的应用场景和优势-水文

区块链技术是一种去中心化的分布式账本技术&#xff0c;其应用场景和优势如下&#xff1a; 金融领域&#xff1a;区块链可以用于加密货币交易&#xff0c;提供安全的、去中心化的支付系统。它也可以用于股票、债券和其他金融交易的记录和结算&#xff0c;提高交易的透明度和效率…...

等保测评基础知识(一)

1、时间类&#xff1a; 网络安全法&#xff1a; 2017年6月1日等保2.0实施时间&#xff1a; 2019年12月1日密码法&#xff1a; 2020年1月1日个人信息保护法&#xff1a; 2021年11月1日&#xff0c;数据安全法实施时间&#xff1a; 2021年9月1日关键信息基础…...

股指期货套期保值中的展期管理有哪些?

在复杂的金融市场环境中&#xff0c;展期作为一种重要的风险管理工具&#xff0c;被广泛应用于期货交易中&#xff0c;特别是当投资者需要对长期资产进行套期保值时。展期的核心思想在于&#xff0c;通过连续替换高流动性的近月期货合约来替代流动性较差的远月合约&#xff0c;…...

如何通过参考文献找到原文

当只有参考文献想要获取原文时&#xff0c;通常会用到以下方法&#xff1a; 举例参考文献1. 杨忠华,周勃,宁宝宽,等.面向新能源产业的专业研究生研创能力培养实践探索——基于“政产学研用”融合驱动[J].高教学刊,2024,10(23):19-22.DOI:10.19980/j.CN23-1593/G4.2024.23.004…...

春秋云境 | SQL | CVE-2022-4230

目录 靶标介绍 开启靶场 wpscan漏洞介绍 查询数据库表名 查询表中字段名 查询字段下数据 靶标介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (ad…...

3.串口(UART)

串口理论部分可看51部分&#xff1a;链接 数据帧 帧头(2字节&#xff0c;例如AA、BB) 数据长度&#xff08;2字节&#xff09; 数据 CRC16校验&#xff08;2字节&#xff09; 帧尾&#xff08;2字节&#xff09; 代码编写 串口一发送命令控制LED灯(PB5、PE5) LED灯、串口、…...

macOS Sonoma 14.6.1 (23G93) Boot ISO 原版可引导镜像下载

macOS Sonoma 14.6.1 (23G93) Boot ISO 原版可引导镜像下载 2024 年 8 月 8 日凌晨&#xff0c;macOS Sonoma 14.6.1 发布&#xff0c;本更新包含了重要的错误修复&#xff0c;并解决了导致高级数据保护无法启用或停用的问题。同时带来了 macOS Ventura 13.6.9 安全更新。 本…...

论企业私域流量运营中的玩法创新与开源 AI 智能名片 O2O 商城小程序的应用

摘要&#xff1a;本文旨在探讨企业在构建私域流量池时的多种玩法策略&#xff0c;并着重分析如何针对不同类型客户制定个性化方案。同时&#xff0c;引入开源 AI 智能名片 O2O 商城小程序这一工具&#xff0c;阐述其在私域流量运营中的重要作用和价值&#xff0c;为企业提升运营…...

nginx.conf alias 静态资源 别名 nginx配置

Linux系统Bug 报权限不足错误 user root; 解决server_name太长时报错的问题 #解决server_name太长时报错的问题server_names_hash_bucket_size 64; 解决文件上传默认限制1M的问题 #解决文件上传默认限制1M的问题client_max_body_size 100m; 监听所有端口 server_name _; a…...

pve虚拟机使用

文章目录 1.pve 直通硬盘 1.pve 直通硬盘 查看硬盘号&#xff1a; ls /dev/disk/by-id -lqm set 101 --virtio1 /dev/disk/by-id/usb-HIKSEMI__93963907-0:0挂载sata类型&#xff1a; qm set 101 --sata1 /dev/disk/by-id/ata-ST4000DM004-2U9104_WFN7TMVV可以将一个硬盘挂…...

Linux:进程概念详解

1. 冯诺依曼体系结构 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成 。 【注意】&#xff1a; a. 这里的存储器指的是内存 b. 不考虑缓存情况&#xff0c;这里的CPU能且只能对内存进行读写&#xff0c;不能访问外设(输入或输出设备) c.外…...

cms框架cookice注入漏洞

目录 一、环境 二、开始分析 2.1代码审计&#xff08;未授权访问&#xff09; 一、环境 环境私聊获取 二、开始分析 2.1代码审计&#xff08;未授权访问&#xff09; 我们可以看到构造函数ip是通过X_FORWARDED_FOR来获取的&#xff0c;而这个刚好可以伪造&#xff0c;那我…...

RabbitMQ高级特性 - 非持久化 / 持久化(交换机、队列、消息)

文章目录 RabbitMQ 持久化机制概述实现非持久化(交换机、队列、消息)实现持久化(交换机、队列、消息)RabbitMQ 持久化机制 概述 前面讲到了 生产者消息确认机制 和 消费者消息确认机制,保证了消息传输的可靠性,但是这还不够,试想如果 Broker 突然崩溃,那么所有的 交换…...

OpenGL ES->工作机制

渲染流程 渲染目的&#xff1a;输入3D立体坐标&#xff0c;输出绘制后的2D平面像素工作流程&#xff1a;顶点着色器->图元装配->几何着色器->光栅化->片段着色器->测试与混合&#xff0c;整个工作流程被封装在GPU内部&#xff0c;无法改变。运行在CPU的代码调用…...

ue4.27 C++ 解析内容为json的字符串

json字符串为 R"({"x": -1870.0, "y": -11400.0})"&#xff0c;里面内容是个json对象。 const FString& Message R"({"x": -1870.0, "y": -11400.0})"; TSharedRef<TJsonReader<>> Reader TJs…...

图论③ | Java | 孤岛的总面积、沉没孤岛、水流问题 、建造最大岛屿

101. 孤岛的总面积 卡玛 101. 孤岛的总面积 https://kamacoder.com/problempage.php?pid1173 孤岛是那些位于矩阵内部、所有单元格都不接触边缘的岛屿。 本题要求找到不靠边的陆地面积&#xff0c;那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都…...

基于VEH的无痕HOOK

这里的无痕HOOK指的是不破坏程序机器码,这样就可以绕过CRC或MD5的校验。 VEH利用了Windows的调试机制和异常处理,人为抛出异常,从异常的上下文中获取寄存器信息。 DLL入口 // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include "CHoo…...

芯片内部如何实现过欠压功能?

大家好,这里是大话硬件。 在前面通过推送《芯片内部如何实现VREF参考稳压源?》实现了芯片内部VREF功能,今天分享一下芯片内部是如何实现过欠压保护。 UC3842芯片系列的数据手册如下: 从上面的描述可知,芯片在工作时,需要电压达到16V,但是电压跌落到10V后,芯片就不能工…...

Basic‘ attribute type should not be a container解决方法

在使用Spring Data JPA的时候&#xff0c;实体类中定义一个用List修饰的成员ip&#xff0c;IDEA会提示Basic‘ attribute type should not be a container错误&#xff0c;导致编译不通过。 查阅一些博客和文档说是Spring Data JPA这个框架会把实体类的属性当做是MySQL数据库中…...

Linkis-RPC的设计思想

我的技术网站 java-broke.site&#xff0c;有大厂完整面经&#xff0c;工作技术&#xff0c;架构师成长之路&#xff0c;等经验分享 Linkis-RPC的设计目标是提供一种灵活、可扩展的微服务间通信机制&#xff0c;支持以下功能&#xff1a; 异步请求与响应&#xff1a;支持请求方…...

31 - memmove()函数

文章目录 1 函数原型2 参数3 返回值4 示例 1 函数原型 memmove()&#xff1a;移动内存块&#xff0c;函数原型如下&#xff1a; void * memmove ( void * destination, const void * source, size_t num );cstring库描述如下&#xff1a; Move block of memory 1. Copies th…...

【深度学习】创建和训练Transformer神经网络模型,将葡萄牙语翻译成英语

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1. 安装2. 数据处理2.1 下载数据集2.2 设置标记器2.3 使用tf.data设置数据管道 3. 测试数据集4. 定义组件4.1 嵌入和位置编码层4.2 添加并规范化4.3 基础注意力…...

[Qt][多元素控件]详细讲解

目录 0.前言1.List Widget2.Table Widget3.Tree Widget 0.前言 Qt中提供的多元素控件有&#xff1a; 列表&#xff1a; QListWidgetQListView 表格&#xff1a; QTableWidgetQTableView 树形&#xff1a; QTreeWidgetQTreeView Widget和View之间的区别&#xff0c;以QTableWi…...

/var/log/里面的文件具体是什么?linux的登录文件

1&#xff0c;什么是登录文件&#xff1f; linux系统官方对登录文件的定义解释我就不说了&#xff0c;我个人理解登录文件其实就是记录系统活动信息的几个文件&#xff0c;登录文件其实就是系统的日志文件。 比如linux系统默认是不会安装nginx的&#xff0c;nginx的日志为/var…...