Linux驱动开发——PCI设备驱动

news2025/1/12 11:59:11

目录

一、 PCI协议简介

二、PCI和PCI-e

三、Linux PCI驱动

四、 PCI设备驱动实例

五、 总线类设备驱动开发习题



一、 PCI协议简介


        PCI (Peripheral Component Interconnect,外设部件互联) 局部总线是由Intel 公司联合其他几家公司一起开发的一种总线标准,最初是为了替代 ISA 之类的总线,用于解决当时的图形化界面显示器的带宽问题。相比于 ISA 总线,它最大的特点是高带宽、突发传输和即插即用(热插拔)。在 PCI 3.0 的规范中,PCI 局部总线的时钟速率有 33MHZ、66MHz 和133MHz 三种标准速率,支持的数据位宽有 32 位和 64 位两种。所以最低的数据传输率为33MHz x 32bit = 132MB/s,即每秒 132M 字节,这完全满足当时的图形显卡的要求。突发传输是指其地址总线和数据总线复用,在传输开始时先发地址,然后再连续传输若干个字节的数据,这样做的好处是可以减少芯片的管脚,并且一个传输周期可以完成若干个字节的传输。即插即用和前面谈到的 USB 类似,总线上的设备存放有配置信息,在初始化的过程中,主机会主动获取这些信息,从而分配其所需要的资源,这会在后面做更详细的介绍。随着 PCI 局部总线的发展,其应用的领域也越来越广泛,现在 PC 中独立的网卡、声卡、数据采集卡等使用的都是 PCI 局部总线。后来又推出了串行的标准,PCI-Express,其传输速率相当高,在 PCI-Express 3.0 规范中,其传输率可以达到 8GT/s即每秒 8G 次传输。因为使用的广泛性,在某些嵌入式系统中也使用了 PCI或 PCI-Express局部总线。下面简单介绍一下 PCI3.0 规范中驱动开发者需要关心的内容,下图是 PCI系统的连接框图(引自 PCI3.0 规范)。


        处理器(Processor)通过 Host/PCI桥(Bridge)连接到了 0号 PCI局部总线(PCI LocalBus #0),在这条局部总线上,有声卡 (Audio)、动态视频(Motion Video)、图形显卡(Graphics)、网卡(LAN)和 SCSI控制器等。通过 PCI-to-PCI Bridge,又扩展出了1号PCI局部总线(PCILocal Bus #1),在这条总线上又接入了其他 PCI功能设备。另外,还有PCI-ISA 桥,可以将 PCI 总线转换为传统的ISA 总线。

        PCI局部总线也是主从结构,在 PCI的规范中主设备叫发起者(Initiator),从设备叫目标(Target),传输由主设备发起,从设备进行响应。一个 PCI 设备都要实现目标的功能,但也可以实现发起者的功能,也就是说,一个设备既可以在某一时刻做主设备,也可以在另一个时刻做从设备。并且一条总线上允许有多个主设备,由仲裁器来决定哪个主设备可以获得总线的控制权。下面我们仅讨论 PCI的从设备。
        PCI 定义了三个物理地址空间,包括内存地址空间、I/O 地址空间和配置地址空间。

        其中配置地址空间是必需的,这个地址空间用于对设备的硬件进行配置。为了更好地理解这三个地址空间的访问,我们先来看看 PCI 的典型写传输时序图,如下图所示(引自PCI3.0 规范)。


        当发起者要对目标进行写操作时,会先将 FRAME 拉低,在之后的第一个时钟周期AD 总线上是发送地址,C/BE 是总线的命令,用于确定一个更具体的写操作,DEVSEI是被选中的目标发出的确认信号。在之后的若干个周期,AD 总线上是要写入的数据,C/BE 上是字节使能,用于确定哪个字节是有效的。IRDY 和 TRDY 分别是发起者和目标的准备信号,当任一个无效时,都会自动插入等待周期。在最后一个数据周期,FRAME无效,但传输最终完成是在 FRAME 无效后 IRDY 也无效的时刻。PCI 的读传输操作和写基本类似,只是数据的方向相反。上面涉及的总线命令如下图所示(引自 PCI3.0 规范)。


        I/O 读、I/O 写、内存读、内存写、配置读和配置写即我们前面提到的三个物理地址空间的读写。我们首先来看配置空间是如何寻址的,地址结构如下图所示。


        PCI规范定义了两种类型的配置空间地址,Type 0 用于选择总线上的一个设备,Type1 用于将请求传递给另一条总线。地址中的各个字段的含义如下。
        Bus Number:8 位总线地址,在 256条PCI 局部总线中选择一条。
        Device Number:5 位设备地址,在一条总线上的 32 个物理设备中选择一个
        Function Number:3 位功能地址,在一个物理设备上的8个功能中选择一个功能,也就是说,PCI 设备和 USB 设备类似,一个物理设备可以有多个功能,从而实现多个逻辑设备。
        Register Number:用于选择配置空间中的一个 32 位寄存器。

        在 PCI规范中,对配置空间的各寄存器都有具体的定义,整个配置空间有 64 个字节我们并不需要关心配置空间中每个寄存器的含义,下面列出最主要的一些寄存器(其他寄存器的定义及地址请参见 PCI规范)。


        Vendor ID:16 位,硬件厂商ID。
        Device ID:16 位,设备 ID。
        Class Code: 24 位,外设所属的类别,如大容量存储设备控制器类、网络控制器类显示控制器类等。为 0表示不属于某一具体的类。
        Subsystem Vendor ID: 16 位,子系统厂商 ID。
        Subsystem ID:16 位,子系统ID。

        Base Address Registers: 32 位,在计算机启动的过程中,会检查所有的 PCI 设备,其中一个重要的操作就是要获取其使用的内存空间和 I/O 空间的大小,然后给每一个空间分配一个基址,这个基址就是存放在基址寄存器中的。配置空间中共有 6 个这样的基址寄存器,在 Linux 驱动中简称 bar。
        上面的 ID 和 Class 用于匹配驱动程序,基址则用于驱动进行资源获取和映射操作,后面会进行更详细的描述。有了基址寄存器后,对内存空间和 IO 空间的访问问题也就迎刃而解了,因为我们只需要发出相应的内存地址或I/O地址就可以访问对应的空间了。
 

 

二、PCI和PCI-e

        PCI和PCIe都是计算机中用于连接设备的接口标准,但它们之间存在一些重要区别。

        PCI(Peripheral Component Interconnect)是一种早期的计算机总线标准,它被设计用于连接各种高速外围设备,如显卡、声卡、网卡等。PCI总线是一种共享总线,这意味着所有设备共享相同的带宽。因此,当多个设备同时尝试使用总线时,可能会出现性能下降或冲突。

        相比之下,PCIe(Peripheral Component Interconnect Express)是一种更现代的计算机总线标准,也被广泛应用于连接各种高速设备。与PCI不同,PCIe是一种点对点互连协议,这意味着每个设备都有自己的专用连接,不会与其他设备共享带宽。这使得PCIe在性能上大大超越PCI,特别是在高带宽应用和多设备环境中。

        总的来说,PCIe在性能、灵活性和扩展性方面都优于PCI,这也是为什么现在的计算机和设备大多采用PCIe接口的原因。但需要注意的是,由于PCIe的复杂性和成本较高,在一些低带宽和低成本的应用中,PCI仍然可能被使用。

        PCI-e在软件层面是兼容PCI的,PCI是并行传输,PCI-e是串行点对点传输。

三、Linux PCI驱动


        下面我们还是只讨论 PCI 从设备。PCI 设备在内中用 struct pci_dev 结构来表示,该结构的成员非常多,在此就不一一列出了,可以参见内核源码中的 include/linux/pci.h 文件。在里面会发现我们前面提到的 ID、类等成员,还有设备所使用的IRQ 线。设备的 ID 还有一个 struct pci_device_id 结构,驱动中通常会定义这样一个数组,来表示可以支持的设备列表,和前面的 USB 设备列表类似。和 PCI 设备结构相关的主要函数和宏如下。

int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);
pci_resource_start(dev, bar)
pci_resource_end(dev, bar)
pci_resource_flags(dev, bar)
pci_resource_len(dev,bar)
int pci_request_regions (struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);


