linux驱动开发篇(四)—— platform平台设备驱动

2023-05-16

linux系列目录:
linux基础篇(一)——GCC和Makefile编译过程
linux基础篇(二)——静态和动态链接
ARM裸机篇(一)——i.MX6ULL介绍
ARM裸机篇(二)——i.MX6ULL启动过程
ARM裸机篇(三)——i.MX6ULL第一个裸机程序
ARM裸机篇(四)——重定位和地址无关码
ARM裸机篇(五)——异常和中断
linux系统移植篇(一)—— linux系统组成
linux系统移植篇(二)—— Uboot使用介绍
linux系统移植篇(三)—— Linux 内核使用介绍
linux系统移植篇(四)—— 根文件系统使用介绍
linux驱动开发篇(一)—— Linux 内核模块介绍
linux驱动开发篇(二)—— 字符设备驱动框架
linux驱动开发篇(三)—— 总线设备驱动模型
linux驱动开发篇(四)—— platform平台设备驱动
linux驱动开发篇(五)—— linux驱动面向对象的编程思想
linux驱动开发篇(六)—— 设备树的引入

文章目录

  • 一、平台设备驱动
    • 1、平台总线
    • 2、平台设备
    • 3、平台驱动
    • 4、平台驱动获取设备信息
  • 二、平台设备驱动实验
    • 1、编程思路
    • 2、定义平台设备
    • 3、定义平台驱动
    • 4、编译


一、平台设备驱动

在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们 SOC 中物理总线的概念并不严格相等:

  • 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
  • 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。

一般对于 I2C、 SPI、 USB 这些常见类型的物理总线来说, Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、 SPI 设备、 USB 设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如 led、 rtc 时钟、蜂鸣器、按键等等, Linux 内核将不会为它们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型, Linux 内核引入了一种虚拟的总线——平台总线(platform bus)

平台设备驱动的核心依然是 Linux 设备驱动模型,平台设备使用 platform_device 结构体来进行表示,其继承了设备驱动模型中的device 结构体。而平台驱动使用 platform_driver 结构体来进行表示,其则是继承了设备驱动模型中的 device_driver结构体。

1、平台总线

内核中使用 bus_type 来抽象描述系统中的总线,平台总线结构体原型如下所示:
platform_bus_type 结 构 体 (内 核 源码/driver/base/platform.c)

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

内核用 platform_bus_type 来描述平台总线,该总线在 linux 内核启动的时候自动进行注册。

这里重点是 platform 总线的 match 函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线,它都必须实例化该函数指针。 platform_match 的函数原型如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform 总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机
制 >ACPI 匹配模式 >id_table 方式 > 字符串比较。

2、平台设备

内核使用 platform_device 结构体来描述平台设备,platform_device 结 构 体 (内 核 源码/include/linux/platform_device.h)结构体原型如下:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};
  • name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
  • id: 指定设备的编号, Linux 支持同名的设备,而同名设备之间则是通过该编号进行区分;
  • dev: Linux 设备模型中的 device 结构体, linux 内核大量使用了面向对象思想, platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
  • num_resources: 记录资源的个数,当结构体成员 resource 存放的是数组时,需要记录 resource数组的个数,内核提供了宏定义 ARRAY_SIZE 用于计算数组的个数;
  • resource: 平台设备提供给驱动的资源,如 irq, dma,内存等等。该结构体会在接下来的内容进行讲解;
  • id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这部分内容会在平台总线小节中讲,这里的 id_entry 用于保存匹配的结果;

平台设备的工作是为驱动程序提供设备信息, 设备信息包括硬件信息和软件信息两部分。

  • 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、 IO 口等等
  • 软件信息:以太网卡设备中的 MAC 地址、 I2C 设备中的设备地址、 SPI 设备的片选信号线等等。

对于硬件信息,使用结构体 struct resource 来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:

