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: 有问题请留言,看到后我会第一时间回复。

Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)的更多相关文章

  1. Android HAL层与Linux Kernel层驱动开发简介

    近日稍微对Android中的驱动开发做了一些简要的了解. HAL:Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件厂商为了保护自己硬 ...

  2. linux kernel 0.11 head

    head的作用 注意:bootsect和setup汇编采用intel的汇编风格,而在head中,此时已经进入32位保护模式,汇编的采用的AT&T的汇编语言,编译器当然也就变成对应的编译和连接器 ...

  3. linux 下mysql的启动 、调试、排错

    Linux 下 MySQL 启动与关闭 说明 一.启动 1.1  MySQL 进程 可以用ps 命令查看进程: [root@rac2 ~]# ps -ef|grep mysql root     21 ...

  4. linux kernel 0.11 bootsect

    bootsect作用 ①将自己移动到0x90000处 ②将setup从磁盘读到0x90200处 ③将system从磁盘读到0x10000处 寄存器 汇编代码中存在:数据段data seg 栈段 sta ...

  5. linux kernel 0.11 setup

    setup作用 ①读取参数放在0x90000处. ②将原本在0x10000处的system模块移至0x00000处 ③加载中断描述符表,全局描述符表,进入32位保护模式. 概念 关于实模式和保护模式区 ...

  6. 摩托罗拉SE4500 三星 S3C6410 Wince6.0平台软解码调试记录以及驱动相关问题解释

    虽然S3C6410出来很多年了,甚至于已经停产了,出货的几乎都有依赖于库存,SE4500也出来很多年了,但是网上依旧不会有调试资料帮助你,一切源于自私.希望本文能帮到你,不必感谢.本文来自C.S.D. ...

  7. windows安装SUSE Linux Enterprise Server 12

    一:打开“开发人员模式” 点击开始菜单按钮,选择“设置” 在设置中选择“更新和安全” 在菜单中选择“针对开发人员”,在三个选项中,选中“开发人员模式” 在弹出的警告框中点击“是” 这样开发人员模式就打 ...

  8. Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞

    漏洞名称: Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-423 发布时间: 2013-11-29 更新时间: 201 ...

  9. Linux kernel ‘aac_send_raw_srb’函数输入验证漏洞

    漏洞名称: Linux kernel ‘aac_send_raw_srb’函数输入验证漏洞 CNNVD编号: CNNVD-201311-422 发布时间: 2013-11-29 更新时间: 2013- ...

随机推荐

  1. ssh原理及加密传输

    1.ssh??(保证过程中是加密的,即安全的)ssh 是 Secure Shell 的缩写,是一个建立在应用层上的安全远程管理协议.ssh 是目前较为可靠的传输协议,专为远程登录会话和其他网络服务提供 ...

  2. UVA-10815 Andy's First Dictionary (非原创)

    10815 - Andy's First Dictionary Time limit: 3.000 seconds Problem B: Andy's First DictionaryTime lim ...

  3. codepen 上25个最受欢迎的HTML/CSS代码

    Codepen是一个非常了不起的网站,优设哥在设计师网址导航上也大力推荐过,得到了很多同学的喜爱,也是全球web前端开发人员的圣地! 我搜索了一些时下最好最流行的codepen(仅限于HTML和CSS ...

  4. React Security Best Practices All In One

    React Security Best Practices All In One Default XSS Protection with Data Binding Dangerous URLs Ren ...

  5. shit LeetCode interview Question

    shit LeetCode interview Question https://leetcode.com/interview/1/ 有点晕,啥意思,没太明白,到底是要按什么排序呀? 去掉 标识符 不 ...

  6. STAR 法则

    STAR 法则 STAR: Situation, Task, Action, Result 一. 什么是 STAR 法则? STAR法则是情境(situation).任务(task).行动(actio ...

  7. TCP 协议三次握手过程分析

    TCP 协议三次握手过程分析 TCP(Transmission Control Protocol) 传输控制协议 TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接: ...

  8. how to disabled prefers-color-scheme in js & dark theme

    how to disabled prefers-color-scheme in js dark theme https://developer.mozilla.org/en-US/docs/Web/C ...

  9. git & Angular git commit 规范

    git & Angular git commit 规范 https://github.com/angular/angular/commits/master https://github.com ...

  10. css useful skills blogs

    css useful skills blogs https://caniuse.com/ https://css-tricks.com https://css-tricks.com/almanac/p ...