Linux驱动开发—并发与竞争,原子操作,自旋锁,信号量详解

news2024/12/27 17:53:18

1.并发与并行的概念

并发是指在同一时间段内,多个任务交替执行。并发可以发生在单核处理器上,通过任务切换实现

在这里插入图片描述

并行是指在同一时间段内,多个任务同时执行。并行可以发生在多核处理器上,例如下图任务1 和任务3同时进行,这是一个并行的过程

在这里插入图片描述

并发同样可以发生在多核处理器之间,通过真正的并行执行实现。并发的主要目的是提高程序的响应能力和利用系统资源的效率。

2.竞争的概念

竞争是指多个线程或进程争夺相同资源(如内存、文件、网络连接等)时发生的冲突情况。竞争会导致资源争用问题,如死锁、饥饿、竞态条件等。

2.1竞态条件(Race Condition)

竞态条件是指多个线程或进程在没有适当同步的情况下访问和修改共享资源时,操作的执行顺序影响到最终结果,从而导致不确定性和错误的情况。竞态条件通常发生在以下情况下:

  1. 共享资源:多个线程或进程同时访问和修改同一个共享资源,如变量、数据结构、文件等。
  2. 没有同步:对共享资源的访问和修改没有进行适当的同步,导致多个线程或进程在读写过程中相互干扰。
  3. 不确定的执行顺序:由于线程或进程的调度是不可预测的,操作的执行顺序也就无法预测,导致最终结果不确定。

示例
假设有两个线程A和B,它们都试图增加一个共享变量counter的值。如果没有同步机制,可能会发生以下情况:

Thread A: load counter (counter = 0)
Thread B: load counter (counter = 0)
Thread A: increment counter (counter = 1)
Thread B: increment counter (counter = 1)
Thread A: store counter (counter = 1)
Thread B: store counter (counter = 1)

最终结果是counter为1,而不是预期的2。

2.2死锁(Deadlock)

死锁是指两个或多个线程或进程在等待彼此持有的资源,导致所有参与者都无法继续执行的情况。死锁通常发生在以下情况下:

  1. 互斥条件:资源不能被共享,必须互斥使用。
  2. 持有并等待条件:线程或进程已经持有一个资源,同时又在等待另一个资源,而不释放它已持有的资源。
  3. 不剥夺条件:资源不能被强制剥夺,必须由持有它的线程或进程自行释放。
  4. 循环等待条件:存在一个资源循环等待链,链中的每个线程或进程都在等待下一个线程或进程持有的资源。

示例
假设有两个线程A和B,以及两个资源R1和R2:

Thread A: lock R1
Thread B: lock R2
Thread A: wait for R2 (held by B)
Thread B: wait for R1 (held by A)

此时,线程A和B都在等待对方释放资源,形成循环等待,导致死锁。

2.3饥饿(Starvation)

饥饿是指一个线程或进程长期无法获得所需资源,从而无法继续执行的情况。饥饿通常发生在以下情况下:

  1. 资源分配不公平:调度器优先分配资源给某些线程或进程,而其他线程或进程一直得不到资源。
  2. 优先级不当:高优先级的线程或进程持续占用资源,低优先级的线程或进程长时间得不到资源。
  3. 资源请求模式:某些线程或进程频繁请求资源,导致其他线程或进程的资源请求一直得不到满足。

示例
假设有多个线程T1、T2、T3,其中T1和T2频繁请求资源R,而T3的请求较少:

Thread T1: request R
Thread T2: request R
Thread T3: request R (but always after T1 and T2)

由于T1和T2总是优先获得资源R,T3的请求得不到满足,导致T3长期处于饥饿状态。

3.原子操作

3.1相关概念

原子操作是指在多线程或多进程环境下,不可分割、不能被中断的操作。原子操作要么完全执行,要么完全不执行,执行过程中不会被其他操作干扰。它们是并发编程中的基本构建块,用于实现线程安全的操作。

特点

