Grub有一个功能,在加载内核镜像后,可以再加载一些别的文件到内核镜像的后面(例如该版本里新增的initrd.img文件),只需在grub.cfg配置文件里添加一个module选项...

    v0.0.7的项目地址:

    github.com地址:https://github.com/zenglong/zenglOX (只包含git提交上来的源代码)
   
    Dropbox地址:点此进入Dropbox网盘  (该版本位于zenglOX_v0.0.7的文件夹,该文件夹里的zip压缩包为源代码)

    sourceforge地址:https://sourceforge.net/projects/zenglox/files  (该版本位于zenglOX_v0.0.7的文件夹,该文件夹里的zip压缩包为源代码)

    有关zenglOX的编译及gdb调试的方法,请参考zenglOX v0.0.1里的文章。

    Grub有一个功能,在加载内核镜像后,可以再加载一些别的文件到内核镜像的后面(例如该版本里新增的initrd.img文件),只需在grub.cfg配置文件里再添加一个module选项:

menuentry "zenglOX" {
    multiboot /boot/zenglOX.bin
    module    /boot/initrd.img
}

    第一个multiboot对应的zenglOX.bin是我们的支持多启动标准的内核镜像文件,内核会将zenglOX.bin的代码拷贝到0x100000即1M的内存地址处,然后再将initrd.img文件加载到zenglOX.bin内核镜像的后面,initrd.img不会紧挨着内核镜像来加载,至于具体加载到哪个位置是由grub内部的算法来决定的,在下面会介绍如何找到initrd.img的内存位置的方法。

    这里需要注意的是:对于使用multiboot启动的内核镜像,必须用module选项来加载它的模块文件(Linux系统在grub.cfg里使用的是initrd选项,如果你在zenglOX系统里使用initrd选项来加载initrd.img文件,那么就会在内存里找不到该文件了,grub也会在启动时弹出一个错误信息)。

    initrd.img文件里存储着一个小型的文件系统,由于initrd.img里的整个文件系统会被加载到内存里,所以它是一个ram disk(内存磁盘镜像),该文件系统目前最多只能存储64个文件(所以它很小),当initrd.img被加载到内存里后,我们就可以从该文件系统里提取出所需的文件,比如,最常见的可以是一些系统安装程式等,当然目前版本里该文件系统以测试为目的,只加入了几个txt为后缀的文本文件,所以v0.0.7的版本只是简单的访问该文件系统,并从该文件系统里读取出txt文件的内容,再将内容显示到屏幕上。

    和initrd.img里的文件系统相关的结构体定义在zlox_initrd.h头文件里:

// zlox_initrd.h -- Defines the interface and structures relating to the initial ramdisk.

#ifndef _ZLOX_INITRD_H_
#define _ZLOX_INITRD_H_

#include "zlox_common.h"
#include "zlox_fs.h"

typedef struct _ZLOX_INITRD_HEADER
{
	ZLOX_UINT32 nfiles; // The number of files in the ramdisk.
}  ZLOX_INITRD_HEADER;

typedef struct _ZLOX_INITRD_FILE_HEADER
{
	ZLOX_UINT8 magic; // Magic number, for error checking.
	ZLOX_CHAR name[64]; // Filename.
	ZLOX_UINT32 offset; // Offset in the initrd that the file starts.
	ZLOX_UINT32 length; // Length of the file.
} ZLOX_INITRD_FILE_HEADER;

ZLOX_FS_NODE * zlox_initialise_initrd(ZLOX_UINT32 location);

#endif //_ZLOX_INITRD_H_


    上面定义的ZLOX_INITRD_HEADER和ZLOX_INITRD_FILE_HEADER结构体在initrd.img文件系统里的关系图如下:


图1

    开头的ZLOX_INITRD_HEADER里只有一个nfiles字段,该字段存储了当前文件系统里已有的文件的总数目,在该结构体的下面是每个文件的ZLOX_INITRD_FILE_HEADER结构体,该结构体里的第一个字段magic用于判断某个文件是否有效,第二个name字段用于存储对应的文件名,第三个offset字段则存储了对应文件的具体内容的起始偏移值,最后一个length字段则存储了对应文件的有效长度。

    在访问initrd.img里的文件系统时,使用的是VFS(虚拟文件系统)来进行间接访问的,VFS提供了一套统一的访问接口,和VFS相关的代码定义在zlox_fs.c文件里,和VFS相关的结构体则定义在对应的zlox_fs.h头文件里,在zlox_fs.h头文件里定义的最主要的结构体是ZLOX_FS_NODE结构体:

struct _ZLOX_FS_NODE;
typedef struct _ZLOX_FS_NODE ZLOX_FS_NODE;
typedef struct _ZLOX_DIRENT ZLOX_DIRENT;

typedef ZLOX_UINT32 (*ZLOX_READ_CALLBACK)(ZLOX_FS_NODE *,ZLOX_UINT32,ZLOX_UINT32,ZLOX_UINT8 *);
typedef ZLOX_UINT32 (*ZLOX_WRITE_CALLBACK)(ZLOX_FS_NODE *,ZLOX_UINT32,ZLOX_UINT32,ZLOX_UINT8 *);
typedef ZLOX_VOID (*ZLOX_OPEN_CALLBACK)(ZLOX_FS_NODE *);
typedef ZLOX_VOID (*ZLOX_CLOSE_CALLBACK)(ZLOX_FS_NODE *);
typedef ZLOX_DIRENT * (*ZLOX_READDIR_CALLBACK)(ZLOX_FS_NODE *,ZLOX_UINT32);
typedef ZLOX_FS_NODE * (*ZLOX_FINDDIR_CALLBACK)(ZLOX_FS_NODE *,ZLOX_CHAR *name);

struct _ZLOX_FS_NODE
{
	ZLOX_CHAR name[128]; // The filename.
	ZLOX_UINT32 mask; // The permissions mask.
	ZLOX_UINT32 uid; // The owning user.
	ZLOX_UINT32 gid; // The owning group.
	ZLOX_UINT32 flags; // Includes the node type. See #defines above.
	ZLOX_UINT32 inode; // This is device-specific - provides a way for a filesystem to identify files.
	ZLOX_UINT32 length; // Size of the file, in bytes.
	ZLOX_UINT32 impl; // An implementation-defined number.
	ZLOX_READ_CALLBACK read;
	ZLOX_WRITE_CALLBACK write;
	ZLOX_OPEN_CALLBACK open;
	ZLOX_CLOSE_CALLBACK close;
	ZLOX_READDIR_CALLBACK readdir;
	ZLOX_FINDDIR_CALLBACK finddir;
	ZLOX_FS_NODE * ptr; // Used by mountpoints and symlinks.
};

struct _ZLOX_DIRENT
{
    ZLOX_CHAR name[128]; // Filename.
    ZLOX_UINT32 ino; // Inode number.
};

extern ZLOX_FS_NODE * fs_root; // The root of the filesystem.


    这里的很多字段都暂时还用不到,这些字段大部分和POSIX标准有关,当然你不遵从这个标准也没关系,上面的ZLOX_FS_NODE里第一个name字段表示文件名,第二个mask字段可以存储一些读写执行之类的权限掩码,随后的uid,gid字段分别可以用来存储文件拥有者的用户ID和组ID信息,flags字段可以存储当前文件节点的类型,比如可以通过flags字段来判断某节点是一个普通的文件还是一个目录,inode字段用于存储文件节点的节点号(可以看作文件的唯一标识符),length字段记录文件的有效长度,impl字段暂时不管它,这里提到的mask,uid,gid,impl字段在当前版本里都没应用到。

    read,write,open,close字段都是函数指针,它们可以指向不同文件系统的具体操作函数,比如将initrd.img文件系统的读写操作函数的指针值赋值给read,write字段,那么在对这些文件节点进行读写操作时,就会转到initrd.img文件系统自定义的函数里了,如果你有很多的文件系统,可以根据需要,为不同的节点设置不同的函数指针值。

    readdir和finddir字段也是函数指针,不过这两个字段主要用于目录节点的读和查找操作,不同的文件系统可以设置不同的函数指针值。

    上面的头文件里,还有一个ZLOX_DIRENT结构体,该结构体主要用于readdir读目录操作时返回一些基本的文件名和节点号信息。

    VFS虚拟文件系统的接口函数主要定义在zlox_fs.c文件里:

// zlox_fs.c -- Defines the interface and structures relating to the virtual file system.

#include "zlox_fs.h"

ZLOX_FS_NODE *fs_root = 0; // The root of the filesystem.

ZLOX_UINT32 zlox_read_fs(ZLOX_FS_NODE *node, ZLOX_UINT32 offset, ZLOX_UINT32 size, ZLOX_UINT8 *buffer)
{
    // Has the node got a read callback?
    if (node->read != 0)
        return node->read(node, offset, size, buffer);
    else
        return 0;
}

ZLOX_DIRENT * zlox_readdir_fs(ZLOX_FS_NODE *node, ZLOX_UINT32 index)
{
    // Is the node a directory, and does it have a callback?
    if ( (node->flags & 0x7) == ZLOX_FS_DIRECTORY &&
         node->readdir != 0 )
        return node->readdir(node, index);
    else
        return 0;
}

ZLOX_FS_NODE * zlox_finddir_fs(ZLOX_FS_NODE *node, ZLOX_CHAR *name)
{
    // Is the node a directory, and does it have a callback?
    if ( (node->flags & 0x7) == ZLOX_FS_DIRECTORY &&
         node->finddir != 0 )
        return node->finddir(node, name);
    else
        return 0;
}


    上面暂时只定义了三个接口函数,例如第一个zlox_read_fs函数(读某文件节点的具体内容),内核代码里调用zlox_read_fs函数时,在该函数的内部会先判断参数node节点的read字段是否设置了函数指针值,如果设置了,则调用node->read指向的函数,该指向的函数通常为不同文件系统里具体的操作函数。zlox_readdir_fs和zlox_finddir_fs是同样的间接调用方式。

    要使用VFS来管理文件,就需要先找到对应的文件系统,然后为该文件系统里的每个文件和每个目录建立一个ZLOX_FS_NODE的节点信息,内核及用户程序会先通过VFS里的节点信息来查找某个文件,并通过节点里的一些权限掩码之类的字段判断当前用户是否具有可读写之类的权限,如果有权限,则调用read,write函数指针转向具体的文件系统里的操作函数去处理,所以VFS只是一个中间的平台,当然,当前的版本里还没建立权限判断机制,只是简单的将函数指针转向不同的操作函数。

    前面我们提到过,initrd.img会被加载到内核镜像后面的某个内存位置,该位置可以通过zlox_kernel_main内核主入口函数(位于zlox_kernel.c文件里)的mboot_ptr参数来找到:

//zenglOX kernel main entry
ZLOX_SINT32 zlox_kernel_main(ZLOX_MULTIBOOT * mboot_ptr)

    mboot_ptr指针指向的是一个ZLOX_MULTIBOOT的结构体,该结构体定义在zlox_multiboot.h头文件里:

/* zlox_multiboot.h -- Declares the multiboot info structure.*/

#ifndef _ZLOX_MULTIBOOT_H_
#define _ZLOX_MULTIBOOT_H_

struct _ZLOX_MULTIBOOT
{
	ZLOX_UINT32 flags;
	ZLOX_UINT32 mem_lower;
	ZLOX_UINT32 mem_upper;
	ZLOX_UINT32 boot_device;
	ZLOX_UINT32 cmdline;
	ZLOX_UINT32 mods_count;
	ZLOX_UINT32 mods_addr;
	ZLOX_UINT32 num;
	ZLOX_UINT32 size;
	ZLOX_UINT32 addr;
	ZLOX_UINT32 shndx;
	ZLOX_UINT32 mmap_length;
	ZLOX_UINT32 mmap_addr;
	ZLOX_UINT32 drives_length;
	ZLOX_UINT32 drives_addr;
	ZLOX_UINT32 config_table;
	ZLOX_UINT32 boot_loader_name;
	ZLOX_UINT32 apm_table;
	ZLOX_UINT32 vbe_control_info;
	ZLOX_UINT32 vbe_mode_info;
	ZLOX_UINT32 vbe_mode;
	ZLOX_UINT32 vbe_interface_seg;
	ZLOX_UINT32 vbe_interface_off;
	ZLOX_UINT32 vbe_interface_len;
}  __attribute__((packed));

