21.SPI万能驱动

news2024/11/28 3:32:35

spi万能驱动:spidev.c文件

存放在内核/drivers/spi/spidev.c文件。

内核开放的通用版本的spi驱动。

内核集成spidev驱动模块,开机后会中的加载此模块。

支持修改多种spi通信参数。

两个数据类型

spidev_data结构体

fops中的函数结构都要用到,会被赋值给file->private_data。

struct spidev_data {
	// 设备号
	dev_t			devt;
	spinlock_t		spi_lock;
	struct spi_device	*spi;
	struct list_head	device_entry;
	struct mutex		buf_lock;
	unsigned		users;
	// 发送buf,接收buf,通信频率
	u8			*tx_buffer;
	u8			*rx_buffer;
	u32			speed_hz;
};

spi_ioc_transfer结构体

可用来设置spi的通信参数,但很少用,用户空间编程也会用到此结构体。

struct spi_ioc_transfer {
	__u64		tx_buf;    // spi数据发送缓存区
	__u64		rx_buf;    // spi数据接收缓存区
	__u32		len;       // 收发数据长度
	__u32		speed_hz;

	__u16		delay_usecs;
	__u8		bits_per_word;
	__u8		cs_change;
	__u8		tx_nbits;
	__u8		rx_nbits;
	__u16		pad;
};

设备子节点

pinctrl子节点

	pinctrl_ecspi3:ecspi3grp {
					// 此属性来记录一个引脚组
					fsl,pins = <
						MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0         0x1a090
						MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK		0x11090
						MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI			0x11090
						MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO			0x11090
					>;
	};

spidev子节点

&ecspi3{
	pinctrl-names = "default";
	// default表示使用pinctrl-0引脚组
	pinctrl-0 = <&pinctrl_ecspi3>;
	status = "okay";
	#address-cells = <1>;
	#size-cells = <0>; 
	// 追加一个设备节点
	// 此节点挂载在spi节点下,会被内核解析成一个spi_device设备,挂在对应的spi总线上
	spidev@0 {
		// 用来匹配对应的驱动,pdidev.c
		compatible = "spidev";
		spi-max-frequency = <20000000>;
		reg = <0>;
	};
};

spidev_init()函数

static int __init spidev_init(void)
{
	int status;
	...
	// 申请设备号,参数1主设备号为153
	// 这一步将主设备号153机器所有的次设备号都占用了
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	...
	// 创建spidev设备类,新增/sys/class/spidev
	spidev_class = class_create(THIS_MODULE, "spidev");
	...
	// 向内核注册一个spi设备驱动
	status = spi_register_driver(&spidev_spi_driver);
	...
	return status;
}

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	.write =	spidev_write,
	.read =		spidev_read,
	.unlocked_ioctl = spidev_ioctl,        // 应用层 ioctl()函数底层操作接口(32位系统)
	.compat_ioctl = spidev_compat_ioctl,   // 应用层 ioctl()函数底层操作接口(64位系统)
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	no_llseek,
};

read和write接口只能半双工收发消息

spi支持全双工,可使用unlocked_ioctl接口可以支持半双工、全双工(switch的default选择)收发消息

spidev_read()函数

static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;

	/* chipselect only toggles at start or end of operation */
	// 先判断用户空间想要读取的字节数
	if (count > bufsiz)
		return -EMSGSIZE;
	// 通过文件指针获取struct spidev_data,在fops->open中完成赋值
	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	// 详见下
	status = spidev_sync_read(spidev, count);
	if (status > 0) {
		unsigned long	missing;

		missing = copy_to_user(buf, spidev->rx_buffer, status);
		if (missing == status)
			status = -EFAULT;
		else
			status = status - missing;
	}
	mutex_unlock(&spidev->buf_lock);

	return status;
}

spidev_ioctl()函数

32bit系统对应fops中的unlocked_ioctl接口,即spidev_ioctl。