不可分割性:原子操作是不可分割的,执行过程中不会被中断,确保操作的完整性。

并发安全:原子操作在多线程环境中是安全的,多个线程可以并发执行而不会引起竞态条件。

应用:

原子操作在多线程编程中有广泛的应用,特别是在以下场景中:

  1. 计数器:原子操作可以用于实现线程安全的计数器,如请求计数、引用计数等。
  2. 标志位:原子操作可以用于设置和清除标志位,以实现状态管理和同步。
  3. 锁的实现:许多锁(如自旋锁)使用原子操作来实现锁的获取和释放。
  4. 无锁数据结构:原子操作是实现无锁(Lock-Free)和无等待(Wait-Free)数据结构的基础,如无锁队列、无锁栈等。

优缺点

优点

  1. 高效:原子操作通常比使用锁更高效,因为它们避免了上下文切换和锁竞争。
  2. 简洁:原子操作提供了简单的接口来实现复杂的同步机制。
  3. 硬件支持:现代处理器直接支持原子操作,确保其高性能。

缺点

  1. 局限性:原子操作适用于简单的同步需求,对于复杂的同步问题可能需要更高级的机制(如锁、条件变量)。
  2. 硬件依赖:不同处理器架构对原子操作的支持可能有所不同,代码的可移植性可能受到影响。

3.2相关API

Linux内核中常用的原子操作包括以下几种:

  1. 原子整数操作
    • atomic_t:定义一个原子变量。
    • atomic_set(atomic_t *v, int i):将原子变量设置为给定值。
    • atomic_read(const atomic_t *v):读取原子变量的值。
    • atomic_add(int i, atomic_t *v):将给定值加到原子变量上。
    • atomic_sub(int i, atomic_t *v):从原子变量中减去给定值。
    • atomic_inc(atomic_t *v):将原子变量递增1。
    • atomic_dec(atomic_t *v):将原子变量递减1。
    • atomic_cmpxchg(atomic_t *v, int old, int new):如果原子变量的值等于old,则将其值设置为new
  2. 位操作
    • set_bit(int nr, volatile unsigned long *addr):设置特定位。
    • clear_bit(int nr, volatile unsigned long *addr):清除特定位。
    • test_and_set_bit(int nr, volatile unsigned long *addr):测试并设置特定位。
    • test_and_clear_bit(int nr, volatile unsigned long *addr):测试并清除特定位。

3.3示例代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME "my_char_device"


#define BUFFER_SIZE 64

static atomic_t counter = ATOMIC_INIT(0);

static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体

static int device_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Device opened\n");
    return 0;
}

static int device_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Device closed\n");
    return 0;
}

static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset) {
    char msg[BUFFER_SIZE];
    int msg_len;
    
    msg_len = snprintf(msg, BUFFER_SIZE, "%d\n", atomic_read(&counter));
    if (*offset >= msg_len) {
        return 0;
    }

    if (len > msg_len - *offset) {
        len = msg_len - *offset;
    }

    if (copy_to_user(buffer, msg + *offset, len)) {
        return -EFAULT;
    }

    *offset += len;
    return len;
}

static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset) {
    char msg[BUFFER_SIZE];
    
    if (len > BUFFER_SIZE - 1) {
        len = BUFFER_SIZE - 1;
    }

    if (copy_from_user(msg, buffer, len)) {
        return -EFAULT;
    }

    msg[len] = '\0';

    if (strcmp(msg, "inc\n") == 0) {
        atomic_inc(&counter);
    } else if (strcmp(msg, "dec\n") == 0) {
        atomic_dec(&counter);
    } else if (sscanf(msg, "add %d\n", &len) == 1) {
        atomic_add(len, &counter);
    } else if (sscanf(msg, "sub %d\n", &len) == 1) {
        atomic_sub(len, &counter);
    } else {
        return -EINVAL;
    }

    return len;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};


