跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第二节 交换分区
 
 

        因为我们的学习的内容都是基于32位保护模式CPU的工作环境,所以CPU的32根内存寻址线最多只能识别和使用4G大小的物理内存。在今天看来4G的物理内存并不算什么,但对于32位架构的计算机系统来说已经是最大可用的了。我们只需要通过32位架构的计算机体系来学习它内部的工作原理,所以在32位寻址模式对于学习来说很有典型意义。我们为虚拟机分配的物理内存为512M大小,并预计为每一个任务分配4G大小的逻辑内存,也就是说每个任务在执行时可以访问4G大小的内存区域。如果有多个任务同时运行,它们都可以独立的使用4G大小的内存区域。

        在上一节我们学习了内存分页机制,每个任务的可用内存状态都存放在页目录和页表当中,事实上,每个任务都可以有独立的页目录和页表,也就是说每个程序都可以使用独立的逻辑内存。我们打算这样来实现:

        操作系统内核程序:

  1. 可识别并使用全部的物理内存512M。

  2. 访问的逻辑地址与物理地址一致。

        普通程序:

  1. 可拥有4G的逻辑内存。

  2. 0 ~ 512M内存与物理内存一致,前16M内存与内核程序共享。

  3. 16M ~ 4G内存中为程序分配pcb、栈、页目录所使用的页面。其它页面均不在内存中。

  4. 当出现缺失异常时再为程序分配当前所需页面。

        缺页异常处理流程如下:



 

        按照上述流程编写缺页异常的具体逻辑。首先来看一下0 ~ 16M物理内存的规划,系统内核程序所使用的物理内存为:[0, 1M);位图mmap所在的固定内存区域为 [0x100000, 0x200000);还要定义一个位图用来记录每一个内存页的使用者,这个位图叫作map_process,它所占内存空间为 [0x200000, 0x600000),其数组大小与MAP大小永远一致;内核页目录和页表是一次性分配的,内核可以使用全部物理内存页,页目录开始于 [0x600000, 0x601000) ,大小为1024个(每个4字节)共4096字节,页表开始于 [0x601000, 0xa01000) ,大小为4M;另外在boot.h中修改了系统内核所使用的栈地址为#define _SEG_KERNEL_DATA_OFFSET 0x1000000也就是16M以下的物理内存均被使用且不可以被换出到外存。前16M物理内存的分配图如下:



 

        定义一些宏,用来指定一此固定数值:

//内存总大小以M为单位
#define MEM_SIZE                        (512)
#define MEM_SIZE_LOGIC                  (4096)
//位图状态标识
#define MM_SWAP_TYPE_CAN                (1)
#define MM_SWAP_TYPE_NO                 (0)
//未使用
#define MM_FREE                          (0x0 << 0)
//已使用
#define MM_USED                          (0x1 << 0)
//动态分配
#define MM_NO_SWAP                       (0x0 << 1)
#define MM_CAN_SWAP                      (0x1 << 1)
//内存页大小4096B
#define MM_PAGE_SIZE                     (4 * 1024)
//内存总页数
#define MAP_SIZE                         (0x20000)        //(MEM_SIZE * 1024 * 1024 / MM_PAGE_SIZE)
#define MAP_SIZE_LOGIC                   (0x100000)        //(MEM_SIZE_LOGIC * 1024 * 1024 / MM_PAGE_SIZE)
//内核程序大小256页 256*4096 = 1MB
#define KERNEL_SIZE                       (0x100)
/*
 * MMAP所在内存地址为 [0x100000, 0x200000)
 * MAP使用情况内存地址为 [0x200000, 0x600000)
 * Kernel所在页目录及页表地址 [0x600000, 0xa01000)
 * 从0x1000000以下为0x1000个内存页
 */
#define MMAP_USED_SIZE                    (0x1000)
//map起始地址
#define MMAP                              (0x100000)
//map_process起始地址
#define MMAP_PRO                          (0x200000)

        修改install_alloc中已使用的内存为0x1000个内存页(前16M):

