RDMA驱动学习(二)- command queue
为了实现用户对网卡硬件的配置,查询,或者执行比如create_cq等命令,mellanox网卡提供了command queue + mailbox的机制,本节将以create_cq为例看下这个过程。
command queue(后续简称cmdq)是一个4K对齐的长度为4K的连续物理内存,如图1所示,cmdq中有多个entry,entry的数量和entry的size都是从initialization segment中读到的。
entry格式如图2,软件的每一个cmd,会对应cmdq中的一个entry,cmd的input会存到entry中,如果input小于16B,那么就直接存储到command_input_inline_data,如果大于16B,那么剩余的会存到图一中的mailbox里,mailbox是一个链表结构,每个mailbox可以容纳512B的数据,第一个mailbox的指针存储在entry的input mailbox pointer。
将输入存到entry和对应的mailbox之后,软件会写cmdq的doorbell,doorbell位于initialization segment,可以理解为一个vector,软件会将这次entry对应的index写入到这个vector对应的bit通知硬件执行。硬件执行cmdq中entry的顺序是不确定的,初始化entry时,entry的ownership会被设置为HW,当硬件执行完成后,ownership会被重新设置为SW,因此软件可以轮询ownership确定硬件是否完成。
然后看下驱动中是怎么做的。
初始化
mlx5_cmd_init主要工作就是创建command queue的buf,然后将这块内存的总线地址告诉网卡硬件。
通过dma_pool_create创建dma内存池,每个内存块大小为sizeof(mlx5_cmd_prot_block),一个内存块表示了一个mailbox,mlx5_cmd_prot_block类型用于表示mailbox。
int mlx5_cmd_init(struct mlx5_core_dev *dev)
{int size = sizeof(struct mlx5_cmd_prot_block);int align = roundup_pow_of_two(size);struct mlx5_cmd *cmd = &dev->cmd;...cmd->pool = dma_pool_create("mlx5_cmd", mlx5_core_dma_dev(dev), size, align, 0);err = alloc_cmd_page(dev, cmd);...
}
然后通过dma_zalloc_coherent分配大小为一页的一致性dma映射,这块内存就是command queue的buf,虚拟地址保存在cmd_alloc_buf,dma地址保存到dma,同时还需要保证这个地址是页对齐的。如果不对齐,还需要重新分配一次大小为两页的dma映射,从而保证页对齐。
static int alloc_cmd_page(struct mlx5_core_dev *dev, struct mlx5_cmd *cmd)
{cmd->cmd_alloc_buf = dma_zalloc_coherent(mlx5_core_dma_dev(dev), MLX5_ADAPTER_PAGE_SIZE, &cmd->alloc_dma, GFP_KERNEL);.../* make sure it is aligned to 4K */if (!((uintptr_t)cmd->cmd_alloc_buf & (MLX5_ADAPTER_PAGE_SIZE - 1))) {cmd->cmd_buf = cmd->cmd_alloc_buf;cmd->dma = cmd->alloc_dma;cmd->alloc_size = MLX5_ADAPTER_PAGE_SIZE;return 0;} dma_free_coherent(mlx5_core_dma_dev(dev), MLX5_ADAPTER_PAGE_SIZE, cmd->cmd_alloc_buf,cmd->alloc_dma);cmd->cmd_alloc_buf = dma_zalloc_coherent(mlx5_core_dma_dev(dev),2 * MLX5_ADAPTER_PAGE_SIZE - 1, &cmd->alloc_dma, GFP_KERNEL);...cmd->cmd_buf = PTR_ALIGN(cmd->cmd_alloc_buf, MLX5_ADAPTER_PAGE_SIZE);cmd->dma = ALIGN(cmd->alloc_dma, MLX5_ADAPTER_PAGE_SIZE);cmd->alloc_size = 2 * MLX5_ADAPTER_PAGE_SIZE - 1;return 0;
}
然后从Initialization Segment(后续简称iseg)读出cmdq_addr_l_sz,然后解析出低位的log_sz和log_stride,log_sz以log形式表示cmdq一共有多少个entry,log_stride以log形式表示cmdq的一个entry大小。然后将cmdq的dma地址写到iseg,这样硬件就知道cmdq的地址了。最后设置mode为CMD_MODE_POLLING,创建一个单线程的workqueue。到这里初始化就完成了。
int mlx5_cmd_init(struct mlx5_core_dev *dev)
{...cmd_l = ioread32be(&dev->iseg->cmdq_addr_l_sz) & 0xff;cmd->log_sz = cmd_l >> 4 & 0xf;cmd->log_stride = cmd_l & 0xf;cmd->max_reg_cmds = (1 << cmd->log_sz) - 1;cmd->bitmask = (1UL << cmd->max_reg_cmds) - 1;cmd_h = (u32)((u64)(cmd->dma) >> 32);cmd_l = (u32)(cmd->dma);iowrite32be(cmd_h, &dev->iseg->cmdq_addr_h);iowrite32be(cmd_l, &dev->iseg->cmdq_addr_l_sz);/* Make sure firmware sees the complete address before we proceed */wmb();...cmd->mode = CMD_MODE_POLLING;cmd->allowed_opcode = CMD_ALLOWED_OPCODE_ALL;cmd->wq = create_singlethread_workqueue(cmd->wq_name)...
}
cmd的下发与执行
输入输出
接下来以create_cq为例看下如何下发一个cmd,create_cq的输入由结构体mlx5_ifc_create_cq_in_bits解释,这个结构体不表示实际内存,比如opcode[0x10],通过u8类型表示仅仅是为了编程的方便,实际只占了16bit。输出由结构体mlx5_ifc_create_cq_out_bits表示。
struct mlx5_ifc_create_cq_in_bits {u8 opcode[0x10];u8 uid[0x10];u8 reserved_at_20[0x10];u8 op_mod[0x10];u8 reserved_at_40[0x40];struct mlx5_ifc_cqc_bits cq_context;u8 reserved_at_280[0x60];u8 cq_umem_valid[0x1];u8 reserved_at_2e1[0x59f];u8 pas[][0x40];
};
首先看下如何存储输入输出,mlx5_cmd_msg 表示一个msg,用于管理cmdq的entry和对应的mailbox,其中first用于存储开始的16B,如果输入大于16B,将会存储到next对应的mailbox链表里。mlx5_cmd_mailbox表示一个mailbox,其中buf为mailbox对应的虚拟地址,dma为这块buf的dma地址。next为软件侧的链,是一个虚拟地址,软件通过next遍历mailbox链表,硬件遍历mailbox的链表是通过buf里的next,是一个dma地址。
struct mlx5_cmd_msg {struct list_head list;struct cmd_msg_cache *parent;u32 len;struct mlx5_cmd_first first;struct mlx5_cmd_mailbox *next;
};struct mlx5_cmd_first {__be32 data[4];
};struct mlx5_cmd_mailbox {void *buf;dma_addr_t dma;struct mlx5_cmd_mailbox *next;
};
然后开始看执行cmd的过程,入口函数为mlx5_cmd_do,实际直接执行cmd_exec,其中callback和context为NULL,force_poling为false。
static int cmd_exec(struct mlx5_core_dev *dev, void *in, int in_size, void *out,int out_size, mlx5_cmd_cbk_t callback, void *context,bool force_polling)
{struct mlx5_cmd_msg *inb;struct mlx5_cmd_msg *outb;opcode = MLX5_GET(mbox_in, in, opcode);pages_queue = is_manage_pages(in);gfp = callback ? GFP_ATOMIC : GFP_KERNEL;inb = alloc_msg(dev, in_size, gfp);...
}
分配msg
cmd_exec首先通过alloc_msg分配input msg,初始化的时候已经分配了若干个mlx5_cmd_msg作为cache。如果in的长度小于16,就是可以放到一个cmdq entry,那么直接分配,不复用cache,否则会先看cache中有没有满足需要大小的msg,如果有则使用cache中的msg,假设没有命中cache,会通过mlx5_alloc_cmd_msg进行分配。
static struct mlx5_cmd_msg *alloc_msg(struct mlx5_core_dev *dev, int in_size,gfp_t gfp)
{struct mlx5_cmd_msg *msg = ERR_PTR(-ENOMEM);struct mlx5_cmd *cmd = &dev->cmd;if (in_size <= 16)goto cache_miss;...
cache_miss:msg = mlx5_alloc_cmd_msg(dev, gfp, in_size, 0);return msg;
}
先分配msg,然后通过mlx5_calc_cmd_blocks计算需要几个mailbox entry,计算方法为cmdq entry可以存16B,然后剩下的看需要几个mailbox存储,一个mailbox可以存储512B。然后通过alloc_cmd_box分配mailbox,这里就是通过dma_pool_zalloc从cmd->pool中获取一个dma内存块,对于每一个新分配的mailbox,通过mailbox的next字段链接到msg的next链表,软件通过这里的next就可以遍历链表。然后开始初始化mailbox的buf,设置block_num,设置token为0,然后设置buf的next为下一个mailbox的dma地址,硬件通过buf里的next就可以遍历链表了。
static struct mlx5_cmd_msg *mlx5_alloc_cmd_msg(struct mlx5_core_dev *dev,gfp_t flags, int size,u8 token)
{struct mlx5_cmd_mailbox *tmp, *head = NULL;struct mlx5_cmd_prot_block *block;struct mlx5_cmd_msg *msg;int err; int n;int i;msg = kzalloc(sizeof(*msg), flags);if (!msg)return ERR_PTR(-ENOMEM);msg->len = size;n = mlx5_calc_cmd_blocks(msg);for (i = 0; i < n; i++) {tmp = alloc_cmd_box(dev, flags);if (IS_ERR(tmp)) {mlx5_core_warn(dev, "failed allocating block\n");err = PTR_ERR(tmp);goto err_alloc;} block = tmp->buf;tmp->next = head;block->next = cpu_to_be64(tmp->next ? tmp->next->dma : 0);block->block_num = cpu_to_be32(n - i - 1);block->token = token;head = tmp; } msg->next = head;return msg; err_alloc:while (head) {tmp = head->next;free_cmd_box(dev, head);head = tmp; } kfree(msg);return ERR_PTR(err);
}
static int cmd_exec(struct mlx5_core_dev *dev, void *in, int in_size, void *out,int out_size, mlx5_cmd_cbk_t callback, void *context,bool force_polling)
{...token = alloc_token(&dev->cmd);err = mlx5_copy_to_msg(inb, in, in_size, token);outb = mlx5_alloc_cmd_msg(dev, gfp, out_size, token);
}
然后开始分配token,token就是一个自增的uint8,标识一次cmd,一次cmd的cmdq entry和对应的所有mailbox的token都需要是一致的,然后通过mlx5_copy_to_msg将数据从in中拷贝到msg inb中,就是将连续的输入分散拷贝到cmd entry和mailbox中。然后分配output msg,和分配input msg一样。
执行
mlx5_cmd_invoke通过cmd_alloc_ent分配一个mlx5_cmd_work_ent ent,用于记录上下文信息,比如输入输出等。初始化ent中的work_struct work,对应的执行函数为cmd_work_handler,然后通过queue_work将ent->work提交到cmd->wq中执行,这里的wq就是初始化中创建的。wait_func通过wait_for_completion等待cmd->wq完成ent->work的执行。
static int mlx5_cmd_invoke(struct mlx5_core_dev *dev, struct mlx5_cmd_msg *in,struct mlx5_cmd_msg *out, void *uout, int uout_size,mlx5_cmd_cbk_t callback,void *context, int page_queue, u8 *status,u8 token, bool force_polling)
{struct mlx5_cmd *cmd = &dev->cmd;struct mlx5_cmd_work_ent *ent;ent = cmd_alloc_ent(cmd, in, out, uout, uout_size,callback, context, page_queue);ent->token = token;ent->polling = force_polling;init_completion(&ent->handling);if (!callback)init_completion(&ent->done);INIT_DELAYED_WORK(&ent->cb_timeout_work, cb_timeout_handler);INIT_WORK(&ent->work, cmd_work_handler);if (page_queue) {cmd_work_handler(&ent->work);} else if (!queue_work(cmd->wq, &ent->work)) {mlx5_core_warn(dev, "failed to queue work\n");err = -ENOMEM;goto out_free;}if (callback)goto out; /* mlx5_cmd_comp_handler() will put(ent) */err = wait_func(dev, ent);...
}
然后看下work_queue是如何执行的,即cmd_work_handler。
首先执行,cmd->bitmask表示了当前cdmq的buff有哪些entry可用,cmd_alloc_index就是通过find_first_bit找到bitmask中第一个置为1的位ret,如果找到了那么将这位置清0,表示占用了,将ret记录到ent->idx。
static int cmd_alloc_index(struct mlx5_cmd *cmd)
{ unsigned long flags;int ret; spin_lock_irqsave(&cmd->alloc_lock, flags);ret = find_first_bit(&cmd->bitmask, cmd->max_reg_cmds);if (ret < cmd->max_reg_cmds)clear_bit(ret, &cmd->bitmask);spin_unlock_irqrestore(&cmd->alloc_lock, flags);return ret < cmd->max_reg_cmds ? ret : -ENOMEM;
}
static void cmd_work_handler(struct work_struct *work)
{struct mlx5_cmd_work_ent *ent = container_of(work, struct mlx5_cmd_work_ent, work);struct mlx5_cmd *cmd = ent->cmd;bool poll_cmd = ent->polling;struct mlx5_cmd_layout *lay;int alloc_ret;int cmd_mode;dev = container_of(cmd, struct mlx5_core_dev, cmd);cb_timeout = msecs_to_jiffies(mlx5_tout_ms(dev, CMD));complete(&ent->handling);sem = ent->page_queue ? &cmd->pages_sem : &cmd->sem;down(sem);if (!ent->page_queue) {alloc_ret = cmd_alloc_index(cmd);if (alloc_ret < 0) { ...up(sem);return;} ent->idx = alloc_ret;} else {...} cmd->ent_arr[ent->idx] = ent; lay = get_inst(cmd, ent->idx);...
}
然后执行get_inst获取cmdq的第idx个entry,即lay,前边有说log_stride是以log表示的cmdq entry大小,因此就是(idx << cmd->log_stride)。
static struct mlx5_cmd_layout *get_inst(struct mlx5_cmd *cmd, int idx)
{ return cmd->cmd_buf + (idx << cmd->log_stride);
}
然后开始设置lay,首先拷贝input msg的前16B到lay->in,如果input msg有next,表示输入长度超过了16B,因此将第一个mailbox的dma地址设置到lay->in_ptr,接着设置inlen,同理设置out和outlen,然后设置token,signature,设置完成后将ownership bit修改为HW。
static void cmd_work_handler(struct work_struct *work) {...ent->lay = lay;memset(lay, 0, sizeof(*lay));memcpy(lay->in, ent->in->first.data, sizeof(lay->in));ent->op = be32_to_cpu(lay->in[0]) >> 16;if (ent->in->next)lay->in_ptr = cpu_to_be64(ent->in->next->dma);lay->inlen = cpu_to_be32(ent->in->len);if (ent->out->next)lay->out_ptr = cpu_to_be64(ent->out->next->dma);lay->outlen = cpu_to_be32(ent->out->len);lay->type = MLX5_PCI_CMD_XPORT;lay->token = ent->token;lay->status_own = CMD_OWNER_HW;set_signature(ent, !cmd->checksum_disabled);...
}
对lay的设置完成之后,然后开始写cmd queue的doorbell,doorbell是一个vector,软件通过写对应的bit表示新的cmd是cmd_buf的哪个位置。
static void cmd_work_handler(struct work_struct *work)
{...wmb();iowrite32be(1 << ent->idx, &dev->iseg->cmd_dbell);/* if not in polling don't use ent after this point */if (cmd_mode == CMD_MODE_POLLING || poll_cmd) {poll_timeout(ent);/* make sure we read the descriptor after ownership is SW */rmb();mlx5_cmd_comp_handler(dev, 1ULL << ent->idx, ent->ret == -ETIMEDOUT ?MLX5_CMD_COMP_TYPE_FORCED : MLX5_CMD_COMP_TYPE_POLLING);}
}
然后通过poll_timeout等待网卡执行,实现上就是通过轮询cmq entry的ownership字段,当ownership变成SW之后表示硬件已经完成执行。
static void poll_timeout(struct mlx5_cmd_work_ent *ent)
{struct mlx5_core_dev *dev = container_of(ent->cmd, struct mlx5_core_dev, cmd);u64 cmd_to_ms = mlx5_tout_ms(dev, CMD);unsigned long poll_end;u8 own;poll_end = jiffies + msecs_to_jiffies(cmd_to_ms + 1000);do {own = READ_ONCE(ent->lay->status_own);if (!(own & CMD_OWNER_HW)) {ent->ret = 0;return;}cond_resched();} while (time_before(jiffies, poll_end));ent->ret = -ETIMEDOUT;
}
然后执行mlx5_cmd_comp_handler将硬件输出的前16B拷贝到output msg,后边的输出可以通过output找到output mailbox获取,通过complete通知执行的完成。
static void mlx5_cmd_comp_handler(struct mlx5_core_dev *dev, u64 vec, enum mlx5_comp_t comp_type)
{struct mlx5_cmd *cmd = &dev->cmd;struct mlx5_cmd_work_ent *ent;...memcpy(ent->out->first.data, ent->lay->out, sizeof(ent->lay->out));complete(&ent->done);...
}
最后回到cmd_exec会将output msg拷贝回out,对于create_cq来说就是mlx5_ifc_create_cq_in_bits。
相关文章:

RDMA驱动学习(二)- command queue
为了实现用户对网卡硬件的配置,查询,或者执行比如create_cq等命令,mellanox网卡提供了command queue mailbox的机制,本节将以create_cq为例看下这个过程。 command queue(后续简称cmdq)是一个4K对齐的长度…...

H2 Database IDEA 源码 DEBUG 环境搭建
H2 Database IDEA 源码 DEBUG 环境搭建 基于最新的 version-2.3.230 拉取分支。 git remote add h2 https://github.com/h2database/h2database.git git fetch h2 git checkout -b version-2.3.230 version-2.3.230使用 # 启动 java -jar h2*.jar# H2 shell 方式使用 java …...

nginx系列--(三)--http
本文主要介绍http模块accept read流程,!!!请求对应的响应直接在read流程里就会返回给用户,而不需要通过write事件,和redis一样,基本都不通过eventloop write事件来发送响应给客户端,…...

通过Wireshark抓包分析,体验HTTP请求的一次完整交互过程
目录 一、关于Wireshark 1.1、 什么是Wireshark 1.2、下载及安装 二、HTTP介绍 2.1、HTTP请求过程介绍 2.2 、TCP协议基础知识 2.2.1、概念介绍 2.2.2、TCP协议的工作原理 2.2.3、三次握手建立连接 2.3.4、四次挥手断开连接 2.3、Wireshark抓包分析过程 2.3.1、三次握…...

