linux V4L2子系统——v4l2架构(5)之v4l2_device与v4l2_subdev异步机制_v4l2_devices和v4l2_subdev-程序员宅基地

技术标签: v4l2  # Linux v4l2  linux v4l2  video  

linux V4L2子系统——v4l2架构(5)之v4l2_device与v4l2_subdev异步机制

备注:
  1. Kernel版本:5.4
  2. 使用工具:Source Insight 4.0
  3. 参考博客:
(1)Linux V4L2子系统分析(一)
(2)linux v4l2 学习之-v4l2设备注册过程及各个设备之间的联系

概述

背景

在soc中的视频处理一般由多个ip组成,比如cis_dphy、mipi_cis、isp、sensor等,甚至更多的ip, 这样就导致了v4l2的复杂性。在v4l2中的视频数据流是有方向和顺序的,因此在linux中引入了异步注册机制。异步注册的核心在于设备树引入port接口,在子设备中有一个或多个port接口,port接口就是子设备的纽带。

用途

在异步模式下,子设备 probing 可以被独立地被调用以检查桥驱动是否可用,子设备驱动必须确认所有的 probing 请求是否成功,如果有任意一个请求条件没有满足,驱动就会返回 -EPROBE_DEFER 来继续下一次尝试,一旦所有的请求条件都被满足,子设备就需要调用 v4l2_async_register_subdev 函数来进行注册(用 v4l2_async_unregister_subdev 卸载)。桥驱动反过来得注册一个 notifier 对象(v4l2_async_notifier_register),该函数的第二个参数类型是 v4l2_async_notifier 类型的结构体,里面包含有一个指向指针数组的指针成员,指针数组每一个成员都指向 v4l2_async_subdev 类型结构体。

v4l2 核心层会利用上述的异步子设备结构体描述符来进行子设备的匹配,过程如下:

    1. 如果成功匹配,.bound() 回调函数将会被调用;
    1. 当所有的子设备全部被加载完毕之后,.complete() 回调函数就会被调用;
    1. 子设备被移除的时候 .unbind() 函数就会被调用。

主要结构体介绍

1)匹配类型:

// 源码: include/media/v4l2-async.h
/**
 * enum v4l2_async_match_type - type of asynchronous subdevice logic to be used
 *	in order to identify a match
 *
 * @V4L2_ASYNC_MATCH_CUSTOM: Match will use the logic provided by &struct
 *	v4l2_async_subdev.match ops
 * @V4L2_ASYNC_MATCH_DEVNAME: Match will use the device name
 * @V4L2_ASYNC_MATCH_I2C: Match will check for I2C adapter ID and address
 * @V4L2_ASYNC_MATCH_FWNODE: Match will use firmware node
 *
 * This enum is used by the asyncrhronous sub-device logic to define the
 * algorithm that will be used to match an asynchronous device.
 */
enum v4l2_async_match_type {
	// 传统的匹配方式,使用v4l2_async_subdev的match方法进行匹配
	V4L2_ASYNC_MATCH_CUSTOM,

	// 使用设备名称进行匹配
	V4L2_ASYNC_MATCH_DEVNAME,

	// 使用I2C adapter ID and address进行匹配
	V4L2_ASYNC_MATCH_I2C,

	// 使用firmware node 进行匹配
	V4L2_ASYNC_MATCH_FWNODE,
};

2)async桥——v4l2_async_subdev:

// 源码: include/media/v4l2-async.h

