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

目录

一、 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2560 动物保护宣传网站设计JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个动物保护宣传网站的系统的设计与实现。该系统包括前台用户模块和后台管理员模块&#xff0c;具有用户注册/登录、新闻、资源库、法律法规、图片赏析、留言板、关于我们、用户后台等功能。通过数据库设计和界面设计&#xff0c;实现了系统的基本功能&#x…

腾讯云3年期轻量应用服务器优惠(薅羊毛教程)

腾讯云轻量应用服务器特价是有新用户限制的&#xff0c;所以阿腾云建议大家选择3年期轻量应用服务器&#xff0c;一劳永逸&#xff0c;免去续费困扰。腾讯云轻量应用服务器3年优惠可以选择2核2G4M和2核4G5M带宽&#xff0c;3年轻量2核2G4M服务器540元&#xff0c;2核4G5M轻量应…

SW如何显示样条曲线的控标

刚刚学习隔壁老王的sw画图时&#xff0c;怎么点都点不出样条曲线的控标&#xff0c;于是果断查询了一下解决方法&#xff0c;其实很简单&#xff0c;只不过是培训机构故意不说&#xff0c;叫你还解决不了&#xff0c;难受了就会花钱买他们的课了。毕竟如果学会了怎么解决问题了…

泛微E-Office信息泄露漏洞复现

简介 Weaver E-Office是中国泛微科技&#xff08;Weaver&#xff09;公司的一个协同办公系统。 Weaver E-Office 9.5版本存在安全漏洞。攻击者利用该漏洞可以访问文件或目录。 漏洞编号&#xff1a;CVE-2023-2766 漏洞复现 FOFA语法&#xff1a; app"泛微-EOffice&qu…

快速走进通信世界 --- 基础知识扫盲

想不到吧&#xff0c;家人们&#xff0c;博主好久没来更新文章了&#xff0c;而且这次更新的是关于通信工程的文章。博主确实以前一直更新关于编程的文章&#xff0c;只不过最近在学习一些新的知识&#xff0c;以后有机会了我还是会继续更新一些编程技术文章的。不过每一门技术…

智慧工地源码:助力数字建造、智慧建造、安全建造、绿色建造

智慧工地围绕建设过程管理&#xff0c;建设项目与智能生产、科学管理建设项目信息生态系统集成在一起&#xff0c;该数据在虚拟现实环境中&#xff0c;将物联网收集的工程信息用于数据挖掘和分析&#xff0c;提供过程趋势预测和专家计划&#xff0c;实现工程建设的智能化管理&a…

在 Microsoft Word 中启用护眼模式

在 Microsoft Word 中启用护眼模式 在使用 Microsoft Word 365 或 Word 2019&#xff08;Windows&#xff09;版本时&#xff0c;启用护眼模式&#xff08;也称为“夜间模式”&#xff09;可以有效减轻屏幕亮度&#xff0c;有助于减少眼睛疲劳。以下是启用护眼模式的步骤&…

YOLOv5算法进阶改进(1)— 改进数据增强方式 + 添加CBAM注意力机制

前言:Hello大家好,我是小哥谈。本节课设计了一种基于改进YOLOv5的目标检测算法。首先在数据增强方面使用Mosaic-9方法来对训练集进行数据增强,使得网络具有更好的泛化能力,从而更好适用于应用场景。而后,为了更进一步提升检测精度,在backbone中嵌入了CBAM注意力机制模块,…

