前言
上一篇文章我们来一起简单分析了bochs的物理内存初始化这一过程,现在引出一个问题:我们编写的代码是如何被bochs读取并映射到物理内存中的呢。要回答这个问题,我们必须来分析bochs中的软盘编写过程。
Bochs的设备链概念
Bochs的各种设备初始化是由一个"PLUG_load_plugin(xx,xx)"宏来完成的,当物理内存初始化完成,其调用一个宏“DEV_init_devices()”,该函数就是来初始化各种设备。函数中有一个是初始化软盘的,我们先不管其他的,仅针对它来分析
PLUG_load_plugin(floppy, PLUGTYPE_CORE);
这里先给出函数的调用堆栈,免得忘记自己走过的路(这里先不画图了)。
> bochs.exe!bx_devices_c::init(BX_MEM_C * newmem) Line 221 C++
bochs.exe!bx_init_hardware() Line 1378 C++
bochs.exe!bx_begin_simulation(int argc, char * * argv) Line 1027 C++
bochs.exe!bx_real_sim_c::begin_simulation(int argc, char * * argv) Line 907 C++
bochs.exe!win32_ci_callback(void * userdata, ci_command_t command) Line 679 C++
bochs.exe!bx_real_sim_c::configuration_interface(....)
bochs.exe!bxmain() Line 341 C++
bochs.exe!main(int argc, char * * argv) Line 557 C++
初始化软盘设备的函数如下,可以看到其操作比较简单,先是创建了一个软盘设备对象"theFloppyController",然后将设备对象赋值给"bx_devices.pluginFloppyDevice",我们很容易推测出"bx_devices"是管理bochs的全部设备的。然后调用一个函数,该函数通过函数名可以知道是"注册设备模型”。(这里我其实拿不准,在Windows内核中有设备链这一概念,比如我们想拦截Windows中的键盘消息,我们应该先通过设备链来搜索出键盘这个设备对象模型,然后对其IRP消息进行过滤拦截。我们用这种模型来尝试套在bochs上。)
int CDECL libfloppy_LTX_plugin_init(plugin_t *plugin, plugintype_t type)
{
if (type == PLUGTYPE_CORE) {
theFloppyController = new bx_floppy_ctrl_c();
bx_devices.pluginFloppyDevice = theFloppyController;
BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theFloppyController, BX_PLUGIN_FLOPPY);
return 0; // Success
} else {
return -1;
}
}
void pluginRegisterDeviceDevmodel(...)函数分析
该函数比较少,很明显就是链表操作,我们直接画出数据结构图,代码就不必分析了。
typedef struct _device_t
{
const char *name; // 设备名称
plugin_t *plugin; // 插件? 此时为NULL,不需要分析
plugintype_t plugtype; // 类型,由该值决定不同的链表
class bx_devmodel_c *devmodel; // 设备模型
struct _device_t *next; // 链表中的下一个设备
} device_t;