#define SPI_MODE_MASK (SPI_CPHA|SPI_CPOL|SPI_CS_HIGH|SPI_LSB_FIRST|SPI_3WIRE|SPI_LOOP|SPI_NO_CS|SPI_READY)  

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			retval = 0;
	struct spidev_data	*spidev;
	struct spi_device	*spi;
	u32			tmp;
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;
	...
	spidev = filp->private_data;
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);
	spin_unlock_irq(&spidev->spi_lock);
	...
	// 由应用层函数ioctl的参数2传递进来,具体的控制命令
	// 控制命令可以分为两类:读和写
	switch (cmd) {
	/* read requests */
	case SPI_IOC_RD_MODE://put_user:复制到用户空间,此处复制spi控制器的模式
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MODE32:
		retval = put_user(spi->mode & SPI_MODE_MASK,
					(__u32 __user *)arg);
		break;
	case SPI_IOC_RD_LSB_FIRST:
		retval = put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
					(__u8 __user *)arg);
		break;
	case SPI_IOC_RD_BITS_PER_WORD:
		retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
		break;
	case SPI_IOC_RD_MAX_SPEED_HZ:
		retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
		break;

	/* write requests */
	case SPI_IOC_WR_MODE:
	case SPI_IOC_WR_MODE32:
		if (cmd == SPI_IOC_WR_MODE)
			retval = get_user(tmp, (u8 __user *)arg);
		else
			retval = get_user(tmp, (u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp & ~SPI_MODE_MASK) {
				retval = -EINVAL;
				break;
			}

			tmp |= spi->mode & ~SPI_MODE_MASK;
			// 根据写入的内容去设置mode
			spi->mode = (u16)tmp;
			// 重新设置spi设备的相关属性
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
		}
		break;
	case SPI_IOC_WR_LSB_FIRST:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u32	save = spi->mode;

			if (tmp)
				spi->mode |= SPI_LSB_FIRST;
			else
				spi->mode &= ~SPI_LSB_FIRST;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->mode = save;
			else
				dev_dbg(&spi->dev, "%csb first\n",
						tmp ? 'l' : 'm');
		}
		break;
	case SPI_IOC_WR_BITS_PER_WORD:
		retval = get_user(tmp, (__u8 __user *)arg);
		if (retval == 0) {
			u8	save = spi->bits_per_word;

			spi->bits_per_word = tmp;
			retval = spi_setup(spi);
			if (retval < 0)
				spi->bits_per_word = save;
			else
				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
		}
		break;
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32	save = spi->max_speed_hz;

			spi->max_speed_hz = tmp;
			retval = spi_setup(spi);
			if (retval >= 0)
				spidev->speed_hz = tmp;
			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
			spi->max_speed_hz = save;
		}
		break;

	default://发送特殊的数据,很少使用
		/* segmented and/or full-duplex I/O request */
		/* Check message and copy into scratch area */
		// 获取用户空间传入的struct spi_ioc_transfer
		// 参数2可能是个数组,参数3表示元素的个数
		ioc = spidev_get_ioc_message(cmd,
				(struct spi_ioc_transfer __user *)arg, &n_ioc);
		if (IS_ERR(ioc)) {
			retval = PTR_ERR(ioc);
			break;
		}
		if (!ioc)
			break;	/* n_ioc is also 0 */

		/* translate to spi_message, execute */
		// 此函数实现和spidev_read差不多,详见下
		retval = spidev_message(spidev, ioc, n_ioc);
		kfree(ioc);
		break;
	}

	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);
	return retval;
}

spidev_message()函数

全双工收发信息,本质上还是调用一些spi核心层的函数。

