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

Linux驱动(五):Linux2.6驱动编写之设备树

目录

  • 前言
  • 一、设备树是个啥?
  • 二、设备树编写语法规则
    • 1.文件类型
    • 2.设备树源文件(DTS)结构
    • 3.设备树源文件(DTS)解析
  • 三、设备树API函数
    • 1.在内核中获取设备树节点(三种)
    • 2.获取设备树节点的属性
  • 四、应用示例
    • 1.设备树
    • 2.驱动层
    • 3.应用层


前言

  本文主要讲解了一下设备树的概念,编写语法规则,API函数和使用流程,最后使用LED灯闪烁,实战验证了一下。


一、设备树是个啥?

  在Linux开源初期,Linux 系统因其开源、稳定和安全而受到了各大公司和开发者的欢迎。然后,随着越来越多个人和公司的使用,在受到追捧的同时也让Linux内核变得越来臃肿。由于当时内核中没有规范来引导,最终使之充斥着大量的屎山代码。这些垃圾的设备代码,全都来自于对应公司单板启动或运行细节强绑定,无法复用和移植
  终于,在2012年,因为Tony Lindgren,内核OMAP development tree的维护者,发送了一个邮件给Linus,请求提交OMAP平台代码修改,并附带修改以及如何解决merge conficts的这个事情。让Linus Torvalds 实在受不了了,直接在推特上爆粗口:“Gaah.Guys, this whole ARM thing is a fucking pain in the ass!!!”
  这个事件发生之后,Linux 内核社区和维护者采取了一些措施来改善这一问题,其中很关键的一条就是:ARM SOC board specific的代码被移除,由DeviceTree机制来负责传递硬件拓扑和硬件资源信息。其实设备树(Device Tree)在 Linux 内核中早在 2005 年已经引入了,只不过该事件之后,设备树的使用得到了更多关注和改进,之后设备树的推动主要是因为其在管理复杂硬件平台方面的固有优势。
  本质上,Device Tree改变了原来用hardcode方式将硬件设备配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。

在这里插入图片描述
  如上所示就是一个大致的设备树抽象图,设备树的出现大大减小了Linux内核的大小,也简化了你写驱动代码的架构。

驱动代码本来是分为两部分的:
1.设备端:提供硬件设备的资源 — 中断、GPIO 资源等
2.驱动端:驱动代码的框架 — 就是之前说过的杂项和 Linux2.6 — 他会和设备端进行匹配,然后从设备端获取硬件的资源,来驱动对应的硬件。
   但是,设备树出来之后就把设备端给替代了,我们只需要简单写几行代码将设备添加到设备树中,然后只写驱动端的代码就行了。简化了硬件描述和配置,使得内核代码中对硬件的硬编码减少了

设备树存放路径,一般都在 arch/arm/boot/dts/下。
我是用的是:

RK3588S/kernel/arch/arm64/boot/dts/rockchip/rk3588s-yyt.dts

二、设备树编写语法规则

1.文件类型

dts:写驱动时主要编写修改的文件 — 类似于 C 语言的.c 文件。
dtsi:设备的原始文件 — 一般是由厂商写 — 类似于 C 语言的.h文件。
dtb:就是设备树编译生成的二进制文件(可执行文件) — 类似于 C 语言的.o 文件。
dtc:编译设备树的工具 — 类似于 C 语言的 gcc 文件。

图示:
在这里插入图片描述

2.设备树源文件(DTS)结构

直接上图:
在这里插入图片描述
其中节点名之前的是
在这里插入图片描述

3.设备树源文件(DTS)解析

1.属性
model:描述开发板信息的属性 – 公司 芯片的型号 — 类型是一个字符串

compatible兼容性标识符,设备树属性里最重要的一个属性,用来做匹配的。
  驱动代码就是通过compatible找到目标节点的,从而获得该设备的设备信息。类型是字符串,也可以是多个字符串
例: compatible = "xyd-led", "rk-led";
查找顺序是先找 xyd-led,再找 rk-led,找到一个即可

status:设备当前的状态 ,类似与开关,四种状态。— 类型也是一个字符串

  1. “okay”:表示设备启用并且应该被驱动程序使用。设备在系统启动时会被初始化。
  2. “disabled”:表示设备被禁用。驱动程序不会加载和初始化这个设备,但他仍在设备树里。
  3. “fail”:表示设备初始化失败,通常用于在设备初始化时出现问题的情况下。设备将不会被驱动程序使用。
  4. “reserved”:表示设备被保留,通常用于未来的扩展或保留给系统内部用途。

