linux内核input子系统概述

目录

  • 一、input子系统
  • 二、关键数据结构和api
    • 2.1 数据结构
      • 2.1.1 input_dev
      • 2.1.2 input_handler
      • 2.1.3 input_event
      • 2.1.4 input_handle
    • 2.2 api接口
      • 2.2.1 input_device 相关接口
        • input_device 注册流程
        • 事件上报
      • 2.2.2 input handle 相关接口
        • 注册 handle
        • 指定 handle
      • 2.2.3 input handler 相关接口
        • 注册handler
  • 三、input handler
    • 3.1 evdev handler
      • 3.1.1 handler 注册
      • 3.1.2 evdev_connect
      • 3.1.3 evdev_events
      • 3.1.4 file_operations
    • 3.2 mousedev handler

一、input子系统

input子系统处理Linux下的输入事件。

驱动层:输入设备的驱动程序,负责检测和接收输入设备的输入事件,将输入事件上报给核心层;
核心层:提供设备驱动、事件 handler 注册和操作的接口;接收驱动层的输入事件并上报给事件处理层;
事件处理层:通过提供 sysfs 接口等方式和用户空间交互,例如用户空间打开特定设备,当有输入数据时就会上传给用户空间。

input子系统框架结构图(总结来自这里):
input子系统框架结构图

input driver 接收到硬件的输入事件 ==> 发送到input core,input core 根据事件类型 ==> 将事件交给对应的input handler处理 ==> input handler 上报用户空间,用户空间收收到事件后进行对应的处理。

二、关键数据结构和api

2.1 数据结构

2.1.1 input_dev

input_dev 描述输入设备,结构体中的多个 bitmap 描述了输入设备的类型和支持的输入事件。这些事件类型相关的宏定义在 input-event-codes.h 头文件中。

struct input_dev {const char *name;const char *phys;const char *uniq;struct input_id id;unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];unsigned long evbit[BITS_TO_LONGS(EV_CNT)];    // 设备支持的事件类型的bitmapunsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  // 设备支持的按键类型unsigned long relbit[BITS_TO_LONGS(REL_CNT)];  // 设备支持的相对坐标事件unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];  // 设备支持的绝对坐标事件unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];  // 设备支持的杂项事件unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];  // ledunsigned long sndbit[BITS_TO_LONGS(SND_CNT)];  // 声音unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    // 压力反馈事件unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    // 开关unsigned int hint_events_per_packet;unsigned int keycodemax;unsigned int keycodesize;void *keycode;int (*setkeycode)(struct input_dev *dev,const struct input_keymap_entry *ke,unsigned int *old_keycode);int (*getkeycode)(struct input_dev *dev,struct input_keymap_entry *ke);struct ff_device *ff;struct input_dev_poller *poller;unsigned int repeat_key;struct timer_list timer;int rep[REP_CNT];struct input_mt *mt;struct input_absinfo *absinfo;unsigned long key[BITS_TO_LONGS(KEY_CNT)];unsigned long led[BITS_TO_LONGS(LED_CNT)];unsigned long snd[BITS_TO_LONGS(SND_CNT)];unsigned long sw[BITS_TO_LONGS(SW_CNT)];int (*open)(struct input_dev *dev);void (*close)(struct input_dev *dev);int (*flush)(struct input_dev *dev, struct file *file);int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);struct input_handle __rcu *grab;spinlock_t event_lock;struct mutex mutex;unsigned int users;bool going_away;struct device dev;struct list_head	h_list;struct list_head	node;unsigned int num_vals;unsigned int max_vals;struct input_value *vals;bool devres_managed;ktime_t timestamp[INPUT_CLK_MAX];
};

2.1.2 input_handler

input_handler 提供了对一类设备输入事件处理的接口。

struct input_handler {void *private;void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);void (*events)(struct input_handle *handle,const struct input_value *vals, unsigned int count);bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);bool (*match)(struct input_handler *handler, struct input_dev *dev);int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);void (*disconnect)(struct input_handle *handle);void (*start)(struct input_handle *handle);bool legacy_minors;int minor;const char *name;const struct input_device_id *id_table;struct list_head	h_list;struct list_head	node;
};

