Part 1: PC Bootstrap

Getting Started with x86 assembly

Exercise 1. Familiarize yourself with the assembly language materials available on the 6.828 reference page. You don’t have to read them now, but you’ll almost certainly want to refer to some of this material when reading and writing x86 assembly.

We do recommend reading the section “The Syntax” in Brennan’s Guide to Inline Assembly. It gives a good (and quite brief) description of the AT&T assembly syntax we’ll be using with the GNU assembler in JOS.

Simulating the x86


The PC’s Physical Address Space

Basic Input/Output System (BIOS) 占据了 64KB 的位置从 0x000F0000 到 0x000FFFFF 。在早期的 PC 里面,BIOS 是真正的只读内存,后面替换成了可更新的闪存。
BIOS 负责系统基础的初始化,比如激活声卡,检查内存的数量。
在完成硬件的初始化之后,将操作系统从磁盘中加载入内存,之后将系统的控制权交给操作系统。

The ROM BIOS



开机要启动的第一行代码:跳转到 0xf000e05b 处开始执行。

  • 开机启动的第一句的运行的地址是 0x0000fff0,位于 BIOS 所在的 ROM 的顶端。
  • 第一句运行的时候 PC = 0xf000,IP = 0xfff0。
  • 第一句代码是一句跳转的代码,执行完成后设置成 PC = 0xf000,IP = 0xe05b

两个问题:

  • 为什么第一句代码的位置是 0x0000fff0 ?
    因为 BIOS 所在的位置从 0x000f0000 - 0x000fffff。这样的设计保证了开机启动的第一句是执行这个代码。此时也没有其他的代码可以执行了。
  • 如何将 PC = 0xf000 ,IP = 0xfff0 转化为物理地址?
    在 x86 的架构上面,开机启动是保护模式,保护模式的寻址方式是 16 * PC + IP。也就是将 PC 所表示的地址左移四位之后再加上 IP 地址。

Exercise 2. Use GDB’s si (Step Instruction) command to trace into the ROM BIOS for a few more instructions, and try to guess what it might be doing. You might want to look at Phil Storrs I/O Ports Description, as well as other materials on the 6.828 reference materials page. No need to figure out all the details - just the general idea of what the BIOS is doing first.




Part 2: The Boot Loader

磁盘的一个扇区的大小为 512 字节,这意味着磁盘的读和写都以 512 个字节的大小作为最基础的操作对象。
第一个扇区放了启动的程序,将启动扇区里面的程序加载到 从 0x7c00 到 0x7dff 所在的位置上。
然后将 PC = 0x0000 IP = 0x7c00。通过改变 PC 和 IP 的位置将 CPU 的控制权交给了启动程序。

