当前位置: 首页 > news >正文

block层:5. 请求分配

请求相关

源码基于5.10

1. 分配请求

static struct request *__blk_mq_alloc_request(struct blk_mq_alloc_data *data)
{// 请求队列struct request_queue *q = data->q;// 电梯struct elevator_queue *e = q->elevator;u64 alloc_time_ns = 0;unsigned int tag;// 判断有无QUEUE_FLAG_RQ_ALLOC_TIME标志// 有的话记录分配的时间if (blk_queue_rq_alloc_time(q))alloc_time_ns = ktime_get_ns();// 不等待if (data->cmd_flags & REQ_NOWAIT)data->flags |= BLK_MQ_REQ_NOWAIT;// 有电梯if (e) {// 非刷新请求 && 调度器有limit_depth && 使用保留tag, 则调用调度器的限制队列深度的方法if (!op_is_flush(data->cmd_flags) &&e->type->ops.limit_depth &&!(data->flags & BLK_MQ_REQ_RESERVED))e->type->ops.limit_depth(data->cmd_flags, data);}retry:// 获取ctxdata->ctx = blk_mq_get_ctx(q);// 根据不同的请求返回hctxdata->hctx = blk_mq_map_queue(q, data->cmd_flags, data->ctx);// 没有调度器,则给hctx->state标记 BLK_MQ_S_TAG_ACTIVEif (!e)blk_mq_tag_busy(data->hctx);// 获取一个tagtag = blk_mq_get_tag(data);// 没tag了if (tag == BLK_MQ_NO_TAG) {// 不等待,直接返回NULLif (data->flags & BLK_MQ_REQ_NOWAIT)return NULL;// 睡3秒再重试msleep(3);goto retry;}// 初始化tag对应的请求return blk_mq_rq_ctx_init(data, tag, alloc_time_ns);
}static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data,unsigned int tag, u64 alloc_time_ns)
{// 获取data里的tagsstruct blk_mq_tags *tags = blk_mq_tags_from_data(data);// 取tag的静态请求struct request *rq = tags->static_rqs[tag];// 根据是否有电梯在不同的字段记录tag// todo: 有电梯时为啥要在internal_tag里记录if (data->q->elevator) {rq->tag = BLK_MQ_NO_TAG;rq->internal_tag = tag;} else {rq->tag = tag;rq->internal_tag = BLK_MQ_NO_TAG;}// 请求队列rq->q = data->q;// 软件队列rq->mq_ctx = data->ctx;// 硬件队列rq->mq_hctx = data->hctx;rq->rq_flags = 0;// 命令标志rq->cmd_flags = data->cmd_flags;// 运行时电源管理请求?if (data->flags & BLK_MQ_REQ_PM)rq->rq_flags |= RQF_PM;// 有io统计需要if (blk_queue_io_stat(data->q))rq->rq_flags |= RQF_IO_STAT;// 一些初始化INIT_LIST_HEAD(&rq->queuelist);INIT_HLIST_NODE(&rq->hash);RB_CLEAR_NODE(&rq->rb_node);rq->rq_disk = NULL;rq->part = NULL;
#ifdef CONFIG_BLK_RQ_ALLOC_TIME// 开始分配请求时的时间戳rq->alloc_time_ns = alloc_time_ns;
#endif// 需要记录请求开始时时间戳if (blk_mq_need_time_stamp(rq))rq->start_time_ns = ktime_get_ns();elserq->start_time_ns = 0;rq->io_start_time_ns = 0;rq->stats_sectors = 0;rq->nr_phys_segments = 0;
#if defined(CONFIG_BLK_DEV_INTEGRITY)rq->nr_integrity_segments = 0;
#endif// 加密blk_crypto_rq_set_defaults(rq);// deadline设置为0WRITE_ONCE(rq->deadline, 0);rq->timeout = 0;rq->end_io = NULL;rq->end_io_data = NULL;// ctx里请求派发的计数data->ctx->rq_dispatched[op_is_sync(data->cmd_flags)]++;// 引用置1refcount_set(&rq->ref, 1);// 如果不是刷新请求if (!op_is_flush(data->cmd_flags)) {struct elevator_queue *e = data->q->elevator;rq->elv.icq = NULL;// 如果电梯有prepare_request,则调用之if (e && e->type->ops.prepare_request) {// 如果有icq缓存,则创建icqif (e->type->icq_cache)blk_mq_sched_assign_ioc(rq);// 调度器的准备请求方法e->type->ops.prepare_request(rq);// rq有调度器的私有数据rq->rq_flags |= RQF_ELVPRIV;}}// 已入队的请求数加1data->hctx->queued++;return rq;
}static inline bool blk_mq_need_time_stamp(struct request *rq)
{// 有统计需要 || 有调度器return (rq->rq_flags & (RQF_IO_STAT | RQF_STATS)) || rq->q->elevator;
}

