imx6ull-驱动开发篇29——Linux阻塞IO 实验

目录

实验程序编写

blockio.c

blockioApp.c

Makefile 文件

运行测试


在之前的文章里,Linux阻塞和非阻塞 IO(上),我们学习了Linux应用程序了两种操作方式:阻塞和非阻塞 IO。

在Linux 中断实验中,Linux 中断实验,我们直接在应用程序中通过 read 函数不断的读取按键状态,当按键有效的时候就打印出按键值。缺点就是:imx6uirqApp 这个测试应用程序拥有很高的 CPU 占用率。

本节实验,我们使用阻塞 IO 的方式,实现同样的功能,但大大降低CPU的占用率。

实验程序编写

在中断实验代码的基础上修改,主要是对其添加阻塞访问相关的代码。

blockio.c

代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"blockio"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*//* 中断IO描述结构体 */
struct irq_keydesc {int gpio;								/* gpio */int irqnum;								/* 中断号     */unsigned char value;					/* 按键对应的键值 */char name[10];							/* 名字 */irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};/* imx6uirq设备结构体 */
struct imx6uirq_dev{dev_t devid;			/* 设备号 	 */	struct cdev cdev;		/* cdev 	*/                 struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */	atomic_t keyvalue;		/* 有效的按键键值 */atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */struct timer_list timer;/* 定义一个定时器*/struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键init述数组 */unsigned char curkeynum;				/* 当前init按键号 */wait_queue_head_t r_wait;	/* 读等待队列头 */
};struct imx6uirq_dev imx6uirq;	/* irq设备 *//* @description		: 中断服务函数,开启定时器		*				  	  定时器用于按键消抖。* @param - irq 	: 中断号 * @param - dev_id	: 设备结构。* @return 			: 中断执行结果*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;dev->curkeynum = 0;dev->timer.data = (volatile long)dev_id;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */return IRQ_RETVAL(IRQ_HANDLED);
}/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后*				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。* @param - arg	: 设备结构变量* @return 		: 无*/
void timer_function(unsigned long arg)
{unsigned char value;unsigned char num;struct irq_keydesc *keydesc;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;num = dev->curkeynum;keydesc = &dev->irqkeydesc[num];value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */if(value == 0){ 						/* 按下按键 */atomic_set(&dev->keyvalue, keydesc->value);}else{ 									/* 按键松开 */atomic_set(&dev->keyvalue, 0x80 | keydesc->value);atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */}               /* 唤醒进程 */if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 *//* wake_up(&dev->r_wait); */wake_up_interruptible(&dev->r_wait);}
}/** @description	: 按键IO初始化* @param 		: 无* @return 		: 无*/
static int keyio_init(void)
{unsigned char i = 0;char name[10];int ret = 0;imx6uirq.nd = of_find_node_by_path("/key");if (imx6uirq.nd== NULL){printk("key node not find!\r\n");return -EINVAL;} /* 提取GPIO */for (i = 0; i < KEY_NUM; i++) {imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);if (imx6uirq.irqkeydesc[i].gpio < 0) {printk("can't get key%d\r\n", i);}}/* 初始化key所使用的IO,并且设置成中断模式 */for (i = 0; i < KEY_NUM; i++) {memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */gpio_request(imx6uirq.irqkeydesc[i].gpio, name);gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif}/* 申请中断 */imx6uirq.irqkeydesc[0].handler = key0_handler;imx6uirq.irqkeydesc[0].value = KEY0VALUE;for (i = 0; i < KEY_NUM; i++) {ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);if(ret < 0){printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);return -EFAULT;}}/* 创建定时器 */init_timer(&imx6uirq.timer);imx6uirq.timer.function = timer_function;/* 初始化等待队列头 */init_waitqueue_head(&imx6uirq.r_wait);return 0;
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{filp->private_data = &imx6uirq;	/* 设置私有数据 */return 0;
}/** @description     : 从设备读取数据 * @param - filp    : 要打开的设备文件(文件描述符)* @param - buf     : 返回给用户空间的数据缓冲区* @param - cnt     : 要读取的数据长度* @param - offt    : 相对于文件首地址的偏移* @return          : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret = 0;unsigned char keyvalue = 0;unsigned char releasekey = 0;struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;#if 0/* 加入等待队列,等待被唤醒,也就是有按键按下 */ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret) {goto wait_error;} 
#endifDECLARE_WAITQUEUE(wait, current);	/* 定义一个等待队列 */if(atomic_read(&dev->releasekey) == 0) {	/* 没有按键按下 */add_wait_queue(&dev->r_wait, &wait);	/* 将等待队列添加到等待队列头 */__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */schedule();							/* 进行一次任务切换 */if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */ret = -ERESTARTSYS;goto wait_error;}__set_current_state(TASK_RUNNING);      /* 将当前任务设置为运行状态 */remove_wait_queue(&dev->r_wait, &wait);    /* 将对应的队列项从等待队列头删除 */}keyvalue = atomic_read(&dev->keyvalue);releasekey = atomic_read(&dev->releasekey);if (releasekey) { /* 有按键按下 */	if (keyvalue & 0x80) {keyvalue &= ~0x80;ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));} else {goto data_error;}atomic_set(&dev->releasekey, 0);/* 按下标志清零 */} else {goto data_error;}return 0;wait_error:set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */remove_wait_queue(&dev->r_wait, &wait);	/* 将等待队列移除 */return ret;data_error:return -EINVAL;
}/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {.owner = THIS_MODULE,.open = imx6uirq_open,.read = imx6uirq_read,
};/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init imx6uirq_init(void)
{/* 1、构建设备号 */if (imx6uirq.major) {imx6uirq.devid = MKDEV(imx6uirq.major, 0);register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);} else {alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);imx6uirq.major = MAJOR(imx6uirq.devid);imx6uirq.minor = MINOR(imx6uirq.devid);}/* 2、注册字符设备 */cdev_init(&imx6uirq.cdev, &imx6uirq_fops);cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);/* 3、创建类 */imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.class)) {	return PTR_ERR(imx6uirq.class);}/* 4、创建设备 */imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);if (IS_ERR(imx6uirq.device)) {return PTR_ERR(imx6uirq.device);}/* 5、始化按键 */atomic_set(&imx6uirq.keyvalue, INVAKEY);atomic_set(&imx6uirq.releasekey, 0);keyio_init();return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit imx6uirq_exit(void)
{unsigned i = 0;/* 删除定时器 */del_timer_sync(&imx6uirq.timer);	/* 删除定时器 *//* 释放中断 */	for (i = 0; i < KEY_NUM; i++) {free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);gpio_free(imx6uirq.irqkeydesc[i].gpio);}cdev_del(&imx6uirq.cdev);unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);device_destroy(imx6uirq.class, imx6uirq.devid);class_destroy(imx6uirq.class);
}module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");

