Input子系统

news2024/11/25 20:26:54

文章目录

        • 前言
        • Input子系统简介
        • Input子系统代码实现框架
            • Linux Input子系统支持的数据类型
          • input核心层
          • 设备驱动层
            • input_allocate_device 与 函数
            • input_set_capability
            • input_register_device 函数
            • input_unregister_device 与 input_free_device 函数
          • 事件处理层
            • input_attach_handler函数
            • input_match_device 函数
            • evdev_connect 函数
            • input_register_handle 函数
        • 参考链接

前言

本片文章是从设备驱动程序层面整理总结,目的是想记录大佬的知识,为了后面能够学以致用。
<阿杰。>写的真不错。

Input子系统简介

input子系统就是管理输入的子系统,和pinctrl和gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。
对于用户空间,所有的输入设备以文件的形式供用户应用程序使用。

在这里插入图片描述
在这里插入图片描述

Input子系统代码实现框架

驱动层:输入设备的具体驱动程序,比如按键驱动程序。主要是实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。如:input_register_device; 通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。 设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
事件层:主要是和用户空间交互。用户编程的接口(设备节点),并处理驱动层提交的数据处理。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev/input下生成相应的设备文件node,这些操作在输入子系统中由事件处理层完成)。
在这里插入图片描述

Linux Input子系统支持的数据类型
EV_SYN0x00同步事件
EV_SYN0x00同步事件
EV_KEY0x01按键事件
EV_REL0x02相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS0x03绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC0x04其它
EV_SW0x05开关
EV_LED0x11按键/设备灯
EV_SND0x12声音/警报
EV_REP0x14重复
EV_FF0x15力反馈
EV_PWR0x16电源
EV_FF_STATUS0x17力反馈状态
EV_MAX0x1f事件类型最大个数和提供位掩码支持
input核心层

CODE PATH : drivers/input/input.c
input核心层会向Linux内核注册一个input类,系统启动后会在/sys/class目录下生成一个input子目录

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);  // 创建class类
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}

	err = input_proc_init();  // 初始化proc相关
	if (err)
		goto fail1;

	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),   // 向linux内核注册input字符设备
				     INPUT_MAX_CHAR_DEVICES, "input");
    .....
}
static int __init input_proc_init(void)
{
	struct proc_dir_entry *entry;

	proc_bus_input_dir = proc_mkdir("bus/input", NULL);  // 在proc目录下创建bus/input目录
	if (!proc_bus_input_dir)
		return -ENOMEM;

	entry = proc_create("devices", 0, proc_bus_input_dir,   //input目录下创建devices文件
			    &input_devices_proc_ops);
	if (!entry)
		goto fail1;

	entry = proc_create("handlers", 0, proc_bus_input_dir,  // input目录下创建handlers文件
			    &input_handlers_proc_ops);
	if (!entry)
		goto fail2;
	.....

CODE PATH : kernel-4.19/include/linux/input.h // 头文件中有注释结构体成员含义

struct input_dev {
	const char *name;			//提供给用户的输入设备名称
	const char *phys;			//提供给编程者的设备节点名称
	const char *uniq;			//指定唯一的ID号,就像MAC地址一样
	struct input_id id;			//输入设备标识ID,用于和事件处理层进行匹配

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    // 下面的成员是用来表示该input设备能够上报的事件类型有哪些,用位的方式来表示
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
	unsigned 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)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
	
	int (*open)(struct input_dev *dev);    // 设备的open函数
	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);   // 上报事件
设备驱动层

在驱动加载模块中,设置input设备支持的事件类型
注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
将输入设备注册到输入子系统中(在使用input子系统时,只需要注册一个input设备)。

input_allocate_device 与 函数

(1)使用input_allocate_device函数申请一个input_dev

struct input_dev *input_allocate_device(void)
{
	struct input_dev *dev;
	动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数 
	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	if (dev) {
		dev->dev.type = &input_dev_type;
		dev->dev.class = &input_class;		//添加进input类设备模型中
		device_initialize(&dev->dev);
		mutex_init(&dev->mutex);			//初始化互斥锁 
		spin_lock_init(&dev->event_lock);	//初始化自旋锁
		INIT_LIST_HEAD(&dev->h_list);		//初始化handle链表 
		INIT_LIST_HEAD(&dev->node);			//初始化输入设备链表

		__module_get(THIS_MODULE);
	}

