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

MX6ULL学习笔记 (八) platform 设备驱动实验

前言:

什么是 Linux 下的 platform 设备驱动

Linux下的字符设备驱动一般都比较简单,只是对IO进行简单的读写操作。但是I2C、SPI、LCD、USB等外设的驱动就比较复杂了,需要考虑到驱动的可重用性,以避免内核中存在大量重复代码,为此人们提出了驱动的分离与分层的思路,演化并诞生出了platform设备驱动。

一、驱动的分层分离

1. 驱动的分离

以I2C接口的三轴加速度传感器为例,传统的设备驱动如下图示:每个平台都有一个ADXL345的驱动,因此设备驱动要重复编写三次。

各平台的主机驱动是不同的,但是ADXL345是一样的,因此上图可以精简为一个ADXL345驱动和统一的接口API。

 实际上,除了ADXL345还有AT24C02、MPU6050等I2C设备,因此实际的驱动框架图如下示

驱动的分离即将主机驱动和设备驱动分隔开来,实际开发中,主机驱动一般由半导体厂家提供,设备驱动也会由器件厂家写好,我们只需要提供设备信息即可。也就是将设备信息从设备驱动中剥离开来,设备驱动使用标准方法获取到设备信息,然后根据获取到的设备信息来初始化设备

因此驱动只负责驱动,设备只负责设备,想办法将两者进行匹配即可。这就是Linux中的总线-驱动-设备模型,也就是常说的驱动分离

如上图示,当向系统注册一个驱动时,总线会在右侧的设备中查找,看看有没有与之匹配的设备,有的话就将两者联系起来;当向系统中注册一个设备时,总线会在左侧的驱动中查找,看有没有与之匹配的驱动,有的话也联系起来。

2. 驱动的分层

Linux下的驱动也是分层的,分层的目的是为了在不同的层处理不同的内容。以input输入子系统为例,input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等。

  • 驱动层:获取输入设备的原始值,获取到的输入事件上报给核心层
  • 核心层:处理各种IO模型,并且提供file_operations操作集合
  • 事件层:和用户空间进行交互

3. platform平台驱动模型

根据总线-驱动-设备驱动模型,IIC、SPI、USB这样实实在在的总线是完全匹配的,但是要有一些外设是没法归结为具体的总线:比如定时器、RTC、LCD等。为此linux内核创造了一个虚拟的总线:platform总线,以及platform驱动、platform设备模型。

platform总线Linux内核使用bus_type结构体表示总线。

其定义在文件include/linux/device.h中

platform驱动platform驱动由platform_driver结构体表示。

此结构体定义在文件include/linux/platform_device.h中。

        编写platform驱动时,先要定义一个platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执行,具体的驱动程序在probe函数里面编写。之后通过以下函数向内核注册platform驱动或卸载platform驱动

③platform设备:platform_device结构体用来表示platform设备。

        注意若内核支持设备树的话,就无需使用该结构体来描述设备,而改用设备树了。该结构体定义在文件include/linux/platform_device.h中,在不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用以下函数将设备信息注册到Linux内核中或从内核中注销掉,这里我们使用的linux是新版本的了,也就直接使用设备树就好了。

 


二、platform框架分析

1.platform总线注册

  和字符型驱动一样,我们要使用platform总线之前,也需要告诉一下内核,也就是注册。使用platform_bus_init函数去进行注册,既然要注册,那么我们也得告诉内核我们的一些信息。

注册的内容就是:

struct bus_type platform_bus_type = {.name           = "platform",.dev_groups   = platform_dev_groups,.match           = platform_match,.uevent          = platform_uevent,.pm        = &platform_dev_pm_ops,}

对于platform平台而言,platform_match函数就是月老,负责驱动和设备的匹配。

2.platform驱动

在注册platform驱动之前要定义一个结构体,为platform_driver,结构体内容为:

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 of_device_id    *of_match_table;//-> const char             *name;const struct platform_device_id *id_table;bool prevent_deferred_probe;};

 然后使用platform_driver_register函数向内核注册platform驱动。向内核注册platform驱动的时候,如果驱动和设备匹配成功,最终会执行platform_driver的probe函数。

3.platform设备

1、无设备树的时候,此时需要驱动开发人员编写设备注册文件,使用platform_device_register函数注册设备。使用platform_device_register函数注册设备也同样需要告诉内核一些注册信息。也就是需要定义个结构体:

