检测物理内存容量

news2025/1/12 6:18:25

文章目录

  • 前言
  • 前置知识
    • BIOS 中断 0x15 子功能 0xe820 获取内存
  • 代码说明
  • 实验操作

前言

本博客记录《操作系统真象还原》第五章实验操作~

实验环境:ubuntu18.04+VMware , Bochs下载安装

实验内容:三种检测内存的方法。

实验原理

Linux中有很多种方式获取内存容量,如果一种方法失败,就会试用其他方法。本实验效仿 Linux 这种获取思想,在实模式下也用这三种方法检测完内存容量后再进入保护模式。如果一种方法获取失败,尝试另一种方法,若三种方法都失败了,由于无法获取内存信息,后续程序无法加载,只好将机器挂起,停止运行。

获取内存容量本质上是调用 BIOS 中断 0x15 的 3 个子功能。子功能号要存放到寄存器 EAX 或 AX 中,例如:

  • EAX=0xE820:遍历主机上全部内存。
  • AX=0xE801: 分别检测低 15MB 和 16MB~4GB 的内存,最大支持 4GB。
  • AH=0x88:最多检测出 64MB 内存,实际内存超过此容量也按照 64MB 返回。

BIOS 中断是实模式下的方法,因此在实模式下用这三种方法检测完内存容量后再进入保护模式。

BIOS获取内存的原理:BIOS 中断可以返回已安装的硬件信息。由于 BIOS 及其中断是一组软件,它要访问硬件要依靠硬件提供的接口。所以,获取内存信息,其内部是通过连续调用硬件的应用程序接口(Application Program Interface,API)来获取内存信息的。

前置知识

BIOS 中断 0x15 子功能 0xe820 获取内存

0xE820 子功能是最灵活的内存获取方式。该方法能够获取系统的内存布局。

由于系统内存各部分的类型属性不同,BIOS 就按照类型属性来划分这片系统内存,所以这种查询呈迭代式,每次 BIOS 只返回一种类型的内存信息,直到将所有内存类型返回完毕。

内存信息包括多个属性字段,所以用地址范围描述符(Address Range Descriptor Structure ,ARDS)进行描述。

在这里插入图片描述

BIOS 中断 是一段函数例程,需要为其提供参数进行调用。

如下图所示:

下图参考自获取内存_一步步编写操作系统 33 利用bios中断0x15子功能0xe820获取内存

在这里插入图片描述

根据上图中的说明,此中断的调用步骤是:

  1. 填写好“调用前输入”中列出的寄存器。
  2. 执行中断调用 int 0x15。
  3. 在CF位为0的情况下,“返回后输出”中对应的寄存器便会有对应的结果。

同样获取内存容量的方法还有以下2种:

利用BIOS 中断 0x15 子功能 0xe820 获取内存

利用 BIOS 中断 0x15 子功能 0x88 获取内存

具体细节操作查看《操作系统真象还原》

代码说明

loader.S

   %include "boot.inc"
   section loader vstart=LOADER_BASE_ADDR
   LOADER_STACK_TOP equ LOADER_BASE_ADDR
   
;构建gdt及其内部的描述符
   GDT_BASE:   dd    0x00000000 
	       dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
	       dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
		     dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	       ; limit=(0xbffff-0xb8000)/4k=0x7
	       dd    DESC_VIDEO_HIGH4  ; 此时dpl为0

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 60 dq 0					 ; 此处预留60个描述符的空位(slot)
   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 

   ; total_mem_bytes用于保存内存容量,以字节为单位,此位置比较好记。
   ; 当前偏移loader.bin文件头0x200字节,loader.bin的加载地址是0x900,
   ; 故total_mem_bytes内存中的地址是0xb00.将来在内核中咱们会引用此地址
   total_mem_bytes dd 0					 
   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

   ;以下是定义gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址
   gdt_ptr  dw  GDT_LIMIT 
	    dd  GDT_BASE

   ;人工对齐:total_mem_bytes4字节+gdt_ptr6字节+ards_buf244字节+ards_nr2,共256字节
   ards_buf times 244 db 0
   ards_nr dw 0		      ;用于记录ards结构体数量

   loader_start:
   
;-------  int 15h eax = 0000E820h ,edx = 534D4150h ('SMAP') 获取内存布局  -------

   xor ebx, ebx		      ;第一次调用时,ebx值要为0
   mov edx, 0x534d4150	      ;edx只赋值一次,循环体中不会改变
   mov di, ards_buf	      ;ards结构缓冲区
