KASan部署、使用与原理分析

文章目录

  • 前言
  • 1、概述
  • 2、使用方法
  • 3、测试用例
    • 3.1、检测加载的内核模块
    • 3.2、检测调用的内核模块
    • 3.3、通过系统调用检测
    • 3.4、检测编译到Linux内核中的内核模块
  • 4、工作原理
    • 4.1、影子内存(Shadow Memory)
    • 4.2、内存状态(Memory States)
    • 4.3、红色区域(Redzones)
    • 4.4、KASan的实现
  • 5、参考文献
  • 总结


前言

  本博客的主要内容为KASan的部署、使用与原理分析。本博文内容较长,因为涵盖了KASan的几乎全部内容,从部署的详细过程到如何使用KASan对Linux内核中的内存错误进行检测,以及其对Linux内核中的内存错误进行检测的原理分析,相信认真读完本博文,各位读者一定会对KASan有更深的了解。以下就是本篇博客的全部内容了。


1、概述

  KASan(Kernel Address Sanitizer)是Linux内核中的一种内存错误检测工具,旨在帮助开发者发现和修复内核空间的内存访问错误。下面是关于KASan的一些基本介绍:

  1. 功能和目的
    • 内存错误检测:KASan主要用于检测内核空间代码中的内存错误,包括但不限于内存越界访问、使用未初始化的内存、以及一些特定的内存使用后释放问题。
    • 开发者工具:作为开发者工具,KASan帮助开发者在早期发现并修复潜在的内存错误,提高内核代码的稳定性和安全性。
  2. 工作原理
    • 内存影子(Shadow Memory):KASan使用内存影子技术来实现内存错误检测。它为每个分配的内存区域维护一个影子内存地图,记录了每个内存字节的分配状态和元数据信息。
    • 访问检查:当程序访问内存时,KASan会同时访问影子内存地图,检查对应的内存访问是否有效。例如,检测是否存在未分配或已释放的内存访问,以及是否发生了内存越界访问。
    • 报告和跟踪:当检测到内存错误时,KASan会生成报告并记录相关信息,帮助开发者追踪和修复问题。
  3. 使用方法
    • 配置和编译:KASan的启用需要在编译内核时进行配置,通常通过修改内核配置文件(如“.config”)来启用相关选项(如CONFIG_KASAN=y)。
    • 运行时检测:一旦内核加载并运行,KASan在检测到内存错误时会向系统日志(如dmesg)输出相应的错误信息,包括错误类型、位置和堆栈跟踪等。
  4. 适用性和限制
    • 适用范围:KASan主要用于内核空间的内存错误检测,适用于内核代码本身、驱动程序和内核模块等。
    • 性能开销:KASan在运行时会增加一定的内存和CPU开销,特别是在大规模的内核代码或高频率的内存访问场景下可能会有显著的性能影响。

  总之,KASan是Linux内核中的一种工具,专用于检测和报告内核空间代码中的内存错误。通过影子内存技术,KASan能够有效地检测内存访问越界、未初始化的内存使用等问题,并生成详细的报告帮助开发者迅速定位和修复这些问题。此外KASan工具基于C语言和汇编语言开发。

2、使用方法

  由于KASan是Linux内核自带的一个工具,所以需要在编译内核时开启该工具,故我们会首先编译一个新的内核已开启KASan工具的功能,最后验证其是否开启成功。

  1. 首先使用如下命令查看当前系统的Linux内核版本:
$ uname -r
  1. 可以发现,当前系统的Linux内核版本为4.15.0-45-generic:
    在这里插入图片描述

  2. 然后使用如下命令查看当前系统的版本:

$ lsb_release -a
  1. 可以发现,当前系统的版本为Ubuntu 16.04.6 LTS:
    在这里插入图片描述

  2. 然后下载安装编译内核所需要的软件:

$ sudo apt-get update
$ sudo apt-get install g++ gcc make build-essential libncurses-dev bison flex libssl-dev libelf-dev -y
$ sudo apt-get install openssl zlibc minizip libidn11-dev libidn11 libncurses5-dev -y
  1. 然后下载一个新的Linux内核(在这里我采用的Linux内核版本为4.9.3)源码压缩包,并将其解压,最后进入解压好的Linux内核源码目录:
$ cd /usr/src/
$ sudo wget https://mirrors.aliyun.com/linux-kernel/v4.x/linux-4.9.3.tar.gz
$ sudo tar -zxvf linux-4.9.3.tar.gz
$ cd linux-4.9.3/
  1. 然后顺序执行如下命令来进行编译内核之前的清理工作:
$ sudo make mrproper
$ sudo make clean
  1. 然后执行如下命令来启动内核的编译配置界面:
$ sudo make menuconfig
  1. 然后按如下图所示进行选择,最后按一下“Enter”键:
    在这里插入图片描述

  2. 然后按如下图所示进行选择,最后按一下“Enter”键:
    在这里插入图片描述

  3. 然后按如下图所示进行选择,最后按一下“Enter”键:
    在这里插入图片描述

  4. 然后按如下图所示进行选择,最后按一下“Enter”键:
    在这里插入图片描述

  5. 然后按如下图所示进行选择,最后按一下“Space”键:
    在这里插入图片描述

  6. 然后连续按“Esc”键直到出现如下图所示的界面,然后按如下图所示进行选择,最后按一下“Enter”键,这样就开启了当前待编译内核的KASan功能:
    在这里插入图片描述

  7. 然后执行如下命令来编译内核:

$ sudo make -j$(nproc)
  1. 然后执行如下命令来安装编译好的内核模块:
$ sudo make modules_install
  1. 然后执行如下命令来安装编译好的内核:
$ sudo make install
  1. 然后重启系统:
$ reboot
  1. 在重启系统的过程中按“Esc”键,即可进入如下图所示界面,我们只需要如下图红框和红箭头处所示进行选择即可:
    在这里插入图片描述

  2. 然后按如下图所示进行选择,然后按一下“Enter”键,目的是使用我们刚刚编译好的Linux内核加载以进入系统:
    在这里插入图片描述

  3. 然后执行如下命令来查看系统内核是否更换成功:

$ uname -r
  1. 可以发现,当前系统的内核已经更换为4.9.3:
    在这里插入图片描述

  2. 然后执行如下命令来查看KASan是否成功开启:

$ grep CONFIG_KASAN /boot/config-$(uname -r)
  1. 执行上面的命令后,出现如下图所示的内容即代表KASan开启成功:
    在这里插入图片描述

3、测试用例

3.1、检测加载的内核模块

  在本章节,我们将通过编写和运行一个简单的内核模块,故意触发一个内存越界访问来对KASan进行测试(注意:该测试用例使用的Linux内核版本为4.4.252)。

  1. 首先来到当前用户的根目录,并新建一个代码文件,然后将其打开:
$ cd ~
$ touch kasan_test.c
$ gedit kasan_test.c
  1. 然后在打开的文件中输入如下内容,最后保存修改后退出:
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/kernel.h>
#include <linux/slab.h>static int __init kasan_test_init(void) 
{ char *ptr; size_t size = 124; printk(KERN_INFO "KASan test module loaded\n");ptr = kmalloc(size, GFP_KERNEL); if (!ptr) { printk(KERN_ERR "Allocation failed\n"); return -1; } printk(KERN_INFO "ptr address: %p\n", ptr);ptr[size] = 'x'; printk(KERN_INFO "Accessing out-of-bounds memory: ptr[%zu] address: %p\n", size, ptr + size);kfree(ptr);return 0;
}static void __exit kasan_test_exit(void)
{printk(KERN_INFO "KASan test module unloaded\n");
}module_init(kasan_test_init);
module_exit(kasan_test_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("IronmanJay");
MODULE_DESCRIPTION("KASan test module");

该内核模块包含一个初始化函数和一个退出函数。在初始化函数kasan_test_init中,分配了一块大小为124字节的内存,然后故意访问越界位置(第124字节,即ptr[size])。函数会打印内存地址及越界访问地址,最后释放分配的内存。退出函数kasan_test_exit仅打印一个模块卸载信息。通过这些步骤,可以测试KASan(Kernel Address Sanitizer)是否能够检测到内存越界访问。

  1. 然后再在当前目录下创建一个Makefile文件,并将其打开:
$ touch Makefile
$ gedit Makefile
  1. 在打开的文件中输入如下内容,最后保存修改后退出:
obj-m += kasan_test.o# 添加调试信息和警告
EXTRA_CFLAGS += -g -Wall# 指定内核源码路径
KERNEL_SOURCE := /lib/modules/$(shell uname -r)/build# 默认目标
all:make -C $(KERNEL_SOURCE) M=$(PWD) modules# 清理目标
clean:make -C $(KERNEL_SOURCE) M=$(PWD) clean
  1. 然后顺序执行下面两条命令,以编译和加载该内核模块:
$ make
$ sudo insmod kasan_test.ko
  1. 然后执行如下命令来查看内核日志以确认KASan是否捕捉到我们设计好的内存越界访问:
$ dmesg -T
  1. 执行上面的命令后,会在命令行窗口打印如下图所示的信息,这就说明我们自定义的内核模块中的内存越界错误已经被KASan捕获到了:
    在这里插入图片描述

3.2、检测调用的内核模块

  在本章节,我们将使用用户态的二进制程序来调用存在内存越界错误的内核模块,从而故意触发一个内存越界访问来对KASan进行测试,测试KASan能否检测到调用的内核模块中的内存越界错误(注意:该测试用例使用的Linux内核版本为4.9.3)。

  1. 首先来到当前用户的根目录,并新建一个代码文件,然后将其打开:
$ cd ~
$ touch kasan_trigger.c
$ gedit kasan_trigger.c
  1. 然后在打开的文件中输入如下内容,最后保存修改后退出:
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>#define PROC_NAME "kasan_trigger"static ssize_t proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos) {char *kbuf;int *p;// 分配内核缓冲区并从用户态复制数据kbuf = kmalloc(count, GFP_KERNEL);if (!kbuf)return -ENOMEM;if (copy_from_user(kbuf, buffer, count)) {kfree(kbuf);return -EFAULT;}// 故意引发内存越界错误p = kmalloc(sizeof(int) * 10, GFP_KERNEL);if (!p) {kfree(kbuf);return -ENOMEM;}p[10] = 0;  // 越界写入kfree(p);kfree(kbuf);return count;
}static const struct file_operations proc_file_ops = {.write = proc_write,
};static int __init kasan_trigger_init(void) {if (!proc_create(PROC_NAME, 0666, NULL, &proc_file_ops)) {printk(KERN_ERR "Error: Could not initialize /proc/%s\n", PROC_NAME);return -ENOMEM;}printk(KERN_INFO "/proc/%s created\n", PROC_NAME);return 0;
}static void __exit kasan_trigger_exit(void) {remove_proc_entry(PROC_NAME, NULL);printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);
}MODULE_LICENSE("GPL");
MODULE_AUTHOR("IronmanJay");
MODULE_DESCRIPTION("A module to trigger KASAN error via proc file");module_init(kasan_trigger_init);
module_exit(kasan_trigger_exit);

该内核模块通过在“/proc”文件系统中创建一个名为“kasan_trigger”的文件。写入该文件时,模块故意引发内存越界错误,以触发KASAN(Kernel Address Sanitizer)检测内存问题。模块的初始化和退出函数分别创建和删除该“/proc”文件。

  1. 然后再在当前目录下创建一个Makefile文件,并将其打开:
$ touch Makefile
$ gedit Makefile
  1. 在打开的文件中输入如下内容,最后保存修改后退出:
obj-m += kasan_trigger.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
  1. 然后顺序执行下面两条命令,以编译和加载该内核模块:
$ make
$ sudo insmod kasan_trigger.ko
  1. 然后创建一个用户态程序来触发上面写好的内存越界错误:
$ touch trigger_kasan.c
$ gedit trigger_kasan.c 
  1. 在打开的文件中输入如下内容,然后保存修改后退出:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {int fd;char buffer[] = "trigger";fd = open("/proc/kasan_trigger", O_WRONLY);if (fd < 0) {perror("open");return 1;}if (write(fd, buffer, strlen(buffer)) < 0) {perror("write");close(fd);return 1;}close(fd);return 0;
}
  1. 然后编译并运行写好的用户态程序:
$ gcc -o trigger_kasan trigger_kasan.c
$ ./trigger_kasan
  1. 然后执行如下命令来查看内核日志以确认KASan是否捕捉到我们设计好的内存越界访问:
$ dmesg -T
  1. 执行上面的命令后,会在命令行窗口打印如下图所示的信息,这就说明我们自定义的内核模块中的内存越界错误已经被KASan捕获到了:
    在这里插入图片描述

3.3、通过系统调用检测

  在本章节,我们将使用用户态的二进制程序来调用某个系统调用,从而故意触发一个内存越界访问来对KASan进行测试,测试KASan能否检测到通过系统调用引发的内核中的内存越界错误(注意:该测试用例使用的Linux内核版本为4.9.3)。

  1. 首先来到当前用户的根目录,并新建一个代码文件,然后将其打开:
$ cd ~
$ touch kasan_syscall.c
$ gedit kasan_syscall.c 
  1. 然后在打开的文件中输入如下内容,最后保存修改后退出:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main() {// 分配内存映射区域size_t length = 4096;void *addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (addr == MAP_FAILED) {perror("mmap failed");return 1;}// 写入数据到内存区域内strcpy((char *)addr, "hello, KASAN!");// 尝试访问映射区域外的内存(引发越界错误)// 这个操作应当引发KASan检测到内存越界错误*((char *)addr + length) = 'x'; // 越界写入// 清理内存映射区域munmap(addr, length);return 0;
}

这段代码是一个用户态程序,通过内存映射分配一块内存区域,并向其中写入数据。随后,它尝试访问并写入该内存区域的边界之外,故意引发内存越界错误,以触发KASAN(Kernel Address Sanitizer)的检测。最后,程序清理内存映射区域并退出。

  1. 然后编译并运行写好的用户态程序:
$ gcc -o kasan_syscall kasan_syscall.c
$ ./kasan_syscall
  1. 执行上面的命令后,会打印如下图所示的内容:
    在这里插入图片描述

  2. 然后执行如下命令来查看内核日志以确认KASan是否捕捉到我们设计好的内存越界访问:

$ dmesg | grep -i kasan
  1. 执行上面的命令后,会在命令行窗口打印如下图所示的信息,这就说明我们自定义的内核模块中的内存越界错误已经被KASan捕获到了:
    在这里插入图片描述

3.4、检测编译到Linux内核中的内核模块

  在前面章节的测试中,我们使用了十分明显的内存错误测试用例,然而在大多数情况下,内存错误是不易被察觉且难以发现的,所以本章节将模拟正常使用的情况,通过比较复杂的内核模块代码来模拟用户正常使用内核模块时的情况来测试KASan能否检测到我们设置的内存错误(注意:该测试用例使用的Linux内核版本为4.9.3)。

  1. 首先来到当前用户的根目录中,并创建测试目录,最后进入该目录:
$ cd ~
$ mkdir test
$ cd test/
  1. 然后创建下面的文件,并编辑:
$ touch mem_error_module.c
$ gedit mem_error_module.c
  1. 在打开的文件中输入下面的内容,最后保存修改后退出:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/device.h>  // 用于 class_create 和相关函数
