ZYNQ之嵌入式驱动开发——字符设备驱动

news2024/11/15 9:25:42

文章目录

  • Linux驱动程序分类
  • Linux应用程序和驱动程序的关系
  • 简单的测试驱动程序
  • 在petalinux中添加LED驱动
  • 新字符设备驱动

Linux驱动程序分类

驱动程序分为字符设备驱动、块设备驱动和网络设备驱动。
字符设备是按字节访问的设备,比如以一个字节收发数据的串口,字符设备在Linux外设中占比最大。
块设备的特点是按一定格式存取的数据,具体的格式由文件系统决定。块设备以存储设备为主,存储设备的特点是以存储块为基础,因此得名块设备。
网络设备不同于上面两种,应用程序和网络设备驱动之间的通信由库和内核提供的一套数据包传输函数替代了open()、read()、write()等函数。


Linux应用程序和驱动程序的关系

(1)应用程序调用库函数提供的open()函数打开某个设备文件,该设备文件是在驱动加载成功之后在目录/dev中生成的,是应用程序调用相应硬件的入口。
(2)库根据open()函数的输入参数引起CPU异常进入内核,系统调用处于内核空间,应用程序无法直接访问,因此需要陷入到内核,方法就是软中断,陷入内核后还要指定系统调用号;
(3)内核的异常处理函数根据输入参数找到相应的驱动程序,返回文件句柄给库,库函数再返回给应用程序;
(4)应用程序再使用得到的文件句柄调用write()、read()等函数发出控制指令;
(5)库根据write()、read()等函数的输入参数引起CPU异常,进入内核;
(6)内核的异常处理函数根据输入参数调用相应的驱动程序执行相应的操作。
Linux应用程序调用驱动程序的步骤如下图所示。
在这里插入图片描述
应用程序中涉及到的open()、read()、write()等是由库提供的系统调用,通过执行某条指令引发异常进入内核,是应用程序操作硬件的途径。应用程序执行系统调用后进入内核,然后会使用驱动程序中对应的函数,驱动程序中的open()、read()、write()等函数是需要驱动开发人员实现的。应用程序运行于用户空间,驱动程序运行于内核空间,Linux系统可以通过MMU限制应用程序运行在某个内存块中,以避免这个应用程序出错导致整个系统崩溃,运行于内核空间的驱动程序是系统的一部分,驱动程序出错有可能牵连整个系统。


简单的测试驱动程序

在进行LED驱动开发之前,先使用下面的代码简单测试一下。

#include <linux/module.h>

static int __init chardev_init(void)
{
    printk("Hello!\n");
    return 0;
}

static void __exit chardev_exit(void)
{
    printk("GoodBye!\n");
}

module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");

如果使用Linux内核编译上面的C文件,需要自己写Makefile,然后编译出驱动文件到7020开发板上验证,验证的结果如下图所示。
在这里插入图片描述
提示这个驱动是无效的模块格式,可能ZYNQ开发板就需要使用petalinux这样特定的工具进行开发,下面来看具体流程。
首先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

进入到定制系统的根目录下,使用下面的命令添加字符设备驱动。

petalinux-create -t modules -n chardev

在当前路径下的/project-spec/meta-user/recipes-modules/下生成了一个名为chardev的文件夹,该文件夹下有以下三个文件,其中.c文件就是需要写入驱动代码的文件。
在这里插入图片描述
Makefile在创建工程的时候已经创建好了,里面的内容如下图所示,也不需要修改。
在这里插入图片描述
在C文件中写入驱动代码后,返回到自定义的/zynq7020目录下,使用下面的命令进行编译。

petalinux-build -c chardev

编译完成后的信息打印如下图所示。
在这里插入图片描述
由于编译成的驱动文件存放路径比较难找,因此直接在搜索栏中直接搜索驱动的名称就会出现,但是文件夹必须打开到自定义的工程的这一层才能搜索到。
在这里插入图片描述
可以右键该文件打开文件的具体存在位置为/opt/pkg/petalinux/zynq7020/build/tmp/sysroots-components/plnx_zynq7/chardev/lib /modules/4.14.0-xilinx-v2018.3/extra。
接下来就可以在开发板上验证该驱动了,验证的结果如下图所示。
在这里插入图片描述


在petalinux中添加LED驱动

同上面的示例,先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

然后进入到定制系统的根目录下,使用下面的命令添加驱动。