struct resource {
 resource_size_t start;
 resource_size_t end;
 const char *name;
 unsigned long flags;
};
  • name: 指定资源的名字,可以设置为 NULL;
  • start、 end: 指定资源的起始地址以及结束地址
  • flags: 用于指定该资源的类型,在 Linux 中,资源包括 I/O、 Memory、 Register、 IRQ、 DMA、Bus 等多种类型,最常见的有以下几种:
资源宏定义描述
IORESOURCE_IO用于 IO 地址空间,对应于 IO 端口映射方式
IORESOURCE_MEM用于外设的可直接寻址的地址空间
IORESOURCE_IRQ用于指定该设备使用某个中断
IORESOURCE_DMA用于指定使用的 DMA 通道

注册/注销平台设备API原型:

int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);

3、平台驱动

内核中使用 platform_driver 结构体来描述平台驱动,platform_driver 结 构 体 (内 核 源
码/include/platform_device.h)结构体原型如下所示:

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};
  • probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
  • remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是 probe 函数实现操作的逆过程。
  • driver: Linux 设备模型中用于抽象驱动的 device_driver 结构体, platform_driver 继承该结构体,也就获取了设备模型驱动对象的特性;
  • id_table: 表示该驱动能够兼容的设备类型。

platform_device_id 结构体原型如下所示:

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

在 platform_device_id 这个结构体中,有两个成员,第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 platform_device 中的变量 name 进行比较匹配,另一个成员变量 driver_data,则是用于来保存设备的配置。

注册/注销平台驱动API

int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);

4、平台驱动获取设备信息

在学习平台设备的时候,我们知道平台设备使用结构体 resource 来抽象表示硬件信息,而软件信息则可以利用设备结构体 device 中的成员 platform_data 来保存。先看一下如何获取平台设备中结构体 resource 提供的资源。
platform_get_resource() 函数通常会在驱动的 probe 函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个 struct resource 类型的指针,该函数原型如下:

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
  • dev: 指定要获取哪个平台设备的资源;
  • type: 指定获取资源的类型,如 IORESOURCE_MEM、 IORESOURCE_IO 等;
  • num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。

对于存放在 device 结构体中成员 platform_data 的软件信息,我们可以使用 dev_get_platdata 函数来获取,函数原型如下所示:

static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

总结一下平台驱动需要实现 probe 函数,当平台总线成功匹配驱动和设备时,则会调用驱动的 probe 函数,在该函数中使用上述的函数接口来获取资源,以初始化设备,最后
填充结构体 platform_driver,调用 platform_driver_register 进行注册。

二、平台设备驱动实验

把平台设备驱动应用到 LED 字符设备驱动的代码中,实现硬件与软件代码相分离。

1、编程思路

  1. 编写第一个内核模块 led_pdev.c
  2. 在内核模块中定义一个平台设备,并填充 RGB 灯相关设备信息
  3. 在该模块入口函数,注册/挂载这个平台设备
  4. 编写第二个内核模块 led_pdrv.c
  5. 在内核模块中定义一个平台驱动,在 probe 函数中完成字符设备驱动的创建
  6. 在该模块入口函数,注册/挂载这个平台驱动

在平台设备总线上,注册/挂载平台设备和平台驱动时,会自动进行配对。配对成功后,回调执行平台驱动的 probe 函数,从而完成字符设备驱动的创建。

2、定义平台设备

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define CCM_CCGR1 															0x20C406C	//时钟控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 				0x20E006C	//GPIO1_04复用功能选择寄存器
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 				0x20E02F8	//PAD属性设置寄存器
#define GPIO1_GDIR 															0x0209C004	//GPIO方向设置寄存器(输入或输出)
#define GPIO1_DR 																0x0209C000	//GPIO输出状态寄存器

#define CCM_CCGR3 															0x020C4074
#define GPIO4_GDIR 															0x020A8004
#define GPIO4_DR 																0x020A8000

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020 			0x020E01E0
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020 			0x020E046C

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019 			0x020E01DC
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019 			0x020E0468

static struct resource rled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO1_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO1_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR1, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4),
};

static struct resource gled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020, 4),
};

static struct resource bled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019, 4),
};
/* not used */ 
static void led_release(struct device *dev)
{

}