request_to_qc_t

static inline blk_qc_t request_to_qc_t(struct blk_mq_hw_ctx *hctx,struct request *rq)
{// queue_num是队列编号// BLK_QC_T_SHIFT是16// 无调度器时rq->tag != -1,用低16位存tag,高16位存队列数量if (rq->tag != -1)return rq->tag | (hctx->queue_num << BLK_QC_T_SHIFT);// BLK_QC_T_INTERNAL = (1U << 31),// 有调度器时tag保存在internal_tag里,和上面类似,低16位存tag,高16位存队列数量,// 在第32位保存internal的标志return rq->internal_tag | (hctx->queue_num << BLK_QC_T_SHIFT) |BLK_QC_T_INTERNAL;
}

把bio转换成请求

static void blk_mq_bio_to_request(struct request *rq, struct bio *bio,unsigned int nr_segs)
{int err;// 如果是预读的话,加上快速失败标志if (bio->bi_opf & REQ_RAHEAD)rq->cmd_flags |= REQ_FAILFAST_MASK;// 请求扇区的起点设置为bio的扇区起点rq->__sector = bio->bi_iter.bi_sector;// todo: what?rq->write_hint = bio->bi_write_hint;// 把bio里的一些读写信息写到请求里blk_rq_bio_prep(rq, bio, nr_segs);// todo: 加密相关后面再看err = blk_crypto_rq_bio_prep(rq, bio, GFP_NOIO);WARN_ON_ONCE(err);// 统计相关blk_account_io_start(rq);
}static inline void blk_rq_bio_prep(struct request *rq, struct bio *bio,unsigned int nr_segs)
{// 段数rq->nr_phys_segments = nr_segs;// 请求的数据长度rq->__data_len = bio->bi_iter.bi_size;// 请求的头、尾bio都指向这个biorq->bio = rq->biotail = bio;// 设置io优先级?rq->ioprio = bio_prio(bio);// 设置请求的磁盘if (bio->bi_disk)rq->rq_disk = bio->bi_disk;
}void blk_account_io_start(struct request *rq)
{// 没有统计io的需要直接返回if (!blk_do_io_stat(rq))return;// 把扇区转换成对应的分区rq->part = disk_map_sector_rcu(rq->rq_disk, blk_rq_pos(rq));part_stat_lock();update_io_ticks(rq->part, jiffies, false);part_stat_unlock();
}static inline bool blk_do_io_stat(struct request *rq)
{return rq->rq_disk && (rq->rq_flags & RQF_IO_STAT);
}static void update_io_ticks(struct hd_struct *part, unsigned long now, bool end)
{unsigned long stamp;
again:// 分区时间戳stamp = READ_ONCE(part->stamp);// 更新分区时间戳if (unlikely(stamp != now)) {if (likely(cmpxchg(&part->stamp, stamp, now) == stamp))__part_stat_add(part, io_ticks, end ? now - stamp : 1);}// 更新0号分区时间戳if (part->partno) {part = &part_to_disk(part)->part0;goto again;}
}

插入请求

插入请求是给调度器或者ctx里插入

插入刷新请求