以 evdev handler 为例,

connect 接口,通过 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定,并创建对应输入设备的字符设备;
event/events 接口,将设备的输入拷贝到 buffer 中,当用户空间调用字符设备的 read 接口时,就可以从 buffer 中读取输入信息;

2.1.3 input_event

handler 上报事件到用户层的时候,以 input_event 格式进行上报。

struct input_event {struct timeval time;  // 事件发生事件__u16 type;           // 事件类型,例如 EV_KEY 按键类型__u16 code;           // 事件编码,例如 KEY_0 按键__s32 value;          // 事件值
};

2.1.4 input_handle

input_handle 实现将 input_device 和 input_handler 绑定的功能,上面已经介绍到,evdev handler 的 connect 接口中,会调用 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定。

struct input_handle {void *private;int open;  // 当前handle是否openconst char *name;struct input_dev *dev;struct input_handler *handler;struct list_head	d_node;struct list_head	h_node;
};

open 记录了当前 handle 是否被 open,以 evdev 为例,当用户空间 open 字符设备的时候,会调用到input_open_device 接口,接口内部实现 input_handle->open++。

2.2 api接口

2.2.1 input_device 相关接口

input核心层提供了如下一系列input device相关的接口,事件input device的注册、事件的上报等功能:

// 申请
struct input_dev *input_allocate_device(void);
struct input_dev *devm_input_allocate_device(struct device *dev);// 设置支持的事件类型
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)// 注册、注销
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);// 释放
void input_free_device(struct input_dev *dev);// 事件上报
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value);
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev);  // 同步通知事件发送完成
static inline void input_mt_sync(struct input_dev *dev);
input_device 注册流程

注册接口中主要做了以下动作:

  1. 检查 bitmap 等参数设置是否正确;
  2. 将 device 添加到 input_device_list 链表中;
  3. 对于 input_handler_list 中的每一个 handler,调用 input_attach_handler 接口尝试将 device 和 handler 绑定,在接口内部会检查 device 和 handler 是否 match,match 的话则调用 handler 的 connect 接口完成绑定动作。
int input_register_device(struct input_dev *dev)
{// 检查bitmap等参数、配置input_dev部分参数/* Every input device generates EV_SYN/SYN_REPORT events. */__set_bit(EV_SYN, dev->evbit);/* KEY_RESERVED is not supposed to be transmitted to userspace. */__clear_bit(KEY_RESERVED, dev->keybit);/* Make sure that bitmasks not mentioned in dev->evbit are clean. */input_cleanse_bitmasks(dev);dev->max_vals = dev->hint_events_per_packet + 2;dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);// device_adderror = device_add(&dev->dev);// device 和 handler绑定error = mutex_lock_interruptible(&input_mutex);list_add_tail(&dev->node, &input_dev_list);list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();mutex_unlock(&input_mutex);if (dev->devres_managed) {dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",__func__, dev_name(&dev->dev));devres_add(dev->dev.parent, devres);}return 0;
}
EXPORT_SYMBOL(input_register_device);
事件上报

input 子系统中封装了针对不同类型事件的上报接口,例如 input_report_key\input_report_abs 等,这些接口实际都是调用 input_event 接口完成事件上报,只不过接口参数中的 type 类型不同,以 input_report_key 为例:

input_report_key(struct input_dev *dev, unsigned int code, int value)-> input_event(dev, EV_KEY, code, !!value);-> input_handle_event(dev, type, code, value);-> input_get_disposition(dev, type, code, &value); // 获取事件类型-> input_pass_values(dev, dev->vals, dev->num_vals);  -> input_to_handler(handle, vals, count);-> handler->events(handle, vals, count); // 通知handler处理事件

在 input_handle_event 接口中,会将事件缓存在 dev->vals 中,并记录事件数目到 dev-num_vals,当检测到 dev->num_vals >= dev->max_vals - 2 或者 input_sync 事件时,将所有缓存事件通知 handler 处理。

