当前位置: 首页 > news >正文

Linux内核编程(二十一)USB驱动开发-键盘驱动

一、驱动类型

        USB 驱动开发主要分为两种:主机侧的驱动程序和设备侧的驱动程序。一般我们编写的都是主机侧的USB驱动程序。

        主机侧驱动程序用于控制插入到主机中的 USB 设备,而设备侧驱动程序则负责控制 USB 设备如何与主机通信。由于设备侧驱动程序通常与设备功能紧密相关,因此常常被称为 USB gadget 驱动程序。USB gadget 驱动程序的作用是定义如何通过 USB 协议栈与主机端进行通信,确保设备能够正确响应主机的请求。

        在 USB 系统中,主机侧和设备侧有各自不同的控制器。主机侧使用的是主机控制器(Host Controller),它负责在主机和设备之间建立数据传输连接,管理设备的连接和断开。设备侧则使用 USB 设备控制器(UDC),它用于控制设备如何响应主机的请求和进行数据传输。主机控制器和设备控制器分别在各自的系统中充当着至关重要的角色,确保 USB 设备和主机之间的有效通信。

        这两种驱动程序通过操作系统中的 USB 子系统进行协同工作,提供了广泛的设备支持,从键盘、鼠标等简单外设到复杂的存储设备、音频设备等多种类型的 USB 设备。

二、USB传输介质-URB

        USB通信和IIC类似,都要先构建数据包,然后使用对应的API函数进行传输。URB就是USB传输的介质。URB(USB请求块)是Linux内核中用于管理USB数据传输的结构体。在Linux中,USB数据传输的核心就是通过URB来进行的,它相当于I2C中的数据包封装,承担着数据传输的“容器”角色。URB用于描述一次USB传输的请求,包括传输方向、数据长度、目标端点等信息。

1. 根据数据传输的方式和协议类型,URB有不同的类型。

(1)控制传输URB:用于管理设备控制请求,如设备的初始化、配置等。使用usb_fill_control_urb()来填充控制传输的URB。

(2)批量传输URB:用于较大数据量的传输,通常用于数据的读取和写入。使用usb_fill_bulk_urb()来填充批量传输URB。

(3)等时传输URB:用于对实时性要求较高的传输,如音频、视频流等。使用usb_fill_int_urb()来填充等时传输URB。

(4)中断传输URB:用于短数据的周期性传输,如键盘、鼠标等设备。使用usb_fill_int_urb()来填充中断传输URB。

2. 结构体如下所示。

struct urb {struct list_head urb_list;          // 用于管理URB队列的链表unsigned int pipe;                  // 传输通道,即端点void *context;                      // 用户定义的上下文,用于回调函数中传递信息unsigned char *transfer_buffer;     // 数据缓冲区指针,指向传输数据的内存dma_addr_t transfer_dma;            // 用于DMA传输的物理地址unsigned int transfer_flags;        // 标志,指示URB的某些属性(例如同步、异步等)unsigned int status;                // 传输状态,成功或失败unsigned int actual_length;         // 实际传输的字节数unsigned int number_of_packets;     // 分包传输时的数据包数unsigned int timeout;               // 传输超时时间(毫秒)unsigned int start_frame;           // 传输开始的帧号unsigned int interval;              // 传输间隔时间(仅对中断传输有效)unsigned char *setup_packet;        // 指向控制传输的请求包(如果是控制传输时)struct urb *next;                   // 下一个URB,供链表使用unsigned int transfer_buffer_length; // 缓冲区的长度(即传输数据的最大字节数)struct usb_device *dev;             // USB设备指针,指向传输目标设备void (*complete)(struct urb *urb);   // 传输完成后的回调函数struct mutex *lock;                 // 用于同步URB访问的互斥锁unsigned int pipe_flags;            // 端点标志,描述端点的类型和特性
};

3. 相关API。

(1)构建URB对象。

        返回一个指向分配的URB对象的指针。如果分配失败,返回 NULL

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
/*
iso_packets:表示如果URB是用于等时传输,这个参数指定URB包含的数据包数量。如果不是等时传输,此参数应为0。
mem_flags:内存分配标志,通常使用 GFP_KERNEL 来分配内存。
*/

(2)释放一个URB对象。 

void usb_free_urb(struct urb *urb);

(3)填充控制传输URB。