//安装申请内存位图        
void install_alloc()
{
        //位图所在的固定内存区域为 [0x100000, 0x200000)
        mmap = (u8 *) MMAP;
        for (u32 i = 0; i < MAP_SIZE_LOGIC; i++)
        {
                //16M以下均为已使用
                if (i < MMAP_USED_SIZE)
                {
                        //设定内核所占用的1MB内存为已使用
                        mmap[i] = (MM_USED | MM_NO_SWAP);
                }
                //剩下的内存为未使用
                else
                {
                        mmap[i] = (MM_FREE | MM_CAN_SWAP);
                }
        }
}

       初始化内存页的使用者,初始时将所有内存页的使用者设置为0,也就是默认的内核程序。

void install_used_map()
{
        map_process = (u32 *) MMAP_PRO;
        /*
         * 初始化map_process,因为map和map_process的大小永远都是一样的所以用MAP_SIZE
         * map_process所占内存空间为 [0x200000, 0x600000)
         */
        for (u32 i = 0; i < MAP_SIZE_LOGIC; i++)
        {
                map_process[i] = 0;
        }
}

        定义几个用来存取mmap和map_process状态函数:

u8 mmap_status(u32 page_no)
{
        return mmap[page_no];
}
u32 map_process_id(u32 page_no)
{
        return map_process[page_no];
}
void set_mmap_status(u32 page_no, u8 status)
{
        mmap[page_no] = status;
}
void set_map_process_id(u32 page_no, u32 pid)
{
        map_process[page_no] = pid;
}

         在int.S中处理缺页异常,在处理缺页异常时首先需要弹出因缺页错误而被压栈的内容,程序运行时使用%eax作为变量赋值的临时存放区,这时如果采用popl %eax的方式来处理,当此中断执行完毕iret执行之后%eax的值并没有被还原为原来的值,所以程序不正确。所以要使用popl %edx来使用%edx寄存器来存放缺页错误码:

//页错误
_int_0x0e:
        //出栈,取得缺页错误码
        popl        %edx
        pushal
        pushfl
        cli
        //C函数中参数和变量所用的栈
        push        %ebp
        mov         %esp, %ebp
        sub         $0x28, %esp
        movl        %edx, (%esp)
        //调用C函数来处理这个异常
        call        int_page_error
        leave
        sti
        popfl
        popal
        iret

        在int_page_error中调用page.c中的page_error函数来处理缺页异常,并传入当前缺页程序号和缺页错误码:

//页错误
void int_page_error(u32 error_code)
{
        set_ds(GDT_INDEX_KERNEL_DS);
        page_error(pcb_current->pid, error_code);
        set_ds(0xf);
}

         缺页处理的过程比较复杂,按照本节开始时画的流程图,来一步步的处理:

//取得页面错误地址
u32 v_cr3 = cr3();
set_cr3(PAGE_DIR);
u32 error_addr = cr2();
//页号
u32 page_no = error_addr / 0x1000;
//面目录
u32 *page_dir = (u32 *) v_cr3;
//页目录索引
u32 page_dir_index = (error_addr >> 22) & 0x3ff;
//页表索引
u32 page_table_index = (error_addr >> 12) & 0x3ff;
//页表
u32 *tbl = NULL;
//如果页表尚未分配则申请一个页表
if ((page_dir[page_dir_index] & 0x1) == 0)
{
        //申请一个普通可用页面
        tbl = alloc_page(pid, 1, 0);
        //如果申请失败
        if (tbl == NULL)
        {
                //申请一个可用的物理页面
                u32 ph_page_no = alloc_page_ph(pid);
                //如果申请成功
                if (ph_page_no != 0)
                {
                        tbl = (u32 *) (ph_page_no * 0x1000);
                }
                //如果申请失败
                else
                {
                        printf("Segmentation fault.\n");
                        hlt();
                }
        }
        //如果申请失败
        else
        {
                printf("Segmentation fault.\n");
                hlt();
        }
        //处理新分配的页表所表示的页面均不在内存中
        for (int i = 0; i < 1024; i++)
        {
                tbl[i] = 6;
        }
        //设置页目录
        page_dir[page_dir_index] = (u32) tbl | 7;
}
//如果页表已分配
else
{
        //取得页表地址
        tbl = (u32 *) (page_dir[page_dir_index] & 0xfffff000);
}
//如果此页面并没有被换出,swap状态为0
if ((tbl[page_table_index] >> 9 & 0x1) == 0)
{
        u32 ph_page_no = 0;
        u32 shared = 0;
        u32 share_addr = 0;
        //如果缺页申请失败
        if (alloc_page_no(pid, page_no, &ph_page_no, &shared, &share_addr) == 0)
        {
                printf("Segmentation fault.\n");
                hlt();
        }
        else
        {
                u32 address = ph_page_no * 0x1000;
                if (shared == 1)
                {
                        //printf("share: %x\n", share_addr);
                        address = share_addr;
                }
                tbl[page_table_index] = address | 7;
        }
}
//如果此页面被已经换出则要从外存换回此页面到内存
else
{
        u32 sec_no = tbl[page_table_index] >> 12;
        u32 ph_page_no = 0;
        /*
         * 如果换入成功,在执行page_swap_in时,
         * tbl[page_table_index]的前20位存放的是此页被换出到
         * 外存的逻辑扇区号sec_no
         * 在读取外存扇区时sec_no要乘以8,因为是4k对齐,
         * 8个512B的扇区大小为4096B=4K
         */
        if (page_swap_in(page_no, sec_no, pid, &ph_page_no) == 1)
        {
                /*
                 * 这里的一条赋值语句有以下3个作用:
                 * 1.修改页表前20位为线性地址
                 * 2.修改swap位为0,表示没有被换入
                 * 3.修改后3位为在内存中,并可读写
                 * 这里需要修改:
                 * 对于物理内存内的地址按物理地址分配,
                 * 对于大于物理内存的地址按alloc_page申请的地址来分配
                 */
                u32 address = ph_page_no * 0x1000;
                tbl[page_table_index] = address | 7;
        }
        else
        {
                printf("Segmentation fault.\n");
                hlt();
        }
}
//恢复cr3
set_cr3(v_cr3);

        上面就是缺页申请的工作流程,其中使用了几个重要的函数:

        缺页申请:

/*
 * 向内核申请一个指定页号的页面,如果些页在MMAP中为“可用”则可申请,
 * 如果为“已使用”再判断此页是否可以被换出
 * 如果此页不可以被换出,则返回0,如果可以被换出则尝试换出,
 * 如果换出失败则返回0,如果换出成功则返回1
 */
int alloc_page_no(u32 pid, u32 page_no, u32 *page_no_ret, 
                    u32 *shared, u32 *share_addr)
{
        *shared = 0;
        *share_addr = 0;
        *page_no_ret = page_no;
        //取得此页的状态
        u8 status = mmap_status(page_no);
        //如果第0位为0,说明页面未使用
        if ((status & 0x1) == 0)
        {
                //设置此页为“已使用”、“可换出”
                set_mmap_status(page_no, MM_USED | MM_CAN_SWAP);
                //设置此页的使用者id,即pid
                set_map_process_id(page_no, pid);
                //如果超出物理页面数
                if (page_no >= MAP_SIZE)
                {
                        //申请一个可用的物理页面号
                        u32 ph_page_no = alloc_page_ph(pid);
                        if (ph_page_no == 0)
                        {
                                return 0;
                        }
                        *page_no_ret = ph_page_no;
                }
                //返回成功
                return 1;
        }
        //如果第0位为1,说明页面已使用
        else
        {
                //尝试将此页面换出到外存
                if (page_swap_out(page_no) == 0)
                {
                        //如果页面共享失败
                        if (page_share(page_no, share_addr) == 0)
                        {
                                return 0;
                        }
                        else
                        {
                                *shared = 1;
                                return 1;
                        }
                }
                else
                {
                        //设置此页为“已使用”、“可换出”
                        set_mmap_status(page_no, MM_USED | MM_CAN_SWAP);
                        //设置此页的使用者id为0
                        set_map_process_id(page_no, pid);
                        //如果超出物理页面数
                        if (page_no >= MAP_SIZE)
                        {
                                //申请一个可用的物理页面号
                                u32 ph_page_no = alloc_page_ph(pid);
                                if (ph_page_no == 0)
                                {
                                        return 0;
                                }
                                *page_no_ret = ph_page_no;
                        }
                        return 1;
                }
        }
}

        换出页面:

/*
 * 换出页面,向交换分区申请一个可用外存页面号
 * 将当前页面数据写入交换分区
 */
