1 任务

  为了学习计算机底层和os,我给自己布置了一个任务:在x86硬件上,使用c和nasm来显示一张bmp图片。完成这个任务,前后估计花了2个月的业余时间。

  这个任务涉及了很多知识点,包括:启动区、保护模式、nasm汇编、c和nasm汇编互调、ld链接、硬盘io读取、显卡调色板模式、bmp图片格式、bios中断指令、c指针操作内存、borch虚拟机、binutils工具集、makefile等。

2 环境

ubuntu

borchs

nasm和 c

PS:c代码遵循google的C++ 风格指南,使用gnu99标准。

3步骤

3.1 生成一个10M的硬盘镜像

  bximage是borchs软件包的一个小工具,可以用于生成硬盘或软盘镜像。打开终端,输入:bximage。按照如下图所示的,一步一步地操作。

最终会在当前目录下,生成一个名为10M.img的文件。

3.2 准备一张320*200的bmp图片

  为简单起见,屏幕的分辨率使用320*200。因此我们的bmp图片的大小320*200。我准备了一张图片,如下,这是我家主子的靓照。

  将文件命名为cat-666.bmp,然后写入到#201扇区

dd if=src/cat-ham.bmp of=10M.img bs=512 seek=201 conv=notrunc

3.3 引导区

  引导区位于启动盘的#0扇区,为计算机启动后首次执行的代码。为简单起见,我们的引导区仅完成以下功能:

  • 设置vga模式设置显示模式为320*200。
  • 配置了配置了5个gdt表项,用作程序运行的内存空间。
  • 跳入32位保护模式。
  • 读取内核至内存0x100000
  • 跳至内核入口。

具体代码如下:boot.asm

  1 ;设置堆栈段和栈指针
mov eax, cs
mov ss, eax
mov sp, 0x7c00 set_vga:
mov ax, 0x0013 ;;0x0013为 320*200*8bit
int 0x10 ;int 0x10 set_gdt:
;GDT 开始于 0x7e00
mov ax, 0x7e00
mov bx, ax; ; null_descriptor,这是处理器的要求
mov dword [bx + 0x00], 0x00000000
mov dword [bx + 0x04], 0x00000000 ; code 启动区
mov dword [bx + 0x08], 0x7c0001ff ;base:0x7c00,limit: 1ff,512B
mov dword [bx + 0x0c], 0x00409A00 ;粒度为1B, ; code kernel
mov dword [bx + 0x10], 0x000000ff ; base: 0x10_0000, limit:0xff,1MB
mov dword [bx + 0x14], 0x00c09a10 ; 粒度为4KB, ; data
mov dword [bx + 0x18], 0x0000ffff ;base: 0, limit:0xf_ffff, 4GB
mov dword [bx + 0x1c], 0x00cf9200 ;粒度为4KB, ; stack
mov dword [bx + 0x20], 0x7a00fffe ; base: 0x7a00, limit:0xfffe
mov dword [bx + 0x24], 0x00cf9600 ; 粒度为4KB, ;初始化描述符表寄存器 GDTR
mov word [cs: gdt_desc + 0x7c00], ;描述符表的界限
lgdt [cs: gdt_desc + 0x7c00] in al, 0x92 ;南桥芯片内的端口
or al, 0000_0010B
out 0x92, al ;打开A20 cli ;中断机制尚未工作 mov eax, cr0
or eax,
mov cr0, eax ;设置PE位 ;以下进入保护模式 ...
jmp dword 0x0008: mode32_start ;16位的描述符选择子:32位偏移 [bits ]
mode32_start:
mov eax, 0x0018 ;加载数据段选择子
mov es, eax;
mov ds, eax; ; 读取内核,并且跳入。读取200个扇区至 0x10_0000
read_kernel:
mov dx, 0x1f2;
mov al, ; 200个扇区
out dx, al ; mov dx, 0x1f3 ;
mov al ,0x01 ; 1号扇区(第2个扇区), zero-based
out dx, al; mov dx, 0x1f4 ;
mov al, 0x00 ;
out dx, al ; mov dx, 0x1f5 ;
mov al, 0x00;
out dx, al; mov dx, 0x1f6 ;
mov al, 0xe0 ;
out dx, al ; ; ask for read
mov dx, 0x1f7 ;
mov al, 0x20 ;
out dx, al ; ; wait for finish
mov dx, 0x1f7 ;
_rk_wait:
in al,dx ;
and al, 0x88 ;
cmp al, 0x08 ;
jnz _rk_wait ; ;read data to bx
mov ebx, 0x10_0000 ;
mov cx, * ; n * 256;
mov dx, 0x1f0 ; _rk_read_loop:
in ax, dx;
mov word[ebx], ax; ; 每次读2个字节
add ebx, ;
loop _rk_read_loop ; ; jump to kernel, 段选择子
jmp dword 0x0010: hlt; ;-------------------------------------------------------------------------------
gdt_desc: dw
dd 0x00007e00 ; GDT的物理地址,刚好在启动区之后
;-------------------------------------------------------------------------------
times -($-$$) db
db 0x55, 0xaa