这里还是提一句吧,其中有一个指针的指针,这个如果第一次读并且指针学的不太好的话可能让你感到困惑。其实这很好理解,其中core_devices是一个指针,代表着核心设备的链表头部,我们想要生成一个变量来使用这个指针,当然需要生成指针的指针。
void pluginRegisterDeviceDevmodel(plugin_t *plugin, plugintype_t type, bx_devmodel_c *devmodel, const char *name)
{
device_t **devlist;
device_t *device = new device_t;
...
switch (type) {
case PLUGTYPE_CORE:
case PLUGTYPE_VGA:
devlist = &core_devices; // 来获取 core_devices
break;
.....
....
}
if (!*devlist) {
/* Empty list, this become the first entry. */
*devlist = device; // 如果 core_devices 为null,则 floppy 作为第一个设备.
} else {
/* Non-empty list. Add to end. */
device_t *temp = *devlist;
while (temp->next)
temp = temp->next;
temp->next = device;
}
}
软盘的初始化
之前我们分析了软盘作为设备是如何注册到设备链中的,我们继续往下分析。在“void bx_init_hardware()” 函数中,其紧接着调用了"bx_init_plugins()"这个函数,该函数是遍历设备链表,依次初始化里面的设备,对于软盘的是调用的“void bx_floppy_ctrl_c::init(void)”函数。
里面内容较多,我们耐心往下分析就好。
DMA的初始化
首先,其调用 “DEV_dma_register_8bit_channel(2, dma_read, dma_write, "Floppy Drive");” 这个函数,是注册DMA,在DMA组件中将floppy注册上去,让DMA得知floppy的存在。
unsigned bx_dma_c::registerDMA8Channel(unsigned channel,
Bit16u (* dmaRead)(Bit8u *data_byte, Bit16u maxlen),
Bit16u (* dmaWrite)(Bit8u *data_byte, Bit16u maxlen),
const char *name)
{
if (channel > 3) {
BX_PANIC(("registerDMA8Channel: invalid channel number(%u).", channel));
return 0; // Fail
}
if (BX_DMA_THIS s[0].chan[channel].used) {
BX_PANIC(("registerDMA8Channel: channel(%u) already in use.", channel));
return 0; // Fail
}
BX_INFO(("channel %u used by %s", channel, name));
/// 这里对用的floppy的dmaRead 与 dmaWrite
BX_DMA_THIS h[channel].dmaRead8 = dmaRead;
BX_DMA_THIS h[channel].dmaWrite8 = dmaWrite;
/// 表示DMA这个通道被占用
BX_DMA_THIS s[0].chan[channel].used = 1;
return 1; // OK
}
关于DMA,在写这篇文章之前我也不太了解,就在网上查阅了相关信息。在我理解,DMA是CPU与外设的中间一层,举个例子:硬盘中的数据要想拷贝到物理内存中,其必须要经过CPU控制的总线,这个过程CPU不能做别的,不同外设IO速度的差异也挺大的。此时,DMA出现了,相当于CPU的一个助理,之后这种工作CPU就交由DMA来全权负责,为了让DMA有能力办成这事情,CPU把总线的控制权交由DMA,与此对应的,DMA完成任务之后就会立刻给CPU相应

Bochs对此的模拟应该相当粗暴,其数据读取不用什么总线这些,其分配了四个channel,每个channel可以负责一个设备的IO操作,其指向该设备对应的读写函数,再后续的分析代码中我们应该再会看到DMA这个东东。
注册IRQ
当注册完DMA时,紧接着调用代码"DEV_register_irq(6, "Floppy Drive");"来注册IRQ,IRQ中断相应等级应该在CPU那部分章节看到。我们来分析这个代码,发现其存在一个字符串数组irq_handler_name,里面存储着对应的中断名称,除了这个其余什么也没做,因此就不过多分析了。

注册IO端口
在注册完IRQ之后,Bochs紧接着为floppy注册IO读写端口,代码如下:
for (unsigned addr=0x03F2; addr<=0x03F7; addr++) {
DEV_register_ioread_handler(this, read_handler, addr, "Floppy Drive", 1);
DEV_register_iowrite_handler(this, write_handler, addr, "Floppy Drive", 1);
}
"DEV_register_ioread_handler(...)"函数的实现代码非常简单,bochs对于io端口使用一个名为“io_handler_struct”的结构体。然后bochs维护了一个0x10000大小的 "io_handler_struct"链表,每个端口号都对应着一个 io_hadner_struct,代码如下:
class BOCHSAPI bx_devices_c
{
...
private:
struct io_handler_struct {
struct io_handler_struct *next;
struct io_handler_struct *prev;
void *funct; // C++ type checking is great, but annoying
void *this_ptr;
char *handler_name; // name of device
int usage_count;
Bit8u mask; // io_len mask
};
struct io_handler_struct io_read_handlers;
struct io_handler_struct io_write_handlers;
#define PORTS 0x10000
struct io_handler_struct **read_port_to_handler;
struct io_handler_struct **write_port_to_handler;
...
char *irq_handler_name[BX_MAX_IRQS]; // 注册irq时用到的
...
}
其整体结构如下图所示,这里右边构成一个链表,我们之后就可以通过这个链表快速遍历存在的io_handler_stuct,如果通过左边那张表来遍历,则一次的时间复杂度为0x1000,这显然不合适的。

evaluate_media(...)函数分析
我们继续在函数“bx_floppy_ctrl_c::init(void)”往下分析,其紧接着就是一个"evaluate_media()"函数。
首先,该函数打开软盘文件,还记得我们在bochs的配置文件中初始化,其就是在这里被打开的,函数如下。