结构体platform_device:

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

 

2,有设备树,修改设备树的设备节点即可。使用兼容性列表,当设备与platform的驱动匹配以后,就会执行platform_driver->probe函数。


 三、编写 platform 驱动流程

接下来我们就来学习一下如何在设备树下编写 platform 驱动流程:

1.在设备树中创建设备节点

由于我们使用的linux是支持设备树的,那么毫无疑问,我们肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。

2、编写platfrom的驱动兼容表

1)建立of_device_id 表,也就是驱动的兼容表:

static const struct of_device_id leds_of_match[] = {{ .compatible = "atkalpha-gpioled" },     /* 兼容属性 */{ /* Sentinel */ }};

3、建立platform_driver结构体

static struct platform_driver leds_platform_driver = {.driver = {.name = "imx6ul-led",.of_match_table = leds_of_match,},.probe = leds_probe,.remove = leds_remove,};

1)设置 platform_driver 中的 of_match_table 匹配表为上面创建的 leds_of_match

2)向总线注册驱动的时候,会检查当前总线下的所有设备,有没有与此驱动匹配的设备,如果有的话就执行驱动里面的probe函数。

3)卸载驱动的时候,会执行驱动里面的remove函数。

4、编写probe函数:

函数原型:

static int led_probe(struct platform_device *dev);

5、编写remove函数:

函数原型

static int led_remove(struct platform_device *dev);


四、实验程序编写

1.在设备树中创建设备节点

2.引入字符设备框架

这里直接引入我们之前写过的字符设备框架,在这份驱动的基础上来进行修改,如果没有看过之前那篇也没关系,下面也会贴出完整代码。

3.编写platfrom的驱动兼容表

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {{ .compatible = "led-gpio" },{ /* Sentinel */ }
};

4.建立platform_driver结构体

/*platform_driver结构体*/
static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led",.of_match_table = led_of_match,},.probe  =  led_probe,.remove =  led_remove,
};

5.编写probe函数:

当设备和驱动兼容表匹配上的时候就会运行peobe函数:

/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{/* 动态注册字符设备的流程一般如下:1.调用 alloc_chrdev_region() 函数申请设备编号。2.使用 cdev_init() 函数初始化设备描述结构体。3.使用 cdev_add() 函数将设备号与设备描述结构体关联起来,注册字符设备驱动。4.使用 class_create() 函数创建一个设备类.5.使用 device_create() 函数创建一个设备*/int ret = 0;/*进入这个函数就表明匹配成功了*/printk("led driver and device was matched!\r\n");/*1 创建设备号根据是否定义了设备号,通过条件判断选择不同的创建方式。如果定义了设备号,则使用MKDEV宏将主设备号和次设备号合成为设备号,并调用register_chrdev_region()函数注册字符设备号。如果没有定义设备号,则使用alloc_chrdev_region()函数动态分配设备号,并通过MAJOR和MINOR宏获取分配得到的主设备号和次设备号。*/if(gpioled.major){gpioled.devid = MKDEV(gpioled.major,0);register_chrdev_region(gpioled.devid,DEVICE_CNT,DEVICE_NAME);}else{alloc_chrdev_region(&gpioled.devid,0,DEVICE_CNT,DEVICE_NAME);gpioled.major = MAJOR(gpioled.devid);gpioled.minor = MINOR(gpioled.devid);}/* 2 初始化cdev设置cdev结构体的拥有者为当前模块(THIS_MODULE),然后使用 cdev_init() 函数初始化cdev结构体。参数包括待初始化的cdev结构体和用于操作该设备的file_operations结构体(hello_drv) */gpioled.cdev.owner= THIS_MODULE;cdev_init(&gpioled.cdev,&gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev,gpioled.devid,DEVICE_CNT);/*4 创建设备类使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。该函数的参数包括所属的模块(THIS_MODULE)和设备类的名称(DEVICE_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */gpioled.class = class_create(THIS_MODULE,DEVICE_NAME);if(IS_ERR(gpioled.class)){printk("newchr fail!\r\n");return PTR_ERR(gpioled.class);}/*5 创建设备使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点。参数包括设备所属的类(newchr.class)、父设备(NULL,如果没有父设备)、设备号(newchr.devid)、设备私有数据(NULL,一般为设备驱动程序提供一个指针)和设备名称(DEVICE_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEVICE_NAME);if(IS_ERR(gpioled.device)){printk("newchr fail!\r\n");return PTR_ERR(gpioled.device);}ret = myled_init(&gpioled);   //初始化ledgpioreturn 0;
}

6.编写remove函数:

static int led_remove(struct platform_device *dev)
{gpio_set_value(gpioled.led_gpio,1);gpio_free(gpioled.led_gpio);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/*在模块卸载时,使用 cdev_del() 函数注销字符设备驱动,并使用 unregister_chrdev_region() 函数释放设备号资源。*//* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, DEVICE_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);// 销毁设备,删除相应的设备节点class_destroy(gpioled.class);// 销毁设备类,释放相关资源printk("gpioled exit!\r\n");return 0;
}

完整代码:

/**************头文件区域*********************************************************/
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>#include <linux/of.h> 
#include <linux/of_address.h> 
#include <linux/of_gpio.h> 
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/io.h>
/**********************************************************************************//************************函数定义-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file);
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr);
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr);
static int gpioled_open(struct inode *inode , struct file *file);
static int led_probe(struct platform_device *dev);
static int led_remove(struct platform_device *dev);
/************************函数定义-end********************************************//************************宏定义-begin***********************************************/
#define DEVICE_NAME "dtsplatled"
#define DEVICE_CNT  1
#define LED_ON     1
#define LED_OFF    0
/************************宏定义-end********************************************//************************结构体定义-begin***********************************************/
/* dtsled设备信息结构体 */
struct dtsled_dev
{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node *nd; /* 设备节点 */int led_gpio; /* led 所使用的 GPIO 编号 */
};
struct dtsled_dev gpioled; /* led设备 *//* 设备操作函数结构体 */
static const struct file_operations gpioled_fops = {.owner		= THIS_MODULE,.open		= gpioled_open,.read		= gpioled_read,.write      = gpioled_write,.release    = gpioled_release
};/* 匹配列表 */
static const struct of_device_id led_of_match[] = {{ .compatible = "led-gpio" },{ /* Sentinel */ }
};static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led",.of_match_table = led_of_match,},.probe  =  led_probe,.remove =  led_remove,
};
/************************结构体定义-end***********************************************//************************file_operations操作函数-begin***********************************************/
static int gpioled_release(struct inode *inode, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
static ssize_t gpioled_read(struct file *file, char __user *buf, size_t size, loff_t *ptr)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
static ssize_t gpioled_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr)
{int ret;unsigned char databuf[1];unsigned char ledstate;struct dtsled_dev *dev = file->private_data;ret = __copy_from_user(databuf,buf,size);if(ret < 0){return -EFAULT;}ledstate = databuf[0];if(ledstate == LED_OFF){   gpio_set_value(dev->led_gpio,1);}else if(ledstate == LED_ON){gpio_set_value(dev->led_gpio,0);}return 0;
}
static int gpioled_open(struct inode *inode , struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);file->private_data = &gpioled; /* 设置私有数据 */ return 0;
}
/************************file_operations操作函数-end***********************************************//*****************led初始化函数************************/
static int myled_init(struct dtsled_dev *dev)
{int ret = 0;/* 1、设置 LED 所使用的 GPIO */ dev->nd = of_find_node_by_path("/gpioled");if(dev->nd == NULL){printk("gpioled node cant not found!\r\n");return -EINVAL;}else{printk("gpioled node hase been found!\r\n");}/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */ dev->led_gpio =  of_get_named_gpio(dev->nd,"gpios",0);if(dev->led_gpio < 0){printk("can't get led-gpio\r\n");return -EINVAL;}printk("led-gpio num = %d\r\n", dev->led_gpio); /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */ ret = gpio_request(dev->led_gpio,"led0");if(ret < 0){printk("led-gpio request fail\r\n"); return -EINVAL;}gpio_direction_output(dev->led_gpio,1);return ret;
}/************************platfrom操作函数-begin***********************************************/
/*当谁列表的设备和驱动匹配上后执行的peobe函数*/
static int led_probe(struct platform_device *dev)
{/* 动态注册字符设备的流程一般如下:1.调用 alloc_chrdev_region() 函数申请设备编号。2.使用 cdev_init() 函数初始化设备描述结构体。3.使用 cdev_add() 函数将设备号与设备描述结构体关联起来,注册字符设备驱动。4.使用 class_create() 函数创建一个设备类.5.使用 device_create() 函数创建一个设备*/int ret = 0;/*进入这个函数就表明匹配成功了*/printk("led driver and device was matched!\r\n");/*1 创建设备号根据是否定义了设备号,通过条件判断选择不同的创建方式。如果定义了设备号,则使用MKDEV宏将主设备号和次设备号合成为设备号,并调用register_chrdev_region()函数注册字符设备号。如果没有定义设备号,则使用alloc_chrdev_region()函数动态分配设备号,并通过MAJOR和MINOR宏获取分配得到的主设备号和次设备号。*/if(gpioled.major){gpioled.devid = MKDEV(gpioled.major,0);register_chrdev_region(gpioled.devid,DEVICE_CNT,DEVICE_NAME);}else{alloc_chrdev_region(&gpioled.devid,0,DEVICE_CNT,DEVICE_NAME);gpioled.major = MAJOR(gpioled.devid);gpioled.minor = MINOR(gpioled.devid);}/* 2 初始化cdev设置cdev结构体的拥有者为当前模块(THIS_MODULE),然后使用 cdev_init() 函数初始化cdev结构体。参数包括待初始化的cdev结构体和用于操作该设备的file_operations结构体(hello_drv) */gpioled.cdev.owner= THIS_MODULE;cdev_init(&gpioled.cdev,&gpioled_fops);/* 3、添加一个cdev */cdev_add(&gpioled.cdev,gpioled.devid,DEVICE_CNT);/*4 创建设备类使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。该函数的参数包括所属的模块(THIS_MODULE)和设备类的名称(DEVICE_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */gpioled.class = class_create(THIS_MODULE,DEVICE_NAME);if(IS_ERR(gpioled.class)){printk("newchr fail!\r\n");return PTR_ERR(gpioled.class);}/*5 创建设备使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点。参数包括设备所属的类(newchr.class)、父设备(NULL,如果没有父设备)、设备号(newchr.devid)、设备私有数据(NULL,一般为设备驱动程序提供一个指针)和设备名称(DEVICE_NAME)。如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEVICE_NAME);if(IS_ERR(gpioled.device)){printk("newchr fail!\r\n");return PTR_ERR(gpioled.device);}ret = myled_init(&gpioled);#if 0/* 5、设置 LED 所使用的 GPIO */ gpioled.nd = of_find_node_by_path("/gpioled");if(gpioled.nd == NULL){printk("gpioled node cant not found!\r\n");return -EINVAL;}else{printk("gpioled node hase been found!\r\n");}/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */ gpioled.led_gpio =  of_get_named_gpio(gpioled.nd,"gpios",0);if(gpioled.led_gpio < 0){printk("can't get led-gpio\r\n");return -EINVAL;}printk("led-gpio num = %d\r\n", gpioled.led_gpio); /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */ gpio_request(gpioled.led_gpio,"led0");gpio_direction_output(gpioled.led_gpio,1);
#endifreturn 0;
}
static int led_remove(struct platform_device *dev)
{gpio_set_value(gpioled.led_gpio,1);gpio_free(gpioled.led_gpio);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/*在模块卸载时,使用 cdev_del() 函数注销字符设备驱动,并使用 unregister_chrdev_region() 函数释放设备号资源。*//* 注销字符设备驱动 */cdev_del(&gpioled.cdev);/*  删除cdev */unregister_chrdev_region(gpioled.devid, DEVICE_CNT); /* 注销设备号 */device_destroy(gpioled.class, gpioled.devid);// 销毁设备,删除相应的设备节点class_destroy(gpioled.class);// 销毁设备类,释放相关资源printk("gpioled exit!\r\n");return 0;
}
/************************platfrom操作函数-endn***********************************************/static int __init gpioled_init(void)
{return platform_driver_register(&led_driver);
}static void __exit gpioled_exit(void)
{platform_driver_unregister(&led_driver);
}module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("oudafa");