void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,unsigned int pipe, unsigned char *setup_packet,void *transfer_buffer, int buffer_length,usb_complete_t complete_fn, void *context);
/*urb:要填充的URB对象。dev:目标USB设备。pipe:USB管道(端点),可以通过usb_sndctrlpipe()和usb_rcvctrlpipe()等函数获取。setup_packet:指向控制请求的setup包指针,包含请求的控制信息(如请求码、值、索引等)。transfer_buffer:指向传输数据缓冲区的指针。如果是控制传输的输入数据,数据会存储在这            里;输出数据也通过此缓冲区发送。buffer_length:传输缓冲区的长度。complete_fn:传输完成后的回调函数。context:用于回调函数的用户定义的上下文数据。可以在回调函数中使用。
*/

 (4)填充批量传输URB。

void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,unsigned int pipe, void *transfer_buffer,int buffer_length, usb_complete_t complete_fn,void *context);
/*urb:要填充的URB对象。dev:目标USB设备。pipe:USB管道(端点),可以通过usb_sndbulkpipe()和usb_rcvbulkpipe()等函数获取。transfer_buffer:指向传输数据缓冲区的指针。buffer_length:传输缓冲区的长度。complete_fn:传输完成后的回调函数。context:用于回调函数的用户定义的上下文数据。
*/

 (5)填充中断传输URB。

void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,unsigned int pipe, void *transfer_buffer,int buffer_length, usb_complete_t complete_fn,void *context, unsigned int interval);
/*urb:要填充的URB对象。dev:目标USB设备。pipe:USB管道(端点),可以通过usb_sndintpipe()和usb_rcvintpipe()等函数获取。transfer_buffer:指向传输数据缓冲区的指针。buffer_length:传输缓冲区的长度。complete_fn:传输完成后的回调函数。context:用于回调函数的用户定义的上下文数据。interval:传输间隔,单位为帧(适用于中断传输)。
*/

 (6)提交URB进行传输。

        如果提交成功,返回 0;如果失败,返回一个负数错误码。

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
/*urb:要提交的URB对象。mem_flags:内存分配标志,通常使用 GFP_KERNEL。
*/

(7)取消一个URB的传输。

void usb_kill_urb(struct urb *urb);

三、USB主机侧驱动开发(键盘驱动)

1. 操作流程。

(1)编写USB驱动框架。

(2)完善probe函数。

(3)在退出函数中释放掉内存以及相关注销。

2. 编写USB驱动框架。

①在函数入口函数中注册usb驱动,在出口函数中注销usb驱动。

②填充usb驱动信息,例如名称、probe函数等。

#include <linux/module.h>
#include <linux/usb.h>
#include <linux/init.h>
//设备连接时执行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {return 0;
}//设备断开时执行
static void my_usb_disconnect(struct usb_interface *intf) {}static const struct usb_device_id my_usb_id_table[] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD) },{ }, // 空结构体,表示数组结束
};static struct usb_driver my_usb_driver = {.name = "my_usb",.probe = my_usb_probe,.disconnect = my_usb_disconnect,.id_table = my_usb_id_table,
};static int my_usb_init(void) {int ret = usb_register(&my_usb_driver);return 0;
}static void my_usb_exit(void) {usb_deregister(&my_usb_driver);
}module_init(my_usb_init);
module_exit(my_usb_exit);
MODULE_LICENSE("GPL");

3. 完善probe函数。

①由于键盘属于输入设备,则在probe函数中先为输入子系统开辟空间,并填充信息。

②设置键盘的按键事件以及键值。

③注册输入子系统到驱动。

④构建URB对象,开辟空间并填充中断传输URB函数中的参数。

⑤提交URB对象,用于数据传输。

⑥在填充URB的回调函数中,上报键盘按键事件。

