Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)


PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=102)

环境说明
  • Ubuntu 18.04
  • gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
  • Bochs 2.6
  • As86 version: 0.16.17

前言

  自从我近段时间开始温习一些基础知识以来,其中觉得以前学的很浅的就是OS原理。为啥这样说呢?因为就是浅,知道一些琐碎的知识。以前我自负的认为OS就是硬件的抽象,然后把这些硬件资源合理的分配给用户使用就完了,因为我觉得合理的整合这些硬件资源是非常‘简单’的。

  由于我本身对底层是非常着迷的。带着觉得OS很简单的想法,想着去看看LinuxKernel的源码。在以前,我对LinuxKernel的认知很肤浅,就知道一些驱动移植的事情。如果硬要说一件我在LinuxKernel中玩的很深的事情,那就是自己理解并实现了一个类似Anonymous Shared Memory的Linux驱动,详见以下两篇文章。

  带着这样的想法其实已经很久了,由于现在的LinuxKernel太大了,对新手不友好。我就想着去找一个老一点的版本内核看看。结果去网上一找,就发现了前人已经做了许多许多了,比如这个之前就有了解的《linux 0.11内核完全注释》,还比如其他许许多多前人种的‘树’,看到了许多,最终我决定跟着国内现在比较好和新的资料从‘远古’开始学习它。它就是《Linux内核完全注释(PDF) v5.0 by 赵炯.pdf》。它是基于LinuxKernel0.12 讲述的,它是我在ubuntu1804上编译通过LinuxKernel0.12的主要参考和学习资料,同时也是我在Bochs上运行成功的主要参考和学习资料。

  好的多说无益,直接看运行效果。

  说来也惭愧,利用断断续续的时间,我花了约2月,把LinuxKernel0.12在Ubuntu1804上编译通过,并在1804上通过Bochs运行成功。而且要命的事情是我其实只加了一些打印调试函数,和根据实际的调试情况修改了一些代码,却花了那么久的时间,搞得我很不自信了QAQ。

  我修改好的源码已经开源,立即想要源码的请直接去文末两个rep clone即可。

  本文主要还是简单介绍LinuxKernel从上电到进入sh的中间的简要流程。这些流程网上已经有很多了,可能我会挑选一些我觉得比较重要的来说。

  本文适用于:

  • 会编译和使用bochs的人。不会可以去网上找找,很多这方面的资料。
  • 对Intel AT&T 汇编有点了解的人。
  • 会GDB调试的人。
  • 知道C语言常识的人。
  • 对LinuxKernel感兴趣的人。

搭环境

  工欲善其事必先利其器。本文主要是在Ubuntu1804上编译生成LinuxKernel,然后用Bochs运行我们的内核。

Ubuntu18.04环境安装

我们应该首先安装make,gcc,gcc-multilib,bin86。

  • sudo apt install build-essential cmake make gcc-multilib g++-multilib module-assistant bin86

然后进入源码目录。

  • cd my_src
  • make disk

更多的详情信息查看开源的rep。

编译两个bochs版本备用

  我们首先就得把Linux0.12的运行环境搭建起来,方便我们调试。我们使用的是Bochs2.6 和 GDB远程调试。并编译出两个bochs版本,一个是带本身调试功能(命名为:bochs),一个是和gdb联调(命名为:bochsdbg)。bochs 主要是调试在init/main()函数之前的内容以及查看更多的x86寄存器。 bochsdbg主要是调试进入init/main()函数之后到sh成功执行的事情。

  • 通过 ./configure --enable-debugger 生成bochs。
  • 通过 ./configure --enable-gdb-stub 生成bochsdbg。
运行我们编译的内核

  通过本文介绍生成的文件是Linux内核镜像,稍微懂点行的人都知道还差一个RootFS。这个文件系统我们在网上下载的例如: http://oldlinux.org/Linux.old/bochs/linux-0.12-080324.zip 。本文生成的Linux内核镜像使用的是rootimage-0.12-hd这个文件系统。

  我建议这里自己配置两个.bxrc文件,一个对应bochs,一个对应bochsdbg远程调试。这样在遇到问题的时候我们可以很方便的调试。

LinuxKernel启动简介

  本节简述LinuxKernel的启动流程。根据我近段时间的学习来看,这里包含了许多的历史性的东西,大家不要去细究为啥是这样,很多都是为了兼容。

  此外在整个学习期间,由于涉及到许多的x86 硬件体系知识,除了参考上文我说的文档以外,还必须参考以下Intel官方文档:

  • Intel® 64 and IA-32 architectures software developer's manual combined volumes 2A, 2B, 2C, and 2D:Instruction set reference, A-Z
  • Intel® 64 and IA-32 architectures software developer's manual combined volumes 3A,
    3B, 3C, and 3D: System programming guide
  • 《Linux内核完全注释(PDF) v5.0 by 赵炯.pdf》 第4章,全篇精华。