pci_enable_device: 使能 PCI设备,在操作 PCI设备之前必须先使能设备

pci_disable_device:禁止PCI设备。
pci_resource_start: 获取 dev 中第 bar 个基址寄存器中记录的资源起始地址.

pci_resource_end:获取 dev 中第 bar 个基址寄存器中记录的资源结束地址。
pci_resource_flags:取 dev 中第 bar 个基址寄存器中记录的资源标志,是内存资源还是 IO 资源。
pci_resource_len:获取 dev 中第 bar 个基址寄存器中记录的资源大小。
pci_request_regions: 申请 PCI 设备 pdev 内的内存资源和I/0 资源,取名为res_name.

pci_release_regions: 释放 PCI设备 pdev 内的内存资源和 IO 资源。

        内核中用 struct pci_driver 结构来表示 PCI 设备驱动,相关的主要函数宏如下


 

void pci_unregister_driver(struct pci_driver *dev);
pci_register_driver(driver)
void pci_set_drvdata(struct pci_dev *pdev, void *data);
void *pci_get_drvdata(struct pci_dev *pdev);


pci_register_driver: 注册 PCI 设备驱动 driver
pci_unregister_driver: 注销 PCI 设备驱动 dev。
pci_set_drvdata:保存 data 指针到PCI设备pdev 中。
pci_get_drvdata: 从 PCI 设备 pdev 中获取保存的指针