void blk_insert_flush(struct request *rq)
{struct request_queue *q = rq->q;// 队列标志unsigned long fflags = q->queue_flags;// 刷新策略unsigned int policy = blk_flush_policy(fflags, rq);// 获取hctx对应的flush_queuestruct blk_flush_queue *fq = blk_get_flush_queue(q, rq->mq_ctx);// 清除REQ_PREFLUSH和REQ_FUA标志,因为policy里已经记录了rq->cmd_flags &= ~REQ_PREFLUSH;if (!(fflags & (1UL << QUEUE_FLAG_FUA)))rq->cmd_flags &= ~REQ_FUA;// 刷新请求是同步的rq->cmd_flags |= REQ_SYNC;// 没有策略的直接结束请求if (!policy) {blk_mq_end_request(rq, 0);return;}// 没有bio或者只能有一个BUG_ON(rq->bio != rq->biotail);// 如果有数据,没有刷新需求。这个请求就可以直接处理不用经过刷新机制。if ((policy & REQ_FSEQ_DATA) &&!(policy & (REQ_FSEQ_PREFLUSH | REQ_FSEQ_POSTFLUSH))) {// 绕过插入blk_mq_request_bypass_insert(rq, false, false);return;}/** @rq should go through flush machinery.  Mark it part of flush* sequence and submit for further processing.*/// 初始化请求的刷新对象memset(&rq->flush, 0, sizeof(rq->flush));INIT_LIST_HEAD(&rq->flush.list);// 顺序刷新?rq->rq_flags |= RQF_FLUSH_SEQ;// 保存endiorq->flush.saved_end_io = rq->end_io;// 设置flush的回调rq->end_io = mq_flush_data_end_io;spin_lock_irq(&fq->mq_flush_lock);// 刷出请求, REQ_FSEQ_ACTIONS = REQ_FSEQ_PREFLUSH | REQ_FSEQ_DATA | REQ_FSEQ_POSTFLUSHblk_flush_complete_seq(rq, fq, REQ_FSEQ_ACTIONS & ~policy, 0);spin_unlock_irq(&fq->mq_flush_lock);
}static unsigned int blk_flush_policy(unsigned long fflags, struct request *rq)
{unsigned int policy = 0;// 请求里有数据,这个获取的是数据长度对应的扇区数if (blk_rq_sectors(rq))policy |= REQ_FSEQ_DATA;// 如果有writeback cachingif (fflags & (1UL << QUEUE_FLAG_WC)) {// 预刷新?if (rq->cmd_flags & REQ_PREFLUSH)policy |= REQ_FSEQ_PREFLUSH;// flag没有fua, 但是cmd有fuaif (!(fflags & (1UL << QUEUE_FLAG_FUA)) &&(rq->cmd_flags & REQ_FUA))policy |= REQ_FSEQ_POSTFLUSH;}return policy;
}
static void blk_flush_complete_seq(struct request *rq,struct blk_flush_queue *fq,unsigned int seq, blk_status_t error)
{struct request_queue *q = rq->q;struct list_head *pending = &fq->flush_queue[fq->flush_pending_idx];unsigned int cmd_flags;BUG_ON(rq->flush.seq & seq);// 设置seq?rq->flush.seq |= seq;// 命令标志cmd_flags = rq->cmd_flags;if (likely(!error))// 计算rq的顺序seq = blk_flush_cur_seq(rq);elseseq = REQ_FSEQ_DONE;switch (seq) {case REQ_FSEQ_PREFLUSH:case REQ_FSEQ_POSTFLUSH:// 预刷出或已经刷出// 如果pending是空的,则记录pending时间if (list_empty(pending))fq->flush_pending_since = jiffies;// 把rq移到pending末尾list_move_tail(&rq->flush.list, pending);break;case REQ_FSEQ_DATA:// 有数据刷出// 把rq移到 flush_data_in_flight 队列list_move_tail(&rq->flush.list, &fq->flush_data_in_flight);// 添加到运行队列里blk_flush_queue_rq(rq, true);break;case REQ_FSEQ_DONE:// 请求已经执行完了BUG_ON(!list_empty(&rq->queuelist));// 从flush里删除list_del_init(&rq->flush.list);// 先还原成正常请求之前的数据blk_flush_restore_request(rq);// 结束请求blk_mq_end_request(rq, error);break;default:BUG();}// 刷出请求blk_kick_flush(q, fq, cmd_flags);
}static unsigned int blk_flush_cur_seq(struct request *rq)
{// ffz是找第一个0的位置return 1 << ffz(rq->flush.seq);
}static void blk_flush_queue_rq(struct request *rq, bool add_front)
{// 添加到队列里blk_mq_add_to_requeue_list(rq, add_front, true);
}void blk_mq_add_to_requeue_list(struct request *rq, bool at_head,bool kick_requeue_list)
{struct request_queue *q = rq->q;unsigned long flags;// 不能有这个标志BUG_ON(rq->rq_flags & RQF_SOFTBARRIER);spin_lock_irqsave(&q->requeue_lock, flags);if (at_head) {// 添加在头部,设置这个标志,这个标志是:io调度器可能无法传递rq->rq_flags |= RQF_SOFTBARRIER;// 加到队列前面list_add(&rq->queuelist, &q->requeue_list);} else {// 加到队列末尾list_add_tail(&rq->queuelist, &q->requeue_list);}spin_unlock_irqrestore(&q->requeue_lock, flags);// 如果需要唤醒队列,则唤醒之if (kick_requeue_list)blk_mq_kick_requeue_list(q);
}void blk_mq_kick_requeue_list(struct request_queue *q)
{// 运行kblockd_workqueue。第1个参数是指定cpu,最后一个参数是延迟时间// 延迟为0表示立即开始kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &q->requeue_work, 0);
}static void blk_flush_restore_request(struct request *rq)
{// 刷新完bio是NULL,我们应该还原它rq->bio = rq->biotail;// 去除刷出请求rq->rq_flags &= ~RQF_FLUSH_SEQ;// 还原之前的回调函数rq->end_io = rq->flush.saved_end_io;
}static void blk_kick_flush(struct request_queue *q, struct blk_flush_queue *fq,unsigned int flags)
{// 待刷出请求队列?struct list_head *pending = &fq->flush_queue[fq->flush_pending_idx];// 第1个请求struct request *first_rq =list_first_entry(pending, struct request, flush.list);struct request *flush_rq = fq->flush_rq;// 同时只能有一个运行或者待刷出队列是空的if (fq->flush_pending_idx != fq->flush_running_idx || list_empty(pending))return;// 有正在运行的,并且没有超过5秒if (!list_empty(&fq->flush_data_in_flight) &&time_before(jiffies,// FLUSH_PENDING_TIMEOUT是5秒fq->flush_pending_since + FLUSH_PENDING_TIMEOUT))return;// 切换pending_idxfq->flush_pending_idx ^= 1;// 初始化flush_rq请求blk_rq_init(q, flush_rq);// 使用第1个请求的上下文flush_rq->mq_ctx = first_rq->mq_ctx;flush_rq->mq_hctx = first_rq->mq_hctx;if (!q->elevator) {// 没有调度器flush_rq->tag = first_rq->tag;/** 这个标记防止多次统计*/flush_rq->rq_flags |= RQF_MQ_INFLIGHT;} else// 有调度器flush_rq->internal_tag = first_rq->internal_tag;// REQ_OP_FLUSH:刷出易失缓存flush_rq->cmd_flags = REQ_OP_FLUSH | REQ_PREFLUSH;// REQ_DRV:需要驱动? REQ_FAILFAST_MASK: 快速结束flush_rq->cmd_flags |= (flags & REQ_DRV) | (flags & REQ_FAILFAST_MASK);// 顺序刷出flush_rq->rq_flags |= RQF_FLUSH_SEQ;// 磁盘flush_rq->rq_disk = first_rq->rq_disk;// 结束回调flush_rq->end_io = flush_end_io;smp_wmb();// 设引用为1refcount_set(&flush_rq->ref, 1);// 刷出,把请求添加列队列里blk_flush_queue_rq(flush_rq, false);
}void blk_rq_init(struct request_queue *q, struct request *rq)
{memset(rq, 0, sizeof(*rq));INIT_LIST_HEAD(&rq->queuelist);// 设置队列rq->q = q;rq->__sector = (sector_t) -1;INIT_HLIST_NODE(&rq->hash);RB_CLEAR_NODE(&rq->rb_node);// 没有tagrq->tag = BLK_MQ_NO_TAG;rq->internal_tag = BLK_MQ_NO_TAG;// 开始时间rq->start_time_ns = ktime_get_ns();rq->part = NULL;// 加密blk_crypto_rq_set_defaults(rq);
}

