理解并使用 Linux 内核的字符设备

理解并使用 Linux 内核的字符设备

1. 引言

1.1 什么是字符设备

字符设备是 Linux 中的一类设备,支持以字符为单位进行数据传输。与块设备不同,字符设备不需要缓冲区,即数据是逐字节直接传递的。典型的字符设备包括串口、键盘、鼠标、伪终端等。

用个简单的比喻:字符设备像流水线,生产(写)和消费(读)可以同时进行且无需额外的仓库(缓冲区)。

1.2 字符设备的用途与典型应用场景

字符设备的主要用途是与硬件直接交互,比如读取传感器数据或控制某些外设。典型场景包括:

  • 提供用户空间与硬件交互的接口。
  • 模拟设备,用于调试或测试。
  • 创建自定义的和应用层通信的方法。

1.3 字符设备的特点(与块设备的对比)

特点字符设备块设备
数据传输单位字符(逐字节)块(通常为 512 字节或更大)
是否有缓冲区无(直接传递)
典型场景键盘、串口磁盘、U盘
接口file_operations 的方法实现I/O 调度层支持

2. 编写一个简单的字符设备

下文所有代码都基于6.9.1内核

2.1 示例代码及功能介绍

以下是一个简单的字符设备驱动示例,功能是从用户空间读取数据并将其回显。此代码展示了字符设备的核心操作流程,适合入门学习。

创建main.c文件如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h> // 用于copy_to_user和copy_from_user#define DEVICE_NAME "simple_char_device" // 设备名称static int major;                        // 主设备号
static char message[256] = {0};          // 缓存区,用于存储用户写入的数据
static int open_count = 0;               // 打开设备的次数计数器// 打开设备
static int device_open(struct inode *inode, struct file *file) {open_count++;printk(KERN_INFO "Device opened %d time(s)\n", open_count);return 0; // 成功返回0
}// 读取设备数据到用户空间
static ssize_t device_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {size_t message_len = strlen(message); // 获取消息长度if (*offset >= message_len) // 如果偏移量超出消息长度,返回0表示EOFreturn 0;if (len > message_len - *offset) // 如果读取长度超过剩余数据,截取剩余部分len = message_len - *offset;if (copy_to_user(buffer, message + *offset, len)) // 数据拷贝到用户空间return -EFAULT; // 失败返回错误码*offset += len; // 更新偏移量return len;     // 返回读取的字节数
}// 写入数据到设备
static ssize_t device_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {if (len > sizeof(message) - 1) // 检查写入数据是否超出缓冲区return -EINVAL; // 无效参数错误memset(message, 0, sizeof(message)); // 清空缓冲区if (copy_from_user(message, buffer, len)) // 从用户空间拷贝数据return -EFAULT; // 失败返回错误码message[len] = '\0'; // 确保字符串以空字符结尾printk(KERN_INFO "Received: %s\n", message); // 打印接收到的数据return len; // 返回写入的字节数
}// 释放设备(关闭)
static int device_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0; // 成功返回0
}// 定义文件操作结构
static struct file_operations fops = {.open = device_open,       // 打开设备.read = device_read,       // 读取设备.write = device_write,     // 写入设备.release = device_release, // 释放设备
};// 模块初始化函数
static int __init char_device_init(void) {// 动态注册字符设备,获取主设备号major = register_chrdev(0, DEVICE_NAME, &fops);if (major < 0) {printk(KERN_ALERT "Failed to register device\n");return major; // 返回错误码}printk(KERN_INFO "Registered char device with major number %d\n", major);return 0; // 成功返回0
}// 模块卸载函数
static void __exit char_device_exit(void) {unregister_chrdev(major, DEVICE_NAME); // 注销字符设备printk(KERN_INFO "Unregistered char device\n");
}module_init(char_device_init); // 指定初始化函数
module_exit(char_device_exit); // 指定卸载函数MODULE_LICENSE("GPL"); // 模块许可声明
MODULE_AUTHOR("Your Name"); // 模块作者
MODULE_DESCRIPTION("A simple char device driver"); // 模块描述

Makefile文件如下

obj-m += main.oall:# 使用内核源码路径编译模块make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:# 清理编译生成的文件make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

