操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之控制台工作
前言
在 Linux 内核中,字符设备主要包括控制终端设备和串行终端设备,对这些设备的输入输出涉及控制台驱动程序,这包括键盘中断驱动程序 keyboard.S 和控制台显示驱动程序 console.c,还有终端驱动程序与上层程序之间的接口部分。
终端驱动程序用于控制终端设备,在终端设备和进程之间传输数据,并对所传输的数据进行一定的处理。用户在键盘上键入的原始数据(Raw data),在通过终端程序处理后,被传送给一个接收进程;而进程向终端发送的数据,在终端程序处理后,会被显示在终端屏幕上或者通过串行线路被发送到远程终端。根据终端程序对待输入或输出数据的方式,可以把终端工作模式分成两种。一种是规范(canonical)模式,此时经过终端程序的数据将被进行变换处理,然后再送出。例如把 TAB 字符扩展为8个空格字符,用键入的删除字符(backspace)控制删除前面键入的字符等。使用的处理函数一般称为行规则(linediscipline)或行规程模块。另一种是非规范(non-canonical)模式或称原始(raw)模式。在这种模式下,行规则程序仅在终端与进程之间传送数据,而不对数据进行规范模式的变换处理。
键盘中断
按下键盘后会触发中断,CPU 收到你的键盘中断后,根据中断号,寻找由操作系统写好的键盘中断处理程序。
内核的初始化如下:
void main(void) {...trap_init();...
}void trap_init(void) {int i;set_trap_gate(0,÷_error);set_trap_gate(1,&debug);set_trap_gate(2,&nmi);set_system_gate(3,&int3); /* int3-5 can be called from all */set_system_gate(4,&overflow);set_system_gate(5,&bounds);set_trap_gate(6,&invalid_op);set_trap_gate(7,&device_not_available);set_trap_gate(8,&double_fault);set_trap_gate(9,&coprocessor_segment_overrun);set_trap_gate(10,&invalid_TSS);set_trap_gate(11,&segment_not_present);set_trap_gate(12,&stack_segment);set_trap_gate(13,&general_protection);set_trap_gate(14,&page_fault);set_trap_gate(15,&reserved);set_trap_gate(16,&coprocessor_error);for (i=17;i<48;i++)set_trap_gate(i,&reserved);set_trap_gate(45,&irq13);set_trap_gate(39,¶llel_interrupt);
}
上面的代码主要划分为
// set 了一堆 trap_gate
set_trap_gate(0, ÷_error);
...
// 又 set 了一堆 system_gate
set_system_gate(45, &bounds);
...
// 又又批量 set 了一堆 trap_gate
for (i=17;i<48;i++)set_trap_gate(i, &reserved);
涉及内联汇编的set_trap_gate 和 set_system_gate,其作用是在中断描述符表中插入了一个中断描述符
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \"movw %0,%%dx\n\t" \"movl %%eax,%1\n\t" \"movl %%edx,%2" \: \: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \"o" (*((char *) (gate_addr))), \"o" (*(4+(char *) (gate_addr))), \"d" ((char *) (addr)),"a" (0x00080000))#define set_trap_gate(n,addr) \_set_gate(&idt[n],15,0,addr)#define set_system_gate(n,addr) \_set_gate(&idt[n],15,3,addr)
往这个 idt 表里一项一项地写东西,其对应的中断号就是第一个参数,中断处理程序就是第二个参数。之后如果来一个中断后,CPU 根据其中断号,就可以到这个中断描述符表 idt 中找到对应的中断处理程序了。
set_trap_gate(0,÷_error);
set_system_gate(5,&overflow);
设置 0 号中断,对应的中断处理程序是 divide_error。设置 5 号中断,对应的中断处理程序是 overflow,是边界出错中断。而divide_error当 CPU 执行了一条除零指令的时候,会从硬件层面发起一个 0 号异常中断,然后执行由我们操作系统定义的 divide_error 也就是除法异常处理程序,执行完之后再返回。
其中trap 与 system 的区别仅仅在于,设置的中断描述符的特权级不同,前者是 0(内核态),后者是 3(用户态),这块展开将会是非常严谨的、绕口的、复杂的特权级相关的知识,可以理解为都是设置一个中断号和中断处理程序的存在优先级的对应关系就好了。
for (i=17;i<48;i++)set_trap_gate(i,&reserved);
17 到 48 号中断都批量设置为了 reserved 函数,这是暂时的,后面各个硬件初始化时要重新设置好这些中断,把暂时的这个给覆盖掉。
最终,内存中那个 idt 此时如下图(低端1M,虚拟地址与物理地址是相同的):
键盘产生的中断的中断号是 0x21,此时这个中断号还仅仅对应着一个临时的中断处理程序 &reserved。
void main(void) {...trap_init();...tty_init();...sti();...
}void tty_init(void) {rs_init();con_init();
}void con_init(void) {...set_trap_gate(0x21,&keyboard_interrupt);...
}
tty_init (终端是一种字符型设备,它有多种类型。通常使用 tty来简称各种类型的终端设备。tty是 Teletype的缩写,它是一种由 Teletype 公司生产的最早出现的终端设备,样子很象电传打字机。)最后根据调用链,会调用到一行添加 0x21 号中断处理程序的代码,set_trap_gate。
而后面的 keyboard_interrupt 就是键盘的中断处理程序!
从这一行代码开始,键盘中断生效了!
现在的中断处于禁用状态,不论是键盘中断还是其他中断;sti 最终会对应一个同名的汇编指令 sti,表示允许中断。所以这行代码之后,键盘中断才真正开始生效!
串口中断的开启,以及设置对应的中断处理程序
void rs_init(void)
{set_intr_gate(0x24,rs1_interrupt);set_intr_gate(0x23,rs2_interrupt);init(tty_table[1].read_q.data);init(tty_table[2].read_q.data);outb(inb_p(0x21)&0xE7,0x21);
}
控制台显示
这些 if else 是为了应对不同的显示模式,来分配不同的变量值。
void con_init(void) {...if (ORIG_VIDEO_MODE == 7) {...if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}else {...}} else {...if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10) {...}else {...}}...
}
当可以随意操作内存和 CPU 等设备,内存中有这样一部分区域,是和显存映射的。就是你往这些内存区域中写数据,相当于写在了显存中。而往显存中写数据,就相当于在屏幕上输出文本了。
mov [0xB8000],'h'
//h 相当于汇编编辑器帮我们转换成 ASCII 码的二进制数值,当然我们也可以直接写。
mov [0xB8000],0x68
//往内存中 0xB8000 这个位置写了一个值,只要一写,屏幕左上角则显示一个h
具体说来,这片内存是每两个字节表示一个显示在屏幕上的字符,第一个是字符的编码,第二个是字符的颜色
//当先忽略颜色,屏幕左上角会出现hello
mov [0xB8000],'h'
mov [0xB8002],'e'
mov [0xB8004],'l'
mov [0xB8006],'l'
mov [0xB8008],'o'
假设显示模式是文本模式,那条件分支就可以去掉很多,简化成如下:
#define ORIG_X (*(unsigned char *)0x90000)
#define ORIG_Y (*(unsigned char *)0x90001)
void con_init(void) {register unsigned char a;// 第一部分 获取显示模式相关信息video_num_columns = (((*(unsigned short *)0x90006) & 0xff00) >> 8);video_size_row = video_num_columns * 2;video_num_lines = 25;video_page = (*(unsigned short *)0x90004);video_erase_char = 0x0720;// 第二部分 显存映射的内存区域 video_mem_start = 0xb8000;video_port_reg = 0x3d4;video_port_val = 0x3d5;video_mem_end = 0xba000;// 第三部分 滚动屏幕操作时的信息origin = video_mem_start;scr_end = video_mem_start + video_num_lines * video_size_row;top = 0;bottom = video_num_lines;// 第四部分 定位光标并开启键盘中断gotoxy(ORIG_X, ORIG_Y);set_trap_gate(0x21,&keyboard_interrupt);outb_p(inb_p(0x21)&0xfd,0x21);a=inb_p(0x61);outb_p(a|0x80,0x61);outb(a,0x61);
}
对照内存数据表
所以,第一部分获取 0x90006 地址处的数据,就是获取显示模式等相关信息。 第二部分就是显存映射的内存地址范围,现在假设是 CGA 类型的文本模式,所以映射的内存是从 0xB8000 到 0xBA000。 第三部分是设置一些滚动屏幕时需要的参数,定义顶行和底行是哪里,这里顶行就是第一行,底行就是最后一行,很合理。 第四部分是把光标定位到之前保存的光标位置处(取内存地址 0x90000 处的数据),然后设置并开启键盘中断。
开启键盘中断后,键盘上敲击一个按键后就会触发中断,中断程序就会读键盘码转换成 ASCII 码,然后写到光标处的内存地址,也就相当于往显存写,于是这个键盘敲击的字符就显示在了屏幕上。
gotoxy函数,定位当前光标
#define ORIG_X (*(unsigned char *)0x90000)
#define ORIG_Y (*(unsigned char *)0x90001)
void con_init(void) {...// 第四部分 定位光标并开启键盘中断gotoxy(ORIG_X, ORIG_Y);...
}static inline void gotoxy(unsigned int new_x,unsigned int new_y) {...x = new_x;y = new_y;pos = origin + y*video_size_row + (x<<1);
}
(给 x y pos 这三个参数赋值。)
其中 x 表示光标在哪一列,y 表示光标在哪一行,pos 表示根据列号和行号计算出来的内存指针,也就是往这个 pos 指向的地址处写数据,就相当于往控制台的 x 列 y 行处写入字符了,然后,当你按下键盘后,触发键盘中断,之后的程序调用链是这样的。
_keyboard_interrupt:...call _do_tty_interrupt...void do_tty_interrupt(int tty) {copy_to_cooked(tty_table+tty);
}void copy_to_cooked(struct tty_struct * tty) {...tty->write(tty);...
}// 控制台时 tty 的 write 为 con_write 函数
void con_write(struct tty_struct * tty) {...__asm__("movb _attr,%%ah\n\t""movw %%ax,%1\n\t"::"a" (c),"m" (*(short *)pos):"ax");pos += 2;x++;...
}
con_write 函数
其中 __asm__ 内联汇编,就是把键盘输入的字符 c 写入 pos 指针指向的内存,相当于往屏幕输出了。之后两行 pos+=2 和 x++,就是调整所谓的光标。
写入一个字符,最底层,其实就是往内存的某处写个数据,然后顺便调整一下光标。
(光标的本质,其实就是 x y pos 这三变量而已。)
换行效果
当发现光标位置处于某一行的结尾时(知道屏幕上一共有几行几列了,就把光标计算出一个新值,让其处于下一行的开头)。
判断列号 x 是否大于了总列数
void con_write(struct tty_struct * tty) {...if (x>=video_num_columns) {x -= video_num_columns;pos -= video_size_row;lf();}...
}static void lf(void) {if (y+1<bottom) {y++;pos += video_size_row;return;}...
}
滚屏的效果,无非就是当检测到光标已经出现在最后一行最后一列了,那就把每一行的字符,都复制到它上一行,本质就是算好哪些内存地址上的值,拷贝到哪些内存地址即可。
具体而言,滚屏操作是指将指定开始行和结束行的一块文本内容向上移动(向上卷动 scroll up)或向下移动(向下卷动 scroll down)。如果将屏幕看作是显示内存上对应屏幕内容的一个窗口的话,那么将屏幕内容向上移即是将窗口沿显示内存向下移动;将屏幕内容向下移动即是将窗口向上移动。在程序中就是重新设置显示控制器中显示内存的起始位置 origin 以及调整程序中相应的变量。对于这两种操作各自都有两种情况。
对于向上卷动,当屏幕对应的显示内存窗口在向下移动后仍然在显示内存范围之内的情况,也即对应当前屏幕的内存块位置始终在显示内存起始位置(video_mem_start)和末端位置 video_mem_end 之间,那么只需要调整显示控制器中起始显示内存位置即可。但是当对应屏幕的内存块位置在向下移动时超出了实际显示内存的末端(video_mem_end)这种情况,就需要移动对应显示内存中的数据,以保证所有当前屏幕数据都落在显示内存范围内。在这第二中情况,程序中是将屏幕对应的内存数据移动到实际显示内存的开始位置处(video_mem_ start)。
程序中实际的处理过程分三步进行。首先调整屏幕显示起始位置 origin;然后判断对应屏幕内存数据是否超出显示内存下界(video_mem_end),如果超出就将屏幕对应的内存数据移动到实际显示内存的开始位置处(video_mem_start);最后对移动后屏幕上出现的新行用空格字符填满。
向下卷动屏幕的操作与向上卷屏相似,也会遇到这两种类似情况。只是由于屏幕窗口上移,因此会在屏幕上方出现一空行,并且在屏幕内容所对应的内存超出显示内存范围时需要将屏幕数据内存块往下移动到显示内存的末端位置。
见惯不怪的控制台,回车、换行、删除、滚屏、清屏等操作,其实底层都有实现相应的代码的。 其中 console.c 中的其他方法就是做这个事的。
// 定位光标的
static inline void gotoxy(unsigned int new_x, unsigned int new_y){}
// 滚屏,即内容向上滚动一行
static void scrup(void){}
// 光标同列位置下移一行
static void lf(int currcons){}
// 光标回到第一列
static void cr(void){}
...
// 删除一行
static void delete_line(void){}
console.c 这个文件可是整个内核中代码量最大的文件,但是功能特别单一,也都很简单,主要是处理键盘各种不同的按键,需要写好多 switch case 等语句。其中的所有子程序都是为了实现终端屏幕写函数 con_write()以及进行终端屏幕显示的控制操作。
当往一个控制台设备执行写操作时,就会调用 con_write()函数。这个函数管理着所有控制字符和换码字符序列,这些字符给应用程序提供全部的屏幕管理操作。所实现的换码序列采用 vt102 终端的规格,这意味着当你使用 telnet 连接到一台非 Linux 主机时,你的环境变量应该有TERM=vt102。然而,对于本地操作最佳的选择是设置 TERM=console,因为 Limux 控制台提供了一个 vt102 功能的超集。
函数 con_write()主要由转换语句组成,用于每次处理一个字符的有限长状态自动转义序列的解释。在正常方式下,显示字符会使用当前属性直接写到显示内存中。该函数会从终端 tty_struct数据结构的写缓冲队列 write_q 中取出字符或字符序列,然后根据字符的性质(是普通字符、控制字符、转义序列还是控制序列),把字符显示在终端屏幕上,或进行一些光标移动、字符除等屏幕控制操作。
终端屏幕初始化函数 con_init()会根据系统启动初始化时获得的系统信息,设置有关屏幕的一些基本参数值,用于 con_write()函数的操作。(有关终端设备字符缓冲队列的说明可参见 include/linux/tty.h 头文件。其中给出了字符缓冲队列的数据结构 tty_queue、终端的数据结构 tty_struct 和一些控制字符的值。另外,其中还有一些对缓冲队列进行操作的宏定义。)
在此之后,内核代码就可以用它来方便地在控制台输出字符!这在之后内核想要在启动过程中告诉用户一些信息,以及后面内核完全建立起来之后,由用户用 shell 进行操作时手动输入命令,都是可以用到这里的代码的!
相关文章:

操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之控制台工作
前言 在 Linux 内核中,字符设备主要包括控制终端设备和串行终端设备,对这些设备的输入输出涉及控制台驱动程序,这包括键盘中断驱动程序 keyboard.S 和控制台显示驱动程序 console.c,还有终端驱动程序与上层程序之间的接口部分。 终端驱动程序…...
Autogen_core: Message and Communication
目录 完整代码代码解释1. 消息的数据类:2. 创建代理人(MyAgent):3. 创建和运行代理人的运行时环境:4. 根据发送者路由消息的代理(RoutedBySenderAgent):5. 创建和运行带路由的代理&a…...
ComfyUI工作流教程、软件使用、开发指导、模型下载
在人工智能和设计技术迅速发展的今天,AI赋能的工作流已成为创意设计与生产的重要工具。无论是图片处理、服装试穿,还是室内设计与3D建模,这些智能化的解决方案极大地提高了效率和创作质量。 为了帮助设计师、开发者以及AI技术爱好者更好地利用这些工具,我们整理了一份详尽…...

零基础Vue学习1——Vue学习前环境准备
目录 环境准备 创建Vue项目 项目目录说明 后续开发过程中常用命令 环境准备 安装开发工具:vscode、webstorm、idea都可以安装node:V22以上版本即可安装pnpm 不知道怎么安装的可以私信我教你方法 创建Vue项目 本地新建一个文件夹,之后在文件夹下打开…...