编译

nasm boot.asm -f bin -o boot.bin

写入到硬盘镜像(写入到#0扇区)

dd if=boot.bin of=10M.img bs= count= conv=notrunc

3.4 bmp文件的结构

  BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式。其结构如下图所示:

  参考:https://www.cnblogs.com/kingmoon/archive/2011/04/18/2020097.html

  参考bmp的结构定义,编写如下头文件:bmp.h

 #ifndef _OS_BMP_H_
#define _OS_BMP_H_ #include <stdint.h> typedef struct {
/**
* 文件类型,
*/
char type[]; /**
* 位图大小
*/
uint32_t size; /**
* 保留位
*/
uint16_t reserved1; /**
* 保留位
*/
uint16_t reserved2; /**
* 图像数据偏移量
*/
uint32_t off_bits; } __attribute__ ((packed)) BitMapFileHeader; /**
* 信息头
*/
typedef struct {
/**
* BitMapFileHeader 字节数
*/
uint32_t size; /**
* 位图宽度
*/
uint32_t width; /**
* 位图高度,正位正向,反之为倒图
*/
uint32_t height; /**
* 为目标设备说明位面数,其值将总是被设为1
*/
uint16_t planes; /**
* 说明比特数/象素,为1、4、8、16、24、或32。
*/
uint16_t bit_count; /**
* 图象数据压缩的类型没有压缩的类型:BI_RGB
*/
uint32_t compression; /**
* 图像数据区大小,以字节为单位
*/
uint32_t image_size; /**
* 水平分辨率
*/
uint32_t x_pixel_per_meter; /**
* 垂直分辨率
*/
uint32_t y_pixel_per_meter; /**
* 位图实际使用的彩色表中的颜色索引数
*/
uint32_t color_used; /**
* 对图象显示有重要影响的索引数,0都重要。
*/
uint32_t color_important;
} __attribute__ ((packed)) BitMapInfoHeader; /*
* 颜色结构体
*/
typedef struct {
/**
*
*/
uint8_t blue; /**
*
*/
uint8_t green; /**
*
*/
uint8_t red; /**
* 保留值
*/
uint8_t reserved; } __attribute__ ((packed)) RGB; #endif //_OS_BMP_H_

代码说明:

  • 定义了3个结构体BitMapFileHeader(文件头)、 BitMapInfoHeader(位图信息头)、RGB(颜色)
  • 需要特别注意的是,在类型定义中加入了__attribute__ ((packed))修饰。它的作用就是告诉编译器取消结构体在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。不加入这个的话,会导致程序在读取bmp数据时发生错位。

3.5 io操作

  在这个任务中需要直接操作硬件,比如读取硬盘扇区、端口读写、打开中断、读取eflags标志等,这部分功能的代码将使用nasm来编写,然后导出相应的方法让c来调用。