2.2 分步解析示例代码

  1. 设备号分配

    • 使用register_chrdev(0, DEVICE_NAME, &fops)动态分配主设备号,并将其绑定到设备名称。
    • 注册失败时返回负值,通常需要打印错误信息以便调试。
  2. 定义file_operations结构

    • file_operations是字符设备的核心结构,用于描述字符设备的操作行为:
      • .open:在用户空间调用open时执行。
      • .read:用户调用read读取设备数据时执行。
      • .write:用户调用write写入设备数据时执行。
      • .release:在设备被关闭时调用。
  3. 与用户空间交互

    • copy_to_user:将内核中的数据拷贝到用户空间,需检查是否返回错误。
    • copy_from_user:将用户空间数据拷贝到内核,需确保长度合法。
    • 使用这些函数的原因是内核和用户空间的内存不共享,直接访问可能导致非法访问错误。
  4. 设备日志输出

    • 使用printk打印日志信息,有助于了解设备运行状态。
    • 日志可通过dmesg命令查看。

2.3 测试字符设备

我们可以通过以下步骤测试该字符设备:

  1. 编译并加载模块
    • 使用make编译模块
    • 使用sudo insmod main.ko命令加载模块。
  2. 创建设备节点
    • 查看主设备号. 使用sudo dmesg | grep major命令, 或者cat /proc/devices | grep simple_char_device
    • 创建设备: sudo mknod /dev/simple_char_device c <major_number> 0
  3. 测试设备
    • 使用echo写入数据:echo "Hello" | sudo tee /dev/simple_char_device
    • 使用cat读取数据:cat /dev/simple_char_device
  4. 卸载模块
    • 使用sudo rmmod main.ko命令卸载模块。

3. 深入解析 Linux 内核中的字符设备

字符设备是 Linux 驱动开发中最基础的设备类型之一。通过字符设备,用户可以实现对硬件的读写操作。本节将探讨创建字符设备的不同方式、设备号的分配方法,以及 file_operations 的作用和实现细节。

3.1 创建字符设备的两种方式

方式一:使用 register_chrdev

register_chrdev 是一种简单的字符设备注册方式。通过调用该函数,可以快速注册一个字符设备并关联 file_operations 接口。

示例

int major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {printk(KERN_ALERT "Failed to register device\n");return major;
}
printk(KERN_INFO "Device registered with major number %d\n", major);

特点

  • 操作简单,适合快速开发和调试。
  • 不需要显式创建 struct cdev 对象。
  • 功能较有限,推荐用于较简单的场景。

方式二(推荐):使用 cdevcdev_add

cdev 是内核提供的字符设备核心数据结构,使用该方式注册字符设备更加灵活且符合现代驱动开发规范。

步骤

  1. 初始化字符设备对象:cdev_init
  2. 分配设备号:alloc_chrdev_region
  3. 将设备添加到内核:cdev_add

示例

struct cdev my_cdev;
dev_t dev_num;// 动态分配设备号
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
major = MAJOR(dev_num);// 初始化字符设备
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;// 注册到内核
if (cdev_add(&my_cdev, dev_num, 1) < 0) {printk(KERN_ALERT "Failed to add cdev\n");unregister_chrdev_region(dev_num, 1);return -1;
}
printk(KERN_INFO "Device registered with major number %d\n", major);

特点

  • 适合复杂设备驱动程序的开发。
  • 提供更细粒度的控制,例如支持同时创建多个设备, 配合device_create自动创建设备等。

3.2 分配设备号:静态与动态分配

设备号由 主设备号次设备号 组成。主设备号标识驱动程序类型,次设备号标识具体的设备实例。主次设备号加在一起就可以唯一标识一个具体的设备。

静态分配

开发者可以直接指定设备号。这种方式简单,但可能与其他驱动冲突。

示例

#define MAJOR_NUM 240
register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);

优缺点

  • 优点:便于调试和定位。
  • 缺点:设备号固定,可能与其他模块冲突。

动态分配

动态分配通过内核自动分配主设备号,推荐在现代开发中使用。

使用 register_chrdev 分配设备号

int major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) {printk(KERN_ALERT "Failed to register device\n");return major;
}
printk(KERN_INFO "Device registered with major number %d\n", major);

使用 alloc_chrdev_region 分配设备号

dev_t dev_num;
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
major = MAJOR(dev_num);

动态分配的设备号可以通过 /proc/devices 查看。
以下是补充和完善后的 3.3 理解 file_operations 与字符设备的交互原理 章节内容。包括技术细节的完善和错误的修正,同时以逻辑清晰的方式组织内容:

3.3 理解 file_operations 与字符设备的交互原理

