input子系统中读取流程解析

往期内容

本专栏往期内容:

  1. input子系统的框架和重要数据结构详解-CSDN博客
  2. input device和input handler的注册以及匹配过程解析-CSDN博客

I2C子系统专栏:

  1. 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有往期内容观看顺序

前言

参考:

  • \Linux-4.9.88\Linux-4.9.88\drivers\input.c📎input.c – input.c
  • \Linux-4.9.88\Linux-4.9.88\drivers\evdev.c📎evdev.c – input handler
  • \Linux-4.9.88\drivers\input\keyboard📎gpio_keys.c – input device

本节主要介绍input子系统中读取流程的相关实现代码的解析。

建议先看完一下input子系统的相关结构体介绍:input device和input handler的注册以及匹配过程解析-CSDN博客

1.接口

先来看一下evdev.c设备驱动程序中提供给上层的api接口:

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,
};

同时,还是牢记下面这张图:

img

2.open

APP调用open函数打开/dev/input/event0

  • 在驱动程序evdev_open里,创建一个evdev_client,表示一个"客户"
static int evdev_open(struct inode *inode, struct file *file)
{
    // 通过 inode 获取关联的 evdev 结构体指针
    struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);

    // 计算输入设备缓冲区大小,依赖于设备的属性
    unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);

    // 确定 evdev_client 结构体的总大小,包含客户端结构体和缓冲区
    unsigned int size = sizeof(struct evdev_client) +
                        bufsize * sizeof(struct input_event);

    // 用于保存新创建的 evdev_client 指针
    struct evdev_client *client;

    // 错误代码变量
    int error;

    // 尝试分配内存,用于存储 evdev 客户端和缓冲区(使用 GFP_KERNEL 分配)
    client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);

    // 如果分配失败,则尝试使用 vzalloc 分配虚拟内存
    if (!client)
        client = vzalloc(size);

    // 如果仍然分配失败,则返回内存不足错误
    if (!client)
        return -ENOMEM;

    // 设置客户端的缓冲区大小
    client->bufsize = bufsize;

    // 初始化客户端的自旋锁,保护缓冲区的访问
    spin_lock_init(&client->buffer_lock);

    // 将 evdev 设备指针保存到客户端结构体中
    client->evdev = evdev;

    // 将客户端附加到 evdev 设备上,因为一个输入设备evdev是可以被多个程序evdev_client打开的
    evdev_attach_client(evdev, client);

    // 尝试打开设备,并准备接受事件
    error = evdev_open_device(evdev);

    // 如果打开设备失败,进行错误处理
    if (error)
        goto err_free_client;

    // 将客户端结构体指针保存到文件私有数据中,以供后续使用
    file->private_data = client;

    // 设置文件为非可寻址文件类型,因为输入设备通常不支持文件定位
    nonseekable_open(inode, file);

    // 成功返回 0
    return 0;

    // 如果打开设备失败,进行清理
 err_free_client:
    // 将客户端从 evdev 设备上移除
    evdev_detach_client(evdev, client);

    // 释放分配的客户端内存
    kvfree(client);

    // 返回错误代码
    return error;
}

里面提到了evdev和evdev_client,这个是啥? 它们在 Linux 内核的输入子系统中用于实现事件设备的管理。evdev 结构体代表一个输入设备,evdev_client 结构体代表与该设备关联的客户端,通常是用户空间中的程序。通俗点就是,在input_dev下层中,用input_dev来表示一个输入设备,在input_handler上层中,用evdev来表示一个输入设备

  • evdev 结构体管理输入设备本身,维护与该设备的所有客户端的连接。
  • evdev_client 结构体代表打开设备的每个客户端,负责处理设备发送的事件并存储在其环形缓冲区中。