int page_swap_out(u32 page_no)
{
        //取得此页的状态
        u8 status = mmap_status(page_no);
        //如果未使用
        if ((status & 0x1) == 0)
        {
                //直接返回成功
                return 1;
        }
        //如果页面不可以换出
        if (((status >> 1) & 0x1) == 0)
        {
                //返回失败
                return 0;
        }
        //如果页面可以换出
        else
        {
                //向外存申请8个扇区用来存放一个4k的页面
                u32 sec_no = swap_alloc_sec();
                //如果申请扇区失败
                if (sec_no == 0xffffffff)
                {
                        //返回失败
                        return 0;
                }
                else
                {
                        //取得正在使用这一页面的pid_used
                        u32 pid_used = map_process_id(page_no);
                        //取得页目录及页表,为换出做准备
                        u32 *page_dir = pcb_page_dir(pid_used);
                        u32 d_ind = page_no / 1024;
                        u32 t_ind = page_no % 1024;
                        u32 *tbl = (u32 *) (page_dir[d_ind] & 0xfffff000);
                        //取得页面地址4k对齐
                        void* page_data = (void *) (tbl[t_ind] & 0xfffff000);
                        //将页面数据写入外存
                        swap_write_page(sec_no, page_data);
                        /*
                         * 设置原任务的此页面失效
                         * 将页表中的地址域放入逻辑扇区号
                         * 将swap状态位置为1表示此页面被换出到外存
                         */
                        tbl[t_ind] = 6;
                        tbl[t_ind] |= (0x1 << 9);
                        tbl[t_ind] |= sec_no << 12;
                        //设置此页为“未使用”、“可换出”
                        set_mmap_status(page_no, MM_FREE | MM_CAN_SWAP);
                        //设置此页的使用者id为0
                        set_map_process_id(page_no, 0);
                        //如果超出物理页面数
                        if (page_no >= MAP_SIZE)
                        {
                                //计算物理页面号
                                u32 ph_page_no = (u32) page_data / 0x1000;
                                //设置此页为“未使用”、“可换出”
                                set_mmap_status(ph_page_no, MM_FREE | MM_CAN_SWAP);
                                //设置此页的使用者id为0
                                set_map_process_id(ph_page_no, 0);
                        }
                        //返回成功
                        return 1;

                }
        }
}

        换回页面:

/*
 * 换回页面,将交换分区的数据换回至内存
 */
int page_swap_in(u32 page_no, u32 sec_no, u32 pid, u32 *page_no_ret)
{
        *page_no_ret = page_no;
        void* page_data = (void *) (page_no * 0x1000);
        //取得此页的状态
        u8 status = mmap_status(page_no);
        //如果页面不可以换出
        if (((status >> 1) & 0x1) == 0)
        {
                //返回失败
                return 0;
        }
        //如果第0位为0,说明页面未使用
        if ((status & 0x1) == 0)
        {
                //如果超出物理页面数
                if (page_no >= MAP_SIZE)
                {
                        //申请一个可用的物理页面号
                        u32 ph_page_no = alloc_page_ph(pid);
                        if (ph_page_no == 0)
                        {
                                return 0;
                        }
                        *page_no_ret = ph_page_no;
                        page_data = (void *) (ph_page_no * 0x1000);
                }
                //将页面数据从外存读入到内存
                swap_read_page(sec_no, page_data);
                //释放扇区
                swap_free_sec(sec_no);
                //返回成功
                return 1;
        }
        //如果第0位为0,说明页面已使用
        if ((status & 0x1) == 1)
        {
                //如果换出失败
                if (page_swap_out(page_no) == 1)
                {
                        //如果超出物理页面数
                        if (page_no >= MAP_SIZE)
                        {
                                //申请一个可用的物理页面号
                                u32 ph_page_no = alloc_page_ph(pid);
                                if (ph_page_no == 0)
                                {
                                        return 0;
                                }
                                *page_no_ret = ph_page_no;
                                page_data = (void *) (ph_page_no * 0x1000);
                        }
                        //将页面数据从外存读入到内存
                        swap_read_page(sec_no, page_data);
                        //释放扇区
                        swap_free_sec(sec_no);
                        //设置此页为“已使用”、“可换出”
                        set_mmap_status(page_no, MM_USED | MM_CAN_SWAP);
                        //设置此页的使用者id,即pid
                        set_map_process_id(page_no, pid);
                        //返回成功
                        return 1;
                }
        }
        return 0;
}

        共享页面:

/*
 * 共享内存页,共享时只读,不能写入,以避免出现内存数据被其它程序破坏
 */
int page_share(u32 page_no, u32 *share_addr)
{
        //取得正在使用这一页面的pid_used
        u32 pid_used = map_process_id(page_no);
        //取得页目录及页表,为换出做准备
        u32 *page_dir = pcb_page_dir(pid_used);
        u32 d_ind = page_no / 1024;
        u32 t_ind = page_no % 1024;
        u32 *tbl = (u32 *) (page_dir[d_ind] & 0xfffff000);
        //取得页面地址4k对齐
        *share_addr = (u32) (tbl[t_ind] & 0xfffff000);
        return 1;
}

        下面还有一个非常重要的函数,叫alloc_page_ph。它的功能是申请一个物理内存页。它与alloc_page的区别是:alloc_page只查找逻辑内存页找到free的页面将其地址返回给程序,并且可以一次申请多个连续页面,而 alloc_page_ph一次只能申请一个页面,但这个页面是一个物理页面,它存在于真实的物理内存中,如果物理内存中已经没有了可以使用的free页面,它会找到一个允许被换出的页面并调用page_swap_out函数,将这个页面换出,再将这一页的身手物理内存页号返回给程序:

/*
 * 申请物理内存页
 */
u32 alloc_page_ph(u32 pid)
{
        u32 ret = 0;
        //从未被分配内存页的地方开始查找
        for (u32 i = MMAP_USED_SIZE; i < MAP_SIZE && ret == 0; i++)
        {
                //如果找到空闲页
                if ((mmap[find_index] & 0x3) == (MM_FREE | MM_CAN_SWAP))
                {
                        set_mmap_status(find_index, MM_USED | MM_CAN_SWAP);
                        set_map_process_id(find_index, pid);
                        ret = find_index;
                }
                find_index++;
                if (find_index >= MAP_SIZE)
                {
                        find_index = MMAP_USED_SIZE;
                }
        }
        //如果没有free页面,则再找可换出页面
        for (u32 i = MMAP_USED_SIZE; i < MAP_SIZE && ret == 0; i++)
        {
                //如果找到可换出页面
                if ((mmap[find_index] & 0x3) == (MM_USED | MM_CAN_SWAP))
                {
                        //将此物理页面换出
                        if (page_swap_out(find_index) == 0)
                        {
                                return 0;
                        }
                        set_mmap_status(find_index, MM_USED | MM_CAN_SWAP);
                        set_map_process_id(find_index, pid);
                        ret = find_index;
                }
                find_index++;
                if (find_index >= MAP_SIZE)
                {
                        find_index = MMAP_USED_SIZE;
                }
        }
        return ret;
}

        另外,交换分区的实现方式为磁盘的读写,我们为虚拟机分配一个20G大小的磁盘,实际上我们只会用到前4G再多0x100个扇区的空间,将磁盘设置为IDE Primary Master,将光驱设置为IDE Primary Slave如下图:

 

        安装交换分区,此时尚未真正的处理磁盘设备,这里只是使用了一个4G大小的物理磁盘,将4G的物理磁盘作为swap交换分区,因为页表中只有高20位可以存放地址数据,也就是为什么在分页时要对内存做4K对齐,当页面被换出到交换分区之后,页表中的高20位转变为存放20位的磁盘扇区号,系统启动时,初始化交换分区,将交换分区的map初始化为0:

//安装交换分区
void install_swap()
{
        u8 bitmap[0x200];
        for (int i = 0; i < 0x200; i++)
        {
                bitmap[i] = 0;
        }
        //map大小为: 4G / 4096 / 8 = 0x20000
        u32 bitmap_size = 0x20000;
        for (u32 j = 0; j < bitmap_size / 0x200; j++)
        {
                hd_rw(j, HD_WRITE, bitmap);
        }
}

        向交换分区申请一个可用的磁盘号:

u32 swap_alloc_sec()
{
        u8 bitmap[0x200];
        //map大小为: 4G / 4096 / 8 = 0x20000
        u32 bitmap_size = 0x20000;
        for (u32 j = 0; j < bitmap_size / 0x200; j++)
        {
                hd_rw(j, HD_READ, bitmap);
                for (u32 k = 0; k < 0x200; k++)
                {
                        for (u32 l = 0; l < 8; l++)
                        {
                                if (((bitmap[k] >> l) & 0x1) == 0)
                                {
                                        bitmap[k] |= (0x1 << l);
                                        hd_rw(j, HD_WRITE, bitmap);
                                        u32 sec_no = (j * 0x200 * 8) + k * 8 + l;
                                        return sec_no;
                                }
                        }
                }
        }
        return 0xffffffff;
}

        向交换分区释放一个磁盘号:

void swap_free_sec(u32 sec_no)
{
        u8 bitmap[0x200];
        u32 ind = sec_no / 8 / 0x200;
        hd_rw(ind, HD_READ, bitmap);
        bitmap[(sec_no % (0x200 * 8)) / 8] &= ~(0x1 << ((sec_no % (0x200 * 8)) % 8));
        hd_rw(ind, HD_WRITE, bitmap);
}

        将内存数据写入交换分区:

void swap_write_page(u32 sec_no, void *page_data)
{
        //计算物理扇区号
        sec_no *= 8;
        //跳过map所在扇区
        sec_no += 0x100;

        //写入8个扇区
        for (int i = 0; i < 8; i++)
        {
                hd_rw(sec_no + i, HD_WRITE, page_data);
                page_data += 0x200;
        }
}

        从交换分区读入数据到内存:

void swap_read_page(u32 sec_no, void *page_data)
{
        //计算物理扇区号
        sec_no *= 8;
        //跳过map所在扇区
        sec_no += 0x100;

        //读出8个扇区
        for (int i = 0; i < 8; i++)
        {
                hd_rw(sec_no + i, HD_READ, page_data);
                page_data += 0x200;
        }
}

        另外,交换分区函数都调用了一个void hd_rw(u32 lba, u8 com, void *buf);函数,此函数的功能是读写磁盘扇区。关于它的具体内容和功能我们将在文件系统一章来学习,这里不作太多的解释。读者只需要了解可以调用这个函数来读写磁盘扇区即可。一次为任务申请16个页面,为的是将pbc、可执行代码、栈、页目录和页表内容都存放到一个内存区域,这样在初始化页表时方便为其指定页面号。

int pages = 16;
//任务ID号
u32 process_id = 1;
//空任务
s_pcb *pcb_empty = NULL;
void* mm_pcb = alloc_page(process_id, pages, 0);
pcb_empty = (s_pcb *) mm_pcb;
init_process(mm_pcb, pcb_empty, process_id, NULL);
process_id++;
//任务A
void* mm_pcb_A = alloc_page(process_id, pages, 0);
pcb_A = (s_pcb *) mm_pcb_A;
init_process(mm_pcb_A, pcb_A, process_id, &run_A);
process_id++;
//任务B
void* mm_pcb_B = alloc_page(process_id, pages, 0);
pcb_B = (s_pcb *) mm_pcb_B;
init_process(mm_pcb_B, pcb_B, process_id, &run_B);
process_id++;
//载入TSS
addr_to_gdt(GDT_TYPE_TSS, (u32) &pcb_empty->tss, &gdts[4], GDT_G_BYTE, sizeof(s_tss) * 8);        //载入LDT
addr_to_gdt(GDT_TYPE_LDT, (u32) pcb_empty->ldt, &gdts[5], GDT_G_BYTE, sizeof(s_gdt) * 2 * 8);
//载入tss和ldt
load_tss(GDT_INDEX_TSS);
load_ldt(GDT_INDEX_LDT);

        初始化任务:

/*
 * create_task : 创建tts任务
 *  - int type : tts任务类型TASK_TYPE_NOR、TASK_TYPE_SPE
 * return : void
 */
