实验15.多线程调度

简介

实验.多线程调度

内核线程

    1.在时钟中断函数中处理中,减少当前线程pcb的tick,tick为0则启动调度2.调度,把当前线程pcb放入就绪对立队尾,把就绪线程队首拿出来执行

主要代码

引导

省略

内核

list.h

#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H#include "global.h"
#include "stdint.h"/// 拿到menber相当于struct_type的偏移
/// struct_type 结构体
// member 结构体的属性
#define offset(struct_type, member) (int)(&((struct_type*)0)->member)///  返回这个属性地址的结构体地址
/// struct_type 结构体
/// struct_member_name 结构体属性
/// elem_ptr 结构体的属性的地址
#define elem2entry(struct_type, struct_member_name, elem_ptr) \(struct_type*)((int)elem_ptr - offset(struct_type, struct_member_name))/// @brief 链表元素
struct list_elem {struct list_elem* prev;  // 前躯元素struct list_elem* next;  // 后继元素
};/// @brief 双向链表
struct list {// 这两个就如同两堵墙,在墙中间的才是元素struct list_elem head;  // 队首 注意: 第一个元素是head.next!!!struct list_elem tail;  // 队尾
};/// @brief 自定义函数类型function
typedef bool(function)(struct list_elem*, int arg);void list_init(struct list*);
void list_insert_before(struct list_elem* before, struct list_elem* elem);
void list_push(struct list* plist, struct list_elem* elem);
void list_iterate(struct list* plist);
void list_append(struct list* plist, struct list_elem* elem);
void list_remove(struct list_elem* pelem);
struct list_elem* list_pop(struct list* plist);
bool list_empty(struct list* plist);
uint32_t list_len(struct list* plist);
struct list_elem* list_traversal(struct list* plist, function func, int arg);
bool elem_find(struct list* plist, struct list_elem* obj_elem);
#endif

list.c