file_operations 结构体定义了一组操作回调函数,用于描述字符设备如何响应来自用户空间的操作请求。这些回调函数实现了设备驱动程序与用户空间之间的接口,覆盖了文件操作的各个方面(如打开、读写、关闭等)。为了深入理解字符设备如何通过 file_operations 实现交互,我们需要从设备号与文件系统的关系、设备的注册过程,以及文件操作的调用链三方面入手。

1. 设备号与文件系统的关系
  • 设备号

    • 每个字符设备通过设备号唯一标识,由主设备号 (major) 和次设备号 (minor) 组成。
      • 主设备号:标识负责管理该类设备的驱动程序。
      • 次设备号:区分同一驱动程序下的不同设备实例。
  • 设备节点

    • 字符设备在文件系统中表现为特殊文件,称为设备节点(例如 /dev/my_device)。设备节点的 inode 结构包含了对应的设备号。
    • 用户空间程序通过系统调用(如 open)访问设备节点,内核通过解析设备号找到对应的驱动程序,并最终调用 file_operations 中的回调函数。
2. 字符设备的注册与绑定

为了让字符设备能被内核管理并提供给用户空间使用,驱动程序需要完成设备的注册和 file_operations 的绑定。这个过程分为以下步骤:

  1. 设备号的分配

    • 使用 alloc_chrdev_region 动态分配主设备号和次设备号范围。
    • 或者,使用 register_chrdev_region 手动指定设备号范围。
    dev_t dev_num;
    alloc_chrdev_region(&dev_num, 0, 1, "my_device");
    
  2. 初始化 cdev 结构

    • 每个字符设备通过 struct cdev 表示,其核心字段 ops 指向设备驱动的 file_operations
    • 使用 cdev_init 初始化 struct cdev
    struct cdev my_cdev;
    cdev_init(&my_cdev, &my_fops);
    
  3. cdev 添加到内核

    • 使用 cdev_add 将设备添加到内核,建立设备号与 cdev 的映射。
    • cdev_add 会将设备号插入到 kobj_map 结构中,以便后续通过设备号快速找到对应的 cdevfile_operations
    cdev_add(&my_cdev, dev_num, 1);
    
  4. 创建设备节点

    • 使用 mknod 命令创建设备节点,或者通过用户空间的设备管理工具(如 udev)自动完成。
3. 文件操作的调用链

以下是用户空间程序调用字符设备时的调用链和关键步骤:

用户调用 open 系统调用
  1. 用户程序调用 open("/dev/my_device", ...)
  2. 内核通过文件系统找到 /dev/my_device 对应的 inode,并从中获取设备号(主设备号和次设备号)。
内核解析设备号并找到 cdev

在以前老的内核中, 内核通过主设备号,从 chrdevs 哈希表(chrdevs[CHRDEV_MAJOR_HASH_SIZE])中找到注册的字符设备。现在已经弃用了这种方式。现在使用 chrdev_open 函数,通过次设备号在 kobj_map 中查找对应的 struct cdev

  static int chrdev_open(struct inode *inode, struct file *file){struct cdev *p = kobj_lookup(cdev_map, inode->i_rdev, NULL);if (!p)return -ENODEV;file->f_op = p->ops;if (file->f_op->open)return file->f_op->open(inode, file);return 0;}
绑定 file_operations
  1. 内核通过 struct cdevops 字段获取对应的 file_operations 结构,并初始化 file->f_op

  2. 内核调用 file_operations 中的 open 回调函数,完成设备打开。

    调用链总结:

    用户程序 -> open() -> vfs_open() -> chrdev_open() -> cdev->ops->open()
    
小结
  • file_operations 是字符设备的操作接口,通过一系列回调函数实现用户空间与设备的交互。
  • 字符设备通过主设备号和次设备号唯一标识,并通过 cdev 结构与 file_operations 绑定。
  • 内核通过 chrdev_openkobj_map 将设备号解析为 file_operations,从而实现了用户空间系统调用与设备驱动的衔接。

4. 创建设备节点

设备节点是用户空间与内核设备驱动程序交互的入口。在 Linux 中,字符设备需要一个设备节点(如 /dev/simple_char_device)供用户访问。

4.1 用户手动创建设备节点

设备节点可以通过 mknod 命令手动创建。
语法如下:

sudo mknod /dev/simple_char_device c <major> <minor>
参数说明:
  • /dev/simple_char_device:设备节点的路径。
  • c:设备类型,c 表示字符设备,b 表示块设备。
  • <major>:主设备号,用于标识字符设备驱动程序。
  • <minor>:次设备号,用于区分驱动程序中的不同设备实例。