/* led hardware information */
unsigned int rled_hwinfo[2] = { 4, 26 };
unsigned int gled_hwinfo[2] = { 20, 12 };
unsigned int bled_hwinfo[2] = { 19, 12 };

/* red led device */ 
static struct platform_device rled_pdev = {
	.name = "led_pdev",
	.id = 0,
	.num_resources = ARRAY_SIZE(rled_resource),
	.resource = rled_resource,
	.dev = {
		.release = led_release,
		.platform_data = rled_hwinfo,
		},
};
/* green led device */ 
static struct platform_device gled_pdev = {
	.name = "led_pdev",
	.id = 1,
	.num_resources = ARRAY_SIZE(gled_resource),
	.resource = gled_resource,
	.dev = {
		.release = led_release,
		.platform_data = gled_hwinfo,
		},
};
/* blue led device */ 
static struct platform_device bled_pdev = {
	.name = "led_pdev",
	.id = 2,
	.num_resources = ARRAY_SIZE(bled_resource),
	.resource = bled_resource,
	.dev = {
		.release = led_release,
		.platform_data = bled_hwinfo,
		},
};

static __init int led_pdev_init(void)
{
	printk("pdev init\n");
	platform_device_register(&rled_pdev);
	platform_device_register(&gled_pdev);
	platform_device_register(&bled_pdev);
	return 0;
}

module_init(led_pdev_init);

static __exit void led_pdev_exit(void)
{
	printk("pdev exit\n");
	platform_device_unregister(&rled_pdev);
	platform_device_unregister(&gled_pdev);
	platform_device_unregister(&bled_pdev);
}

module_exit(led_pdev_exit);

MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");

3、定义平台驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define DEV_MAJOR 243
#define DEV_NAME  "led"

static struct class *my_led_class;

//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
	unsigned int led_pin;
	unsigned int clk_regshift;

	unsigned int __iomem *va_dr;
	unsigned int __iomem *va_gdir;
	unsigned int __iomem *va_iomuxc_mux;
	unsigned int __iomem *va_ccm_ccgrx;
	unsigned int __iomem *va_iomux_pad;	

	struct cdev led_cdev;

};

static int led_cdev_open(struct inode *inode, struct file *filp)
{
	printk("%s\n", __func__);
	
	struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
	unsigned int val = 0;

	val = readl(cur_led->va_ccm_ccgrx);
	val &= ~(3 << cur_led->clk_regshift);
	val |= (3 << cur_led->clk_regshift);
	writel(val, cur_led->va_ccm_ccgrx);

	writel(5, cur_led->va_iomuxc_mux);

	writel(0x1F838, cur_led->va_iomux_pad);

	val = readl(cur_led->va_gdir);
	val &= ~(1 << cur_led->led_pin);
	val |= (1 << cur_led->led_pin);
	writel(val, cur_led->va_gdir);

	val = readl(cur_led->va_dr);
	val |= (0x01 << cur_led->led_pin);
	writel(val, cur_led->va_dr);

	filp->private_data = cur_led;

	return 0;
}


static int led_cdev_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
			      size_t count, loff_t * ppos)
{
	unsigned long val = 0;
	unsigned long ret = 0;

	int tmp = count;

	struct led_data *cur_led = (struct led_data *)filp->private_data;

	kstrtoul_from_user(buf, tmp, 10, &ret);

	val = readl(cur_led->va_dr);
	if (ret == 0)
		val &= ~(0x01 << cur_led->led_pin);
	else
		val |= (0x01 << cur_led->led_pin);

	writel(val, cur_led->va_dr);
	*ppos += tmp;

	return tmp;
}

static struct file_operations led_cdev_fops = {
	.open = led_cdev_open,
	.release = led_cdev_release,
	.write = led_cdev_write,
};