petalinux-create -t modules -n psled1-driver

需要注意的是,驱动文件命名不能使用下划线,而要使用"-"代替。
在这里插入图片描述
创建成功以后,打印的消息提示创建的模块在当前路径下的/project-spec/meta-user/recipes-modules/psled1-driver中,进到这个目录下。在这里插入图片描述
一步步进到最终的目录下,在files文件夹下的.c文件就是要写入驱动代码的地方,在里面键入下面的代码。

//该代码来自ZYNQ教程,教程在文末给出
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  
  
/* 驱动名称 */  
#define DEVICE_NAME       "ps_led1"  
/* 驱动主设备号 */  
#define GPIO_LED_MAJOR    200  
  
/* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  
  
/* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        
  
/* open函数实现, 对应到Linux系统调用函数的open函数 */  
static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  
	/* 把需要修改的物理地址映射到虚拟地址 */
	gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  
	clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE);
    /* MIO_0时钟使能 */  
    *APER_CLK_CTRL |= 0x00400000;  
    /* MIO_0设置成输出 */  
    *GPIO_DIRM_0 |= 0x00000001;  
    /* MIO_0使能 */  
    *GPIO_OEN_0 |= 0x00000001;  
    printk("gpio_test module open\n");   
    return 0;  
}  
  
/* write函数实现, 对应到Linux系统调用函数的write函数 */  
static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  
    int rst;  
    char writeBuf[5] = {0};  
    printk("gpio_test module write\n");  
    rst = copy_from_user(writeBuf, buf, len);  
    if(0 != rst)  
    {  
        return -1;    
    }  
      
    if(1 != len)  
    {  
        printk("gpio_test len err\n");  
        return -2;  
    }  
    if(1 == writeBuf[0])  
    {  
        *GPIO_DATA_0 &= 0xFFFFFFFE;  
        printk("gpio_test ON\n");  
    }  
    else if(0 == writeBuf[0])  
    {  
        *GPIO_DATA_0 |= 0x00000001;  
        printk("gpio_test OFF\n");  
    }  
    else  
    {  
        printk("gpio_test para err\n");  
        return -3;  
    }    
    return 0;  
}  
  
/* release函数实现, 对应到Linux系统调用函数的close函数 */  
static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{     	
    printk("gpio_test module release\n");  
    return 0;  
}  
	  
/* file_operations结构体声明, 是上面open、write实现函数与系统调用函数对应的关键 */  
static struct file_operations gpio_leds_fops = {  
    .owner   = THIS_MODULE,  
    .open    = gpio_leds_open,  
    .write   = gpio_leds_write,     
    .release = gpio_leds_release,   
};  
  
/* 模块加载时会调用的函数 */  
static int __init gpio_led_init(void)  
{  
    int ret;  
    /* 通过模块主设备号、名称、模块带有的功能函数(及file_operations结构体)来注册模块 */  
    ret = register_chrdev(GPIO_LED_MAJOR, DEVICE_NAME, &gpio_leds_fops);  
    if (ret < 0)   
    {  
        printk("gpio_led_dev_init_error\n");  
        return ret;  
    }  
    else  
    {  
        /* 注册成功 */ 
        printk("gpio_led_dev_init_ok\n");  
    }  
    return 0;  
}  
  
/* 卸载模块 */  
static void __exit gpio_led_exit(void)  
{  
    /* 释放对虚拟地址的占用 */  
    iounmap((unsigned int *)gpio_add_minor);  
    iounmap((unsigned int *)clk_add_minor); 
    /* 注销模块, 释放模块对这个设备号和名称的占用 */  
    unregister_chrdev(GPIO_LED_MAJOR, DEVICE_NAME);
    printk("gpio_led_dev_exit_ok\n");  
}  
  
/* 标记加载、卸载函数 */  
module_init(gpio_led_init);  
module_exit(gpio_led_exit);  
  