struct evdev {
    int open;  // 设备是否已经被打开的计数器,如果大于0表示有客户端打开了设备
    struct input_handle handle;  // 用于与输入子系统交互的句柄
    wait_queue_head_t wait;  // 等待队列,用于同步客户端和设备之间的事件处理
    struct evdev_client __rcu *grab;  // 表示当前抓取设备的客户端,通常用于独占设备的客户端
    struct list_head client_list;  // 链表头,用于管理当前连接到设备的所有客户端
    spinlock_t client_lock;  // 保护 client_list 的自旋锁,确保多客户端的并发安全
    struct mutex mutex;  // 保护设备状态的互斥锁,防止设备的并发访问问题
    struct device dev;  // 设备模型中的 device 结构,表示该 evdev 设备
    struct cdev cdev;  // 字符设备结构,允许该设备通过字符设备文件与用户空间交互
    bool exist;  // 设备是否存在的标志,用于检查设备是否被移除
};

struct evdev_client {
    unsigned int head;  // 缓冲区的头部索引,指向最旧的未读取事件
    unsigned int tail;  // 缓冲区的尾部索引,指向下一个可以写入事件的位置
    unsigned int packet_head;  // [未来功能] 指向下一个完整数据包的起始位置
    spinlock_t buffer_lock;  // 自旋锁,用于保护缓冲区的并发访问
    struct fasync_struct *fasync;  // 异步通知的结构体,用于支持信号通知
    struct evdev *evdev;  // 该客户端关联的 evdev 设备指针
    struct list_head node;  // 链表节点,挂载到 evdev 的 `client_list` 上
    unsigned int clk_type;  // 客户端时钟类型
    bool revoked;  // 标志客户端是否被撤销(比如设备被移除时)
    unsigned long *evmasks[EV_CNT];  // 用于跟踪设备事件掩码的数组
    unsigned int bufsize;  // 缓冲区大小
    struct input_event buffer[];  // 环形缓冲区,用于存储输入事件
};

3.read/poll

  • APP调用read/poll读取、等待数据

    • 没有数据时休眠:wait_event_interruptible(evdev->wait, …)
static ssize_t evdev_read(struct file *file, char __user *buffer,
			  size_t count, loff_t *ppos)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	struct input_event event;
	size_t read = 0;
	int error;

	if (count != 0 && count < input_event_size())
        //检查用户请求的读取字节数 count 是否有效。
		return -EINVAL;

	for (;;) {
		if (!evdev->exist || client->revoked)
            //该部分检查设备是否仍然存在,或者客户端是否已被撤销(例如设备被移除)。
			return -ENODEV;

		if (client->packet_head == client->tail &&
		    (file->f_flags & O_NONBLOCK))
            //如果事件缓冲区为空(即 packet_head 等于 tail),并且文件被打开时设置了 O_NONBLOCK 标志,表示调用者不希望阻塞等待事件。
            //不是阻塞等待,数据为空,直接返回
			return -EAGAIN;

		/*
		 * count == 0 is special - no IO is done but we check
		 * for error conditions (see above).
		 */
		if (count == 0)
			break;

		while (read + input_event_size() <= count &&
		       evdev_fetch_next_event(client, &event)) {

			if (input_event_to_user(buffer + read, &event))
				return -EFAULT;

			read += input_event_size();
		}
        //通过 evdev_fetch_next_event() 从缓冲区中获取下一个输入事件,将事件复制到 event 结构体中
        //并使用 input_event_to_user() 函数将事件复制到用户提供的缓冲区中。

		if (read)
			break;

		if (!(file->f_flags & O_NONBLOCK)) {
			error = wait_event_interruptible(evdev->wait,
					client->packet_head != client->tail ||
					!evdev->exist || client->revoked);
            //如果启用了阻塞模式(没有设置 O_NONBLOCK 标志),当缓冲区为空时,代码会调用 wait_event_interruptible(),等待新的事件到来或者设备状态发生变化。
			if (error)
				return error;
		}
	}

	return read;
}