#include "list.h"
#include "interrupt.h"/// @brief 初始化双向链表
/// @param list 链表指针
void list_init(struct list* list) {list->head.prev = NULL;list->head.next = &list->tail;list->tail.prev = &list->head;list->tail.next = NULL;
}/// @brief 把链表元素elem插入在元素before之前
/// @param before 链表元素
/// @param elem  链表元素
void list_insert_before(struct list_elem* before, struct list_elem* elem) {// 关闭中断enum intr_status old_status = intr_disable();// 将before前驱元素的后继元素更新为elem, 暂时使before脱离链表before->prev->next = elem;// 更新elem自己的前驱结点为before的前驱elem->prev = before->prev;// 更新elem自己的后继结点为before, 于是before又回到链表elem->next = before;// 更新before的前驱结点为elembefore->prev = elem;// 恢复原来的中断状态intr_set_status(old_status);
}/// @brief 添加元素到列表队首,类似栈push操作
/// @param plist 队列
/// @param elem 元素
void list_push(struct list* plist, struct list_elem* elem) {list_insert_before(plist->head.next, elem);
}/// @brief 追加元素到链表队尾
/// @param plist
/// @param elem
void list_append(struct list* plist, struct list_elem* elem) {list_insert_before(&plist->tail, elem);
}/// @brief 使元素pelem脱离链表
/// @param pelem 元素
void list_remove(struct list_elem* pelem) {// 关闭中断enum intr_status old_status = intr_disable();pelem->prev->next = pelem->next;pelem->next->prev = pelem->prev;// 恢复中断状态intr_set_status(old_status);
}/// @brief 将链表第一个元素弹出并返回,类似栈的pop操作
/// @param list 双向链表
struct list_elem* list_pop(struct list* plist) {struct list_elem* elem = plist->head.next;list_remove(elem);return elem;
}/// @brief 从链表中查找元素obj_elem,
/// @param plist
/// @param obj_elem
/// @return 成功时返回true,失败时返回false
bool elem_find(struct list* plist, struct list_elem* obj_elem) {struct list_elem* elem = plist->head.next;while (elem != &plist->tail) {if (elem == obj_elem) { return true; }elem = elem->next;}return false;
}/// @brief 遍历列表内所有元素,逐个判断是否有符合条件的元素
/// @param list 链表
/// @param function 判断函数,返回true位满足条件,false位不满足条件
/// @param int 参数个数
struct list_elem* list_traversal(struct list* plist, function func, int arg) {struct list_elem* elem = plist->head.next;if (list_empty(plist)) return NULL;while (elem != &plist->tail) {if (func(elem, arg)) {  // func返回ture则认为该元素在回调函数中符合条件,直接返回return elem;}elem = elem->next;}return NULL;
}/// @brief 返回链表长度
/// @param plist 链表
/// @return 链表长度
uint32_t list_len(struct list* plist) {struct list_elem* elem = plist->head.next;uint32_t length = 0;while (elem != &plist->tail) {length++;elem = elem->next;}return length;
}/// @brief 判断链表是否为空
/// @param plist 链表
/// @return 空时返回true,否则返回false
bool list_empty(struct list* plist) {  // 判断队列是否为空return (plist->head.next == &plist->tail ? true : false);
}

switch.s

; switch.s
; 时间: 2024-07-25
; 来自: ccj
; 描述: 定义切换pcb的函数;---切换pcb(cur_pcb,next_pcb) begin---
section .text
global switch_to
switch_to:; 保存当前环境push esipush edipush ebxpush ebp; 保存当前环境到cur_pcbmov eax, [esp + 20] ; 得到栈中的参数cur_pcb, cur_pcb = [esp+20]mov [eax], esp      ; 保存esp到pcb的 self_kstack =; 结果 curpub.self_kstack = esp; 切换当前环境到nex_pcbmov eax, [esp + 24] ; 得到栈中的参数next_pcb, next_pcb = [esp+24]mov esp, [eax]      ; esp = next_pcb.self_kstack; 恢复当前环境pop ebppop ebxpop edipop esiret
;---切换pcb(cur_pcb,next_pcb) end---

thread.h

#ifndef __THREAD_THREAD_H
#define __THREAD_THREAD_H
#include "stdint.h"typedef void thread_func(void*);/// @brief 进程或线程的状态
enum task_status {TASK_RUNNING,  //TASK_READY,TASK_BLOCKED,TASK_WAITING,TASK_HANGING,TASK_DIED
};/// @brief 中断栈,中断发生时,保护程序(线程或进程)的上下文环境
/// 进程或线程被外部中断或软中断打断时,会按照此结构压入上下文寄存器
/// kernel.s中intr_exit的出栈操作是此结构的逆操作
/// 中断栈在pcb的最顶端
struct intr_stack {// kernel.s 宏VECTOR中push %1压入的中断号uint32_t vec_no;// kernel.s 中pushad压入的寄存器uint32_t edi;uint32_t esi;uint32_t ebp;uint32_t esp_dummy;  // 虽然pushad把esp也压入,但esp是不断变化的,所以会被popad忽略uint32_t ebx;uint32_t edx;uint32_t ecx;uint32_t eax;// kernel.s 中保存上下文环境压入的寄存器uint32_t gs;uint32_t fs;uint32_t es;uint32_t ds;// 以下由cpu从低特权级进入高特权级时压入uint32_t err_code;  // err_code会被压入在eip之后void (*eip)(void);uint32_t cs;uint32_t eflags;void* esp;uint32_t ss;
};/// @brief 线程栈 保存线程的上下文
/// ABI规则:主调函数调用被调函数,被调函数一定要保存ebp、ebx、edi、esi、esp
/// eip:线程下一步
struct thread_stack {uint32_t ebp;uint32_t ebx;uint32_t edi;uint32_t esi;// 线程第一次执行时,eip指向待调用的函数kernel_thread// 其它时候,eip是指向switch_to的返回地址void (*eip)(thread_func* func, void* func_arg);// 以下仅供第一次被调度上cpu时使用void(*unused_retaddr);  // 占位置充数为返回地址thread_func* function;  // 由Kernel_thread所调用的函数名void* func_arg;         // 由Kernel_thread所调用的函数所需的参数
};/// @brief 进程或线程的pcb process control block 4096字节
/// 一个pcb包含1个中断栈,1个线程栈,
struct task_struct {uint32_t* self_kstack;    // pcb中线程栈的地址enum task_status status;  // 状态uint8_t priority;         // 线程优先级char name[16];            //uint32_t stack_magic;     // pcb魔数,用于检测栈的溢出
};void thread_create(struct task_struct* pthread, thread_func function, void* func_arg);
void init_thread(struct task_struct* pthread, char* name, int prio);
struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg);
#endif

thread.c

// 文件: thread.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 申请了一个物理页做pcb,在pcb中定义了下一步kernel_thread,然后切换到该pcb的环境,运行#include "thread.h"
#include "stdint.h"
#include "string.h"
#include "global.h"
#include "memory.h"#define PG_SIZE 4096/// @brief 由kernel_thread去执行function(func_arg)
/// @param function 函数指针
/// @param func_arg 函数参数
static void kernel_thread(thread_func* function, void* func_arg) { function(func_arg); }/// @brief 初始化pcb,将待执行的函数和参数放到pcb中相应的位置
/// @param pthread pcb
/// @param function 待执行的函数
/// @param func_arg 函数参数
void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) {// 先预留中断使用栈的空间,可见thread.h中定义的结构pthread->self_kstack -= sizeof(struct intr_stack);// 再留出线程栈空间pthread->self_kstack -= sizeof(struct thread_stack);// 此时的self_kstack看作线程栈的首地址struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack;kthread_stack->eip = kernel_thread;  // 指向kernel_threadkthread_stack->function = function;  // 设置函数kthread_stack->func_arg = func_arg;  // 设置参数kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0;
}/// @brief 初始化线程基本信息
/// @param pthread pcb
/// @param name 线程名
/// @param prio 优先级
void init_thread(struct task_struct* pthread, char* name, int prio) {// 清0memset(pthread, 0, sizeof(*pthread));// 设置名字、状态、优先级strcpy(pthread->name, name);pthread->status = TASK_RUNNING;pthread->priority = prio;// self_kstack是线程自己在内核态下使用的栈顶地址pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);pthread->stack_magic = 0x19870916;  // 自定义的魔数
}/// @brief 创建一优先级为prio的线程,线程名为name,线程所执行的函数是function(func_arg)
/// @param name 线程名
/// @param prio 优先级
/// @param function 线程要执行的函数
/// @param void* 函数参数
struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) {// 申请1个物理页来存放pcbstruct task_struct* thread = get_kernel_pages(1);// 初始化pcbinit_thread(thread, name, prio);// 创建线程thread_create(thread, function, func_arg);// 切换到thread的上下文,并执行threadasm volatile("movl %0, %%esp; pop %%ebp; pop %%ebx; pop %%edi; pop %%esi; ret":: "g"(thread->self_kstack): "memory");// movl %0, %%esp;// 把thread->self_kstack的值给esp// pop %%ebp; pop %%ebx; pop %%edi; pop %%esi;// 从thread->self_kstack中把这几个属性值给到寄存器// ret// 由于堆栈和这些关键寄存器已被设置为新线程的值,这会导致执行流跳转到新线程的上下文中继续执行return thread;
}}/// @brief 初始化线程环境
/// @param
void thread_init(void) {put_str("[thread] thread_init start\n");list_init(&thread_ready_list);list_init(&thread_all_list);// 将当前main函数创建为线程make_main_thread();put_str("[thread] thread_init done\n");
}