/* 驱动描述信息 */  
MODULE_AUTHOR("Alinx");  
MODULE_ALIAS("gpio_led");  
MODULE_DESCRIPTION("GPIO LED driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  

上面介绍的是直接编译驱动,也可以以图形化的形式进行编译,返回到自定义的/zynq7020目录下,输入下面的命令配置根文件系统。

petalinux-config -c rootfs

在弹出的图形化配置窗口中选择modules进入子菜单中。
在这里插入图片描述
按Y键将该驱动包括进来,然后保存退出。
在这里插入图片描述
根文件系统就配置成功了,然后使用petalinux-build命令编译该工程。
在这里插入图片描述
编译成功后打印下面的信息。
在这里插入图片描述
在/zynq7020目录下搜索驱动文件,如下图所示。
在这里插入图片描述
右键该文件选择打开文件存放位置,其存放在/zynq7020/build/tmp/sysroots-components/plnx_zynq7/psled1-driver/lib/modules/4.14.0-xilinx-v2018.3/extra,还是比较难找的,所以以后直接在工程目录下搜索即可。
在开发板上加载驱动,可以看到相应的设备号已经出现了。
在这里插入图片描述
使用下面的命令创建字符设备文件,指定主设备号和次设备号,设备文件名称为psled1,之后写应用程序的时候要使用该名称来操作字符设备。

mknod /dev/psled1 c 200 0

创建设备文件成功之后在/dev目录下就可以看到新添加的设备,如下图所示。
在这里插入图片描述
接下来写一个应用端的测试程序,用来传入数据点亮或者熄灭LED,程序如下。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    int status;
    fd = open("/dev/psled1", O_RDWR);
    if(fd < 0)
    {
        perror("open /dev/psled1 error!\n"); 
        return fd; 
    }
    status = atoi(argv[1]);
    write(fd, &status, 1);
    close(fd);  
    return 0;
}

将上面的程序通过交叉编译工具编译出适合在ARM平台运行的文件,将其发送到开发板验证,结果如下图所示。
在这里插入图片描述
如果采用下面的应用程序进行测试,LED将每隔一秒改变一下状态。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    int status;
    fd = open("/dev/psled1", O_RDWR);
    if(fd < 0)
    {
        perror("open /dev/psled1 error!\n"); 
        return fd; 
    }
   while(1)
   {
        status = 1;
        write(fd, &status, 1);
        sleep(1);
        status = 0;
        write(fd, &status, 1);
        sleep(1);
    }
    close(fd);  
    return 0;
}

在开发板上执行后的结果如下图所示。
在这里插入图片描述
开发板上PS LED1的状态开始循环亮灭,如下动图所示。
请添加图片描述
终端里也是每隔一秒打印一次LED关闭或打开的状态。请添加图片描述
卸载驱动程序后打印下面的信息。
在这里插入图片描述

新字符设备驱动

上面驱动代码中将设备号写死了,这样做有很多不便之处,因为编译驱动代码前需要查看目标系统中设备号的占用情况,驱动注册函数中仅有主设备号没有次设备号,这意味着一个设备会占用所有的次设备号,十分浪费资源。针对这些问题,Linux内核提出了新的字符设备注册方法,并由内核来管理设备号。
注册字符设备号的函数原型如下。

int register_chrdev_region(dev_t from,unsigned count,const char* name);

from :需要申请的起始设备号,取代了原有的主设备号和次设备号,在需要指定主次设备号的情况下,可以通过方法from = MKDEV(major,minor); 来实现。
count :需要申请的设备号个数。
name :设备名称。
在不需要指定主次设备号的情况下,设备号由内核来分配,传入指针来获取设备号,注册、注销设备号的函数原型如下。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char* name);
void unregister_chrdev_region(dev_t from,unsigned count);

dev :设备号指针,注册成功之后,主次设备号可以通过 major = MAJOR(dev); minor = MINOR(dev); 来获取。
baseminor :次设备号的起始地址。
新的注册方法使用cdev结构体来定义一个字符设备,cdev结构体如下。

struct cdev
{
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

cdev结构体的初始化函数原型如下。

void cdev_init(struct cdev *cdev,const struct file_operations *fops);

注册、注销字符设备的函数原型如下。

int cdev_add(struct cdev *cdev,dev_t dev,unsigned count);
void cdev_del(struct cdev *cdev);

类的创建和删除函数原型如下。

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key);
void class_destroy(struct class *cls);

owner指定为THIS_MODULE,name是类的名称,第三个参数可以省略。
设备节点的创建和删除函数原型如下。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void * drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);

class是通过class_create创建的类,parent是父设备,无则填NULL,devt是设备号,drvdata是设备可能用到的数据,没有则填NULL,fmt是设备名,创建成功后在/dev下生成。
下面代码使用的是新字符设备方法。

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
  