#include <linux/string.h>#define DEVICE_NAME "mem_error"
#define CLASS_NAME  "mem_error_class"
#define BUFFER_SIZE 1024
#define TRIGGER_CONDITION 1024static int majorNumber;
static char kernel_buffer[BUFFER_SIZE];
static struct class* memErrorClass = NULL;
static struct device* memErrorDevice = NULL;static ssize_t dev_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset) {// 复杂的条件判断if (len > TRIGGER_CONDITION) {// 只有当len大于TRIGGER_CONDITION时,才会导致缓冲区溢出printk(KERN_WARNING "mem_error: Buffer overflow condition met\n");// 使用不安全的内存操作来制造溢出memcpy(kernel_buffer, buffer, len);  // 不安全的内存操作return len;} else {// 安全地复制数据if (copy_from_user(kernel_buffer, buffer, len)) {return -EFAULT;}return len;}
}static int dev_open(struct inode *inodep, struct file *filep) {printk(KERN_INFO "mem_error: Device has been opened\n");return 0;
}static int dev_release(struct inode *inodep, struct file *filep) {printk(KERN_INFO "mem_error: Device successfully closed\n");return 0;
}static struct file_operations fops = {.open = dev_open,.write = dev_write,.release = dev_release,
};static int __init mem_error_init(void) {printk(KERN_INFO "mem_error: Initializing the mem_error module\n");// 动态分配主设备号majorNumber = register_chrdev(0, DEVICE_NAME, &fops);if (majorNumber < 0) {printk(KERN_ALERT "mem_error: Failed to register a major number\n");return majorNumber;}printk(KERN_INFO "mem_error: Registered correctly with major number %d\n", majorNumber);// 注册设备类memErrorClass = class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(memErrorClass)) {unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_ALERT "mem_error: Failed to register device class\n");return PTR_ERR(memErrorClass);}printk(KERN_INFO "mem_error: Device class registered correctly\n");// 注册设备驱动memErrorDevice = device_create(memErrorClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);if (IS_ERR(memErrorDevice)) {class_destroy(memErrorClass);unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_ALERT "mem_error: Failed to create the device\n");return PTR_ERR(memErrorDevice);}printk(KERN_INFO "mem_error: Device class created correctly\n");return 0;
}static void __exit mem_error_exit(void) {device_destroy(memErrorClass, MKDEV(majorNumber, 0));class_unregister(memErrorClass);class_destroy(memErrorClass);unregister_chrdev(majorNumber, DEVICE_NAME);printk(KERN_INFO "mem_error: Goodbye from the mem_error module!\n");
}module_init(mem_error_init);
module_exit(mem_error_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("IronmanJay");
MODULE_DESCRIPTION("A simple Linux char driver with a conditional buffer overflow");
MODULE_VERSION("1.0");
  1. 然后创建下面的文件,并编辑:
$ touch Makefile
$ gedit Makefile
  1. 在打开的文件中输入下面的内容,最后保存修改后退出:
obj-m += mem_error_module.oall:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
  1. 然后执行下面的命令来编译该内核模块:
$ make
  1. 然后执行下面的命令来加载编译好的内核模块:
$ sudo insmod mem_error_module.ko
  1. 然后创建下面的文件,并编辑:
$ touch test_mem_error.c
$ gedit test_mem_error.c
  1. 在打开的文件中输入下面的内容,最后保存修改后退出:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>#define DEVICE_PATH "/dev/mem_error"int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <input_string>\n", argv[0]);return 1;}const char *input = argv[1];int fd = open(DEVICE_PATH, O_WRONLY);if (fd < 0) {perror("Failed to open the device");return 1;}ssize_t bytes_written = write(fd, input, strlen(input));if (bytes_written < 0) {perror("Failed to write to the device");return 1;}printf("Wrote %zd bytes to the device\n", bytes_written);close(fd);return 0;
}
  1. 然后执行下面的命令来编译我们刚刚写好的代码:
$ gcc -o test_mem_error test_mem_error.c
  1. 然后打开一个新的命令行终端,并执行下面的命令来实时检测KASan捕获到的信息:
$ sudo dmesg -Tw
  1. 然后执行下面的命令来进行第一次测试,本次的测试用例并不会触发内存错误:
$ sudo ./test_mem_error "This is a normal situation"
  1. 由于该测试用例并不会触发内存错误,所以可以发现KASan并没有捕获到相关信息:
    在这里插入图片描述

  2. 然后执行下面的命令来进行第二次测试,本次的测试用例会触发我们手动设置的内存错误:

$ sudo ./test_mem_error $(perl -e 'print "A" x 2000')
  1. 由于该测试用例会触发我们手动设置的内存错误,所以可以发现KASan成功捕获到了相关信息:
    在这里插入图片描述

4、工作原理

  KASan(Kernel Address Sanitizer)是用于检测内核内存错误的工具,主要用于检测内存越界访问和使用已释放的内存。它通过在内存周围插入红色区域(称为“毒区”或“红色区域”)并在内存分配和释放时检查这些区域的状态来工作。它可以检测以下类型的内存错误:

  • 内存越界访问(Out-of-bounds access)
  • 使用未初始化的内存(Use-after-free)
  • 使用未初始化的堆内存(Use-after-poison)
  • 重复释放内存(Double-free)

  KASan的实现依赖于影子内存(Shadow Memory)和红色区域(Redzones)来跟踪内存分配和访问情况。

4.1、影子内存(Shadow Memory)

  影子内存是KASan用于记录每个内存字节状态的内存区域。每个实际内存字节对应一个影子字节,影子字节的值表示相应内存字节的状态。影子内存的布局如下:

  • 1字节影子内存对应8字节实际内存
  • 每个影子字节可以表示8个实际内存字节的状态

  影子内存的计算公式如下:
S h a d o w A d d r = ( A d d r > > 3 ) + S H A D O W _ O F F S E T ShadowAddr = (Addr >> 3) + SHADOW\_OFFSET ShadowAddr=(Addr>>3)+SHADOW_OFFSET

  其中 A d d r Addr Addr是实际内存地址, S H A D O W _ O F F S E T SHADOW\_OFFSET SHADOW_OFFSET是影子内存的起始偏移。

4.2、内存状态(Memory States)

  影子内存字节的值表示相应内存字节的状态:

  • 0:对应内存字节是安全的
  • 负数(-1到-8):对应内存字节在红色区域内,不可访问
  • 正数(1到7):对应内存字节部分有效

4.3、红色区域(Redzones)

  红色区域是在每个分配的内存块前后插入的未分配区域。这些区域用于检测越界访问。当访问红色区域时,影子内存会标记这些访问为非法。

4.4、KASan的实现

  KASan通过以下步骤实现内存错误检测:

  1. 内存分配
    • 在内存块前后插入红色区域
    • 更新影子内存以标记红色区域和实际分配的内存块
  2. 内存释放
    • 将整个内存块及其红色区域标记为中毒状态
    • 更新影子内存
  3. 内存访问
    • 检查影子内存对应的值,以确定内存访问是否合法
    • 如果发现非法访问,记录错误信息并触发内核警告

5、参考文献

  1. linux之kasan原理及解析
  2. linux内核(5.4.81)—KASAN - povcfe’s blog
  3. 编译内核报错 No rule to make target ‘debian/canonical-certs.pem‘ 或 ‘canonical-revoked-certs.pem‘ 的解决方法
  4. warning: the frame size of 1040 bytes is larger than 1024 bytes
  5. ubuntu22.04:使用时遇到的问题_missing symbol table
  6. 编译内核报错——Failed to generate BTF for vmlinux
  7. 编译内核启用KASan动态检测内核内存错误功能(ubuntu16.04 4.4.0内核编译升级到linux-4.4.252版本)_module for testing kasan for bug detection

总结

  以上就是本篇博文的全部内容,可以发现,KASan的部署与使用过程并不复杂,我们本篇博客对其进行了详细的分析。相信读完本篇博客,各位读者一定对KASan有了更深的了解。

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

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

相关文章

Go小技巧易错点100例(十八)