static int __init test_init(void)
{
    int retval;
    dev_t dev;

    printk(KERN_INFO "module init success\n");

    // 1. 动态分配主次设备号
    retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        goto fail_alloc_chrdev_region;
    }

    major_number = MAJOR(dev);
    printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));

    // 2. 初始化 cdev 结构体并添加到内核
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev_add;
    }

    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class))
    {
        printk(KERN_ERR "Failed to create class\n");
        retval = PTR_ERR(my_class);
        goto fail_class_create;
    }

    // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!
    my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(my_device))
    {
        printk(KERN_ERR "Failed to create device\n");
        retval = PTR_ERR(my_device);
        goto fail_device_create;
    }

    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;

fail_device_create:
    class_destroy(my_class);
fail_class_create:
    cdev_del(&mydev);
fail_cdev_add:
    unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
    return retval;
}

static void __exit test_exit(void)
{
    dev_t dev = MKDEV(major_number, 0);
    if (my_device)
        device_destroy(my_class, dev);
    if (my_class)
        class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_char_device: module unloaded\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");

3.4测试

可以使用echo 和 cat 来简单演示 原子操作加减

root@imx8qmmek:~/module_test# echo "inc" > /dev/my_char_device 
root@imx8qmmek:~/module_test# echo "add 5" > /dev/my_char_device 
-sh: echo: write error: Invalid argument
root@imx8qmmek:~/module_test# cat /dev/my_char_device 
6

ps:不知道为什么 sscanf 会报错,但是仍然是成功执行了

4.自旋锁

自旋锁(spinlock)是一种用于多处理器系统中的同步机制,它可以保护共享资源免受多个进程或线程的并发访问。自旋锁在等待锁的时候不会引起调度器的上下文切换,而是不断地循环检查锁的状态,直到锁可用为止。

4.1自旋锁的基本概念

  1. 自旋锁的定义和用途: 自旋锁通过忙等待(busy-waiting)的方式来实现锁机制,主要用于多处理器环境中短时间的临界区保护。由于自旋锁不会引起进程切换,因此在锁竞争不激烈且临界区很短的情况下,它比互斥锁更高效。
  2. 忙等待(Busy-waiting): 当一个线程试图获取一个自旋锁而锁已经被其他线程持有时,该线程会在一个循环中不断检查锁的状态,而不是被阻塞和切换到其他线程。这种等待方式称为忙等待。
  3. 适用场景: 自旋锁适用于以下场景:
    • 临界区非常短,持有锁的时间很短。
    • 在中断上下文中使用,因为在中断上下文中不能使用可能引起睡眠的锁。
    • 在多处理器系统中,由于自旋锁避免了上下文切换的开销,短时间的忙等待可能更高效。
  4. 限制
    • 自旋锁不能在单处理器系统中使用,因为忙等待会导致死锁。
    • 不适合长时间持有锁的情况,因为忙等待会浪费CPU资源。

4.2Linux内核中的自旋锁

在Linux内核中,自旋锁的API提供了一组函数来初始化、获取和释放自旋锁。以下是常用的自旋锁API:

  1. 定义和初始化自旋锁
    • spinlock_t my_lock;:定义一个自旋锁。
    • spin_lock_init(&my_lock);:初始化自旋锁。
  2. 获取和释放自旋锁
    • spin_lock(&my_lock);:获取自旋锁。如果锁已经被其他处理器获取,该处理器将进入忙等待。
    • spin_unlock(&my_lock);:释放自旋锁。
  3. 获取和释放中断上下文中的自旋锁
    • spin_lock_irqsave(&my_lock, flags);:获取自旋锁并保存中断状态,禁用本地中断。
    • spin_unlock_irqrestore(&my_lock, flags);:释放自旋锁并恢复中断状态。

4.3示例代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME "my_char_device"

#define BUFFER_SIZE 64

static atomic_t counter = ATOMIC_INIT(0);
static spinlock_t my_lock;
static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体
static int shared_resource = 0;

static int device_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device opened\n");
    spin_lock_init(&my_lock); // 打开设备的时候 初始化自旋锁
    return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device closed\n");
    return 0;
}