批量插入

这个函数主要在flush_plug时调用,它会把plug里的请求,按hctx排序来按hctx刷出.

void blk_mq_sched_insert_requests(struct blk_mq_hw_ctx *hctx,struct blk_mq_ctx *ctx,struct list_head *list, bool run_queue_async)
{struct elevator_queue *e;struct request_queue *q = hctx->queue;// 增加引用计数,怕在使用期间队列被释放了percpu_ref_get(&q->q_usage_counter);// 获取调度器e = hctx->queue->elevator;if (e && e->type->ops.insert_requests)// 有电梯 && 有插入请求函数,则调用之e->type->ops.insert_requests(hctx, list, false);else {// 没有调度器或者调度器没有insert函数的话就调用通用函数// 队列不忙 && 没有电梯 && 不是异步,直接发布// 这样能给软队列节省一个入队或出队请求if (!hctx->dispatch_busy && !e && !run_queue_async) {// 直接发布请求blk_mq_try_issue_list_directly(hctx, list);// 链表空了,则退出if (list_empty(list))goto out;}// 把list里剩余的元素插入队列blk_mq_insert_requests(hctx, ctx, list);}// 运行队列blk_mq_run_hw_queue(hctx, run_queue_async);out:// 减少引用percpu_ref_put(&q->q_usage_counter);
}void blk_mq_insert_requests(struct blk_mq_hw_ctx *hctx, struct blk_mq_ctx *ctx,struct list_head *list){struct request *rq;enum hctx_type type = hctx->type;// 这个打印插入的tracelist_for_each_entry(rq, list, queuelist) {// 原注释:抢占不会刷新列表头,所以ctx->cpu可能已经下线?BUG_ON(rq->mq_ctx != ctx);trace_block_rq_insert(hctx->queue, rq);}spin_lock(&ctx->lock);// 把list添加到软队列的末尾list_splice_tail_init(list, &ctx->rq_lists[type]);// 标记软队列有准备提交的请求blk_mq_hctx_mark_pending(hctx, ctx);spin_unlock(&ctx->lock);
}static void blk_mq_hctx_mark_pending(struct blk_mq_hw_ctx *hctx,struct blk_mq_ctx *ctx)
{const int bit = ctx->index_hw[hctx->type];// 标记软位图,这个标记之后就表示有准备提交的请求if (!sbitmap_test_bit(&hctx->ctx_map, bit))sbitmap_set_bit(&hctx->ctx_map, bit);
}

