OS实战笔记(9)-- 构建二级引导器

这篇具有很好参考价值的文章主要介绍了OS实战笔记(9)-- 构建二级引导器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Grub内核映像格式

        Grub工作的时候,需要一个内核映像文件,其中包括了二级引导器模块、内核模块、图片和字库等。这些不同的文件都被放到了一个映像文件中,为了Grub能够正常加载,需要一个预先定义好的格式,以便Grub能解析。

OS实战笔记(9)-- 构建二级引导器

         上图中,Grub头为4KB大小,Grub会用这一小段代码来识别映像文件。根据映像文件头描述符和文件描述符的信息,这一小段代码还可以解析映像文件中的其它文件。

        映像文件头描述符和文件头描述符的结构如下:


//映像文件头描述符
typedef struct s_mlosrddsc
{
    u64_t mdc_mgic; //映像文件标识
    u64_t mdc_sfsum;//未使用
    u64_t mdc_sfsoff;//未使用
    u64_t mdc_sfeoff;//未使用
    u64_t mdc_sfrlsz;//未使用
    u64_t mdc_ldrbk_s;//映像文件中二级引导器的开始偏移
    u64_t mdc_ldrbk_e;//映像文件中二级引导器的结束偏移
    u64_t mdc_ldrbk_rsz;//映像文件中二级引导器的实际大小
    u64_t mdc_ldrbk_sum;//映像文件中二级引导器的校验和
    u64_t mdc_fhdbk_s;//映像文件中文件头描述的开始偏移
    u64_t mdc_fhdbk_e;//映像文件中文件头描述的结束偏移
    u64_t mdc_fhdbk_rsz;//映像文件中文件头描述的实际大小
    u64_t mdc_fhdbk_sum;//映像文件中文件头描述的校验和
    u64_t mdc_filbk_s;//映像文件中文件数据的开始偏移
    u64_t mdc_filbk_e;//映像文件中文件数据的结束偏移
    u64_t mdc_filbk_rsz;//映像文件中文件数据的实际大小
    u64_t mdc_filbk_sum;//映像文件中文件数据的校验和
    u64_t mdc_ldrcodenr;//映像文件中二级引导器的文件头描述符的索引号
    u64_t mdc_fhdnr;//映像文件中文件头描述符有多少个
    u64_t mdc_filnr;//映像文件中文件头有多少个
    u64_t mdc_endgic;//映像文件结束标识
    u64_t mdc_rv;//映像文件版本
}mlosrddsc_t;

#define FHDSC_NMAX 192 //文件名长度
//文件头描述符
typedef struct s_fhdsc
{
    u64_t fhd_type;//文件类型
    u64_t fhd_subtype;//文件子类型
    u64_t fhd_stuts;//文件状态
    u64_t fhd_id;//文件id
    u64_t fhd_intsfsoff;//文件在映像文件位置开始偏移
    u64_t fhd_intsfend;//文件在映像文件的结束偏移
    u64_t fhd_frealsz;//文件实际大小
    u64_t fhd_fsum;//文件校验和
    char   fhd_name[FHDSC_NMAX];//文件名
}fhdsc_t;

        知道了映像文件格式,我们还需要有工具能够打包出映像文件才行。这个工具可以到下面这个链接中去拉取:

极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/tools/lmoskrlimg        将代码拉下来后在Linux下编译出这个工具即可。工具的使用方法如下:

lmoskrlimg -m k -lhf GRUB头文件 -o 映像文件 -f 输入的文件列表
-m 表示模式 只能是k内核模式
-lhf 表示后面跟上GRUB头文件
-o 表示输出的映像文件名 
-f 表示输入文件列表
例如:lmoskrlimg -m k -lhf grubhead.bin -o kernel.img -f file1.bin file2.bin file3.bin file4.bin 

二级引导器的工作有哪些

        我们要做的二级引导器,主要作为操作系统的先驱,它需要收集机器信息,确定这个计算机能不能运行我们的操作系统,对 CPU、内存、显卡进行一些初级的配置,放置好内核相关的文件。对于要搜集的信息,在后面的笔记中会记录。