nasm代码如下:x86.asm

;数据区
[section .data] ; ;代码区
[section .text] ; global read_sector;
global io_hlt ;
;
global io_in8;
global io_in16;
global io_in32;
global io_out8;
global io_out16;
global io_out32;
;
global io_read_eflags;
global io_write_eflags;
;
global io_cli;
global io_sti; ;
;功能 : 读取一个扇区
;入口 : 无
;出口 : 无
;堆栈使用: 无
;全局变量:
;函数签名:void read_sector(int sector, int dst);
read_sector:
mov ecx, [esp + ] ;参数1:sector
mov ebx, [esp + ] ;参数2:dst mov dx, 0x1f2 ;
mov al, 0x01 ; sector
out dx, al ;
mov dx, 0x1f3 ;
mov al, cl ;-
out dx, al ; mov dx, 0x1f4 ;
mov al, ch ;-
out dx, al mov dx, 0x1f5 ;
mov al, 0x00 ;-
out dx, al ; mov dx, 0x1f6 ;
mov al, 0xe0 ;
out dx, al ; ; ask for read
mov dx, 0x1f7 ;
mov al, 0x20 ;
out dx, al ; ; wait for finish
mov dx, 0x1f7 ;
_rs_wait:
in al, dx ;
and al, 0x88 ;
cmp al, 0x08 ;
jnz _rs_wait ; ;read data to bx
mov cx, ;
mov dx, 0x1f0 ; _rs_read_loop:
in ax, dx ;
mov word[ebx], ax ;
add ebx, ;
loop _rs_read_loop ; ret ; ;功能 : 挂起
;入口 : 无
;出口 : 无
;堆栈使用: 无
;全局变量:
;函数签名:void io_hlt(void);
io_hlt:
hlt ;
ret; ;功能 : 读取 eflags
;函数签名: int read_eflags(void);
io_read_eflags:
pushfd ;将 eflags 压入栈
pop eax ;将 eflags 弹出并保存至eax
ret ;功能 : 往端口写入1个字节
;函数签名: void io_out8(int port, int value);
io_out8:
mov edx, [esp + ] ;参数1: port
mov al, [esp + ] ;参数2:value
out dx, al
ret ;功能 : 从端口读取1个字节
;函数签名:uint8_t io_in8(int port);
io_in8:
mov edx, [esp + ] ;参数1: port
mov eax, ;将数据置为0,防止干扰
in al, dx ;
ret ;功能 : 从端口读取2个字节
;函数签名:uint16_t io_in16(int port);
io_in16:
mov edx, [esp + ] ;参数1: port
mov eax, ;将数据置为0,防止干扰
in ax, dx ;
ret ;功能 : 从端口读取4个字节
;函数签名:uint32_t io_in32(int port);
io_in32:
mov edx, [esp + ] ;参数1: port
mov eax, ;将数据置为0,防止干扰
in eax, dx ;
ret ;功能 : 往端口写入2个字节
;函数签名: void io_out16(int port, int value);
io_out16:
mov edx, [esp + ] ;参数1: port
mov al, [esp + ] ;参数2:value
out dx, ax
ret ;功能 : 往端口写入4个字节
;函数签名: void io_out32(int port, int value);
io_out32:
mov edx, [esp + ] ;参数1: port
mov al, [esp + ] ;参数2:value
out dx, eax
ret ;功能 : 关闭中断
;函数签名: void io_cli(void);
io_cli:
cli ; clean interrupt flag
ret ;功能 : 打开中断
;函数签名: void io_sti(void);
io_sti:
sti ; set interrupt flag
ret ;功能 : 写入 eflags
;函数签名: void write_eflags(int flags);
io_write_eflags:
mov eax, [esp + ] ;参数1:eflags
push eax ;将参数 eflags压入栈中
popfd ;从栈中弹出eflags的值并将之写入到 EFLAGS 寄存器
ret