static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset)
{

    // 保护共享资源
    spin_lock(&my_lock);
    shared_resource++;
    printk(KERN_INFO "Shared resource value: %d\n", shared_resource);
    spin_unlock(&my_lock);
   // 模拟中断上下文中使用自旋锁
    unsigned long flags;
    spin_lock_irqsave(&my_lock, flags);
    shared_resource++;
    printk(KERN_INFO "Shared resource value (interrupt context): %d\n", shared_resource);
    spin_unlock_irqrestore(&my_lock, flags);
    return 0;
}

static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset)
{
    char msg[BUFFER_SIZE];

    if (len > BUFFER_SIZE - 1)
    {
        len = BUFFER_SIZE - 1;
    }

    if (copy_from_user(msg, buffer, len))
    {
        return -EFAULT;
    }

    msg[len] = '\0';

    if (strcmp(msg, "inc\n") == 0)
    {
        atomic_inc(&counter);
    }
    else if (strcmp(msg, "dec\n") == 0)
    {
        atomic_dec(&counter);
    }
    else if (sscanf(msg, "add %d\n", &len) == 1)
    {
        atomic_add(len, &counter);
    }
    else if (sscanf(msg, "sub %d\n", &len) == 1)
    {
        atomic_sub(len, &counter);
    }
    else
    {
        return -EINVAL;
    }

    return len;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

static int __init test_init(void)
{
    int retval;
    dev_t dev;

    printk(KERN_INFO "module init success\n");

    // 1. 动态分配主次设备号
    retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        goto fail_alloc_chrdev_region;
    }

    major_number = MAJOR(dev);
    printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));

    // 2. 初始化 cdev 结构体并添加到内核
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev_add;
    }

    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class))
    {
        printk(KERN_ERR "Failed to create class\n");
        retval = PTR_ERR(my_class);
        goto fail_class_create;
    }

    // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!
    my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(my_device))
    {
        printk(KERN_ERR "Failed to create device\n");
        retval = PTR_ERR(my_device);
        goto fail_device_create;
    }

    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;

fail_device_create:
    class_destroy(my_class);
fail_class_create:
    cdev_del(&mydev);
fail_cdev_add:
    unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
    return retval;
}

static void __exit test_exit(void)
{
    dev_t dev = MKDEV(major_number, 0);
    if (my_device)
        device_destroy(my_class, dev);
    if (my_class)
        class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_char_device: module unloaded\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");

4.4测试

使用cat 读取设备,使用dmesg 查看内核消息

[33585.032083] module init success
[33585.035302] major number is: 235, minor number is: 0
[33585.040606] my_char_device: module loaded
[33594.159584] Device opened
[33594.162327] Shared resource value: 1
[33594.165941] Shared resource value (interrupt context): 2
[33594.171320] Device closed
[33607.662150] Device opened
[33607.664859] Shared resource value: 3
[33607.668436] Shared resource value (interrupt context): 4
[33607.673827] Device closed

5.信号量

信号量(Semaphore)是一种用于多线程或多进程同步和互斥的机制。它由荷兰计算机科学家 Edsger Dijkstra 在1960年代提出,用于解决操作系统中的资源共享问题。信号量主要用于控制对共享资源的访问,避免竞争条件和死锁。

5.1基本概念

  1. 信号量的类型
    • 计数信号量(Counting Semaphore):用于控制对多个相同资源的访问。其计数值可以是任何非负整数。
    • 二值信号量(Binary Semaphore):也称为互斥量(Mutex),只允许两种状态(0或1),通常用于保护对单个资源的独占访问。
  2. 基本操作
    • P 操作(Proberen,或称为 down 操作):试图获取信号量。如果信号量的计数值大于0,计数值减1,操作成功;否则,进程将被阻塞,直到信号量的计数值变为正数。
    • V 操作(Verhogen,或称为 up 操作):释放信号量,将计数值加1,并唤醒被阻塞的进程(如果有的话)。
  3. 用途
    • 互斥(Mutual Exclusion):确保一次只有一个进程或线程能够访问共享资源。
    • 同步(Synchronization):协调多个进程或线程的执行顺序,以实现正确的操作顺序。

5.2示例代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#define DEVICE_NAME "my_char_device"

#define BUFFER_SIZE 64

static atomic_t counter = ATOMIC_INIT(0);

static int major_number;
static struct class *my_class = NULL;
static struct device *my_device = NULL;
static struct cdev mydev; // 声明 cdev 结构体



static struct semaphore my_semaphore;  //定义信号量
static int shared_resource = 0;  //共享资源


static int device_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device opened\n");
    // 初始化信号量 设为1  代表互斥,只能有一个进程访问
    sema_init(&my_semaphore, 1);
    return 0;
}