五   、运行测试

1.编写 Makefile 文件

编写完使用make命令编译驱动程序。

KERN_DIR = /home/odf/linux-imx/linux-imxall:clearmake -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o dtsplatledApp dtsplatledApp.c clean:clearmake -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f dtsplatledAppobj-m += dtsplatled.o 

 

2.使用nfs挂载到开发板上。

将编译出来 dtsplatled.ko 和dtsplatledApp 拷贝到 rootfs/lib/modules/4.1.15 目录中,

sudo cp dtsplatled.ko dtsplatledApp /home/odf/nfs_rootfs/rootfs/lib/modules/4.1.15/ 

    驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在

dtsplatled.c 中设置 led_driver (platform_driver 类型)的 name 字段为“imx6ul-led”,因此会在

/sys/bus/platform/drivers/目录下存在名为“imx6ul-led”这个文件

重启开发板,进 入到目录 lib/modules/4.1.15 中,输入如下命令加载 dtsplatled.ko 这个驱动模块。

insmod dtsplatled.ko

 驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图一行语句:

 3.测试:

驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:  

./ledApp /dev/dtsplatled 1 

 输入如下命令关闭 LED 灯:  

./ledApp /dev/dtsplatled 0

相关文章:

MX6ULL学习笔记 (八) platform 设备驱动实验