定西市建筑房屋轮廓数据shp格式gis无偏移坐标(字段有高度和楼层)内容测评
定西市建筑房屋轮廓数据是GIS(Geographic Information System,地理信息系统)领域的重要资源,用于城市规划、土地管理、环境保护等多个方面。这份2022年的数据集采用shp(Shapefile)格式,这是一种…...

汉语向编程指南
汉语向编程指南 一、引言王阳明代数与流形学习理论慢道缓行理性人类型指标系统为己之学与意气实体过程晏殊几何学半可分离相如矩阵与生成气质邻域镶嵌气度曲面细分生成气质邻域镶嵌气度曲面细分社会科学概论琴生生物机械科技工业研究所软凝聚态物理开发工具包琴生生物机械 报告…...
Writing an Efficient Vulkan Renderer
本文出自GPU Zen 2。 Vulkan 是一个新的显式跨平台图形 API。它引入了许多新概念,即使是经验丰富的图形程序员也可能不熟悉。Vulkan 的主要目标是性能——然而,获得良好的性能需要深入了解这些概念及其高效应用方法,以及特定驱动程序实现的实…...
AI常见的算法
人工智能(AI)中常见的算法分为多个领域,如机器学习、深度学习、强化学习、自然语言处理和计算机视觉等。以下是一些常见的算法及其用途: 1. 机器学习 (Machine Learning) 监督学习 (Supervised Learning) 线性回归 (Linear Regr…...

LibreChat
文章目录 一、关于 LibreChat✨特点 二、使用LibreChat🪶多合一AI对话 一、关于 LibreChat LibreChat 是增强的ChatGPT克隆:Features Agents, Anthropic, AWS, OpenAI, Assistants API, Azure, Groq, o1, GPT-4o, Mistral, OpenRouter, Vertex AI, Gemi…...

Spring Boot 日志:项目的“行车记录仪”
一、什么是Spring Boot日志 (一)日志引入 在正式介绍日志之前,我们先来看看上篇文章中(Spring Boot 配置文件)中的验证码功能的一个代码片段: 这是一段校验用户输入的验证码是否正确的后端代码,…...

Spring Boot 实现文件上传和下载
文章目录 Spring Boot 实现文件上传和下载一、引言二、文件上传1、配置Spring Boot项目2、创建文件上传控制器3、配置文件上传大小限制 三、文件下载1、创建文件下载控制器 四、使用示例1、文件上传2、文件下载 五、总结 Spring Boot 实现文件上传和下载 一、引言 在现代Web应…...

慕课:若鱼1919的视频课程:Java秒杀系统方案优化 高性能高并发实战,启动文档
代码: Javahhhh/miaosha191: 运行成功了慕课若鱼1919的视频课程:Java秒杀系统方案优化 高性能高并发实战https://github.com/Javahhhh/miaosha191 https://github.com/Javahhhh/miaosha191 miaosha项目启动文档 需安装的配置环境: VMwar…...

React第二十七章(Suspense)
Suspense Suspense 是一种异步渲染机制,其核心理念是在组件加载或数据获取过程中,先展示一个占位符(loading state),从而实现更自然流畅的用户界面更新体验。 应用场景 异步组件加载:通过代码分包实现组件…...
虚幻基础08:组件接口
能帮到你的话,就给个赞吧 😘 文章目录 作用 作用 组件接口:可以直接调用对方的组件接口,而无需转换为actor。 实现对象间的通知。 A 通知 B 做什么。...

iPhone SE(第三代) 设备详情图
目录 产品宣传图内部图——后设备详细信息 产品宣传图 内部图——后 设备详细信息 信息收集于HubWeb.cn...

2025苹果CMS v10短剧模板源码
文件不到70kb,加载非常快 无配置,没有详情页,上传就可以直接使用 使用教程:上传到网站template目录并解压、进入网站后台选择模板 注意:默认调用ID为1的数据和扩展分类,建议新建站使用 源码下载…...

2007-2020年各省国内专利申请授权量数据
2007-2020年各省国内专利申请授权量数据 1、时间:2007-2020年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区名称、年份、国内专利申请授权量(项) 4、范围:31省 5、指标解释:专利是专利权的简称&…...
第一天-嵌入式应用开发介绍
首先,我们来介绍一下嵌入式的发展路线,虽然嵌入式的知识点众多,但是总体上来说,嵌入式分为以下两条主要路线: 单片机开发ArmLinux开发 当然,还有其他的一些例如FPGA这种的我们就不计算在内了,F…...
约瑟夫问题(信息学奥赛一本通-2037)
【题目描述】 N个人围成一圈,从第一个人开始报数,数到M的人出圈;再由下一个人开始报数,数到M 的人出圈;…输出依次出圈的人的编号。 【输入】 输入N和M。 【输出】 输出一行,依次出圈的人的编号。 【输入样…...

WPF5-x名称空间
1. x名称空间2. x名称空间内容3. x名称空间内容分类 3.1. x:Name3.2. x:Key3.3. x:Class3.4. x:TypeArguments 4. 总结 1. x名称空间 “x名称空间”的x是映射XAML名称空间时给它取的名字(取XAML的首字母),里面的成员(如x:Class、…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...