#define DEVICE_NAME       "psled1"
#define DEVID_COUNT       1   //设备号个数 
#define DRIVE_COUNT       1   //驱动个数
  
/* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  
  
/* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        

#if 0
struct chardev
{
	dev_t            devid;      //设备号
	struct cdev      cdev;       //字符设备
	struct class     *class;     //类
	struct device    *device;    //设备节点
};

static struct chardev alinx_char = {
	.cdev = {
		.owner = THIS_MODULE,
	},
};
#endif

dev_t            devid;      //设备号
struct cdev      cdev;       //字符设备
struct class     *class;     //类
struct device    *device;    //设备节点

static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  
	gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  
	clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE); 
    /* MIO_0时钟使能 */  
    *APER_CLK_CTRL |= 0x00400000;  
    /* MIO_0设置成输出 */  
    *GPIO_DIRM_0 |= 0x00000001;  
    /* MIO_0使能 */  
    *GPIO_OEN_0 |= 0x00000001;    
    printk("gpio_test module open\n");   
    return 0;  
}  

static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  
    int rst;  
    char writeBuf[5] = {0};   
    printk("gpio_test module write\n");  
    rst = copy_from_user(writeBuf, buf, len);  
    if(0 != rst)  
    {  
        return -1;    
    }  
    if(1 != len)  
    {  
        printk("gpio_test len err\n");  
        return -2;  
    }  
    if(1 == writeBuf[0])  
    {  
        *GPIO_DATA_0 &= 0xFFFFFFFE;  
        printk("gpio_test ON\n");  
    }  
    else if(0 == writeBuf[0])  
    {  
        *GPIO_DATA_0 |= 0x00000001;  
        printk("gpio_test OFF\n");  
    }  
    else  
    {  
        printk("gpio_test para err\n");  
        return -3;  
    }  
    return 0;  
}  
   
static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{  
    printk("gpio_test module release\n");  
    return 0;  
}  
	  
static struct file_operations chardev_fops = {  
    .owner   = THIS_MODULE,  
    .open    = gpio_leds_open,  
    .write   = gpio_leds_write,     
    .release = gpio_leds_release,   
};  
  