boot/bootsect.S 阶段

  当我们的计算机上电以后,IntelCPU进入实模式,并且PC指向了0xfff0整个地址,如下图。什么意思呢?就是开机的时候执行的第一句指令放在0xffff0这个地方,通常这里有一个很重要的东西叫做BIOS。我们可以看到下图,cs=0xf000,base=0xffff0000,在实模式下面,cs:pc 就是真实的指向地址0xffff0。到了这里不知道大家发现没有,这里还差一个东西,那就是bios本来是放在rom里面的,怎么被指向了内存地址0xffff0的地方呢?是谁在之前自动搬运的吗?经过查询后发现,大部分人说开机的时候,对特殊地址的访问会被仲裁器件指向BIOS-ROM器件。仲裁器还可以把地址翻译并指向我们熟悉的MEM和IO。所以这里我理解对0xffff0的访问就是对BIOS-ROM器件的直接访问和执行。

  BIOS主要是做自检,并且在物理地址0x0开始初始化BIOS的中断向量,同时通过BIOS访问存储设备的中断,将可启动设备的第一个扇区512字节给搬运到绝对地址0x7c00(31k)处。然后跳转到0x7c00继续执行,这里被搬运的512字节就是bootsect.S生成的指令。这一段没啥营养,都是一些约定好的,到了CPU执行到绝对地址0x7c00的时候,才是真正的我们能控制的地方。其实这里也能够看到,我们的bootsect.S生成的指令最大只能够512字节,超过了就会出问题。下图为我们的0x7c00处的开始几句指令和bootsect.S的几句指令,同时也能够看到BIOS初始化和自检打印的一些内容:

  在上图的图中,我打印了0x7c00开始的一部分反汇编代码。可以看到和下面的bootsect.S的代码是一致的。

entry start
start:
! start at 0x07c0:0
! add by sky
	mov ax,#BOOTSEG
	mov es,ax
	mov	bp,#msg2	  ! sky-notes: src-str is es:bp
	mov	si,#15        ! sky-notes: src-str-len is cx
	call pirnt_str
! add by sky

	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw
	jmpi	go,INITSEG

  从0x7c00开始,就是我们自己的可以编程的领域了,也开始有了一些我自己特有的内容。主要是各种方法实现的print语句。这种调试方法简直不要太好。

  下面简要说明一下bootsect.S的功能:

  • 首先用rep movw把自己从0x7c00搬运到0x90000,并跳转cs=0x9000, pc=go 标号的地址。继续执行剩下的内容。
  • 通过读取0x1E号中断向量位置的软驱参数(由BIOS初始化时候通过BIOS中断读取的)到内存,然后修改其中的最大扇区数,并重新写回到0x1E中断向量位置绝对地址0x78去。最后重置软驱,使其加载最新的参数。
  • 使用BIOS INT 0x13的2号功能,将第一个软盘第2,3,4,5扇区读取到0x90200开始的位置。这里读取的就是setup.S的指令内容,最大共2k(4*512)。0x90000-0x90200存放的是bootsect.S, 0x90200-0x90A00 为setup.S。
  • 使用BIOS INT 0x13的8号功能,读取磁盘参数:每磁道扇区数。并保存到变量sectors中。
  • 使用BIOS INT 0x13的2号功能,使用刚刚的参数,读取system模块到0x10000,我们的bootsect.S放在0x90000,所以我们system模块最大只能够占用0x10000~0x8ffff。这里的system模块就是除了bootsect和setup模块之外的所有内核代码。
  • 判断bootsect模块第508,509字节是否为0,来判断我们是否指定根文件系统的设备号。我们的内核定义为0x0301,代表第一个磁盘第一个分区为我们的根文件系统。
  • 然后通过jmpi 0:9020跳转到cs=0x9020,pc=0的地方去执行setup.S的代码。

  在我的bootsect模块,我定义了一个打印字符串的函数,主要是通过使用BIOS INT 0x10的0x13号功能实现。主要还是为了调试,注意,这里不能够随意添加代码,因为生成的代码超过512byte后,链接器会报错。只能够少量的添加我们的调试代码。

  至此,我们就执行完了bootsect模块。本模块的主要内容还是加载setup和system到指定位置。bootsect执行的一些调试日志如下图(在0x90200下断点):

注意:图中话框的部分就是我们上文贴出的call pirnt_str打印的。