.e820_mem_get_loop:	      ;循环获取每个ARDS内存范围描述结构
   mov eax, 0x0000e820	      ;执行int 0x15后,eax值变为0x534d4150,所以每次执行int前都要更新为子功能号。
   mov ecx, 20		      ;ARDS地址范围描述符结构大小是20字节
   int 0x15
   jc .e820_failed_so_try_e801   ;若cf位为1则有错误发生,尝试0xe801子功能
   add di, cx		      ;使di增加20字节指向缓冲区中新的ARDS结构位置
   inc word [ards_nr]	      ;记录ARDS数量
   cmp ebx, 0		      ;若ebx为0且cf不为1,这说明ards全部返回,当前已是最后一个
   jnz .e820_mem_get_loop

;在所有ards结构中,找出(base_add_low + length_low)的最大值,即内存的容量。
   mov cx, [ards_nr]	      ;遍历每一个ARDS结构体,循环次数是ARDS的数量
   mov ebx, ards_buf 
   xor edx, edx		      ;edx为最大的内存容量,在此先清0
.find_max_mem_area:	      ;无须判断type是否为1,最大的内存块一定是可被使用
   mov eax, [ebx]	      ;base_add_low
   add eax, [ebx+8]	      ;length_low
   add ebx, 20		      ;指向缓冲区中下一个ARDS结构
   cmp edx, eax		      ;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
   jge .next_ards
   mov edx, eax		      ;edx为总内存大小
.next_ards:
   loop .find_max_mem_area
   jmp .mem_get_ok

;------  int 15h ax = E801h 获取内存大小,最大支持4G  ------
; 返回后, ax cx 值一样,以KB为单位,bx dx值一样,以64KB为单位
; 在ax和cx寄存器中为低16M,在bx和dx寄存器中为16MB到4G。
.e820_failed_so_try_e801:
   mov ax,0xe801
   int 0x15
   jc .e801_failed_so_try88   ;若当前e801方法失败,就尝试0x88方法

;1 先算出低15M的内存,ax和cx中是以KB为单位的内存数量,将其转换为以byte为单位
   mov cx,0x400	     ;cx和ax值一样,cx用做乘数
   mul cx 
   shl edx,16
   and eax,0x0000FFFF
   or edx,eax
   add edx, 0x100000 ;ax只是15MB,故要加1MB
   mov esi,edx	     ;先把低15MB的内存容量存入esi寄存器备份

;2 再将16MB以上的内存转换为byte为单位,寄存器bx和dx中是以64KB为单位的内存数量
   xor eax,eax
   mov ax,bx		
   mov ecx, 0x10000	;0x10000十进制为64KB
   mul ecx		;32位乘法,默认的被乘数是eax,积为64位,高32位存入edx,低32位存入eax.
   add esi,eax		;由于此方法只能测出4G以内的内存,故32位eax足够了,edx肯定为0,只加eax便可
   mov edx,esi		;edx为总内存大小
   jmp .mem_get_ok

;-----------------  int 15h ah = 0x88 获取内存大小,只能获取64M之内  ----------
.e801_failed_so_try88: 
   ;int 15后,ax存入的是以kb为单位的内存容量
   mov  ah, 0x88
   int  0x15
   jc .error_hlt
   and eax,0x0000FFFF
      
   ;16位乘法,被乘数是ax,积为32位.积的高16位在dx中,积的低16位在ax中
   mov cx, 0x400     ;0x400等于1024,将ax中的内存容量换为以byte为单位
   mul cx
   shl edx, 16	     ;把dx移到高16位
   or edx, eax	     ;把积的低16位组合到edx,为32位的积
   add edx,0x100000  ;0x88子功能只会返回1MB以上的内存,故实际内存大小要加上1MB

.mem_get_ok:
   mov [total_mem_bytes], edx	 ;将内存换为byte单位后存入total_mem_bytes处。


;-----------------   准备进入保护模式   -------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1

   ;-----------------  打开A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al

   ;-----------------  加载GDT  ----------------
   lgdt [gdt_ptr]

   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

   jmp dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
					     ; 这将导致之前做的预测失效,从而起到了刷新的作用。
.error_hlt:		      ;出错则挂起
   hlt

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,LOADER_STACK_TOP
   mov ax, SELECTOR_VIDEO
   mov gs, ax

   mov byte [gs:160], 'P'

   jmp $