PCI设备的配置空间访问的主要函数如下。

int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val);
int pei_read_config_dword(const struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(const struct pci_dev *dev, int where, u16 val);
int pei_write_config_dword(const struct pci_dev *dev, int where, u32 val);


上面的函数分别实现了对配置空间的字节、字 (16 位) 和双字(32 位) 的读写操作。


四、 PCI设备驱动实例


这里使用的 PCI 设备是南京沁恒公司的 CH368EVT 评估板,该评估板使用了一片该公司设计的 CH368 PCI-Express 接口芯片,虽然是 PCI-Express 协议,但是在驱动上两者可以兼容,只是 PCI-Express 速率更高,能够支持更多的功能。选用该评估板的原因是其价格低廉,完全国产,也能够全面验证三个空间的读写操作。
        使用L1~L4 这 4个 LED 显示 I/O 数据端口 D3~DO 位的状态。灯亮代表 1,灯灭代表0。
CH368 的配置空间定义如下图所示。


         厂商 ID 和设备 ID 是我们比较关心的内容,驱动的设备列表中的 ID 要和这里的致。第一个基址寄存器是 I/O 地址空间的基址,有 232 个字节,定义如下图所示。另外,CH368 的内存空间有 32KB。

该设备的 Linux 驱动代码如下,为了尽量突出 PCI驱动的核心,并没有加入并发控制相关的代码。

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

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/uaccess.h>

#include "ch368.h"

#define CH368_MAJOR	256
#define CH368_MINOR	11
#define CH368_DEV_NAME	"ch368"

struct ch368_dev {
	void __iomem *io_addr;
	void __iomem *mem_addr;
	unsigned long io_len;
	unsigned long mem_len;
	struct pci_dev *pdev;
	struct cdev cdev;
	dev_t dev;
};

static unsigned int minor = CH368_MINOR;

static int ch368_open(struct inode *inode, struct file *filp)
{
	struct ch368_dev *ch368;

	ch368 = container_of(inode->i_cdev, struct ch368_dev, cdev);
	filp->private_data = ch368;

	return 0;
}

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

static ssize_t ch368_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{
	int ret;
	struct ch368_dev *ch368 = filp->private_data;

	count = count > ch368->mem_len ? ch368->mem_len : count;
	ret = copy_to_user(buf, ch368->mem_addr, count);

	return count - ret;
}

static ssize_t ch368_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
	int ret;
	struct ch368_dev *ch368 = filp->private_data;

	count = count > ch368->mem_len ? ch368->mem_len : count;
	ret = copy_from_user(ch368->mem_addr, buf, count);

	return count - ret;
}