#include <inc/mmu.h># Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00..set PROT_MODE_CSEG, 0x8         # kernel code segment selector
.set PROT_MODE_DSEG, 0x10        # kernel data segment selector
.set CR0_PE_ON,      0x1         # protected mode enable flag.globl start
start:.code16                     # Assemble for 16-bit modecli                         # Disable interrupts#关闭中断cld                         # String operations increment#设置字行块从低地址传送到高地址#clear direction flag#df: 方向标志位。在串处理指令中,控制每次操作后si,di的增减。(df=0,每次操作后si、di递增;df=1,每次操作后si、di递减)。# Set up the important data segment registers (DS, ES, SS).#将 ds,es,ss 三个字段清零,因为经过了 BIOS ,这三个段寄存器可能是随机的数字xorw    %ax,%ax             # Segment number zero#得到0movw    %ax,%ds             # -> Data Segmentmovw    %ax,%es             # -> Extra Segmentmovw    %ax,%ss             # -> Stack Segment# Enable A20:#   For backwards compatibility with the earliest PCs, physical#   address line 20 is tied low, so that addresses higher than#   1MB wrap around to zero by default.  This code undoes this.#使能A20#为了兼容早期的 PC机 ,当地址大于 1M 的时候会默认回滚到 0 处。下面的程序是撤销这一操作。通过打开 A20 门来取消地址的回滚。#seta20.1和seta20.2两段代码实现打开A20门的功能,其中seta20.1是向键盘控制器的0x64端口发送0x61命令,这个命令的意思是要向键盘控制器的 P2 写入数据;seta20.2是向键盘控制器的 P2 端口写数据了。写数据的方法是把数据通过键盘控制器的 0x60 端口写进去。写入的数据是 0xdf,因为 A20 gate 就包含在键盘控制器的 P2 端口中,随着 0xdf 的写入,A20 gate 就被打开了。
#————————————————
#版权声明:本文为CSDN博主「雪原学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
#原文链接:#https://blog.csdn.net/qq_32473685/article/details/93626548
seta20.1:inb     $0x64,%al               # Wait for not busytestb   $0x2,%al#test 逻辑运算指令,对两个操作数进行AND操作,并且修改PSW(程序状态寄存器PSW是计算机系统的核心部件——运算器的一部分,PSW用来存放两类信息:一类是体现当前指令执行结果的各种状态信息,称为状态标志,如有无借位进位(CY位)、有无溢出), test 与 AND 指令唯一不同的地方是,TEST 指令不修改目标操作数。jnz     seta20.1movb    $0xd1,%al               # 0xd1 -> port 0x64outb    %al,$0x64
seta20.2:inb     $0x64,%al               # Wait for not busytestb   $0x2,%aljnz     seta20.2movb    $0xdf,%al               # 0xdf -> port 0x60outb    %al,$0x60# Switch from real to protected mode, using a bootstrap GDT# and segment translation that makes virtual addresses # identical to their physical addresses, so that the # effective memory map does not change during the switch.#lgdt这条指令的格式是lgdt m48操作数是一个48位的内存区域,该指令将这6字节加载到全局描述表寄存器(GDTR)中,低16位是全局描述符表(GDT)的界限值,高32位是GDT的基地址。”gdtdesc“被定义在第82行:#lgdt指令后面的三行是将CR0寄存器第一位置为1,其他位保持不变,这将导致处理器的运行变成保护模式。支持处理器已经进入保护模式。#(貌似不能直接对 %cr0寄存器进行操作?)lgdt    gdtdescmovl    %cr0, %eaxorl     $CR0_PE_ON, %eaxmovl    %eax, %cr0# Jump to next instruction, but in 32-bit code segment.# Switches processor into 32-bit mode.ljmp    $PROT_MODE_CSEG, $protcseg.code32                     # Assemble for 32-bit mode
protcseg:# Set up the protected-mode data segment registersmovw    $PROT_MODE_DSEG, %ax    # Our data segment selectormovw    %ax, %ds                # -> DS: Data Segmentmovw    %ax, %es                # -> ES: Extra Segmentmovw    %ax, %fs                # -> FSmovw    %ax, %gs                # -> GSmovw    %ax, %ss                # -> SS: Stack Segment# Set up the stack pointer and call into C.#设置esp,然后调用bootmain函数#Extended Stack Pointer为扩展栈指针寄存器movl    $start, %espcall bootmain# If bootmain returns (it shouldn't), loop.
spin:jmp spin
# Bootstrap GDT
.p2align 2                                # force 4 byte alignment
#GDT表中包括三个表项(4,5,6行),分别代表三个段,null seg,code seg,data seg。由于xv6其实并没有使用分段机制,也就是说数据和代码都是写在一起的,所以数据段和代码段的起始地址都是0x0,大小都是0xffffffff=4GB。
#有个疑问?是每个段分成三个部分?还是就一共3个段?
gdt:SEG_NULL                # null segSEG(STA_X|STA_R, 0x0, 0xffffffff) # code segSEG(STA_W, 0x0, 0xffffffff)           # data seg
gdtdesc:.word   0x17                            # sizeof(gdt) - 1.long   gdt                             # address gdt#gdt是一个标识符,标识从这里开始就是GDT表了。

boot/main.c