static int device_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "Device closed\n");
    return 0;
}

static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset)
{

    int ret;
    size_t to_copy;

    // 获取信号量
    if (down_interruptible(&my_semaphore)) {
        return -ERESTARTSYS;
    }

    // 模拟文件的读取行为
    if (*offset >= sizeof(shared_resource)) {
        // 文件末尾,返回 0
        up(&my_semaphore);
        return 0;
    }

    // 确定要复制的字节数
    to_copy = min(len, (size_t)sizeof(shared_resource) - *offset);

    // 访问共享资源
    ret = snprintf(buffer, to_copy, "%d\n", shared_resource);

    // 更新偏移量
    *offset += ret;

    // 释放信号量
    up(&my_semaphore);

    return ret;
}

static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset)
{
     char buf[64];
    int new_value;

    // 获取信号量
    if (down_interruptible(&my_semaphore)) {
        return -ERESTARTSYS;
    }

    // 从用户空间复制数据
    if (copy_from_user(buf, buffer, len)) {
        up(&my_semaphore);
        return -EFAULT;
    }

    buf[len] = '\0';
    if (sscanf(buf, "%d", &new_value) == 1) {
        shared_resource = new_value;
    }

    // 释放信号量
    up(&my_semaphore);

    return len;
}

static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

static int __init test_init(void)
{
    int retval;
    dev_t dev;

    printk(KERN_INFO "module init success\n");

    // 1. 动态分配主次设备号
    retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        goto fail_alloc_chrdev_region;
    }

    major_number = MAJOR(dev);
    printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));

    // 2. 初始化 cdev 结构体并添加到内核
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1);
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        goto fail_cdev_add;
    }

    // 3. 创建设备类
    my_class = class_create(THIS_MODULE, "my_class");
    if (IS_ERR(my_class))
    {
        printk(KERN_ERR "Failed to create class\n");
        retval = PTR_ERR(my_class);
        goto fail_class_create;
    }

    // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!
    my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);
    if (IS_ERR(my_device))
    {
        printk(KERN_ERR "Failed to create device\n");
        retval = PTR_ERR(my_device);
        goto fail_device_create;
    }

    printk(KERN_INFO "my_char_device: module loaded\n");

    


    return 0;

fail_device_create:
    class_destroy(my_class);
fail_class_create:
    cdev_del(&mydev);
fail_cdev_add:
    unregister_chrdev_region(dev, 1);
fail_alloc_chrdev_region:
    return retval;
}

static void __exit test_exit(void)
{
    dev_t dev = MKDEV(major_number, 0);
    if (my_device)
        device_destroy(my_class, dev);
    if (my_class)
        class_destroy(my_class);
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "my_char_device: module unloaded\n");
}

module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("Marxist");
MODULE_LICENSE("GPL");

5.3测试

root@imx8qmmek:~/module_test# rmmod kernel_test.ko 
root@imx8qmmek:~/module_test# insmod kernel_test.ko 
root@imx8qmmek:~/module_test# echo 5 > /dev/my_char_device 
root@imx8qmmek:~/module_test# cat /dev/my_char_device 
5

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

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