⑦在退出函数中释放内存。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
#include <linux/slab.h>struct input_dev *myusb_inputdev = NULL;  // 输入设备
struct urb *myusb_urb = NULL;  // USB请求块(URB)
unsigned char *myusb_buf = NULL;  // 数据缓冲区
unsigned char myusb_buf_old[8];  // 数据缓冲区int myusb_size = 0;  // 缓冲区大小
dma_addr_t myusb_dma;  // DMA地址// 键盘的键码表,包含标准键盘的按键映射
static const unsigned char usb_keyboardcode[256] = {0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,3, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2,4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,65, 66, 67, 68, 87, 88, 99, 70, 119, 110, 102, 104, 111, 107, 109, 106,105, 108, 103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,72, 73, 82, 83, 86, 127, 116, 117, 183, 184, 185, 186, 187, 188, 189, 190,191, 192, 193, 194, 134, 138, 130, 132, 128, 129, 131, 137, 133, 135, 136, 113,115, 114, 0, 0, 0, 121, 0, 89, 93, 124, 92, 94, 95, 0, 0, 0,122, 123, 90, 91, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 29, 42, 56, 125, 97, 54, 100, 126, 164, 166, 165, 163, 161, 115,114, 113, 150, 158, 159, 128, 136, 177, 178, 176, 142, 152, 173, 140
};//回调函数执行的条件是 URB传输完成,无论是成功还是失败。
static void myusb_func(struct urb *urb)  //填充中断URB的回调函数
{//上报特殊按键for (i = 0; i < 8; i++) {input_report_key(myusb_inputdev, usb_keyboardcode[i + 224], (myusb_buf[0] >> i) & 1);}//上报普通按键for (i = 2; i < 8; i++) {if (myusb_buf_old[i] > 3 && memscan(myusb_buf + 2, myusb_buf_old[i], 6) == myusb_buf + 8) {if (usb_keyboardcode[myusb_buf_old[i]]) {input_report_key(myusb_inputdev, usb_keyboardcode[myusb_buf_old[i]], 0);}}if (myusb_buf[i] > 3 && memscan(myusb_buf_old+ 2, myusb_buf[i], 6) == myusb_buf_old+ 8) {if (usb_keyboardcode[myusb_buf[i]]) {input_report_key(myusb_inputdev, usb_keyboardcode[myusb_buf[i]], 1);}}}input_sync(myusb_inputdev); //同步事件,即上报完毕。memcpy(myusb_buf_old, myusb_buf, 8); //myusb_buf复制给myusb_buf_oldusb_submit_urb(myusb_urb , GFP_ATOMIC); //提交URB
}// 设备连接时执行
static int my_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) {int i;int ret;struct usb_device *myusb_dev = interface_to_usbdev(intf); // 获取USB设备指针struct usb_endpoint_descriptor *endpoint;// 1. 为输入设备分配内存myusb_inputdev = input_allocate_device();if (!myusb_inputdev) {pr_err("Failed to allocate input device\n");return -ENOMEM;}myusb_inputdev->name = "myusb_input";   // 设置设备名称// 2. 设置事件类型:按键事件和重复事件set_bit(EV_KEY, myusb_inputdev->evbit);set_bit(EV_REP, myusb_inputdev->evbit);for (i = 0; i < 255; i++) {  // 设置按键位图set_bit(usb_keyboardcode[i], myusb_inputdev->keybit);}clear_bit(0, myusb_inputdev->keybit);  // 清除无效的按键位// 3. 注册输入设备ret = input_register_device(myusb_inputdev);if (ret) {input_free_device(myusb_inputdev);return ret;}
// 4. URB分配内存myusb_urb = usb_alloc_urb(0, GFP_KERNEL); // 分配一个URB,ISO数据包数为0,内存分配标志为GFP_KERNEL// 获取端点描述符并获取端点最大数据包大小endpoint = &intf->cur_altsetting->endpoint[0].desc;myusb_size = endpoint->wMaxPacketSize;// 分配一致性内存缓冲区用于数据传输myusb_buf = usb_alloc_coherent(myusb_dev, myusb_size, GFP_ATOMIC, &myusb_dma);// 获取接收中断管道unsigned int pipe = usb_rcvintpipe(myusb_dev, endpoint->bEndpointAddress);// 填充URBusb_fill_int_urb(myusb_urb, myusb_dev, pipe, myusb_buf, myusb_size, myusb_func, 0, endpoint->bInterval);// 5. 提交URB进行数据传输ret = usb_submit_urb(myusb_urb, GFP_KERNEL);mykbd_urb->transfer_dma = mykbd_dma;  // 设置URB的DMA地址mykbd_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; // 设置URB的标志:不使用DMA映射return 0;
}// 设备断开时执行
void myusb_disconnect(struct usb_interface *intf) {usb_kill_urb(myusb_urb);  // 取消URB传输usb_free_urb(myusb_urb);  // 释放URBusb_free_coherent(myusb_dev, myusb_size, myusb_buf, myusb_dma);  // 释放一致性内存input_unregister_device(myusb_inputdev);    // 注销输入设备input_free_device(myusb_inputdev);    // 释放输入设备
}

 问题1:上述代码中按键数组中的数据代表什么呢?