static int spidev_message(struct spidev_data *spidev,
		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
	struct spi_message	msg;
	struct spi_transfer	*k_xfers;
	struct spi_transfer	*k_tmp;
	struct spi_ioc_transfer *u_tmp;
	unsigned		n, total, tx_total, rx_total;
	u8			*tx_buf, *rx_buf;
	int			status = -EFAULT;
	// 初始化struct spi_message
	spi_message_init(&msg);
	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
	if (k_xfers == NULL)
		return -ENOMEM;

	tx_buf = spidev->tx_buffer;
	rx_buf = spidev->rx_buffer;
	total = 0;
	tx_total = 0;
	rx_total = 0;
	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
			n;
			n--, k_tmp++, u_tmp++) {
		k_tmp->len = u_tmp->len;

		total += k_tmp->len;
		
		if (total > INT_MAX || k_tmp->len > INT_MAX) {
			status = -EMSGSIZE;
			goto done;
		}

		if (u_tmp->rx_buf) {
			/* this transfer needs space in RX bounce buffer */
			rx_total += k_tmp->len;
			if (rx_total > bufsiz) {
				status = -EMSGSIZE;
				goto done;
			}
			k_tmp->rx_buf = rx_buf;
			rx_buf += k_tmp->len;
		}
		if (u_tmp->tx_buf) {
			/* this transfer needs space in TX bounce buffer */
			tx_total += k_tmp->len;
			if (tx_total > bufsiz) {
				status = -EMSGSIZE;
				goto done;
			}
			k_tmp->tx_buf = tx_buf;
			// 全双工之收,读取用户空间的数据
			if (copy_from_user(tx_buf, (const u8 __user *)
						(uintptr_t) u_tmp->tx_buf,
					u_tmp->len))
				goto done;
			tx_buf += k_tmp->len;
		}

		k_tmp->cs_change = !!u_tmp->cs_change;
		k_tmp->tx_nbits = u_tmp->tx_nbits;
		k_tmp->rx_nbits = u_tmp->rx_nbits;
		k_tmp->bits_per_word = u_tmp->bits_per_word;
		k_tmp->delay_usecs = u_tmp->delay_usecs;
		k_tmp->speed_hz = u_tmp->speed_hz;
		if (!k_tmp->speed_hz)
			k_tmp->speed_hz = spidev->speed_hz;
#ifdef VERBOSE
		dev_dbg(&spidev->spi->dev,
			"  xfer len %u %s%s%s%dbits %u usec %uHz\n",
			u_tmp->len,
			u_tmp->rx_buf ? "rx " : "",
			u_tmp->tx_buf ? "tx " : "",
			u_tmp->cs_change ? "cs " : "",
			u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
			u_tmp->delay_usecs,
			u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
#endif
		spi_message_add_tail(k_tmp, &msg);
	}
	// 同步发送
	status = spidev_sync(spidev, &msg);
	if (status < 0)
		goto done;

	/* copy any rx data out of bounce buffer */
	rx_buf = spidev->rx_buffer;
	for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
		if (u_tmp->rx_buf) {
			// 全双工之发,发回用户空间的数据
			if (copy_to_user((u8 __user *)
					(uintptr_t) u_tmp->rx_buf, rx_buf,
					u_tmp->len)) {
				status = -EFAULT;
				goto done;
			}
			rx_buf += u_tmp->len;
		}
	}
	status = total;

done:
	kfree(k_xfers);
	return status;
}

spidev_get_ioc_message()函数

static struct spi_ioc_transfer *
spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc,
		unsigned *n_ioc)
{
	u32	tmp;
	// 帕努单命令的合法性
	/* Check type, command number and direction */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
			|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
			|| _IOC_DIR(cmd) != _IOC_WRITE)
		return ERR_PTR(-ENOTTY);
	// 此处约定cmd表示struct spi_ioc_transfer数组总的大小
	tmp = _IOC_SIZE(cmd);
	if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
		return ERR_PTR(-EINVAL);
	// struct spi_ioc_transfer数组元素个数
	*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
	if (*n_ioc == 0)
		return NULL;

	/* copy into scratch area */
	// 申请内存,复制结构体,返回回去
	return memdup_user(u_ioc, tmp);
}

应用层对应的ioctl()函数

应用层函数,用来控制文件。

#include <sys/ioctl.h>

/*
 * fd:要控制的文件描述符
 * request:控制命令
 */
int ioctl(int fd, int request, ...);
/*
 * 返回值:
 *    成功:
 *    失败:-1
 */

spidev_open()函数

为tx_buffer、rx_buffer分配4096字节内存。

static int spidev_open(struct inode *inode, struct file *filp)
{
	struct spidev_data	*spidev;
	int			status = -ENXIO;

	mutex_lock(&device_list_lock);
	// 之前在spidev_probe中将spidev加入到device_list中保存
	list_for_each_entry(spidev, &device_list, device_entry) {
		// 通过设备号来匹配
		if (spidev->devt == inode->i_rdev) {
			status = 0;
			break;
		}
	}

	if (status) {
		pr_debug("spidev: nothing for minor %d\n", iminor(inode));
		goto err_find_dev;
	}

	if (!spidev->tx_buffer) {
		// 为空则分配内存,参数1为4096
		spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->tx_buffer) {
			dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_find_dev;
		}
	}

	if (!spidev->rx_buffer) {
		// 为空则分配内存,参数1为4096
		spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
		if (!spidev->rx_buffer) {
			dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_alloc_rx_buf;
		}
	}

	spidev->users++;
	// spidev 记录在文件指针里面
	filp->private_data = spidev;
	// 让当前文件不支持lseek函数
	nonseekable_open(inode, filp);

	mutex_unlock(&device_list_lock);
	return 0;