//probe函数中,驱动需要去提取设备的资源,完成字符设备的注册等工作
static int led_pdrv_probe(struct platform_device *pdev)
{
	struct led_data *cur_led;
	unsigned int *led_hwinfo;
	
	struct resource *mem_dr;
	struct resource *mem_gdir;
	struct resource *mem_iomuxc_mux;
	struct resource *mem_ccm_ccgrx;
	struct resource *mem_iomux_pad; 	

	dev_t cur_dev;

	int ret = 0;
	
	printk("led platform driver probe\n");

	//第一步:提取平台设备提供的资源
	//devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
	cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
	if(!cur_led)
		return -ENOMEM;
	led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
	if(!led_hwinfo)
		return -ENOMEM;

	/* get the pin for led and the reg's shift */
	//dev_get_platdata函数获取私有数据,得到LED灯的寄存器偏移量,并赋值给cur_led->led_pin和cur_led->clk_regshift
	led_hwinfo = dev_get_platdata(&pdev->dev);

	cur_led->led_pin = led_hwinfo[0];
	cur_led->clk_regshift = led_hwinfo[1];
	/* get platform resource */
	//利用函数platform_get_resource可以获取到各个寄存器的地址
	mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 2);
	mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 3);
	mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 4);

	//使用devm_ioremap将获取到的寄存器地址转化为虚拟地址
	cur_led->va_dr =
	    devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
	cur_led->va_gdir =
	    devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
	cur_led->va_iomuxc_mux =
	    devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,
			 resource_size(mem_iomuxc_mux));
	cur_led->va_ccm_ccgrx =
	    devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,
			 resource_size(mem_ccm_ccgrx));
	cur_led->va_iomux_pad =
	    devm_ioremap(&pdev->dev, mem_iomux_pad->start,
			 resource_size(mem_iomux_pad));

	//第二步:注册字符设备
	cur_dev = MKDEV(DEV_MAJOR, pdev->id);

	register_chrdev_region(cur_dev, 1, "led_cdev");

	cdev_init(&cur_led->led_cdev, &led_cdev_fops);

	ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
	if(ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}
	
	device_create(my_led_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);

	/* save as drvdata */ 
	//platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
	platform_set_drvdata(pdev, cur_led);

	return 0;

add_err:
	unregister_chrdev_region(cur_dev, 1);
	return ret;
}


static int led_pdrv_remove(struct platform_device *pdev)
{
	dev_t cur_dev; 
	//platform_get_drvdata,获取当前LED灯对应的结构体
	struct led_data *cur_data = platform_get_drvdata(pdev);


	printk("led platform driver remove\n");

	cur_dev = MKDEV(DEV_MAJOR, pdev->id);

	//cdev_del删除对应的字符设备
	cdev_del(&cur_data->led_cdev);

	//删除/dev目录下的设备
	device_destroy(my_led_class, cur_dev);

	//unregister_chrdev_region, 注销掉当前的字符设备编号
	unregister_chrdev_region(cur_dev, 1);

	return 0;
}

static struct platform_device_id led_pdev_ids[] = {
	{.name = "led_pdev"},
	{}
};

MODULE_DEVICE_TABLE(platform, led_pdev_ids);

//led_pdrv中定义了两种匹配模式
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {
	
	.probe = led_pdrv_probe,
	.remove = led_pdrv_remove,
	.driver.name = "led_pdev",
	.id_table = led_pdev_ids,
};


static __init int led_pdrv_init(void)
{
	printk("led platform driver init\n");
	//class_create,来创建一个led类
	my_led_class = class_create(THIS_MODULE, "my_leds");
	//调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
	platform_driver_register(&led_pdrv);

	return 0;
}
module_init(led_pdrv_init);


static __exit void led_pdrv_exit(void)
{
	printk("led platform driver exit\n");	
	platform_driver_unregister(&led_pdrv);
	class_destroy(my_led_class);
}
module_exit(led_pdrv_exit);

MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");

4、编译

makefile文件

KERNEL_DIR=../ebf_linux_kernel

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export  ARCH  CROSS_COMPILE

obj-m := led_pdev.o led_pdrv.o

all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
modules clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean	

