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

Linux驱动(六):Linux2.6驱动编写之平台设备总线

目录

  • 前言
  • 一、平台设备总线
    • 1.是个啥?
    • 2.API函数
  • 二、设备端+驱动端
    • 1. 匹配机制
    • 2. 实现代码
  • 三、设备树+驱动端
    • 1.匹配机制
    • 2.代码实现


前言

本文主要介绍了一下两种驱动编写方法:
1.比较原始的设备端+驱动端编写方法。
2.效率较高的设备树+驱动端编写方法。
最后,使用LED闪烁实战验证了一下。


一、平台设备总线

1.是个啥?

在这里插入图片描述

  在Linux驱动开发中,平台设备总线(Platform Device Bus)是一个重要的概念,用于管理与特定平台紧密集成的设备。这些设备通常不是通过传统的总线(如PCI、USB)连接的,而是直接连接到系统的硬件平台上。
  说人话就是:平台设备总线(Platform Device Bus)是Linux内核虚拟出来的一条总线,不是真实的导线。它将底层驱动分为了设备端驱动端设备端主要就是负责提供稳定不变的东西。例如设备的信息等,大部分就是和硬件相关的(该部分可以选择用设备树写)。驱动端主要就是负责先从设备端获取稳定不变的信息,然后再去驱动一些需要变动的东西。
  平台设备总线也分为两种编写方式:1.设备端+驱动端。2.设备树+驱动端。本质上的区别就是硬件配置的管理方式不同,前者是要单独写一个驱动程序,里面包含了有关设备的所有配置信息。后者的硬件配置信息通过挂载到设备树上来管理。
注意:无论是设备端+驱动端还是设备树+驱动端他们在加载时都不用分先后。

2.API函数

注册平台设备函数:

int platform_device_register(struct platform_device *pdev);

函数参数:指向 platform_device 结构体的指针(核心结构体)

void xyd_release(struct device *dev)//虽然不用填东西,但必须要有
{
}struct platform_device {const char *name;int id;struct device dev;u32 num_resources;struct resource *resource;
}

核心结构体里的参数有很多,需要填写的部分如上所示。
name:就是和驱动端所要匹配的名字。
id:也是匹配方式的一种,这里一般填-1即可
dev:存放设备信息的核心结构体(void (*release)(struct device *dev);
num_resources:提供资源的数量,一般用ARRAY_SIZE()计算。
resource:提供的具体资源

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
};

start:资源设备的起始地址
end:资源设备的结束地址
name:给这个设备资源取的名字
flags:资源的类型 — 使用宏即可,内核已经帮你定义好了

注销平台设备函数:

void platform_device_unregister(struct platform_device *pdev)

函数参数:平台设备的核心结构体

接下来是驱动端的API函数:

platform_driver_register(struct platform_driver *prv)
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
struct device_driver driver;
};

probe:探测函数。 设备端和驱动端匹配成功时就会进入到该函数中去执行,从而去获取资源以及注册相关的设备资源。
remove:移除函数,有一端被卸载时就会进入该移除函数中执行。一般存放卸载的API函数。
driver:设备资源的核心结构体

struct device_driver {
const char *name;
const struct of_device_id *of_match_table;
};

name:驱动端和设备端进行匹配的名字
of_match_table:他是用于驱动端和设备树进行匹配的参数

struct of_device_id {
char compatible[128];//他要和设备树里的 compatible 保持一致
};

如果你使用的是驱动端+设备树的编写方法,那么此时你需要填写如下结构体:

struct device_driver {
const char *name,
}

否则你去加载内核驱动时,内核会出问题。

二、设备端+驱动端

  这种方法,现在用的已经较少了,因为比较麻烦。设备信息通常在代码中直接定义,通常适合简单的工作场景。

1. 匹配机制

  因为平台设备总线将驱动分为设备端和驱动端,那么驱动运行时,驱动端是如何找到对应的设备端,去寻找设备信息的呢?
  关键就在于:名字,设备端+驱动端的匹配是由名字来匹配的,本质上就是利用strcmp函数进行字符串比较
  设备端和驱动端都有一个属于自己的核心结构体,而各自的核心结构体里都有一个名为name的成员变量,他们就是通过该变量进行匹配的。
  在编写时一定要注意两端的name变量要保持一致

2. 实现代码