err_alloc_rx_buf:
	kfree(spidev->tx_buffer);
	spidev->tx_buffer = NULL;
err_find_dev:
	mutex_unlock(&device_list_lock);
	return status;
}

spidev_probe()函数

主要内容:

创建字符设备

次设备号按位图分配

设备文件名后缀数字的含义

spi控制器编号

spi设备片选信号编号

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;
	...
	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	// 分配一个次设备号,从256个次设备号中提取1个
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;
		// 此成员记录设备号
		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		// 参数4表示spi控制器的编号,参数5表示spi设备使用哪个片选信号
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		// 链接
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

SPI实验环节

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

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

相关文章

C/C++ CMake编译工具轻度使用

目录 CMake编译工具 一、CMake概述 二、CMake的使用 2.1 注释 2.1.1 注释行 2.1.2 注释块 2.2 源文件 2.1.1 共处一室 2.1.2 VIP包房 2.3 私人定制 2.2.1 定义变量 2.2.2 指定使用的C标准 2.2.3 指定输出的路径 2.4 搜索文件 2.3.1 方式1 2.3.2 方式2 2.5 包含…

WebGL射击游戏的优化

myshmup.com 允许在浏览器中创建 shmup(射击)游戏,我们可以使用具有创意通用许可证的资源或上传自己的艺术作品和声音。 创建的游戏可以在网站上发布。 该平台不需要编码,游戏对象的配置是在用户界面的帮助下执行的,后端是使用Django框架开发的,编辑器 UI 用 Javascript …

Spring项目配置

1.创建项目 2.修改编码格式为UTF-8 3.检查或配置代码⾃动补全功能 4.检查或配置⾃动导包 5.检查或配置Maven&#xff0c;可以⽤国内仓库镜像 6.idea识别Maven项⽬&#xff1a;在Notifications视图中会提⽰Load Maven Project&#xff0c;点击即可。 7.开启热部署 a.确认 pom.x…

vue制作页面水印