设计机器信息数据结构

        二级引导器所搜集的信息,需要按照一定的数据结构来存储。我们规定在内存1MB的地方来存放这个数据结构。

        下面是这个数据结构的一些关键信息(完整的结构,请查看Cosmos/initldr/include/ldrtype.h,代码仓库极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/lesson10~11/Cosmos)。


typedef struct s_MACHBSTART
{
    u64_t   mb_krlinitstack;//内核栈地址
    u64_t   mb_krlitstacksz;//内核栈大小
    u64_t   mb_imgpadr;//操作系统映像
    u64_t   mb_imgsz;//操作系统映像大小
    u64_t   mb_bfontpadr;//操作系统字体地址
    u64_t   mb_bfontsz;//操作系统字体大小
    u64_t   mb_fvrmphyadr;//机器显存地址
    u64_t   mb_fvrmsz;//机器显存大小
    u64_t   mb_cpumode;//机器CPU工作模式
    u64_t   mb_memsz;//机器内存大小
    u64_t   mb_e820padr;//机器e820数组地址
    u64_t   mb_e820nr;//机器e820数组元素个数
    u64_t   mb_e820sz;//机器e820数组大小
    //……
    u64_t   mb_pml4padr;//机器页表数据地址
    u64_t   mb_subpageslen;//机器页表个数
    u64_t   mb_kpmapphymemsz;//操作系统映射空间大小
    //……
    graph_t mb_ghparm;//图形信息
}__attribute__((packed)) machbstart_t;

二级引导器规划

        我们的二级引导器模块划分如下表所示:

文件 功能描述
imginithead.asm  GRUB头汇编
inithead.c GRUB头的C语言实现部分,用于将二级引导器放到指定内存中
realintsve.asm 实现调用BIOS中断功能
ldrkrl32.asm 二级引导器核心入口汇编
ldrkrlentry.c 二级引导器核心入口
bstartpram.c 收集机器信息,创建页面数据
chkcpmm.c 检查CPU工作模式和内存视图
fs.c 解析映像文件
graph.c 切换显卡图形模式
vgastr.c 字符串输出显示

        这些文件,完整的代码,在下面这个链接中可以找到(lesson10~11/Cosmos/initldr/ldrkrl):

极客时间-操作系统实战45讲: 极客时间-操作系统实战45讲课程已经上线,欢迎订阅 - Gitee.comhttps://gitee.com/lmos/cosmos/tree/master/lesson10~11      这个工程编译后,会生成三个bin文件:  initldrmh.bin,initldrsve.bin和initldrkrl.bin。我们使用前面所说的lmoskrlimg打包工具制作出映像文件即可。打包命令如下:


lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin

实现GRUB头

        GRUB头由两个文件组成:

        1. imginithead.asm汇编,它被GRUB所识别,也设置C语言运行环境(用于后续调用C函数)。

        2. inithead.c,它主要查找二级引导器的核心文件 -- initldrkrl.bin,然后将其放到内存指定地址上去。

imginithead.asm

        我们先来实现imginithead.asm,它主要是初始化CPU寄存器,加载GDT,将CPU切换到保护模式。

        我们先做出GRUB1和GRUB2的两个头结构:


MBT_HDR_FLAGS  EQU 0x00010003
MBT_HDR_MAGIC  EQU 0x1BADB002
MBT2_MAGIC  EQU 0xe85250d6
global _start
extern inithead_entry
[section .text]
[bits 32]
_start:
  jmp _entry
align 4
mbt_hdr:
  dd MBT_HDR_MAGIC
  dd MBT_HDR_FLAGS
  dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
  dd mbt_hdr
  dd _start
  dd 0
  dd 0
  dd _entry
ALIGN 8
mbhdr:
  DD  0xE85250D6
  DD  0
  DD  mhdrend - mbhdr
  DD  -(0xE85250D6 + 0 + (mhdrend - mbhdr))
  DW  2, 0
  DD  24
  DD  mbhdr
  DD  _start
  DD  0
  DD  0
  DW  3, 0
  DD  12
  DD  _entry 
  DD  0  
  DW  0, 0
  DD  8
mhdrend:

        如果想要深入理解multiboot头格式,可以去网上搜索一下Grub multiboot/multiboot2规范,这里给一个multiboot2规范的下载地址仅供参考:

https://download.csdn.net/download/vivo01/85626166https://download.csdn.net/download/vivo01/85626166        接下来,关闭中断,加载GDT:

_entry:
  cli           ;关中断
  in al, 0x70 
  or al, 0x80  
  out 0x70,al  ;关掉不可屏蔽中断   
  lgdt [GDT_PTR] ;加载GDT地址到GDTR寄存器
  jmp dword 0x8 :_32bits_mode ;
  ;………………
;GDT全局段描述符表
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE  dd GDT_START

        上面的in,out指令是读和写0x70端口的指令,关于x86端口的描述,之前的笔记中有给过参考链接,也可以自行去google。

        lgdt指令是修改全局描述符表GDT寄存器的指令,此处表示加载GDT_PTR地址处开始的6个字节,在GDT_PTR处,前两个字节表示GDT的大小,后四个字节表示GDT的起始地址。

        如果需要了解GDT,请回看之前的笔记:

OS实战笔记(3) -- X86 CPU三种工作模式(实模式,保护模式,长模式)_x86保护模式_亦枫Leonlew的博客-CSDN博客X86 CPU三种工作模式(实模式,保护模式,长模式)https://blog.csdn.net/vivo01/article/details/125752362        jmp dword 0x8 : _32bits_mode,这里的0x8表示选择了GDT里(TI(bit2) = 0)索引为1的段描述符所确定的基地址(参考下图),加上_32bits_mode所指示的偏移得到最终地址。

OS实战笔记(9)-- 构建二级引导器

        下标为1的段 描述符值为0x00cf9e000000ffff,可以参考下图进行理解。

OS实战笔记(9)-- 构建二级引导器

        可以看到这个段的基地址是0x0,jmp指令实际可以理解为跳转到_32bits_mode处。


_32bits_mode:
  mov ax, 0x10
  mov ds, ax
  mov ss, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  xor eax,eax
  xor ebx,ebx
  xor ecx,ecx
  xor edx,edx
  xor edi,edi
  xor esi,esi
  xor ebp,ebp
  xor esp,esp
  mov esp,0x7c00 ;设置栈顶为0x7c00
  call inithead_entry ;调用inithead_entry函数在inithead.c中实现
  jmp 0x200000  ;跳转到0x200000地址

         _32bits_mode处代码最核心的功能是设置esp,有了栈后就可以调用C语言的函数了。后面call inithead_entry函数主要功能就是加载对应的映像文件中的 initldrsve.bin 文件和 initldrkrl.bin 文件写入到特定的内存地址空间中去。


#define MDC_ENDGIC 0xaaffaaffaaffaaff
#define MDC_RVGIC 0xffaaffaaffaaffaa
#define REALDRV_PHYADR 0x1000
#define IMGFILE_PHYADR 0x4000000
#define IMGKRNL_PHYADR 0x2000000
#define LDRFILEADR IMGFILE_PHYADR
#define MLOSDSC_OFF (0x1000)
#define MRDDSC_ADR (mlosrddsc_t*)(LDRFILEADR+0x1000)