4.event

  • 点击、操作输入设备,产生中断:input_dev硬件层,这里以内核自带的gpio_keys.c为例:
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
	struct gpio_button_data *bdata = dev_id;
	const struct gpio_keys_button *button = bdata->button;
	struct input_dev *input = bdata->input;
	unsigned long flags;

	BUG_ON(irq != bdata->irq);

	spin_lock_irqsave(&bdata->lock, flags);

	if (!bdata->key_pressed) {
		if (bdata->button->wakeup)
			pm_wakeup_event(bdata->input->dev.parent, 0);

		input_event(input, EV_KEY, button->code, 1);//上报中断事件
		input_sync(input);//本质也是input_event

		if (!bdata->release_delay) {
			input_event(input, EV_KEY, button->code, 0);  
			input_sync(input); 
			goto out;
		}

		bdata->key_pressed = true;
	}

	if (bdata->release_delay)
		mod_timer(&bdata->release_timer,
			jiffies + msecs_to_jiffies(bdata->release_delay));
out:
	spin_unlock_irqrestore(&bdata->lock, flags);
	return IRQ_HANDLED;
}
  • input_event(input, EV_KEY, button->code, 1);:在中断服务程序input_event

    • 从硬件读取到数据
    • 使用input_handle_event/input_sync函数上报数据
\Linux-4.9.88\drivers\input\input.c:
void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;

	if (is_event_supported(type, dev->evbit, EV_MAX)) {

		spin_lock_irqsave(&dev->event_lock, flags);
		input_handle_event(dev, type, code, value);//处理输入事件,并决定如何将其分发给设备和输入处理程序。
        // ----------------------(1)
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}
  • input_event做什么?

    • 从dev->h_list中取出input_handle,从input_handle取出input_handler

    • 优先调用input_handler->filter来处理

    • 如果没有input_handler->filter或者没处理成功

      • 调用input_handler->events
      • 没有input_handler->events的话,调用input_handler->event
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);
    //根据设备的状态和输入事件的类型,返回事件的处理方式,称为 disposition

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		add_input_randomness(type, code, value);
    //如果事件不应被忽略(INPUT_IGNORE_EVENT)且不是同步事件(EV_SYN),则调用 add_input_randomness() 增加输入随机性。

	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);
    //如果 disposition 标记为 INPUT_PASS_TO_DEVICE,并且设备有注册事件处理函数(dev->event),则将事件传递给设备的事件处理程序。

	if (!dev->vals)//检查设备是否存储事件
		return;

    //将事件传递给处理程序
	if (disposition & INPUT_PASS_TO_HANDLERS) {
		struct input_value *v;

		if (disposition & INPUT_SLOT) {
			v = &dev->vals[dev->num_vals++]; //和input_dev关联上,对v操作就是对dev->vals操作
			v->type = EV_ABS; //事件类型
			v->code = ABS_MT_SLOT; //事件类型下的哪一种
			v->value = dev->mt->slot; //事件的值
            //这里是用input_value来表示一个事件
		}

		v = &dev->vals[dev->num_vals++];
		v->type = type;
		v->code = code;
		v->value = value;
	}
    /*
    如果事件需要传递给输入处理程序(INPUT_PASS_TO_HANDLERS),则事件被存储到 dev->vals 中。这里通过 dev->num_vals++ 依次递增存储事件值。
        如果 disposition 包含 INPUT_SLOT,表示多点触控设备需要处理槽位信息,会首先存储槽位事件(ABS_MT_SLOT)。
        然后存储当前事件的 type、code 和 value。
    */

    //检查是否需要刷新或同步事件
	if (disposition & INPUT_FLUSH) {
		if (dev->num_vals >= 2)
			input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 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);  //------(1)
		dev->num_vals = 0;
	}
    //如果 disposition 包含 INPUT_FLUSH,表示应立即传递事件。此时,如果存储的事件数量(dev->num_vals)达到 2 个或以上,调用 input_pass_values() 将事件传递给所有注册的处理程序。
    //如果事件数量接近设备存储区的上限 (dev->max_vals - 2),则首先添加一个同步事件(input_value_sync),然后调用 input_pass_values() 将事件传递出去。最后重置事件计数器 dev->num_vals。
    

}

其中(1)input_pass_values(dev, dev->vals, dev->num_vals)进行传输事件:

\Linux-4.9.88\drivers\input\input.c
static void input_pass_values(struct input_dev *dev,
			      struct input_value *vals, unsigned int count)
{
	struct input_handle *handle;
	struct input_value *v;

	if (!count)
		return;//没有需要处理的事件。

	rcu_read_lock();//使用 RCU(Read-Copy-Update)锁来确保数据的一致性。

    //检查设备是否有处理程序
	handle = rcu_dereference(dev->grab);
	if (handle) {
		count = input_to_handler(handle, vals, count); //-------------(2)
	} 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;
			}
	}
    /*
    首先,通过 rcu_dereference() 检查设备是否有 “grab” 的处理程序(即,独占处理事件的处理程序)
        如果有,则调用 input_to_handler() 将事件传递给它。返回的 count 表示处理程序还剩下多少未处理的事件(可能部分事件已处理)。
    如果设备没有 grab 处理程序,则遍历设备的处理程序链表(dev->h_list)。对于每个已打开的处理程序(handle->open 为真),
        调用 input_to_handler() 将事件传递过去。如果事件被处理完毕(count == 0),则终止循环。
    */

    //读取保护区退出
	rcu_read_unlock();

	//处理按键自动重复(auto-repeat)
	if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {
    //首先检查设备是否支持自动重复(EV_REP)并且事件类型为按键(EV_KEY)。
		for (v = vals; v != vals + count; v++) {
            //然后遍历所有剩余未处理的事件,检查每个事件的类型是否为按键(EV_KEY)且其值不为 2(2 表示自动重复按键事件,不在这里处理)。
			if (v->type == EV_KEY && v->value != 2) {
				if (v->value)
					input_start_autorepeat(dev, v->code);
				else
					input_stop_autorepeat(dev);
                //如果按键事件值为 1(按下),则启动自动重复功能(input_start_autorepeat()),如果按键事件值为 0(松开),则停止自动重复功能(input_stop_autorepeat())。
			}
		}
	}
}

(2)其中count = input_to_handler(handle, vals, count) 将事件传递给handler处理程序,内部会承上到input_handler层,调用input_handler中的filter/events处理事件函数:

static unsigned int input_to_handler(struct input_handle *handle,
			struct input_value *vals, unsigned int count)
{
	// 获取处理当前事件的 handler
	struct input_handler *handler = handle->handler;

	// 定义一个指向事件值数组的指针,初始指向 vals
	struct input_value *end = vals;
	struct input_value *v;

	// 如果处理程序 handler 定义了一个过滤函数
	if (handler->filter) {
		// 遍历所有事件
		for (v = vals; v != vals + count; v++) {
			// 通过过滤函数决定是否处理该事件
			// 如果返回非零值,则跳过该事件(继续处理下一个)
			if (handler->filter(handle, v->type, v->code, v->value))
				continue;
			// 如果事件通过过滤,将其复制到 end 指针位置
			// 这样可以过滤掉无效的事件
			if (end != v)
				*end = *v;
			// end 指针向前移动
			end++;
		}
		// 更新 count 为过滤后剩余的事件数量
		count = end - vals;
	}

	// 如果没有任何剩余事件,则返回 0
	if (!count)
		return 0;

	// 如果处理程序 handler 定义了批量处理函数 `events`
	if (handler->events)
		// 调用批量处理函数处理事件
		handler->events(handle, vals, count);
	// 如果没有批量处理函数,但定义了单事件处理函数 `event`
	else if (handler->event)
		// 遍历所有事件,调用单事件处理函数处理每个事件
		for (v = vals; v != vals + count; v++)
			handler->event(handle, v->type, v->code, v->value);

	// 返回剩余事件的数量
	return count;
}
  • 以evdev.c为例

    • 它有evdev_events:用来处理多个事件
    • 也有evdev_event:实质还是调用evdev_events
    • 唤醒"客户":wake_up_interruptible(&evdev->wait);