timer.h

#ifndef __DEVICE_TIME_H
#define __DEVICE_TIME_H#include "stdint.h"void timer_init(void);
#endif

timer.c

// 文件: timer.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 调快时钟,调快时钟、注册时钟中断来调度线程#include "timer.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"#define IRQ0_FREQUENCY   100
#define INPUT_FREQUENCY  1193180
#define COUNTER0_VALUE   INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT    0x40
#define COUNTER0_NO      0
#define COUNTER_MODE     2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43uint32_t ticks;  // 中断开始,开始计数/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value*/
static void frequency_set(uint8_t counter_port,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value) {/* 往控制字寄存器端口0x43中写入控制字 */outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));/* 先写入counter_value的低8位 */outb(counter_port, (uint8_t)counter_value);/* 再写入counter_value的高8位 */outb(counter_port, (uint8_t)counter_value >> 8);
}/* 时钟的中断处理函数 */
static void intr_timer_handler(void) {struct task_struct* cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x19870916);  // 检查栈是否溢出cur_thread->elapsed_ticks++;  // 记录此线程占用的cpu时间嘀ticks++;                      // 记录总时钟数if (cur_thread->ticks == 0) {  // 若进程时间片用完就开始调度新的进程上cpuschedule();} else {  // 将当前进程的时间片-1cur_thread->ticks--;}
}/* 初始化PIT8253 */
void timer_init(void) {put_str("[timer] timer_init start\n");/* 设置8253的定时周期,也就是发中断的周期 */frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);// 注册时钟中断,用来调度线程register_handler(0x20, intr_timer_handler);put_str("[timer] timer_init done\n");
}