批量插入的主要流程很简单:

  1. 把list里的请求全部加到软队列里
  2. 标记软队列对应hctx的位图,表示这个软队列有请求需要提交
  3. 运行队列

调度器插入请求

void blk_mq_sched_insert_request(struct request *rq, bool at_head,bool run_queue, bool async)
{struct request_queue *q = rq->q;struct elevator_queue *e = q->elevator;struct blk_mq_ctx *ctx = rq->mq_ctx;struct blk_mq_hw_ctx *hctx = rq->mq_hctx;// 有调度器时,rq->tag应该为空,tag在internal_tag里保存WARN_ON(e && (rq->tag != BLK_MQ_NO_TAG));// 判断是否要绕过插入直接发布, bypass的条件是flush或passthrough请求if (blk_mq_sched_bypass_insert(hctx, !!e, rq)) {// 如果是刷新请求则放到队头at_head = (rq->rq_flags & RQF_FLUSH_SEQ) ? true : at_head;// 直接插入到hctx的派发队列里,最后一个参数表示是否运行队列blk_mq_request_bypass_insert(rq, at_head, false);goto run;}// 如果没有直接发布就加到软队列里if (e && e->type->ops.insert_requests) {// 有调度器 && 有insert_requests方法,则调用之LIST_HEAD(list);list_add(&rq->queuelist, &list);e->type->ops.insert_requests(hctx, &list, at_head);} else {// 否则调用通用方法插入请求spin_lock(&ctx->lock);// 这个会把请求插到软队列里__blk_mq_insert_request(hctx, rq, at_head);spin_unlock(&ctx->lock);}run:// 若需运行,则执行请求if (run_queue)blk_mq_run_hw_queue(hctx, async);
}

直接发布