Requestium:Python中的Web自动化新贵
文章目录 Requestium:Python中的Web自动化新贵背景:为何选择Requestium?Requestium是什么?如何安装Requestium?简单的库函数使用方法场景应用常见Bug及解决方案总结 Requestium:Python中的Web自动化新贵 背…...

2024版红娘金媒10.3婚恋相亲系统源码小程序(亲测)
1. 红娘服务 红娘服务模块是该系统的一大特色。专业红娘会通过分析用户的个人资料和偏好, 为用户提供精准的配对建议和个性化服务。用户可以预约红娘服务,通过红娘的介入,提升配对成功率。 2. 相亲活动 相亲活动模块用于组织和管理线下或线…...

k8s-实战——ES集群部署
文章目录 yaml文件es-pvc.yamles-svc.yamles-cluster-sts.yaml创建elasticsearch集群yaml文件 es-pvc.yaml 通过nfs服务进行新增pv并通过labels关联pvc前置准备需要提前准备pv的服务器以及挂在路径--- apiVersion: v1 kind: PersistentVolume metadata:name: nfs-es-pv-data-...

无人机的就业前景怎么样?
无人机的就业前景在当前及未来一段时间内都非常广阔。随着低空经济的蓬勃发展,无人机在农业、公安、测绘、交通、应急救援、影视拍摄等多个领域得到了广泛应用,对无人机操控员和相关专业人才的需求也随之急剧增加。 一、无人机操控员的就业前景 1. 高需…...