\Linux-4.9.88\drivers\input\evdev.c
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 void evdev_events(struct input_handle *handle,
			 const struct input_value *vals, unsigned int count)
{
	// 获取与该事件设备关联的 evdev 结构
	struct evdev *evdev = handle->private;

	// 定义一个指针指向当前客户端
	struct evdev_client *client;

	// 创建一个数组来存储事件发生的时间,数组大小为 3(EV_CLK_MAX)
	// 分别存储 MONO、REAL 和 BOOT 时钟时间
	ktime_t ev_time[EV_CLK_MAX];

	// 获取当前的单调(MONOTONIC)时间
	ev_time[EV_CLK_MONO] = ktime_get();

	// 将单调时间转换为实际时间(REAL),并存储在相应的数组位置
	ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);

	// 将单调时间转换为启动时间(BOOT),并存储在相应的位置
	ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
						 TK_OFFS_BOOT);

	// 进入 RCU 读取临界区
	rcu_read_lock();

	// 尝试获取当前正在 "抓取" 设备的客户端
	client = rcu_dereference(evdev->grab);

	// 如果有客户端 "抓取" 了设备,则将事件传递给该客户端
	if (client)
		evdev_pass_values(client, vals, count, ev_time);//主要是看这里-----(3)
	else
		// 否则,将事件传递给所有连接的客户端
		list_for_each_entry_rcu(client, &evdev->client_list, node)
			evdev_pass_values(client, vals, count, ev_time); 

	// 退出 RCU 读取临界区
	rcu_read_unlock();
}

其中evdev_pass_values(client, vals, count, ev_time);

static void evdev_pass_values(struct evdev_client *client,
			const struct input_value *vals, unsigned int count,
			ktime_t *ev_time)
{
	struct evdev *evdev = client->evdev;
	const struct input_value *v;
	struct input_event event;
	bool wakeup = false;

	if (client->revoked)
		return;

	event.time = ktime_to_timeval(ev_time[client->clk_type]);

	/* Interrupts are disabled, just acquire the lock. */
	spin_lock(&client->buffer_lock);

	for (v = vals; v != vals + count; v++) {
		if (__evdev_is_filtered(client, v->type, v->code))
			continue;

		if (v->type == EV_SYN && v->code == SYN_REPORT) {
			/* drop empty SYN_REPORT */
			if (client->packet_head == client->head)
				continue;

			wakeup = true;
		}

		event.type = v->type;
		event.code = v->code;
		event.value = v->value; //将传来的input_value“装进”input_event
        //input_event在上层用于表示一个事件,input_value主要是中层传递给上层用的
		__pass_event(client, &event);
	}

	spin_unlock(&client->buffer_lock);

	if (wakeup)
		wake_up_interruptible(&evdev->wait); //唤醒休眠的客户端,在read中有提到没数据读了就休眠
}

5.总结

img

内容涉及了 Linux 的 input 子系统,包括 input_handler、evdev、evdev_client 等结构体的实现细节,尤其是 evdev 设备驱动的接口和数据传递的工作机制。

  1. 输入子系统的整体框架和接口:

    • evdev 设备驱动程序通过 struct file_operations evdev_fops 向用户层提供接口,包括 .open.read.write 等。evdev_open 用于在 APP 调用时打开 /dev/input/event 设备,创建并关联一个 evdev_client 客户端实例,实现事件设备的多客户端支持。
  2. 事件读取和事件处理:

    • evdev_read 负责从内核读取输入事件,遇到没有事件时阻塞等待;通过 wait_event_interruptible 来等待事件,利用 input_handle_event 进行数据传输。
    • gpio_keys_irq_isr 实现了 GPIO 按键输入,依靠 input_event 产生事件并调用 input_handle_event 分发事件,经过 input_to_handler 传递给上层的 input_handler
  3. input_event 和 input_handler 层的细化调用流程:

    • input_event 负责从硬件读取事件,将其封装为 input_value 对象,并通过 input_pass_values 传输至上层 input_handler
    • input_to_handler 将事件传递给实际的事件处理程序,支持过滤(filter)、批量处理(events)和逐个处理(event)。
  4. evdev 驱动的事件批量处理函数:

    • 通过 evdev_eventsevdev_event 函数,将多个事件批量上报到用户空间的 APP。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