代码说明

  1. 第 28 行定义了 4 字节的变量 total_mem_bytes,此变量用于存储获取到的内存容量。total_mem_bytes 定义的上方解释了total_mem_bytes 的地址是 0xb00 的原因。理由:它前
    面有 4 个段描述符的定义,还有预留 60 个段描述槽位 times 60 dq 0。因此偏移量是(4+60)*8=512=0x200 字节,本程序的加载地址是 0x900,0x900+0x200=0xb00。因此 0xb00 是变量 total_mem_bytes 加载到内存中的地址
  2. 35~37 行是提前定义的缓冲区,为的是存储 BIOS 0x15 中断 0xe820 子功能返回的 ARDS (Address Range Descriptor Structure)结构。
  3. 第 41~69 行用的子功能 0xe820 方法。
  4. 第 56~69 行是找出最大的内存块。
  5. 第 71~95 行是利用子功能 0xe801 探测内存容量。
  6. 第 88~95 行是计算 16MB 之上的内存容量,结果存放在寄存器 BX 和 DX,单位是 64KB,需要转换成字节。
  7. 第 98~110 行是用子功能 0x88 方法探测内存容量。

bochsrc.disk

这里为了和书本中输出保持一致,因此将bochsrc.disk中megs设置为32

在这里插入图片描述

mbr.S

【注意】:mbr和原先的哪个文件存在出入

;主引导程序 
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00         
   mov ax,cs      
   mov ds,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06	   功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
   mov     ax, 0600h
   mov     bx, 0700h
   mov     cx, 0                   ; 左上角: (0, 0)
   mov     dx, 184fh		   ; 右下角: (80,25),
				   ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
				   ; 下标从0开始,所以0x18=24,0x4f=79
   int     10h                     ; int 10h

   ; 输出字符串:MBR
   mov byte [gs:0x00],'1'
   mov byte [gs:0x01],0xA4

   mov byte [gs:0x02],' '
   mov byte [gs:0x03],0xA4

   mov byte [gs:0x04],'M'
   mov byte [gs:0x05],0xA4	   ;A表示绿色背景闪烁,4表示前景色为红色

   mov byte [gs:0x06],'B'
   mov byte [gs:0x07],0xA4

   mov byte [gs:0x08],'R'
   mov byte [gs:0x09],0xA4
	 
   mov eax,LOADER_START_SECTOR	 ; 起始扇区lba地址
   mov bx,LOADER_BASE_ADDR       ; 写入的地址
   mov cx,4			 ; 待读入的扇区数
   call rd_disk_m_16		 ; 以下读取程序的起始部分(一个扇区)
  
   jmp LOADER_BASE_ADDR + 0x300
       
;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:	   
;-------------------------------------------------------------------------------
				       ; eax=LBA扇区号
				       ; ebx=将数据写入的内存地址
				       ; ecx=读入的扇区数
      mov esi,eax	  ;备份eax
      mov di,cx		  ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;读取的扇区数

      mov eax,esi	   ;恢复ax

;第2步:将LBA地址存入0x1f3 ~ 0x1f6

      ;LBA地址7~0位写入端口0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位写入端口0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位写入端口0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f	   ;lba第24~27位
      or al,0xe0	   ; 设置7~4位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7端口写入读命令,0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;第4步:检测硬盘状态
  .not_ready:
      ;同一端口,写时表示写入命令字,读时表示读入硬盘状态
      nop
      in al,dx
      and al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready	   ;若未准备好,继续等。

;第5步:从0x1f0端口读数据
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2		  
      loop .go_on_read
      ret

   times 510-($-$$) db 0
   db 0x55,0xaa

实验操作

创建mbr.S和loader.S文件并编译