interupt.h

#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H#include "stdint.h"typedef void* intr_handler;void idt_init(void);/// @brief 中断机制状态
enum intr_status {INTR_OFF,  // 中断关闭INTR_ON    // 中断打开
};
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);
enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
void register_handler(uint8_t vector_no, intr_handler function);#endif

interupt.c

// 文件: timer.c
// 时间: 2024-07-23
// 来自: ccj
// 描述: 调快时钟,调快时钟、注册时钟中断来调度线程#include "timer.h"
#include "io.h"
#include "print.h"
#include "interrupt.h"
#include "thread.h"
#include "debug.h"#define IRQ0_FREQUENCY   100
#define INPUT_FREQUENCY  1193180
#define COUNTER0_VALUE   INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT    0x40
#define COUNTER0_NO      0
#define COUNTER_MODE     2
#define READ_WRITE_LATCH 3
#define PIT_CONTROL_PORT 0x43uint32_t ticks;  // 中断开始,开始计数/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value*/
static void frequency_set(uint8_t counter_port,uint8_t counter_no,uint8_t rwl,uint8_t counter_mode,uint16_t counter_value) {/* 往控制字寄存器端口0x43中写入控制字 */outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));/* 先写入counter_value的低8位 */outb(counter_port, (uint8_t)counter_value);/* 再写入counter_value的高8位 */outb(counter_port, (uint8_t)counter_value >> 8);
}/* 时钟的中断处理函数 */
static void intr_timer_handler(void) {struct task_struct* cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x19870916);  // 检查栈是否溢出cur_thread->elapsed_ticks++;  // 记录此线程占用的cpu时间嘀ticks++;                      // 记录总时钟数if (cur_thread->ticks == 0) {  // 若进程时间片用完就开始调度新的进程上cpuschedule();} else {  // 将当前进程的时间片-1cur_thread->ticks--;}
}/* 初始化PIT8253 */
void timer_init(void) {put_str("[timer] timer_init start\n");/* 设置8253的定时周期,也就是发中断的周期 */frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);// 注册时钟中断,用来调度线程register_handler(0x20, intr_timer_handler);put_str("[timer] timer_init done\n");
}

init.h

#ifndef __KERNEL_INIT_H
#define __KERNEL_INIT_H
void init_all(void);
#endif

init.c

// 文件: init.c
// 时间: 2024-07-22
// 来自: ccj
// 描述: 内核所有初始化操作#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "timer.h"
#include "memory.h"
#include "thread.h"/// @brief 内核所有初始化
void init_all() {put_str("init all\n");idt_init();    // 初始化中断timer_init();  // 调快时钟、注册时钟中断来调度线程mem_init();  // 初始化内存管理系统thread_init();
}

main.c

// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"void k_thread_a(void*);
void k_thread_b(void*);int main(void) {put_str("I am kernel\n");init_all();thread_start("k_thread_a", 4, k_thread_a, "argA\n");thread_start("k_thread_a", 16, k_thread_b, "argB\n");intr_enable();  // 打开中断,使时钟中断起作用while (1) { put_str("Main\n"); };return 0;
}void k_thread_a(void* arg) {char* para = arg;while (1) { put_str(para); }
}void k_thread_b(void* arg) {char* para = arg;while (1) { put_str(para); }
}