void init_process(void *mm_pcb, s_pcb *pcb, u32 process_id, void *run_addr)
{
        //s_tss
        pcb->tss.back_link = 0;
        pcb->tss.ss0 = GDT_INDEX_KERNEL_DS;
        pcb->tss.esp1 = 0;
        pcb->tss.ss1 = 0;
        pcb->tss.esp2 = 0;
        pcb->tss.ss2 = 0;
        pcb->tss.cr3 = 0;
        pcb->tss.eflags = 0x3202;
        pcb->tss.eax = 0;
        pcb->tss.ecx = 0;
        pcb->tss.edx = 0;
        pcb->tss.ebx = 0;
        pcb->tss.ebp = 0;
        pcb->tss.esi = 0;
        pcb->tss.edi = 0;
        pcb->tss.eip = 0;
        pcb->tss.esp = 0;
        pcb->tss.esp0 = 0;
        pcb->tss.es = USER_DATA_SEL;
        pcb->tss.cs = USER_CODE_SEL;
        pcb->tss.ss = USER_DATA_SEL;
        pcb->tss.ds = USER_DATA_SEL;
        pcb->tss.fs = USER_DATA_SEL;
        pcb->tss.gs = USER_DATA_SEL;
        pcb->tss.ldt = GDT_INDEX_LDT;
        pcb->tss.trace_bitmap = 0x0;
        //设置多任务的gdt描述符
        addr_to_gdt(LDT_TYPE_CS, 0, &(pcb->ldt[0]), GDT_G_KB, 0xfffff);
        addr_to_gdt(LDT_TYPE_DS, 0, &(pcb->ldt[1]), GDT_G_KB, 0xfffff);
        //设置pcb相关值
        pcb->pid = process_id;
        pcb->tss.eip = (u32) mm_pcb + 0x1000;
        pcb->tss.esp = (u32) mm_pcb + 0x2000;
        pcb->tss.esp0 = (u32) mm_pcb + 0x3000;
        pcb->tss.cr3 = (u32) mm_pcb + 0x4000;
        pcb->swap = NULL;
        //copy可执代码到eip位置
        mmcopy(run_addr, (void *) (pcb->tss.eip), 0x1000);
        //页目录
        u32 *page_dir = (u32 *) (pcb->tss.cr3);
        //页表
        u32 *page_tbl = (u32 *) ((u32) mm_pcb + 0x5000);
        //地址
        u32 address = 0;
        /*
         * 前16M系统内存为已使用
         * 实际上16M系统内存是不应该让普通程序可写的
         * 但为了能让普通程序直接操作0xb8000显示缓冲区
         */
        for (int i = 0; i < 4; i++)
        {
                for (int j = 0; j < 1024; j++)
                {
                        page_tbl[j] = address | 7;
                        address += 0x1000;
                }
                page_dir[i] = ((u32) page_tbl | 7);
                page_tbl += 1024;
        }
        //mm_pcb所在内存
        address = (u32) mm_pcb;
        //mm_pcb所在内存页目录索引
        u32 page_dir_index = (address >> 22) & 0x3ff;
        //mm_pcb所在内存页表索引
        u32 page_table_index = (address >> 12) & 0x3ff;
        //mm_pcb所在内存页表
        page_tbl = (u32 *) alloc_page(process_id, 1, 0);
        for (int i = 0; i < 1024; i++)
        {
                //设置mm_pcb所在内存的页表
                if (i >= page_table_index && i <= (page_table_index + 16))
                {
                        page_tbl[i] = address | 7;
                        address += 0x1000;
                }
                else
                {
                        page_tbl[i] = 6;
                }
        }
        //设置mm_pcb所在内存的页目录
        page_dir[page_dir_index] = ((u32) page_tbl | 7);
        //设置16个页面剩余页
        if (page_table_index + 16 >= 1024)
        {
                page_tbl = (u32 *) alloc_page(process_id, 1, 0);
                for (int i = 0; i < 1024; i++)
                {
                        if (i < (10 - (1024 - page_table_index)))
                        {
                                page_tbl[i] = address | 7;
                                address += 0x1000;
                        }
                        else
                        {
                                page_tbl[i] = 6;
                        }
                }
                page_dir[page_dir_index + 1] = ((u32) page_tbl | 7);
        }
}

        最后修改任务B的代码,让其访问0 ~ 4G的内存地址:

void run_B()
{
        char *ps = (char *) 0;
        char ch;
        while ((u32) ps <= 0xFFFFFFFF)
        {
                ch = *ps;
                ps += 0x1000;
        }
        char *p = (char *) 0xb8000;
        p += ((23 * 80 + 76)) * 2;
        int i = 99;
        while (1)
        {
                *p = i;
                if (++i >= 127)
                {
                        i = 33;
                }
        }
}

        在Makefile中加入处理交换分区功能函数的hd.c、swap.c两个文件,编译运行并查看结果:



 

        可以看到0 ~ 0x20000000(512M)内存的访问速度非常快,因为访问的实际物理内存,从0x20000000开始之后内存访问的速度变的很慢,原因是使用了交换分区一直在做内存与外存的数据交换,由于外存的读写速度远远小于内存,所以才会这样慢。同时也可以看到在访问512M以上内存时虚拟机的磁盘一直处理工作状态,也就是说我们的交换分区正在工作:



 

        源代码的下载地址为:

https           https://github.com/magicworldos/lidqos.git 
git             git@github.com:magicworldos/lidqos.git 
subverion       https://github.com/magicworldos/lidqos 
branch          v0.16

 

    返回首页    返回顶部
#1楼  小七  于 2018年07月15日19:07:04 发表
 
我想问一个问题,我在编译您的这个kernel,链接时,出现下面的问题

uild/kernel/osiso.o: In function `osiso_read_block':

osiso.c:(.text+0x331): undefined reference to `__stack_chk_fail'

build/kernel/osiso.o: In function `osiso_read_block_phy':

osiso.c:(.text+0x4de): undefined reference to `__stack_chk_fail'

我在CFLAGS中加了-fno-stack-protector这个选项,编译是通过了,但是内核在虚拟机的运行效果是:

void schedule()

{

//取得链表头

s_list *list_header = list_pcb;

if (list_header == NULL)

{

printf(0, "empty\n");

return;

}

...

}

这个函数list_header = NULL,走了printf(0, "empty\n");

注:用branch:v16

当我用您master的编译时,也是上面link的错误,加了-fno-stack-protector这个选项编译是通过了,的运行的时候,不打印任何信息,不知道为什么

出现这个问题是gcc版本的问题还是有其它方面的原因,您用的gcc版本是?,还是缺什么库
#2楼  匿名  于 2019年05月26日17:28:22 发表
 
void delay(int time)

{

int i = 0;

for (i = 0; i < time; i++)

{

;

}

return;

}

void hd_rw(u32 lba, u8 com, void *buf)

{

...

do

{

...

outb_p(com, HD_PORT_COMMAND);

hd_status = inb_p(HD_PORT_STATUS);

while (hd_status < 0)

{

delay(1000);

hd_status = inb_p(HD_PORT_STATUS);

}

while ((hd_status & 0xc0) != 0x40)

{

delay(1000);

hd_status = inb_p(HD_PORT_STATUS);

if ((hd_status & 0xc0) == 0x40)

{

break;

}

}

if (com == HD_READ)

{

insl(buf2, HD_PORT_DATA, sects_to_access << 7);

}

else if (com == HD_WRITE)

{

outsl(buf2, HD_PORT_DATA, sects_to_access << 7);

}

s = inb_p(HD_PORT_STATUS);

if (((com == HD_READ) && (s != 0x50)) || ((com == HD_WRITE) && (s != 0x90)))

{

}

if (try_times-- == 0)

{

break;

}

}

while (((com == HD_READ) && (s != 0x50)) || ((com == HD_WRITE) && (s != 0x90)));

}

修改成这样,虚拟机可以跑出上面截图显示的结果。

hd.c这个驱动写的有问题,在没有开始对端口操作之前去读取状态,一直无法就绪。

对于之后的文件系统,完全无法运行起来。
#3楼  匿名  于 2019年05月30日14:49:10 发表
 
系统是可以正常运行起来的,只是必须要确保光驱为第一IDE从通道,上面的使用的是第二IDE主通道,导致各种问题。
#4楼  飘雪冰封  于 2024年01月15日16:17:01 发表
 
这一节内容编写的比较复杂,把缺页异常处理和交换分区给放到一起来写了。

单纯为了实验,其实可以单独写一个缺页异常处理的实验章节,然后再加入交换分区的内容,因为交换分区还涉及到了磁盘读写,这又是后面章节中的内容了。

我在阅读这一章节的时候,做实验就先把交换分区的代码给删除了,然后等后面加入了磁盘读写相关的实现之后,再加入交换分区中的相关内容。
#5楼  李德强  于 2024年01月19日14:32:28 发表
 
感谢您的支持!
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号