(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo vim loader.S
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo vim  mbr.S
(base) user@ubuntu:/home/cooiboi/bochs/boot$ cd ..
(base) user@ubuntu:/home/cooiboi/bochs$ sudo nasm -I include/ -o boot/loader.bin boot/loader.S
(base) user@ubuntu:/home/cooiboi/bochs$ sudo nasm -I include/ -o boot/mbr.bin boot/mbr.S

将mbr和load分别写入磁盘中

sudo dd if=/home/cooiboi/bochs/boot/mbr.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=1 conv=notrunc
sudo dd if=/home/cooiboi/bochs/boot/loader.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=2 seek=2 conv=notrunc
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo dd if=/home/cooiboi/bochs/boot/mbr.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=1 conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.000133292 s, 3.8 MB/s
(base) user@ubuntu:/home/cooiboi/bochs/boot$ sudo dd if=/home/cooiboi/bochs/boot/loader.bin of=/home/cooiboi/bochs/boot/hd60M.img bs=512 count=2 seek=2 conv=notrunc
1+1 records in
1+1 records out
1000 bytes (1.0 kB) copied, 0.00834544 s, 120 kB/s

启动Bochs

sudo bin/bochs -f boot/bochsrc.disk
(base) user@ubuntu:/home/cooiboi/bochs$ sudo bin/bochs -f boot/bochsrc.disk

输出6c
在这里插入图片描述
在这里插入图片描述

输入Ctrl+C组合键中断bochs运行。

接着再输入命令xp 0xb00+回车,显示结果。

🆗 32M为0x02000000。

在这里插入图片描述

参考资料

  • 《操作系统真象还原》
  • 《操作系统真象还原》第五章 ---- 轻取物理内存容量 启用分页畅游虚拟空间 力斧直斩内核先劈一角 闲庭信步摸谈特权级
  • 获取内存_一步步编写操作系统 33 利用bios中断0x15子功能0xe820获取内存

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

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

相关文章

volaile关键字详解!

文章目录什么是volatile &#xff1f;volatile三大特性volatile如何使用volatile保证可见性volatile不保证原子性volatile禁止指令重排volatile总结什么是volatile &#xff1f; volatile是一个Java关键字volatile是Java虚拟机提供的轻量级的同步机制 volatile三大特性 保证…

【服务器搭建个人网站】教程五:手把手教你怎样进行公安备案 快来学~

前言 购买一台服务器&#xff0c;再来个域名&#xff0c;搭建一个自己的个人博客网站&#xff0c;把一些教程、源码、想要分享的好玩的放到网站上&#xff0c;供小伙伴学习玩耍使用。我把这个过程记录下来&#xff0c;想要尝试的小伙伴&#xff0c;可以按照步骤&#xff0c;自己…

【Qt】将QtDesigner生成的.ui文件转化为.h头文件

【Qt】将QtDesigner生成的.ui文件转化为.h头文件1、背景2、实例3、附件1、背景 操作系统&#xff1a;windows10专业版。 Qt版本&#xff1a;qt-opensource-windows-x86-msvc2013_64-5.7.1.exe 博主的Qt安装目录&#xff1a;E:\E01_cppIDE\E01_qt\install 并将安装后的bin目录…

校园wifi网页认证登录入口

一、校园WIFI自助服务简介在我校校园网认证业务中&#xff0c;教职工校园通行证账号支持最大3个终端同时在线&#xff0c;如果超出最大在线数&#xff0c;最后上线的终端会把第一个上线的终端踢下线&#xff0c;导致终端经常掉线&#xff0c;需要重新登录才可上网。那么&#x…

【Linux】基础:基础IO

【Linux】基础&#xff1a;基础IO 摘要&#xff1a;本文基础IO的内容将从过往熟悉的C语言文件操作出发&#xff0c;引申指系统调用的文件操作&#xff0c;再进一步深化为对于进程管理的文件进行介绍&#xff0c;从而了解文件描述符的概念和管理方式&#xff0c;其中还会介绍其运…

2022 IoTDB Summit:京东刘刚《Apache IoTDB 在京东万物互联场景中的应用》

12 月 3 日、4日&#xff0c;2022 Apache IoTDB 物联网生态大会在线上圆满落幕。大会上发布 Apache IoTDB 的分布式 1.0 版本&#xff0c;并分享 Apache IoTDB 实现的数据管理技术与物联网场景实践案例&#xff0c;深入探讨了 Apache IoTDB 与物联网企业如何共建活跃生态&#…

基于JavaSpringMvc+mybatis实现学生信息管理系统

基于JavaSpringMvcmybatis实现学生信息管理系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系…

IOS Theos Tweak 之 HelloWorld

一、目标 Theos是什么&#xff1f; 是一套跨平台的开发工具套件,不仅可以开发Ios&#xff0c;Mac、Windows和Linux也可以的哦&#xff0c;开源免费。 Tweak是什么&#xff1f; 可以理解成动态链接库&#xff0c;有搞过Windows下dll注入的同学应该可以秒懂了。Android的同学…

JavaWeb——redis_1/6

链接: day23_redis_代码&资料 提取码&#xff1a;ijyg 今日内容redis——数据库软件1. 概念2. 下载安装3. 命令操作1. redis的数据结构2. 字符串类型 string3. 哈希类型 hash4. 列表类型 list:可以添加一个元素到列表的头部&#xff08;左边&#xff09;或者尾部&#xff0…

硬件工程师成长之路(0)----认识元件

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录前言1、电阻①、贴片电阻②、金属膜电阻③、水泥电阻④、制动电阻⑤、电位器⑥、压敏电阻⑦、热敏电阻⑧、光敏电阻⑨、…

奇舞周刊478期:算法工程师深度解构 ChatGPT 技术

记得点击文章末尾的“ 阅读原文 ”查看哟~下面先一起看下本期周刊 摘要 吧~奇舞推荐■ ■ ■算法工程师深度解构 ChatGPT 技术本文用专业视野带你由浅入深了解 ChatGPT 技术全貌。它经历了什么训练过程&#xff1f;成功关键技术是什么&#xff1f;将如何带动行业的变革&#xf…

Spark学习(8)-SparkSQL的运行流程,Spark On Hive

1. SparkSQL的运行流程 1.1 SparkRDD的执行流程回顾 1.2 SparkSQL的自动优化 RDD的运行会完全按照开发者的代码执行&#xff0c; 如果开发者水平有限&#xff0c;RDD的执行效率也会受到影响。而SparkSQL会对写完的代码&#xff0c;执行“自动优化”&#xff0c; 以提升代码运…

In-Context Learning玩法大全

卷友们好&#xff0c;我是rumor。虽然ChatGPT在大众眼里的热度可能已经过去了&#xff0c;但它prompt出了我的焦虑&#xff0c;于是最近一直在补大模型相关的工作。比起小模型&#xff0c;大模型有一个很重要的涌现能力&#xff08;Emergent ability&#xff09;就是In-Context…

CORS 跨域资源共享 与 JSONP

CORS 1、接口的跨域问题 我们使用express编写的 GET 和 POST接口&#xff0c;存在一个很严重的问题&#xff1a;不支持跨域请求。 解决接口跨域问题的方案主要有两种&#xff1a; CORS&#xff08;主流的解决方案&#xff0c;推荐使用&#xff09;JSONP&#xff08;有缺陷的…

excel查重技巧:如何用组合函数快速统计重复数据(下)

数据源照旧&#xff0c;如下图所示&#xff0c;要求统计出不重复的客户数&#xff1a;在上期我们掌握了破解公式的方法后&#xff0c;今天我们再来看看计算不重复数据个数的第二个公式套路。套路2&#xff1a;COUNT和MATCH的组合这个公式的难度就稍微有点大了&#xff0c;一起看…

Word控件Spire.Doc 【Table】教程(3):如何在C#、VB.NET中设置Word表格样式

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

聊聊帮助别人这件事--爱摸鱼的美工(12)

年近了&#xff0c;上班途中依然匆忙 看女孩子们渐渐开始倒腾 做了新发型&#xff0c;做了美美的指甲 换上了新衣服&#xff0c;买了新包 电话里讨论着 去哪里过年&#xff0c;买什么年货 好像以前的我也这样 今年挣得少了&#xff0c;不想添新衣&#xff08;不能&#xff09; …

基于QWidget和QImage的水印添加实现

目录 1. 基于QWidget的屏幕阅读水印的添加 1.1 平铺 1.1.1 核心代码 1.1.2 构造函数中设置透明度和鼠标穿透 1.1.3 调用 1.1.4 效果展示 1.2 指定位置添加水印 1.2.1 核心代码 1.2.2 设置水印透明度和鼠标穿透 1.2.3 调用 1.2.4 效果展示 2、基于QImage的屏幕水印的…

QT 学习笔记(十四)

文章目录一、TCP/IP 通信过程简介1. Socket 通信2. Linux 下的 TCP/IP 通信过程3. QT 下的 TCP/IP 通信过程3.1 在 QT 中实现 TCP/IP 服务器端通信的流程3.2 客户端通信流程二、TCP/IP 通信过程操作实现1. 服务器端2. 客户端三、服务器端和客户端实现代码1. 主函数 main.c2. 服…

重装系统重新分区方法教程

对电脑进行系统重装以后&#xff0c;若是想电脑使用起来更稳定舒适&#xff0c;就要对电脑硬盘进行系统分区&#xff0c;但是许多用户都不知道怎样分区&#xff0c;今天小编就来为大家详细介绍一下重装系统重新分区方法教程。 工具/原料&#xff1a; 系统版本&#xff1a;win1…