找不到包的老版本???scikit-learn,numpy,scipy等等!!

废话不多说 直接上链接了&#xff1a; https://pypi.tuna.tsinghua.edu.cn/simple/https://pypi.tuna.tsinghua.edu.cn/simple/https://pypi.tuna.tsinghua.edu.cn/simple/xxx/ 后面的这个xxx就是包的名字 大家需要什么包的版本&#xff0c;直接输进去就可以啦 举个栗子&#…

银河麒麟相关

最近安装了银河麒麟server版本&#xff0c;整理下遇到的一些小问题 1、vmware安装Kylin-Server-V10-SP3-General-Release-2303-X86_64虚拟机完成后&#xff0c;桌面窗口很小&#xff0c;安装vmwaretools后解决&#xff0c;下载地址http://softwareupdate.vmware.com/cds/vmw-de…

听见文本的魅力:AI 与未来的语音交互

AI 与未来的语音交互 引言什么是文本转语音&#xff08;TTS&#xff09;&#xff1f;当前 TTS 技术现状国内海外文本转语音能力调研文本转语音能力说明多情感风格SSML语音合成标记语言 未来趋势 引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;文本转…

全流程演示:如何从0到1构建分布式GPU计算环境

【温馨提示&#xff1a;本篇文章涉及较多代码&#xff0c;请移步至wx公号星融元Asterfusion&#xff0c;发送关键词“分布式”获取完整版及Q&A】 随着AI、大模型的快速发展&#xff0c;传统的集中式计算已无法应对激增的数据处理需求&#xff0c;而分布式计算是指将一个计…

VTK的学习方法-第一类型应用

相信很多做研究的人都在使用VTK&#xff0c;其实VTK的学习分成两类。一类就是使用VTK中现有的算法来完成自己的工作&#xff0c;比如数据的显示和渲染。另外一类是需要继承里面的算法类&#xff0c;自己根据自己的需求来重新写一个算法。 对于第一种类型的应用&#xff0c;不要…

E43.【C语言】练习:传值调用和传址调用混淆点解释

1.问题 阅读下列代码,在Function函数返回前,px的值会因malloc的返回值而发生改变吗? #include <stdlib.h> void Function(int* ptr) {ptr (int*)malloc(sizeof(int)); }int main() {int* px NULL;Function(px);return 0; }2.分析 VS2022进入调试模式,下断点在retur…

利用客户端导入有关联的业务数据(DBeaver+sql)

前言 最近有点坑&#xff0c;麻辣烫的活落手上了&#xff0c;上个迭代除了自己的开发任务&#xff0c;还有处理接手的工作。然后节后问题又多&#xff0c;还有前1个迭代没有测试的模块本迭代测试&#xff0c;烦死了。 这次这个数据处理的活&#xff0c;以后希望可以交出…

Android 判断手机放置的方向

#1024程序员节&#xff5c;征文# 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 需求 老板&#xff1a;我有个手持终端&#xff0c;不能让他倒了&#xff0c;当他倒或者倾斜的时候要发出报警&#xff1b; 程序猿&#xff1a;我这..... 老板…

uniapp 常用的地区行业各种多选多选,支持回显,复制粘贴可使用

uniapp 常用的地区行业各种多选多选&#xff0c;支持回显 必须导入uni-popup 弹出层 该组件 1.目前项目开发中使用到这类似挺多的&#xff0c;记录一下&#xff0c;方便以后是使用 2.使用前提&#xff0c;目前不做无限级&#xff0c;只支持二维数组&#xff0c;模板里只循环了两…

GitLab+Jenkins 实现 Webhook 自动化触发构建

在持续集成和持续部署&#xff08;CI/CD&#xff09;过程中&#xff0c;如何实现代码提交后自动触发构建&#xff1f;今天&#xff0c;我们将通过GitLab与Jenkins的集成&#xff0c;利用Webhook实现自动化触发构建&#xff0c;为你的开发流程注入高效能量&#xff01; 在每次代…

从零开始:AI制作PPT工具大比拼