答:如下图所示不同的键值对应不同的按键,数组中的0元素就是保留位,也就是未分配的值。

 问题2:URB回调函数中的代码是什么意思?

答:是用于上报键盘上各个按键事件。USB每次传输都是8个字节的数据!对于键盘来说,这8个字节的数据分别对应下面的按键类型。 如下面两张图所示:

这张图是USB键盘按下后读取的8个字节的值。

下面这张图是读取的8个字节的值的解析。

4.验证

(1) 取消开发板上之前默认的USB驱动(键盘)。将内核源码重新配置编译到开发板。

(2)将自己写的驱动的ko文件上传至开发板,并将USB键盘连接开发板。

(3)使用命令:exec 0</dev/ttyl,按下按键如果没有反应,则证明此时没有键盘驱动了。由于按键现在用不了,所以重新启动开发板即可。

(4)安装ko驱动模块,然后重新使用命令:exec 0</dev/ttyl,这时按下按键就会有响应,如下所示:

五、USB设备侧驱动开发

31.Gadget驱动:把开发板模拟成USB网卡_哔哩哔哩_bilibili

相关文章:

Linux内核编程(二十一)USB驱动开发-键盘驱动

一、驱动类型 USB 驱动开发主要分为两种&#xff1a;主机侧的驱动程序和设备侧的驱动程序。一般我们编写的都是主机侧的USB驱动程序。 主机侧驱动程序用于控制插入到主机中的 USB 设备&#xff0c;而设备侧驱动程序则负责控制 USB 设备如何与主机通信。由于设备侧驱动程序通常与…...

模拟算法习题篇

在算法中&#xff0c;模拟是一种通过计算机程序来模拟现实世界中的过程或系统行为的方法。它的核心思想是根据题目给定的规则和逻辑&#xff0c;按照步骤细致地重现事件的发展流程&#xff0c;从而获得最终结果。 解题时如何使用模拟算法&#xff1a; 理解题目规则&#xff1a;…...

蓝桥杯真题 - 翻转 - 题解

题目链接&#xff1a;https://www.lanqiao.cn/problems/3520/learning/ 个人评价&#xff1a;难度 1 星&#xff08;满星&#xff1a;5&#xff09; 前置知识&#xff1a;无 整体思路 贪心&#xff0c;除了第一位跟最后一位&#xff0c;其它字符&#xff0c;每当 S [ i ] ≠…...

IP属地与视频定位位置不一致:现象解析与影响探讨

在数字化时代&#xff0c;IP属地和视频定位位置已成为我们获取网络信息、判断内容真实性的重要依据。然而&#xff0c;有时我们会发现&#xff0c;某些视频内容中展示的定位位置与其发布者的IP属地并不一致。这种不一致现象引发了广泛的关注和讨论。本文旨在深入剖析IP属地与视…...

管道符、重定向与环境变量

个人博客站—运维鹿: http://www.kervin24.top CSDN博客—做个超努力的小奚&#xff1a; https://blog.csdn.net/qq_52914969?typeblog 一、重定向 将命令和文件结合 标准输入重定向&#xff08;STDIN&#xff0c;文件描述符为0&#xff09;&#xff1a;默认从键盘输入&am…...

可扩展性设计架构模式——开闭原则

1. 概述 在架构设计中&#xff0c;遵循开闭原则&#xff08;Open/Closed Principle, OCP&#xff09;,代码应该“对扩展开放&#xff0c;对修改关闭”是实现可扩展性的关键。这个原则指导我们设计系统时&#xff0c;应使其对新增功能开放&#xff0c;而对现有代码的修改封闭。这…...

算法随笔_17: 回文数

上一篇: 算法随笔_16: 找出第k小的数对距离-CSDN博客 题目描述如下: 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&…...

计算机的错误计算(二百一十九)

摘要 大模型能确定 sin(2.6^10) 的符号吗&#xff1f;实验表明&#xff0c;大模型的计算、推理均有问题。另外&#xff0c;结论也是错的。 前面讨论的内容为自变量是 2.6^100的正弦&#xff0c;本节讨论自变量为 2.6^10的正弦&#xff08;对于某些大模型&#xff0c;2.6^100似…...