了解:
chosen:就是你 uboot 可以给内核传递的一些参数

bootargs = "earlycon=uart8250,mmio32,0xff690000 console=ttyFIQ0 
androidboot.baseband=N/A androidboot.veritymode=enforcing 
androidboot.hardware=rk30board androidboot.console=ttyFIQ0 
androidboot.selinux=permissive init=/init kpti=0";

bootargs:在 uboot 启动的时,会把后边的这些信息传递给内核,内核就可以带着这些信息去找你的文件系统,这些信息也可以在设备树里设置。
aliases:就是给其他的节点取一个别名,你操作这个别名就相对于操作该节点
#address-cells = <2>: — 设备寄存器的地址
#size-cells = <2>: — 设备寄存器的长度 – 就是大小
以上两个都是一个无符号 32 位数值

address-cells = <1> — 代表这个地址的是 2 – 代表的是 2 个字节
size-cells = <1> — 代表这个地址长度是 2 – 代表的是 2 个字节
其实以上两个值是给 reg 属性使用的

reg = <> — 这里你就可以填写硬件设备的地址以及他的长度
地址和长度具体是几个字节的是有 address-cells 和 size-cells
例:reg = <0xff690000 0x20>

三、设备树API函数

1.在内核中获取设备树节点(三种)

struct device_node *of_find_node_by_name(struct device_node
*from, const char *name);

函数功能:通过节点名获取设备树节点
函数头文件:#include <linux/of.h>
函数参数:
from:直接写 NULL — 代表从根节点开始找
name:想要从设备树上获取的节点名
函数返回值:成功返回指向一个 struct device_node 指针,失败返回NULL。

struct device_node *of_find_compatible_node(struct device_node
*from, const char *type, const char *compatible)

函数功能:通过兼容性标识符获取设备树节点
函数头文件:#include <linux/of.h>
函数参数:
device_node:写 NULL
type:写 NULL
compatible:设备节点上的 兼容性标识符属性的值
函数返回值:成功返回指向一个 struct device_node 指针,失败返回NULL。

struct device_node *of_find_node_by_path(const char *path);

函数功能:通过给定路径获取设备树节点
函数头文件:#include <linux/of.h>
函数参数:
path:写你要查找节点的路径
比如你要找 xyd_led ,就填写 “/xyd_led” —表示从设备树上的根目录开始找 xyd_led 的节点。
函数返回值:成功返回指向一个 struct device_node 指针,失败返回NULL。

struct device_node 是设备树的一个重要结构体,用于描述设备树中的一个节点。一般我们只使用该结构中的const char *name; — 从设备树上找到的设备的节点名。

2.获取设备树节点的属性

int of_get_named_gpio(struct device_node *np,const char*propname, int index);

函数功能:从设备树(Device Tree)中获取 GPIO 的编号
函数头文件:#include <linux/of_gpio.h>
函数参数:
np:指向 device_node 结构的指针,表示设备树中的设备节点。
propname:指向包含 GPIO 编号的属性名称的字符串。例:“gpios”。
index:属性中 GPIO 编号的索引获取— 就是你要获取第几个GPIO口。
函数返回值:成功获取得到的 gpio 口的编号 失败返回负数

int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);

函数功能:从设备树中的设备节点属性中读取一个 32 位无符号整数。
函数头文件:#include <linux/of.h>
函数参数:
np:指向 device_node 结构的指针,表示设备树中的设备节点。
propname:节点里具体哪一个属性 — 属性的名字
index:获取这个属性里的一个值 — 如果一个默认就写 0
out_value:保存获取得到信息
函数返回值:成功返回 0,失败返回负数。

int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

函数功能:从设备树中的设备节点属性中读取一个字符串
函数头文件:#include <linux/of.h>
函数参数:
np:指向 device_node 结构的指针,表示设备树中的设备节点。
propname:获取的节点属性名。
out_string:保存获取得到的属性值
函数返回值:成功返回 0,失败返回负数。

四、应用示例

目标:利用设备树+字符设备驱动实现对LED等的控制