【学习】软件测试中V模型、W模型、螺旋模型三者介绍
在软件工程的星辰大海之中,存在着三种独特的航路图:V模型、W模型以及螺旋模型。它们分别以各自的方式描绘了软件开发与测试的不同旅程。 首先映入眼帘的是V模型——一个以垂直线条贯穿始终的简洁图形。这个模型如同一座倒立的“V”字形山峰,…...

Kafka存储机制大揭秘:从日志结构到清理策略的全面解析
文章目录 一、前言二、日志存储结构1.日志文件结构2.topic3.partition4.segment索引文件5.message结构6.message查找过程 三、存储策略1.顺序写2.页缓存3.零拷贝4.缓存机制 四、日志格式演变1.V0 版本2.V1 版本3.V0/V1消息集合4.V2 版本消息格式5.V2版本消息集合 五、偏移量维护…...

显卡服务器和普通服务器之间的区别有哪些?
显卡服务器也被称之为GPU服务器,显卡服务器与普通的服务器之间有着很明显的区别,下面就让我们共同来了解一下吧! 普通服务器的主要处理器通常都是配备的中央处理器,可以用于执行大部分通用计算任务和操作系统的管理;而…...

国产科技里程碑:自主算力走向世界,“表格编程”横空出世
近日,中国高科技领域迎来里程碑式的进展。 据安徽省量子计算工程研究中心官方消息,本源量子计算科技(合肥)股份有限公司(简称“本源量子”)成功向海外销售了其第三代自主超导量子计算机“本源悟空”的机时。…...