示例:

假设主设备号为 240,次设备号为 0:

sudo mknod /dev/simple_char_device c 240 0
sudo chmod 666 /dev/simple_char_device  # 设置读写权限

用户空间通过设备节点与字符设备交互。例如:

echo "Hello" > /dev/simple_char_device
cat /dev/simple_char_device
缺点:
  • 手动创建节点不方便,且设备号可能在系统重启或驱动加载时发生变化。

4.2 使用内核代码配合 udev 动态创建设备节点

现代 Linux 系统中,推荐通过内核和 udev 配合实现设备节点的自动创建。内核代码通过创建设备类和设备对象,通知 udev 守护进程自动创建设备节点。

核心函数:
  1. class_create:创建设备类,在 /sys/class 下注册。
  2. device_create:为设备类添加设备,在 /sys/class/<class_name>/<device_name> 下注册。
完整代码示例:

以下是一个字符设备驱动中动态创建设备节点的示例:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/version.h>#define DEVICE_NAME "simple_char_device"
#define CLASS_NAME "simple_char_class"static int major;                      // 主设备号
static struct class *char_class;       // 设备类
static struct device *char_device;     // 设备对象// 文件操作函数
static int dev_open(struct inode *inode, struct file *file) {printk(KERN_INFO "Device opened\n");return 0;
}static ssize_t dev_read(struct file *file, char __user *buffer, size_t len, loff_t *offset) {char *msg = "Hello from kernel!";size_t msg_len = strlen(msg);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 dev_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset) {printk(KERN_INFO "Data written to device\n");return len;
}static int dev_release(struct inode *inode, struct file *file) {printk(KERN_INFO "Device closed\n");return 0;
}// 文件操作结构体
static struct file_operations fops = {.open = dev_open,.read = dev_read,.write = dev_write,.release = dev_release,
};static int __init char_init(void) {// 动态分配主设备号major = register_chrdev(0, DEVICE_NAME, &fops);if (major < 0) {printk(KERN_ALERT "Failed to register char device\n");return major;}printk(KERN_INFO "Registered char device with major number %d\n", major);// 创建设备类#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)char_class = class_create(THIS_MODULE, CLASS_NAME);
#elsechar_class = class_create(CLASS_NAME);
#endifif (IS_ERR(char_class)) {unregister_chrdev(major, DEVICE_NAME);printk(KERN_ALERT "Failed to create class\n");return PTR_ERR(char_class);}// 创建设备char_device = device_create(char_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);if (IS_ERR(char_device)) {class_destroy(char_class);unregister_chrdev(major, DEVICE_NAME);printk(KERN_ALERT "Failed to create device\n");return PTR_ERR(char_device);}printk(KERN_INFO "Device created successfully\n");return 0;
}static void __exit char_exit(void) {device_destroy(char_class, MKDEV(major, 0)); // 销毁设备class_destroy(char_class);                  // 销毁类unregister_chrdev(major, DEVICE_NAME);      // 注销设备号printk(KERN_INFO "Char device unregistered\n");
}module_init(char_init);
module_exit(char_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple char device with auto node creation");
关键步骤:
  1. 动态分配主设备号:使用 register_chrdev
  2. 创建设备类class_create 创建设备类,在 /sys/class 下可见。
  3. 创建设备对象device_create 将设备注册到 /sys/class/<class_name>
  4. 加载驱动时创建设备节点:udev 守护进程会在 /dev 中自动创建设备节点。
udev 自动创建节点的工作原理:
  • 内核通过 class_createdevice_create/sys/class 添加设备信息。
  • udev 监听 /sys 文件系统的事件,发现新设备时根据设备属性规则自动创建节点。

4.3 查看设备节点信息

查看设备类和设备信息:

加载驱动后,可以通过以下命令查看设备信息:

ls /sys/class/simple_char_class
查看设备号:

通过 dmesg 日志获取主设备号和次设备号:

dmesg | grep "Registered char device"

4.4 小结

  1. 手动创建:通过 mknod 创建设备节点,但需要指定设备号,手动管理麻烦。
  2. 自动创建:结合 class_createdevice_create 配合 udev,实现设备节点的动态创建,现代驱动开发的推荐方式。

通过动态分配设备号和自动创建设备节点,字符设备驱动的加载、管理和用户访问变得更加简洁和高效。

5. 总结

本文介绍了Linux内核中的字符设备,这是一种支持逐字节数据传输的设备类型,与块设备相比不需要缓冲区。字符设备广泛用于直接硬件交互,如读取传感器或控制外设。文中详细描述了编写简单字符设备驱动的过程,包括定义file_operations结构来处理打开、读写和关闭操作,以及使用register_chrdev动态分配主设备号。

进一步探讨了创建字符设备的不同方法,强调了使用cdev结构和cdev_add函数的优势,这种方式提供了更灵活的控制,适合复杂场景。同时讨论了设备号静态与动态分配的区别,指出动态分配是现代开发中的推荐做法。对于file_operations的作用机制,文章解释了它如何作为接口实现用户空间与字符设备之间的交互,并深入分析了从用户调用到内核响应的整个过程。

最后,针对设备节点的创建,提出了两种方式:一是用户手动通过mknod命令创建;二是利用内核代码配合udev规则自动创建,后者在现代系统中更为常见且便捷。

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

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

相关文章

五、Swagger 介绍(Flask+Flasgger的应用)

Swagger 介绍 0. 引言1. Swagger 介绍2. Flasgger 介绍3. Flasgger效果3.1 原始flask代码3.2 转化成Flasgger形式3.3 使用Try it out调试3.4 多个url接口自动生成和调试 4. 使用教程4.1 使用 docstrings 作为规范4.2 使用外部 YAML 文件4.3 使用 Python 字典作为原始规范 5. 和…

LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测

LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测 目录 LSTM-SVM时序预测 | Matlab基于LSTM-SVM基于长短期记忆神经网络-支持向量机时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.LSTM-SVM时序预测 | Matlab基于LSTM…

【MySQL】十三,关于MySQL的全文索引

MySQL的全文索引用于搜索文本中的关键字&#xff0c;类似于like查询。 演示 建表 CREATE TABLE demo (id INT(11) NOT NULL,name CHAR(30) NOT NULL,age INT(11) NOT NULL,info VARCHAR(255),primary key(id),fulltext index futxt_idx_info(info) );此表的默认存储引擎为In…

数据可视化echarts学习笔记

目录&#xff0c;介绍 知识储备 一端操作&#xff0c;多端联动的效果&#xff08;开启了多个网页&#xff0c;操作一端&#xff0c;多个网页的效果会跟着改变&#xff09; cmd命令控制面板返回上一级或上上级 在当前目录打开文件&#xff1a; cd 文件名 在Windows命令提示符&am…

NS3学习——tcpVegas算法代码详解(2)

NS3学习——tcpVegas算法代码详解&#xff08;1&#xff09;-CSDN博客 目录 4.TcpVegas类中成员函数 (5) CongestionStateSet函数 (6) IncreaseWindow函数 1.检查是否启用 Vgas 2.判断是否完成了一个“Vegas 周期” 2.1--if&#xff1a;判断RTT样本数量是否足够 2.2--e…

在 CentOS 8 系统上安装 Jenkins 的全过程

一、前言 我是一个前端开发&#xff0c;需要频繁将编写的前端系统打包更新到公司的linux服务器&#xff0c;觉得这种工作纯体力活&#xff0c;有时候太浪费时间&#xff0c;以前用过别人搭建的Jenkins可以很好的解决这个问题。 Jenkins 是一款流行的开源持续集成和持续交付&a…

Mac上Stable Diffusion的环境搭建(还算比较简单)

https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon AI兴起的速度是真的快&#xff0c;感觉不了解点相关的东西都要与时代脱节了&#xff0c;吓得我赶紧找个AIGC看看能不能实现我艺术家的人梦想&#xff08;绷不住了&#xff09; 我…

瑞吉外卖项目学习笔记(九)套餐列表分页查询、新增套餐、图片上传和下载

瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现 瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现 瑞吉外卖项目学习笔记(三)过滤器实现登录校验、添加员工、分页查询员工信息 瑞吉外卖项目学习笔记(四)TableField(fill FieldFill.INSERT)公共字…

VMware Workstation虚拟机网络模式

做虚拟机和宿主机互ping实验时&#xff0c;除了要提前配置好网段、ip等信息&#xff0c;还要把宿主机、虚拟机的防火墙关闭&#xff01; 首先说一下VMware的几种虚拟交换机。 VMnet0&#xff1a;用于虚拟桥接网络下的虚拟交换机。 VMnet1&#xff1a;用于虚拟Host-Only网络下…

UDP传输层通信协议详解

引言 在计算机网络通信的广阔天地中&#xff0c;传输层协议扮演着至关重要的角色。它们负责在网络中的两个终端之间建立、管理和终止数据传输。在众多传输层协议中&#xff0c;UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;以其独特的特性和应…

Node.js 工具:在 Windows 11 中配置 Node.js 的详细步骤

一、概述 记录时间 [2024-12-25] 本文讲述如何在 Windows 11 中进行 Node.js 工具的安装和配置。 以下是详细的步骤和说明。 二、安装 Node.js 1. 官网下载 通过官网&#xff0c;下载 Node.js&#xff0c;上面有好几种下载方式&#xff0c;文中下载的是 zip 压缩包。 如图&…

Chrome被360导航篡改了怎么改回来?

一、Chrome被360导航篡改了怎么改回来&#xff1f; 查看是否被360主页锁定&#xff0c;地址栏输入chrome://version&#xff0c;看命令行end后面&#xff08;蓝色部分&#xff09;&#xff0c;是否有https://hao.360.com/?srclm&lsn31c42a959f 修改步骤 第一步&#xff1a…

Mysql 查询性能调优总结

一、查询分析性能的相关配置 1.1 配置显示查询性能的参数 在 MySQL 中&#xff0c;SHOW PROFILES 命令用于显示最近的查询性能概况&#xff0c;帮助你分析哪些查询比较耗时。 show profiles; 需要启用参数 profiling&#xff0c;才能使用上述功能&#xff0c;其相关参数设置…

python中使用selenium执行组合快捷键ctrl+v不生效问题

在执行ctrlv进行粘贴时&#xff0c;绑定一个页面上的元素对象&#xff08;无论元素对象是否是引用过期或者是粘贴的目标文本区&#xff0c;但前提需要粘贴的目标文本区获取焦点&#xff09;执行ctrlv后可以生效。执行粘贴组合快捷键&#xff08;ctrlv&#xff09;的示例代码 se…

C++模板:编译时模拟Duck Typing

C泛型与多态&#xff08;4&#xff09;: Duck Typing - 简书 James Whitcomb Riley在描述这种is-a的哲学时&#xff0c;使用了所谓的鸭子测试&#xff08;Duck Test&#xff09;: 当我看到一只鸟走路像鸭子&#xff0c;游泳像鸭子&#xff0c;叫声像鸭子&#xff0c;那我就把它…

【求职面试】驾照的种类

大型客车 A1 大型载客汽车 A3、B1、B2、C1、C2、C3、C4、M 牵引车 A2 重型、中型全挂、半挂汽车列车 B1、B2、C1、C2、C3、C4、M 城市公交车 A3 核载10人以上的城市公共汽车 C1、C2、C3、C4 中型客车 B1 中型载客汽车&#xff08;10人以上、19人以下&#xff09; C1、C2、C3…

PyQt实战——使用python提取JSON数据(十)

系类往期文章&#xff1a; PyQt5实战——多脚本集合包&#xff0c;前言与环境配置&#xff08;一&#xff09; PyQt5实战——多脚本集合包&#xff0c;UI以及工程布局&#xff08;二&#xff09; PyQt5实战——多脚本集合包&#xff0c;程序入口QMainWindow&#xff08;三&…

RAG实战:构建基于本地大模型的智能问答系统

RAG实战&#xff1a;构建基于本地大模型的智能问答系统 引言 在当今AI快速发展的时代&#xff0c;如何构建一个既智能又可靠的问答系统是一个重要课题。本文将介绍如何使用RAG&#xff08;检索增强生成&#xff09;技术&#xff0c;结合本地大模型&#xff0c;构建一个高效的智…

OAuth 2.0

简介 OAuth 是一种开放标准的授权协议或框架&#xff0c;它提供了一种安全的方式&#xff0c;使第三方应用程序能够访问用户在其他服务上的受保护资源&#xff0c;而无需共享用户的凭证&#xff08;如用户名和密码&#xff09;。OAuth 的核心思想是通过“授权令牌”来代替直接…

IntelliJ IDEA 远程调试

IntelliJ IDEA 远程调试 在平时开发 JAVA 程序时&#xff0c;在遇到比较棘手的 Bug 或者是线上线下结果不一致的情况下&#xff0c;我们会通过打 Log 或者 Debug 的方式去定位并解决问题&#xff0c;两种方式各有利弊&#xff0c;今天就简要介绍下如何通过远程 Debug 的情况下…