static long ch368_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	union addr_data ad;
	struct ch368_dev *ch368 = filp->private_data;

	if (_IOC_TYPE(cmd) != CH368_MAGIC)
		return -ENOTTY;

	if (copy_from_user(&ad, (union addr_data __user *)arg, sizeof(union addr_data)))
		return -EFAULT;

	switch (cmd) {
	case CH368_RD_CFG:
		if (ad.addr > 0x3F)
			return -ENOTTY;
		pci_read_config_byte(ch368->pdev, ad.addr, &ad.data);
		if (copy_to_user((union addr_data __user *)arg, &ad, sizeof(union addr_data)))
			return -EFAULT;
		break;
	case CH368_WR_CFG:
		if (ad.addr > 0x3F)
			return -ENOTTY;
		pci_write_config_byte(ch368->pdev, ad.addr, ad.data);
		break;
	case CH368_RD_IO:
		ad.data = ioread8(ch368->io_addr + ad.addr);
		if (copy_to_user((union addr_data __user *)arg, &ad, sizeof(union addr_data)))
			return -EFAULT;
		break;
	case CH368_WR_IO:
		iowrite8(ad.data, ch368->io_addr + ad.addr);
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations ch368_ops = {
	.owner = THIS_MODULE,
	.open = ch368_open,
	.release = ch368_release,
	.read = ch368_read,
	.write = ch368_write,
	.unlocked_ioctl = ch368_ioctl,
};

static int ch368_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	int ret;

	unsigned long io_start;
	unsigned long io_end;
	unsigned long io_flags;
	unsigned long io_len;
	void __iomem *io_addr = NULL;

	unsigned long mem_start;
	unsigned long mem_end;
	unsigned long mem_flags;
	unsigned long mem_len;
	void __iomem *mem_addr = NULL;

	struct ch368_dev *ch368;

	ret = pci_enable_device(pdev);
	if(ret)
		goto enable_err;

	io_start  = pci_resource_start(pdev, 0);
	io_end    = pci_resource_end(pdev, 0);
	io_flags  = pci_resource_flags(pdev, 0);
	io_len    = pci_resource_len(pdev, 0);

	mem_start = pci_resource_start(pdev, 1);
	mem_end   = pci_resource_end(pdev, 1);
	mem_flags = pci_resource_flags(pdev, 1);
	mem_len   = pci_resource_len(pdev, 1);

	if (!(io_flags & IORESOURCE_IO) || !(mem_flags & IORESOURCE_MEM)) {
		ret = -ENODEV;
		goto res_err;
	}

	ret = pci_request_regions(pdev, "ch368");
	if (ret)
		goto res_err;

	io_addr = ioport_map(io_start, io_len);
	if (io_addr == NULL) {
		ret = -EIO;
		goto ioport_map_err;
	}

	mem_addr = ioremap(mem_start, mem_len);
	if (mem_addr == NULL) {
		ret = -EIO;
		goto ioremap_err;
	}

	ch368 = kzalloc(sizeof(struct ch368_dev), GFP_KERNEL);
	if (!ch368) {
		ret = -ENOMEM;
		goto mem_err;
	}
	pci_set_drvdata(pdev, ch368);

	ch368->io_addr = io_addr;
	ch368->mem_addr = mem_addr;
	ch368->io_len = io_len;
	ch368->mem_len = mem_len;
	ch368->pdev = pdev;

	ch368->dev = MKDEV(CH368_MAJOR, minor++);
	ret = register_chrdev_region (ch368->dev, 1, CH368_DEV_NAME);
	if (ret < 0)
		goto region_err;

	cdev_init(&ch368->cdev, &ch368_ops);
	ch368->cdev.owner = THIS_MODULE;
	ret = cdev_add(&ch368->cdev, ch368->dev, 1); 
	if (ret)
		goto add_err;

	return 0;

add_err:
	unregister_chrdev_region(ch368->dev, 1);
region_err:
	kfree(ch368);
mem_err:
	iounmap(mem_addr);
ioremap_err:
	ioport_unmap(io_addr);
ioport_map_err:
	pci_release_regions(pdev);
res_err:
	pci_disable_device(pdev);
enable_err:
	return ret;
}