boot/setup.S 阶段

  首先我们还是来看一下0x90200的位置是否是setup.S,换句话来说是否加载好了setup模块。

  这里和bootsect一样,我也弄了一个prtstr函数,这个prtstr和bootsect里面的是一样的,原理也是一致的。

  刚刚我们提到,setup是从0x90200开始存放的。那么0x90000~0x901ff中的bootsect已经无用了,于是我们setup中,用这里的内存存放一些参数。下面简要说明一下setup.S的功能:

  • 用BIOS INT 0x15功能号0x88取系统所含扩展内存大小并保存在内存0x90002~0x90003处。共两个字节。
  • 用BIOS INT 0x10功能号0x12读取显卡参数,0x9000A 显存大小,0x9000B 显卡类型(单色/彩色),0x9000C显卡特性参数。
  • 用BIOS中断读取屏幕的行列存放到0x9000E 0x9000F
  • 用BIOS INT 0x10功能号0x03读取当前光标位置存放到0x90000 0x90001
  • 用BIOS INT 0x10功能号0x0f读取当前显示页,显示模式,字符列数。 0x90004~0x90005 存放当前显示页。 0x90006 显示模式, 0x90007 字符列数。
  • 读取第一个硬盘参数表和第二个硬盘参数表,并放到0x90080 0x90090。每个表共16byte。注意,这里和之前的软盘参数一样,在BIOS自检过程中,就被放到了中断向量0x41 0x46 的位置。
  • 用BIOS INT 0x13功能号0x15读取当前硬盘设备情况,如果硬盘2不存在,则把0x90090之后的16byte清零。

  下面我们将使CPU从实模式变更为保护模式,下面继续说明一下setup.S的功能:

  • 禁用中断。
  • 然后我们把system模块0x10000~0x8ffff整体下移到0x0开始的位置。就是把最大0x80000(512k)的system模块向下移动0x10000(64k)。
  • 首先加载LDT和GDT。
  • 开启A20地址线,支持1M以上的内存。
  • 初始化两个8259A中断控制器。
  • 通过lmsw 设置cr0最低位位1,进入保护模式。
  • 通过jmpi 0:0x8跳转到绝对地址0x0开始执行system的代码。system是从boot/head.s开始的。

  这里需要说明几个事情:

  • 我们在下移system模块的时候,覆盖了BIOS中断向量表。所以通过BIOS中断打印字符串是行不通的。
  • 在实模式中,cs:pc就是真实执行的地址。但是在保护模式中,cs是一个选择符号,根据选择符号值不同,分表在GDT或者LDT中查找对应的CS段描述符,其中最重要的就是base地址,当未开启分页的时候,这里的base+pc就是我们真实的执行地址。上面我们加载了LDT和GDT。这里的LDT是空,GDT有3项,第零项是空,第一项是代码段描述符,第二项是数据段描述符,他们的基地址都是0x0。当cs=0x08,ds=0x10时,分别指向这里的第一项和第二项。

  刚刚说了,system下移导致BIOS中断向量表被冲掉了,于是我们不能够通过BIOS打印字符串,于是这里我们使用的是直接操作显存内存地址显示字符,这个原理和LinuxKernel tty显示原理差别不是很大。

  这里我们设计了print_str函数,通过直接操控显存然后写入字符进行显示,这里还使用到了刚刚我们保存的当前光标位置(0x90000 0x90001)。写这个主要还是为了调试。

  到此,我们已经开始去执行system的内容,其中head.s是入口。下图是在0x0下断点得到的setup模块的一些打印日志。

  这里我们可以看到,红框还是BIOS中断打印的,黄框是通过直接操纵显存显示的。注意,我这里设计的直接操作显存的函数,是通过循环在当前显存页显示的,并不是我们常见的整页上移的方式。

boot/head.s 阶段

  首先我们还是来看一下0x0的位置是否是head.s,换句话来说是否加载好了system模块。并且,从这里开始,我们就是进入了真正的LinuxKernel的世界,前面都是做一些环境初始化,都是一些固定的内容。

  这里我们需要说明的是,bootsect.S和setup.S用的是intel汇编,而从head.s开始,我们用的都是AT&T汇编。同理,这里我也弄了一个safe_mode_print_str_no_page,打印字符串,为了调试,还是用的直接操作显存的方式。

  从这里开始,CPU开始工作于保护模式,下面简要介绍一下工作流程:

  • 刚刚我们通过jmpi切换到0x0开始执行,这时cs=0x8,根据setup设置好的GDT,base为0x0,同理我们设置其他段寄存器。
  • 设置堆栈为stack_start,这个就是内核堆栈。此符号定义于kernel/sched.c中,如下文。