static int __init gpio_led_init(void)  
{  
    #if 0
	alloc_chrdev_region(&alinx_char.devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号
	cdev_init(&alinx_char.cdev, &chardev_fops);  //初始化字符设备结构体
	cdev_add(&alinx_char.cdev, alinx_char.devid, DRIVE_COUNT);  //注册字符设备 
	alinx_char.class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类
	if(IS_ERR(alinx_char.class)) 
	{
		return PTR_ERR(alinx_char.class);
	}
	alinx_char.device = device_create(alinx_char.class, NULL, alinx_char.devid, NULL, DEVICE_NAME);  //创建设备节点
    printk("alloc success, major = %d minor = %d\n",MAJOR(alinx_char.devid),MINOR(alinx_char.devid));
	if (IS_ERR(alinx_char.device)) 
	{
		return PTR_ERR(alinx_char.device);
	}
    #endif
    alloc_chrdev_region(&devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号
    cdev.owner = THIS_MODULE;
	cdev_init(&cdev, &chardev_fops);  //初始化字符设备结构体
	cdev_add(&cdev, devid, DRIVE_COUNT);  //注册字符设备 
	class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类
	if(IS_ERR(class)) 
	{
		return PTR_ERR(class);
	}
	device = device_create(class, NULL, devid, NULL, DEVICE_NAME);  //创建设备节点
    printk("alloc success, major = %d minor = %d\n",MAJOR(devid),MINOR(devid));
	if (IS_ERR(device)) 
	{
		return PTR_ERR(device);
	}
    return 0;  
}

static void __exit gpio_led_exit(void)  
{  
    iounmap((unsigned int *)gpio_add_minor);  
    iounmap((unsigned int *)clk_add_minor); 
    #if 0
	cdev_del(&alinx_char.cdev);   //注销字符设备
	unregister_chrdev_region(alinx_char.devid, DEVID_COUNT);  //注销设备号
	device_destroy(alinx_char.class, alinx_char.devid);  //删除设备节点
	class_destroy(alinx_char.class);  //删除类
    #endif
    cdev_del(&cdev);   //注销字符设备
	unregister_chrdev_region(devid, DEVID_COUNT);  //注销设备号
	device_destroy(class, devid);  //删除设备节点
	class_destroy(class);  //删除类
    printk("gpio_led_dev_exit_ok\n");  
}  
 
module_init(gpio_led_init);  
module_exit(gpio_led_exit);  
MODULE_LICENSE("GPL");  

加载驱动之后,内核就会为设备指定主设备号和次设备号,不用再使用命中自己指定了。
在这里插入图片描述


参考文档:course_s6_ZYNQ那些事儿-Linux驱动篇V1.05

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1680253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

谷歌全力反击 OpenAI:Google I/O 2024 揭晓 AI 新篇章,一场激动人心的技术盛宴

&#x1f680; 谷歌全力反击 OpenAI&#xff1a;Google I/O 2024 揭晓 AI 新篇章&#xff0c;一场激动人心的技术盛宴&#xff01; 在这个人工智能的全新时代&#xff0c;只有谷歌能让你眼前一亮&#xff01;来自全球瞩目的 Google I/O 2024 开发者大会&#xff0c;谷歌用一场…

项目组GIT操作规范

分支规范 在开发过程中&#xff0c;一般会存在以下几种分支&#xff1a; main分支(master) master为主分支&#xff0c;也是用于部署生产环境的分支&#xff0c;一般由 dev 以及 fixbug分支合并&#xff0c;任何时间都不能直接修改代码。dev分支 develop 为开发分支&#xff…

Altium Designer封装库和元器件符号库下载与导入教程(SnapEDA 、Ultra Librarian、Alldatasheetcn)

1.AD封装库和元器件符号库下载网址 以下是一些全球热门的Altium Designer封装库和元器件符号库下载网址推荐&#xff1a; Altium Content Vault (现称为Altium Manufacturer Part Search)&#xff1a;这是Altium官方提供的元器件库&#xff0c;可以直接在Altium Designer中使用…

Java码农的福音:再也不怕乱码了

即便是Java这样成熟的语言&#xff0c;开发者们也常常会遇到一个恼人的问题——乱码。 本文将深入探讨乱码的根本原因&#xff0c;并针对Java开发中的乱码场景提出有效的解决方案&#xff0c;辅以实战代码&#xff0c;让Java程序员从此告别乱码困扰。 一&#xff0c;字符集的…

文件存储解决方案-阿里云OSS

文章目录 1.菜单分级显示问题1.问题引出1.苹果灯&#xff0c;放到节能灯下面也就是id大于1272.查看菜单&#xff0c;并没有出现苹果灯3.放到灯具下面id42&#xff0c;就可以显示 2.问题分析和解决1.判断可能出现问题的位置2.找到递归返回树形菜单数据的位置3.这里出现问题的原因…

什么是最大路径?什么是极大路径?

最近学习中&#xff0c;在这两个概念上出现了混淆&#xff0c;导致了一些误解&#xff0c;在此厘清。 最大路径 在一个简单图G中&#xff0c;u、v之间的距离 d ( u , v ) min ⁡ { u 到 v 的最短路的长度 } d(u,v) \min \{ u到v的最短路的长度 \} d(u,v)min{u到v的最短路的…

音乐的力量

常听音乐的好处可以让人消除工作紧张、减轻生活压力、避免各类慢性疾病等等&#xff0c;其实这些都是有医学根据的。‍ 在医学研究中发现&#xff0c;经常的接触音乐节 奏、旋律会对人体的脑波、心跳、肠胃蠕动、神经感应等等&#xff0c;产生某些作用&#xff0c;进而促进身心…

Postman基础功能-接口返回值获取

大家好&#xff0c;之前给大家分享关于Postman的接口关联&#xff0c;我们平时在做接口测试时&#xff0c;请求接口返回的数据都是很复杂的 JSON 数据&#xff0c;有着多层嵌套&#xff0c;这样的数据层级在 Postman 中要怎么获取呢&#xff1f; 接下来给大家展示几个获取 JSO…

容联云零代码平台容犀desk:重新定义坐席工作台

在数智化浪潮的推动下&#xff0c;企业亟待灵活适应市场变化、快速响应客户需求&#xff0c;同时还要控制成本并提升效率&#xff0c;传统的软件开发模式因开发周期长、成本高、更新迭代慢等问题&#xff0c;逐渐难以满足企业灵活多变的业务需求。 容犀Desk&#xff0c;观察到…

(1)双指针算法介绍与练习:移动零

目录 双指针算法介绍 练习&#xff1a;移动零 双指针算法介绍 双指针算法常见于数组和双向链表的题型 在数组中&#xff0c;双指针中的指针代表数组元素的下标&#xff0c;而不是真正的指针类型变量 在双向链表中&#xff0c;双指针中的指针即为真正意义上的指针&#xff…

Windows安装Django

1、下载Python程序包 Python程序包官网下载地址Download Python | Python.org,若下载最新版本&#xff0c;有最新版本则下载"Windows installer (64-bit)" 若是下载其他版本,可在下图位置找到相应的版本,然后点击Download.如下图所示&#xff1a; 打开后查看注意事项…

开源连锁收银系统哪个好

针对开源连锁收银系统的选择&#xff0c;商淘云是一个备受关注的候选。商淘云以其功能丰富、易于定制和稳定性等优势&#xff0c;吸引了众多企业和开发者的关注。下面将从四个方面探讨商淘云开源连锁收银系统的优势&#xff1a; 首先&#xff0c;商淘云提供了丰富的功能模块。作…

Retrying,一个神奇优雅的 Python 库

大家好&#xff01;我是爱摸鱼的小鸿&#xff0c;关注我&#xff0c;收看每期的编程干货。 一个简单的库&#xff0c;也许能够开启我们的智慧之门&#xff0c; 一个普通的方法&#xff0c;也许能在危急时刻挽救我们于水深火热&#xff0c; 一个新颖的思维方式&#xff0c;也许能…

Docker安装Mosquitto

在物联网项目中&#xff0c;我们经常用到MQTT协议&#xff0c;用MQTT协议做交互就需要部署一个MQTT服务&#xff0c;而mosquitto是一个常用的MQTT应用服务&#xff0c; Mosquitto是一个实现了消息推送协议MQTT v3.1的开源消息代理软件。MQTT&#xff08;Message Queuing Teleme…

AI大模型日报#0515:Google I/O大会、 Ilya官宣离职、腾讯混元文生图大模型开源

导读&#xff1a;欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”&#xff08;ERNIE 4.0&#xff09;、“零一万物”&#xff08;Yi-34B&#xff09;生成了今日要点以及每条资讯的摘要。 《AI大模型日报》今日要点&#xff1a;谷歌…

Java 自然排序和比较器排序区别?Comparable接口和Comparator比较器区别?

注&#xff1a;如果你对排序不理解&#xff0c;请您耐心看完&#xff0c;你一定会明白的。文章通俗易懂。建议用idea运行一下案例。 1&#xff09;自然排序和比较器排序的区别&#xff1f; 自然排序是对象本身定义的排序规则&#xff0c;由对象实现 Comparable 接口&#xff…

什么?你设计接口什么都不考虑?

如果让你设计一个接口&#xff0c;你会考虑哪些问题&#xff1f; 1.接口参数校验 接口的入参和返回值都需要进行校验。 入参是否不能为空&#xff0c;入参的长度限制是多少&#xff0c;入参的格式限制&#xff0c;如邮箱格式限制 返回值是否为空&#xff0c;如果为空的时候是…

代码随想录算法训练营第二十九天 | 39. 组合总和、40.组合总和II、131.分割回文串

39. 组合总和 题目链接/文章讲解&#xff1a; 代码随想录 视频讲解&#xff1a;带你学透回溯算法-组合总和&#xff08;对应「leetcode」力扣题目&#xff1a;39.组合总和&#xff09;| 回溯法精讲&#xff01;_哔哩哔哩_bilibili 解题思路 这里和组合不同的是元素可以重复选取…

2024 Google I/O大会:全方位解读最新AI技术和产品

引言&#xff1a; 2024年的Google I/O大会如期举行&#xff0c;作为技术圈的年度盛事之一&#xff0c;谷歌展示了其在人工智能领域的最新进展。本次大会尤其引人注目&#xff0c;因为它紧随着OpenAI昨天发布GPT-4o的脚步。让我们详细解析Google此次公布的各项新技术和产品&…

【C语言】6.C语言VS实用调试技巧(1)

文章目录 1.什么是 bug2.什么是调试&#xff08;debug&#xff09;&#xff1f;3.Debug 和 Release4.VS调试快捷键4.1 环境准备4.2 调试快捷键 5.监视和内存观察5.1 监视5.2 内存 1.什么是 bug bug现在一般是指在电脑系统或程序中&#xff0c;隐藏着的一些未被发现的缺陷或问题…