typedef struct _ZLOX_MULTIBOOT ZLOX_MULTIBOOT;

#endif //_ZLOX_MULTIBOOT_H_


    上面各个字段的含义可以参考 http://www.uruk.org/orig-grub/boot-proposal.html 该链接里的文章,这篇文章就是v0.0.1版本的介绍篇里提到过的多启动头标准,在该篇文章的 Boot Information Format 部分就介绍了各个字段的含义:

	+-------------------+
0	| flags		    |	(required)
	+-------------------+
4	| mem_lower	    |	(present if flags[0] is set)
8	| mem_upper	    |	(present if flags[0] is set)
	+-------------------+
12	| boot_device	    |	(present if flags[1] is set)
	+-------------------+
16	| cmdline	    |	(present if flags[2] is set)
	+-------------------+
20	| mods_count	    |	(present if flags[3] is set)
24	| mods_addr	    |	(present if flags[3] is set)
	+-------------------+
28 - 40 | syms		    |   (present if flags[4] or flags[5] is set)
	+-------------------+
44	| mmap_length	    |	(present if flags[6] is set)
48	| mmap_addr	    |	(present if flags[6] is set)
	+-------------------+


    代码里主要用到的是mods_count和mods_addr字段,其中mods_count字段表示Grub加载的模块数(例如,该版本的grub.cfg文件里的module选项只加载了一个文件,所以mods_count值就会是1),mods_addr的值为第一个模块结构的物理内存地址,模块结构里有如下几个字段:

	+-------------------+
0	| mod_start	    |
4	| mod_end	    |
	+-------------------+
8	| string	    |
	+-------------------+
12	| reserved (0)	    |
	+-------------------+


    mod_start的值为当前加载的模块文件的起始物理内存地址,mod_end的值为模块文件的内存结束位置,string的值是和该模块文件相关的一个字符串的起始内存地址,类似于命令行参数之类的,当前版本并没用到也没设置string字段即当前版本的string指向的是一个空字符串。

    根据上面的介绍,我们就可以通过下图来找到initrd.img所加载到的内存地址:


图2

    所以在zlox_kernel.c文件的zlox_kernel_main主入口函数里就有如下代码:

extern ZLOX_UINT32 placement_address;

//zenglOX kernel main entry
ZLOX_SINT32 zlox_kernel_main(ZLOX_MULTIBOOT * mboot_ptr)
{
	ZLOX_UINT32 initrd_location;
	ZLOX_UINT32 initrd_end;

............................
............................

	ZLOX_ASSERT(mboot_ptr->mods_count > 0);
	initrd_location = *((ZLOX_UINT32*)mboot_ptr->mods_addr);
	initrd_end = *((ZLOX_UINT32*)(mboot_ptr->mods_addr+4));
	// grub会将initrd模块放置到kernel内核后面,所以将placement_address设置为initrd_end,
	// 这样zlox_init_paging分配页表时才能将initrd考虑进来,同时堆分配空间时就不会覆盖到initrd里的内容
	placement_address = initrd_end;

	// 开启分页,并创建堆
	zlox_init_paging();

	// Initialise the initial ramdisk, and set it as the filesystem root.
	fs_root = zlox_initialise_initrd(initrd_location);

............................
............................
}


    上面代码里通过initrd_location = *((ZLOX_UINT32*)mboot_ptr->mods_addr);从mods_addr指向的模块结构里得到第一个成员作为initrd.img的起始内存地址,并通过initrd_end = *((ZLOX_UINT32*)(mboot_ptr->mods_addr+4));将模块结构的第二个成员作为initrd.img的结束内存位置。

    另外,由于placement_address一开始在zlox_kheap.c文件里被设置为了_end的地址即zenglOX.bin内核镜像的结束位置,但是initrd.img被grub加载到了内核镜像的后面,如果不调整placement_address的值的话,zlox_kmalloc函数在分配内存时,placement_address就可能会覆盖掉内核后面的initrd.img里的内容,所以还必须将placement_address设置为initrd_end,让堆能从initrd.img后面进行内存分配工作。

    上面代码里还有一个zlox_initialise_initrd函数,该函数可以根据initrd_location即initrd.img的内存位置,为initrd.img里的所有文件分配VFS节点信息,zlox_initialise_initrd函数位于zlox_initrd.c文件里:

ZLOX_FS_NODE * zlox_initialise_initrd(ZLOX_UINT32 location)
{
	// Initialise the main and file header pointers and populate the root directory.
	initrd_header = (ZLOX_INITRD_HEADER *)location;
	file_headers = (ZLOX_INITRD_FILE_HEADER *) (location+sizeof(ZLOX_INITRD_HEADER));

	// Initialise the root directory.
	initrd_root = (ZLOX_FS_NODE *)zlox_kmalloc(sizeof(ZLOX_FS_NODE));
	zlox_strcpy(initrd_root->name, "initrd");
	initrd_root->mask = initrd_root->uid = initrd_root->gid = initrd_root->inode = initrd_root->length = 0;
	initrd_root->flags = ZLOX_FS_DIRECTORY;
	initrd_root->read = 0;
	initrd_root->write = 0;
	initrd_root->open = 0;
	initrd_root->close = 0;
	initrd_root->readdir = &zlox_initrd_readdir;
	initrd_root->finddir = &zlox_initrd_finddir;
	initrd_root->ptr = 0;
	initrd_root->impl = 0;

	// Initialise the /dev directory (required!)
	initrd_dev = (ZLOX_FS_NODE *)zlox_kmalloc(sizeof(ZLOX_FS_NODE));
	zlox_strcpy(initrd_dev->name, "dev");
	initrd_dev->mask = initrd_dev->uid = initrd_dev->gid = initrd_dev->inode = initrd_dev->length = 0;
	initrd_dev->flags = ZLOX_FS_DIRECTORY;
	initrd_dev->read = 0;
	initrd_dev->write = 0;
	initrd_dev->open = 0;
	initrd_dev->close = 0;
	initrd_dev->readdir = &zlox_initrd_readdir;
	initrd_dev->finddir = &zlox_initrd_finddir;
	initrd_dev->ptr = 0;
	initrd_dev->impl = 0;

	root_nodes = (ZLOX_FS_NODE *)zlox_kmalloc(sizeof(ZLOX_FS_NODE) * initrd_header->nfiles);
	nroot_nodes = initrd_header->nfiles;

	// For every file...
	ZLOX_UINT32 i;
	for (i = 0; i < initrd_header->nfiles; i++)
	{
		// Edit the file's header - currently it holds the file offset
		// relative to the start of the ramdisk. We want it relative to the start
		// of memory.
		file_headers[i].offset += location;
		// Create a new file node.
		zlox_strcpy(root_nodes[i].name, (const ZLOX_CHAR *)file_headers[i].name);
		root_nodes[i].mask = root_nodes[i].uid = root_nodes[i].gid = 0;
		root_nodes[i].length = file_headers[i].length;
		root_nodes[i].inode = i;
		root_nodes[i].flags = ZLOX_FS_FILE;
		root_nodes[i].read = &zlox_initrd_read;
		root_nodes[i].write = 0;
		root_nodes[i].readdir = 0;
		root_nodes[i].finddir = 0;
		root_nodes[i].open = 0;
		root_nodes[i].close = 0;
		root_nodes[i].impl = 0;
	}
	return initrd_root;
}


    上面代码先在堆里分配了一个名为"initrd"的目录类型的initrd_root根节点,接着分配了一个名为"dev"的initrd_dev目录节点,该节点在当前版本里并没有什么实际的意义,因为当前版本里的ram disk文件系统还不能处理子目录,所以initrd_dev目录节点主要用于以后版本里存储设备文件用的。最后通过for循环从initrd.img文件系统里为每个文件生成对应的VFS文件节点信息。

    当创建initrd_root之类的目录节点时,会将ZLOX_FS_NODE文件节点结构里的readdir和finddir的函数指针分别设置为zlox_initrd_readdir和zlox_initrd_finddir函数,这样在后面的内核代码里调用VFS的zlox_readdir_fs及zlox_finddir_fs接口函数时,就会分别转到zlox_initrd_readdir和zlox_initrd_finddir函数里去执行。

    当创建root_nodes数组里的普通文件节点时,则会将ZLOX_FS_NODE里的read函数指针设置为zlox_initrd_read操作函数,这样在调用VFS里的zlox_read_fs接口函数时,就会转到zlox_initrd_read函数里去执行。

    在使用zlox_initialise_initrd函数为initrd.img初始化完VFS文件节点信息后,zlox_kernel_main主入口函数就可以通过VFS提供的接口函数来访问文件或目录里的信息:

//zenglOX kernel main entry
ZLOX_SINT32 zlox_kernel_main(ZLOX_MULTIBOOT * mboot_ptr)
{

..............................
..............................

	// Initialise the initial ramdisk, and set it as the filesystem root.
	fs_root = zlox_initialise_initrd(initrd_location);

	// list the contents of /
	ZLOX_SINT32 i = 0;
	ZLOX_DIRENT *node = 0;
	while ( (node = zlox_readdir_fs(fs_root, i)) != 0)
	{
		zlox_monitor_write("Found file ");
		zlox_monitor_write(node->name);
		ZLOX_FS_NODE *fsnode = zlox_finddir_fs(fs_root, node->name);

		if ((fsnode->flags&0x7) == ZLOX_FS_DIRECTORY)
		{
			zlox_monitor_write("\n\t(directory)\n");
		}
		else
		{
			zlox_monitor_write("\n\t contents: \"");
			ZLOX_CHAR buf[256];
			ZLOX_UINT32 sz = zlox_read_fs(fsnode, 0, 256, (ZLOX_UINT8 *)buf);
			ZLOX_UINT32 j;
			for (j = 0; j < sz; j++)
				zlox_monitor_put(buf[j]);
			
			zlox_monitor_write("\"\n");
		}
		i++;
	}

	// Write out a sample string
	zlox_monitor_write("hello world!\nwelcome to zenglOX v0.0.7!\n");

..............................
..............................

}


    上面代码里循环通过zlox_readdir_fs接口函数从fs_root根目录节点里读取出每个文件节点的基本信息,然后根据文件节点的名称和zlox_finddir_fs接口函数,就可以得到对应的fsnode文件节点的指针,fsnode指向的ZLOX_FS_NODE结构体里存储了当前文件节点的所有相关信息,例如文件节点的类型,如果是ZLOX_FS_DIRECTORY即目录类型则输出显示"\n\t(directory)\n"的字符串,如果不是目录节点,就是普通的文件节点,则通过zlox_read_fs接口函数来读取出文件里的内容,并将内容显示到屏幕上。

    v0.0.7版本在bochs里的运行结果如下:


图3

    以上就是当前版本里涉及到的VFS和initrd相关的内容。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: zenglOX v0.0.8 Multitask(多任务)

上一篇: zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

相关文章

zenglOX v0.0.2 VGA输出显示字符串

zenglOX v1.1.0 通过ATAPI读取光盘里的数据

zenglOX v3.0.2 PNG(Portable Network Graphics) and BUG Fix

zenglOX v0.0.6 使用Heap(堆)动态分配和释放内存

zenglOX v2.2.0 ee(easy editor)文本编辑器 C标准库函数 uheap(单独的用户堆空间) atapi驱动BUG zenglfs文件系统BUG 分页BUG 堆算法BUG等修复

zenglOX v1.5.0 zenglfs文件系统, MBR主引导记录, fdisk分区工具及format磁盘格式化工具, file文件目录读写工具等