static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{// 获取事件类型int disposition = input_get_disposition(dev, type, code, &value);if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)add_input_randomness(type, code, value);// 如果 INPUT_PASS_TO_DEVICE并且device实现了event,则通知deviceif ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)dev->event(dev, type, code, value);if (!dev->vals)return;// 记录要通知给handler的事件if (disposition & INPUT_PASS_TO_HANDLERS) {struct input_value *v;if (disposition & INPUT_SLOT) {v = &dev->vals[dev->num_vals++];v->type = EV_ABS;v->code = ABS_MT_SLOT;v->value = dev->mt->slot;}v = &dev->vals[dev->num_vals++];v->type = type;v->code = code;v->value = value;}// sync事件或者要超出缓存,则将缓存的vals flush到handlerif (disposition & INPUT_FLUSH) {if (dev->num_vals >= 2)input_pass_values(dev, dev->vals, dev->num_vals);dev->num_vals = 0;dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);} else if (dev->num_vals >= dev->max_vals - 2) {dev->vals[dev->num_vals++] = input_value_sync;input_pass_values(dev, dev->vals, dev->num_vals);dev->num_vals = 0;}}

2.2.2 input handle 相关接口

int input_register_handle(struct input_handle *handle);
void input_unregister_handle(struct input_handle *handle);
注册 handle

input_register_handle 接口实现注册一个input_handle,将 device 和 handler 绑定,例如在 evdev handler 的 connect 接口中,就调用了 input_register_handle 接口。
接口流程:

int input_register_handle(struct input_handle *handle)
{// 将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handlerif (handler->filter)list_add_rcu(&handle->d_node, &dev->h_list);elselist_add_tail_rcu(&handle->d_node, &dev->h_list);// 将 handle->h_node 加入到 handler->h_list,实现遍历handler->h_list就能找到所有关联的input_handler,进而找到input_devicelist_add_tail_rcu(&handle->h_node, &handler->h_list);if (handler->start)handler->start(handle);return 0;
}

在 input_register_handle 接口中,会将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler。
实际上在 input_pass_values 中,如果未指定 input_device 的 input_handle, 就是通过遍历列表的方式,将事件通过所有关联的 input_handle 发送到 input_handler 中。
也就是说默认input_event的事件上报是一个广播行为:

	handle = rcu_dereference(dev->grab);if (handle) {count = input_to_handler(handle, vals, count);  // 指定handle} else {list_for_each_entry_rcu(handle, &dev->h_list, d_node)   // 广播if (handle->open) {count = input_to_handler(handle, vals, count);if (!count)break;}}
指定 handle

在 input_grab_device 接口中,实现了 dev->grab 与 handle 的绑定:

int input_grab_device(struct input_handle *handle)
{if (dev->grab) {retval = -EBUSY;goto out;}rcu_assign_pointer(dev->grab, handle);
}

以 evdev handler 为例,ioctl 中实现了 EVIOCGRAB,用于 input_device 指定 input_handle:

// ioctl接口中,调用evdev_grab或evdev_ungrab事件绑定和解绑:case EVIOCGRAB:if (p)return evdev_grab(evdev, client);elsereturn evdev_ungrab(evdev, client);// evcev_grab中调用 input_grab_device 实现 dev->grab 与 handle 的绑定
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{int error;if (evdev->grab)return -EBUSY;error = input_grab_device(&evdev->handle);if (error)return error;rcu_assign_pointer(evdev->grab, client);return 0;
}

2.2.3 input handler 相关接口

// 注册、注销
int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);
注册handler

input_register_handler 接口中,将 handler 添加到 input_handler_list 中,遍历 input_dev_list,执行input_attach_handler(dev, handler):

int input_register_handler(struct input_handler *handler)
{INIT_LIST_HEAD(&handler->h_list);list_add_tail(&handler->node, &input_handler_list);list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler);return 0;
}

三、input handler

3.1 evdev handler

3.1.1 handler 注册

在evdev_init中调用 input_register_handler 实现 handler 的注册。