/**
 * struct v4l2_async_subdev - sub-device descriptor, as known to a bridge
 *
 * @match_type:	type of match that will be used
 * @match:	union of per-bus type matching data sets
 * @match.fwnode:
 *		pointer to &struct fwnode_handle to be matched.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_FWNODE.
 * @match.device_name:
 *		string containing the device name to be matched.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_DEVNAME.
 * @match.i2c:	embedded struct with I2C parameters to be matched.
 *		Both @match.i2c.adapter_id and @match.i2c.address
 *		should be matched.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_I2C.
 * @match.i2c.adapter_id:
 *		I2C adapter ID to be matched.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_I2C.
 * @match.i2c.address:
 *		I2C address to be matched.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_I2C.
 * @match.custom:
 *		Driver-specific match criteria.
 *		Used if @match_type is %V4L2_ASYNC_MATCH_CUSTOM.
 * @match.custom.match:
 *		Driver-specific match function to be used if
 *		%V4L2_ASYNC_MATCH_CUSTOM.
 * @match.custom.priv:
 *		Driver-specific private struct with match parameters
 *		to be used if %V4L2_ASYNC_MATCH_CUSTOM.
 * @asd_list:	used to add struct v4l2_async_subdev objects to the
 *		master notifier @asd_list
 * @list:	used to link struct v4l2_async_subdev objects, waiting to be
 *		probed, to a notifier->waiting list
 *
 * When this struct is used as a member in a driver specific struct,
 * the driver specific struct shall contain the &struct
 * v4l2_async_subdev as its first member.
 */
struct v4l2_async_subdev {
	// 匹配方式
	enum v4l2_async_match_type match_type;
	union {
		struct fwnode_handle *fwnode;

		// 设备名称匹配方式
		const char *device_name;
		
		struct {
			// 使用I2C adapter ID and address进行匹配
			int adapter_id;
			unsigned short address;
		} i2c;
		
		struct {
			// 传统的匹配方式
			bool (*match)(struct device *dev,
				      struct v4l2_async_subdev *sd);
			void *priv;
		} custom;
	} match;

	/* v4l2-async core private: not to be used by drivers */
	// v4l2-async核心层使用,将此结构体挂入到notifier的waiting链表,驱动不可使用
	struct list_head list;
	struct list_head asd_list;
};

3)notifier 句柄:

// 源码: include/media/v4l2-async.h

/**
 * struct v4l2_async_notifier_operations - Asynchronous V4L2 notifier operations
 * @bound:	a subdevice driver has successfully probed one of the subdevices
 * @complete:	All subdevices have been probed successfully. The complete
 *		callback is only executed for the root notifier.
 * @unbind:	a subdevice is leaving
 */
struct v4l2_async_notifier_operations {
	// 驱动匹配到从设备后调用此函数
	int (*bound)(struct v4l2_async_notifier *notifier,
		     struct v4l2_subdev *subdev,
		     struct v4l2_async_subdev *asd);

	// 所有从设备被probed成功,调用此函数
	int (*complete)(struct v4l2_async_notifier *notifier);

	// 从设备注销时调用此函数
	void (*unbind)(struct v4l2_async_notifier *notifier,
		       struct v4l2_subdev *subdev,
		       struct v4l2_async_subdev *asd);
};

/**
 * struct v4l2_async_notifier - v4l2_device notifier data
 *
 * @ops:	notifier operations
 * @v4l2_dev:	v4l2_device of the root notifier, NULL otherwise
 * @sd:		sub-device that registered the notifier, NULL otherwise
 * @parent:	parent notifier
 * @asd_list:	master list of struct v4l2_async_subdev
 * @waiting:	list of struct v4l2_async_subdev, waiting for their drivers
 * @done:	list of struct v4l2_subdev, already probed
 * @list:	member in a global list of notifiers
 */
struct v4l2_async_notifier {
	const struct v4l2_async_notifier_operations *ops;

	// 指向struct v4l2_device
	struct v4l2_device *v4l2_dev;
	struct v4l2_subdev *sd;
	
	struct v4l2_async_notifier *parent;
	struct list_head asd_list;

	// v4l2_async_subdev的链表,等待匹配drivers
	struct list_head waiting;
	
	// 已经probed的v4l2_subdev链表
	struct list_head done;

	// 挂在全局的notifiers链表上
	struct list_head list;
};

V4L2主从设备匹配过程分析