	return dev;
}
input_set_capability

(2)初始化input_dev的事件类型以及事件值(设置输入设备可以上报哪些输入事件)

input_register_device 函数

(3)input_dev初始化完成以后就需要向Linux内核注册input_dev了,需要用到input_register_device函数。
定义在drivers/input/input.c文件中。

int input_register_device(struct input_dev *dev)
{
	static atomic_t input_no = ATOMIC_INIT(0);
	struct input_handler *handler;
	const char *path;
	int error;

	/* 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成员,如果设备驱动没有指定的函数,将赋予系统默认的函数 */
	if (!dev->hint_events_per_packet)
		dev->hint_events_per_packet =
				input_estimate_events_per_packet(dev);

	init_timer(&dev->timer);
	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
		dev->timer.data = (long) dev;
		dev->timer.function = input_repeat_key;
		dev->rep[REP_DELAY] = 250;
		dev->rep[REP_PERIOD] = 33;
	}
	
	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;
	
	/* 动态获取input设备的ID号,名称为input*, *为id号 */
	/* 例如:input5 */
	dev_set_name(&dev->dev, "input%ld",
		     (unsigned long) atomic_inc_return(&input_no) - 1);
	
	/* 在/sys目录下创建设备目录和文件 */
	/* 例如:/sys/devices/virtual/input/input5/ */
	error = device_add(&dev->dev);
	if (error)
		return error;
		
	/* 在终端上打印设备的绝对路径名称 */ 
	/* 例如:input: keyinput as /devices/virtual/input/input5 */
	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	pr_info("%s as %s\n",
		dev->name ? dev->name : "Unspecified device",
		path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error) {
		device_del(&dev->dev);
		return error;
	}
	/* 把设备挂到全局的input子系统设备链表input_dev_list上 */
	list_add_tail(&dev->node, &input_dev_list);
	/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 
     * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
     * 方式与设备模型的device和driver匹配过程很相似,所有的input 
     * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
     * 上,进行“匹配相亲” */  
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	return 0;
}
input_unregister_device 与 input_free_device 函数

(4)卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用input_free_device函数释放掉前面申请的input_dev

 struct input_dev *inputdev; /* input 结构体变量 */

 /* 驱动入口函数 */
 static int __init xxx_init(void)
 {
	......
	inputdev = input_allocate_device(); /* 申请 input_dev */
	inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */

 	/*********第一种设置事件和事件值的方法***********/
 	__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
 	__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
 	__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
 	/************************************************/

 	/*********第二种设置事件和事件值的方法***********/
 	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 	keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
 	/************************************************/

 	/*********第三种设置事件和事件值的方法***********/
 	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
 	input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
 	/************************************************/

 	/* 注册 input_dev */
 	input_register_device(inputdev);
 	......
 	return 0;
 }

 /* 驱动出口函数 */
 static void __exit xxx_exit(void)
 {
 	input_unregister_device(inputdev); /* 注销 input_dev */
 	input_free_device(inputdev); /* 删除 input_dev */
 }
事件处理层
input_attach_handler函数
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	id = input_match_device(handler, dev);		//匹配事件驱动,事件驱动一共有三种evdev,mousedev,joydev
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);	//调用事件驱动的connect函数进行匹配
	if (error && error != -ENODEV)
		pr_err("failed to attach handler %s to device %s, error: %d\n",
		       handler->name, kobject_name(&dev->dev.kobj), error);

	return error;
}

input_attach_handler 函数里面有两个函数比较重要,input_match_device 和 handler->connect, 看看具体是怎样实现的。

