拼一个自己的操作系统(SnailOS 0.03的实现)

拼一个自己的操作系统 SnailOS 0.03的实现

拼一个自己的操作系统SnailOS0.03源代码-Linux文档类资源-CSDN下载

操作系统SnailOS学习拼一个自己的操作系统-Linux文档类资源-CSDN下载

SnailOS0.00-SnailOS0.00-其它文档类资源-CSDN下载

https://pan.baidu.com/s/19tBHKyzOSKACX-mGxlvIeA?pwd=i889

 

读取fat32文件系统的文件。

在真正的讲fat32文件系统读取文件的内容前,让我们先把几个bmp文件复制到我们的虚拟硬盘上。并且创建了hello.txt的文本文件。

5aed52325ed864a329dfe8a4c1123692.png

 

看到了吧,都是大美女吧!继续写下去是不是好有兴致。

e4bca3b0d521fb8ec6f9faaaab7a77b5.png

 

 

时至今日,fat32文件系统已经被ntfs、ext?等其他文件系统甩了多少条街都说不好了。但有一点确定的是最近还有很多U盘用的是该文件系统。其实,仔细想来也没有什么特殊的原因,主要就是该文件系统还是非常好用的。但我们选择fat32文件系统的唯一目的就是它相对来说还是比较简单的。也就是能用比较少的代码就完成我们的目标。其实,我们的目标更是简单和直接,就是读取文件内容,然后做一些简单的操作,比如我们可以输出文本文件的内容,我们可以在屏幕上显示图片等等。

现在的问题就转化为,如何用上一章的硬盘驱动程序,把某个lba28扇区的内容读取到内存中供我们使用这么简单了。网上关于fat32文件系统的文章可以用多如牛毛来形容,其详解每个都有独到之处,所以笔者根本就不想拷贝那些信息在这里班门弄斧。我们的思路是通过二进制文件查看器,从目录项检索开始,逐步高清,该如何读取文件内容。首先让我们用UE打开temp.vhd的硬盘镜像文件。然后我们来一波骚操作,逐步摸清fat32的底数。下面的图中我们看到了一片16进制的数据(中间),左边是该数据对应的16进制地址,右边是数据对应的ascii码值。这是硬盘的开头,粗估一下应该是主引导扇区。因此我们要找到一个存在于512字节的标记也就是0xaa55,这样就比较确定了。那么我们往下看看哪个纯蓝色的部分正好是我们要找的地方。可是我们的目的可不是看这个让人迷糊的引导扇区,我是要找到我们的文件内容所在的扇区。因此按照fat32文件系统的安排,我们知道文件的目录项中含有该内容,同时目录项中也还有文件名称。干脆我们在此处搜索文件名看看能不能一眼就找到目录项,从而访问到文件内容。

91cbd7c546ac287a09f0cb03148e2bbe.png

 

 

说干就干,在下图大家看到了吧,一眼熟呀,可不是一个文件名我们熟悉,上一节我们拷贝的文件都在这里排队呢?这不是天上直接就掉下个林妹妹吗。同时看到下图我们用纯蓝色圈出的部分,在目录项中它是文件开始的簇号低16位,也就是0x30df,而往前4个字节处的0x0000处是簇号的高16位。而我们要的是文件的起始扇区,计算方法就是网上查到的

起始扇区= 根目录起始扇区 + (起始簇号 - 2)* 8

而我们现在缺少的是根目录起始扇区数。仔细一想这个并不难啊,我们稍微往前看看不就是根目录的起始扇区了吗。也就是说,我们的根目录根部没有几个文件吗。

 

4e0d8dffa7445e401a28e70254652a1e.png

 

 

看下面的图,不出所料的话,这就是根目录开始的位置。而这里是字节数,我们要转化为扇区数才能算计算出来了。用0x303000除以512就应该是扇区数吧。让我们来掰着手指头算一下好了。这个结果是0x1818,现在套入那个公式,0x1818 + (0x30df - 2) * 8 = 0x1f900;

 

1d9412d31b63d186c3d7d312041ba13f.png

 

 

接下来就是要查找文件了。看下图一打眼,是不是文件内容。我搜索的是Snail OS!...因此不是在文件开头,文件开头是一堆空格了。看看左边的地址,0x33e0000,这个是字节的,所以要除以512得出我们文件的起始扇区数。正是前面推算出的0x1f900这个数。至此查找一个简单txt文件的就大功告成了。

 

26041f9dce5335bf3043ef1c9b55da87.png

 

 

不过那个根目录起始扇区数可是我们手工蒙出来的,也就是说只可意授不可言传。这样可不行呀。

我们这里也就不兜圈子了,按照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)处。

 

858e8c5c546ba78f3b9d0225a7980355.png

 

 

看到下图大家就明白了吧,这么简单就找到了bpb,而我们所需要的所有信息都在这里了。对了这是通过转到地址0x100000处得到的。接下来大家就没什么疑问了吧,通过访问bpb中的关键数据,我们就能一步一步的最终访问文件内容了。

 

7b8176c3384d883ea09358f244fc3004.png

 

 

下面我们就给出完整的代码,相信大家学了这么久,对这些代码不会有什么问题了。

 

 

【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开发的第一阶段也就是这样了。拼一个自己的操作系统真是不容易啊!。

 

58d9637d5bfcaa28267a94c7be99bd48.png

 

 

参考书目:

《操作系统真相还原》郑钢

《30天自制操作系统》川合秀实

《linux内核完全剖析》赵炯

《一个操作系统的实现》于渊

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_39410618

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值