驱动-兼容不同设备-container_of
驱动兼容不同类型设备
在 Linux 驱动开发中,container_of 宏常被用来实现一个驱动兼容多种不同设备的架构。这种设计模式在 Linux 内核中非常常见,特别
是在设备驱动模型中。linux内核的主要开发语言是C,但是现在内核的框架使用了非常多的面向对象的思想,这就面临了一个用C语言
来实现面向对象编程的问题
文章目录
- 参考资料
- 典型应用场景
- 原理:利用结构体中元素指针获取结构体指针
- container_of 函数
- 理解
- C程序例子
- 实验
- 源码程序 file.c
- 源码分析
- 基本步骤
- 知识点
- container_of 当前使用分析
- read、write
- Makefile 编译脚本
- 测试程序app.c
- 加载驱动 insmod file.ko
- 生成的驱动设备
- 运行程序 ./app
- 总结
参考资料
在字符设备这块内容,所有知识点都是串联起来的,需要整体来理解,缺一不可,建议多了解一下基础知识
驱动-申请字符设备号
驱动-注册字符设备
驱动-创建设备节点
驱动-字符设备驱动框架
驱动-杂项设备
驱动-内核空间和用户空间数据交换
驱动-文件私有数据
典型应用场景
- 同一厂商的不同型号设备
- 功能相似但寄存器布局不同的设备
- 需要维护设备特定数据的场合
原理:利用结构体中元素指针获取结构体指针
Kobject是linux设备驱动模型的基础,也是设备模型中抽象的一部分。
linux内核为了兼容各种形形色色的设备,就需要对各种设备的共性进行抽象,抽象出一个基类,其余的设备只需要继承此基类就可以了。
而此基类就是kobject(暂且把它看成是一个类),但是C语言没有面向对象语法。
在C++中这样的操作非常简单,继承基类就可以了,而在C语言中需要将基类的结构体指针嵌入到派生的类中,那么为什么将基类指针嵌入就可以得到派生类的指针呢?
这个实现是一个宏:container_of。
container_of 函数
container_of 在 Linux 内核中是一个常用的宏, 用于从包含在某个结构中的指针获得结构本
身的指针, 通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地
址。 那么可以使用这个函数获取不同设备的地址, 来对不同的设备进行操作, 从而一个驱动可
以兼容不同的设备。
container_of 函数原型:container_of(ptr,type,member)
函数作用:通过结构体变量中某个成员的首地址获取到整个结构体变量的首地址。
参数含义:ptr 是结构体变量中某个成员的地址。type 是结构体的类型member 是该结构体变量的具体名字
container_of 宏的作用是通过结构体内某个成员变量的地址和该变量名, 以及结构体类型。
找到该结构体变量的地址
理解
C程序例子
#include <stdio.h>struct Base{ //定义一个Base类int var;char *string;};struct Derived{ //定义一个派生类int var;struct Base base; //派生类中包含了struct Base类};#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) //offset_of宏#define container_of(ptr, type, member) ({ \ //阉割版container_of,省去了类型检查void *__mptr = (void *)(ptr); \((type *)(__mptr - offsetof(type, member))); })struct Base *base_p; struct Derived test_derived; struct Derived *get;int main(){base_p = &test_derived.base; //赋值给指针printf("Derived addr = %x\n",(unsigned int)&test_derived);printf("=================================\n");get = container_of(base_p,struct Derived,base); //使用base_p指针获取派生类的首地址printf("get Derived addr = %x\n",(unsigned int)get);return 0;}
输出结果:
输出如下:
Derived addr = 601050
=================================
get Derived addr = 601050
源码分析:最重要的就是理解 container_of 函数
get = container_of(base_p,struct Derived,base); //使用base_p指针获取派生类的首地址
-
base_p :ptr 是结构体变量中某个成员的地址。 这个base_p 是怎么定义的 struct Base *base_p; Base 又是在哪里定义的呢? 作为派生类Deviced 的成员变量 struct Base base
-
struct Derived: type 是结构体的类型,不就是派生类的结构体类型吗? 这个函数就是要返回这个结构体类型的指针
-
base: member 是该结构体变量的具体名字。 就是派生类里面 定义的成员变量名字。
我们这里讨论和接下来讨论的目的就是要理解这个函数container_of的作用,理解参数和返回类型 以及理解实际应用的价值。
实验
使用 container_of 函数编写一个驱动兼容不同设备
源码程序 file.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>struct device_test
{dev_t dev_num; // 设备号int major; // 主设备号int minor; // 次设备号struct cdev cdev_test; // cdevstruct class *class; // 类struct device *device; // 设备char kbuf[32];
};struct device_test dev1; // 定义一个device_test结构体变量dev1
struct device_test dev2; // 定义一个device_test结构体变量dev2/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{dev1.minor = 0; // 设置dev1的次设备号为0dev2.minor = 1; // 设置dev2的次设备号为1// inode->i_rdev 为该 inode 的设备号,使用container_of函数找到结构体变量dev1 dev2的地址// 然后设置私有数据file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test);printk("This is cdev_test_open\r\n");return 0;
}/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev = (struct device_test *)file->private_data;// 如果次设备号是0,则为dev1if (test_dev->minor == 0){if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");return -1;}printk(" test_dev->kbuf is %s\r\n", test_dev->kbuf);}// 如果次设备号是1,则为dev2else if (test_dev->minor == 1){if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");return -1;}printk(" test_dev->kbuf is %s\r\n", test_dev->kbuf);}return 0;
}/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev = (struct device_test *)file->private_data;if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据{printk("copy_to_user error\r\n");return -1;}printk("This is cdev_test_read\r\n");return 0;
}static int cdev_test_release(struct inode *inode, struct file *file)
{printk("This is cdev_test_release\r\n");return 0;
}/*设备操作函数,定义file_operations结构体类型的变量cdev_test_fops*/
struct file_operations cdev_test_fops = {.owner = THIS_MODULE, // 将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = cdev_test_open, // 将open字段指向chrdev_open(...)函数.read = cdev_test_read, // 将open字段指向chrdev_read(...)函数.write = cdev_test_write, // 将open字段指向chrdev_write(...)函数.release = cdev_test_release, // 将open字段指向chrdev_release(...)函数
};static int __init chr_fops_init(void) // 驱动入口函数
{/*注册字符设备驱动*/int ret;/*1 创建设备号,,这里注册2个设备号*/ret = alloc_chrdev_region(&dev1.dev_num, 0, 2, "alloc_name"); // 动态分配设备号if (ret < 0){printk("alloc_chrdev_region is error\n");}printk("alloc_chrdev_region is ok\n");dev1.major = MAJOR(dev1.dev_num); // 获取主设备号dev1.minor = MINOR(dev1.dev_num); // 获取次设备号printk("major is %d \r\n", dev1.major); // 打印主设备号printk("minor is %d \r\n", dev1.minor); // 打印次设备号// 对设备1进行操作/*2 初始化cdev*/dev1.cdev_test.owner = THIS_MODULE;cdev_init(&dev1.cdev_test, &cdev_test_fops);/*3 添加一个cdev,完成字符设备注册到内核*/cdev_add(&dev1.cdev_test, dev1.dev_num, 1);/*4 创建类*/dev1.class = class_create(THIS_MODULE, "test1");/*5 创建设备*/dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test1");dev2.major = MAJOR(dev1.dev_num + 1); // 获取主设备号dev2.minor = MINOR(dev1.dev_num + 1); // 获取次设备号printk("major is %d \r\n", dev2.major); // 打印主设备号printk("minor is %d \r\n", dev2.minor); // 打印次设备号// 对设备2进行操作/*2 初始化cdev*/dev2.cdev_test.owner = THIS_MODULE;cdev_init(&dev2.cdev_test, &cdev_test_fops);/*3 添加一个cdev,完成字符设备注册到内核*/cdev_add(&dev2.cdev_test, dev1.dev_num + 1, 1);/*4 创建类*/dev2.class = class_create(THIS_MODULE, "test2");/*5 创建设备*/dev2.device = device_create(dev2.class, NULL, dev1.dev_num + 1, NULL, "test2");return 0;
}static void __exit chr_fops_exit(void) // 驱动出口函数
{/*注销字符设备*/unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号unregister_chrdev_region(dev1.dev_num + 1, 1); // 注销设备号cdev_del(&dev1.cdev_test); // 删除cdevcdev_del(&dev2.cdev_test); // 删除cdevdevice_destroy(dev1.class, dev1.dev_num); // 删除设备device_destroy(dev2.class, dev1.dev_num + 1); // 删除设备class_destroy(dev1.class); // 删除类class_destroy(dev2.class); // 删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");
源码分析
这个代码程序就是在一个程序驱动程序适配了两个设备,里面有两套字符设备,所以字符设备驱动实践的基本步骤都是有的:
基本步骤
这里虽然在一个驱动程序里面,但是两个字符设备在里面生成,基本步骤是有的。
- 动态申请设备号:alloc_chrdev_region
- 初始化cdev:cdev_init
- 添加一个cdev,完成字符设备注册到内核:cdev_add
- 创建类:class_create
- 创建device device_create
知识点
- private_data:这里也用到了之前的私有数据结构体,file 相关的:file->private_data;
- device_test: 匹配private_data 就有了一个结构体,作为变量的封装。 搭配private_data 使用。 这里也体现了私有数据 private_data 和 结构体的意义。封装:设备号 dev_t dev_num ; 主 次设备号:major、minor;字符设备:cdev ;class:字符类 ;device 创建的字符设备:device
- alloc_chrdev_region(&dev1.dev_num, 0, 2, “alloc_name”): 看参数数据,动态申请设备号时候,主设备号一样的,从0 开始,申请两个次设备号来使用。
container_of 当前使用分析
// ,使用container_of函数找到结构体变量dev1 dev2的地址// 然后设置私有数据file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test);
参数分析:
- struct device_test:struct device_test,不就是封装的结构体类型吗? 说明生成的结果就是封装结构体类型的指针,然后放到private_data里面。 这样在 系统调用中的read / write 就特别方便使用了。
- cdev_test : 第三个参数要求是 派生类里面,定义的基类的对象的名称。 在 结构体里面定义的如下: struct cdev cdev_test; // cdev 不就是动态注册的字符设备嘛。
- inode->i_cdev:第一个参数本身需要派生类中某个成员变量的的地址就可以了。 这里用如下 inode 就是传递过来节点的设备号。它本身也是一个结构体指针要,简要如说也如下所示:
cdev_test_open(struct inode *inode, struct file *file)
struct inode {// ...union {struct pipe_inode_info *i_pipe;struct block_device *i_bdev;struct cdev *i_cdev; // 指向字符设备结构的指针char *i_link;};// ...
};
所以,这里在open 方法中,使用了cdev_test_open 函数,最终将 封装的结构体存储在私有数据里面,传递不同的设备过来 比如打开不同设备节点,那么就会生成不同的封装的结构体类型的指针了。
read、write
核心逻辑还是上面的 知识点 container_of 函数的分析。既然封装的结构体指针都获取到了,那么在read/write 就可以通过不同的属性来适配不同的字符设备了。
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev = (struct device_test *)file->private_data;// 如果次设备号是0,则为dev1if (test_dev->minor == 0){if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");return -1;}printk(" test_dev->kbuf is %s\r\n", test_dev->kbuf);}// 如果次设备号是1,则为dev2else if (test_dev->minor == 1){if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");return -1;}printk(" test_dev->kbuf is %s\r\n", test_dev->kbuf);}return 0;
}/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev = (struct device_test *)file->private_data;if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据{printk("copy_to_user error\r\n");return -1;}printk("This is cdev_test_read\r\n");return 0;
}
Makefile 编译脚本
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += file.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean
测试程序app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd1; //定义设备1的文件描述符int fd2; //定义设备2的文件描述符char buf1[32] = "nihao /dev/test1"; //定义写入缓存区buf1char buf2[32] = "nihao /dev/test2"; //定义写入缓存区buf2fd1 = open("/dev/test1", O_RDWR); //打开设备1:test1if (fd1 < 0){perror("open error \n");return fd1;}write(fd1,buf1,sizeof(buf1)); //向设备1写入数据close(fd1); //取消文件描述符到文件的映射fd2= open("/dev/test2", O_RDWR); //打开设备2:test2if (fd2 < 0){perror("open error \n");return fd2;}write(fd2,buf2,sizeof(buf2)); //向设备2写入数据close(fd2); //取消文件描述符到文件的映射return 0;
}
编译生成可执行程序:
aarch64-linux-gnu-gcc app.c -o app
加载驱动 insmod file.ko
如下,生成了两个不同的次设备号,主设备号一样的。
生成的驱动设备
驱动加载成功之后会生成/dev/test1 和/dev/test2 设备驱动文件,如下:
root@topeet:/mnt/sdcard]# ls /dev/test1 -al
crw------- 1 root root 236, 0 Jan 12 08:18 /dev/test1
[root@topeet:/mnt/sdcard]# ls /dev/test2 -al
crw------- 1 root root 236, 1 Jan 12 08:18 /dev/test2
运行程序 ./app
结果如下所示,一切按照程序逻辑来执行的。
总结
本篇其实还是对以前技术的总结,这里着重用到了函数 container_of,有点面向对象的意思。 这里适配不同的驱动设备只是一个案例而已。
相关文章:
驱动-兼容不同设备-container_of
驱动兼容不同类型设备 在 Linux 驱动开发中,container_of 宏常被用来实现一个驱动兼容多种不同设备的架构。这种设计模式在 Linux 内核中非常常见,特别 是在设备驱动模型中。linux内核的主要开发语言是C,但是现在内核的框架使用了非常多的面向…...
UE5 检测球形范围的所有Actor
和Untiiy不同,不需要复杂的调用 首选确保角色添加了Sphere Collision 然后直接把sphere拖入蓝图,调用GetOverlappingActors来获取碰撞范围内的所有Actor...
AI大模型学习十:Ubuntu 22.04.5 调整根目录大小,解决根目录磁盘不够问题
一、说明 由于默认安装时导致home和根目录大小一样,导致根目录不够,所以我们调整下 二、调整 # 确认/home和/是否为独立逻辑卷,并属于同一卷组(VG) rootnode1:~# lsblk NAME MAJ:MIN RM SIZE…...
在ros2上使用opencv显示一张图片
1.先将图片放到桌面上 2.打开终端ctrlaltT,查看自己是否已安装opencv 3.创建工作环境 4.进入工作目录并创建ROS2包添加OpenCV依赖项 5.进入/home/kong/opencv_ws/opencv_use/src目录创建.cpp文件并编辑 6.代码如下 my_opencv.cpp #include <cstdio> #include…...
训练神经网络的原理(前向传播、反向传播、优化、迭代)
训练神经网络的原理 通过前向传播计算预测值和损失,利用反向传播计算梯度,然后通过优化算法更新参数,最终使模型在给定任务上表现更好。 核心:通过计算损失函数(通常是模型预测与真实值之间的差距)对模型参…...
每日一题(小白)暴力娱乐篇30
顺时针旋转,从上图中不难看出行列进行了变换。因为这是一道暴力可以解决的问题,我们直接尝试使用行列转换看能不能得到想要的结果。 public static void main(String[] args) {Scanner scan new Scanner(System.in);int nscan.nextInt();int mscan.next…...
【HTTPS】免费SSL证书配置Let‘s Encrypt自动续期
【HTTPS】免费SSL证书配置Lets Encrypt自动续期 1. 安装Certbot1.1 snapd1.2 certbot2. 申请泛域名证书使用 DNS 验证申请泛域名证书3.配置nginx申请的 SSL 证书文件所在目录nginx配置证书示例查看证书信息和剩余时间4.自动续期手动自动5.不同服务器使用1. 安装Certbot 1.1 sn…...
企业应如何防范 AI 驱动的网络安全威胁?
互联网技术和 AI 科技为世界开启了一个新的发展篇章。同时,网络攻击也呈现出愈发强势的发展势头:高级持续性威胁 (APT:Advanced Persistent Threat)组织采用新的战术、技术和程序 (TTP)、AI 驱动下攻击数量和速度的提高…...
决策树简介
【理解】决策树例子 决策树算法是一种监督学习算法,英文是Decision tree。 决策树思想的来源非常朴素,试想每个人的大脑都有类似于if-else这样的逻辑判断,这其中的if表示的是条件,if之后的else就是一种选择或决策。程序设计中的…...
ScrollView(滚动视图)详解和按钮点击事件
文章目录 **ScrollView(滚动视图)详解****1. 核心特性****2. 基本用法****XML 示例:简单滚动布局** **3. 水平滚动:HorizontalScrollView****4. 高级用法****(1) 嵌套滚动控件****(2) 动态添加内容****(3) 监听滚动事件** **5. 注…...
2025年3月,再上中科院1区TOP,“等级熵+状态识别、故障诊断”
引言 2025年3月,研究者在国际机械领域顶级期刊《Mechanical Systems and Signal Processing》(JCR 1区,中科院1区 Top,IF:7.9)上以“Rating entropy and its multivariate version”为题发表科学研究成果。…...
根据pdf文档生成问答并进行评估
目标是根据pdf文档生成问答,并进行评估。 首先,安装依赖 pip install PyPDF2 pandas tqdm openai -q 具体过程如下: 1、将pdf放在opeai_blog_pdfs目录下,引用依赖 2、上传pdf文件,创建向量库 3、单个提问的向量检索…...
计算机网络 - 四次挥手相关问题
通过一些问题来讨论 TCP 的四次挥手断开连接 说一下四次挥手的过程?为什么需要四次呢?time-wait干嘛的,close-wait干嘛的,在哪一个阶段?状态CLOSE_WAIT在什么时候转换成下一个状态呢?为什么 TIME-WAIT 状态…...
SLAM | 两组时间戳不同但同时开始的imu如何对齐
场景: 两个手机在支架上,同时开始采集数据 需求: 对齐两个数据集的imu数据 做到A图片 B imu 做法: 取出来两组imu数据到excel表中,画图 A组 B组: x轴 : 所有imu的时间戳减去第一个时间…...
code review时线程池的使用
一、多线程的作用 多个任务并行执行可以提升效率异步,让与主业务无关的逻辑异步执行,不阻塞主业务 二、问题描述 insertSelective()方法是一个并发度比较高的业务,主要是插入task到任务表里,新建task,并且insertSele…...
物流网络暗战升级DHL新布局将如何影响eBay卖家库存分布策略?
物流网络暗战升级:DHL新布局将如何影响eBay卖家库存分布策略? 跨境电商发展迅猛,卖家对物流的依赖程度不言而喻。尤其是平台型卖家,例如在eBay上经营多站点的卖家,物流成本和时效几乎直接决定了利润空间与客户满意度。…...
JAMA Netw. Open:机器学习解码大脑:精准预测PTSD症状新突破
创伤后应激障碍(PTSD)是一种常见的心理健康状况,它可以在人们经历或目睹创伤性事件(如战争、严重事故、自然灾害、暴力攻击等)后发展。PTSD的症状可能包括 flashbacks(闪回)、噩梦、严重的焦虑、…...
域控制器升级的先决条件验证失败,证书服务器已安装
出现“证书服务器已安装”导致域控制器升级失败时,核心解决方法是卸载已安装的证书服务。具体操作如下: 卸载证书服务 以管理员身份打开PowerShell,执行命令: Remove-WindowsFeature -Name AD-Certificate该命令会移除A…...
Node.js入门
Node.js入门 html,css,js 30年了 nodejs环境 09年出现 15年 nodejs为我们解决了2个方面的问题: 【锦上添花】让我们前端工程师拥有了后端开发能力(开接口,访问数据库) - 大公司BFF(50)【✔️】前端工程…...
使用CubeMX新建EXTI外部中断工程——不使用回调函数
具体的使用CubeMX新建工程的步骤看这里:STM32CubeMX学习笔记(3)——EXTI(外部中断)接口使用_cubemx exti-CSDN博客 之前一直都是在看野火的视频没有亲手使用CubeMX生成工程,而且野火给的例程代码框架和自动生成的框架也不一样&…...
Verilog的整数除法
1、可变系数除法实现----利用除法的本质 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2025/04/15 13:45:39 // Design Name: // Module Name: divide_1 // Project Name: // Target Devices: // Tool Versions: // Description: // // Depe…...
win32汇编环境,网络编程入门之十九
;win32汇编环境,网络编程入门之十九 ;在这一编程里,我们学习一下如何使用gethostbyname函数,也顺便学一下如何将C定义的函数在WIN32汇编环境中使用 ;先看一下官方解释:从主机数据库中检索与主机名对应的主机信息。 ;它的原理是从你的电脑DNS中…...
Java学习手册:Java线程安全与同步机制
在Java并发编程中,线程安全和同步机制是确保程序正确性和数据一致性的关键。当多个线程同时访问共享资源时,如果不加以控制,可能会导致数据不一致、竞态条件等问题。本文将深入探讨Java中的线程安全问题以及解决这些问题的同步机制。 线程安…...
在生信分析中,从生物学数据库中下载的序列存放在哪里?要不要建立一个小型数据库,或者存放在Gitee上?
李升伟 整理 在Galaxy平台中使用时,从NCBI等生物学数据库下载的DNA序列的存储位置和管理方式需要根据具体的工作流程和需求进行调整。以下是详细的分步说明和建议: 一、Galaxy中DNA序列的默认存储位置 在Galaxy的“历史记录”(History&…...
Python异步编程入门:Async/Await实战详解
引言 在当今高并发的应用场景下,传统的同步编程模式逐渐暴露出性能瓶颈。Python通过asyncio模块和async/await语法为开发者提供了原生的异步编程支持。本文将手把手带你理解异步编程的核心概念,并通过实际代码案例演示如何用异步爬虫提升10倍效率&#…...
cmd 终端输出乱码问题 |Visual Studio 控制台输出中文乱码解决
在网上下载,或者移植别人的代码到自己的电脑,使用VS运行后,控制台输出中文可能出现乱码。这是因为源代码的编码格式和控制台的编码格式不一致。 文章目录 查看源代码文件编码格式查看输出控制台编码格式修改编码格式修改终端代码页 补充总结 …...
【算法】椭圆曲线签名(ECDSA)
🤔什么是椭圆曲线签名(ECDSA)? 椭圆曲线签名算法(Elliptic Curve Digital Signature Algorithm,简称 ECDSA)是一种基于 椭圆曲线密码学 的数字签名算法。它主要用于加密货币(如 Bit…...
Linux下使用MTK的SP_Flash_tool刷机工具
MTK的SP_Flash_tool刷机工具安装流程如下: 1、解压SP_Flash_Tool_Linux_v5.1336.00.100_Customer.zip unzip SP_Flash_Tool_exe_Linux_64Bit_v5.1520.00.100.zip 2、首先安装 libusb-dev 这个包: sudo apt-get install libusb-dev 3、安装成功之后…...
FRP内网穿透代理两个web页面(多端口内网穿透)
内网机器代理两个web页面出来 下载frp 选择0.51.2版本下载,高版本测试为成功 frp下载地址 部署frp server端(公网部署) #上传到opt rootsdgs-server07:/opt# ll frp_0.51.2_linux_amd64.tar.gz -rw-r--r-- 1 root root 11981480 Apr 15 1…...
Jenkins插件下载慢解决办法
jenkins设置插件使用国内镜像_jenkins 国内镜像-CSDN博客 国内源 以下是一些常用的国内 Jenkins 插件更新源地址: 清华大学:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json华为开源镜像站:https://mirrors.huawei…...
【Unity笔记】Unity开发笔记:ScriptableObject实现高效游戏配置管理(含源码解析)
在Unity开发中,高效管理游戏配置数据是提升开发效率的关键。本文分享如何使用ScriptableObject构建可编辑的键值对存储系统,并实现运行时动态读取。 一、为什么选择ScriptableObject? 1.1 ScriptableObject的核心优势 独立资源:…...
FPAG IP核调用小练习
一、调用步骤 1、打开Quartus 右上角搜索ROM,如图所示 2、点击后会弹出如图所示 其中文件路径需要选择你自己的 3、点击OK弹出如图所示 图中红色改为12与1024 4、然后一直点NEXT,直到下图 这里要选择后缀为 .mif的文件 5、用C语言生成 .mif文件 //…...
vue动画
1、动画实现 (1)、操作css的transition或animation (2)、在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名 (3)、过渡的相关类名: xxx-enter-active: 进入的时候激活…...
大数据学习(106)-hivesql函数
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一…...
AI日报 - 2025年04月16日
🌟 今日概览(60秒速览) ▎🤖 模型井喷 | OpenAI (o3/o4-mini, GPT-4.1), Meta (Llama 4 Scout/Maverick), Z.ai (GLM-4家族), Cohere (Embed 4), Google (DolphinGemma) 等发布新模型,多模态、长文本、高效推理成焦点。 ▎💼 商业…...
C# 经纬度坐标的精度及WGS84(谷歌)、GCJ02(高德)、BD09(百度)坐标相互转换(含高精度转换)
1. 概述 WGS-84坐标系(World Geodetic System一1984 Coordinate System)是一种国际上采用的地心坐标系,GCJ-02是由中国国家测绘局(G表示Guojia国家,C表示Cehui测绘,J表示Ju局)制订的地理信息系…...
案例:陌陌聊天数据分析
背景分析: 陌陌作为聊天平台每天都会有大量的用户在线,会出现大量的聊天数据,通过对 聊天数据的统计分析 ,可以更好的 对用户构建精准的 用户画像 ,为用户提供更好的服务以及实现 高 ROI 的平台运营推广ÿ…...
关闭谷歌浏览器(Google Chrome)的自动更新可以通过以下方法实现。具体操作步骤取决于你的操作系统。
关闭谷歌浏览器(Google Chrome)的自动更新可以通过以下方法实现。具体操作步骤取决于你的操作系统。 1. 在 Windows 上关闭 Chrome 自动更新2. 在 macOS 上关闭 Chrome 自动更新3. 在 Linux 上关闭 Chrome 自动更新4. 注意事项1. 在 Windows 上关闭 Chro…...
进程(完)
今天我们就补充一个小的知识点,查看进程树命令,来结束我们对linux进程的学习,那么话不多说,来看. 查看进程树 pstree 基本语法: pstree [选项] 优点:可以更加直观的来查看进程信息 常用选项: -p:显示进程的pid -uÿ…...
(劳特巴赫调试器学习笔记)四、Practice脚本.cmm文件编写
Lauterbach调试器 文章目录 Lauterbach调试器一、什么是Practice脚本文件二、cmm脚本使用示例总结 一、什么是Practice脚本文件 官方文档解释: 因为Practice脚本以cmm为后缀,所以大多数人叫它cmm脚本。 以tricore为例,在安装目录下ÿ…...
并行流parallelStream.map().collect()
一、使用场景 先贴代码 public static void main(String[] args) {List<String> stringList new ArrayList<>();List<Integer> integerList new ArrayList<>();int num 10000;for (int i 0;i<num;i){stringList.add(String.valueOf(i));}stri…...
2025最新版flink2.0.0安装教程(保姆级)
Flink支持多种安装模式。 local(本地)——本地模式 standalone——独立模式,Flink自带集群,开发测试环境使用 standaloneHA—独立集群高可用模式,Flink自带集群,开发测试环境使用 yarn——计算资源统一…...
软件测试小讲
大家好,我是程序员小羊! 前言: 在 Web 项目开发中,全面的测试是保证系统稳定性、功能完整性和良好用户体验的关键。下面是一个详细的 Web 项目测试点列表,涵盖了不同方面的测试: 1. 功能测试 确保应用…...
DP35 【模板】二维前缀和 ---- 前缀和
目录 一:题目 二:算法原理 三:代码实现 一:题目 题目链接:【模板】二维前缀和_牛客题霸_牛客网 二:算法原理 三:代码实现 #include <iostream> #include <vector> using name…...
C语言——分支语句
在现实生活中,我们经常会遇到作出选择和判断的时候,在C语言中也同样要面临作出选择和判断的时候,所以今天,就让我们一起来了解一下,C语言是如何作出选择判断的。 目录 1.何为语句? 2.if语句 2.1 if语句的…...
使用Docker安装Jenkins
1、准备 2、安装 详见: https://www.jenkins.io/doc/book/installing/ https://www.jenkins.io/zh/doc/book/installing/ https://www.jenkins-zh.cn/tutorial/get-started/install/ # 方式1: # 详见:https://www.jenkins.io/doc/book/inst…...
东方博宜OJ ——2395 - 部分背包问题
贪心入门 ————2395 - 部分背包问题 2395 - 部分背包问题题目描述输入输出样例问题分析贪心算法思路代码实现总结 2395 - 部分背包问题 题目描述 阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N (N < 100)堆金币,第i堆金币的总重量和总价值分别是mi,vi (l …...
【期中准备特辑】计组,电路,信号
计组 以点带面地复习书中内容! 指令体系结构(ISA)是计算机硬件和软件的分界面 世界上第一台电子计算机是 ENIAC(埃尼阿克) 第一代计算机采用电子管作为主要器件;第二代计算机采用晶体管;第三代…...
经典算法 判断一个图是不是树
判断一个图是不是树 问题描述 给一个以0 0结尾的整数对列表,除0 0外的每两个整数表示一条连接了这两个节点的边。假设节点编号不超过100000大于0。你只要判断由这些节点和边构成的图是不是树。是输出YES,不是输出NO。 输入样例1 6 8 5 3 5 2 6 4 5…...
力扣 283 移动零的两种高效解法详解
目录 方法一:两次遍历法 方法二:单次遍历交换法 两种方法对比 在解决数组中的零移动到末尾的问题时,我们需要保持非零元素的顺序,并原地修改数组。以下是两种高效的解法及其详细分析。 方法一:两次遍历法 思路分析…...