V4L2主设备和从设备采用异步的匹配方法。首先介绍一下异步匹配用到的方法。主设备使用 v4l2_async_notifier_register 函数进行异步匹配,匹配到从设备,则调用 v4l2_device_register_subdev 函数注册从设备,使用 v4l2_async_notifier_unregister 函数异步取消匹配。

从设备使用 v4l2_async_register_subdev 函数异步匹配主设备,若匹配到主设备,则调用 v4l2_device_register_subdev 函数注册从设备,使用 v4l2_async_unregister_subdev 函数异步取消匹配。

匹配的方法由 v4l2_async_subdev 结构体决定,主设备可以有多个 v4l2_async_subdev 结构体,也说明主设备有多种匹配从设备的方法。match_type表示匹配方式,由枚举 v4l2_async_match_type 定义,具体有使用设备名称匹配-V4L2_ASYNC_MATCH_DEVNAME、使用I2C adapter ID and address进行匹配-V4L2_ASYNC_MATCH_I2C等。联合体match中包含了具体的匹配信息,根据匹配方式进行设置。v4l2_async_notifier 管理整个匹配过程,未匹配的 v4l2_async_subdev 结构体被挂到waiting链表,匹配完成的挂到 done链表 同时调用 bound函数 进行绑定。

V4L2主设备匹配过程

以sun6i_csi为例分析主设备和从设备的匹配过程。

1)首先初始化需要匹配的 v4l2_async_notifier 结构体,主要设备匹配方式、bound函数、complete函数。示例如下:

a)初始化 v4l2_async_notifier 结构体
b)实现 v4l2_async_notifier ops
c)注册 v4l2_async_notifier

// 源码:  drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
// 源码:  drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h
struct sun6i_csi {
    ......

	struct v4l2_async_notifier	notifier;

	/* video port settings */
	struct v4l2_fwnode_endpoint	v4l2_ep;

    ......
};

static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = {
	.complete = sun6i_subdev_notify_complete,
};

static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
{
    .......

    // 初始化 notifier,实为初始化 notifier 的 asd_list 链表
	v4l2_async_notifier_init(&csi->notifier);

   ......

   // 解析 endpoint,以及调用 v4l2_async_notifier_fwnode_parse_endpoint
   // 申请 asd, match_type = V4L2_ASYNC_MATCH_FWNODE ,
   // 将其添加到 notifier asd_list 链表中
	ret = v4l2_async_notifier_parse_fwnode_endpoints(csi->dev,
							 &csi->notifier,
							 sizeof(struct v4l2_async_subdev),
							 sun6i_csi_fwnode_parse);
	if (ret)
		goto clean_video;

    // 初始化 notifier 的 ops,在此只实现了 complete 函数
	csi->notifier.ops = &sun6i_csi_async_ops;

    // 注册当前 notifier 到 全局 notifier 链表中,并将 当前 notifier 与 V4L2 主设备绑定
	ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
	if (ret) {
		dev_err(csi->dev, "notifier registration failed\n");
		goto clean_video;
	}

	return 0;

   ......
}

(2)设置v4l2_async_notifier的v4l2_dev指针指向主设备的v4l2_device结构体。

// 源码: drivers/media/v4l2-core/v4l2-async.c
int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
				 struct v4l2_async_notifier *notifier)
{
	int ret;

	if (WARN_ON(!v4l2_dev || notifier->sd))
		return -EINVAL;

	// notifier 的 v4l2_dev 指向当前 主设备v4l2_dev
	notifier->v4l2_dev = v4l2_dev;

	// 注册当前 notifier 到 全局 notifier 链表中,
	// 并执行异步机制
	ret = __v4l2_async_notifier_register(notifier);
	if (ret)
		notifier->v4l2_dev = NULL;

	return ret;
}
EXPORT_SYMBOL(v4l2_async_notifier_register);