设备端

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>//定义结构体表明存放的设备信息
struct resource Led_s[2]={[0]={.start = 21,.end = 22,.name = "my_leds",.flags = IORESOURCE_MEM,},	[1]={.start = 0,.end = 1,.name = "my_leds",.flags = IORESOURCE_MEM,}	
};void xyd_release(struct device *dev)
{
}struct platform_device pdev={.name="my_leds",.id= -1,.dev={.release = xyd_release,},.num_resources = ARRAY_SIZE(Led_s),.resource = Led_s,
};static int __init mydevice_init(void)
{int a=0;printk("设备端加载函数运行!\n");a=platform_device_register(&pdev);if(a < 0){printk("platform_device_register error\n");return -1;}printk("设备端注册成功\n");	return 0;
}static void __exit mydevice_exit(void)
{printk("设备端卸载函数运行!\n");platform_device_unregister(&pdev);printk("设备端卸载完成!\n");
}module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL");

驱动端

#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>
#include <linux/platform_device.h>struct resource *led[2];
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;int my_open (struct inode *inode, struct file *fp)
{int i;for(i=led[0]->start;i<=led[0]->end;i++){gpio_set_value(i,led[1]->end);//21,1/22,1}printk("led灯:亮!\n");return 0;
}int my_release (struct inode *inode, struct file *fp)
{int i;for(i=led[0]->start;i<=led[0]->end;i++){gpio_set_value(i,led[1]->start);//21,0/22,0}printk("led灯:灭!\n");return 0;
}struct file_operations my_file = {.open = my_open,.release = my_release,
};int my_probe(struct platform_device *pdev)
{int i;for(i=0;i<=1;i++){led[i] = platform_get_resource(pdev,IORESOURCE_MEM,i);}gpio_request(led[0]->start, "led1");gpio_request(led[0]->end, "led2");gpio_direction_output(led[0] -> start, 0);gpio_direction_output(led[0] -> end, 0);//动态申请设备号alloc_chrdev_region(&dev, 0, 1, "myled");//初始化设备核心结构体cdev_init(&mydev,&my_file);//向内核申请Linux2.6字符设备cdev_add(&mydev, dev, 1);myclass = class_create(THIS_MODULE,"LED_CLASS");device_create(myclass, NULL, dev, NULL, "myleds");printk("探测函数:设备端和驱动端匹配成功\n");return 0;
}int myled_remove(struct platform_device *pdev)
{printk("移除函数运行\n");device_destroy(myclass,dev);//销毁设备节点class_destroy(myclass);//销毁设备类cdev_del(&mydev);//删除字符设备unregister_chrdev_region(dev,1);//释放设备号printk("设备注销成功\n");gpio_free(led[0]->start);gpio_free(led[1]->start);return 0;
}struct platform_driver drv = {.probe = my_probe,.remove = myled_remove,.driver = {.name = "my_leds",},
};static int __init mydriver_init(void)
{platform_driver_register(&drv);return 0;
}static void __exit mydriver_exit(void)
{platform_driver_unregister(&drv);
}	module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");

应用端

#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;
}

三、设备树+驱动端

  使用设备树+驱动端的编写方法是当前最常用效率最高的方式。通过设备树,我们能够通过几行简洁的代码完成硬件信息的挂载,从而将主要精力集中在编写驱动端的代码上。

1.匹配机制

  和设备端+驱动端的匹配方法不同,设备树+驱动端的匹配机制主要是通过设备树里的 Compatible属性来匹配的。所以在编写时,驱动端代码要和设备树上的compatible(兼容性标识符)保持一致。

设备树
在这里插入图片描述

驱动端:
在这里插入图片描述

2.代码实现

设备树:

// 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>,<&gpio0 RK_PD0 GPIO_ACTIVE_HIGH>,<&gpio1 RK_PD5 GPIO_ACTIVE_HIGH>;status = "okay";};xydkey:xyd_keys{compatible = "xyd_keys";keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;interrupt-parent = <&gpio1>;interrupts = <RK_PA7 IRQ_TYPE_LEVEL_LOW>,<RK_PB1 IRQ_TYPE_LEVEL_LOW>;		 status = "okay";};xydbeep:xyd_beep{compatible = "xyd-beep";beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;status = "okay";	};xydinfo:xyd_device{compatible = "xyd-device";devices-gpios =  <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>,<&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>,<&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;status = "okay";	};xydnod:xyd_devs{compatible = "xyd-devs";xydled1: xyd_leds {leds-gpios = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,								<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;status = "okay";};xydkey1: xyd_keys {keys-gpios = <&gpio1 RK_PA7 GPIO_ACTIVE_LOW>,<&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;status = "okay";};xydbeep1: xyd_beep {beeps-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;status = "okay";};};
};

驱动端:

#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>
#include <linux/platform_device.h>int led[2]={0};
dev_t dev;
struct cdev mydev;
struct class *myclass = NULL;int my_open (struct inode *inode, struct file *fp)
{int i;for(i=0;i<=1;i++){gpio_set_value(led[i],1);//21,1/22,1}printk("led灯:亮!\n");return 0;
}int my_release (struct inode *inode, struct file *fp)
{int i;for(i=0;i<=1;i++){gpio_set_value(led[i],0);//21,0/22,0}printk("led灯:灭!\n");return 0;
}struct file_operations my_file = {.open = my_open,.release = my_release,
};int my_probe(struct platform_device *pdev)
{//获取编号led[0] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",0);led[1] = of_get_named_gpio(pdev->dev.of_node,"leds-gpios",1);gpio_request(led[0], "led1");gpio_request(led[1], "led2");gpio_direction_output(led[0], 0);gpio_direction_output(led[1], 0);//动态申请设备号alloc_chrdev_region(&dev, 0, 1, "myled");//初始化设备核心结构体cdev_init(&mydev,&my_file);//向内核申请Linux2.6字符设备cdev_add(&mydev, dev, 1);myclass = class_create(THIS_MODULE,"LED_CLASS");device_create(myclass, NULL, dev, NULL, "my_leds");printk("探测函数:设备端和驱动端匹配成功\n");return 0;
}
int myled_remove(struct platform_device *pdev)
{printk("移除函数运行\n");device_destroy(myclass,dev);//销毁设备节点class_destroy(myclass);//销毁设备类cdev_del(&mydev);//删除字符设备unregister_chrdev_region(dev,1);//释放设备号printk("设备注销成功\n");gpio_free(led[0]);gpio_free(led[1]);return 0;
}const struct of_device_id mydev_node={.compatible = "xyd_led",
};struct platform_driver drv = {.probe = my_probe,.remove = myled_remove,.driver = {.name = "my_leds",.of_match_table = &mydev_node	},
};static int __init mydriver_init(void)
{platform_driver_register(&drv);return 0;
}static void __exit mydriver_exit(void)
{platform_driver_unregister(&drv);
}	module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");

应用端:

#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驱动编写之平台设备总线

目录 前言一、平台设备总线1.是个啥&#xff1f;2.API函数 二、设备端驱动端1. 匹配机制2. 实现代码 三、设备树驱动端1.匹配机制2.代码实现 前言 本文主要介绍了一下两种驱动编写方法&#xff1a; 1.比较原始的设备端驱动端编写方法。 2.效率较高的设备树驱动端编写方法。 最…...

回溯——11.重新安排行程

力扣题目链接 给定一个机票的字符串二维数组 [from, to]&#xff0c;子数组中的两个成员分别表示飞机出发和降落的机场地点&#xff0c;对该行程进行重新规划排序。所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;所以该行程必须从…...

python+pytest+request 接口自动化测试

一、环境配置 1.安装python3 brew update brew install pyenv 然后在 .bash_profile 文件中添加 eval “$(pyenv init -)” pyenv install 3.5.3 -v pyenv rehash 安装完成后&#xff0c;更新数据库 pyenv versions 查看目前系统已安装的 Python 版本 pyenv global 3.5…...

《JavaEE进阶》----10.<SpringMVC应用分层:【三层架构】>

本篇博客我们主要讲解 1.应用的分层&#xff1a;三层架构 2.Spring MVC和三层架构的区别和联系 3.软件设计原则&#xff1a;高内聚低耦合 4.应用分层的好处 5.通过应用分层后的代码示例 一、三层架构简介 阿里开发手册中,关于工程结构部分,定义了常见工程的应用分层结构: 上图…...

【网络】网络通信的传输方式

目录 1.网络通信中的两种基本通信模式 1.1.怎么理解连接 1.2.面向有连接类型 1.3.面向无连接类型 2.实现这两种通信模式的具体交换技术 2.1.电路交换 2.2.分组交换 3.根据接收端数量分类 单播&#xff08;Unicast&#xff09; 广播&#xff08;Broadcast&#xff09; …...

数据仓库理论知识

1、数据仓库的概念 数据仓库&#xff08;英文&#xff1a;Date Warehouse&#xff0c;简称数仓、DW&#xff09;&#xff0c;是一个用于数据存储、分析、报告的数据系统。数据仓库的建设目的是面向分析的集成化数据环境&#xff0c;其数据来源于不同的外部系统&#…...

容易中、见刊快的6本医学期刊推荐!

常笑医学整理了6本容易中、见刊快的医学期刊&#xff0c;以及期刊详细参数与投稿经验&#xff0c;供医生、医学生们在论文投稿时参考。投稿经历均来自常笑医学网用户真实分享&#xff0c;欢迎大家到常笑医学网分享自己的投稿经历和实用经验。 1.《中国医药科学》 &#xff08;详…...

nnunetv2系列:使用默认的预测类推理2D数据

nnunetv2系列&#xff1a;使用默认的预测类推理2D数据 这里参考源代码nnUNet/nnunetv2/inference/predict_from_raw_data.py中给的示例进行调整和测试。 代码示例 from torch import device from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor# from nn…...

伺服电机如何计算扭矩——看这一篇就够了

#零基础学工控##西门子PLC##V90##电机##工控分享##自动化#工控人加入PLC工业自动化精英社群 工控人加入PLC工业自动化精英社群...

数据库C语言删除修改和输出

#include<myhead.h> #include<sqlite3.h> int out_callback(void *arg,int column_num, char **msgrow,char **msgcolumn)//输出查找的工人信息 { int i 0, j 0; while(i<column_num) { printf("%s\t" ,*(msgcolumni)); …...

插槽slot

一、简介&#xff1a; 插槽是 Vue 组件化开发中非常强大且灵活的工具&#xff0c;它使得组件的复用性和可定制性大大提高。通过合理地使用不同类型的插槽&#xff0c;可以构建出更加灵活和可维护的 Vue 应用程序。 二、使用场景 可重用组件的定制化 比如一个通用的弹窗组件&am…...

交换技术是一种在计算机网络和通信系统中广泛应用的关键技术,它主要通过交换设备(如交换机、路由器等)实现数据的转发和传输

交换技术是一种在计算机网络和通信系统中广泛应用的关键技术&#xff0c;它主要通过交换设备&#xff08;如交换机、路由器等&#xff09;实现数据的转发和传输。交换技术的核心目的是在不同的设备之间高效地传输数据&#xff0c;实现信息的互联互通。 一、交换技术的定义 交换…...

数仓建模:数仓设计中的10个陷阱

目录 0 引言 1 主要内容 1.1 过于迷恋技术&#xff0c;而没有将重点放在业务需求和目标上 1.2 没有或无法找到一个有影响的、平易近人的、明白事理的高级管理人员作为数仓建设的发起人 1.3 将项目处理为一个巨大的持续多年的项目&#xff0c;而不是追求更容易管理的、虽然…...

Vue如何将网页转换成图片或PDF并上传

一.使用html2canvas获取页面元素并绘制成图片 htmlcanvas中文文档 npm install --save html2canvas<template><div><button click"uploadImg">上传</button><div ref"yourDom"><!-- ...图片中页面内容 --><img s…...

【引领数据分析革命】TaskWeaver框架全景解读与入门指南!

亲爱的技术爱好者们&#xff0c;我是你们的老朋友—— 一个热爱.NET和AI相关技术的博主&#xff0c;在今天这个信息与数据爆炸的时代&#xff0c;我们始终寻求着处理数据分析任务的更优雅、更高效的方式。Microsoft团队推出了一个叫做TaskWeaver的神器&#xff0c;这可不仅仅是…...

LabVIEW灵活集成与调试的方法

在LabVIEW开发中&#xff0c;为了构建一个既便于调试又能灵活集成到主VI中的控制VI&#xff0c;开发者需要采用适当的编程方式和架构。常见的选择包括模块化设计、状态机架构以及事件驱动编程。这些方法有助于简化调试过程、提高系统的稳定性&#xff0c;并确保代码的重用性和可…...

网络药理学:分子对接之二:PDB数据库的使用(已知PDB ID)、PubChem数据库如果没有3D结构

PDB数据库使用 官方地址&#xff1a;https://www.rcsb.org/ 首页如下&#xff1a; 我们以热休克蛋白HSP90AA1为例&#xff0c;其PDB ID为7DHG&#xff0c;所以我们在搜索栏输入7DHG&#xff1a; 主要关注红框里的几个地方。 Download 下载文件&#xff0c;一般选择PDB For…...

JS获取页面中video标签视频的封面和时长

从HTML中提取Video信息 /*** 从html字符串中提取video标签* 入参&#xff1a; {String} htmlString* 出参&#xff1a;{Array} 数组*/ function extractVideosFromHTML(htmlString) {const dom new DOMParser().parseFromString(htmlString, text/html);const videos Arr…...

LLM大模型学习:AI Agent综述

AI Agent是什么 将LLM思想链接到一起&#xff0c;自主实现用户设定的任何目标。只需要告诉AutoGPT一个目标&#xff0c;能自主生成执行计划。 吴恩达&#xff1a;“与其争论哪些工作才算是真正的 Agent&#xff0c;不如承认系统可以具有不同程度的 Agentic 特性。” 核心在于…...

极米科技:走出舒适圈,推动数据架构现代化升级 | OceanBase 《DB大咖说》

《DB 大咖说》第 13 期&#xff0c;邀请到了极米科技软件与创新产品线高级架构师施刘凡来进行分享。 在小红书平台上&#xff0c;“是否应将家里的电视升级为投影仪&#xff1f;”这一话题激发了上百万篇笔记的分享与推荐&#xff0c;反映出年轻群体对投影仪的偏好。随着手机、…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...