正文&#xff1a; 使用下划线增加数字可读性 有时候我们代码里会定义很长的数字&#xff0c;虽然计算机程序能支持很大的数据的计算&#xff0c;但是对我们来说&#xff0c;可读性是一个需要考虑的点&#xff0c;特别是1后面全是0的时候。 但是这个问题在Go语言中是可以通过…

使用js和canvas实现简单的网页打砖块小游戏

玩法介绍 点击开始游戏后&#xff0c;使用键盘上的←→控制移动&#xff0c;小球会不停移动&#xff0c;板子触碰小球时会反弹&#xff0c;碰撞到砖块时会摧毁砖块&#xff0c;如果没有用板子接住小球就游戏失败 代码实现 代码比较简单&#xff0c;直接阅读注释即可&#x…

Leetcode—1226. 哲学家进餐【中等】(多线程)

2024每日刷题&#xff08;185&#xff09; Leetcode—1226. 哲学家进餐 C实现代码 class DiningPhilosophers { public:mutex mx;DiningPhilosophers() {}void wantsToEat(int philosopher,function<void()> pickLeftFork,function<void()> pickRightFork,functi…

云快充1.5协议+云快充1.6协议通讯框架

云快充协议云快充1.5协议云快充1.6云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩协议云快充源码 介绍 云快充协议云快充1.5协议云快充1.6云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩协议云快充源码 软件架构 1、提供云快充底层桩直连协…

Java【多线程】阻塞队列

目录 阻塞队列 阻塞队列是什么&#xff1f; 生产者消费者模型 生产者消费者模型的两个重要优势 1.解耦合&#xff08;不一定是两个线程之间&#xff0c;也可以是两个服务器之间&#xff09; 2.阻塞队列就相当于一个缓冲区&#xff0c;平衡了生产者和消费者的处理能力&…

【Web前端概述】

HTML 是用来描述网页的一种语言&#xff0c;全称是 Hyper-Text Markup Language&#xff0c;即超文本标记语言。我们浏览网页时看到的文字、按钮、图片、视频等元素&#xff0c;它们都是通过 HTML 书写并通过浏览器来呈现的。 一、HTML简史 1991年10月&#xff1a;一个非正式…

深度学习(一)基础:神经网络、训练过程与激活函数(1/10)

深度学习基础&#xff1a;神经网络、训练过程与激活函数 引言&#xff1a; 深度学习作为机器学习的一个子领域&#xff0c;近年来在人工智能的发展中扮演了举足轻重的角色。它通过模仿人脑的神经网络结构&#xff0c;使得计算机能够从数据中学习复杂的模式和特征&#xff0c;…

当小程序学会‘读心术’:表单处理的神秘法则

哈喽&#xff0c;我是阿佑&#xff0c;今天将给大家给咱们的小程序赋能——“读心术”&#xff01; 文章目录 微信小程序的表单处理表单元素&#xff1a;小程序的“语言”表单事件&#xff1a;小程序的“听觉”表单提交&#xff1a;小程序的“表达”总结 微信小程序的表单处理 …

1 -《本地部署开源大模型》如何选择合适的硬件配置

如何选择合适的硬件配置 为了在本地有效部署和使用开源大模型&#xff0c;深入理解硬件与软件的需求至关重要。在硬件需求方面&#xff0c;关键是配置一台或多台高性能的个人计算机系统或租用配备了先进GPU的在线服务器&#xff0c;确保有足够的内存和存储空间来处理大数据和复…

设置了超时时间但是不起作用,浏览器里的setTimeout有 bug?

你可能也遇到过这样的问题:写个setTimeout定时器,结果时间一长,浏览器就开始捣乱。比如你想要设置一个几小时甚至几天的延时,突然发现浏览器不听话了!这时候你就会想,难道浏览器的定时器是有上限的?没错,你没看错,setTimeout其实有个最大值限制,时间一超过这个值,就…

Python Numpy 实现神经网络自动训练:反向传播与激活函数的应用详解