static void ch368_remove(struct pci_dev *pdev)
{
	struct ch368_dev *ch368 = pci_get_drvdata(pdev);

	cdev_del(&ch368->cdev);
	unregister_chrdev_region(ch368->dev, 1);
	iounmap(ch368->mem_addr);
	ioport_unmap(ch368->io_addr);
	kfree(ch368);
	pci_release_regions(pdev);
	pci_disable_device(pdev);
}

static struct pci_device_id ch368_id_table[] =	
{
	{0x1C00, 0x5834, 0x1C00, 0x5834, 0, 0, 0},
	{0,}
};
MODULE_DEVICE_TABLE(pci, ch368_id_table);

static struct pci_driver ch368_driver = {
	.name = "ch368",
	.id_table = ch368_id_table,
	.probe = ch368_probe,
	.remove = ch368_remove,
};

module_pci_driver(ch368_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("CH368 driver");
#ifndef _CH368_H
#define _CH368_H

union addr_data {
	unsigned char addr;
	unsigned char data;
};

#define CH368_MAGIC   'c'

#define CH368_RD_CFG	_IOWR(CH368_MAGIC, 0, union addr_data)
#define CH368_WR_CFG	_IOWR(CH368_MAGIC, 1, union addr_data)
#define CH368_RD_IO	_IOWR(CH368_MAGIC, 2, union addr_data)
#define CH368_WR_IO	_IOWR(CH368_MAGIC, 3, union addr_data)

#endif


        代码第 19 行至第 27 行是设备结构的定义,包含了保存映射之后的 IO 地址和内存地址的 io_addr 和 mem_addr 指针成员、保存 IO 地址空间大小和内存地址空间大小的io_len 和 mem_len 成员、保存 PCI 设备结构的 pdev 指针成员。该 PCI设备实现为一个字符设备,所以有 cdev 和 dev 成员。
        代码第 224 行至第 242 行是 PCI驱动结构的定义、注册和注销。ch368_id_table 是该驱动支持的设备列表,其中的 ID 号要和上图中的 ID 号一致。
        当有匹配的 PCI设备被检测到后,ch368_probe 函数自动被调用。代码第 134 行首先使能了 PCI 设备,代码第 138 行至第 146 行分别获取了 IO 和内存的物理地址、标志和长度信息。代码第 148 行至第 151 行判断了获取的标志内的资源类型信息,如果不和预期的相同,则设备探测失败。代码第 153 行至第 167 行申请了 PCI 设备所声明的资源,然后进行了映射,获得了对应的虚拟地址。代码第 169 行至第 180 行分配了 struct ch368_dev 结构的内存空间,并对各成员进行了相应的初始化,还使用 pci_set_drvdata函
 

        总线类设备驱动微将该结构地址保存在了 PCI 设备结构之中,方便之后从 PCI 设备结构中获得该地址该函数之后的代码是字符设备相关的注册操作。ch368 remove 做的工作和 h368 probe第亿函数相反。


        ch368_open 和 ch368_release 没有做太多的工作,ch368_read 和 ch368_write 则是针对内存空间的读和写,因为在这片内存空间没有对应外接的设备,所以没有实际意义。比较实际的操作是在 ch368_ioctl 中,CH368_RD_CFG 命今用来读取配置空间的数据,CH368_WR_CFG 命令用于向配置空间写入数据。CH368_RD_IO和CH368_WR_IO则分别是对 I/0 空间进行读和写。union addr_data 用于传送地址和返回数据,这和ADC 驱动的例子是类似的。
        应用层的测试代码如下
 

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

#include "ch368.h"

int main(int argc, char *argv[])
{
	int i;
	int fd;
	int ret;
	union addr_data ad;
	unsigned char id[4];

	fd = open("/dev/ch368", O_RDWR);
	if (fd == -1)
		goto fail;

	for (i = 0; i < sizeof(id); i++) {
		ad.addr = i;
		if (ioctl(fd, CH368_RD_CFG, &ad))
			goto fail;
		id[i] = ad.data;
	}

	printf("VID: 0x%02x%02x, DID: 0x%02x%02x\n", id[1], id[0], id[3], id[2]);

	i = 0;
	ad.addr = 0;
	while (1) {
		ad.data = i++;
		if (ioctl(fd, CH368_WR_IO, &ad))
			goto fail;
		i %= 15;
		sleep(1);
	}
fail:
	perror("pci test");
	exit(EXIT_FAILURE);
}


        上面的代码在打开设备后先读取了配置空间的前 4 个字节,根据 PCI 规范,这4字节刚好是厂商ID 和设备ID。接下来在 while 循环中对 I/0 空间的第一个字节依次写入了0~15,这样 PCI 设备上的 4个 LED 灯就会按照此规律被点亮。前面说过,4个LED反映了写入 I/ O空间的数据的低 4 位的状态,数据位为 1 对应的灯被点亮,数据位为0对应的灯熄灭。
        使用下面的命令进行编译和测试。需要说明的是,需要有一台安装了 Linux 系统的物理机,并且物理机上要有对应的 PCIE 插槽才能插入该设备并进行测试。


五、 总线类设备驱动开发习题


1.I2C 总线协议规定是由 ( ) 来进行应答的。
[A] 数据发送者
[B] 数据接收者


2.I2C 总线协议规定所有访问都是由 ( )来发起的
[B] 从设备
[A] 主设备


3.SPI是 ( )总线。
[B] 异步
[A] 同步


4.SPI总线是 ( )的

[B] 半双工
[A] 单工

[C] 全双功


5.SPI 总线是 ( )的。

[A] 单主
[B] 多主


6.USB 的传输类型分为 (
[B] 等时传输
[A] 控制传输
[D] 块传输
[C] 中断传输
 


7.USB 的接口是由多个 ( ) 组成的。
[A] 配置
[B] 管道

[C] 端点


8.PCI的配置空间包括 ( ) 信息。
[B] 设备ID
[A] 厂商ID
[D] 地址空间大小
[C] 基地址

 

答案:B         A         A         A         C        ABCD        C        ABCD

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

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

相关文章

初识-Servlet (第一个 Servlet 程序详解)

Servlet 是什么? Servlet 是一种实现动态页面的技术. 是一组 Tomcat 提供给程序员的 API, 帮助程序员简单高效的开发一个 web app. 静态页面就只是单纯的 html 动态页面则是 html 数据 第一个 Servlet 程序 我们写一个 hello world 预期写一个 Servlet 程序, 部署到 Tomca…

图论12-无向带权图及实现

文章目录 带权图1.1带权图的实现1.2 完整代码 带权图 1.1带权图的实现 在无向无权图的基础上&#xff0c;增加边的权。 使用TreeMap存储边的权重。 遍历输入文件&#xff0c;创建TreeMap adj存储每个节点。每个输入的adj节点链接新的TreeMap&#xff0c;存储相邻的边和权重 …

时间序列预测实战(十二)DLinear模型实现滚动长期预测并可视化预测结果

官方论文地址->官方论文地址 官方代码地址->官方代码地址 个人修改代码->个人修改的代码已经上传CSDN免费下载 一、本文介绍 本文给大家带来是DLinear模型&#xff0c;DLinear是一种用于时间序列预测&#xff08;TSF&#xff09;的简单架构&#xff0c;DLinear的核…

Leetcode刷题详解—— 目标和

1. 题目链接&#xff1a;494. 目标和 2. 题目描述&#xff1a; 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可…

【计算机网络笔记】IP分片

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

有没有实时检测微信聊天图片的软件,只要微信收到了有二维码的图片就把它提取出来?

10-2 如果你有需要自动并且快速地把微信收到的二维码图片保存到指定文件夹的需求&#xff0c;那本文章非常适合你&#xff0c;本文章教你如何实现自动保存微信收到的二维码图片到你指定的文件夹中&#xff0c;助你快速扫码&#xff0c;比别人领先一步。 首先需要准备好的材料…

19 异步通知

一、异步通知 1. 异步通知简介 阻塞和非阻塞两种方式都是需要应用程序去主动查询设备的使用情况。 异步通知类似于驱动可以主动报告自己可以访问&#xff0c;应用程序获取信号后会从驱动设备中读取或写入数据。 异步通知最核心的就是信号&#xff1a; #define SIGHUP 1 /* 终…

openssl研发之base64编解码实例

一、base64编码介绍 Base64编码是一种将二进制数据转换成ASCII字符的编码方式。它主要用于在文本协议中传输二进制数据&#xff0c;例如电子邮件的附件、XML文档、JSON数据等。 Base64编码的特点如下&#xff1a; 字符集&#xff1a; Base64编码使用64个字符来表示二进制数据…

UPLAOD-LABS2

less7 任务 拿到一个shell服务器 提示 禁止上传所有可以解析的后缀 发现所有可以解析的后缀都被禁了 查看一下源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists($UPLOAD_ADDR)) {$deny_ext array(".php",".php5&quo…

password game

目录 password game &#xff08;1-2&#xff09; &#xff08;3&#xff09; &#xff08;4&#xff09; &#xff08;5&#xff09; &#xff08;6&#xff09; &#xff08;7&#xff09; &#xff08;8&#xff09; &#xff08;9&#xff09; &#xff08;10&am…

CCF ChinaSoft 2023 论坛巡礼|机器人大模型与具身智能挑战赛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

第一百六十九回 如何修改NavigationBar的形状

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何修改按钮的形状"相关的内容&#xff0c;本章回中将介绍NavigationBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

【java进阶】集合的三种遍历(迭代器、增强for、Lambda)

目录 一、先谈集合&#xff1a; 二、单列集合的三种遍历方式 迭代器遍历 增强for遍历 Lambda表达式遍历 一、先谈集合&#xff1a; &#x1f525;那我们平常用for循环依赖下标遍历不行嘛&#xff0c;这就与集合的分类有关了。 集合的体系结构&#xff1a; collection是单…

我的前端笔记JS

js介绍 js是编程语音&#xff0c;之前学的html和css是标记语言 百度搜索mdn官网就可以 语法 输出、对话框、控制台日志、输入对话框 字面量 简单理解就是看到的内容是属于什么类型&#xff0c;例如1232&#xff0c;这个是属于数字字面量

【C++】this指针讲解超详细!!!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

如何用 GPTs 构建自己的知识分身?(进阶篇)

&#xff08;注&#xff1a;本文为小报童精选文章&#xff0c;已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 有了这些改进&#xff0c;你可以快速判断 GPT 助手给出的答案是真实还是「幻觉」了。 问题 在《如何用自然语言 5 分钟构建个人知识库应用&am…

【Git】Git使用Gui图形化界面,Git中SSH协议,Idea集成Git

一&#xff0c;Git使用Gui图形化界面 1.1 Gui的简介 Gui &#xff08;Graphical User Interface&#xff09;指的是图形用户界面&#xff0c;也就是指使用图形化方式来协同人和计算机进行交互的一类程序。它与传统的命令行界面相比&#xff0c;更加直观、易用&#xff0c;用户…

MATLAB的编程与应用,匿名函数、嵌套函数、蒙特卡洛法的掌握与使用

目录 1.匿名函数 1.1.匿名函数的定义与分类 1.2.匿名函数在积分和优化中应用 2.嵌套函数 2.1.嵌套函数的定义与分类 2.2.嵌套函数彼此调用关系 2.3.嵌套函数在积分和微分中应用 3.微分和积分 4.蒙特卡洛法 4.1.圆周率的模拟 4.2.计算N重积分&#xff08;均匀分布&am…

Python与ArcGIS系列(一)ArcGIS中使用Python

目录 0 简述1 arcgis中的python窗口2 开始编写代码 0 简述 按照惯例&#xff0c;作为本系列专栏的第一篇&#xff0c;先简单地介绍下本系列文章的内容&#xff1a;通过python语言创建arcgis环境脚本、将脚本以工具箱形式存放在arcgis中、通过脚本自动执行地理处理、数据修复、…

Benchmarking Large Language Models in Retrieval-Augmented Generation-学习翻译

提检索增强生成中大型语言模型的基准测试文献学习 作者将在https://github.com/chen700564/RGB上发布本文的代码和RGB。 y ˇ \check{y} yˇ​ 文章目录 摘要IntroductionRelated workRetrieval-Augmented Generation BenchmarkRAG所需能力数据构建评估指标 ExperimentsSetting…