代码说明:

  • 导出函数使用global关键字。比如global read_sector,将导出read_sector函数。
  • 函数的参数使用栈来存储,次序为从右到左,使用esp栈指针来访问。
  • 所有的函数都放在section  .text 中。
  • 整型类型的返回值可以放在eax/ax/dx寄存中进行返回。

编译

nasm -f elf -o x86.o x86.asm

  为了便于c代码调用上面的代码,我们还需要创建一个头文件:x86.h:

#ifndef _OS_X86_H_
#define _OS_X86_H_ #include <stdint.h> /**
* 读取扇区的数据
* @param sector 扇区号。
* @param dst 目标地址
*/
void read_sector(int sector, uint8_t *dst); /**
* 挂起
*/
void io_hlt(); /**
* 读取 eflags
* @return
*/
uint32_t io_read_eflags(); /**
* 写入 eflags
* @param flags
*/
void io_write_eflags(uint32_t flags); /**
* 从端口读取1个字节
* @param port 端口号
* @return 端口上的数据
*/
uint8_t io_in8(uint16_t port); /**
* 从端口读取2个字节
* @param port 端口号
* @return 端口上的数据
*/
uint16_t io_in16(uint16_t port); /**
* 从端口读取4个字节
* @param port 端口号
* @return 端口上的数据
*/
uint32_t io_in32(uint16_t port); /**
* 往端口写入1个字节
* @param port 端口号
* @param value 要写入的值
* @return
*/
void io_out8(uint16_t port, uint8_t value); /**
* 往端口写入2个字节
* @param port 端口号
* @param value 要写入的值
* @return
*/
void io_out16(uint16_t port, uint16_t value); /**
* 往端口写入4个字节
* @param port 端口号
* @param value 要写入的值
* @return
*/
void io_out32(uint16_t port, uint32_t value); /**
* 关闭中断
*/
void io_cli(); /**
* 打开中断
*/
void io_sti(); #endif //_OS_X86_H_

代码说明:

  • 函数的签名要跟nasm文件中的保持一致,包括函数名,参数个数、参数类型。
  • 在调用的时候跟普通的头文件一样,先引入x86.h,然后调用相应的方法。

3.6 内核代码

  在内核代码中,执行以下操作:

  • 读取bmp文件所在的起始个扇区。从该扇区数据中取出文件大小,决定要还要继续读几个扇区,接着读完所有扇区。
  • 从bmp数据中取出调色板数据,然后用它来更改显卡的调色板。
  • 从bmp数据中取出图像数据,写入到图像缓冲区。

  代码如下:kernel.c

 #include <stdint.h>