Python Numpy 实现神经网络自动训练&#xff1a;反向传播与激活函数的应用详解 这篇文章介绍了如何使用 Python 的 Numpy 库来实现神经网络的自动训练&#xff0c;重点展示了反向传播算法和激活函数的应用。反向传播是神经网络训练的核心&#xff0c;能够通过计算梯度来优化模…

嵌入式入门学习——7Protues导入Arduino IDE生成的固件和Arduino使用库文件开发

0 系列文章入口 嵌入式入门学习——0快速入门&#xff0c;Let‘s Do It&#xff01; 1 Arduino IDE 请自行下载安装&#xff0c;点击标题链接即可&#xff0c;下载完成后 1新建工程并保存&#xff0c;注意工程名和工程所在的文件夹必须同名。 2新建工程的时候注意选择板子型…

循环移位的学习

循环移位&#xff08;Rotational Shift&#xff09;&#xff0c;也称为循环位移&#xff0c;是一种特殊的位移操作。在循环移位中&#xff0c;移出的位会被重新放入到另一端&#xff0c;从而实现循环效果。与逻辑移位和算术移位不同&#xff0c;循环移位不丢失任何位&#xff0…

php中的错误和异常捕获

目录 一&#xff1a; 异常&#xff08;Exceptions&#xff09; 二&#xff1a; 错误&#xff08;Errors&#xff09; 三&#xff1a;实际项目的异常和错误处理 在PHP中&#xff0c;异常&#xff08;Exceptions&#xff09;和错误&#xff08;Errors&#xff09;是两个不同的…

比亚迪车机安装第三方应用教程

比亚迪车机安装第三方应用教程 比亚迪车机U盘安装APP&#xff0c; 无论是dlink3.0还是4.0都是安卓系统&#xff0c;因此理论上安卓应用是都可以安装的&#xff0c;主要就是横屏和竖屏的区别。在比亚迪上安装软件我主要推荐两种方法。 第一种&#xff0c;直接从电脑端下载安装布…

Standard IO

为了提高可移植性&#xff0c;将通用IO接口经过再封装就形成了标准IO&#xff0c;标准IO不仅适用于Unix环境&#xff0c;也兼容非Unix环境&#xff0c;这也是为什么说我们应该尽可能的使用标准IO&#xff0c;通用IO通过文件描述符fd来与文件交互&#xff0c;为了以示区分&#…

DCGAN的原理(附代码解读)

学习DCGAN之前需要了解一下转置卷积 可以参考学DCGAN对抗网络之前--转置卷积(附代码解读)-CSDN博客 1.DCGAN对于GAN的改进之处 网络架构的优化&#xff1a; DCGAN在生成器和判别器中明确使用了卷积层和卷积转置层&#xff08;也称为反卷积层或分数阶卷积层&#xff09;。这一…

『 Linux 』HTTPS

文章目录 HTTPS协议密钥加密的原因加密方式数据指纹网络通信加密方案及短板CA认证CA证书的细节以及如何保证服务端公钥的安全性和数据完整性 CA认证后对称加密与非对称加密配合使用的安全性中间人的攻击方式 HTTPS协议 HTTPS协议并不是一个独立的协议,其是一种以HTTP协议为基础…

基于SSM的洗浴中心管理系统的设计与实现

文未可获取一份本项目的java源码和数据库参考。 方案设计&#xff08;研究的基本内容&#xff0c;拟解决的基本问题&#xff0c;研究步骤、方法及措施&#xff09;&#xff1a; 研究的基本内容&#xff1a;根据当今社会市场所需&#xff0c;通过对比多家洗浴中心进行深入细致的…

第二十九篇:图解TCP三次握手,看过不会忘,从底层说清楚,TCP系列四

⼀开始&#xff0c;客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端⼝&#xff0c;处于 LISTEN 状态。 接下来这部分内容的介绍将影响你能不能彻底理解了TCP的三次握手。 一、划重点&#xff1a;只有服务端启动了端口监听&#xff0c;客户端TCP握手才能建立连接&…