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驱动,详见以下两篇文章。
- 《Android匿名共享内存(Anonymous Shared Memory) --- 瞎折腾记录 (驱动程序篇)》 https://blog.csdn.net/u011728480/article/details/88420467
- 《linux kernel 中进程间描述符的传递方法及原理》 https://blog.csdn.net/u011728480/article/details/88553602
带着这样的想法其实已经很久了,由于现在的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)的更多相关文章
- Android HAL层与Linux Kernel层驱动开发简介
近日稍微对Android中的驱动开发做了一些简要的了解. HAL:Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件厂商为了保护自己硬 ...
- linux kernel 0.11 head
head的作用 注意:bootsect和setup汇编采用intel的汇编风格,而在head中,此时已经进入32位保护模式,汇编的采用的AT&T的汇编语言,编译器当然也就变成对应的编译和连接器 ...
- linux 下mysql的启动 、调试、排错
Linux 下 MySQL 启动与关闭 说明 一.启动 1.1 MySQL 进程 可以用ps 命令查看进程: [root@rac2 ~]# ps -ef|grep mysql root 21 ...
- linux kernel 0.11 bootsect
bootsect作用 ①将自己移动到0x90000处 ②将setup从磁盘读到0x90200处 ③将system从磁盘读到0x10000处 寄存器 汇编代码中存在:数据段data seg 栈段 sta ...
- linux kernel 0.11 setup
setup作用 ①读取参数放在0x90000处. ②将原本在0x10000处的system模块移至0x00000处 ③加载中断描述符表,全局描述符表,进入32位保护模式. 概念 关于实模式和保护模式区 ...
- 摩托罗拉SE4500 三星 S3C6410 Wince6.0平台软解码调试记录以及驱动相关问题解释
虽然S3C6410出来很多年了,甚至于已经停产了,出货的几乎都有依赖于库存,SE4500也出来很多年了,但是网上依旧不会有调试资料帮助你,一切源于自私.希望本文能帮到你,不必感谢.本文来自C.S.D. ...
- windows安装SUSE Linux Enterprise Server 12
一:打开“开发人员模式” 点击开始菜单按钮,选择“设置” 在设置中选择“更新和安全” 在菜单中选择“针对开发人员”,在三个选项中,选中“开发人员模式” 在弹出的警告框中点击“是” 这样开发人员模式就打 ...
- Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞
漏洞名称: Linux kernel ‘qeth_snmp_command’函数缓冲区溢出漏洞 CNNVD编号: CNNVD-201311-423 发布时间: 2013-11-29 更新时间: 201 ...
- Linux kernel ‘aac_send_raw_srb’函数输入验证漏洞
漏洞名称: Linux kernel ‘aac_send_raw_srb’函数输入验证漏洞 CNNVD编号: CNNVD-201311-422 发布时间: 2013-11-29 更新时间: 2013- ...
随机推荐
- HDU 3488-Tour KM
为什么可以这样拆点在 这道题 都已经证明过 代码: 1 //题目上面说了"The only exception is that the first and the last city sho ...
- Strongly connected HDU - 4635 原图中在保证它不是强连通图最多添加几条边
1 //题意: 2 //给你一个有向图,如果这个图是一个强连通图那就直接输出-1 3 //否则,你就要找出来你最多能添加多少条边,在保证添加边之后的图依然不是一个强连通图的前提下 4 //然后输出你最 ...
- Codeforces Round #650 (Div. 3) F1. Flying Sort (Easy Version) (离散化,贪心)
题意:有一组数,每次操作可以将某个数移到头部或者尾部,问最少操作多少次使得这组数非递减. 题解:先离散化将每个数映射为排序后所对应的位置,然后贪心,求最长连续子序列的长度,那么最少的操作次数一定为\( ...
- K8S(14)监控实战-grafana出图_alert告警
k8s监控实战-grafana出图_alert告警 目录 k8s监控实战-grafana出图_alert告警 1 使用炫酷的grafana出图 1.1 部署grafana 1.1.1 准备镜像 1.1 ...
- Operating System:管程相关概念
管程 (Moniters,也称为监视器)一.管程的概念是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源.这些共享资源一般是硬件设备或一群变量.管程实现了在一个时间点, ...
- 基于OpenCV全景拼接(Python)SIFT/SURF
一.实验内容: 利用sift算法,实现全景拼接算法,将给定的两幅图片拼接为一幅. 二.实验环境: 主机配置: CPU :intel core i5-7300 2.50GHZ RAM :8.0GB 运行 ...
- Front End Frameworks Trending 2021
Front End Frameworks Trending 2021 Front End Frameworks https://2019.stateofjs.com/front-end-framewo ...
- vue-cli emit webpack config
vue-cli emit webpack config 如何暴漏出 vue-cli 的 webpack 配置文件 refs xgqfrms 2012-2020 www.cnblogs.com 发布文章 ...
- how to use brew install gpg
how to use brew install gpg https://formulae.brew.sh/formula/gnupg $ brew install gnupg https://gith ...
- 10月份上线的NGK有什么不同之处?
近日,有小道消息传出公链项目NGK即将在10月上线的消息.各大社区纷纷开始布局,市场中关于NGK项目的消息也变得更多了起来.仅是社区热度这一点,对比之下就已经优于很多项目,那么是否还有其他优势呢?让我 ...