#include "x86.h"
#include "bmp.h" // 视频缓冲区的内存位置
#define VIDEO_BUFFER_MEMORY_LOC 0x0a0000
// bmp文件的内存位置
#define BMP_FILE_MEMORY_LOC 0x200000
// bmp文件所在的起始扇区
#define BMP_FILE_SECTOR 201 int main(void) {
// 读扇区的索引,
uint32_t sector_read_index = BMP_FILE_SECTOR;
// 读文件的索引
uint8_t *file_read_index = (uint8_t *) BMP_FILE_MEMORY_LOC; // 读取bmp文件所在的第1个扇区
read_sector(sector_read_index, file_read_index);
file_read_index = file_read_index + ;
sector_read_index++; // 文件头
BitMapFileHeader *bmp_header = (BitMapFileHeader *) BMP_FILE_MEMORY_LOC;
uint32_t file_size = bmp_header->size; // 图像数据偏移
uint32_t off_bits = bmp_header->off_bits; // 需要再读取几个扇区?
int more_sectors = (file_size / ) - ;
if (file_size % != ) {
more_sectors++;
} // 读取更多扇区
for (int i = ; i < more_sectors; i++) {
read_sector(sector_read_index, file_read_index);
sector_read_index++;
file_read_index += ;
} //*********************调色板设置 *************
// 读取调色板数据
// 调色板数据开始于文件偏移 54
RGB *palette_index = (RGB *) (BMP_FILE_MEMORY_LOC + );
//
uint32_t eflags = io_read_eflags();
io_cli(); // 写入0号调色板
io_out8(0x03c8, ); // 写入调色板数据
for (int i = ; i < ; ++i) {
RGB rgb = *palette_index;
// 必须除以4,因为 vga 只能显示64色
io_out8(0x03c9, rgb.red / );
io_out8(0x03c9, rgb.green / );
io_out8(0x03c9, rgb.blue / );
palette_index++;
} io_write_eflags(eflags); // 位图信息头
BitMapInfoHeader *info_header = (BitMapInfoHeader *) (BMP_FILE_MEMORY_LOC + );
// 数据位数组
uint8_t *file_bits = (uint8_t *) (BMP_FILE_MEMORY_LOC + off_bits);
// 坐标点的内存地址
uint8_t *p = ;
//
for (int i = ; i < info_header->image_size; i++) {
// x 坐标
int x = i % info_header->width;
// y 坐标
int y = (info_header->height - ) - (i / info_header->width);
// 点(x,y)的内存地址
p = (uint8_t *) (VIDEO_BUFFER_MEMORY_LOC + x + y * info_header->width);
*p = *file_bits;
file_bits++;
} // use this to avoid to reset
while () {
io_hlt();
}
return ;
}

关键代码说明:

  • 在写入调色板之前,eflags要先暂存,然后再回写。
  • bmp的数据是从下往上,从左往右存储的,所以显示的时候要反过来。
  • 对视频缓冲区内存区域的读写用到了指针。定义一个指针uint8_t *p, p为坐标点的内存地址,然后使用*p = *file_bits来修改该内存的值。

编译

gcc -c-std=gnu99  -fno-stack-protector  -m32 -Wall -o kernel.o kernel.c

3.7 链接