关键代码分析如下:

设备文件名字为“blockio”,当驱动程序加载成功以后就会在根文件系统中出现一个名为“/dev/blockio”的文件。

#define IMX6UIRQ_NAME "blockio" /* 名字 */

imx6uirq 设备结构体中,添加一个等待队列头 r_wait,因为在 Linux 驱动中处理阻塞 IO需要用到等待队列。

wait_queue_head_t r_wait; /* 读等待队列头 */

timer_function函数里,定时器中断处理函数执行,表示有按键按下,先判断一下是否是一次有效的按键,如果是的话就通过 wake_up 或者 wake_up_interruptible 函数来唤醒等待队列r_wait。

	/* 唤醒进程 */if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 *//* wake_up(&dev->r_wait); */wake_up_interruptible(&dev->r_wait);}

keyio_init函数,调用 init_waitqueue_head 函数初始化等待队列头 r_wait。

/* 初始化等待队列头 */init_waitqueue_head(&imx6uirq.r_wait);

imx6uirq_read函数,采用等待事件来处理 read 的阻塞访问, wait_event_interruptible 函数等待releasekey 有效,也就是有按键按下。

如果按键没有按下的话进程就会进入休眠状态,因为采用了 wait_event_interruptible 函数,因此进入休眠态的进程可以被信号打断。