现在真的万物皆可AI了&#xff0c;甚至是令人头疼的PPT&#xff0c;也可以直接用AI来搞定了。作为一个PPT新手&#xff0c;我最近对AI制作PPT这个话题产生了浓厚的兴趣。我决定亲自试一试市面上几款热门的AI制作PPT工具&#xff1a;笔灵AIPPT、轻竹PPT、博思白板AIPPT和KimiAI。…

GPT-4o 和 GPT-4 Turbo 模型之间的对比

GPT-4o 和 GPT-4 Turbo 之间的对比 备注 要弄 AI &#xff0c;不同模型之间的对比就比较重要。 GPT-4o 是 GPT-4 Turbo 的升级版本&#xff0c;能够提供比 GPT-4 Turbo 更多的内容和信息&#xff0c;但成功相对来说更高一些。 第三方引用 在 2024 年 5 月 13 日&#xff0…

kaggle在线训练深度学习模型

kaggle https://www.kaggle.com/ code 通过jupyter notebook上传代码&#xff0c;执行训练 dataset 支持手动上传本地资源文件到input /kaggle/input&#xff0c;dataset通过input访问&#xff0c;input目录是只读的 /kaggle/working&#xff0c;保存文件到working&#…

多厂商的实现不同vlan间通信

Cisco单臂路由 Cisco路由器配置 -交换机配置 -pc配置 华三的单臂路由 -路由器配置 -华三的接口默认是打开的 -pc配置及ping的结果 -注意不要忘记配置默认网关 Cisco-SVI -交换机的配置 -创建vlan -> 设置物理接口对应的Acess或Trunk -> 进入vlan接口&#xff0c;打开接…

ctfshow-web入门-web172

//拼接sql语句查找指定ID用户 $sql "select username,password from ctfshow_user2 where username !flag and id ".$_GET[id]." limit 1;"; 联合查询 该题目与上一个题目不是同一个类型&#xff0c;该题目需要进行sql联合查询。 第一步&#xff1a;确…

微信小程序中关闭默认的 `navigationBar`,并使用自定义的 `nav-bar` 组件

要在微信小程序中关闭默认的 navigationBar&#xff0c;并使用自定义的 nav-bar 组件&#xff0c;你可以按照以下步骤操作&#xff1a; 1. 关闭默认的 navigationBar 在你的页面的配置文件 *.json 中设置 navigationBar 为 false。你需要在页面的 JSON 配置文件中添加以下代码…

Echarts_柱状图属性汇总

目录 1、基础 柱状图属性 2、常用 柱状图属性 3、双Y轴双柱 柱状图属性 4、渐变圆角 柱状图属性 5、横向渐变圆角 柱状图属性 6、嵌套圆角 柱状图属性 7、堆叠 柱状图属性 1、基础 柱状图属性 var myChart echarts.init(document.getElementById(charts)); var option …

二叉树的基本概念及运用

二叉树的概念&#xff1a;一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1.或者为空 2.或者是由一个根节点加上两棵别称为为左子树和右子树的二叉树组成。 2.2&#xff1a; 两种特殊的二叉树&#xff1a; 1.满二叉树&#xff1a;一课二叉树&#xff0c;如…

Z-BlogPHP显示错误Undefined array key 0 (set_error_handler)的解决办法

今天打开博客的时候&#xff0c;意外发现页面&#xff0c;打开均显示错误&#xff1a;Undefined array key 0 (set_error_handler)。 博客程序采用的是Z-BlogPHP。百度了一圈没有找到解决办法&#xff0c;在官方论坛里也没找到解决办法。 于是开始自己排查原因。我服务器采用…

问:MySQL表过大,你有哪些优化实践?

当MySQL单表记录数过大时&#xff0c;数据库的CRUD&#xff08;创建、读取、更新、删除&#xff09;性能会明显下降。为了提升性能&#xff0c;我们需要采取一些优化措施。本文将详细介绍几种常见的优化方案。 1. 限定数据的范围 描述 务必禁止不带任何限制数据范围条件的查…