链接脚本如下:kernel.ld

 OUTPUT_FORMAT("elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(main) SECTIONS
{
. = 0x040000;
.text : {
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
/DISCARD/ : {
*(.eh_frame .note.GNU-stack)
}
}

脚本说明:

  • OUTPUT_FORMAT("elf32-i386") 表示输出格式为efl32 32位格式。
  • ENTRY(main) 表示入口函数为main
  • /DISCARD/表示忽略.eh_frame段和.note.GNU-stack

链接

ld -s -T kernel.ld -o kernel.out kernel.o x86.o
注意,对象文件(*.o)的次序要正确,否则运行的时候会出错。次序的原则是被依赖的放在后面。

3.8 .text段提取

  链接后的文件kernel.out是一个elf类型的文件,它包含了elf头信息、.text、.data等。通过readelf命令可以查看efl文件的结构。

readelf -a kernel.out

命令结果如下:

我们仅需要 .text段 。这个时候通过objcopy来提取kernel.out中的.text段,如下:

objcopy -S -O binary -j .text kernel.out kernel.bin

将kernel.bin写入到硬盘镜像(从#1扇区开始)

dd if=target/kernel.bin of=10M.img bs= seek= count= conv=notrunc

3.9 放入borch虚拟机中运行

配置一个虚拟机,配置如下,bochsrc :

 ###############################################################
# Configuration file for Bochs
############################################################### # how much memory the emulated machine will have
megs: 32 # filename of ROM images
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest # what disk images will be used
#floppya: 1_44=a.img, status=inserted
ata0-master: type=disk, mode=flat, path="10M.img", cylinders=20, heads=16, spt=63 # choose the boot disk.
#boot: floppy
boot: disk # where do we send log messages?
# log: bochsout.txt
# disable the mouse
mouse: enabled=0 # enable key mapping, using US layout as default.
keyboard_mapping: enabled=1, map=/usr/local/share/bochs/keymaps/x11-pc-us.map

关键配置说明:

  • megs: 32 表示内存为32M
  • boot: disk 表示从硬盘启动
  • ata0-master: path="10M.img", 设置了硬盘镜像的路径
  • vgaromimage: file=VGABIOS-lgpl-latest 表示显卡的rom镜像为VGABIOS-lgpl-latest,如果设置错误,显示就会不正常。
  • keyboard_mapping: enabled=1, 用于设置键盘布局,这里采用美式键盘布局。

启动虚拟机

bochs -q

效果如下:

3.10  makefile

  用makefile将上面零散的命令整合一下。脚本如下,Makefile:

 .PHONY : all clean run install

 CFLAGS = -std=gnu99  -fno-stack-protector  -m32 -Wall

 all: target/boot.bin target/kernel.bin install

 target/boot.bin : src/boot.asm
nasm src/boot.asm -f bin -o target/boot.bin target/kernel.bin : target/kernel.out
objcopy -S -O binary -j .text target/kernel.out target/kernel.bin target/x86.o : src/x86.asm
nasm -f elf -o target/x86.o src/x86.asm target/kernel.o : src/kernel.c
gcc -c $(CFLAGS) -o target/kernel.o src/kernel.c # x86.o要放到最后,否则会无法运行
target/kernel.out : target/kernel.o target/x86.o
ld -s -T kernel.ld -o target/kernel.out target/kernel.o target/x86.o install :
# #0扇区
dd if=target/boot.bin of=10M.img bs=512 count=1 conv=notrunc
# #1 ~ #200 扇区
dd if=target/kernel.bin of=10M.img bs=512 seek=1 count=200 conv=notrunc
# #201扇区开始
dd if=src/cat-666.bmp of=10M.img bs=512 seek=201 conv=notrunc run :
make all
bochs -q clean :
-rm target/*.bin
-rm target/*.o
-rm target/*.out

脚本说明:

  • 将源文件放到src目录下,将目标文件放到target目录下。
  • make run 为运行。
  • make install 为安装。
  • make clean 为清理。

3.11 内存和硬盘布局

内存布局

物理地址

内容

0x7c00 ~ 0x7dff

启动区

0x7e00~ 0x7eff

gdt

0x100000~0x1fffff

内核,大小1M。

0x200000开始

图片。

0x0a0000-0xaf9ff

图像缓冲区

硬盘布局

扇区

内容

#0

boot.bin

#1 ~ #200

kernel.bin

#201

cat-6666.bmp

4 参考资料

  • 《x86汇编语言 从实模式到保护模式》
  • 《Linux0.11内核完全注释》
  • 《30天自制操作系统》
  • 《一步一步学习linux汇编语言程序设计》
  • 《xv6》

直接在x86硬件上显示图片(无os)的更多相关文章

  1. 图解android开发在界面上显示图片

    图解android开发在界面上显示图片<申明:转自百度> <原文章地址:http://jingyan.baidu.com/article/49711c6153a277fa441b7c ...

  2. 对话框上动态控件的创建、在Picture Control控件上显示图片

    1  MFC对话框之上的动态控件的创建 对话框上的控件是MFC类的一个具体对象. 当在对话框之上使用静态控件时,可以根据类向导来为每个控件添加消息.响应函数以及变量. 当需要在对话框中动态的创建某个控 ...

  3. [转]jquery 鼠标放在图片上显示图片的放大镜效果jqzoom_ev-2.3

    本文转自:http://blog.csdn.net/weizengxun/article/details/6768183 鼠标放在图片上显示图片的放大镜效果使用jqzoom实现,本例版本2.3 效果图 ...

  4. iOS View自定义窍门——UIButton实现上显示图片,下显示文字

    “UIButton实现上显示图片,下显示文字”这个需求相信大家在开发中都或多或少会遇见.比如自定义分享View的时候.当然,也可以封装一个item,上边imageView,下边一个label.但是既然 ...

  5. ABAP 在屏幕上显示图片

    1.se78 上传 或 预览图片 图片预览 2.程序代码 定义各变量 DATA: H_PICTURE TYPE REF TO CL_GUI_PICTURE, H_PIC_CONTAINER TYPE ...

  6. 在JLabel上显示图片,并且图片自适应JLabel的大小

    本文转载地址:       http://blog.csdn.net/xiaoliangmeiny/article/details/7060250 在写<Core Java>上的示例代码时 ...

  7. ImageView显示网络上的图片

    ImageView显示网络上的图片 一.简介 二.方法 1)ImageView显示网络上的图片方法 第一步:从网络上下载图片 byte[] byteArr = downImage();//这个是自己写 ...

  8. Qt 显示图片 放大 缩小 移动(都是QT直接提供的功能)

    本文章原创于www.yafeilinux.com 转载请注明出处. 现在我们来实现在窗口上显示图片,并学习怎样将图片进行平移,缩放,旋转和扭曲.这里我们是利用QPixmap类来实现图片显示的. 一.利 ...

  9. FrameBuffer系列 之 显示图片

     摘自:http://blog.csdn.net/luxiaoxun/article/details/7622988 #include <unistd.h> #include < ...

随机推荐

  1. visual studio 2005/2010/2013/2015/2017 vc++ c#代码编辑常用快捷键-代码编辑器的展开和折叠

    visual studio 2005/2010/2013/2015/2017 vc++ c#代码编辑快捷键-代码编辑器的展开和折叠 VS2015代码编辑器的展开和折叠代码确实很方便和实用.以下是展开代 ...

  2. SLAM:使用EVO测评ORBSLAM2

    SLAM:使用EVO测评ORBSLAM2 EVO是用来评估SLAM系统测量数据以及输出估计优劣的Python工具,详细说明请参照: https://github.com/MichaelGrupp/ev ...

  3. MDX

    简介 把md文件里的图片转成base64,方便发给别人和上传博客园等博客平台 初衷 用Typora写markdown的感觉很爽,但是每当我写好一篇文章,想要发给小伙伴们炫耀炫耀,或者上传博客园,CSD ...

  4. Beta冲刺<8/10>

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 Beta冲刺 这个作业的目标 Beta冲刺--第八天(05.26) 作业正文 如下 其他参考文献 ... B ...

  5. 【Flutter实战】定位装饰权重组件及柱状图案例

    老孟导读:Flutter中有这么一类组件,用于定位.装饰.控制子组件,比如 Container (定位.装饰).Expanded (扩展).SizedBox (固定尺寸).AspectRatio (宽 ...

  6. 一文搞懂InnoDB索引存储结构

    参考资料:掘金小册:MySQL 是怎样运行的:从根儿上理解 MySQL B+树 我们知道,InnoDB是用B+树作为组织数据形式的数据结构.不论是存放用户记录的数据页,还是存放目录项记录的数据页,我们 ...

  7. Linux上的Systemctl命令

    LinuxSystemctl是一个系统管理守护进程.工具和库的集合,用于取代System V.service和chkconfig命令,初始进程主要负责控制systemd系统和服务管理器.通过Syste ...

  8. MarkDown编辑器的区别对比

    标题: MarkDown编辑器的区别对比 作者: 梦幻之心星 sky-seeker@qq.com 标签: [MarkDown, 编辑器,区别] 目录: [软件] 日期: 2020-6-22 前提说明 ...

  9. LeetCode59. 螺旋矩阵 II

    这题和第54题类似,都是套一个搜索的模板. 用dx和dy表示方向,方向的顺序是先向右,再向下,再向左,再向上,再向右... 如果"撞墙"了就需要改变到下一个方向."撞墙& ...

  10. Linux--容器命令

    ***执行:yum install lrzsz 然后sz和rz命令就可以使用了 1.查找文件的命令:find / -name [文件名:override.xml] eg:  find / -name ...