人工智能如何改变未来生活:从医疗到日常的全面升级
人工智能如何改变未来生活:从医疗到日常的全面升级 随着人工智能(AI)技术的进步,我们正逐渐看到它为各行各业带来的巨大变革。从医疗、企业到日常生活,AI通过简化流程、提高效率,甚至改善生活质量…...

第112届全国糖酒会(3月成都)正式官宣!
作为食品饮料行业内备受瞩目的年度盛事,全国糖酒商品交易会(简称“糖酒会”)一直是各大厂商与经销商展现企业风采、寻觅合作伙伴及签署订单的关键舞台。2024年10月31日,第111届全国糖酒商品交易会(秋糖)在深…...

NFT Insider #154:The Sandbox Alpha 4 第四周开启,NBA Topshot NFT 销量激增至新高
市场数据 加密艺术及收藏品新闻 NBA 赛季开幕推动 Topshot NFT 销量激增至新高 随着波士顿凯尔特人队和纽约尼克斯队在 10 月 22 日开启 2024-2025 NBA 赛季的序幕,NBA Topshot 的 NFT 销售量达到了自上赛季季后赛以来的最高水平。截止到 10 月 27 日的这一周&…...

【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新
文章目录 一、安装 MySQL1.1 启动 mysql 服务器1.2 开启 Binlog 写入功能1.2.1创建 binlog 配置文件1.2.2 修改配置文件权限1.2.3 挂载配置文件1.2.4 检测 binlog 配置是否成功 1.3 创建账户并授权 二、安装 RocketMQ2.1 创建容器共享网络2.2 启动 NameServer2.3 启动 Broker2.…...