编译

省略

运行

start.sh

#/bin/bash
# 文件: start.sh
# 描述: 启动bochs
# 时间: 2024-07-19
# 来自: ccjset -xbin/bochs -f bochsrc.disk

在这里插入图片描述

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

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

相关文章

【2024最新】 服务器安装Ubuntu20.04 (安装教程、常用命令、故障排查)持续更新中.....

安装教程&#xff08;系统、NVIDIA驱动、CUDA、CUDNN、Pytorch、Timeshift、ToDesk、花生壳&#xff09; 制作U盘启动盘&#xff0c;并安装系统 在MSDN i tell you下载Ubuntu20.04 Desktop 版本&#xff0c;并使用Rufus制作UEFI启动盘&#xff0c;参考UEFI安装Ubuntu使用GPTU…

mysql 的MHA

mysql 的MHA 什么是MHA 高可用模式下的故障切换&#xff0c;基于主从复制。 单点故障和主从复制不能切换的问题。 至少需要3台。 故障切换过程0-30秒。 vip地址&#xff0c;根据vip地址所在的主机&#xff0c;确定主备。 主 vip 备 vip 主和备不是优先确定的&#xff…

InternLM Linux 基础知识

完成SSH连接与端口映射并运行hello_world.py 创建并运行test.sh文件 使用 VSCODE 远程连接开发机并创建一个conda环境

“pandas”的坑

参考&#xff1a;百度安全验证 本文基于python第三方数据分析库pandas&#xff0c;分享这几天所遇到的3个爬坑的案例&#xff0c;希望对也在爬坑的同学们尽一份绵薄之力&#xff0c;如有错误或者写得不好的地方&#xff0c;烦请指正&#xff0c;谢谢。 01df中startswith的坑 …

led灯什么牌子的质量好?led灯护眼效果好的五款爆品分享

大家在选择led灯的时候&#xff0c;最关心的就是“led灯什么牌子的质量好&#xff1f;”市面上商家推出来的led灯品牌众多&#xff0c;型号以及功能也是令人眼花缭乱的&#xff0c;既然如此&#xff0c;那我们应该如何买到质量过关又好用的led灯呢&#xff1f;接下来我将为大家…

敏感信息泄露wp

1.右键查看网页源代码 2.前台JS绕过&#xff0c;ctrlU绕过JS查看源码 3.开发者工具&#xff0c;网络&#xff0c;查看协议 4.后台地址在robots,拼接目录/robots.txt 5.用dirsearch扫描&#xff0c;看到index.phps,phps中有源码&#xff0c;拼接目录&#xff0c;下载index.phps …

网页封装app:如何将网站转换为移动应用程序?(网页封装app)

随着移动互联网的普及&#xff0c;越来越多的企业开始关注移动应用程序的开发。但是&#xff0c;对于一些小型企业或个人&#xff0c;开发一款移动应用程序可能需要投入大量的时间和金钱。这时&#xff0c;网页封装app就成了一个不错的选择。 app在线封装www,ppzhu.net 什么是…

【AI人工智能】文心智能体,00后疯感工牌生成器,低代码工作流的简单应用以及图片快速响应解决方案,干货满满,不容错过哦

背景 文心智能体平台&#xff0c;开启新一轮活动&#xff0c;超级创造营持续百日活动。 在AI 浪潮席卷的今天&#xff0c;如雨后春笋般丛生的 AI 应用&#xff0c;昭告着时代风口显然已随之到来。 如何能把握住时代红利&#xff0c;占据风口&#xff0c;甚至打造新风向&#x…

探索 Kubernetes 持久化存储之 Longhorn 初窥门径

作者&#xff1a;运维有术星主 在 Kubernetes 生态系统中&#xff0c;持久化存储扮演着至关重要的角色&#xff0c;它是支撑业务应用稳定运行的基石。对于那些选择自建 Kubernetes 集群的运维架构师而言&#xff0c;选择合适的后端持久化存储解决方案是一项至关重要的选型决策。…