编译成功后,实验目录下会生成两个名为“led_pdev.ko”、” led_pdrv.ko”的驱动模块文件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux驱动开发篇(四)—— platform平台设备驱动 的相关文章

  • 微信支付登录总结

    做微信支付 xff0c 登录之前需要 提前注册开发者帐号 xff0c 创建移动应用 代码下载路径 xff1a http pan baidu com s 1o7aBxqU xff08 主要是做笔记 xff0c 把微信登录以及微信支付整到一起
  • 微软仿真神器 AirSim + Unreal Engine 4.24 + Ubuntu 18.04 + ROS 编译流程小结

    时间 xff1a 20210107 文章目录 一 参考资料二 系统情况简介三 编译UE引擎流程四 UE引擎测试五 AirSim编译流程六 UE 4 24 43 AirSim 联合测试七 AirSim 的 ROS 功能包测试八 UE 43 R
  • android进阶---【注解(一)之运行时注解】

    android进阶 注解 注解1 什么是注解2 注解的产生3 注解的基础介绍3 1元注解3 2运行时注解与编译时注解区别 4 自定义注解4 1自定义编写规则4 2自定义运行时注解 注解 注解这个概念 xff0c 有些人可能会有些陌生 但是撸
  • 设计容器 实现put get getCount 方法,生产者消费者问题

    设计一个容器 xff0c 支持put get getCount 方法 xff0c 满足两个生产者 二十个消费者阻塞调用 public class ProdConsuCont static ReentrantLock lock 61 new
  • C++程序员必看书单

    转载 xff1a https blog csdn net ljy1988123 article details 7748913 comments C 43 43 xff1a Prata C 43 43 Primer Plus xff1a 基
  • git submodule 升级commit并push

    git submodule 升级commit并push 关于这个问题 xff0c 可以参照以下文章 xff1a https blog csdn net wwj 748 article details 73991862 流程写的很清楚 xff
  • 欧拉角pitch、yaw,roll的理解

    关于旋转永远是做游戏的难点和混乱点 我们知道表示一个旋转有多种方式 xff0c 简单的欧拉角 xff0c 复杂点的四元数 xff0c 再复杂点的矩阵 之前接触unity可以用四元数和欧拉角两种方式表示旋转 xff0c 最近一直研究虚幻引擎
  • Mac执行ruby命令提示 dyld: Library not loaded等类似问题解决方案

    说一下为啥会遇见这么个问题 xff0c 我在给一个xcode项目添加podfile的时候 xff0c 在终端执行了pod init命令 xff0c 随即给了我一个如下图的提示 xff08 报错信息一样的 xff0c 执行pod的命令早就被解
  • 编程题:多线程交替打印ABC

    要求创建3个线程 xff0c 分别打印ABC xff0c 共交替打印10次 span class token keyword public span span class token keyword class span span clas
  • Android App Bundle 自动打包原理

    Google推出Android App Bundle 已经有一段时间了 根据Google的政策说明 xff0c 预计2021年8月之后 xff0c 新发布的应用都必须使用Android App Bundle aab 来上架Google Pl
  • 【玩转Linux】Linux虚拟机设置固定IP

    Linux虚拟机Centos系统的ip总是变化 xff0c 如何固定下来 xff1f 尝试了好多方式 xff0c 终于找到一种最为简单的方法 文章目录 1 查看Centos的IP信息2 修改文件3 刷新网络配置 1 查看Centos的IP信
  • Docker安装MySQL数据库

    文章目录 一 简单方式二 挂载方式1 创建挂载目录2 启动容器 三 修改配置文件1 新建my cnf2 编辑my cnf3 查看是否生效 一 简单方式 span class token function docker span run sp
  • 解决Docker镜像拉取失败问题

    一 问题 Docker拉取mysql镜像 xff0c 发生报错 span class token function docker span pull mysql 8 0 22 报错信息 xff1a Error response from d
  • Docker安装RabbitMQ消息队列

    文章目录 1 启动容器2 连接访问 1 启动容器 span class token function docker span run name rabbitmq span class token punctuation span resta
  • 【玩转Linux】Linux安装宝塔面板

    文章目录 一 简介二 安装1 centos脚本安装2 浏览器访问 三 总结 一 简介 宝塔面板 xff0c 是安全高效的服务器运维面板 xff0c 一个提升运维效率的服务器管理软件 xff0c 支持一键LAMP LNMP 集群 监控 网站
  • 使用JDK的keytool工具生成JKS证书

    使用JDK的keytool工具生成JKS证书 文章目录 1 生成JKS证书2 查看JKS证书详细信息3 导出证书 1 生成JKS证书 keytool genkey alias jwt keyalg RSA keystore jwt jks
  • 【算法】二分查找

    算法 二分查找 题目 xff1a 请实现无重复数字的升序数组的二分查找 难度 xff1a 简单 代码 xff1a 二分查找 xff0c 又叫折半查找 xff0c 要求待查找的序列有序 每次取中间位置的值与待查关键字比较 xff0c 如果中间
  • 【算法】反转链表

    算法 反转链表 题目 xff1a 给定一个单链表的头结点pHead xff0c 长度为n xff0c 反转该链表后 xff0c 返回新链表的表头 难度 xff1a 简单 代码 xff1a span class token keyword c
  • 【算法】合并两个排序的链表

    算法 合并两个排序的链表 题目 xff1a 输入两个递增的链表 xff0c 单个链表的长度为n xff0c 合并这两个链表并使新链表中的节点仍然是递增排序的 难度 xff1a 简单 代码 xff1a span class token key
  • 【算法】判断链表中是否有环

    算法 判断链表中是否有环 题目 xff1a 判断给定的链表中是否有环 如果有环则返回true xff0c 否则返回false 难度 xff1a 简单 代码 xff1a span class token keyword public span