long user_stack [ PAGE_SIZE>>2 ] ;

struct {
	long * a;
	short b;
	// } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
	} stack_start = { & user_stack , 0x10 };
  • 设置IDT,所有的中断向量指向ignore_int,一个预定义的中断服务程序。共256项,每项8byte。
  • 重新设置GDT。共256项,每项8byte。重新设置GDT的原因是setup的GDT可能会被冲掉,于是把GDT设置到合理的内存位置。这里设置好的GDT有4个。和setup中类似。第0,3个为0.第1,2项为cs和ds的段描述符。
  • 检查A20是否开通,主要是通过判断0x100000 和 0x0值是否相等。
  • 检查数学协处理器是否存在。

  到这里,我们就开始准备正式进入到init/main.c中的main函数了,但是还差最后一个重要的事情,那就是启用分页机制,下面继续介绍其工作流程:

after_page_tables:
	# sky print
	push %ebp
	lea msg5, %ebp
	call safe_mode_print_str_no_page
	pop %ebp	
	#
	pushl $0		# These are the parameters to main :-)
	pushl $0
	pushl $0
	pushl $L6		# return address for main, if it decides to.
	pushl $main
	jmp setup_paging
  • 从上面的代码我们可只,我们在启用分页前,把init/main.c中的main函数地址设置到了堆栈中。
  • 首先我们把从0x0开始的5页内存清零。每页4096字节。其中第一页为页表目录,第2-5页为页表。
  • 设置页表目录的前4项为第2-5页页表地址。注意页表目录为1024项,每项4字节。
  • 倒序设置每一个页表的每一项内容,第5页最后一项为0xfff000。映射之后,2-5页分别映射好了16MB内存的空间。
  • 操作cr0,开启分页
  • 通过ret指令,从堆栈中把main地址弹出去执行。

  到这里,我们正式进入到init/main.c中的main函数中,进入c语言相关代码的地界。下面是进入main之前的一些日志输出。

init/main.c 到进入shell

  这里我们进入了init/main.c中的main函数,可从下图看到。从这里开始,也是我们大家都熟知的Linux内核部分。

void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
	char _my_msg_buf[100];
	sprintf(_my_msg_buf, "kernel main() start, root_dev=%x, swap_dev=%x ... ...\0", ORIG_ROOT_DEV, ORIG_SWAP_DEV);
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);



 	ROOT_DEV = ORIG_ROOT_DEV;
 	SWAP_DEV = ORIG_SWAP_DEV;

	sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);

	envp[1] = term;	
	envp_rc[1] = term;
 	drive_info = DRIVE_INFO;

	memory_end = (1<<20) + (EXT_MEM_K<<10);
	memory_end &= 0xfffff000;//align 4k



	if (memory_end > 16*1024*1024)//if memory_end > 16MB, set it to be 16 MB
		memory_end = 16*1024*1024;

	if (memory_end > 12*1024*1024) 
		buffer_memory_end = 4*1024*1024;
	else if (memory_end > 6*1024*1024)
		buffer_memory_end = 2*1024*1024;
	else
		buffer_memory_end = 1*1024*1024;

	main_memory_start = buffer_memory_end;

	sprintf(_my_msg_buf, "Mem size is %x, buf-mem size is %x, main-mem start %x ... ...\0", memory_end, main_memory_start, buffer_memory_end);
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);