1.新建一个js js的代码 let watermark {}let setWatermark (str) > {let id 1.23452384164.123412415if (document.getElementById(id) ! null) {document.body.removeChild(document.getElementById(id))}let can document.createElement(canvas)can.width 500can.he…

一文彻底吃透自动化测试框架所有知识

定义测试自动化 在任何行业中&#xff0c;自动化通常被解释为自动处理流程&#xff0c;而这些流程几乎不需要人工干预。在软件行业&#xff0c;测试自动化意味着使用许可版本或开源的自动化工具对软件应用程序执行各种测试。用技术术语来说&#xff0c;测试自动化框架是一组定制…

css自学框架之图片灯箱展示

实现的功能是页面中的图片单击&#xff0c;在灯箱中显示&#xff0c;单击按钮上下切换&#xff0c;单击灯箱退出展示&#xff0c;效果如下GIF展示。 实现步骤还是老样子&#xff0c;三方面工作一是CSS、二是JavaSxcript&#xff0c;三是HTML&#xff0c;下面开始一步一步实现&…

助眠小程序源码系统搭建

一直比较热门的项目&#xff0c;这款系统支持流量主和会员体系的。 用抖音伴侣直播也不错&#xff0c;然后间接引流到自己的小程序&#xff0c;后期还可以卖项目等&#xff0c; 也适合拿来做流量矩阵小程序。也可以用来做其他的声音音乐类的项目 大致功能: 支持流量主支持会员…

运维Shell脚本小试牛刀(五):until循环|循环控制break|continue

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 运维Shell脚本小试…

Photoscan/Metashape 2.0.0中的地面激光扫描处理

在Metashape(原Photoscan)2.0.0, 结构化地面激光扫描和非结构化航空激光扫描都可以使用导入点云&#xff08;文件>导入>导入点云&#xff09;命令导入。导入时会保留所有点属性&#xff08;包括结构化信息&#xff09;。 本文讨论以下主题 如何将激光扫描数据导入项目&am…

高忆管理:六连板!我乐家居累计涨超77%,公司:存在估值较高风险

9月4日&#xff0c;家具板块继续活泼&#xff0c;同花顺家具板块涨幅达5.46%&#xff0c;顶固集创&#xff08;300749.SZ&#xff09;20CM涨停&#xff0c;美之高(834765)涨超12%&#xff0c;帝欧家居&#xff08;002798.SZ&#xff09;、亚振家居&#xff08;603389.SH&#x…

day04_基本数据类型丶变量丶基本数据类型转换

前置知识 计算机世界中只有二进制。那么在计算机中存储和运算的所有数据都要转为二进制。包括数字、字符、图片、声音、视频等。 进制 进制也就是进位计数制&#xff0c;是人为定义的带进位的计数方法 。不同的进制可以按照一定的规则进行转换。 进制的分类 十进制&#x…

sqlserver数据库链接mysql服务器访问数据

sqlserver数据库链接mysql服务器访问数据 关于SqlServer数据库怎么链接mysql数据库我一直不明白&#xff0c;今天项目碰到一个问题需要链接&#xff0c;我就研究了一下&#xff0c;然后就成功了&#xff0c;在这里记录一下。也欢迎朋友互相学习交流借鉴。 1.使用navicat打开S…

推荐6款普通人搞副业做自媒体AI工具

hi&#xff0c;同学们&#xff0c;我是赤辰&#xff0c;本期是赤辰第5篇AI工具类教程&#xff0c;文章底部准备了粉丝福利&#xff0c;看完可以领取&#xff01;身边越来越多的小伙伴靠自媒体实现财富自由了&#xff01;因此&#xff0c;推荐大家在工作之余或空闲时间从事自媒体…

windows查看端口占用,通过端口找进程号(查找进程号),通过进程号定位应用名(查找应用)(netstat、tasklist)

文章目录 通过端口号查看进程号netstat通过进程号定位应用程序tasklist 通过端口号查看进程号netstat 在Windows系统中&#xff0c;可以使用 netstat 命令来查看端口的占用情况。以下是具体的步骤&#xff1a; 打开命令提示符&#xff08;CMD&#xff09;&#xff1a;按WinR组…

听厂家聊聊:劳保鞋何时该报废?

在现代工业社会里&#xff0c;劳保鞋作为一种较为常见的劳保用品&#xff0c;被广泛用于各行各业。劳保鞋&#xff0c;也称安全鞋&#xff0c;是保护使用者脚部免受意外事故引起的伤害&#xff0c;可以对足部起到一定的防护作用。不管是防砸还是防静电&#xff0c;甚至是耐高温…

进程、线程与构造方法

进程、线程与构造方法 目录 一&#xff0e; 进程与线程1. 通俗解释2. 代码实现3. 线程生命周期&#xff08;图解&#xff09; 二&#xff0e; 构造方法 一&#xff0e; 进程与线程 1. 通俗解释 进程&#xff1a;就像电脑上运行的软件&#xff0c;例如QQ等。 线程&#xff1a;…

robot framework入门案例

Robot Framework是一个完全基于关键字测试驱动的框架&#xff1b; 关键字可以理解为一个能实现特定功能的对象&#xff08;如ssh连接、登录、新增配置等&#xff09; 有了关键字后&#xff0c;就可以通过关键字组合成案例&#xff1b; 所以入门案例只需要两个文件 &#xf…

AcWing 788. 逆序对的数量(归并排序)

基本思想 归并排序是用分治思想&#xff0c;分治模式在每一层上有三个步骤&#xff1a; &#xff08;1&#xff09;分解&#xff1a;将n个元素分解成n/2个元素的子序列。 &#xff08;2&#xff09;解决&#xff1a;用合并排序法对两个子序列递归排序。 &#xff08;3&…

护眼灯的色温是多少比较好?如何选择护眼台灯

色温是台灯的一个重要指标&#xff0c;它可以表示光线中包含颜色的成分&#xff0c;从理论上简单来讲&#xff0c;色温从低到高对应着光线从黑到红&#xff0c;再到黄、白&#xff0c;最后到蓝色光。色温也可以对应大众所熟悉的色调&#xff0c;色温越高&#xff0c;光线偏白色…

开学哪种电容笔好用?推荐的ipad手写笔

如果你希望通过iPad进行绘画&#xff0c;那么Apple Pencil就很重要了。不过&#xff0c;苹果原装电容笔的售价实在是太高了&#xff0c;许多人无法承受。因此&#xff0c;最佳方法是选择一款平替电容笔。我以前一直用iPad平板&#xff0c;也是个数码爱好者&#xff0c;这两年我…