input_match_device 函数
static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;
	int i;

	for (id = handler->id_table; id->flags || id->driver_info; id++) {
	
		/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID 
         * 如果没有匹配上将进行MATCH_BIT匹配 */  
		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
			if (id->vendor != dev->id.vendor)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
			if (id->product != dev->id.product)
				continue;

		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;
		/* MATCH_BIT用于匹配设备驱动中是否设置了这些位,MATCH_BIT的宏 
         * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的 
         * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL 
         */  
		MATCH_BIT(evbit,  EV_MAX);
		MATCH_BIT(keybit, KEY_MAX);
		MATCH_BIT(relbit, REL_MAX);
		MATCH_BIT(absbit, ABS_MAX);
		MATCH_BIT(mscbit, MSC_MAX);
		MATCH_BIT(ledbit, LED_MAX);
		MATCH_BIT(sndbit, SND_MAX);
		MATCH_BIT(ffbit,  FF_MAX);
		MATCH_BIT(swbit,  SW_MAX);

		if (!handler->match || handler->match(handler, dev))
			return id;
	}

	return NULL;
}

handler 是事件驱动结构体的指针,事件驱动一般有三种evdev,mousedev,joydev,分别对应三个结构体evdev_handler ,mousedev_handler ,joydev_handler ,如果匹配成功了会返回id,再回看 input_attach_handler 函数,若id不为NULL,就会调用 handler->connect 函数。假如现在匹配到 evdev_handler 这个事件驱动,然后就会调用evdev_handler->connect 函数,下面来看看evdev_handler->connect 函数做了什么。

evdev_connect 函数

drivers/input/evdev.c文件中有如下内容:

static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

在注册evdev驱动时,evdev_handler结构体的connect成员指向evdev_connect函数,所以evdev_handler->connect 就是 evdev_connect 函数了,evdev_connect 函数也是定义在drivers/input/evdev.c文件中,内容如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int error;

	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])
			break;

	if (minor == EVDEV_MINORS) {
		pr_err("no more free evdev devices\n");
		return -ENFILE;
	}
	
	/* 给evdev事件层驱动分配空间 ,
	 * 可以不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,
	 * 注意不是handler, handle 就是个中间件,可以理解成胶带,
	 * 它把 handler 与 dev 连在一起 */
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;

	INIT_LIST_HEAD(&evdev->client_list);		
	spin_lock_init(&evdev->client_lock);		
	mutex_init(&evdev->mutex);					
	init_waitqueue_head(&evdev->wait);			

	dev_set_name(&evdev->dev, "event%d", minor);	
	evdev->exist = true;
	evdev->minor = minor;
	evdev->hw_ts_sec = -1;
	evdev->hw_ts_nsec = -1;

	/* 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,
	 * 这样通过handle就可以找到dev与handler, 即是实现
	 * handle -> dev ,  handle -> hander 的联系 */
	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

	evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + 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);		// 注册 handle
	if (error)
		goto err_free_evdev;

	error = evdev_install_chrdev(evdev);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);	//在 /dev/input 类下面创建设备节点, 名字为event%d
	if (error)
		goto err_cleanup_evdev;
    ......
}
内容里面比较重要就是evdev->handle这个成员了,它的作用就是把 hander 和 dev 连在一起,hander 表示事件驱动层,dev表示设备驱动层,这样 事件驱动层 和 设备驱动层 之间就通过handle这个 ”红娘“ 建立了关系了。现在可以通过 handle 找到 hander 或者 dev,不过还差一步,就是实现双向性,通过hander 或者 dev 也能找到 handle ,这样才能实现 handle —hander — dev 三者之间畅通无阻,而这一实现就体现在 handle 注册函数input_register_handle里面。
input_register_handle 函数
int input_register_handle(struct input_handle *handle)
{
	/* 第二次建立联系	*/
	struct input_handler *handler = handle->handler;
	struct input_dev *dev = handle->dev;
	int error;

	error = mutex_lock_interruptible(&dev->mutex);
	if (error)
		return error;

	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);	// 将handle 记录在 dev->h_list 链表中

	mutex_unlock(&dev->mutex);

	list_add_tail_rcu(&handle->h_node, &handler->h_list);	// 将handle 记录在 handler->h_list 链表中


	if (handler->start)
		handler->start(handle);

	return 0;
}

input_register_handle函数注册handle时,把handle作为节点分别加入到dev->h_list 链表和handler->h_list 链表中去,至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻了。
在这里插入图片描述

通过上图可以看到input输入设备匹配关联的关键过程,以及涉及到的关键函数和数据。
以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。

参考链接