前言&#xff1a; 什么是 Linux 下的 platform 设备驱动 Linux下的字符设备驱动一般都比较简单&#xff0c;只是对IO进行简单的读写操作。但是I2C、SPI、LCD、USB等外设的驱动就比较复杂了&#xff0c;需要考虑到驱动的可重用性&#xff0c;以避免内核中存在大量重复代码&…...

初识Linux:权限(2)

目录 权限 用户&#xff08;角色&#xff09; 文件权限属性 文件的权限属性&#xff1a; 有无权限的区别&#xff1a; 身份匹配&#xff1a; 拥有者、所属组的修改&#xff1a; 八进制的转化&#xff1a; 文件的类型&#xff1a; x可执行权限为什么不能执行&#xf…...

测试环境使用问题及其优化对策实践

1 背景及问题 G.J.Myers在<软件测试技巧>中提出&#xff1a;测试是为了寻找错误而运行程序的过程&#xff0c;一个好的测试用例是指很可能找到迄今为止尚未发现的错误的测试&#xff0c; 一个成功的测试是揭示了迄今为止尚未发现的错误的测试。 对于新手来说&#xff0…...

【力扣】206.反转链表

206.反转链表 这道题有两种解法&#xff0c;但不只有两种&#xff0c;嘿嘿。 法一&#xff1a;迭代法 就是按循序遍历将每一个指针的指向都给改了。比如说1——>2——>3改为null<——1<——2<——3这样。那这里以第二个结点为例&#xff0c;想一想。我想要指向…...

Python:核心知识点整理大全7-笔记

目录 4.2.5 遗漏了冒号 4.3 创建数值列表 4.3.1 使用函数 range() 4.3.2 使用 range()创建数字列表 结果如下&#xff1a; 4.3.3 对数字列表执行简单的统计计算 4.3.4 列表解析 4.4 使用列表的一部分 4.4.1 切片 4.4.2 遍历切片 4.4.3 复制列表 4.2.5 遗漏了冒号 fo…...

Hadoop学习笔记(HDP)-Part.15 安装HIVE

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …...

【力扣100】4.移动零

题目链接 我的题解&#xff1a; class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""# 思路是先计算共有几个0&#xff0c;然后remove几次&#xff0c;再末位加几个…...

Filebeat使用指南

Filebeat介绍主要优势主要功能配置日志的解析Kibana中设置日志解析安装步骤安装Filebeat安装监控通过prometheus监控 Filebeat和Logstash的主要区别 Filebeat介绍 Filebeat是使用Golang实现的轻量型日志采集器&#xff0c;也是Elasticsearch stack的一员。它可以作为一个agent…...

【Vue2】Vue的介绍与Vue的第一个实例

文章目录 前言一、为什么要学习Vue二、什么是Vue1.什么是构建用户界面2.什么是渐进式Vue的两种开发方式&#xff1a; 3.什么是框架 三、创建Vue实例四、插值表达式 {{}}1.作用&#xff1a;利用表达式进行插值&#xff0c;渲染到页面中2.语法3.错误用法 五、响应式特性1.什么是响…...

十五届蓝桥杯分享会(一)

