操作系统Operating System作业-01

目录

简述Linux系统启动过程

以下,总体说明 Linux 操作系统启动部分的主要执行流程。当 PC 的电源打开后,80x86 结构的 CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这个地址通常是 ROM-BIOS 中的 地址。PC 机的 BIOS 将执行系统的某些硬件检测和诊断功能,并在物理地址 0 处开始设置和初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512 字节)读入内存绝对地址 0x7C00 处,并跳转到这个地方开始引导启动机器运行了。启动设备通常是软驱或是硬盘。这里的叙述非常简单,但这已够理解内核初始化工作开始的过程了。 Linux 的最前面部分是用 8086 汇编语言编写的(boot/bootsect.S),并保存在引导设备的第一个扇区 中。它将由 BIOS 读入到内存绝对地址 0x7C00(31KB)处。当它被执行时就会把自己移动到内存绝对 地址 0x90000(576KB)处,并把启动设备盘中后 2KB 字节代码(boot/setup.S)读入到内存 0x90200 处。 而内核的其他部分(system 模块)则被读入到从内存地址 0x10000(64KB)开始处。因此从机器加电开始顺序执行的程序如下图所示。
在这里插入图片描述

因为当时 system 模块的长度不会超过 0x80000 字节大小(即 512KB),所以 bootsect 程序把 system 模块读入物理地址 0x10000 开始位置处时并不会覆盖在 0x90000(576KB)处开始的 bootsect 和 setup 模 块上。后面 setup 程序将会把 system 模块移动到物理内存起始位置处,这样 system 模块中代码的地址也 即等于实际的物理地址,便于对内核代码和数据进行操作。图 6-2 清晰地显示出 Linux系统启动时这几个程序或模块在内存中的动态位置。图中,每一竖条框代表某一时刻内存中各程序的映像位置图。在系 统加载期间将显示信息"Loading…"。然后控制权将传递给 boot/setup.S 中的代码,这是另一个实模式汇编语言程序。
在这里插入图片描述

启动部分识别主机的某些特性以及 VGA 卡的类型。如果需要,它会要求用户为控制台选择显示模式。然后将整个系统从地址 0x10000 移至 0x0000 处,进入保护模式并跳转至系统的余下部分(在 0x0000 处)。到此时,所有 32 位运行方式的启动设置均已被完成: IDT、GDT 以及 LDT 被加载,处理器和协处理器也已确认,分页工作也设置好了,最终会调用执行 init/main.c 中的 main()代码。上述操作的源代码是在 boot/head.s 中的,这可能是整个内核中最有诀窍的代码了。注意如果在前述任何一步中出了错,计算机就会死锁。在操作系统还没有完全运转之前是处理不了出错的。

bootsect 的代码之所以不把系统模块直接加载到物理地址 0x0000 开始处而要在 setup 程序中再进行移动,是因为随后执行的 setup 开始部分的代码还需要利用 ROM BIOS 提供的中断调用功能来获取有关机器配置的一些参数(例如显示卡模式、硬盘参数表等)。而当 BIOS 初始化时会在物理内存开始处放置一个大小为 0x400 字节(1KB)的中断向量表,直接把系统模块放在物理内存开始处将导致该中断向量表被覆盖掉。因此引导程序需要在使用完 BIOS 的中断调用后才能将这个区域覆盖掉。另外,仅在内存中加载了上述内核代码模块还不足以让 Linux 系统运行起来。作为完整可运行的 Linux 系统还需要有一个基本的文件系统支持,即根文件系统(Root file-system)。Linux 0.12 内核仅支持 MINIX 的 1.0 文件系统。根文件系统通常存在于另一个软盘上或者在一个硬盘分区中。为了通知内核所 需要的根文件系统在什么地方,bootsect.S 程序第 44 行上给出了根文件系统所在的默认块设备号 ROOT_DEV。块设备号的含义请参见程序中的注释。在内核初始化时会使用编译内核时放在引导扇区第 509、510(0x1fc–0x1fd)字节中的指定设备号。bootsect.S 程序第 45 行上给出了交换设备号 SWAP_DEV,它指出用作虚拟存储交换空间的外部设备号。
参考资料Linux内核完全注释 作者:赵炯

Printf函数多参数输入分析

在C/C++中,对函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了,下面给出printf("%d,%d",a,b);(其中a、b都是int型的)的汇编代码.

.section
.data
string out = "%d,%d"
push b  //最后的先压入栈中
push a //最先的后压入栈中
push $out//参数控制的那个字符串常量是最后被压入的
call printf

会看到,参数是最后的先压入栈中,最先的后压入栈中,参数控制的那个字符串常量是最后被压入的,所以这个常量总是能被找到的。
通常情况下函数可变参数表的长度是已知的,通过num参数传入,这种函数比较容易实现。而printf函数的实现非常复杂因为
1)可变参数的个数不能轻易的得到
2)而可变参数的类型也不是固定的,需由格式字符串进行识别(由%f、%d、%s等确定)
在这个函数中,需通过对传入的格式字符串(首地址为lpStr)进行识别来获知可变参数个数及各个可变参数的类型,具体实现体现在for循环中。譬如,在识别为%d后,做的是va_arg ( vap, int ),而获知为%l和%lf后则进行的是va_arg ( vap, long )、va_arg ( vap, double )。格式字符串识别完成后,可变参数也就处理完了。