因为媳妇的一句话,我做了一个AI画图软件

因为媳妇的一句话&#xff0c;我做了一个AI画图软件 T恤的配图 前些天媳妇参加了一个创业比赛&#xff0c;其中一个比赛任务是参赛成员需要穿主题队服&#xff0c;队服的图案完全需要自己设计&#xff0c;需要独一无二还得漂亮。 问我&#xff1a;“能不能用AI做一张图&#…

Python酷库之旅-第三方库Pandas(052)

目录 一、用法精讲 191、pandas.Series.drop方法 191-1、语法 191-2、参数 191-3、功能 191-4、返回值 191-5、说明 191-6、用法 191-6-1、数据准备 191-6-2、代码示例 191-6-3、结果输出 192、pandas.Series.droplevel方法 192-1、语法 192-2、参数 192-3、功能…

C# 介绍

文章目录 一. 一个简单的helloworld二. 程序结构三. 类型和变量四. 表达式1. f(x)2. []3. typeof4. default5. new6. checked和unchecked7. sizeof8. 移位9. is和as10. null合并 五. 语句六. 类和对象1. 可访问性2. 类型参数3. 基类和派生类4. 字段5. 方法6. 参数7. 扩展方法&a…

53.综合实验:UART接收图像、写入RAM、通过TFT显示

&#xff08;1&#xff09;设计定义&#xff1a;UART_RX模块接收数据&#xff0c;通过写入逻辑写入RAM存储器中&#xff0c;然后通过读取逻辑&#xff0c;从RAM中读出数据&#xff0c;发送给TFT显示屏。 &#xff08;2&#xff09;FPGA逻辑资源有限&#xff0c;因此设置128 * 1…

新生报到系统2024((代码+论文+ppt)

下载在最后 技术栈: ssmmysqljsp 展示: 下载地址: CSDN现在上传有问题,有兴趣的朋友先收藏.正常了贴上下载地址 备注:

docker安装部署elasticsearch7.15.2

docker安装部署elasticsearch7.15.2 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.2如果不想下载或者镜像拉去太慢可以直接下载文章上面的镜像压缩包 使用镜像解压命令 docker load -i elasticsearch-7-15-2.tar如下图所示就表示镜像解压成…

Qt+OpenCascade开发笔记(二):windows开发环境搭建(二):Qt引入occ库,搭建基础工程模板Demo和发布Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140763014 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

51单片机嵌入式开发:19、STC89C52R控制LCD1602码表+数码管+后台数显(串口)

STC89C52R控制LCD1602码表数码管后台数显&#xff08;串口&#xff09; 1 概述1.1 项目概述1.2 项目组成部分1.3 功能描述 2 开发环境2.1 支持设备2.2 硬件电路 3 软件代码工程4 演示4.1 Proteus仿真4.2 实物演示 5 总结 1 概述 1.1 项目概述 本项目旨在利用STC89C52R单片机实…

后端笔记(1)--javaweb简介

1.JavaWeb简介 ​ *用Java技术来解决相关web互联网领域的技术栈 1.网页&#xff1a;展现数据 2.数据库&#xff1a;存储和管理数据 3.JavaWeb程序&#xff1a;逻辑处理 2.mysql 1.初始化Mysql mysqld --initialized-insecure2.注册Mysql服务 mysqld -install3.启动Mysql…

USB3.0的等长要求到底是多少?

USB2.0与USB3.0接口的PCB布局布线要求PCB资源PCB联盟网 - Powered by Discuz! (pcbbar.com) 90欧姆阻抗&#xff0c;走差分线&#xff1a; 重点来了&#xff1a;

第十九届全国大学生智能汽车竞赛地平线创意组在武汉理工大学隆重开幕

7月27日上午&#xff0c;第十九届全国大学生智能汽车竞赛地平线创意组智慧医疗赛道全国选拔赛开幕式隆重举行&#xff0c;大赛由中国自动化学会、第十九届全国大学生智能汽车竞赛组织委员会主办&#xff0c;武汉理工大学、地平线、古月居承办。首年即吸引来自全国各地280支队伍…