static struct input_handler evdev_handler = {.event		= evdev_event,.events		= evdev_events,.connect	= evdev_connect,.disconnect	= evdev_disconnect,.legacy_minors	= true,.minor		= EVDEV_MINOR_BASE,.name		= "evdev",.id_table	= evdev_ids,
};static int __init evdev_init(void)
{return input_register_handler(&evdev_handler);
}

3.1.2 evdev_connect

evdev_connect 接口在 input_attach_handler 中被调用,接口实现以下功能:

  • 以 evdev_fops 为 file_operations 创建 cdev,设备名称为 event%d;
  • 调用 input_register_handle 实现 input_device 与 input_handler 的绑定;
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);INIT_LIST_HEAD(&evdev->client_list);dev_no = minor;dev_set_name(&evdev->dev, "event%d", dev_no);evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);evdev->dev.class = &input_class;evdev->dev.parent = &dev->dev;evdev->dev.release = evdev_free;device_initialize(&evdev->dev);error = input_register_handle(&evdev->handle);cdev_init(&evdev->cdev, &evdev_fops);error = cdev_device_add(&evdev->cdev, &evdev->dev);}

3.1.3 evdev_events

evdev_events 接口负责处理 input_device 上报的事件,并上报给用户层:

handler->events(handle, vals, count); // evdev_events,读时间--> evdev_pass_values(client, vals, count, ev_time);  // 组input_event--> __pass_event(client, &event);  // 将event存在evdev_client的buffer中--> kill_fasync(&client->fasync, SIGIO, POLL_IN); // 异步信号通知用户层--> wake_up_interruptible(&evdev->wait);  // 唤醒等待队列

3.1.4 file_operations

evdev handler 的 file_operations 提供了 fasync\poll\read 等接口,供用户层读取 input event。

static const struct file_operations evdev_fops = {.owner		= THIS_MODULE,.read		= evdev_read,.write		= evdev_write,.poll		= evdev_poll,.open		= evdev_open,.release	= evdev_release,.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl	= evdev_ioctl_compat,
#endif.fasync		= evdev_fasync,.flush		= evdev_flush,.llseek		= no_llseek,
};

evdev_fasync接口实现异步通知处理函数,当有input_event事件时,在evdev_events接口中,最终会调用 kill_fasync实现发送异步通知信号,用户层接收到状态变化后,可知晓有input_event事件需要处理。
evdev_read接口为用户空间提供了读input_event事件的接口,实际是将evdev_events接口中缓存在buffer中的数据copy到用户空间。当缓存中没有数据是,调用wait_event_interruptible 等待 evdev_events 唤醒。

3.2 mousedev handler

TODO

参考链接:https://www.cnblogs.com/arnoldlu/p/17952329

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

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

相关文章

基于springboot+vue的电影院购票系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…

数码管的动态显示

1.共阴极数码管实现HELLO #include<reg51.h> char str[]{0x76,0x79,0x38,0x38,0x3F}; //HELLO char wei[]{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; void delay(int n) {int i0,j0;for(i0;i<n;i){for(j0;j<120;j);} } void seg() {int i;for(i0;i<…

NC 现金流量查询 节点 多账簿联查时,根据所选择的列来判断明细和现金流量联查按钮是否可用,根据添加列选择监听事件处理。

NC 现金流量查询 节点 多账簿联查时&#xff0c;根据所选择的列来判断明细和现金流量联查按钮是否可用&#xff0c;如下图的情况&#xff1a; 在现金流量查询界面UI类的initTable(QueryConditionVO conVO)方法中添加列选择监听事件即可&#xff0c;如下&#xff1a; // 列监听…

LeetCode刷题【树状数组、并查集、二叉树】

目录 树状数组307. 区域和检索 - 数组可修改406. 根据身高重建队列673. 最长递增子序列的个数1409. 查询带键的排列 并查集128. 最长连续序列130. 被围绕的区域 二叉树94. 二叉树的中序遍历104. 二叉树的最大深度101. 对称二叉树543. 二叉树的直径108. 将有序数组转换为二叉搜索…

web性能检测工具lighthouse