static bool blk_mq_sched_bypass_insert(struct blk_mq_hw_ctx *hctx,bool has_sched,struct request *rq)
{// RQF_FLUSH_SEQ是顺序刷出// blk_rq_is_passthrough判断是否是REQ_OP_SCSI_IN/OUT,REQ_OP_DRV_IN/OUT这4种之一的请求// 这2种情况绕过插入,直接发出请求if ((rq->rq_flags & RQF_FLUSH_SEQ) || blk_rq_is_passthrough(rq))return true;// 有调度器,则需要排序if (has_sched)rq->rq_flags |= RQF_SORTED;return false;
}void blk_mq_request_bypass_insert(struct request *rq, bool at_head,bool run_queue)
{struct blk_mq_hw_ctx *hctx = rq->mq_hctx;spin_lock(&hctx->lock);// 添加到硬件队列的派发队列,// 加到dispatch队列后,在运行队列时,会优先处理if (at_head)list_add(&rq->queuelist, &hctx->dispatch);elselist_add_tail(&rq->queuelist, &hctx->dispatch);spin_unlock(&hctx->lock);// 如需运行硬件队列,则运行之if (run_queue)blk_mq_run_hw_queue(hctx, false);
}

插入软队列

void __blk_mq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,bool at_head)
{struct blk_mq_ctx *ctx = rq->mq_ctx;lockdep_assert_held(&ctx->lock);// 把请求插到队列里__blk_mq_insert_req_list(hctx, rq, at_head);// 标记hctx有待处理的请求blk_mq_hctx_mark_pending(hctx, ctx);
}static inline void __blk_mq_insert_req_list(struct blk_mq_hw_ctx *hctx,struct request *rq,bool at_head)
{struct blk_mq_ctx *ctx = rq->mq_ctx;enum hctx_type type = hctx->type;lockdep_assert_held(&ctx->lock);trace_block_rq_insert(hctx->queue, rq);// 根据at_head的值,加到软队列头或者队列尾if (at_head)list_add(&rq->queuelist, &ctx->rq_lists[type]);elselist_add_tail(&rq->queuelist, &ctx->rq_lists[type]);
}

相关文章:

block层:5. 请求分配