http://t.zoukankan.com/zhaobinyouth-p-6257662.html
https://blog.csdn.net/qq_17639223/article/details/119489552
https://blog.csdn.net/qq_43286311/article/details/117437595

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

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

相关文章

A. Divide and Conquer

An array bb is good if the sum of elements of bb is even. You are given an array aa consisting of nn positive integers. In one operation, you can select an index ii and change ai:⌊ai2⌋ai:⌊ai2⌋. †† Find the minimum number of operations (possibly 00)…

ArcGIS 切片问题小结

1. 如果发布的切片缓存服务没有自动启动怎么办&#xff1f; 在进行切片时偶然情况下可能会遇到&#xff0c;你在切片时已经设置了server自动进行切片处理&#xff0c;但是在服务发布后&#xff0c;服务发布成功&#xff0c;但是服务没有成功启动&#xff0c;导致服务器没有自动…

免费数据恢复方法有哪些?分享这几种简单又实用的恢复方法(2023年最新)

很多时候&#xff0c;我们使用电脑总是容易误删一些数据。比如使用电脑&#xff0c;误删了办公资料&#xff1b;使用SD卡&#xff0c;出现照片没有办法打开的情况&#xff1b;使用移动硬盘&#xff0c;出现文档误格式化等问题。 不必要的数据被删除那就没问题&#xff0c;如果…

Java反序列化—Fastjson基础

0x01 前言 最近摆烂了很久&#xff0c;来学习一下fastjson 0x02 Fastjson 简介 Fastjson 是 Alibaba 开发的 Java 语言编写的高性能 JSON 库&#xff0c;用于将数据在 JSON 和 Java Object 之间互相转换。 提供两个主要接口来分别实现序列化和反序列化操作。 JSON.toJSONStr…

socket应用之从电脑发送图片到手机(1)之通信过程建立

本人曾经做了一个基于MPVd的C#开发的播放器&#xff0c;用于自娱自乐&#xff0c;后来又用websocket 写了个简单的远程控制器。由于websocket 要依赖于浏览器&#xff0c;因此有诸多不便&#xff0c;后来又用flutter写了一个&#xff0c;方便多了。 下面介绍具体实现。 1、通信…

RabbitMQ 消息持久化

RabbitMQ 消息持久化 持久化是为提高rabbitmq消息的可靠性&#xff0c;防止在异常情况(重启&#xff0c;关闭&#xff0c;宕机)下数据的丢失。设置完队列和消息的持久化&#xff0c;并不能完全保证消息不会丢失。尽管它告诉 RabbitMQ 将消息保存到磁盘&#xff0c;但当 Rabbit…

mapbox-gl添加threejs飞线

文章目录前言飞线实现1 初始化地图并加载three图层2 绘制飞线几何体将几何体正确定位在mapbox上正确操作BufferGeometry几何体3 tween实现动画全部代码总结待改进之处参考前言 mapbox-gl是一个基于webgl开发的三维地图渲染引擎&#xff0c;但是很多三维特效只用mapbox并不容易…

【CSDN 年终总结】CSDN的进阶之路—— “1+1=王”的2022总结

正文之前打个广告&#xff0c;我正在参加年度博客之星评选&#xff0c;请大家帮我投票打分&#xff0c;您的每一分都是对我的支持与鼓励。⭐ ⭐ ⭐ ⭐ ⭐https://bbs.csdn.net/topics/611386885?spm1001.2014.3001.6953 2022我在CSDN 2022 在CSDN是持续输出&#xff0c;持续…

TinyPng图片压缩的正确打开方式

https://tinypng.com/ TinyPNG使用智能的「有损压缩技术」来减少WEBP、JPEG和PNG文件的文件大小。通过选择性地减少图像中的「颜色数量」&#xff0c;使用更少的字节来存储数据。这种效果几乎是看不见的&#xff0c;但在文件大小上有非常大的差别。 使用过TinyPNG的都知道&…

MyBatis 万字长文:从入门到动态SQL超详细

文章目录1. 前言2. 创建项目3. 添加框架支持4. 建库5. 配置数据库连接信息和 XML 文件路径5.1 创建 Java 类5.2 Java 接口5.3 XML 文件6. 查询6.1 不带参数的查询6.2 单元测试6.3 带参数的查询7. 修改8. 增加8.1 将对象插入表中8.2 获取自增主键9. 删除10. 数据库字段和类属性名…