#if 0/* 加入等待队列,等待被唤醒,也就是有按键按下 */ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret) {goto wait_error;} 
#endif

imx6uirq_read函数,使用等待队列实现阻塞访问的关键代码:

  • 首先使用 DECLARE_WAITQUEUE 宏定义一个等待队列,
  • 如果没有按键按下的话,就使用 add_wait_queue 函数将当前任务的等待队列,添加到等待队列头 r_wait 中。
  • 随后调用__set_current_state 函数,设置当前进程的状态为 TASK_INTERRUPTIBLE,也就是可以被信号打断。
  • 接下来调用 schedule 函数进行一次任务切换,当前进程就会进入到休眠态。如果有按键按下,那么进入休眠态的进程就会唤醒,然后接着从休眠点开始运行。
  • 通过 signal_pending 函数,判断一下进程是不是由信号唤醒的,如果是由信号唤醒的话就直接返回-ERESTARTSYS 这个错误码。
  • 如果不是由信号唤醒的(也就是被按键唤醒的),那么就调用__set_current_state 函数将任务状态设置为 TASK_RUNNING,然后调用 remove_wait_queue 函数将进程从等待队列中删除。
	DECLARE_WAITQUEUE(wait, current);	/* 定义一个等待队列 */if(atomic_read(&dev->releasekey) == 0) {	/* 没有按键按下 */add_wait_queue(&dev->r_wait, &wait);	/* 将等待队列添加到等待队列头 */__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */schedule();							/* 进行一次任务切换 */if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */ret = -ERESTARTSYS;goto wait_error;}__set_current_state(TASK_RUNNING);      /* 将当前任务设置为运行状态 */remove_wait_queue(&dev->r_wait, &wait);    /* 将对应的队列项从等待队列头删除 */}

总结一下,使用等待队列实现阻塞访问的步骤:

  • 将任务或者进程加入到等待队列头,
  • 在合适的点唤醒等待队列,一般都是中断处理函数里面。

blockioApp.c

测试app的代码和中断实验的代码一致,代码如下:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;int ret = 0;char *filename;unsigned char data;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, &data, sizeof(data));if (ret < 0) {  /* 数据读取错误或者无效 */} else {		/* 数据读取正确 */if (data)	/* 读取到数据 */printf("key value = %#X\r\n", data);}}close(fd);return ret;
}

Makefile 文件

makefile文件只需要修改 obj-m 变量的值,改为blockio.o。

内容如下:

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := blockio.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行测试

编译代码:

make -j32 //编译makefile文件
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp   //编译测试app

编译成功以后,就会生成一个名为“blockio.ko”的驱动模块文件,和blcokioApp 这个应用程序。

将编译出来 blockio.ko 和 blockioApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板。

进入到目录 lib/modules/4.1.15 中,输入如下命令加载 blockio.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe blockio.ko //加载驱动

加载成功以后,使用如下命令打开 blockioApp 这个测试 APP,并且以后台模式运行:

./blockioApp /dev/blockio &

按下正点原子开发板上的 KEY0 按键,结果如图:

当按下 KEY0 按键以后 blockioApp 这个测试 APP 就会打印出按键值。

输入“top”命令,查看 blockioAPP 这个应用 APP 的 CPU 使用率,如图:

可以看出,当我们在按键驱动程序里面加入阻塞访问以后, blockioApp 这个应用程序的 CPU 使用率从 99.6%降低到了 0.0%。

我们可以使用“kill”命令关闭后台运行的应用程序,比如我们关闭掉 blockioApp 这个后台运行的应用程序。先查看 blockioApp 这个应用程序的 PID:

使用如下命令可“杀死”指定 PID 的进程:

kill -9 149

“./blockioApp /dev/blockio”这个应用程序已经被“杀掉”了。

再输入“ps”命令查看当前系统运行的进程,会发现 blockioApp 已经不见了。

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

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

相关文章

97. 小明逛公园,Floyd 算法,127. 骑士的攻击,A * 算法

97. 小明逛公园Floyd 算法dijkstra, bellman_ford 是求单个起点到单个终点的最短路径&#xff0c;dijkstra无法解决负权边的问题&#xff0c; bellman_ford解决了负权边的问题&#xff0c;但二者都是基于单起点和单终点。而Floyd 算法旨在解决多个起点到多个终点的最短路径问题…