1.设备树

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/** Copyright (c) 2021 Rockchip Electronics Co., Ltd.**/
/dts-v1/;
#include "rk3588s-evb4-lp4x-yyt.dtsi"
#include "rk3588-linux.dtsi"
/ {model = "Rockchip RK3588S EVB4 LP4X V10 Board";compatible = "rockchip,rk3588s-evb4-lp4x-v10", "rockchip,rk3588";xydled:xyd_leds {compatible = "xyd_led";leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;status = "okay";};
};

  没什么好说的,就是在一个子节点中添加了两个LED灯的GPIO口,默认高电平。
  添加完,需在瑞芯微编写好的脚本去编译烧录。就是在RK3588s文件路径下的中断中使用:./build.sh kernel命令重新编译boot.img并重新烧录。

  使用该命令默认就编译设备树文件了,并且他把编译生成的设备树的 dtb 文件给继承了到 boot.img 镜像里。
开发板设备树节点的位置
/sys/firmware/devicetree/base/xyd_led
/proc/device-tree/xyd_led

为什么有两个路径?
1.不同的访问接口: /sys/firmware/devicetree/base 和 /proc/device-tree 提供不同的接口来访问设备树数据,适用于不同的需求和使用场景。sysfs 更适合用来与用户空间程序交互,而 procfs 更适合获取原始数据。

2.数据更新方式: /sys/firmware/devicetree/base 的数据可能会受到内核的处理和格式化影响,而 /proc/device-tree 则提供的是设备树的原始视图。

3.兼容性: 一些旧的工具或脚本可能仍然依赖于 /proc/device-tree,而较新的工具和机制可能使用 /sys/firmware/devicetree/base。同时,procfs 和 sysfs 具有不同的历史背景和设计目标。

2.驱动层

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>const char *out_string = NULL;
dev_t dev;//设备号
struct cdev mydev;
struct class *myclass = NULL;
int gpio_value[2] = {0};
int i=0;
int my_open (struct inode *inode, struct file *fp)
{gpio_set_value(21,1);gpio_set_value(22,1);printk("led_Open ok\n");return 0;
}int my_release (struct inode *inode, struct file *fp)
{gpio_set_value(21,0);gpio_set_value(22,0);printk("led_close ok\n");return 0;
}struct file_operations my_filop = {.open = my_open,.release = my_release,
};static int __init my_open_init(void)
{struct device_node *node=of_find_node_by_name(NULL,"xyd_leds");//寻找指定的设备节点,并将找到的设备节点返回给node里if(node == NULL){printk("该节点不存在\n");return -1;}printk("节点的名字:%s\n",node->name);//获取设备树节点的属性for(i=0;i<2;i++){gpio_value[i] = of_get_named_gpio(node, "leds-gpios", i);printk("gpio_value[%d]:%d\n",i,gpio_value[i]);}//获取设备节点上的属性of_property_read_string(node,"status",&out_string);printk("设备节点上的属性out_string:%s\n",out_string);if(strcmp("okay", out_string)!=0){printk("设备节点不可用!\n");return 0;}gpio_request(gpio_value[0],"led1");gpio_request(gpio_value[1],"led2");gpio_direction_output(gpio_value[0],1);gpio_direction_output(gpio_value[1],1);alloc_chrdev_region(&dev,0, 1, "led_tree");//注册索取设备号printk("设备号注册成功!\n");printk("主设备号:%d\n",MAJOR(dev));printk("次设备号:%d\n",MINOR(dev));cdev_init(&mydev,&my_filop);cdev_add(&mydev,dev,1);myclass = class_create(THIS_MODULE, "class_led");if(myclass == NULL){printk("class_creat error!\n");return -1;}device_create(myclass,NULL,dev,NULL,"led_shine");return 0;
}static void __exit my_open_exit(void)
{device_destroy(myclass,dev);//销毁设备节点class_destroy(myclass);//销毁设备类cdev_del(&mydev);//删除字符设备unregister_chrdev_region(dev,1);//释放设备号printk("设备注销成功\n");gpio_free(gpio_value[0]);gpio_free(gpio_value[1]);
}module_init(my_open_init);
module_exit(my_open_exit);
MODULE_LICENSE("GPL");

入口函数:

设备树
1.of_find_node_by_name寻找指定的设备节点,并将找到的设备节点返回给node里。
2.of_get_named_gpio获取节点上的属性,本例获取的就是GPIO的编号。
3.of_property_read_string获取节点上的属性,本例目的就是获取status的值。