React进阶之高阶组件HOC、react hooks、自定义hooks

React高级 高阶组件 HOC属性代理反向继承属性代理和反向继承的区别实例实例一实例二 HooksHooks APIuseState&#xff1a;useEffect&#xff1a;useLayoutEffect&#xff1a;useRef&#xff1a;useContext&#xff1a;useReducer:useMemouseCallback 自定义Hooks 拓展&#xff…...

【Pytest】基础到高级功能的理解使用

文章目录 第一部分&#xff1a;Pytest 简介1.1 什么是 Pytest&#xff1f;1.2 Pytest 的历史1.3 Pytest 的核心概念1.4 Pytest 的特点1.5 为什么选择 Pytest&#xff1f; 第二部分&#xff1a;Pytest 的基本使用2.1 安装 Pytest2.2 编写第一个测试用例2.2.1 创建一个简单的测试…...

RHCE实验详解

目录 实验分析 环境拓扑结构 项目需求 主机环境描述 实验步骤 一、密钥互信和主机名更改 二、DNS 三、NGINX 四、MARIADB 五、NFS 六、NTP 七、论坛服务 结果展示及痛点解答 实验分析 环境拓扑结构 项目需求 1. 172.25.250.101 主机上的 Web 服务要求提供 www.ex…...

备赛蓝桥杯之第十五届职业院校组省赛第二题:分享点滴

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…...

MyBatis 注解开发详解

MyBatis 注解开发详解 MyBatis 支持使用注解来进行数据库操作。注解方式将 SQL 语句直接写在 Java 接口中&#xff0c;通过注解来完成 CRUD&#xff08;增删改查&#xff09;操作&#xff0c;省去了使用 XML 配置的繁琐步骤。这种方式适合简单项目或快速原型开发&#xff0c;因…...

Kivy App开发之UX控件VideoPlayer视频播放

kivy使用VideoPlayer控件实现视频播放,可以控制视频的播放,暂停,音量调节等功能。 在使用VideoPlayer视频播放器时,可以参考下表属性来设置其样式和触发事件。 属性说明source视频路径,默认为空state视频状态,值play,pause,stop,默认为stopthumbnail显示视频的缩略图…...

简单排序算法

异或运算及异或运算实现的swap方法 ^(异或): ^运算是计算机中的位运算&#xff0c;运算规则为相同为0&#xff0c;不同为1&#xff08;也被称为无进位相加&#xff09;。位运算处理效率比常规运算符效率更高。 异或运算遵循的法则&#xff1a; 0^N N N^N 0 异或运算…...

C语言初阶牛客网刷题——JZ17 打印从1到最大的n位数【难度:入门】

1.题目描述 牛客网OJ题链接 题目描述&#xff1a; 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 用返回一个整数列表来代替打印n 为正整数&#xff0c;0 < n < 5 示例1 输入&…...

基于springboot+vue的校园二手物品交易系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

开发环境搭建-2:配置 python 运行环境(使用 uv 管理 python 项目)

在 WSL 环境中配置&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 UV 介绍 uv软件官网&#xff08;github 需要梯子&#xff0c;没错这个软件的官网真就是 github 页面&#xff09;&#xff1a;https://github.com/astral-sh/uv 中文官网&#xff08;github 需要梯…...

STM32 ST7735 128*160

ST7735 接口和 STM32 SPI 引脚连接 ST7735 引脚功能描述STM32 引脚连接&#xff08;示例&#xff0c;使用 SPI1&#xff09;SCLSPI 时钟信号 (SCK)PA0(SPI1_SCK)SDASPI 数据信号 (MOSI)PA1 (SPI1_MOSI)RST复位信号 (Reset)PA2(GPIO 手动控制)DC数据/命令选择 (D/C)PA3 (GPIO 手…...

【面试总结】FFN(前馈神经网络)在Transformer模型中先升维再降维的原因

FFN&#xff08;前馈神经网络&#xff09;在Transformer模型中先升维再降维的设计具有多方面的重要原因&#xff0c;以下是对这些原因的总结&#xff1a; 1.目标与动机 高维映射空间&#xff1a;FFN的设计目的是通过一系列线性变换来拟合一个高维的映射空间&#xff0c;而不仅…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...