注&#xff1a;省赛4月&#xff0c;决赛6月 一、蓝桥杯整体介绍 1.十四届蓝桥杯软件电子赛参赛人数&#xff1a;C 8w&#xff0c;java/python 2w&#xff0c;web 4k&#xff0c;单片机 1.8w&#xff0c;嵌入式/EDA5k&#xff0c;物联网 300 1.1设计类参赛人数&#xff1a;平…...

原生video设置控制面板controls显示哪些控件

之前我们学习了如何使用原生video播放视频 今天来一个进阶版的——设置控制面板controls显示哪些控件 先看一下当我们使用原生video时&#xff0c;controls属性为true时&#xff0c;相关代码如下&#xff1a; 正常的控制面板默认显示的控件有&#xff1a;播放、时间线、音量调…...

openlayers地图使用---跟随地图比例尺动态标绘大小的一种方式2

openlayers地图使用—跟随地图比例尺动态标绘大小的一种方式2 预期&#xff1a;随着地图比例尺放大缩小&#xff0c;地图上的标绘随着变化尺寸 思路&#xff1a;通过不断添加地图图层实现标绘的动态缩放 优点&#xff1a;标绘放大缩小非常流畅 缺点&#xff1a;标绘超过1000…...

C语言期末考试复习PTA数据类型及表达式-分支结构程序-循环结构-数组经典选择题

目录 第一章&#xff1a;C语言数据类型和表达式 第一题&#xff1a; 第二题&#xff1a; 第三题&#xff1a; 第四题&#xff1a; 第五题&#xff1a; 第六题&#xff1a; 第七题&#xff1a; 第八题&#xff1a; 第九题&#xff1a; 第二章&#xff1a;分支结构程序…...

RHEL8_Linux访问NFS存储及自动挂载

本章主要介绍NFS客户端的使用 创建FNS服务器并通过NFS共享一个目录在客户端上访问NFS共享的目录自动挂载的配置和使用 1.访问NFS存储 前面介绍了本地存储&#xff0c;本章就来介绍如何使用网络上的存储设备。NFS即网络文件系统&#xff0c;所实现的是 Linux 和 Linux 之间的共…...

python 使用 AppiumService 类启动appium server

一、前置说明 在Appium的1.6.0版本中引入了AppiumService类&#xff0c;可以很方便的通过该类来管理Appium服务器的启动和停止。 二、操作步骤 import osfrom appium.webdriver.appium_service import AppiumService as OriginalServerfrom libs import pathclass AppiumSer…...

HbuilderX使用Uniapp+Vue3安装uview-plus

如果你是vue2版本想使用uniapp去配置uviewui库可以参考之前的文章 小程序的第三方ui库推荐较多的还是uview的&#xff0c;看起来比较美观&#xff0c;功能也比较完善&#xff0c;下面将提一下Vue3安装uview-plus库的教程 创建项目 安装 首先进入官网 uView-Plus 直接下载并导…...

【Android】Java NIO(New I/O)的`Selector`类来实现非阻塞的Socket监听

如果你不想使用循环来监听客户端的连接和数据&#xff0c;你可以使用Java NIO&#xff08;New I/O&#xff09;的Selector类来实现非阻塞的Socket监听。Selector类提供了一种选择一组已经就绪的通道的机制&#xff0c;这样你就不需要使用循环来等待连接和数据。 以下是使用Sel…...

『亚马逊云科技产品测评』在当前飞速发展的AI人工智能时代云服务技术哪家强?

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 引言一、亚马逊&阿里云发展历史介绍1.1 亚马逊发展历史1.2…...

经典神经网络——ResNet模型论文详解及代码复现

论文地址&#xff1a;Deep Residual Learning for Image Recognition (thecvf.com) PyTorch官方代码实现&#xff1a;vision/torchvision/models/resnet.py at main pytorch/vision (github.com) B站讲解&#xff1a; 【精读AI论文】ResNet深度残差网络_哔哩哔哩_bilibili …...

OpenCV-Python:DevCloud CodeLab介绍及学习

1.Opencv-Python演示环境 windows10 X64 企业版系统python 3.6.5 X64OpenCV-Python 3.4.2.16本地PyCharm IDE线上注册intel账号&#xff0c;使用DevCloud CodeLab 平台 2.DevCloud CodeLab是什么&#xff1f; DevCloud是一个基于云端的开发平台&#xff0c;提供了强大的计算…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...