About Automated auditing, performance metrics, and best practices for the web. Lighthouse 可以自动检查Web页面的性能。 你可以以多种方式使用它。 浏览器插件 作为浏览器插件&#xff0c;访问chrome网上商店 搜索Lighthouse 插件安装。以两种方式使用。 方式一 安装…

DP:路径规划模型

创作不易&#xff0c;感谢三连支持&#xff01; 路径规划主要是让目标对象在规定范围内的区域内找到一条从起点到终点的无碰撞安全路径。大多需要用二维dp数组去实现 一、不同路径 . - 力扣&#xff08;LeetCode&#xff09;不同路径 class Solution { public:int uniquePath…

重学SpringBoot3-MyBatis的三种分页方式

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-MyBatis的三种分页方式 准备工作环境搭建数据准备未分页效果 1. 使用MyBatis自带的RowBounds进行分页演示 2. 使用物理分页插件演示 3. 手动编写分页SQL…

pcl 凸包ConvexHull

pcl 凸包ConvexHull 头文件等 #include <pcl/surface/convex_hull.h>typedef pcl::PointXYZ PointT; typedef pcl::PointCloud<PointT> CloudT; typedef CloudT::Ptr CP 代码 CP PSO::tubao(CP cloud) {pcl::ConvexHull<PointT> hull;hull.setInputCloud…

代码随想录算法训练营第十七天|110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

代码随想录算法训练营第十七天|110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和 110.平衡二叉树 给定一个二叉树&#xff0c;判断它是否是 平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true题解&#xff1a;平衡…

查看Scala类的方法

文章目录 一、概述如何查看Scala类的方法二、使用Scala文档查看类的方法三、使用反射机制查看类的方法 一、概述如何查看Scala类的方法 本文介绍了在Scala中查看Int类方法的两种方法&#xff1a;使用Scala标准库文档和使用反射机制。通过Scala标准库文档&#xff0c;您可以方便…

【C++庖丁解牛】二叉搜索树(Binary Search Tree,BST)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. 二叉搜索树概念2. 二叉…

Etcd Raft 协议(进阶篇)

前言 在正式开始介绍 Raft 协议之间&#xff0c;我们有必要简单介绍一下其相关概念。在分布式系统中&#xff0c;一致性是比较常见的概念&#xff0c;所谓一致性指的是集群中的多个节点在状态上达成一致。在程序和操作系统不会崩溃、硬件不会损坏、服务器不会掉电、网络绝对可靠…

【Linux】环境变量常见指令操作&基本实验(入门必看!)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《…

安卓实现翻转时间显示效果

效果 废话不多说上代码 自定义组件 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.…

BM83 字符串变形

import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param s string字符串 * param n int整型 * return string字符串*/public String trans (String s, int n) {// write co…

RK3568驱动指南|第十三篇 输入子系统-第143章 多对多的匹配关系分析

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

ubuntu20.04_PX4_1.13

说在前面&#xff1a;&#xff08;最好找一个干净的Ubuntu系统&#xff09;如果配置环境的过程中出现很多编译的错误或者依赖冲突&#xff0c;还是建议新建一个虚拟机&#xff0c;或者重装Ubuntu系统&#xff0c;这样会避免很多麻烦&#x1f490; &#xff0c; 安装PX4 1.13.2 …

小红书扫码登录分析与python实现

文章目录 1. 写在前面2. 接口分析3. 代码实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

代码随想录算法训练营第十七天(二叉树IV)| 110. 平衡二叉树、257. 二叉树的所有路径、404.左叶子之和(JAVA)

文章目录 110. 平衡二叉树解题思路源码 257. 二叉树的所有路径解题思路源码 404.左叶子之和解题思路源码 110. 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7]输出&#xff1a;true 示例…

伊理威科技:抖音开网店新手刚做选啥品

在数字浪潮中&#xff0c;抖音不仅是展示才艺的舞台&#xff0c;更是创业者的新天地。新手若想在这片热土上开垦网店&#xff0c;选品便是首要课题。选择产品如同种下希望的种子&#xff0c;既要考量土壤肥沃度&#xff0c;也得预测风雨适宜期。 兴趣与专长是选品的罗盘。热爱所…