相关文章

JAVA基础知识点3 (String 和 StringBuffer 以及 StringBuilder 的特点以及区别)

1&#xff0c;String 和 StringBuffer 以及 StringBuilder 的特点 &#xff08;1&#xff09;String的特点&#xff1a;String是final修饰的字符序列是不可改变的&#xff0c; 是字符串常量&#xff0c;一旦初始化就不可以被更改,因此是线程安全的 因为是常量每次对其操作都会…

C++必修:STL之vector的模拟实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 为了让我们更加深入理解vector&#xff0c;接下来我们将模拟实现一个简易版的vect…

龙迅#LT8918适用于TTL/LVDS转MIPIDSI/CSI应用方案,分辨率高达1080P@60HZ,可提供技术支持!

1. 描述 Lontium LT8918 是一款高性能 MIPIDSI/CSI-2 发射器&#xff0c;适用于移动显示面板或相机应用。 LT8918 的 TTL 输入在 SDR 或 DDR 采样下支持 24 位 RGB 和 BT656/1120 视频格式。最大输入像素时钟频率为 SDR 148.5MHz 或 DDR 74.25MHz&#xff0c;适用于1080P60Hz高…

PCL从理解到应用【08】 点云特征 | 法线估计 | 主曲率估计

前言 在PCL中&#xff0c;有多种方法和函数可以用来提取点云特征&#xff0c;本文介绍几何特征。 其中&#xff0c;几何特征主要包括法线估计和主曲率估计。 这些特征能够描述点云表面的几何形状&#xff0c;常用于进一步的点云处理和分析&#xff0c;如配准、分割和物体识别…

为什么 Kubernetes 是现代开发的必备工具

引言 在现代软件开发中&#xff0c;容器已经成为打包和运行应用程序的标准方式。然而&#xff0c;在生产环境中&#xff0c;管理这些运行中的容器并确保服务的高可用性和稳定性并不是一件容易的事。比如&#xff0c;当一个容器发生故障时&#xff0c;需要快速启动另一个容器来代…

C++ std::atomic和std::mutex

C11 引入了两个重要的同步机制用于多线程编程&#xff1a;std::atomic 和 std::mutex。它们各自适用于不同的并发控制需求&#xff0c;并在实现和使用上有很大的不同。 1. 目的和用途 std::atomic: 设计目的&#xff1a;为原子操作提供支持&#xff0c;保证对变量的操作&#…

Python试讲

Python试讲 导语Python简介Python及其特点如何使用Python Python与计算计算变量 导语 本次试讲内容如下&#xff1a;Python简介与使用&#xff0c;Python与基本运算 辅助教材为 《趣学Python编程》和《Python编程从入门到实践》 Python简介 Python是目前入门最简单最好学的…

FVM安装及配置

一、下载fvm 包 git&#xff1a;Release fvm 3.1.7 leoafarias/fvm GitHub 解压到本地文件夹&#xff0c;然后添加环境变量 管理员模式打开cmd&#xff0c;查看是否成功 fvm --version 二、安装Dart SDK 下载Dart SDK&#xff1a;Dart for Windows 三、安装GIT 四、指定…

python3.10安装geopandans实战笔记

1.geopandans安装所需软件库版本 python3.10 GDAL-3.4.3-cp310-cp310-win_amd64.whl【手动下载】 Fiona-1.8.21-cp310-cp310-win_amd64.whl【手动下载】 shapely-2.0.2-cp310-cp310-win_amd64.whl【手动下载】 pyproj 手动下载地址&#xff1a;https://download.csdn.net/down…

range和enumerate的区别

range通过索引遍历元素&#xff0c;属于间接访问。 enumerate直接遍历元素&#xff0c;效率稍高&#xff0c;代码简洁。range输出的是元素的索引。 enumerate输出的是元素的索引和元素。 参考&#xff1a;range与enumerate的区别_enumerate和range-CSDN博客