#include <inc/x86.h>
#include <inc/elf.h>/*********************************************************************** This a dirt simple boot loader, whose sole job is to boot* an ELF kernel image from the first IDE hard disk.** DISK LAYOUT*  * This program(boot.S and main.c) is the bootloader.  It should*    be stored in the first sector of the disk.**  * The 2nd sector onward holds the kernel image.**  * The kernel image must be in ELF format.** BOOT UP STEPS*  * when the CPU boots it loads the BIOS into memory and executes it**  * the BIOS intializes devices, sets of the interrupt routines, and*    reads the first sector of the boot device(e.g., hard-drive)*    into memory and jumps to it.**  * Assuming this boot loader is stored in the first sector of the*    hard-drive, this code takes over...**  * control starts in boot.S -- which sets up protected mode,*    and a stack so C code then run, then calls bootmain()**  * bootmain() in this file takes over, reads in the kernel and jumps to it.**********************************************************************/
//扇盘区间的大小
#define SECTSIZE        512
#define ELFHDR          ((struct Elf *) 0x10000) // scratch space
//ELF header 在内存当中临时存放的地址
void readsect(void*, uint32_t);
void readseg(uint32_t, uint32_t, uint32_t);void
bootmain(void)
{struct Proghdr *ph, *eph;//void readseg(uint32_t pa, uint32_t count, uint32_t offset)函数从磁盘offset字节(offset相对于第一个扇区第一个字节开始算)对应的扇区开始读取count字节到物理内存pa处。首先读取第一个扇区的SECTSIZE*8(一页)字节的内核文件(ELF格式)到物理内存ELFHDR(0x10000)处。
//将系统文件 kernel 当中的前 4096 个字节读到 0x10000处。// read 1st page off diskreadseg((uint32_t) ELFHDR, SECTSIZE*8, 0);//检查ELF文件的魔数,来确认是否是有效的 ELF 文件// is this a valid ELF?if (ELFHDR->e_magic != ELF_MAGIC)goto bad;//接下来从ELF文件头读取ELF Header的e_phoff和e_phnum字段,分别表示Segment结构在ELF文件中的偏移,和项数。然后将每一个Segment从ph->p_offset对应的扇区读到物理内存ph->p_pa处。
// e_phoff得到的是0x34,ELFHDR + ELFHDR->e_phoff = 0x10034,这个地址就是第一个program header的地址// load each program segment (ignores ph flags)ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);eph = ph + ELFHDR->e_phnum;//eph是program table entry的个数, ELFHDR->e_phnum//这里决定了要循环多少次for (; ph < eph; ph++)// p_pa is the load address of this segment (as well// as the physical address)//根据readelf -l kernel的输出结果,第一个段的offset = 0x1000,p_pa = 0x00100000,size = 0x07dac// 所以我们要做的就是,系统镜像偏移0x1000处读取0x07dac个字节的数据到0x00100000// 在readseg函数中,我们将offset转为真正的扇区号,因为镜像是存放在第1扇区开始的,所以//这个是可以计算的readseg(ph->p_pa, ph->p_memsz, ph->p_offset);// call the entry point from the ELF header// note: does not return!//当所有的段都加载到内存的当中的时候,下面这条语句完成了,跳转到内核当中去执行((void (*)(void)) (ELFHDR->e_entry))();bad:outw(0x8A00, 0x8A00);outw(0x8A00, 0x8E00);while (1)/* do nothing */;
}// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
// Might copy more than asked
//为什么存在会拷贝多的字节的可能?
void
readseg(uint32_t pa, uint32_t count, uint32_t offset)
{uint32_t end_pa;end_pa = pa + count;// round down to sector boundary//这里是因为pa不一定都是512字节对齐的,我们将pa做一个512字节对齐//下面进行一个举例,例如pa=700,~(512-1) = 0x1110_0000_0000//700 & 0x1110_0000_0000 = 0x200 = 512,这样我们就做到了对齐pa &= ~(SECTSIZE - 1);// translate from bytes to sectors, and kernel starts at sector 1offset = (offset / SECTSIZE) + 1;// If this is too slow, we could read lots of sectors at a time.// We'd write more to memory than asked, but it doesn't matter --// we load in increasing order.//使用偏移量来计算所要读取的扇区是哪个
//,比如说上面的offset = 0x1000,0x1000/512+1 = 9,就是读取9号扇区。
//扇区号+1这个应该是这样理解的,
//硬盘扇区默认就是从1扇区开始计算的。
//如果我们要读取511字节的内容,511/512=0,肯定是不对的。所以要+1while (pa < end_pa) {// Since we haven't enabled paging yet and we're using// an identity segment mapping (see boot.S), we can// use physical addresses directly.  This won't be the// case once JOS enables the MMU.readsect((uint8_t*) pa, offset);//物理地址+512字节pa += SECTSIZE;//读下一个扇区offset++;}
}void
waitdisk(void)
{// wait for disk reaadywhile ((inb(0x1F7) & 0xC0) != 0x40)/* do nothing */;
}void
readsect(void *dst, uint32_t offset)
{// wait for disk to be readywaitdisk();
//使用LBA模式的逻辑扇区方式来寻找扇区,这里和硬件相关//就暂且不要管好了,涉及到的硬件细节太多了,确实很麻烦outb(0x1F2, 1);         // count = 1outb(0x1F3, offset);outb(0x1F4, offset >> 8);outb(0x1F5, offset >> 16);outb(0x1F6, (offset >> 24) | 0xE0);outb(0x1F7, 0x20);      // cmd 0x20 - read sectors// wait for disk to be readywaitdisk();// read a sector//一次读取的单位是四个字节,所以循环sectorsize/4 = 128次insl(0x1F0, dst, SECTSIZE/4);
}