随机推荐

  • 教你快速高效接入SDK——手游聚合SDK的总体思路和架构

    U8SDK技术博客 xff1a http www uustory com xff0c 欢迎来坐坐 百度传课已经停运 xff0c 最新U8SDK视频教程已经转移至B站 xff1a U8SDK最新视频教程 题记 xff1a 很多做游戏开发的人
  • 【算法】删除链表的倒数第N个结点

    算法 删除链表的倒数第N个结点 题目 xff1a 给你一个链表 xff0c 删除链表的倒数第 n 个结点 xff0c 并且返回链表的头结点 难度 xff1a 中等 代码 xff1a span class token keyword publ
  • 阿里云智能编码插件Cosy,提升开发效率杠杠滴!

    文章目录 一 简介二 核心功能1 代码智能补全2 代码示例搜索 三 产品特性1 提升编码效率2 沉浸式开发3 低资源消耗4 隐私保护 四 快速开始1 安装IntelliJ IDEA插件2 测试代码智能补全3 测试代码示例搜索 五 总结 一
  • Alibaba Cloud Toolkit轻量部署插件,一键发布服务器

    文章目录 一 简介二 部署方式1 传统部署方式2 Cloud Toolkit部署方式 四 产品功能五 部署步骤1 安装插件2 添加主机3 Deploy to Host4 控制台5 服务器 六 服务器代码热部署七 Arthas诊断 一 简介
  • 新人一看就懂:Dubbo3 + Nacos的RPC远程调用框架demo

    文章目录 一 前言Feign和Dubbo到底有啥区别 xff0c 为啥大厂都爱用RPC框架 xff1f 二 简介三 dubbo api xff08 对外暴漏的接口 xff09 1 TestService接口 四 dubbo provider
  • Docker安装Kafka消息队列

    文章目录 1 安装zookeeper2 安装kafka3 安装kafka map xff08 可选 xff09 1 安装zookeeper span class token function docker span run span cla
  • 【Spring Boot实战与进阶】集成Kafka消息队列

    汇总目录链接 xff1a Spring Boot实战与进阶 学习目录 文章目录 一 简介二 集成Kafka消息队列1 引入依赖2 配置文件3 测试生产消息4 测试消费消息 一 简介 Kafka是由Apache软件基金会开发的一个开源流处理平
  • Hutool工具类之excel导入导出

    文章目录 1 导入excel2 导出excel 1 导入excel span class token class name ExcelReader span reader span class token operator 61 span
  • Docker安装RockerMQ消息队列

    文章目录 1 安装namesrv2 安装broker3 安装console xff08 可选 xff09 1 安装namesrv namesrv就类似于消息队列的注册中心 span class token function docker s
  • 【Spring Boot实战与进阶】集成RockerMQ消息队列

    汇总目录链接 xff1a Spring Boot实战与进阶 学习目录 文章目录 一 简介二 集成RockerMQ消息队列1 引入依赖2 配置文件3 测试生产消息4 测试消费消息 一 简介 RocketMQ 是阿里巴巴在2012年开源的分布式
  • linux下elasticsearch 安装、配置及示例

    简介 开始学es xff0c 我习惯边学边记 xff0c 总结出现的问题和解决方法 本文是在两台linux虚拟机下 xff0c 安装了三个节点 本次搭建es同时实践了两种模式 单机模式和分布式模式 条件允许的话 xff0c 可以在多台机器上
  • Java服务器热部署的实现原理

    今天发现早年在大象笔记中写的一篇笔记 xff0c 之前放在ijavaboy上的 xff0c 现在它已经访问不了了 前几天又有同事在讨论这个问题 这里拿来分享一下 在web应用开发或者游戏服务器开发的过程中 xff0c 我们时时刻刻都在使用热
  • win10打开热点提示:我们无法设置移动热点

    解决方案 xff1a 1 右键我的电脑 xff0c 打开管理 2 双击带有下载标记wi fi 适配器 xff0c 点击启用设备 xff0c 确认即可
  • Docker容器打包迁移

    有时在一个docker内部署的容器不想在其它服务器容器上重新部署 xff0c 再配置配置文件 这时直接将当前的容器打包然后直接部署到新的服务器上即可 xff0c 免了再配置一遍 1 将容器保存为镜像 先把容器停止运行不然会有问题 容器保存为
  • 四旋翼飞行器的原理研究和建模

    四旋翼飞行器的原理研究和建模 对四旋翼飞行器的工作原理进行了简单介绍 xff0c 对其飞行姿态角进行描述 xff0c 并在此基础上建立数学模型 四旋翼飞行器的原理 根据四旋翼飞行器的运动方式的特点将其飞行控制划分为四种基本的飞行控制方式 1
  • 数组名和函数名是什么东西

    数组名和函数名的本质都是一个 指向数组首地址或函数体的指针常量 的名字 规则1 xff1a 数组 61 指向数组首地址的指针常量 43 数组元素 简单说就是 xff0c 数组 61 指针常量 43 数组内容 xff0c 数组名就是这个指针常
  • RT-Thread进阶之低功耗PM组件应用笔记

    电源管理组件 嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下 xff0c 尽可能降低系统能耗以延长设备待机时间 高性能与有限的电池能量在嵌入式系统中矛盾最为突出 xff0c 硬件低功耗设计与软件低功耗管理的联合应用成为解决矛盾的有
  • 【STM32H750】玩转ART-Pi(一)——使用STM32CUBMX生成TouchGFX工程

    目录 STM32H750 玩转ART Pi xff08 一 xff09 使用STM32CUBMX生成TouchGFX工程 STM32H750 玩转ART Pi xff08 二 xff09 制作MDK的外部QSPI FLASH烧录算法 STM
  • 【STM32H750】玩转ART-Pi(二)——制作MDK的外部QSPI-FLASH烧录算法

    目录 STM32H750 玩转ART Pi xff08 一 xff09 使用STM32CUBMX生成TouchGFX工程 STM32H750 玩转ART Pi xff08 二 xff09 制作MDK的外部QSPI FLASH烧录算法 STM
  • linux驱动开发篇(四)—— platform平台设备驱动

    linux系列目录 xff1a linux基础篇 xff08 一 xff09 GCC和Makefile编译过程 linux基础篇 xff08 二 xff09 静态和动态链接 ARM裸机篇 xff08 一 xff09 i MX6ULL介绍 A