独立开发的个人品牌打造:个人IP与独立开发的结合
引言 个人品牌程序员也需要打造。在当今的创意经济中,个人IP与独立开发的结合成为了一种趋势,为个体带来了前所未有的机会和可能性。本文将探讨如何通过打造个人IP来增强独立开发的影响力,并探索这种结合为个人带来的潜在价值。 个人IP的重…...
每天一题:洛谷P2002 消息扩散
题目背景 本场比赛第一题,给个简单的吧,这 100 分先拿着。 题目描述 有 n 个城市,中间有单向道路连接,消息会沿着道路扩散,现在给出 n 个城市及其之间的道路,问至少需要在几个城市发布消息才能让这所有 …...

【深度学习】用LSTM写诗,生成式的方式写诗系列之一
Epoch 4: 100%|███████████████████████████████████████████████████████████| 63/63 [00:07<00:00, 8.85batch/s, acc18.5, loss5.8] [5] loss: 5.828, accuracy: 18.389 , lr:0.001000 Epoch 5: 100%|███…...

HomeAssistant自定义组件学习-【二】
#要说的话# 前面把中盛科技的控制器组件写完了。稍稍熟悉了一些HA,现在准备写窗帘控制组件,构想的东西会比较多,估计有些难度,过程会比较长,边写边记录吧! #设备和场景环境# 使用的是Novo的电机…...