boot.s 和 main.c 运行完后 CPU、内存、磁盘的模式。

使用命令来查看 readelf -l obj / kern / kernel
第一个 offerset 位于 0x001000 ,说明在此之前都是 ELF header

使用 readelf -h obj/kern/kernel 来查看 ELF 文件头,发现入口地址在 0x10000c 处

如何保证 boot.s 和 main.c 链接后刚好 512 个字节?并且保证这个主导扇区以 0x55AA 结尾。
存在一个 boot/sign.pl 文件,这个文件的作用是:如果 boot.c 和 main.c 编译后的总字节超过了 510 就得退出。如果少于 510 字节,填充到 510 字节,并且在后面追加两个字节 0x55AA.


open(BB, $ARGV[0]) || die "open $ARGV[0]: $!";
binmode BB;
my $buf;
read(BB, $buf, 1000);
$n = length($buf);
if($n > 510){print STDERR "boot block too large: $n bytes (max 510)\n";exit 1;
}
print STDERR "boot block is $n bytes (max 510)\n";
$buf .= "\0" x (510-$n);
$buf .= "\x55\xAA";
open(BB, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
binmode BB;
print BB $buf;
close BB;

Exercise 3. Take a look at the lab tools guide, especially the section on GDB commands. Even if you’re familiar with GDB, this includes some esoteric GDB commands that are useful for OS work.

Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in boot/boot.S, using the source code and the disassembly file obj/boot/boot.asm to keep track of where you are. Also use the x/i command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly in obj/boot/boot.asm and GDB.

Trace into bootmain() in boot/main.c, and then into readsect(). Identify the exact assembly instructions that correspond to each of the statements in readsect(). Trace through the rest of readsect() and back out into bootmain(), and identify the begin and end of the for loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader.








猜测是 readseg()

Be able to answer the following questions:

At what point does the processor start executing 32-bit code?

What exactly causes the switch from 16- to 32-bit mode?

 # Switches processor into 32-bit mode.ljmp    $PROT_MODE_CSEG, $protcseg

What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?

//当所有的段都加载到内存的当中的时候,下面这条语句完成了,跳转到内核当中去执行((void (*)(void)) (ELFHDR->e_entry))();

Where is the first instruction of the kernel?

How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?

疑问:是不是调试的过程中出了什么问题?导致程序一直在一个地方当中循环。

总结:
总的开机流程
BIOS —— 带电自检,初始化中断向量表,获取硬件信息,将boot.s ( 和 main.c ? 总之就是第一块硬盘当中的内容) 读入磁盘,并且跳转到 boot.s
boot.s —— 设置全局描述符表,完成实模式到保护模式的切换,调用 bootmain(位于 main.c,可以理解为控制权的转移)
main.c —— 将内核读入内存当中,并且将系统的控制权交给内核)