之后,其来配置软盘的信息,磁道数等信息。其代码如下面所示。从中我们可以获取信息,1.44M这里的M是1000,000大小,经计算 1.44 * 1000,000 / 2880 = 500(0x1F4),每个扇区中可以存储500个字节。
default: // 1.44M 3.5"
media->type = type;
if (stat_buf.st_size <= 1474560) {
media->tracks = floppy_type[type_idx].trk;
media->heads = floppy_type[type_idx].hd;
media->sectors_per_track = floppy_type[type_idx].spt;
}
static floppy_type_t floppy_type[8] = {
{BX_FLOPPY_160K, 40, 1, 8, 320, 0x03},
{BX_FLOPPY_180K, 40, 1, 9, 360, 0x03},
{BX_FLOPPY_320K, 40, 2, 8, 640, 0x03},
{BX_FLOPPY_360K, 40, 2, 9, 720, 0x03},
{BX_FLOPPY_720K, 80, 2, 9, 1440, 0x1f},
{BX_FLOPPY_1_2, 80, 2, 15, 2400, 0x02},
{BX_FLOPPY_1_44, 80, 2, 18, 2880, 0x18},
{BX_FLOPPY_2_88, 80, 2, 36, 5760, 0x10}
};
typedef struct {
unsigned id;
Bit8u trk;
Bit8u hd;
Bit8u spt;
unsigned sectors;
Bit8u drive_mask;
} floppy_type_t;
下面代码是设置启动顺序与CMOS,关于CMOS我也不太了解,通过查阅资源发现其关于BIOS有关,我们尝试查阅bochs的用户手册,发现了以下信息:
4.3.17. cmosimage
Example:
cmosimage: file=cmos.img, rtc_init=time0This defines a binary image file with size 128 bytes that can be loaded into the CMOS RAM at startup. The rtc_init parameter controls whether initialize the RTC with values stored in the image. By default the time0 argument given to the clock option is used. With 'rtc_init=image' the image is the source for the initial time.
其启动界面也出现了下面两行信息,可以看出启动顺序只是启动floppy。
00000000000i[FLOPPY] Using boot sequence floppy, none, none
00000000000i[FLOPPY] Floppy boot signature check is enabled
我们继续Google一下CMOS的有关信息,下面看到了一个比较详细的解答。简单来说CMOS是一个RAM芯片,里面存储着一些关于电脑组件的信息以及它们的相关设置,Bios代码在Bios芯片上读取来自CMOS的信息当电脑开始启动时。(我们先理解到这里吧,等之后遇到详情再来分析,现在这些描述太抽象了,我们无法直观看到有关信息)
The BIOS is a ROM chip on the motherboard which contains code that tells the CPU how to interact and control the other components in the computer.
The CMOS is also a chip on the motherboard but is a RAM chip with volatile memory which stores information about the computer components and their settings.
RAM chips lose the info stored in them when the computer is turned off so this is where the CMOS battery comes in to it to keep power to the CMOS chip so as to keep the settings.
The BIOS code on the BIOS chip reads information from the CMOS chip when the computer starts during the boot up process.
Hope this helps.
之后其配置一些bochs运行时的函数,还是那句话,等用到了我们再来进行分析,我们现在也不知道其是做什么的。
// runtime parameters
for (i = 0; i < 2; i++) {
sprintf(pname, "floppy.%d", i);
bx_list_c *floppy = (bx_list_c*)SIM->get_param(pname);
SIM->get_param_string("path", floppy)->set_handler(floppy_param_string_handler);
SIM->get_param_string("path", floppy)->set_runtime_param(1);
SIM->get_param_bool("readonly", floppy)->set_handler(floppy_param_handler);
SIM->get_param_bool("readonly", floppy)->set_runtime_param(1);
SIM->get_param_enum("status", floppy)->set_handler(floppy_param_handler);
SIM->get_param_enum("status", floppy)->set_runtime_param(1);
}
总结
这篇文章我们来梳理了bochs中软盘的加载过程,DMA端口注册,IRQ注册,IO端口,打开磁盘中的软盘映像(a.img),配置软盘参数以及后面的CMOS与运行时参数配置。 遗憾的是,我们似乎还是没有能分析到其如何将软盘中的二进制文件映射到物理内存然后运行的,别急,估计下一章很快就可以看到。

688



被折叠的 条评论
为什么被折叠?