(3)调用 __v4l2_async_notifier_register, 初始化当前 notifier 中的 waitting、done链表
(4)将当前 notifier asd_list链表中的v4l2_async_subdev,添加到其 waitting链表
(5) 调用 v4l2_async_notifier_try_all_subdevs,匹配 subdev及注册subdev,并调用当前notifier bound函数
(6)匹配完成,调用 v4l2_async_notifier_try_complete进行回调 当前notifier complete函数
(7)将当前 notifier 添加到全局的 notifier_list 链表中


a)__v4l2_async_notifier_register 函数解析:

// 源码: drivers/media/v4l2-core/v4l2-async.c

static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier)
{
	struct v4l2_async_subdev *asd;
	int ret, i = 0;

	INIT_LIST_HEAD(&notifier->waiting);	// 初始化 wait 链表
	INIT_LIST_HEAD(&notifier->done);	// 初始化 done 链表

	mutex_lock(&list_lock);

	// 遍历 asd 链表,查找 v4l2_async_subdev, 并将其添加到 waitting 链表
	list_for_each_entry(asd, &notifier->asd_list, asd_list) {
		ret = v4l2_async_notifier_asd_valid(notifier, asd, i++);
		if (ret)
			goto err_unlock;

		list_add_tail(&asd->list, &notifier->waiting);
	}

	// 匹配 subdev 设备, 及调用 notifier bound函数
	ret = v4l2_async_notifier_try_all_subdevs(notifier);
	if (ret < 0)
		goto err_unbind;

	// 匹配完成,调用 notifier complete函数
	ret = v4l2_async_notifier_try_complete(notifier);
	if (ret < 0)
		goto err_unbind;

	/* Keep also completed notifiers on the list */
	// 将当前 notifier 添加到 全局notifier_list
	list_add(&notifier->list, &notifier_list);

	mutex_unlock(&list_lock);

	return 0;

err_unbind:
	/*
	 * On failure, unbind all sub-devices registered through this notifier.
	 */
	v4l2_async_notifier_unbind_all_subdevs(notifier);

err_unlock:
	mutex_unlock(&list_lock);

	return ret;
}


b)v4l2_async_notifier_try_all_subdevs 函数解析:

// 源码: drivers/media/v4l2-core/v4l2-async.c

/* Test all async sub-devices in a notifier for a match. */
static int
v4l2_async_notifier_try_all_subdevs(struct v4l2_async_notifier *notifier)
{
	struct v4l2_device *v4l2_dev =
		v4l2_async_notifier_find_v4l2_dev(notifier);
	struct v4l2_subdev *sd;

	if (!v4l2_dev)
		return 0;

again:
	// 遍历subdev_list链表,所有从设备的v4l2_subdev结构体都挂到subdev_list链表
	list_for_each_entry(sd, &subdev_list, async_list) {
		struct v4l2_async_subdev *asd;
		int ret;

		// 判断子设备的v4l2_subdev是否和主设备的notifier匹配,
		// 匹配则返回v4l2_async_subdev结构体
		asd = v4l2_async_find_match(notifier, sd);
		if (!asd)
			continue;

		// 注册 subdev,并调用 notifier 的 bound函数
		ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd);
		if (ret < 0)
			return ret;

		/*
		 * v4l2_async_match_notify() may lead to registering a
		 * new notifier and thus changing the async subdevs
		 * list. In order to proceed safely from here, restart
		 * parsing the list from the beginning.
		 */
		goto again;
	}

	return 0;
}

V4L2从设备匹配过程

以ov7251为例,进行分析主设备和从设备的匹配过程。

(1)subdev的异步注册——调用 v4l2_async_register_subdev 异步注册 subdev

//源码: drivers/media/i2c/ov7251.c

static int ov7251_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	struct fwnode_handle *endpoint;
	struct ov7251 *ov7251;
	u8 chip_id_high, chip_id_low, chip_rev;
	int ret;