补充1:
实模式和保护模式

  • 实模式

程序中用到的地址就是实际的物理地址。寻址方式 :16 * 段地址 + 偏移地址 = 目的地址 ,一共 20 位的物理地址,最大寻址为 1MB(2^20),分段大小为 64KB 。
实模式在安全方面有缺陷。在实模式下,内核和用户具有同样的操作权限。程序可以将段地址修改成任意值。这样,程序便可以非法访问操作系统代码所在的地址,具有不小的安全隐患。

  • 保护模式
    保护模式通过分页机制、内存的保护、硬件虚拟存储模式来提高了对程序的保护。
    保护模式还添加了段属性来限制程序对段的访问。(这才有了非法访问一说。)
    保护模式因此引入了全局描述符表(Global Descriptor Table,GDT),每个表项便是一个描述段属性的数据结构(段描述符)。段描述符是某个段的所有属性的集合,包括段基址,段界限,类型等。

    有了这个表,便可以在访问段前进行地址是否非法的检查,大大提升了安全性能。

参考文章:实模式和保护模式

补充2:
ELF魔数
魔数是用来表示文件的类型,和文件所在的平台的属性(操作系统、CPU 类型)。

一般前四个字节是固定的,被称作 ELF 文件的魔数。
参考文章:ELF魔数
ELF 文件头

Loading the Kernel
Exercise 4. Read about programming with pointers in C. The best reference for the C language is The C Programming Language by Brian Kernighan and Dennis Ritchie (known as ‘K&R’). We recommend that students purchase this book (here is an Amazon Link) or find one of MIT’s 7 copies.

Read 5.1 (Pointers and Addresses) through 5.5 (Character Pointers and Functions) in K&R. Then download the code for pointers.c, run it, and make sure you understand where all of the printed values come from. In particular, make sure you understand where the pointer addresses in printed lines 1 and 6 come from, how all the values in printed lines 2 through 4 get there, and why the values printed in line 5 are seemingly corrupted.

There are other references on pointers in C (e.g., A tutorial by Ted Jensen that cites K&R heavily), though not as strongly recommended.

Warning: Unless you are already thoroughly versed in C, do not skip or even skim this reading exercise. If you do not really understand pointers in C, you will suffer untold pain and misery in subsequent labs, and then eventually come to understand them the hard way. Trust us; you don’t want to find out what “the hard way” is.

#include <stdio.h>
#include <stdlib.h>
void f(void)
{int a[4];int *b = malloc(16);//这一句就无法通过编译了,malloc返回值是 void*//需要强制转换成 int* 类型的指针//应当改为 int*b = (int*)malloc(16);int *c;int i;printf("1: a = %p, b = %p, c = %p\n", a, b, c);//a和b都放置在栈区,c放置在堆区//1: a = 0x7ffffffedfe0, b = 0x80052a0, c = 0x7ffffffee007c = a;//将 a 的地址传递给 cfor (i = 0; i < 4; i++)a[i] = 100 + i;c[0] = 200;printf("2: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",a[0], a[1], a[2], a[3]);//2: a[0] = 200, a[1] = 101, a[2] = 102, a[3] = 103c[1] = 300;*(c + 2) = 301;3[c] = 302;//与 *(c+3)等价,这个语法和汇编语言的语法类似printf("3: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",a[0], a[1], a[2], a[3]);//3: a[0] = 200, a[1] = 300, a[2] = 301, a[3] = 302c = c + 1;//移动到 a[1] 的位置*c = 400;printf("4: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",a[0], a[1], a[2], a[3]);//4: a[0] = 200, a[1] = 400, a[2] = 301, a[3] = 302c = (int *) ((char *) c + 1);//int 是 4 字节,这个操作是,让 c指针在内存中移动1字节*c = 500;printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n",a[0], a[1], a[2], a[3]);//5: a[0] = 200, a[1] = 128144, a[2] = 256, a[3] = 302b = (int *) a + 1;//让 b 指向a[1],结果果真如此,a 指向 a[0],和 b 只相差了 4 字节 c = (int *) ((char *) a + 1);// c 从a 所在的位置向后移动 1 字节,和 b 相差 3 字节printf("6: a = %p, b = %p, c = %p\n", a, b, c);//6: a = 0x7ffffffedfe0, b = 0x7ffffffedfe4, c = 0x7ffffffedfe1
}int main(int ac, char **av)
{f();return 0;
}