如何看待AI技术的应用前景?
文章目录 如何看待AI技术的应用前景引言AI技术的现状1. AI的定义与分类2. 当前AI技术的应用领域 AI技术的应用前景1. 经济效益2. 社会影响3. 技术进步 AI技术应用面临的挑战1. 数据隐私与安全2. 可解释性与信任3. 技能短缺与就业影响 AI技术的未来发展方向1. 人工智能的伦理与法…...

Unity中的屏幕坐标系
获得视口宽高 拖动视口会改变屏幕宽高数值 MousePosition 屏幕坐标系的原点在左下角,MousePosition返回Z为0也就是纵深为0的Vector3 但是如果鼠标超出屏幕范围不会做限制,所以可能出现负数或者大于屏幕宽高的情况,做鼠标拖拽物体时需要注…...

标题点击可跳转网页
要实现点击标题跳转到网页的功能,你可以在Vue组件中使用<a>标签(锚点标签)并设置href属性为网页的URL。如果你希望使用uni-app的特性来控制页面跳转,可以使用uni.navigateTo方法(这适用于uni-app环境,…...

易语言模拟真人动态生成鼠标滑动路径
一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言,原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势: 模拟…...

Linux:生态与软件安装
文章目录 前言一、Linux下安装软件的方案二、包管理器是什么?三、生态问题相关的理解1. 什么操作系统是好的操作系统?2. 什么是生态?3. 软件包是谁写的?这些工程师为什么要写?钱的问题怎么解决? 四、我的服务器怎么知…...

R 语言与其他编程语言的区别
R 语言与其他编程语言的区别 R 语言作为一种专门用于统计计算和图形的编程语言,与其他编程语言相比有一些独特的特点和区别。本文将详细介绍这些区别,帮助你更好地理解 R 语言的优势和适用场景。 1. 专为统计和数据分析设计 统计功能 内置统计函数&…...

RC低通滤波器Bode图分析(传递函数零极点)
RC低通滤波器 我们使得R1K,C1uF;电容C的阻抗为Xc; 传递函数 H ( s ) u o u i X C X C R 1 s C 1 s C R 1 1 s R C (其中 s j ω ) H(s)\frac{u_{o} }{u_{i} } \frac{X_{C} }{X_{C}R} \frac{\frac{1}{sC} }{\…...

基于深度学习的网络入侵检测
基于深度学习的网络入侵检测是一种利用深度学习技术对网络流量进行实时监测与分析的方法,旨在识别并防范网络攻击和恶意活动。随着网络环境日益复杂,传统的入侵检测系统(IDS)在面对不断变化的攻击模式时,往往难以保持高…...

《构建一个具备从后端数据库获取数据并再前端显示的内容页面:前后端实现解析》
一、前端页面:布局与功能 1. 页面结构 我们先来看前端页面的 HTML 结构,它主要由以下几个部分组成: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewp…...

Rust 力扣 - 59. 螺旋矩阵 II
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 使用一个全局变量current记录当前遍历到的元素的值 我们只需要一圈一圈的从外向内遍历矩阵,每一圈遍历顺序为上边、右边、下边、左边,每遍历完一个元素后current 我们需要注意的是如果上…...