请求相关 源码基于5.10 1. 分配请求 static struct request *__blk_mq_alloc_request(struct blk_mq_alloc_data *data) {// 请求队列struct request_queue *q data->q;// 电梯struct elevator_queue *e q->elevator;u64 alloc_time_ns 0;unsigned int tag;// 判断…...

L1-038 新世界(Python实现) 测试点全过

题目 这道超级简单的题目没有任何输入。 你只需要在第一行中输出程序员钦定名言“Hello World”&#xff0c;并且在第二行中输出更新版的“Hello New World”就可以了。 输入样例&#xff1a; 无输出样例&#xff1a; Hello World Hello New World题解 """…...

【hello git】初识Git

目录 一、简述Git 二、Linux 下 Git 的安装&#xff1a;CentOS 2.1 基本命令 2.2 示例&#xff1a; 三、Linux 下 Git 的安装&#xff1a;ubuntu 3.1 基本命令 3.2 示例&#xff1a; 一、简述Git Git &#xff1a;版本控制器&#xff0c;记录每次的修改以及版本迭代的一个管…...

Vueelementui动态渲染Radio,Checkbox,笔记

<div id"app"><el-card style"width: 300px"><el-form label-position"top" size"mini"><el-form-item label"标题"><el-input></el-input></el-form-item><el-form-item v-f…...

SpringDataRedis 使用

1. SpringDataRedis 特点2. 使用 SpringDataRedis 步骤3. 自定义 RedisTemplate 序列化4. SpringDataRedis 操作对象 1. SpringDataRedis 特点 提供了对不同 Redis 客户端的整合&#xff08;Lettuce 和 Jedis&#xff09;提供了 RedisTemplate 统一 API 来操作 Redis支持 Redi…...

Redis全局命令与数据结构

"那篝火在银河尽头~" Redis-cli命令启动 现如今&#xff0c;我们已经启动了Redis服务&#xff0c;下⾯将介绍如何使⽤redis-cli连接、操作Redis服务。客户端与服务端交互的方式有两种: ● 第⼀种是交互式⽅式: 后续所有的操作都是通过交互式的⽅式实现&#xff0c;…...

LibreOffice新一代的办公软件for Mac/Windows免费版

LibreOffice是一款免费、开源的办公软件套件&#xff0c;可在多个操作系统上运行&#xff0c;包括Windows、Mac和Linux。它提供了一系列功能强大的办公工具&#xff0c;包括文档处理、电子表格、演示文稿、数据库管理等。 LibreOffice的界面简洁直观&#xff0c;与其他流行的办…...

Python|OpenCV-读取视频,显示视频并保存视频(3)

前言 本文是该专栏的第3篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用OpenCV处理视频的时候,不论是摄像头画面还是视频文件,通常情况下都要使用VideoCapture类来进行每一帧图像的处理。对于OpenCV而言,只要使用视频文件作为参数,它就可以打开视频文件…...

上传WSL项目到gitlab

上传WSL项目到gitlab 设置ssh将SSH公钥添加到Gitlab 将WSL上的代码上传到gitlab确保在WSL环境中安装了git下面是上传代码到GitLab的具体步骤&#xff1a; 可能遇到的各种错误 设置ssh Gitlab添加SSH KEY 什么是SSH ? SSH 是一种网络协议&#xff0c;具备协议级别的认证及会话…...

从0开始做yolov5模型剪枝

文章目录 从0开始做yolov5模型剪枝 ****1 前言2 GitHub取源码3 原理3.1 原理3.2 network slimming过程 4 具体实施步骤4.1 安装虚拟环境4.2 配置参数4.2.1 数据集参数4.2.2 模型结构参数4.2.3 train.py中的参数 4.3 正常训练4.3.1 准备4.3.2 训练及问题解决 4.4 稀疏化训练4.4.…...

飞天使-k8s基础组件分析-安全

文章目录 名称空间解释访问kubernetes API的控制RBAC的介绍 kubeconfig用户的创建集群默认角色 给组创建授权针对pod配置服务账户参考文档 名称空间解释 名字是啥&#xff1f; 答&#xff1a;集群中每个对象的名称对于该类型的资源都是唯一的。并且每一个对象在整个集群中也有…...

Mysql安装使用

Mysql下载: MySQL :: Download MySQL Community Server Mysql解压&#xff1a; 解压后在根目录新建data文件夹和新建my.ini文件 my.ini文件内容如下: 注意&#xff1a;记得修改目录位置 [mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 basedirD:\\mysql-5.7.30…...

聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化

聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化 目录 聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化&#xff0c;聚类结果可视化&#xff0c;MATLAB程…...

uniapp 使用 mui-player 插件播放 m3u8/flv 视频流

在UniApp中使用mui-player插件播放M3U8/FLV视频流&#xff0c;可以按照以下步骤进行操作&#xff1a; 1. 安装mui-player插件 &#xff1a;在UniApp项目根目录下&#xff0c;使用命令行工具执行以下命令安装mui-player插件&#xff1a; npm install mui-player --save2. 在需…...

大数据课程K4——Spark的DAGRDD依赖关系

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的DAG; ⚪ 掌握Spark的RDD的依赖关系; ⚪ 了解Spark对于DAG的Stage的划分; 一、DAG概念 1. 概述 Spark会根据用户提交的计算逻辑中的RDD的转换和动作来生成RDD之间的依赖关…...

disable 禁用元素后无法触发点击事件

业务需求点击被禁用的输入框触发事件 在被禁用元素上套一层div div上绑定事件 原本是不需要加事件穿透即可触发 但是最近谷歌更新触发不了 加一个事件穿透就好了 核心代码 style"pointer-events:none"style“pointer-events:none” 事件穿透 整体代码 <el-table-…...

uni-app开启gzip配置

指令&#xff1a;npm install webpack4.46.0 --save-dev 指令&#xff1a;npm install compression-webpack-plugin6.1.1 --save-dev vue.config.js const CompressionWebpackPlugin require(compression-webpack-plugin);module.exports {configureWebpack: config > {…...

房屋结构健康监测,科技助力让建筑更安全

房屋建筑是人们赖以生存的场所&#xff0c;然而当前我国许多房屋已经达到了使用寿命的中期&#xff0c;房屋的安全系数逐年降低&#xff0c;风险也随着时间的推移而累积。长期以来&#xff0c;我国的房屋普遍存在寿命短、隐患多的问题&#xff0c;“重建设&#xff0c;轻管理”…...

Android 面试之Glide做了哪些优化?

前言 Glide可以说是最常用的图片加载框架了&#xff0c;Glide链式调用使用方便&#xff0c;性能上也可以满足大多数场景的使用&#xff0c;Glide源码与原理也是面试中的常客。 但是Glide的源码内容比较多&#xff0c;想要学习它的源码往往千头万绪&#xff0c;一时抓不住重点.…...

【韩顺平 零基础30天学会Java】数组、排序和查找(2days)

数组、排序、查找和多维数组 数组可以存放多个同一类型的数据。数组也是一种数据类 型&#xff0c;是引用数据类型。 定义一个数组 double[] hens {3,5,1,3.4,2,50} 遍历数组得到数组所有元素的和 hens[下标]&#xff0c;下标是从0开始编号的。 可以通过数组名.lenght得到数组…...

VUE笔记(一)初识vue

一、vue的简介 1、什么是vue 官网地址:Vue.js Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。 构建用户界面&#xff1a;之前在学习vue之前通过原生js对DOM操作进行构建用户界面的 使用原生js构建用户界面的不足 - 没有规范&#xff0c…...

3D点云处理:学习总结(更新整理中)

文章目录 开发工具个人看法 微信&#xff1a;dhlddx B站演示视频 前置说明&#xff1a;仅是个人在使用pcl开发过程中的总结&#xff08;点云处理顺序或比较实用的功能&#xff09;&#xff0c;不喜勿喷&#xff1b; 开发工具 开发IDE&#xff1a;Qt Creator&#xff08;Windo…...

Day45|leetcode 70. 爬楼梯、322. 零钱兑换、279.完全平方数

leetcode 70. 爬楼梯 题目链接&#xff1a;70. 爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 本题可以用背包问题来解决&#xff0c;就相当于楼顶是背包&#xff0c;台阶是物品&#xff0c;相当于之前写法的进阶版。 代码实现 class Solution { public:int climbStairs(in…...

arm:day9

1。思维导图 2..I2C实验&#xff0c;检测温度和湿度 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "gpio.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4…...

【大模型AIGC系列课程 1-2】创建并部署自己的ChatGPT机器人

OpenAI API 调用 获取 openai api api-key https://platform.openai.com/account/api-keys 利用 python requests 请求 openai 参考 openai 接口说明:https://platform.openai.com/docs/api-reference/chat/create import json # 导入json包 import requests # 导入req…...

启动metastore服务报错

启动Metastore的时候报错&#xff1a; 简略的报错信息&#xff1a; MetaException(message:Error creating transactional connection factory)Caused by: MetaException(message:Error creating transactional connection factory)Caused by: javax.jdo.JDOFatalInternalExce…...

c 语言 算法 技巧 之 用移位来代替乘除

除法 当你需要计算一个数的一半时&#xff0c;通常我们会考虑使用除法运算&#xff08;/&#xff09;来实现。然而&#xff0c;计算机内部的运算中&#xff0c;除法通常比加法和乘法运算慢得多&#xff0c;因为除法需要更多的处理步骤。 位运算在这种情况下可以提供一个快速的…...

python爬虫实战零基础(3)——某云音乐

爬取某些云网页音乐&#xff0c;无需app 分析网页第二种方式批量爬取 声明&#xff1a;仅供参考学习&#xff0c;参考&#xff0c;若有不足&#xff0c;欢迎指正 你是不是遇到过这种情况&#xff0c;在pc端上音乐无法下载&#xff0c;必须下载客户端才能下载&#xff1f; 那么&…...

渗透测试漏洞原理之---【XSS 跨站脚本攻击】

文章目录 1、跨站 脚本攻击1.1、漏洞描述1.2、漏洞原理1.3、漏洞危害1.4、漏洞验证1.5、漏洞分类1.5.1、反射性XSS1.5.2、存储型XSS1.5.3、DOM型XSS 2、XSS攻防2.1、XSS构造2.1.1、利用<>2.1.2、JavaScript伪协议2.1.3、时间响应 2.2、XSS变形方式2.2.1、大小写转换2.2.2…...

【浮点数二分】

数的三次方根 #include<iostream> using namespace std;double n;int main(){cin>>n;double l -10000;double r 10000;while((r-l)>1e-8){double mid (lr)/2;if((mid*mid*mid)>n) r mid;else l mid;}printf("%lf",l);return 0; }...