void inithead_entry()
{
    write_realintsvefile();
    write_ldrkrlfile();
    return;
}
//写initldrsve.bin文件到特定的内存中
void write_realintsvefile()
{
    fhdsc_t *fhdscstart = find_file("initldrsve.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrsve.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)REALDRV_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//写initldrkrl.bin文件到特定的内存中
void write_ldrkrlfile()
{
    fhdsc_t *fhdscstart = find_file("initldrkrl.bin");
    if (fhdscstart == NULL)
    {
        error("not file initldrkrl.bin");
    }
    m2mcopy((void *)((u32_t)(fhdscstart->fhd_intsfsoff) + LDRFILEADR),
            (void *)ILDRKRL_PHYADR, (sint_t)fhdscstart->fhd_frealsz);
    return;
}
//在映像文件中查找对应的文件
fhdsc_t *find_file(char_t *fname)
{
    mlosrddsc_t *mrddadrs = MRDDSC_ADR;
    if (mrddadrs->mdc_endgic != MDC_ENDGIC ||
        mrddadrs->mdc_rv != MDC_RVGIC ||
        mrddadrs->mdc_fhdnr < 2 ||
        mrddadrs->mdc_filnr < 2)
    {
        error("no mrddsc");
    }
    s64_t rethn = -1;
    fhdsc_t *fhdscstart = (fhdsc_t *)((u32_t)(mrddadrs->mdc_fhdbk_s) + LDRFILEADR);
    for (u64_t i = 0; i < mrddadrs->mdc_fhdnr; i++)
    {
        if (strcmpl(fname, fhdscstart[i].fhd_name) == 0)
        {
            rethn = (s64_t)i;
            goto ok_l;
        }
    }
    rethn = -1;
ok_l:
    if (rethn < 0)
    {
        error("not find file");
    }
    return &fhdscstart[rethn];
}

         在调用完inithead_entry后,最后的jmp 0x200000是跳转到二级引导器的主模块(initldrkrl.bin)。

进入二级引导器主模块

        前面加载了initldrkrl.bin到内存0x200000并跳转到了二级引导器的主模块,由于模块发生了变化,我们首先会重新去设置一下GDT,IDT,栈指针,相关寄存器。这些工作在ldrkrl32.asm文件中去实现。


_entry:
  cli
  lgdt [GDT_PTR];加载GDT地址到GDTR寄存器
  lidt [IDT_PTR];加载IDT地址到IDTR寄存器
  jmp dword 0x8 :_32bits_mode;长跳转刷新CS影子寄存器
_32bits_mode:
  mov ax, 0x10  ; 数据段选择子(目的)
  mov ds, ax
  mov ss, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  xor eax,eax
  xor ebx,ebx
  xor ecx,ecx
  xor edx,edx
  xor edi,edi
  xor esi,esi
  xor ebp,ebp
  xor esp,esp
  mov esp,0x90000 ;使得栈底指向了0x90000
  call ldrkrl_entry ;调用ldrkrl_entry函数
  xor ebx,ebx
  jmp 0x2000000 ;跳转到0x2000000的内存地址
  jmp $
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9a000000ffff ;a-e
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009a000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1  ;GDT界限
GDTBASE  dd GDT_START

IDT_PTR:
IDTLEN  dw 0x3ff
IDTBAS  dd 0  ;这是BIOS中断表的地址和长度

        大部分内容基本和前面一致,这里需要注意最后jmp的地址是0x2000000( IMGKRNL_PHYADR, 内核映像地址),不是initldrkrl.bin加载到内存中的地址。

        ldrkrl_entry函数是二级引导器的C语言主函数,暂时还未实现。

调用BIOS中断

        前面说过,二级引导器主要功能中最重要的一点是收集机器信息,比如内存布局,显卡图形模式等。这些信息要通过BIOS提供的中断服务才能实现。

        BIOS中断服务工作在16位实模式,而目前我们所用的二级引导器工作在32位保护模式下。因此在二级引导器中无法直接调用BIOS中断服务,我们需要做一些额外的事情:

        1. 保存 C 语言环境下的 CPU 上下文 ,即保护模式下的所有通用寄存器、段寄存器、程序指针寄存器,栈寄存器,把它们都保存在内存中。

        2. 切换回实模式,调用 BIOS 中断,把 BIOS 中断返回的相关结果,保存在内存中。

        3. 切换回保护模式,重新加载第 1 步中保存的寄存器。这样 C 语言代码才能重新恢复执行。

        上面的这些过程,我们写在ldrkrl32.asm 文件中:


realadr_call_entry:
  pushad     ;保存通用寄存器
  push    ds
  push    es
  push    fs ;保存4个段寄存器
  push    gs
  call save_eip_jmp ;调用save_eip_jmp 
  pop  gs
  pop  fs
  pop  es      ;恢复4个段寄存器
  pop  ds
  popad       ;恢复通用寄存器
  ret
save_eip_jmp:
  pop esi  ;弹出call save_eip_jmp时保存的eip到esi寄存器中, 
  mov [PM32_EIP_OFF],esi ;把eip保存到特定的内存空间中
  mov [PM32_ESP_OFF],esp ;把esp保存到特定的内存空间中
  jmp dword far [cpmty_mode];长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs
cpmty_mode:
  dd 0x1000
  dw 0x18
  jmp $

        唯一需要注意的是最后的jmp dword far [cpmty_mode],这个指令是一个长跳转,表示把[cpmty_mode]处的数据装入 CS:EIP,也就是把 0x18:0x1000 装入到 CS:EIP 中。根据之前的描述,我们知道所使用的段的index是0x3(0x18的bits[15:3],根据段选择子的定义, 低3位是RPL和TI, 其他位才是索引)。 之前在汇编中定义的二级引导器的GDT表中,下标为3的段描述符对应的是16位代码段。0x1000 代表段内的偏移地址(由于段描述符指定的基地址是0x0,因此实际物理地址就是0x1000)。我们跳转到这里,必须要有代码CPU才能继续干活。这个地址开始的代码是16位的指令,这段汇编我们放到一个单独的realintsve.asm文件中:


[bits 16]
_start:
_16_mode:
  mov  bp,0x20 ;0x20是指向GDT中的16位数据段描述符 
  mov  ds, bp
  mov  es, bp
  mov  ss, bp
  mov  ebp, cr0
  and  ebp, 0xfffffffe
  mov  cr0, ebp ;CR0.P=0 关闭保护模式
  jmp  0:real_entry ;刷新CS影子寄存器,真正进入实模式
real_entry:
  mov bp, cs
  mov ds, bp
  mov es, bp
  mov ss, bp ;重新设置实模式下的段寄存器 都是CS中值,即为0 
  mov sp, 08000h ;设置栈
  mov bp,func_table
  add bp,ax
  call [bp] ;调用函数表中的汇编函数,ax是C函数中传递进来的
  cli
  call disable_nmi
  mov  ebp, cr0
  or  ebp, 1
  mov  cr0, ebp ;CR0.P=1 开启保护模式
  jmp dword 0x8 :_32bits_mode
[BITS 32]
_32bits_mode:
  mov bp, 0x10
  mov ds, bp
  mov ss, bp;重新设置保护模式下的段寄存器0x10是32位数据段描述符的索引
  mov esi,[PM32_EIP_OFF];加载先前保存的EIP
  mov esp,[PM32_ESP_OFF];加载先前保存的ESP
  jmp esi ;eip=esi 回到了realadr_call_entry函数中

func_table:  ;函数表
  dw _getmmap ;获取内存布局视图的函数
  dw _read ;读取硬盘的函数
    dw _getvbemode ;获取显卡VBE模式 
    dw _getvbeonemodeinfo ;获取显卡VBE模式的数据
    dw _setvbemode ;设置显卡VBE模式

        这个文件会单独编译成initldrsve.bin,在前面write_realintsvefile()函数作用就是将这个文件的内容放到对应的内存地址0x1000上。

二级引导器主函数

        二级引导器主函数放在ldrkrlentry.c中,目前仅调用一个空函数init_bstartparm()用来收集机器信息,在后面实现。文章来源地址https://www.toymoban.com/news/detail-469529.html


void ldrkrl_entry()
{
    init_bstartparm();
    return;
}

到了这里,关于OS实战笔记(9)-- 构建二级引导器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux常见维护报错,修复MBR引导/修复grub2菜单/内核文件丢失

    目录 一,修复MBR 1.模拟MBR损坏 2.重启查看系统 3.借助当前系统的光盘来进行修复 4.进入修复模式 进行修复 5.恢复正常 二,修复grub2菜单 1.出现情况 2.进入修复模式 3.进行修复grub2菜单 4.退出重启即可 三,内核文件丢失 1.进入安全模式 2.挂载光盘 3.安装光盘内的内核包 4.修复

    2024年02月13日
    浏览(44)
  • 通过 jekyll 构建 github pages 博客实战笔记

    jekyll 搭建教程 安装 Ruby,请访问 下载地址。 Jekyll 是一个简单且具备博客特性的静态网站生成器。 Jekyll 中文文档 极客学院中文文档 使用以下命令安装 Jekyll。 在中国可能需要使用 代理软件 。然后,请等待并学习如何使用它。 或者,您可以使用 rails 替代 Jekyll。 如果您使用

    2024年02月04日
    浏览(70)
  • macOS_Ventura_13.0_22A380可引导可虚拟机安装的纯净版苹果OS系统ISO镜像安装包免费下载

    现在网络上黑果系统出现了许多多合一的多功能版,不是说这些版本不好,只是小编个人觉得,操作系统就是用来使用的,黑果本来就是服务于一些非苹果机的苹果OS爱好者的,简洁稳定应该是首选,固小编毅然放弃那些多功能的豪华版镜像版,独宠我的纯净可引导安装版。

    2024年02月08日
    浏览(60)
  • macOS_Monterey_12.6_21G115可引导可虚拟机安装的纯净版苹果OS系统ISO镜像安装包免费下载

    现在网络上黑果系统出现了许多多合一的多功能版,不是说这些版本不好,只是小编个人觉得,操作系统就是用来使用的,黑果本来就是服务于一些非苹果机的苹果OS爱好者的,简洁稳定应该是首选,固小编毅然放弃那些多功能的豪华版镜像版,独宠我的纯净可引导安装版。

    2024年02月11日
    浏览(50)
  • Linux 之七 Ubuntu 22.04 配置内核版本、GRUB 引导、远程桌面、包后缀(-dev、-dbg等)、Ubuntu 阶段更新

      前段时间重新安装了 Ubuntu 22.04 LTS,安装后没有显示 GRUB 引导页面(默认自动跳过),直接使用默认内核启动,而我需要变更一下默认的内核版本,特此记录一下修改过程。   Ubuntu 中安装其他版本的内核非常简单,内核其实就是相当于一个软件(DEB 包),安装方式与

    2024年02月12日
    浏览(64)
  • Linux 之十八 Ubuntu 22.04 配置内核版本、GRUB 引导、远程桌面、包后缀(-dev、-dbg等)、Ubuntu 阶段更新

      前段时间重新安装了 Ubuntu 22.04 LTS,安装后没有显示 GRUB 引导页面(默认自动跳过),直接使用默认内核启动,而我需要变更一下默认的内核版本,特此记录一下修改过程。   Ubuntu 中安装其他版本的内核非常简单,内核其实就是相当于一个软件(DEB 包),安装方式与

    2024年02月07日
    浏览(65)
  • 深度学习笔记(七)——基于Iris/MNIST数据集构建基础的分类网络算法实战

    文中程序以Tensorflow-2.6.0为例 部分概念包含笔者个人理解,如有遗漏或错误,欢迎评论或私信指正。 截图和程序部分引用自北京大学机器学习公开课 在神经网络的构建过程中,都避不开以下几个步骤: 导入网络和依赖模块 原始数据处理和清洗 加载训练和测试数据 构建网络

    2024年01月18日
    浏览(33)
  • Surface实现TF卡槽引导Ventoy加载Fydeos,不格式化硬盘,不重装Windows!

    最近买了一台Suface Pro 3,装的是Win 10系统,4+64GB版本。 但因为Windows的触屏体验不是特别好, 所以想装一个Windows+Android系统来满足一下日常学习+娱乐。 在市面上找到一个Fydeos系统,可以运行安卓APP,并且fydeos团队对Surface全系列都做了对应版本,所以驱动适配会很好,于是就

    2024年02月08日
    浏览(119)
  • 吴恩达提示工程实战演练 - 如何引导GPT主动思考

    策略:明确完成任务所需的步骤 接下来的任务我想让GPT处理一段文本,且需要完成一下一系列任务: 1)将文本总结成摘要  2)将摘要翻译成英文 3)提取英文摘要中的名字 4)将翻译的英文摘要和提取的名字以JSON格式输出 每一步任务都是紧密相关的,且都是依赖上一步任务

    2024年02月09日
    浏览(50)
  • flutter开发实战-实现左右来回移动的按钮引导动画效果

    flutter开发实战-实现左右来回移动的按钮引导动画效果 最近开发过程中需要实现左右来回移动的按钮引导动画效果 AnimationController用来控制一个或者多个动画的正向、反向、停止等相关动画操作。在默认情况下AnimationController是按照线性进行动画播放的。AnimationController两个监听

    2024年02月13日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包