LED灯gpio配置
1.先使用gpio_request申请GPIO口所需的资源。
2.配置工作模式。由于是控制LED灯,所以这里将模式设置为输出模式gpio_direction_output。
3.使用alloc_chrdev_region注册设备号。
4.使用cdev_init初始化设备。
5.使用cdev_add向内核申请linux2.6字符设备。
下面是自动创建节点的部分:
6.使用class_create创建一个类方便管理注册的设备。
7.使用device_create创建设备节点。

出口函数:
1.device_destroy销毁设备节点。
2.class_destroy销毁设备类。
3.cdev_del删除字符设备。
4.unregister_chrdev_region释放设备号。
5.gpio_free释放GPIO口的资源。

3.应用层

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{int fd =0;char buffer[100];const char *data = "Hello, this is a test write!";if(argc<2){printf("请输入正确的参数\n");return -1;}fd = open(argv[1],O_RDWR);if(fd<0){perror("open");return -1;}while(1){fd = open(argv[1],O_RDWR); // --- 底层的open函数sleep(1);close(fd);//底层的closesleep(1);}	return 0;
}

结果:
在这里插入图片描述

在这里插入图片描述


相关文章:

Linux驱动(五):Linux2.6驱动编写之设备树

目录 前言一、设备树是个啥&#xff1f;二、设备树编写语法规则1.文件类型2.设备树源文件&#xff08;DTS&#xff09;结构3.设备树源文件&#xff08;DTS&#xff09;解析 三、设备树API函数1.在内核中获取设备树节点&#xff08;三种&#xff09;2.获取设备树节点的属性 四、…...

算法【Java】 —— 前缀和

模板引入 一维前缀和 https://www.nowcoder.com/share/jump/9257752291725692504394 解法一&#xff1a;暴力枚举 在每次提供 l 与 r 的时候&#xff0c;都从 l 开始遍历数组&#xff0c;直到遇到 r 停止&#xff0c;这个方法的时间复杂度为 O(N * q) 解法二&#xff1a;前…...

python网络爬虫(四)——实战练习

0.为什么要学习网络爬虫 深度学习一般过程:   收集数据&#xff0c;尤其是有标签、高质量的数据是一件昂贵的工作。   爬虫的过程&#xff0c;就是模仿浏览器的行为&#xff0c;往目标站点发送请求&#xff0c;接收服务器的响应数据&#xff0c;提取需要的信息&#xff0c…...

tio websocket 客户端 java 代码 工具类

为了更好地组织代码并提高可复用性&#xff0c;我们可以将WebSocket客户端封装成一个工具类。这样可以在多个地方方便地使用WebSocket客户端功能。以下是使用tio库实现的一个WebSocket客户端工具类。 1. 添加依赖 确保项目中添加了tio的依赖。如果使用的是Maven&#xff0c;可以…...

通过卷积神经网络(CNN)识别和预测手写数字

一&#xff1a;卷积神经网络&#xff08;CNN&#xff09;和手写数字识别MNIST数据集的介绍 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称CNN&#xff09;是一种深度学习模型&#xff0c;它在图像和视频识别、分类和分割任务中表现出色。CNN通过模仿…...

【A题第二套完整论文已出】2024数模国赛A题第二套完整论文+可运行代码参考(无偿分享)

“板凳龙” 闹元宵路径速度问题 摘要 本文针对传统舞龙进行了轨迹分析&#xff0c;并针对一系列问题提出了解决方案&#xff0c;将这一运动进行了模型可视化。 针对问题一&#xff0c;我们首先对舞龙的螺线轨迹进行了建模&#xff0c;将直角坐标系转换为极坐标系&#xff0…...

一份热乎的数据分析(数仓)面试题 | 每天一点点,收获不止一点

目录 1. 已有ods层⽤⼾表为ods_online.user_info&#xff0c;有两个字段userid和age&#xff0c;现设计数仓⽤⼾表结构如 下&#xff1a; 2. 设计数据仓库的保单表&#xff08;⾃⾏命名&#xff09; 3. 根据上述两表&#xff0c;查询2024年8⽉份&#xff0c;每⽇&#xff0c…...

3 html5之css新选择器和属性

要说css的变化那是发展比较快的&#xff0c;新增的选择器也很多&#xff0c;而且还有很多都是比较实用的。这里举出一些案例&#xff0c;看看你平时都是否用过。 1 新增的一些写法&#xff1a; 1.1 导入css 这个是非常好的一个变化。这样可以让我们将css拆分成公共部分或者多…...

【Kubernetes】K8s 的鉴权管理(一):基于角色的访问控制(RBAC 鉴权)

K8s 的鉴权管理&#xff08;一&#xff09;&#xff1a;基于角色的访问控制&#xff08;RBAC 鉴权&#xff09; 1.Kubernetes 的鉴权管理1.1 审查客户端请求的属性1.2 确定请求的操作 2.基于角色的访问控制&#xff08;RBAC 鉴权&#xff09;2.1 基于角色的访问控制中的概念2.1…...

保研 比赛 利器: 用AI比赛助手降维打击数学建模

数学建模作为一个热门但又具有挑战性的赛道&#xff0c;在保研、学分加分、简历增色等方面具有独特优势。近年来&#xff0c;随着AI技术的发展&#xff0c;特别是像GPT-4模型的应用&#xff0c;数学建模的比赛变得不再那么“艰深”。通过利用AI比赛助手&#xff0c;不仅可以大大…...

秋招校招,在线性格测评应该如何应对

秋招校招&#xff0c;如果遇到在线测评&#xff0c;如何应对&#xff1f; 这里写个总结稿&#xff0c;希望对大家有些帮助。在线测评是企业深入了解求职人的渠道&#xff0c;如果是性格测试&#xff0c;会要求测试者能够快速答出&#xff0c;以便于反应实际情况&#xff08;时间…...

chrome 插件开发入门

1. 介绍 Chrome 插件可用于在谷歌浏览器上控制当前页面的一些操作&#xff0c;可自主控制网页&#xff0c;提升效率。 平常我们可在谷歌应用商店中下载谷歌插件来增强浏览器功能&#xff0c;作为开发者&#xff0c;我们也可以自己开发一个浏览器插件来配合我们的日常学习工作…...

揭开面纱--机器学习

一、人工智能三大概念 1.1 AI、ML、DL 1.1.1 什么是人工智能? AI&#xff1a;Artificial Intelligence 人工智能 AI is the field that studies the synthesis and analysis of computational agents that act intelligently AI is to use computers to analog and instead…...

Python中的私有属性与方法:解锁面向对象编程的秘密

在Python的广阔世界里&#xff0c;面向对象编程&#xff08;OOP&#xff09;是一种强大而灵活的方法论&#xff0c;它帮助我们更好地组织代码、管理状态&#xff0c;并构建可复用的软件组件。而在这个框架内&#xff0c;私有属性与方法则是实现封装的关键机制之一。它们不仅有助…...

开篇_____何谓安卓机型“工程固件” 与其他固件的区别 作用

此系列博文将分析安卓系列机型与一些车机 wifi板子等工程固件的一些常识。从早期安卓1.0起始到目前的安卓15&#xff0c;一些厂家发布新机型的常规流程都是从工程机到量产的过程。在其中就需要调试各种参数以便后续的量产参数可以固定到最佳&#xff0c;工程固件由此诞生。 后…...

DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed

DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed 文章目录 DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed问题解决办法 问题 使用 DBeaver 连接 MySQL 数据库的时候&#xff0c; 一直报错下面的错误 Public Key Retrieval is not allowed详细…...

三个月涨粉两万,只因为知道了这个AI神器

大家好&#xff0c;我是凡人&#xff0c;最近midjourney的账号到期了&#xff0c;正准备充值时&#xff0c;被一个国内AI图片的生成神器给震惊了&#xff0c;不说废话&#xff0c;先上图看看生成效果。 怎么样还不错吧&#xff0c;是我非常喜欢的国风画&#xff0c;哈哈&#x…...

vulhub GhostScript 沙箱绕过(CVE-2018-16509)

1.搭建环境 2.进入网站 3.下载包含payload的png文件 vulhub/ghostscript/CVE-2018-16509/poc.png at master vulhub/vulhub GitHub 4.上传poc.png图片 5.查看创建的文件...

李宏毅机器学习笔记——反向传播算法

反向传播算法 反向传播&#xff08;Backpropagation&#xff09;是一种用于训练人工神经网络的算法&#xff0c;它通过计算损失函数相对于网络中每个参数的梯度来更新这些参数&#xff0c;从而最小化损失函数。反向传播是深度学习中最重要的算法之一&#xff0c;通常与梯度下降…...

内推|京东|后端开发|运维|算法...|北京 更多岗位扫内推码了解,直接投递,跟踪进度

热招岗位 更多岗位欢迎扫描末尾二维码&#xff0c;小程序直接提交简历等面试。实时帮你查询面试进程。 安全运营中心研发工程师 岗位要求 1、本科及以上学历&#xff0c;3年以上的安全相关工作经验&#xff1b; 2、熟悉c/c、go编程语言之一、熟悉linux网络编程和系统编程 3、…...

编写Dockerfile第二版

目标 更快的构建速度 更小的Docker镜像大小 更少的Docker镜像层 充分利用镜像缓存 增加Dockerfile可读性 让Docker容器使用起来更简单 总结 编写.dockerignore文件 容器只运行单个应用 将多个RUN指令合并为一个 基础镜像的标签不要用latest 每个RUN指令后删除多余文…...

校验码:奇偶校验,CRC循环冗余校验,海明校验码

文章目录 奇偶校验码CRC循环冗余校验码海明校验码 奇偶校验码 码距&#xff1a;任何一种编码都由许多码字构成&#xff0c;任意两个码字之间最少变化的二进制位数就称为数据检验码的码距。 奇偶校验码的编码方法是&#xff1a;由若干位有效信息(如一个字节)&#xff0c;再加上…...

增维思考,减维问题,避免焦虑!

什么是嵌入式软件开发的核心技能&#xff1f; 1. 编程语言 熟练掌握C/C&#xff1a;C语言是嵌入式领域最重要也是最主要的编程语言&#xff0c;用于实现系统功能和性能优化。C在需要面向对象编程的场合也是重要的选择。了解汇编语言&#xff1a;在某些需要直接与硬件交互或优…...

自动化抢票 12306

自动化抢票 12306 1. 明确需求 明确采集的网站以及数据内容 网址: https://kyfw.12306.cn/otn/leftTicket/init数据: 车次相关信息 2. 抓包分析 通过浏览器开发者工具分析对应的数据位置 打开开发者工具 F12 或鼠标右键点击检查 刷新网页 点击下一页/下滑网页页面/点击搜…...

海外云服务器安装 MariaDB10.6.X (Ubuntu 18.04 记录篇二)

本文首发于 秋码记录 MariaDB 的由来&#xff08;历史&#xff09; 谈起新秀MariaDB&#xff0c;或许很多人都会感到陌生吧&#xff0c;但若聊起享誉开源界、业界知名的关系型数据库——Mysql&#xff0c;想必混迹于互联网的人们&#xff08;coder&#xff09;无不知晓。 其…...

Mybatis_基础

文章目录 第一章 Mybatis简介1.1 Mybatis特性1.2 和其它持久化层技术对比 第二章 Mybatis的增删改查第三章 Mybatis的增删改查 第一章 Mybatis简介 1.1 Mybatis特性 MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和…...

8Manage采购申请管理:轻松实现手动采购流程自动化

您是否感受到通过手动采购申请流程管理成本的压力&#xff1f; 信息的不充分常常导致现金流的不透明&#xff0c;这已成为财务高管们的常见痛点。本文将展示采购申请管理软件如何帮助您减轻负担&#xff0c;使您能够简化流程。 没有采购申请软件会面临哪些挑战&#xff1f; …...

PADS Router 入门基础教程(一)

有将近三周没有更新过博客了&#xff0c;最近在整理PADS Router 入门基础教程&#xff0c;希望喜欢本系列教程的小伙伴可以点点关注和订阅&#xff01;下面我们开始进入PADS Router课程的介绍。 一、PADS Router 快捷键 ​ 二、课程介绍 本教程主要介绍&#xff1a;PADS Rou…...

一台手机一个ip地址吗?手机ip地址泄露了怎么办

在数字化时代&#xff0c;‌手机作为我们日常生活中不可或缺的一部分&#xff0c;‌其网络安全性也日益受到关注。‌其中一个常见的疑问便是&#xff1a;‌“一台手机是否对应一个固定的IP地址&#xff1f;‌”实际上&#xff0c;‌情况并非如此简单。‌本文首先解答这一问题&a…...

【扇贝编程】使用Selenium模拟浏览器获取动态内容笔记

文章目录 selenium安装 selenium下载浏览器驱动 获取数据处理数据查找一个元素查找所有符合条件的元素 控制浏览器 selenium selenium是爬虫的好帮手&#xff0c; 可以控制你的浏览器&#xff0c;模仿人浏览网页&#xff0c;从而获取数据&#xff0c;自动操作等。 我们只要让…...