#ifdef RAMDISK
	sprintf(_my_msg_buf, "ramdisk init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif

	sprintf(_my_msg_buf, "memory init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	mem_init(main_memory_start,memory_end);

	sprintf(_my_msg_buf, "trap init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	trap_init();

	sprintf(_my_msg_buf, "blk init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	blk_dev_init();

	sprintf(_my_msg_buf, "chr init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	chr_dev_init();

	sprintf(_my_msg_buf, "tty init ... ...\0");
	__asm__ (
	"push %%ebp\n\t"
	"mov %0, %%ebp\n\t"
	"call safe_mode_print_str_after_page\n\t" 
	"pop %%ebp\n\t"
	:
	:"p"((char *)&_my_msg_buf)
	:);
	tty_init();

	printk("time init ... ...\n\r");
	time_init();

	printk("sched init ... ...\n\r");
	sched_init();
	/*
	After sched_init()

	gdt[0] = NULL
	gdt[1] = kernel cs
	gdt[2] = kernel ds
	gdt[3] = NULL
	
	gdt[4] = task0.tss
	gdt[5] = task0.ldt

	tr=task0.tss
	ldtr=task0.ldt
	*/

	printk("buffer init ... ...\n\r");
	buffer_init(buffer_memory_end);

	printk("hd init ... ...\n\r");
	hd_init();

	printk("floppy init ... ...\n\r");
	floppy_init();

	printk("enable interrupts ... ...\n\r");
	sti();

	printk("go to user mode ... ...\n\r");
	/*
	movl %%esp,%%eax
	pushl $0x17
	pushl %%eax
	pushfl
	pushl $0x0f
	pushl $1f
	iret
	1:
	movl $0x17,%%eax
	mov %%ax,%%ds
	mov %%ax,%%es
	mov %%ax,%%fs
	mov %%ax,%%gs

	iret instruction will do follow op:
	popl eip
	popl cs
	popl eflag
	popl esp
	popl ss
	*/
	move_to_user_mode();

	printf("user_mode: fork() task0 ... ...");
	if (!fork()) {		/* we count on this going ok */

		printf("user_mode: task1 call init ... ...");
		init();
	}
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
	printf("user_mode: task0 call sys_pause() in while ... ...");
	for(;;)
		__asm__("int $0x80"::"a" (__NR_pause):);
}

  注意,这里我们仍然设计了一个函数为safe_mode_print_str_after_page,通过直接操作显存进行显示字符串,知道tty_init之后,我们才能够调用printk类似的函数进行打印。

  下面简要介绍一下main函数主要做的事情:

  • 根据我们在setup中保存到内存中的内存参数初始化高速缓冲区和主存的位置。
  • 然后就是我们常见的初始化mm模块。
  • 初始化中断向量。
  • 初始化块设备。
  • 初始化字符串设备。
  • 初始化tty设备。
  • 初始化时间。
  • 初始化调度模块。
  • 初始化缓冲区。
  • 初始化硬盘。
  • 初始化软盘。
  • 开启中断。
  • 把当前任务切换到用户态。

  当我们切换到用户态之后,并且当前我们的进程是0号进程,我们内核的一些重要初始化基本设置完毕。然后就像我们常见的linux编程那样,通过fork,创建我们的1号进程。然后我们继续进行下面的事情:

  • task0在fork出task1之后,就循环调用sys_pause, 这里主要还是执行schedule()开始执行进程调度。
  • task1成功创建后,调用setup,开始加载根文件系统。然后task1 通过fork创建了task2。
  • task2通过execve开始运行/bin/sh,进入shell。后续就是一些其他的事情。

  到这里,我们已经把kernel跑起来了。在我调试的过程中,主要还是mm模块和schedule模块有些问题,可能和编译器版本有关系,反正我生成的代码,总会报错。哪怕到现在,我开源出来的我修改的内核,也非常的不稳定,经常崩溃。但是好在正常工作了。

  下面给出两种不同打印的日志:


tool/build.c

  此工具是生成LinuxKernel镜像的手段。但是我们在Ubuntu上生成的内核,由于gcc版本变更的原因,需要做一些变更。主要还是把生成的elf格式system模块通过objcopy 生成二进制内存镜像。主要原因就是elf格式需要一个elf加载器进行各个段的重定位,但是由于我们是内核,所以没有。详情,请查看tool/build.c 及 Makefile。

开源

  https://github.com/flyinskyin2013/LinuxKernel-src0.12

  https://gitee.com/sky-X/LinuxKernel-src0.12 (镜像)

后记

  为啥想要在ubuntu1804环境下弄这个东西呢?一方面是想学习一下,通过踩坑的方式加深自己的理解。另一方面还是太懒了,我只想在我的ubuntu1804上编译内核,不想安装其他虚拟机了,我的电脑太卡了(毕竟8年的电脑了QAQ)。

  经过了这一波调试,我对LinuxKernel有了更深的认知,我觉得很不错,如果以后有必要,我还可以分别对这些模块进行详细的查看,在这里,我只是简单的说明了init/main中的内容,其实,还有许多其他的内容是运行在背后的。比如system_call,sys_table等等内容。还有do_fork do_execve等等内容都是我在调试过程中踩过的坑。

  这里还是要说明,深入调试学习这个的原因还是想看看OS是怎么运行起来,虽然不能说已经100%的熟知,但是也可管中窥豹。

  注意,这个版本的内核和现代的2.0,4.0,5.0还缺了一些主要的知识,比如网络栈,VFS等。但是其他的一些内容,在现在的最新内核中,多多少少都能够看到这个版本的一些影子。这也是学习这个内核的原因之一。

打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

本站声明:网站内容来源于网络,如有侵权,请联系我们,我们将及时处理。

  • 分享:
评论
还没有评论
    发表评论 说点什么