拼一个自己的操作系统 SnailOS 0.03的实现
拼一个自己的操作系统SnailOS0.03源代码-Linux文档类资源-CSDN下载
操作系统SnailOS学习拼一个自己的操作系统-Linux文档类资源-CSDN下载
SnailOS0.00-SnailOS0.00-其它文档类资源-CSDN下载
读取fat32文件系统的文件。
在真正的讲fat32文件系统读取文件的内容前,让我们先把几个bmp文件复制到我们的虚拟硬盘上。并且创建了hello.txt的文本文件。
看到了吧,都是大美女吧!继续写下去是不是好有兴致。
时至今日,fat32文件系统已经被ntfs、ext?等其他文件系统甩了多少条街都说不好了。但有一点确定的是最近还有很多U盘用的是该文件系统。其实,仔细想来也没有什么特殊的原因,主要就是该文件系统还是非常好用的。但我们选择fat32文件系统的唯一目的就是它相对来说还是比较简单的。也就是能用比较少的代码就完成我们的目标。其实,我们的目标更是简单和直接,就是读取文件内容,然后做一些简单的操作,比如我们可以输出文本文件的内容,我们可以在屏幕上显示图片等等。
现在的问题就转化为,如何用上一章的硬盘驱动程序,把某个lba28扇区的内容读取到内存中供我们使用这么简单了。网上关于fat32文件系统的文章可以用多如牛毛来形容,其详解每个都有独到之处,所以笔者根本就不想拷贝那些信息在这里班门弄斧。我们的思路是通过二进制文件查看器,从目录项检索开始,逐步高清,该如何读取文件内容。首先让我们用UE打开temp.vhd的硬盘镜像文件。然后我们来一波骚操作,逐步摸清fat32的底数。下面的图中我们看到了一片16进制的数据(中间),左边是该数据对应的16进制地址,右边是数据对应的ascii码值。这是硬盘的开头,粗估一下应该是主引导扇区。因此我们要找到一个存在于512字节的标记也就是0xaa55,这样就比较确定了。那么我们往下看看哪个纯蓝色的部分正好是我们要找的地方。可是我们的目的可不是看这个让人迷糊的引导扇区,我是要找到我们的文件内容所在的扇区。因此按照fat32文件系统的安排,我们知道文件的目录项中含有该内容,同时目录项中也还有文件名称。干脆我们在此处搜索文件名看看能不能一眼就找到目录项,从而访问到文件内容。
说干就干,在下图大家看到了吧,一眼熟呀,可不是一个文件名我们熟悉,上一节我们拷贝的文件都在这里排队呢?这不是天上直接就掉下个林妹妹吗。同时看到下图我们用纯蓝色圈出的部分,在目录项中它是文件开始的簇号低16位,也就是0x30df,而往前4个字节处的0x0000处是簇号的高16位。而我们要的是文件的起始扇区,计算方法就是网上查到的
起始扇区= 根目录起始扇区 + (起始簇号 - 2)* 8
而我们现在缺少的是根目录起始扇区数。仔细一想这个并不难啊,我们稍微往前看看不就是根目录的起始扇区了吗。也就是说,我们的根目录根部没有几个文件吗。
看下面的图,不出所料的话,这就是根目录开始的位置。而这里是字节数,我们要转化为扇区数才能算计算出来了。用0x303000除以512就应该是扇区数吧。让我们来掰着手指头算一下好了。这个结果是0x1818,现在套入那个公式,0x1818 + (0x30df - 2) * 8 = 0x1f900;
接下来就是要查找文件了。看下图一打眼,是不是文件内容。我搜索的是Snail OS!...因此不是在文件开头,文件开头是一堆空格了。看看左边的地址,0x33e0000,这个是字节的,所以要除以512得出我们文件的起始扇区数。正是前面推算出的0x1f900这个数。至此查找一个简单txt文件的就大功告成了。
不过那个根目录起始扇区数可是我们手工蒙出来的,也就是说只可意授不可言传。这样可不行呀。
我们这里也就不兜圈子了,按照fat32的规定,根目录起始扇区在两个fat表之后,而两个fat表的起始扇区是通过bpb起始扇区加上bpb后的保留扇区数的到的。而这些信息除了bpb的起始扇区在mbr中获得外,其他的都在bpb中。因此我们现在要返回到mbr中得到bpb起始扇区数。然后通过bpb得到根目录起始扇区。bpb起始扇区在mbr分区表结构中的lba_start中,分区表在mbr第446字节处,也就是0x1be处,也就是lba_start在mbr的0x1c7处。
观察mbr的下图,纯蓝色的部分就是bpb起始扇区数,为0x800,乘以512(0x200)转化成字节数就是0x100000(1M)处。
看到下图大家就明白了吧,这么简单就找到了bpb,而我们所需要的所有信息都在这里了。对了这是通过转到地址0x100000处得到的。接下来大家就没什么疑问了吧,通过访问bpb中的关键数据,我们就能一步一步的最终访问文件内容了。
下面我们就给出完整的代码,相信大家学了这么久,对这些代码不会有什么问题了。
【fs.h】
// fs.h 作者:至强 创建时间:2022年12月
#ifndef __FS_H
#define __FS_H
struct partition_table_item {
unsigned char flags, head_start, sec_start, chs_start;
unsigned char fs_type, head_end, sec_end, chs_end;
unsigned int lba_start, sec_cnt;
}__attribute__((packed));
struct boot_sector {
unsigned char code_area[446];
struct partition_table_item parts[4];
unsigned short magic;
}__attribute__((packed));
struct bios_parameter_block {
unsigned char bpb_head[11];
unsigned short bytes_per_sector;
unsigned char sectors_per_cluster;
unsigned short reserved_sector;
unsigned char number_of_fat;
unsigned short root_entries;
unsigned short small_sector;
unsigned char media_descriptor;
unsigned short sectors_per_fat_16;
unsigned short sectors_per_track;
unsigned short number_of_head;
unsigned int hidden_sector;
unsigned int large_sector;
unsigned int sectors_per_fat_32;
unsigned short extended_flag;
unsigned short file_system_version;
unsigned int root_cluster_number;
unsigned short file_system_information_sector_number;
unsigned short backup_boot_sector;
unsigned char reserved[12];
unsigned char physical_drive_number;
unsigned char reserved_byte;
unsigned char extended_boot_signature;
unsigned int volume_serial_number;
unsigned char volume_label[11];
unsigned char system_id[8];
}__attribute__((packed));
struct dir_item {
char name[8], ext[3], attrib, empty, ticks;
unsigned short create_time, create_date, access_date;
unsigned short clust_no_h, modify_time, modify_date;
unsigned short clust_no_l;
unsigned int filesize;
};
struct fat32_krn_p {
int* fat1_p;
struct dir_item* root_dir_p;
unsigned int root_dir_start_sector;
};
void read_mbr_dbr(struct fat32_krn_p* fat32_p);
char* format_filename(const char* filename);
int find_file(struct fat32_krn_p* fat32_p, const char* filename);
void* file_read(const char* filename);
void hd_read_clust(struct fat32_krn_p* fat32_p, unsigned int start,
int mem_start);
#endif
【fs.c】
// fs.c 作者:至强 创建时间:2022年12月
#include "fs.h"
#include "memory.h"
#include "hd.h"
#include "string.h"
struct fat32_krn_p fat32_krn;
extern struct disk hds[2];
void read_mbr_dbr(struct fat32_krn_p* fat32_p) {
/*
为主引导扇区mbr分配内存,由于mbr仅有512字节,这里剩余的空间
实际是可以留作他用的。
*/
struct boot_sector* mbr = get_kernel_pages(1);
/*
用硬盘驱动把主引导扇区读入分配的内存中。
*/
hd_read_sub(&hds[0], 0, (void*)mbr, 1);
unsigned int dbr_lba_start;
/*
文件系统类型是fat32则bioa参数块的lba28模式其实扇区为主引导
扇区分区信息的lba_start成员。
*/
if(mbr->parts[0].fs_type == 0x0b) {
dbr_lba_start = mbr->parts[0].lba_start;
} else {
return;
}
/*
由于前面的mbr只使用的512字节,所以这里使用了接下来的512字节
作为bpb的内存地址,同样根据bpb在硬盘上的起始扇区,读取512字节
到内存中。
*/
struct bios_parameter_block* bpb = (struct bios_parameter_block* )
((unsigned int)mbr + 512);
hd_read_sub(&hds[0], dbr_lba_start, (void*)bpb, 1);
/*
得到fat32文件系统的几个重要信息,这个是每扇区的字节数,
这个数字估计是512。
*/
unsigned short bytes_per_sector = bpb->bytes_per_sector;
// printf_("^%x^", bytes_per_sector);
/*
这个是每簇扇区数,估计数值是8。也就是4096字节。
*/
unsigned char sectors_per_cluster = bpb->sectors_per_cluster;
// printf_("^%x^", sectors_per_cluster);
/*
bpb的保留扇区数,用于求解下面的fat表1的起始扇区。
*/
unsigned short reserved_sector = bpb->reserved_sector;
// printf_("^%x^", reserved_sector);
/*
这个是bpb的fat表的个数。此处的值应该是2。
*/
unsigned char number_of_fat = bpb->number_of_fat;
// printf_("^%x^", number_of_fat);
/*
每个fat表所占的扇区数。
*/
unsigned int sectors_per_fat_32 = bpb->sectors_per_fat_32;
// printf_("@%x@", sectors_per_fat_32);
/*
顺利地得到了fat表的起始扇区数。
*/
unsigned int fat_start_sector = dbr_lba_start +
reserved_sector;
// printf_("^%x^", fat_start_sector);
/*
我们的目标就在这里了,它将得到根目录的起始扇区数,也就是
fat表1的起始扇区数加上两个fat表的长度。
*/
unsigned int root_dir_start_sector = fat_start_sector +
sectors_per_fat_32 * number_of_fat;
// printf_("^%x^", root_dir_start_sector);
/*
根据fat表1的起始扇区数已经表的长度,将fat表1读入到内存中待用,
这个表将占用较大的内存。
*/
unsigned int* fat1_mem_start =
get_kernel_pages(up_pgs(sectors_per_fat_32 * bytes_per_sector));
hd_read_sub(&hds[0], fat_start_sector, (void*)fat1_mem_start,
sectors_per_fat_32);
// printf_("^%x^", fat1_mem_start);
/*
对于根目录我们这里仅仅读取了一页的内容,估计这样的情况对于我们
现在的系统也是足够用了。
*/
unsigned int* root_mem_start = get_kernel_pages(1);
hd_read_sub(&hds[0], root_dir_start_sector, (void*)root_mem_start, 8);
/*
将信息保存在全局结构中待用。
*/
fat32_p->fat1_p = (int*) fat1_mem_start;
fat32_p->root_dir_p = (struct dir_item*) root_mem_start;
fat32_p->root_dir_start_sector = root_dir_start_sector;
}
/*
这是简单的格式化fat32目录项中段格式文件名的函数,也就是尽量
正确的判断我们给出的文件名的正确性,然后存储到静态字符数组中
待用。这个函数肯定存在一些特殊的情况不能处理,因此,大家在根目录
中存储文件时,应该按照8+3的标准格式存储,否则就会有风险^_^。
*/
char* format_filename(const char* filename) {
int i = 0, j, k;
static char result[12];
char space = 0x20;
result[11] = 0;
if(strlen_(filename) > 11) {
return NULL;
}
for(j = 0; j < 11 && filename[j]; j++){
if(filename[j] == '.') {
i = 1;
}
}
if(i == 0) {
if(strlen_(filename) > 8) {
return NULL;
}
for(j = 0; j < 8 && filename[j]; j++) {
result[j] = filename[j] & 0xdf;
}
for(; j < 8 + 3; j++) {
result[j] = space;
}
return result;
} else {
if(filename[0] == '.') {
result[0] = '.';
if(filename[1] == '.') {
result[1] = '.';
for(j = 2; j < 8 + 3; j++) {
result[j] = space;
}
return result;
}
for(j = 1; j < 8 + 3; j++) {
result[j] = space;
}
return result;
}
}
for(j = 0; j < 8 && filename[j]; j++) {
result[j] = filename[j] & 0xdf;
if(filename[j] == '.') {
k = j + 1;
break;
}
}
for(; j < 8 + 3; j++) {
result[j] = space;
}
if(k < 11) {
for(j = 0; j < 3; j++) {
(result + 8)[j] = filename[k++] & 0xdf;
}
}
return result;
}
/*
整合上面的文件,从而在目录相中找到正确的文件。
*/
int find_file(struct fat32_krn_p* fat32_p, const char* filename) {
struct dir_item* root_dir_start = fat32_p->root_dir_p;
unsigned int file_start_clust;
unsigned int filesize;
unsigned int root_dir_start_sector = fat32_p->root_dir_start_sector;
char file[12] = {0};
strcpy_(file, format_filename(filename));
int k = 0;
int j;
/*
这个就是遍历目录项从而找到我们想要的文件。
*/
while((root_dir_start[k].name[0] != 0)) {
if(root_dir_start[k].name[0] == 0xe5) {
k++;
continue;
}
char f1 = 1, f2 = 1;
for(j = 0; j < 8; j++) {
if(root_dir_start[k].name[j] != file[j]) {
f1 = 0;
}
}
for(j = 0; j < 3; j++) {
if(root_dir_start[k].ext[j] != (file + 8)[j]) {
f2 = 0;
}
}
if(f1 && f2) {
file_start_clust = ((unsigned int)root_dir_start[k].clust_no_h <<
16)+ (unsigned int)root_dir_start[k].clust_no_l;
filesize = root_dir_start[k].filesize;
// printf_("*** %s %d %d %x %x ***", file, k + 1,
// filesize, file_start_clust, root_dir_start_sector);
return k;
} else {
// printf_("\n not found %s 第%d个文件", file, k + 1);
}
k++;
}
return -1;
}
/*
按簇来读取文件内容。每个簇有8个扇区,因此循环就是8次。
*/
void hd_read_clust(struct fat32_krn_p* fat32_p, unsigned int start, int mem_start) {
int i;
unsigned int data_start = fat32_p->root_dir_start_sector;
for(i = 0; i < 8; i++) {
/*
起始扇区的计算公式一个难以理解的东西,它是用根目录的起始扇区数
加上(起始簇数减去二)* 8来得到的,这个玩意简直太烧脑了,反正笔者也
不想弄明白了,只要记住公式就好了。
*/
hd_read_sub(&hds[0], data_start + (start - 2) * 8 + i,
(void*)mem_start + i * 512, 1);
}
}
/*
进一步整合就可以按文件名称读取相应的文件了。
*/
void* file_read(const char* filename) {
struct fat32_krn_p* fat32_p = &fat32_krn;
int index = find_file(fat32_p, filename);
if(index == -1) {
return NULL;
}
struct dir_item* root_dir_start = fat32_p->root_dir_p;
unsigned int* fat1_p = fat32_p->fat1_p;
unsigned int start_clust;
unsigned int filesize;
filesize = root_dir_start[index].filesize;
unsigned int root_dir_start_sector = fat32_p->root_dir_start_sector;
char* filep = get_kernel_pages(up_pgs(filesize));
char* fp = filep;
/*
起始簇数在根目录项中分两部分存储,因此要拼接出来。
*/
start_clust = ((unsigned int)root_dir_start[index].clust_no_h <<
16)+ (unsigned int)root_dir_start[index].clust_no_l;
/*
如果文件仅占用一个簇就够用了。我们就读取该簇就可以了。
*/
while(start_clust != 0x0fffffff) {
if(filesize <= 4096) {
hd_read_clust(fat32_p, start_clust, (int)filep);
return fp;
}
/*
如果文件大于一个簇则也要先把第一个簇的内容读入内存。
*/
hd_read_clust(fat32_p, start_clust, (int)filep);
filep += 4096;
filesize -= 4096;
/*
下一个簇的算法又是一个烧脑的玩意,居然在fat表中用上一个簇
下标得到的,笔者对于为什么会是这样也是不想弄懂了,只要记住公式
就好。
*/
start_clust = fat1_p[start_clust];
// printf_(" %x ", start_clust);
// printf_("|");
}
/*
到此便可返回得到文件内容的内存地址。
*/
return fp;
}
笔者在实验的过程中,发现仅仅使用二元信号量,对于一个资源多个线程共享的情况会很别扭,所以增加了一个多值信号量的操作。下面是它的代码。
【 semaphore.h】
// semaphore.h 作者:至强 创建时间:2022年12月
#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H
/*
这是多值信号量的实现,通过多值信号量可以控制同一资源的多次
访问。也就是一种其他的同步关系。
*/
struct event {
void* msgptr;
int count;
unsigned int type;
struct double_linked_list waiters;
};
void event_init(struct event* event_, void* msg, int cnt, int type);
void event_array_init(struct event* event_array_);
struct event* get_event(struct event* event_array_);
void sema_pend(struct event* pevent);
void sema_post(struct event* pevent);
#endif
【 semaphore.c】
// semaphore.c 作者:至强 创建时间:2022年12月
#include "global.h"
#include "intr.h"
#include "thread.h"
#include "semaphore.h"
#include "memory.h"
struct event event_array[256];
struct event* pevent_;
/*
我们这里是按照事件来定义信号量结构的,所以这里是初始化事件。
*/
void event_init(struct event* event_, void* msg, int cnt, int type) {
unsigned int old_status = intr_disable();
event_->msgptr = msg;
event_->count = cnt;
event_->type = type;
double_linked_list_init(&event_->waiters);
set_intr_status(old_status);
}
/*
初始化最多256个信号量。
*/
void event_array_init(struct event* event_array_) {
int i;
for(i = 0; i < 256; i++) {
event_init(&event_array_[i], NULL, 0, -1);
}
}
/*
它实际上是获得一个信号量。
*/
struct event* get_event(struct event* event_array_) {
int i;
unsigned int old_status = intr_disable();
for(i = 0; i < 256; i++) {
if(event_array[i].type == -1) {
break;
}
}
set_intr_status(old_status);
return &event_array_[i];
}
/*
用于等待信号量,也就是说当信号量的值为0时,说明期待的事件
并未发生,这是线程需要阻塞自己,并进入等待队列。直到事件发生
后,完成事件的线程通过累加信号量唤醒线程。
*/
void sema_pend(struct event* pevent) {
struct task* cur;
unsigned int old_status = intr_disable();
if(pevent->count == 0) {
cur = running_thread();
double_linked_list_append(&pevent->waiters, &cur->general_tag);
cur->status = WAITING;
schedule();
set_intr_status(old_status);
} else {
pevent->count--;
set_intr_status(old_status);
}
}
/*
当一个事件完成后,完成该事件的线程会发送1个或者若干个信号,
(仅仅是对信号量的值进行累加)来唤醒在信号量的等待队列中
阻塞的线程。
*/
void sema_post(struct event* pevent) {
struct list_node* waiter;
unsigned int old_status = intr_disable();
if(!double_linked_list_is_empty(&pevent->waiters)) {
waiter = double_linked_list_pop(&pevent->waiters);
struct task* next = node2entry(struct task, general_tag, waiter);
thread_ready_list = &ready_list[next->level];
double_linked_list_append(thread_ready_list, waiter);
set_intr_status(old_status);
} else {
pevent->count++;
set_intr_status(old_status);
}
}
【kernel.c】
// kernel.c 创建者:至强 创建时间:2022年8月
#include "global.h"
#include "x.h"
#include "string.h"
#include "gdt_idt_init.h"
#include "memory.h"
#include "intr.h"
#include "debug.h"
#include "thread.h"
#include "ring_queue.h"
#include "screen.h"
#include "queue.h"
#include "mouse.h"
#include "timer.h"
#include "sheet.h"
#include "tss.h"
#include "process.h"
#include "syscall.h"
#include "hd.h"
#include "fs.h"
#include "semaphore.h"
unsigned long long limit;
void create_clock(int x, int y);
struct queue ins_queue;
void k_thread_a(void* arg);
void k_thread_b(void* arg);
void k_thread_c(void* arg);
void idle(void* arg);
void ua(void);
void ub(void);
void uc(void);
void instructions_switch(struct buf* e);
unsigned int mouse_bin[16 * 16];
unsigned int mouse_before[16 * 16];
extern void test();
extern unsigned int zzz;
struct fat32_krn_p fat32_krn;
struct semaphore fs_sema;
extern struct event event_array[256];
extern struct event* pevent_;
unsigned int* desktop;
void kernel_main(unsigned int magic, unsigned int addr) {
multiboot2_magic = magic;
multiboot2_addr = addr;
pos = 0;
int i, j;
// unsigned int* video = get_video_addr(magic, addr);
unsigned int* video = (unsigned int*)0xe0000000;
screen_init();
info_area_cls();
/*
fill_rectangle(video, 1024, 50, 50, 40, 40, 0x00ff0000);
fill_circle(video, 1024, 200, 200, 80, 0x00ffff00);
draw_line(video, 1024, 0, 0, 1023, 767, 0x00ffffff);
draw_circle(video, 1024, 200, 200, 100, 0x0000ffff);
draw_box(video, 1024, 40, 40, 20, 100, 0x0000ff00);
struct point a, b, c;
a.x = 300;
a.y = 300;
b.x = 200;
b.y = 400;
c.x = 500;
c.y = 450;
draw_triangle(video, 1024, &a, &b, &c, 0x000000ff);
a.x = 600;
a.y = 300;
b.x = 500;
b.y = 400;
c.x = 800;
c.y = 450;
fill_triangle(video, 1024, &a, &b, &c, 0x00bcbcbc);
char pic[16][16] =
{
{'*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','#','#','#','#','#','#','#','#','#','#','#','#','#','*'},
{'*','*','#','*','*','*','*','*','#','*','*','*','*','*','#','*'},
{'*','*','#','*','*','*','*','*','#','*','*','*','*','*','#','*'},
{'*','*','#','*','*','*','*','*','#','*','*','*','*','*','#','*'},
{'*','*','#','*','*','*','*','*','#','*','*','*','*','*','#','*'},
{'*','*','#','#','#','#','#','#','#','#','#','#','#','#','#','*'},
{'*','*','#','*','*','*','*','*','#','*','*','*','*','*','#','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','#','*','*','*','*','*','*','*'},
{'*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*'},
};
unsigned int pic_data[16][16];
for(j = 0; j < 16; j++) {
for(i = 0; i < 16; i++) {
if(pic[j][i] == '#') {
pic_data[j][i] = 0x000000ff;
}
if(pic[j][i] == '*') {
pic_data[j][i] = 0x0000ff00;
}
}
}
for(j = 0; j < 16; j++) {
for(i = 0; i < 16; i++) {
draw_point(video + 1024 * (128 + 16 + 16),
1024, i + 512, j, pic_data[j][i]);
}
}
char font_A[16] = {
0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};
extern char font_A_;
make_ascii(video + 1024 * 128, 1024, 0, 0, 0x000000ff, (char*)&font_A_);
make_ascii(video + 1024 * (128 + 16), 1024, 0, 0, 0x0000ff00, font_A);
printf_("%s %d %x %b", "I love you SnailOS...!", 0x8, 8, 8);
*/
init();
sema_init(&fs_sema, 0);
event_array_init(event_array);
pevent_ = get_event(event_array);
event_init(pevent_, NULL, 0, 0);
/*
extern struct tss tss, tss1;
第1任务(主函数)的任务状态段只需要初始化页目录表,这
主要可能是因为处理器在保存进程的上文时不更新cr3。如果
不返回到主任务,甚至不用对它的任务状态段进行任何处理,
当然也可以没有。
// tss.eip = 0;
// tss.ss = tss.ds = tss.es = 0;
// tss.esp = 0;
tss.cr3 = 0x8000;
// tss.eflags = 0;
// tss.ss0 = 0;
// tss.esp0 = 0;
// tss.cs = 0;
// tss.ldt = 0;
*/
/*
简单的初始化第2个任务的任务状态段。
*/
/*
tss1.eip = (unsigned int)_0x70_handler;
tss1.ss = tss1.ds = tss1.es = 5 * 8 + 3;
tss1.esp = 0xa000;
tss1.cr3 = 0x8000;
tss1.eflags = 0x202;
tss1.ss0 = 2 * 8;
tss1.esp0 = 0x8000;
tss1.cs = 4 * 8 + 3;
tss1.ldt = 0;
// asm volatile ("jmp $6 * 8, $10000");
// asm volatile ("call $6 * 8, $0xabcd");
// asm volatile ("jmp $7 * 8, $10000");
// asm volatile ("call $7 * 8, $0xabcd");
// asm volatile ("cli;int $0x70");
asm volatile("pushl $5 * 8 + 3;\
pushl $5 * 8 + 3;\
pushfl;\
movl $0x202, (%esp);\
popfl;\
movl $6 * 8 , %eax;\
ltr %ax;\
popl %es;\
popl %ds;\
pushl $5 * 8 + 3;\
pushl $0xa000;\
pushfl;\
pushl $4 * 8 + 3;\
pushl $__0x70_handler;\
iret");
printf_("\n@@@task sucessful return!!!!!!");
while(1);
*/
// printf_("%x %x\n", pde_ptr(0x2000000), pte_ptr(0x2000000));
// 虚拟地址的页目录表项写入物理地址0x2000000 + 0x07,也就
// 是页表的首地址在0x2000000处。
// *((unsigned int*)pde_ptr(0x2000000)) = 0x2000000 + 0x07;
// 页表项的物理地址也写入0x2000000 + 0x07,也就是最终该
// 虚拟地址映射到物理地址一样的地方。
// *((unsigned int*)pte_ptr(0x2000000)) = 0x2000000 + 0x07;
// *((unsigned int*)pte_ptr(0x2000000)) = 0x2001000 + 0x07;
// 由于该地址已经映射了实际的物理内存,所以访问时不会出现
// 任何问题。需要注意的是,由于这里是页表第一项(从0开始)
// 是映射关系,所以如果把第一项也清零了,也会出现宕机的情况。
// 所以我们从第二项开始清零。
// for(i = 1; i < 1024; i++) {
// ((unsigned int*)0x2000000)[i] = 0;
// }
/*
当第一项清零的情况下,如果不加入此句是不会宕机的。是不是
非常的奇怪。这是因为只有执行了下面一句,处理器才按照内存
中实际的页表建立映射关系。不要忘了处理器还有自己的缓存。
下面的汇编语句是不是觉得有些怪怪的,是啊,他是AT&T的格式
格式的非扩展的嵌入式汇编语句,主要是函数调用得到形式要
切换到汇编文件中。注意了大伙, AT&T格式的汇编,源操作数和
目标操作数的顺序是相反的。
*/
// asm("mov %cr3, %eax;mov %eax, %cr3");
// printf_("\n%x %x\n", ((unsigned int*)0x2000000)[0],
// ((unsigned int*)0x2000000)[1]);
// i /= 0;
// asm("ud2");
// asm volatile ("movl $0xffffffff, %ebx;\
movl $0x0, %edx;\
movl $0xffffffff, %eax;\
mul %ebx;");
// asm volatile ("into");
// asm("int3");
// limit = 0xaaaa00000000;
// asm volatile("movl $0xaaaa, %eax;bound %eax, (_limit)");
// printf_("\n @@@@@@ NEW START! @@@@@@\n");
// asm volatile("movl $0xaaab, %eax;bound %eax, (_limit)");
/*
向主从芯片的数据端口写入数据,0xfb的二进制形式是11111011b,
也就是通过将第2位(从0计算)复位(清零),使向量0x22能够接收
中断,从而开启从芯片的所有中断。0xfe的二进制形式是11111110b,
正是从芯片的0x28向量对应的中断,该中断便是实时时钟中断。
*/
// out(0x21, 0xfb);
// out(0xa1, 0xfe);
struct bmp_buf_info bbi;
save_bmp((char*)&zzz, &bbi);
unsigned int* v = (unsigned int*)(0xe0000000 + 1024 * 128 * 4);
put_bmp_buf(v, 1024, 20, 20, &bbi);
/*
将表盘在这里描画。从而成为桌面图层的一部分。
*/
dial(930, 90);
/*
下面是鼠标位置坐标和鼠标控制结构。并对位置坐标进行了初始化。
*/
int mx = 200, my = 300;
struct mouse_dec mdec;
/*
把鼠标的缓冲区用我们预制的图形写好,以备图层管理函数使用。
*/
make_mouse_pointer(mouse_bin, 0x003098df);
// make_mouse_before(mouse_before, 0x003098df);
/*
在原始的没有图层的桌面上写了一串文字。
*/
put_gb2312_buf(video, 1024, 480, 400 - 16, 0x00d9d919,
"蜗牛");
put_gb2312_buf(video, 1024, 400, 400, 0x00d9d919,
"该不该搁下重重的壳");
put_gb2312_buf(video, 1024, 400, 416, 0x00d9d919,
"寻找到底哪里有蓝天");
put_gb2312_buf(video, 1024, 400, 432, 0x00d9d919,
"随着轻轻的风轻轻的飘");
put_gb2312_buf(video, 1024, 400, 448, 0x00d9d919,
"历经的伤都不感觉疼");
put_gb2312_buf(video, 1024, 400, 464, 0x00d9d919,
"我要一步一步往上爬");
put_gb2312_buf(video, 1024, 400, 480, 0x00d9d919,
"等待阳光静静看着它的脸");
put_gb2312_buf(video, 1024, 400, 496, 0x00d9d919,
"小小的天有大大的梦想");
put_gb2312_buf(video, 1024, 400, 512, 0x00d9d919,
"重重的壳裹着轻轻的仰望");
put_gb2312_buf(video, 1024, 400, 528, 0x00d9d919,
"我要一步一步往上爬");
put_gb2312_buf(video, 1024, 400, 544, 0x00d9d919,
"在最高点乘着叶片往前飞");
put_gb2312_buf(video, 1024, 400, 560, 0x00d9d919,
"小小的天留过的泪和汗");
put_gb2312_buf(video, 1024, 400, 576, 0x00d9d919,
"总有一天我有属于我的天");
/*
分别是桌面、鼠标、窗口0、窗口1的图层指针。
*/
struct SHEET* sht_back, * sht_mouse, *sht_win, *sht_win1;
/*
桌面图层缓冲区分配内存。
*/
void* buf_back = get_kernel_pages(up_pgs(1024 * (768 - 128) * 4));
desktop = buf_back;
/*
复制桌面当前内容当图层缓冲区,成为图层的一部分后,这些
东西上面图层将不能擦除。
*/
memcpy_((void*)buf_back, (void*)(video + 1024 * 128), 1024 *
(768 - 128) * 4);
/*
不通过拷贝直接往桌面图层中画一个黄色的实心圆形。
*/
// fill_circle(buf_back, 1024, 800, 300, 80, 0x00ffff00);
/*
分别是窗口0、窗口1的图层缓冲区内存分配、即颜色填充,同时画蛇添足
地往窗口中画了些图形、写了些文字。
*/
int* buf_win = get_kernel_pages(up_pgs(500 * 300 * sizeof(int)));
int* buf_win1 = get_kernel_pages(up_pgs(300 * 250 * sizeof(int)));
for(i = 0; i < 500 * 300; i++) {
buf_win[i] = 0x00cccccc;
}
for(i = 0; i < 300 * 250; i++) {
buf_win1[i] = 0x00cccccc;
}
fill_rectangle(buf_win1, 300, 180, 10, 80, 80, 0x00ff0000);
struct point a, b, c;
a.x = 80 + 200;
a.y = 5;
b.x = 20 + 200;
b.y = 200;
c.x = 150 + 200;
c.y = 210;
fill_triangle(buf_win, 500, &a, &b, &c, 0x00ffffff);
put_str_buf(buf_win, 500, 80, 80, 0x00ff0000,
"Hello, SnailOS. Good luck!...");
/*
下面是初始化、分配图层结构、设置图层基本信息、移动图层到
适当位置以及重置图层高度。
*/
shtctl = shtctl_init(video + 1024 * 128, 1024, 768 - 128);
sht_back = sheet_alloc(shtctl);
sht_mouse = sheet_alloc(shtctl);
sht_win = sheet_alloc(shtctl);
sht_win1 = sheet_alloc(shtctl);
sheet_setbuf(sht_back, buf_back, 1024, 768 - 128, -1);
sheet_setbuf(sht_mouse, mouse_bin, 16, 16, 0x003098df);
sheet_setbuf(sht_win, buf_win, 500, 300, -1);
sheet_setbuf(sht_win1, buf_win1, 300, 250, -1);
sheet_slide(sht_back, 0, 0);
sheet_slide(sht_win, 80, 200);
sheet_slide(sht_mouse, 300, 300);
sheet_slide(sht_win1, 2, 2);
sheet_updown(sht_back, 0);
sheet_updown(sht_mouse, 3);
sheet_updown(sht_win, 1);
sheet_updown(sht_win1, 2);
/*
这些变量将用于鼠标对图层的操作。
*/
int x, y, mmx = -1, mmy = -1;
struct SHEET* sht = NULL;
/*
既定定时器的设置。
*/
timer_set(t_m, 100, &main_thread->r, 0);
timer_set(t_m, 100, &main_thread->r, 1);
enable_extern_intr(0x28);
enable_extern_intr(0x20);
enable_extern_intr(0x21);
enable_extern_intr(0x2c);
enable_extern_intr(0x2e);
// out(0x21, 0xf8);
// out(0xa1, 0xae);
intr_enable();
thread_start("k_thread_a", 2, k_thread_a, " argA ", 1);
thread_start("k_thread_b", 2, k_thread_b, " argB ", 1);
thread_start("k_thread_c", 3, k_thread_c, " argC ", 1);
/*
创建空闲线程,把空闲线程放在运行级别的最低级上,则当除了主线程
之外没有其他线程或者进程运行时,空闲线程粉墨登场,从而避免了由于
主线程睡眠而造成的无进程运行的调试异常。
*/
thread_start("Idle", 1, idle, "idle ", 7);
/*
创建3个用户级进程,它们都运行在1的运行级上,且优先级为2(优先级
的设置归init_thread函数管)。
*/
process_execute(ua, "UA", 1);
process_execute(ub, "UB", 1);
process_execute(uc, "Uc", 1);
unsigned int keymap[0x80] = {
0,0,'1','2','3','4','5','6','7','8','9','0','-','=',0,0,
'q','w','e','r','t','y','u','i','o','p','[',']',0,0,'a','s',
'd','f','g','h','j','k','l',';','\'','`',0,'\\','z','x','c','v',
'b','n','m',',','.','/',0,'*',0,' ',0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
while(1) {
asm("cli");
/*
遍历各个线程的循环缓冲区,统一向其中发送整数0。从而使
每个线程的缓冲区都有数据,让线程的循环中显示信息的条件
成立。
*/
struct list_node* n;
struct task* t;
/*
这里我们也要改变一下,因为已经不是遍历一个队列,所以要
做个大循环来对每个就绪队列遍历。
*/
for(i = 0; i < 8; i++) {
thread_ready_list = &ready_list[i];
if(!double_linked_list_is_empty(thread_ready_list)) {
n = thread_ready_list->head.next;
while(n != &thread_ready_list->tail) {
struct task* t =
node2entry(struct task, general_tag, n);
ring_queue_in(&t->r, 0);
n = n->next;
}
}
}
sheet_refresh(sht_back, 700, 0, 1020, 300);
/*
如果ins_queue指令缓冲区为空,则不做任何操作。否则赋值指令
及其参数,并执行该指令。注意指令是用整数表示的。
*/
/*
当且仅当两个缓冲区都为空时才直接恢复中断。
*/
if(is_empty(&ins_queue) && ring_queue_is_empty(&running_thread()->r)) {
thread_block(BLOCKED);
asm("sti;hlt");
} else {
struct buf e;
e = ins_queue.buffer[queue_out(&ins_queue)];
/*
这里我们要消费掉鼠标和键盘中断发向缓冲区的数据。
*/
int d = ring_queue_out(&main_thread->r);
asm("sti");
instructions_switch(&e);
/*
打印缓冲区元素的数目,因为一直往缓冲区中填入数据,没有人读出,所以
缓冲区空间逐步减小,知道填满。
*/
/*
if(ring_queue_is_full(&running_thread()->r)) {
printf_("...%d...", running_thread()->r.free);
} else {
printf_("___%d___", running_thread()->r.free);
}
*/
/*
用于测试定时器的部分,当既定的时刻(滴答数)到来时,经由时钟
中断处理程序删除这个定时器,并向主线程发送数据0,这里接收到数据后,
就重新添加一个定时器,拟发送的数据为1,当次定时器到期时,时钟中断
处理程序也删除它,同时发送数据0。因此,形成定时循环显示文字的效果。
*/
if(d == 0) {
put_gb2312_buf(video, 1024, 200, 300, 0x00ff0000,
"我爱你蜗牛操作系统!");
timer_set(t_m, 100, &main_thread->r, 1);
}
if(d == 1) {
put_gb2312_buf(video, 1024, 200, 300, 0x000000ff,
"我爱你蜗牛操作系统!");
timer_set(t_m, 100, &main_thread->r, 0);
}
/*
这里我们键盘中断处理程序发送过来的数据,由于在原始数据的基础
上加上了0x100,所以这里要恢复原貌。
*/
if((d >= 0x100) && (d < 0x100 + 0x100)) {
if((d - 0x100) & 0x80) {
} else {
/*
!!!请特别的注意这里,它将printf_函数改为了内核专用的kprintf_
函数。这只要是因为主线程暂时不能使用互斥机制,因该函数的唤醒,不
依赖于互斥机制,而是由中断和循环缓冲区异步唤醒的。大家可以尝试一下,
当改为互斥机制的printf_函数时,系统会宕机。
*/
kprintf_("%c", keymap[(d - 0x100) & 0x7f]);
}
}
/*
这里我们鼠标中断处理程序发送过来的数据。
*/
if((d >= 0x100 + 0x100) && (d < 0x100 + 0x100 + 0x100)) {
if(mouse_decode(&mdec, d - 0x100 - 0x100)) {
/*
在鼠标未动之前,把鼠标指针消隐。
*/
// draw_mouse_before(video + 1024 * 128,
// mx, my, 16, 16, mouse_before);
/*
鼠标数据中的横向和纵向数据其实是相对与当前的增减数据。
*/
mx += mdec.x;
my += mdec.y;
/*
对数据在画面的范围进行判断。也就是横轴和纵轴的极值。
*/
if(mx < 0) {
mx = 0;
}
if(my < 0) {
my = 0;
}
if(mx > 1024 - 1) {
mx = 1024 - 1;
}
if(my > 768 - 1 - 128) {
my = 768 - 1 - 128;
}
/*
移动后,根据新的坐标位置重新描画鼠标指针。
*/
sheet_slide(sht_mouse, mx, my);
int btn = mdec.btn;
/*
这里是鼠标左键的处理。
*/
if(btn & 1) {
/*
mmx和mmy是窗口被移动前的坐标信息,它同时也表示窗口是否
是移动状态。窗口被移动的最高前提条件时,鼠标左键被按下,
而是否按在了窗口上,需要在从高往低遍历图层,只有当窗口位于鼠标
坐标范围内且不是透明色,才把窗口置为次顶层。显然mmx<0是
未被移动的状态,窗口也只有这两种状态。这时便可以通过mmx、
mmy记住窗口未移动前的位置。因为已经左击了窗口,所以窗口
位置升高到鼠标的下层(次顶层)。当未处于移动状态时,仅仅
点击了鼠标左键,则窗口位置提升后,退出循环。
*/
if(mmx < 0) {
for(j = shtctl->top - 1; j > 0; j--) {
sht = shtctl->sheets[j];
x = mx - sht->vx0;
y = my - sht->vy0;
if(0 <= x && x < sht->bxsize &&
0 <= y && y < sht->bysize)
{
if(sht->buf[y * sht->bxsize + x]
!= sht->col_inv) {
sheet_updown(sht, shtctl->top - 1);
mmx = mx;
mmy = my;
}
break;
}
}
/*
仅仅是mmx和mmy大于0并不能引起窗口的移动,只有在按下鼠标后,鼠标位置
的值发生变化了,才可能移动窗口图层。它们的差值加上图层在画面中的初始
位置作为移动窗口图层函数的参数。同时,在鼠标未按下时置图层移动标志为
-1,即不会移动。
*/
} else {
x = mx - mmx;
y = my - mmy;
sheet_slide(sht, sht->vx0 + x, sht->vy0 + y);
mmx = mx;
mmy = my;
}
} else {
mmx = -1;
}
if(btn & 2) {
/*
这里是鼠标右键的处理,现在未作任何处理。
*/
}
if(btn & 4) {
/*
这里是鼠标中键的处理,现在未作任何处理。
*/
}
}
}
}
}
}
/*
选择要执行的指令,这里只是随意安排的,目前仅仅是画实心矩形、
实心圆以及空心圆。
*/
void instructions_switch(struct buf* e) {
switch(e->instruction) {
case 0:
fill_rectangle((unsigned int*)e->argv[0], e->argv[1], e->argv[2],
e->argv[3], e->argv[4], e->argv[5], e->argv[6]);
break;
case 1:
fill_circle((unsigned int*)e->argv[0], e->argv[1], e->argv[2],
e->argv[3], e->argv[4], e->argv[5]);
break;
case 5:
draw_circle((unsigned int*)e->argv[0], e->argv[1], e->argv[2],
e->argv[3], e->argv[4], e->argv[5]);
break;
default:
break;
}
}
extern unsigned int ticks;
void init(void) {
ticks = 0;
/*
全局描述符表的初始化。
*/
gdt_init();
/*
分页机制的初始化。
*/
page_init();
/*
中断描述符表的初始化。
*/
idt_init();
/*
可编程中断控制器的初始化。
*/
i8259_init();
/*
实时时钟的初始化。
*/
rtc_init();
/*
发生频率的初始化。
*/
i8253_init();
/*
内存管理模块的初始化。
*/
mem_init();
thread_init();
screen_init_();
/*
一定要初始化指令队列。
*/
queue_init(&ins_queue);
keyboard_init();
mouse_init();
timer_man_init();
tss_init();
syscall_init();
hd_init();
}
unsigned int uav = 0, ubv = 0, ucv = 0;
void k_thread_a(void* arg) {
/*
每个线程都通过入队指令向指令队列中插入指令。当插入指令时,
为了防止竞争条件的发生,果断的关闭中断。
*/
struct buf e;
/*
通过临时变量,构造一个符合指令格式的完整指令。
*/
e.instruction = 0;
e.argc = 7;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 400;
e.argv[3] = 400;
e.argv[4] = 30;
e.argv[5] = 50;
e.argv[6] = 0x00ffff00;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
e.instruction = 0;
e.argc = 7;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 100;
e.argv[3] = 100;
e.argv[4] = 70;
e.argv[5] = 70;
e.argv[6] = 0x0000ff00;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
struct fat32_krn_p fat32_p;
read_mbr_dbr(&fat32_p);
fat32_krn = fat32_p;
unsigned int i;
for(i = 0; i < 0xffff; i++){
sema_post(pevent_);
}
char* s = file_read("hello.txt");
printf_("!!!%x %x %x!!!", fat32_krn.fat1_p, fat32_krn.root_dir_p,
fat32_krn.root_dir_start_sector);
printf_(s);
while(1) {
printf_(" THREAD A ");
mtime_sleep1(8000);
}
/*
*/
}
void k_thread_b(void* arg) {
struct buf e;
e.instruction = 0;
e.argc = 7;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 600;
e.argv[3] = 200;
e.argv[4] = 80;
e.argv[5] = 80;
e.argv[6] = 0x00ff0000;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
e.instruction = 5;
e.argc = 6;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 300;
e.argv[3] = 300;
e.argv[4] = 100;
e.argv[5] = 0x00ffffff;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
sema_pend(pevent_);
///*
char* s = file_read("y.bmp");
struct bmp_buf_info bbi;
save_bmp((char*)s, &bbi);
unsigned int* v = (unsigned int*)(0xe0000000 + 1024 * 128 * 4);
put_bmp_buf(v, 1024, 700, 300, &bbi);
while(1) {
printf_(" THREAD B ");
mtime_sleep1(9188);
}
/*
*/
}
void k_thread_c(void* arg) {
struct buf e;
e.instruction = 0;
e.argc = 7;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 600;
e.argv[3] = 200;
e.argv[4] = 50;
e.argv[5] = 100;
e.argv[6] = 0x00ff00ff;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
e.instruction = 1;
e.argc = 6;
e.t = running_thread();
e.argv[0] = 0xe0000000;
e.argv[1] = 1024;
e.argv[2] = 200;
e.argv[3] = 400;
e.argv[4] = 50;
e.argv[5] = 0x000f00f0;
// asm("cli");
// queue_in(&ins_queue, &e);
// asm("sti");
sema_pend(pevent_);
///*
char* s = file_read("l.bmp");
struct bmp_buf_info bbi;
save_bmp((char*)s, &bbi);
unsigned int* v = (unsigned int*)(0xe0000000 + 1024 * 128 * 4);
put_bmp_buf(v, 1024, 500, 320, &bbi);
while(1) {
printf_(" THREAD C ");
mtime_sleep1(10088);
}
/*
*/
}
/*
三个进程的实体,它们仅仅是玩了命的自增三个不同的全局
变量。显示变量的工作由内核进程来完成。
*/
void ua(void) {
while(1) {
// uav++;
/*
之所以这里没用,printf_打印变量信息,主要是因为它在几层
调用后含有特权指令,所以会出现一般保护异常。我们暂时选择
用全局变量在线程中显示。
*/
write(" PORCESS A");
msleep(8000);
}
}
void ub(void) {
while(1) {
write(" PORCESS B");
msleep(8000);
}
}
void uc(void) {
while(1) {
write(" PORCESS C");
msleep(8000);
}
}
void idle(void* arg) {
unsigned char* s = arg;
while(1) {
printf_(s);
__asm__ __volatile__ ("sti; hlt;");
}
}
同时在最后给大家一个完整kernel.c,从而可以看到系统实际运行的简单原貌。下图大家可以看到,真是美女如云啊,至此,我们Snail OS开发的第一阶段也就是这样了。拼一个自己的操作系统真是不容易啊!。
参考书目:
《操作系统真相还原》郑钢
《30天自制操作系统》川合秀实
《linux内核完全剖析》赵炯
《一个操作系统的实现》于渊