附录

  • boot/目录下的三个汇编语言文件
    这三个文件虽然都是汇编程序,但却使用了两种不同语法格式。bootsect.S 和 setup.S 是实模式下运行的 16 位代码程序,采用近似于 Intel 的汇编语言语法,并且需要使用 8086 汇编编译器和连接器 as86 和 ld86。 而 head.s 则使用一种 AT&T 的汇编语法格式,并且运行在保护模式下,需要用 GNU 的 as(gas)汇编器进行编译。

    1)bootsect.S 程序:bootsect.S 代码是磁盘引导块程序,驻留在磁盘的第一个扇区中(引导扇区,0 磁道(柱面),0 磁头, 第 1 个扇区)。在 PC 机加电、ROM BIOS 自检后,ROM BIOS 会把引导扇区代码 bootsect 加载到内存地 址 0x7C00 开始处并执行之。在 bootsect 代码执行期间,它会将自己移动到内存绝对地址 0x90000 开始处 并继续执行。该程序的主要作用是首先把从磁盘第 2 个扇区开始的 4 个扇区的 setup 模块(由 setup.s 编译而成)加载到内存紧接着 bootsect 后面位置处(0x90200),然后利用 BIOS 中断 0x13 取磁盘参数表中 当前启动引导盘的参数,接着在屏幕上显示“Loading system…”字符串。再者把磁盘上 setup 模块后面的 system 模块加载到内存 0x10000 开始的地方。随后确定根文件系统的设备号。若没有指定,则根据所保存的引导盘的每磁道扇区数判别出盘的类型和种类,并保存其设备号于 root_dev (引导块的 508 地址处)中。最后长跳转到 setup 程序开始处(0x90200)去执行 setup 程序。
    2)setup.S 程序:setup.S 是一个操作系统加载程序,它的主要作用是利用 ROM BIOS 中断读取机器系统数据,并将这 些数据保存到 0x90000 开始的位置(覆盖掉了 bootsect 程序所在的地方)。这些参数将被内核中相关程序使用,例如字符设备驱动程序集中的 console.c 和 tty_io.c 程序等。然后 setup 程序将 system 模块从 0x10000-0x8ffff 整块向下移动到内存绝对地址 0x00000 处(当时认 为内核系统模块 system 的长度不会超过此值:512KB)。接下来加载中断描述符表寄存器(IDTR)和全局 描述符表寄存器(GDTR),开启 A20 地址线,重新设置两个中断控制芯片 8259A,将硬件中断号重新设 置为 0x20 - 0x2f。最后设置 CPU 的控制寄存器 CR0(也称机器状态字),进入 32 位保护模式运行,并跳 转到位于 system 模块最前面部分的 head.s 程序继续运行。
    3)head.s 程序:head.s 程序在被编译生成目标文件后会与内核其他程序的目标文件一起被链接成 system 模块,并位 于 system 模块的最前面开始部分。这也就是为什么称其为头部(head)程序的原因。system 模块将被放置 在磁盘上 setup 模块之后开始的扇区中,即从磁盘上第 6 个扇区开始放置。一般情况下 Linux 0.12 内核的 6.4 head.s 程序 249 system 模块大约有 120KB 左右,因此在磁盘上大约占 240 个扇区。 从这里开始,内核完全都是在保护模式下运行了。heads.s 汇编程序与前面汇编的语法格式不同,它 采用的是 AT&T 的汇编语言格式,并且需要使用 GNU 的 gas 和 gld7进行编译连接。因此请注意代码中赋 值的方向是从左到右。

  • printf的简单实现:

#include
 
void myitoa(int n, char str[], int radix)
{
int i , j , remain;
char tmp;
i = 0;
do
{
remain = n % radix;
if(remain > 9)
str[i] = remain  - 10 + 'A';
else
str[i] = remain + '0';
i++;
}while(n /= radix);
str[i] = '\0';
 
for(i-- , j = 0 ; j <= i ; j++ , i--)
{
tmp = str[j];
str[j] = str[i];
str[i] = tmp;
}
 
}
 
void myprintf(const char *format, ...)
{
char c, ch, str[30];
va_list ap;
 
va_start(ap, format);
while((c = *format))
{
switch(c)
{
case '%':
ch = *++format;
switch(ch)
{
case 'd':
{
int n = va_arg(ap, int);
myitoa(n, str, 10);
fputs(str, stdout);
break;
}
case 'x':
{
int n = va_arg(ap, int);
myitoa(n, str, 16);
fputs(str, stdout);
break;
}
case 'f':
{
double f = va_arg(ap, double);
int n;
n = f;
myitoa(n, str, 10);
fputs(str, stdout);
putchar('.');
n = (f - n) * 1000000;
myitoa(n, str, 10);
fputs(str, stdout);
break;
}
case 'c':
{
putchar(va_arg(ap, int));
break;
}
case 's':
{
char *p = va_arg(ap, char *);
fputs(p, stdout);
break;
}
case '%':
{
putchar('%');
break;
}
default:
{
fputs("format invalid!", stdout);
break;
}
}
break;
default:
putchar(c);
break;
}
format++;
}
va_end(ap);
}
 
int main(void)
{
myprintf("%d, %x, %f, %c, %s, %%,%a\n", 10, 15, 3.14, 'B', "hello");
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值