ELF 文件格式

使用命令来查看 readelf -l obj / kern / kernel


使用 命令 objdump -s -d obj/kern/kernel 来查看不同段对应的十六进制 (-s 命令 ) 和反汇编 (-d 命令)。

可以从汇编代码的顺序中看出,这个是小端字节序。
人生第一个靠自己写出来的小端字节序测试代码。(用理论指导实践)

使用命令具体查看信息

objdump obj/kern/kernel


可以通过 size 命令来查看每个段的长度。

后面的 ‘DATA’ ‘LOAD’ ‘ALLOC’ ‘CONTENTS’ 是每个段的属性。

.text : 可执行的指令。

.rodata : 只读数据段。比如 C风格的字符串。①操作系统可以在载入内存的时候将其映射成为只读,来保证其正确性;② 在某些嵌入式平台下可以直接放入只读存储器当中。

.data:存放着已初始化的数据,比如初始化好的全局数据和局部变量。

.bss :未初始化的数据,并未为其分配内存。

未初始化的变量视为 0 ,所以会放在 .bss 段。如果变量被初始化为 0 ,也可视作未被初始化,也可以放在 .bass 段。

其他段的说明

其他段可能存放了一些用来调试的信息,在将程序载入内存当中的时候不会说明。

堆区和栈区

5、6的示意图

使用 命令来查看 boot.c 的示意图

Exercise 5. Trace through the first few instructions of the boot loader again and identify the first instruction that would “break” or otherwise do the wrong thing if you were to get the boot loader’s link address wrong. Then change the link address in boot/Makefrag to something wrong, run make clean, recompile the lab with make, and trace into the boot loader again to see what happens. Don’t forget to change the link address back and make clean again afterward!

看来得复习 makefile 的语法规则了
修改前的原来的地址

将原来的 0x7c00 修改为 0x7e00

之后 make 和 make clean 走一波

无法正确完成地址的跳转会执行循环,这里能够看到循环的执行。

qemu 也在不停的闪烁

使用命令查看 ELF 可执行文件程序入口地址