......

	ret = v4l2_async_register_subdev(&ov7251->sd);
	if (ret < 0) {
		dev_err(dev, "could not register v4l2 device\n");
		goto free_entity;
	}

......

	return ret;
}

(2)遍历全局 notifier_list 获取已注册的 notifier
(3)根据当前获取到的 notifier,调用 v4l2_async_find_match根据 match_type 进行匹配
(4)匹配成功,则调用 v4l2_async_match_notify 注册 subdev,以及回调 notifier 的 bound 函数
(5)bound成功,调用 v4l2_async_notifier_try_complete 回调 notifier 的 complete 函数

v4l2_async_register_subdev 函数分析:

// 源码: drivers/media/v4l2-core/v4l2-async.c

int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_async_notifier *notifier;
	int ret;

	/*
	 * No reference taken. The reference is held by the device
	 * (struct v4l2_subdev.dev), and async sub-device does not
	 * exist independently of the device at any point of time.
	 */
	if (!sd->fwnode && sd->dev)
		sd->fwnode = dev_fwnode(sd->dev);

	mutex_lock(&list_lock);

	// 初始化当前 subdev 的 async_list 链表
	INIT_LIST_HEAD(&sd->async_list);

	// 遍历 全局notifier_list,查找已注册的 notifier
	list_for_each_entry(notifier, &notifier_list, list) {
		// 获取当前 notifier 的主设备 v4l2_device
		struct v4l2_device *v4l2_dev =
			v4l2_async_notifier_find_v4l2_dev(notifier);
		struct v4l2_async_subdev *asd;

		if (!v4l2_dev)
			continue;

		// 调用match函数,进行设备匹配
		// 匹配方式有以下几种:
		// V4L2_ASYNC_MATCH_CUSTOM
		// V4L2_ASYNC_MATCH_DEVNAME
		// V4L2_ASYNC_MATCH_I2C
		// V4L2_ASYNC_MATCH_FWNODE
		asd = v4l2_async_find_match(notifier, sd);
		if (!asd)
			continue;

		// 注册subdev设备,并调用notify中的bound函数
		ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd);
		if (ret)
			goto err_unbind;

		// 调用notify中的complete函数
		ret = v4l2_async_notifier_try_complete(notifier);
		if (ret)
			goto err_unbind;

		goto out_unlock;
	}

	/* None matched, wait for hot-plugging */
	list_add(&sd->async_list, &subdev_list);

out_unlock:
	mutex_unlock(&list_lock);

	return 0;

err_unbind:
	/*
	 * Complete failed. Unbind the sub-devices bound through registering
	 * this async sub-device.
	 */
	subdev_notifier = v4l2_async_find_subdev_notifier(sd);
	if (subdev_notifier)
		v4l2_async_notifier_unbind_all_subdevs(subdev_notifier);

	if (sd->asd)
		v4l2_async_notifier_call_unbind(notifier, sd, sd->asd);
	v4l2_async_cleanup(sd);

	mutex_unlock(&list_lock);

	return ret;
}

V4L2异步注册过程

(1)主从设备 match

// 源码: drivers/media/v4l2-core/v4l2-async.c
static bool match_i2c(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
#if IS_ENABLED(CONFIG_I2C)
	struct i2c_client *client = i2c_verify_client(sd->dev);

	return client &&
		asd->match.i2c.adapter_id == client->adapter->nr &&
		asd->match.i2c.address == client->addr;
#else
	return false;
#endif
}

static bool match_devname(struct v4l2_subdev *sd,
			  struct v4l2_async_subdev *asd)
{
	return !strcmp(asd->match.device_name, dev_name(sd->dev));
}

static bool match_fwnode(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
	return sd->fwnode == asd->match.fwnode;
}

static bool match_custom(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd)
{
	if (!asd->match.custom.match)
		/* Match always */
		return true;

	return asd->match.custom.match(sd->dev, asd);
}