​崩坏世界观中的安全漏洞与哲学映射:从渗透测试视角解构虚拟秩序的脆弱性​

​崩坏世界观&#xff1a;游戏中的世界&#xff0c;是真实&#xff0c;也是虚幻的&#xff01;对于游戏中的NPC角色而言&#xff0c;TA们生存的世界&#xff0c;是真实的&#xff01;对于游戏玩家而言&#xff0c;游戏中的世界&#xff0c;是虚拟的&#xff01;通过沉浸式的游戏…

【离线安装】CentOS Linux 7 上离线部署Oracle 19c(已成功安装2次)

1.部署参考链接&#xff1a; CentOS 7 rpm方式离线安装 Oracle 19chttps://blog.csdn.net/Vampire_1122/article/details/123038137?fromshareblogdetail&sharetypeblogdetail&sharerId123038137&sharereferPC&sharesourceweixin_45806267&sharefromfrom…

小白向:Obsidian(Markdown语法学习)快速入门完全指南:从零开始构建你的第二大脑(免费好用的笔记软件的知识管理系统)、黑曜石笔记

一、认识Obsidian&#xff1a;不只是笔记软件的知识管理系统 1.1 什么是Obsidian Obsidian是一个基于本地存储的知识管理系统&#xff0c;它将你的所有笔记以纯文本Markdown格式保存在电脑本地。这个名字来源于黑曜石——一种火山熔岩快速冷却形成的玻璃质岩石&#xff0c;象…

攻防世界—Confusion1—(模板注入ssti)

一.解题在login和register的页面中发现这个文件路径接下去就找有什么点可以利用二.ssti通过题目信息可知是一只蛇把一只大象缠绕起来了&#xff0c;蛇代表python&#xff0c;大象代表php这边通过python可以推测可能是模板注入&#xff0c;这边我看其他的解题是说通过看报文信息…

【Protues仿真】基于AT89C52单片机的超声波测距

目录 1 HCSR04超声波测距传感器 1.1 基本参数 1.2 引脚说明 1.3 工作原理&#xff08;时序图&#xff09; 2 基于AT89C52单片机的超声波测距电路原理图 2.1 硬件连接说明 2.2 工作原理 3 基于AT89C52单片机的超声波测距控制程序 3.1.1 初始化设置 3.1.2 超声波测距原…

LLM - Agent核心架构:四大“身体”部件

文章目录一、Agent核心架构&#xff1a;四大“身体”部件1. 核心大脑&#xff1a;大型语言模型&#xff08;LLM&#xff09;2. 记忆系统&#xff1a;短期与长期记忆3. 工具箱&#xff08;Toolkit&#xff09;&#xff1a;从“思想家”到“行动家”4. 驱动循环&#xff08;Engin…

html-docx-js 导出word

2025.08.23今天我学习了如何将html页面内容导出到word中&#xff0c;并保持原有格式&#xff0c;效果如下&#xff1a;代码如下&#xff1a;1&#xff1a;列表页面按钮<el-button type"warning" plain icon"el-icon-download" size"mini" cli…

Science Robotics 通过人机交互强化学习进行精确而灵巧的机器人操作

机器人操作仍然是机器人技术中最困难的挑战之一&#xff0c;其方法范围从基于经典模型的控制到现代模仿学习。尽管这些方法已经取得了实质性进展&#xff0c;但它们通常需要大量的手动设计&#xff0c;在性能方面存在困难&#xff0c;并且需要大规模数据收集。这些限制阻碍了它…

Dism++备份系统时报错[句柄无效]的解决方法

当使用Dism进行系统备份时遇到“[句柄无效]”的错误&#xff0c;这通常是由于某些文件或目录的句柄无法正确访问或已被占用所导致。以下是一种有效的解决方法&#xff1a;一、查看日志文件定位日志文件&#xff1a;首先&#xff0c;打开Dism软件所在的目录&#xff0c;并找到其…

华为/思科/H3C/锐捷操作系统操作指南