Exercise 6. We can examine memory using GDB’s x command. The GDB manual has full details, but for now, it is enough to know that the command x/Nx ADDR prints N words of memory at ADDR. (Note that both 'x’s in the command are lowercase.) Warning: The size of a word is not a universal standard. In GNU assembly, a word is two bytes (the ‘w’ in xorw, which stands for word, means 2 bytes).

Reset the machine (exit QEMU/GDB and start them again). Examine the 8 words of memory at 0x00100000 at the point the BIOS enters the boot loader, and then again at the point the boot loader enters the kernel. Why are they different? What is there at the second breakpoint? (You do not really need to use QEMU to answer this question. Just think.)

在内核尚未被加载之前, 0x00100000 地址处没有其他什么东西。在内核被加载到 0x00100000 当中之后就有了内核的代码。

2022-2-16 MIT 6.828 Lab1:Booting a PC part1-part2相关推荐

  1. MIT6.828——LAB1:Booting a PC

    MIT6.828--LAB1:Booting a PC Part1:PC Bootstrap 练习1: 熟悉X86汇编语言 The PC's Physical Address Space 电脑的物理地 ...

  2. 2022-2-27 MIT 6.828 Lab 1: Booting a PC | Part 3: The Kernel | The Stack |exercise 9 - 11

    Exercise 9. Determine where the kernel initializes its stack, and exactly where in memory its stack ...

  3. MIT 6.828 lab1 part2

    Part 2: The Boot Loader 加载内核 为了理解boot/main.c,你需要知道ELF二进制文件是什么.当你编译和链接一个C程序(如JOS内核)时,编译器将每个C源文件('. C ...

  4. MIT JOS 6.828 Lab1学习笔记

    官网:https://pdos.csail.mit.edu/6.828/2018/schedule.html 参考资料: https://blog.csdn.net/bysui/category_62 ...

  5. MIT 6.828 操作系统工程 lab4A:多处理器支持和协作多任务

    MIT 6.828 操作系统工程 lab4A:多处理器支持和协作多任务 这篇是我自己探索实现 MIT 6.828 lab 的笔记记录,会包含一部分代码注释和要求的翻译记录,以及踩过的坑/个人的解决方案 ...

  6. 适合人工智能的编程语言有哪些 人工智能学习路线(2022.10.16)

    人工智能ai用什么编程语言_用于AI开发的6种最佳编程语言 人工智能ai用什么编程语言_用于AI开发的6种最佳编程语言_cxq8989的博客-CSDN博客 最适合人工智能开发的六种编程语言 最适合人工 ...

  7. 《MIT 6.828 Lab 1 Exercise 10》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 10. 题目 Exercise 10. To become familiar with the C calling conventi ...

  8. on java 8 学习笔记 2022.2.16

    2022.2.16 问题 其实我感觉引用计数的方法不只书中提到的这种问题吧,难道不会有对象被误删的情况吗? 答:不会,因为这种方法就是参考了只要有引用,它就是有效对象的路子,而只要引用大于0,那它就是 ...

  9. 湖北2022农民丰收节 国稻种芯:麻城启动王忠林宣布活动

    湖北2022农民丰收节 国稻种芯:麻城启动王忠林宣布活动 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯·中国水稻节 中国三农智库网-功能性农业·农业大健康 ...

最新文章

  1. sitecore系列教程之如何以编程方式将访客数据关联到联系人卡片
  2. Shell文件的排序、合并和分割
  3. 李宏毅机器学习课程9~~~深度学习技巧
  4. 关于“数组指针”的一点想法
  5. 随想录(c编译器的实现)
  6. 新的一年,这7个“菜鸟坑”千万别再踩了!
  7. php上传文件502,PHPStrom上传文件报502错误原因
  8. Ubuntu14.04系统中文输入法安装详解
  9. MEncoder的基础用法—6.1. 选择编解码器及容器格式
  10. figure函数--Matplotlib
  11. 【Feign源码】保存请求数据的载体--Template
  12. 详解java静态数组
  13. roseha 11 用VM虚拟机创建集群测试
  14. java 账本 创建数据库_想用你所学的JAVA与数据库写一个属于自己的账本吗?一起来看看呗!看如何用java项目操作数据库...
  15. 处理回归问题常见的算法
  16. 2月18日绿健简报,星期六,农历正月廿八
  17. 模拟工作室机架音频插件
  18. 2022年第十二届APMCM亚太地区大学生数学建模竞赛--思路代码
  19. 反射feign接口,调用feign的方法
  20. 数据摆渡服务器_信息安全华能国际:烟气监测数据网络传输的安全防护

热门文章

  1. 小米、京东、360争抢入局,智能门锁血战开启
  2. DEJA_VU3D - Cesium功能集 之 038-直接加载本地或在线的Shape数据
  3. VOD三种VOD视频点播技术的简介和比较
  4. PVCBOT【14号A版】机械狗--四足爬行机器人
  5. 【光波电子学】MATLAB绘制光纤中线性偏振模式LP之单模光纤的电场分布(光斑)
  6. 技术人如何通过了解业务,获取晋升机会
  7. keka 1.2.7 中文版 macOS 压缩文件管理器
  8. 低成本:小米盒子输出到笔记本上看视频和调试
  9. 机器阅读理解笔记之glove词向量与attentive readerimpatient reader和bi-DAF
  10. Android 编译IJKPlayer源码