(一)springboot2.7.6集成activit5.23.0之集成引擎

集成引擎很简单。 首先是创建springboot项目然后引入相关依赖就完成了。pom.xml如下&#xff1a; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.…

鸿蒙应用框架开发【媒体库视频】 UI框架

媒体库视频 介绍 本示例使用Video组件展示了视频组件的基本功能&#xff0c;包括视频组件化&#xff0c;全屏化&#xff0c;窗口化&#xff0c;上下轮播视频等。 效果预览 使用说明&#xff1a; 进入首页点击播放按键&#xff1b;点击视频播放按钮&#xff0c;视频开始播放…

CTFHub技能树web——XSS——DOM反射

根据框里的内容 直接右键查看网页源代码 看到 了其闭合方式 然后去网页测试一下alert&#xff08;1&#xff09;反射 ;</script><script>alert(1)</script> 看到 确实存在 去xssaq.cn 创建一个项目 把src粘过来 在第一个输入框中 再将返回回来的url 复…

NAND行业回归盈利:AI与云存储需求驱动

市场概览 根据Yole Group于2024年6月25日发布的市场报告&#xff0c;经过五个季度的亏损之后&#xff0c;NAND闪存行业在2024年第一季度&#xff08;1Q24&#xff09;实现了盈利回归。这一转变主要得益于企业级固态硬盘&#xff08;SSD&#xff09;领域的强劲需求增长&#xf…

【教程】Python语言的地球科学常见数据——海温数据-NOAA OISST 的处理

NOAA 1/4每日最佳内插海面温度&#xff08;OISST&#xff09;是一个长期的气候数据记录&#xff0c;它将来自 不同平台&#xff08;卫星、船舶、浮标和 Argo 浮标&#xff09;的观测数据纳入一个定期的全球网格。该 数据集经过插值处理&#xff0c;以填补网格上的空白&#x…

创意无限:11个设计圈热议的UI设计灵感网站集锦

无论你是一个经验丰富的UI设计师还是一个新的UI设计师&#xff0c;拥有一些高质量、可靠的UI设计网站灵感库都能加速你的设计过程。借助灵感资源&#xff0c;您可以更快、更有效地启动该项目。与此同时&#xff0c;优秀的UI设计网站也能帮助您探索新的设计解决方案&#xff0c;…

3DsMax展开管道UV,展开圆柱体UV,展开带有拐弯部分的UV

效果 3dsmax展开管道的UV 创建管道 创建样条线 制作弯曲部分 打开样条线先的顶点&#xff0c;选择样条线的顶点&#xff0c;不选中&#xff0c;开头和结尾的顶点&#xff0c;点击圆角 &#xff0c;鼠标移动到顶点上&#xff0c;左键点击顶点然后向上拖拽。 设置样条线可渲染…

隐私安全测试:保护您的数字世界

大家好&#xff0c;我是一名_全栈_测试开发工程师&#xff0c;已经开源一套【自动化测试框架】和【测试管理平台】&#xff0c;欢迎大家关注我&#xff0c;和我一起【分享测试知识&#xff0c;交流测试技术&#xff0c;趣聊行业热点】。 一、引言 在当今数字化的时代&#xff0…

安装Docker以及安装过程中的错误解决

一、纯享版教程&#xff0b;操作截图 环境&#xff1a;centOs 7 FinalShell &#xff01;&#xff01;&#xff01;此教程针对第一次安装docker的友友&#xff0c;如果已经安装过且报错的朋友&#xff0c;请移步报错合集。 1.卸载旧版本&#xff08;无论是否安装过都建议执…

C++11深度剖析

目录 &#x1f680; 前言&#xff1a;C11简介 一&#xff1a; &#x1f525; 统一的列表初始化&#x1f4ab; 2.1 &#xff5b;&#xff5d;初始化 二&#xff1a; &#x1f525; std::initializer_list &#x1f4ab; 2.1 std::initializer_list是什么类型&#x1f4ab; 2.2 s…