[100天算法】-面试题 04.01.节点间通路(day 72)

题目描述 节点间通路。给定有向图&#xff0c;设计一个算法&#xff0c;找出两个节点之间是否存在一条路径。示例1:输入&#xff1a;n 3, graph [[0, 1], [0, 2], [1, 2], [1, 2]], start 0, target 2 输出&#xff1a;true 示例2:输入&#xff1a;n 5, graph [[0, 1], …

Python异常处理:三种不同方法的探索与最佳实践

Python异常处理&#xff1a;三种不同方法的探索与最佳实践 前言 本文旨在探讨Python中三种不同的异常处理方法。通过深入理解各种异常处理策略&#xff0c;我们可以更好地应对不同的编程场景&#xff0c;选择最适合自己需求的方法。 异常处理在编程中扮演着至关重要的角色。合…

Springboot集成JWT,用户名,密码生成token

何为token&#xff1f;【如果想直接看代码可以往下翻】 使用基于 Token 的身份验证方法&#xff0c;在服务端不需要存储用户的登录记录。大概的流程是这样的&#xff1a; 1. 客户端使用用户名跟密码请求登录 2. 服务端收到请求&#xff0c;去验证用户名与密码 3. 验证成功后&a…

IOC - Google Guice

Google Guice是一个轻量级的依赖注入框架&#xff0c;专注于依赖注入和IoC&#xff0c;适用于中小型应用。 Spring Framework是一个全面的企业级框架&#xff0c;提供了广泛的功能&#xff0c;适用于大型企业应用。 是吧&#xff01;IOC 容器不止Spring,还有Google Guice,来体…

Linux的make和Makefile

目录 一、 介绍二、快速使用三、依赖关系和依赖方法四、语法 一、 介绍 1、makefile带来的好处就是——“自动化编译”&#xff0c;一旦写好&#xff0c;只需要一个make命令&#xff0c;整个工程完全自动编译&#xff0c;极大的提高了软件开发的效率。 2、make是一个命令工具&…

一文了解游戏行业(数据分析)

一.概况 1.基本术语 游戏行业基础术语——持续更新ing... 2.产业链 包括游戏开发&#xff0c;发行和销售等环节 游戏开发&#xff1a;上游环节&#xff1b;是游戏产业链的核心环节&#xff0c;包括游戏策划&#xff0c;美术设计&#xff0c;程序开发等&#xff0c;是决定游…

redux-devtools谷歌扩展插件的使用示例

目录 1. store.ts 2. reducer.ts 3. ReduxProvider.tsx 4. mapStateToProps.ts 5. mapDispatchToProps.ts 6. Todo组件(最外层包ReduxProvider 7. Todo组件里面涉及的子组件 1) TodoInput.tsx 2) TodoList.tsx 3) TodoItem.tsx 8. App组件使用Todo组件 1. store.ts …

组件的设计原则

目录 插槽的基本概念 基础用法 具名插槽 使用场景 布局控制 嵌套组件 组件的灵活性 高级用法 作用域插槽 总结 前言 Vue 的 slot 是一项强大的特性&#xff0c;用于组件化开发中。它允许父组件向子组件传递内容&#xff0c;使得组件更加灵活和可复用。通过 slot&…

【LeetCode】挑战100天 Day09(热题+面试经典150题)

【LeetCode】挑战100天 Day09&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-112.1 题目2.2 题解 三、面试经典 150 题-113.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

索尼RSV文件怎么恢复为MP4视频

索尼相机RSV是什么文件&#xff1f; 如果您的相机是索尼SONY A7S3&#xff0c;A7M4&#xff0c;FX3&#xff0c;FX3&#xff0c;FX6&#xff0c;或FX9等&#xff0c;有时录像会产生一个RSV文件&#xff0c;而没有MP4视频文件。RSV其实是MP4的前期文件&#xff0c;经我对RSV文件…

进程线程

从Android3.0开始&#xff0c;系统要求网络访问必须在子线程中进行&#xff0c;否则会抛出异常&#xff0c;这么做是为了避免主线程被阻塞而导致ANR&#xff0c;那么网络访问的操作就必须要放到线程中去执行。 进程 进程是操作系统结构的基础&#xff0c;是程序在一个数据集合…

Day27力扣打卡

打卡记录 情侣牵手&#xff08;并查集&#xff09; 链接 class Solution:def minSwapsCouples(self, row: List[int]) -> int:def find(x: int) -> int:if p[x] ! x:p[x] find(p[x])return p[x]n len(row) >> 1p list(range(n))for i in range(0, len(row), 2…