好的,这是一份针对 华为(VRP)、思科(IOS/IOS-XE)、H3C(Comware)和锐捷(Ruijie OS) 这四大主流网络设备厂商操作系统的对比操作指南。本指南将聚焦于它们的共性和特性,帮助你快速掌握多厂商设备的基本操作。 四大网络厂商操作系统综合操作指南 一、 核心概念与模式对…

一文读懂 DNS:从域名解析到百度访问全流程

目录 前言 一、什么是 DNS&#xff1f;—— 互联网的 “地址簿” 为什么需要 DNS&#xff1f; DNS 的核心参数 二、DNS 解析原理&#xff1a;递归与迭代的协作 1. 两种核心查询方式 2. 完整解析流程&#xff08;以www.baidu.com为例&#xff09; 缓存清理命令 三、DNS …

初试Docker Desktop工具

文章目录1. 概述2. 下载3. 安装4. 注册5. 登录6. 启动7. 容器8. 运行容器8.1 运行容器的镜像8.2 获取示例应用8.3 验证Dockerfile文件8.4 拉取Alpine精简镜像8.5 创建镜像8.6 运行容器8.7 查看前端9. 访问静态资源9.1 本地静态资源9.2 创建服务器脚本9.3 修改Dockerfile文件9.4…

百度披露Q2财报:营收327亿,AI新业务收入首超百亿

8月20日&#xff0c;百度发布2025年第二季度财报&#xff0c;显示季度总营收327亿元&#xff0c;百度核心营收263亿元&#xff0c;归属百度核心净利润74亿元&#xff0c;同比增长35%。受AI驱动&#xff0c;涵盖智能云在内的AI新业务收入增长强劲&#xff0c;首次超过100亿元&am…

【字母异位分组】

思路 核心思路&#xff1a;使用排序后的字符串作为键&#xff0c;将原始字符串分组 键的选择&#xff1a;对于每个字符串&#xff0c;将其排序后得到标准形式作为键分组存储&#xff1a;使用哈希表&#xff0c;键是排序后的字符串&#xff0c;值是对应的原始字符串列表结果构建…

高防cdn如何缓存网页静态资源

为什么需要优化网页静态资源的缓存&#xff1f; 网页静态资源包括图片、CSS、JavaScript等文件&#xff0c;它们通常体积大、访问频繁。在网页访问过程中&#xff0c;如果每次都从源服务器请求这些静态资源&#xff0c;会导致网络延迟和带宽消耗。而优化网页静态资源的缓存&am…

使用Pandas进行缺失值处理和异常值检测——实战指南

目录 一、缺失值处理 1.1 缺失值的识别 1.2 删除缺失值 1.3 填充缺失值 二、异常值检测 2.1 异常值的定义 2.2 常用检测方法 IQR&#xff08;四分位数间距&#xff09;法 Z-score&#xff08;标准分数&#xff09;法 三、实战案例&#xff1a;基因表达数据预处理 四…

B.30.01.1-Java并发编程及电商场景应用

摘要 本文深入探讨了Java并发编程的核心概念及其在电商系统中的实际应用。从基础并发机制到高级并发工具&#xff0c;结合电商业务场景中的典型问题&#xff0c;如高并发秒杀、库存管理、订单处理等&#xff0c;提供了实用的解决方案和最佳实践。 1. Java并发编程基础 1.1 并发…

怎样避免游戏检测到云手机?

以下是一些可能避免游戏检测到云手机的方法&#xff1a;云手机可能会因网络配置等因素出现一些异常网络行为&#xff0c;如网络延迟的规律性变化等&#xff0c;在使用云手机玩游戏时&#xff0c;尽量保持网络行为的稳定性和自然性&#xff0c;避免短时间内频繁切换网络连接&…

文件上传 --- uploadlabs靶场

目录 1 前端和js校验 抓包改包 2 . 2.1 .htaccess&#xff08;伪静态&#xff09; 2.2 %00截断 &#xff08;php5.2&#xff09; 2.3 user_init_ 2.4 3 图片码防御 4 竞争型漏洞 思路&#xff1a; 容易出现的问题: 1 前端和js校验 关闭JS的代码&#xff0c;上传PHP…