Video2StyleGAN: Disentangling Local and Global Variations in a Video翻译

点击下载论文 代码地址 摘要 使用预训练的StyleGAN生成器进行图像编辑已成为面部编辑的强大范例&#xff0c;它提供了对年龄、表情、照明度等的解纠缠控制。然而&#xff0c;该方法不能直接用于视频操作。我们认为主要因素是缺乏对面部位置、面部姿势和局部面部表情的精细和清…

腾讯云-云服务器购买流程-Java项目部署(详细的一批)

文章目录云服务器购买云服务搭建部署环境宝塔面板使用&#xff08;安装所需环境&#xff09;部署SpringBoot项目出现Error: Unable to access jarfile /www/wwwroot/xxxx.jar--server.port6066 问题解决腾讯云COS有什么用&#xff1f;如果感觉有用就一键三连吧&#xff0c;创作…

Electron 实现切换暗_亮模式与主题

文章末尾附上仓库地址&#xff01;&#xff01;&#xff01;&#xff01; 清单 模板基于 electron-vite-vue vue3 ts vite组件库 element-plushooks库 vueuse 、useElementPlusTheme 初始化工程 使用 electron-vite 作为模板&#xff0c;方便大家尽快吧项目跑起来 # 创建模…

Java面试之数据库篇

一、基础 1.数据库事务的特征ACID 原子性&#xff08;Atomicity&#xff09;&#xff1a;原子性是指事务包含的所有操作要么全部成功&#xff0c;要么全部失败回滚&#xff0c;这和前面两篇博客介绍事务的功能是一样的概念&#xff0c;因此事务的操作如果成功就必须要完全应用…

UOS服务器操作系统多版本Java切换

一、修改java的环境变量和软链接来实现版本切换 1、配置环境变量 sudo vim &#xff5e;/.bashrc 2、创建java运行程序软连接 3、使配置生效&#xff0c;并检查java版本 source /etc/profile 二、使用update-alternatives 进行版本的切换 1、同时安装了openjdk-8-jdk 和…

shell第七天作业——awk

题目 1、获取根分区剩余大小 2、获取当前机器ip地址 3、统计出apache的/var/log/httpd/access_log文件中访问量最多的前3个IP 4、打印/etc/passwd中UID大于500的用户名和uid 5、/etc/passwd 中匹配包含root或sys或tcp的任意行 6、请打印出/etc/passwd 第一个域&#xff0…

指针进阶之数组参数和指针参数

文章目录一、回顾1.字符指针2.指针数组和数组指针&#xff08;1&#xff09;指针数组&#xff08;2&#xff09;数组指针二、数组参数1.一维数组传参&#xff08;1&#xff09;整型数组&#xff08;2&#xff09;指针数组&#xff08;3&#xff09;总结2.二维数组传参&#xff…

基于Python tensorflow2.3实现的水果识别系统源码+模型+数据集,卷积神经网络的入门案例

水果识别-基于tensorflow2.3实现 水果识别是卷积神经网络的入门案例&#xff0c;这里我将模型的训练、测试、保存以及使用整合在了一起&#xff0c;至于原理部分&#xff0c;大家可以参考知乎或者B站上的回答&#xff0c;在这里我就不赘述了 完整代码下载地址&#xff1a;基于…

计算机网络实验---验证性实验

实验一/ipconfig 实作一 实作二 实验二/ping 实作一 实作二 实验三/tracert 实作一 实作二 实验四/ARP 实作一 实作二 实作二 实验五/DHCP 实作一 实验六/netstat 实作一 实作二 实验七/DNS 实作一 实作二 实作二 实验八/cache 实作一 实作二 总结 实验一/ipconfig 实…

[Leetcode] 二叉树的遍历

转载自&#xff08;有删减和少量改动&#xff09; 图解二叉树的四种遍历 https://leetcode.cn/problems/binary-tree-preorder-traversal/solution/tu-jie-er-cha-shu-de-si-chong-bian-li-by-z1m/1. 相关题目144.二叉树的前序遍历 https://leetcode.cn/problems/binary-tree-p…