static struct v4l2_async_subdev *
v4l2_async_find_match(struct v4l2_async_notifier *notifier,
		      struct v4l2_subdev *sd)
{
	// 定义匹配的函数指针
	bool (*match)(struct v4l2_subdev *sd, struct v4l2_async_subdev *asd);
	struct v4l2_async_subdev *asd;

	list_for_each_entry(asd, &notifier->waiting, list) {
		/* bus_type has been verified valid before */
		// 确认匹配方式
		switch (asd->match_type) {
		case V4L2_ASYNC_MATCH_CUSTOM:	// 传统的匹配方式
			match = match_custom;
			break;
		case V4L2_ASYNC_MATCH_DEVNAME:	// 设备名称匹配方法
			match = match_devname;
			break;
		case V4L2_ASYNC_MATCH_I2C:		// i2c 匹配方法
			match = match_i2c;
			break;
		case V4L2_ASYNC_MATCH_FWNODE:	// firmware node 匹配方法
			match = match_fwnode;
			break;
		default:
			/* Cannot happen, unless someone breaks us */
			WARN_ON(true);
			return NULL;
		}

		/* match cannot be NULL here */
		// 根据匹配方式,调用相应的匹配方法
		if (match(sd, asd))
			return asd;
	}

	return NULL;
}


(2)主从设备 bound

// 源码: drivers/media/v4l2-core/v4l2-async.c

static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
				   struct v4l2_device *v4l2_dev,
				   struct v4l2_subdev *sd,
				   struct v4l2_async_subdev *asd)
{
	struct v4l2_async_notifier *subdev_notifier;
	int ret;

	// 注册 subdev
	ret = v4l2_device_register_subdev(v4l2_dev, sd);
	if (ret < 0)
		return ret;

	// bound非空,则调用bound函数,
	// bound函数 的主要作用是设置主设备的v4l2_subdev指针,
	// 使其指向匹配的从设备的v4l2_subdev结构体,从而完成主设备到从设备的绑定
	ret = v4l2_async_notifier_call_bound(notifier, sd, asd);
	if (ret < 0) {
		v4l2_device_unregister_subdev(sd);
		return ret;
	}

	/* Remove from the waiting list */
	// 将 asd 从 waitting链表 移除
	list_del(&asd->list);

	// subdev 绑定 asd 及 notifier
	sd->asd = asd;
	sd->notifier = notifier;

	/* Move from the global subdevice list to notifier's done */
	// 将subdevice从async_list链表中移除后挂到done链表中
	list_move(&sd->async_list, &notifier->done);

	/*
	 * See if the sub-device has a notifier. If not, return here.
	 */
	// 检查 subdev 是否有 notifier,有则调用,无则到此,就地返回
	subdev_notifier = v4l2_async_find_subdev_notifier(sd);
	if (!subdev_notifier || subdev_notifier->parent)
		return 0;

	/*
	 * Proceed with checking for the sub-device notifier's async
	 * sub-devices, and return the result. The error will be handled by the
	 * caller.
	 */
	subdev_notifier->parent = notifier;

	return v4l2_async_notifier_try_all_subdevs(subdev_notifier);
}


(3)主从设备complete

// 源码: drivers/media/v4l2-core/v4l2-async.c

/*
 * Complete the master notifier if possible. This is done when all async
 * sub-devices have been bound; v4l2_device is also available then.
 */
static int
v4l2_async_notifier_try_complete(struct v4l2_async_notifier *notifier)
{
	/* Quick check whether there are still more sub-devices here. */
	if (!list_empty(&notifier->waiting))
		return 0;

	/* Check the entire notifier tree; find the root notifier first. */
	while (notifier->parent)
		notifier = notifier->parent;

	/* This is root if it has v4l2_dev. */
	if (!notifier->v4l2_dev)
		return 0;

	/* Is everything ready? */
	if (!v4l2_async_notifier_can_complete(notifier))
		return 0;

	return v4l2_async_notifier_call_complete(notifier);
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013836909/article/details/125360143

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签