java开发系统内核:运行第一个应用程序

更详细的讲解和代码调试演示过程,请参看视频
Linux kernel Hacker, 从零构建自己的内核

如果你对机器学习感兴趣,请参看一下链接:
机器学习:神经网络导论

上一节,我们已经能够成功的通过type命令,将文件的内容打印到控制台上。能够读取文件内容是相当重要的一步,试想如果我把一个能运行在我们系统上的应用程序的二进制代码作为一个文件存储在磁盘上,当系统运行时,这些二进制代码能够被系统读取,当系统把这些二进制数据当做代码执行的话,那不意味着,我们的操作系统变成了一个平台,能够运行独立于它的应用程序了吗,我们本节的目的就是构建一个独立于系统内核的最简单的应用程序,然后让这个程序运行在我们的系统内核之上。

首先我们看看这个最简单的程序是怎样的,代码如下:

org 0
fin:
   jmp fin

上面三行汇编代码逻辑很简单,就是一个死循环, org 0 告诉编译器代码的加载地址是0,如果我们用系统的控制台进程运行这段程序的话,不难预见,控制台进程会被这个死循环程序给”锁死“,也就是运行后,控制台进程将不能再接收任何输入,进入死循环状态。

把上面的代码存成文件hlt.asm 然后使用nasm编译器编译成二进制代码,执行命令: nasm -o hlt.bat hlt.asm,本地目录下会生成二进制文件hlt.bat,查看该文件内容如下:
0xeb 0xfe 0x0a
也就是说,上面的汇编代码被编译成了三个字节,我们把这三个字节当做一个文件的内容,通过java程序写入磁盘,代码如下:

FileHeader header = new FileHeader();
        header.setFileName("abc");
        header.setFileExt("exe");
        byte[] date = new byte[2];
        date[0] = 0x11;
        date[1] = 0x12;
        header.setFileTime(date);
        header.setFileDate(date);
        String s = "abc.exe";
        int[] buf = new int[]{ 0xeb, 0xfe, 0x0a};
        byte[] bbuf = new byte[9];
        for (int i = 0; i < buf.length; i++) {
            bbuf[i] = (byte) (buf[i] & 0x0ff);
        }

        header.setFileContent(bbuf);
        fileSys.addHeader(header);

上面的代码,我们在上一节讲type命令的时候已经解释过了,这里我们把前面汇编代码编译后的二进制数据作为文件abc.exe的内容,写入到磁盘中,一旦系统内核加载后,这些数据会被拷贝到系统内存里面。

内存中的一段数据,既可以读取,也可以直接让CPU当做代码执行。上一节我们是把加载到内存的数据读取出来,这次,我们把这些数据当做代码来直接执行就可以了。要想实现该功能,我们只要找到这段数据在内存中的位置,然后使用一个段描述符指向这段内存,在描述符中,把这段内存设置成可执行代码段,接着让CPU加载这个段描述符,那么这段数据就会被当成代码给执行起来了。

在C语言实现的内核中,我们增加下面代码,用于加载指定文件的内容,在源代码文件golbal_define.h 中添加下面定义:

struct Buffer {
    unsigned char *pBuffer;
    int  length;
};

这个结构体中指针pBuffer 指向一块内存地址,这块内存存储着要执行的代码的二进制数据,length表示数据的长度。

在write_vga_desktop.c中添加下面代码:

void file_loadfile(char *name, struct Buffer *buffer) {
     struct FILEINFO *finfo = (struct FILEINFO*)(ADR_DISKIMG);
     char *s = memman_alloc(memman, 13);
     s[12] = 0;

     while (finfo->name[0] != 0) {
         int k;
         for (k = 0; k < 8; k++) {
             if (finfo->name[k] != 0) {
                 s[k] = finfo->name[k];
             }else {
                 break;
             }
         }

         int t = 0;
         s[k] = '.';
         k++;
         for (t = 0; t < 3; t++) {

             s[k] = finfo->ext[t];
             k++;
         }


         if (strcmp(name, s) == 1) {
             buffer->pBuffer = (char*)memman_alloc_4k(memman, finfo->size);
             buffer->length = finfo->size;
             char *p =  FILE_CONTENT_HEAD_ADDR;
             p += finfo->clustno * DISK_SECTOR_SIZE;
             int sz = finfo->size;
             int t = 0;
             for (t = 0; t < sz; t++) {
                 buffer->pBuffer[t] = p[t];
             }
             break;             
         }

         finfo++;

    }

    memman_free(memman, s, 13);
}

这个函数的逻辑,跟我们上一节讲解如何使用type命令输出文件内容时,如何根据文件名查找文件数据时所讲的算法步骤是一样的,只不过这里多做了一步,那就是找到文件对应的数据后,申请一块大小为4k的动态内存,把文件数据拷贝到内存中。

在console控制台进程的主函数中,我们增加一个命令叫hlt, 这个命令通过上面的函数读取abc.exe中的数据,然后使用一个全局段描述符指向存储这些数据的内存,同时把这块内存的属性设置为可执行代码段,接着让CPU加载这个描述符,进而就能让CPU把abc.exe文件中的数据当做代码执行了,具体实现代码如下:

void console_task(struct SHEET *sheet, int memtotal) {
.....
for(;;) {
    .....
    else if (strcmp(cmdline, "hlt") == 1) {
                      struct Buffer buffer;
                      file_loadfile("abc.exe", &buffer);

                      struct SEGMENT_DESCRIPTOR *gdt = 
                            (struct SEGMENT_DESCRIPTOR *)get_addr_gdt();
                      set_segmdesc(gdt+19, 0xfffff, buffer.pBuffer,
                            0x409a);
                      farjmp(0, 19*8);
                      memman_free_4k(memman, buffer.pBuffer, buffer.length);

                  }
    .....
}

.....
}

如果控制台进程接收到hlt 命令时,它调用函数file_loadfile把文件abc.exe中的数据加载到一块内存中,这块内存的起始地址对应的就是buffer.pBuffer,然后获得全局描述符表的起始地址,大家如果忘记了全局描述符表的知识,可以翻看早期课程,我们用第19个描述符来指向存储了abc.exe内容的内存,并通过set_segmdesc 函数把这块描述符的起始地址设置成buffer.pBuffer, 内存的长度设置成buffer.length, 这块内存的性质设置成可执行代码段,也就是最后一个参数0x409a所表示的意思,最后通过一个farjmp让cpu加载第19个描述符,由于描述符描述的是可执行代码段,因此cpu会跳转到描述符所指向的起始地址,将该地址起的数据当做代码来执行。

由于我们的”应用程序“执行的是死循环操作,因此控制台进程会被这个循环锁死,使得控制台不再接收键盘信息,代码运行后结果如下:

这里写图片描述

执行hlt命令后,如果再敲击键盘,控制台窗口将没有任何响应。控制台是被堵死了,但是硬件中断没有,所以如果此时点击tab键,那么输入焦点会从控制台转移到task_a窗口,此时点击键盘,键盘信息会显示到task_a窗口中。

我们把应用程序再改进一下,增添一条指令,cli, 也就是关中断:

org 0
cli
fin:
   jmp fin

代码编译后的二进制数据如下:
0xfa 0xeb 0xfe 0x0a

所增加的字节0xfa, 对应的就是指令CLI,上面的代码如果执行的话,那么所以硬件中断就会被关闭,于是尽管点击tab键,系统也不会有任何反应了:

这里写图片描述

更加详细的讲解和演示请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
主要特性Java 语言是简单的:Java 语言的语法与 C 语言和 C++ 语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java 丢弃了 C++ 中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java 语言不使用指针,而是引用。并提供了自动分配和回收内存空间,使得程序员不必为内存管理而担忧。Java 语言是面向对象的:Java 语言提供类、接口和继承等面向对象的特性,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为 implements)。Java 语言全面支持动态绑定,而 C++语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。Java语言是分布式的:Java 语言支持 Internet 应用的开发,在基本的 Java 应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括 URL、URLConnection、Socket、ServerSocket 等。Java 的 RMI(远程方法激活)机制也是开发分布式应用的重要手段。Java 语言是健壮的:Java 的强类型机制、异常处理、垃圾的自动收集等是 Java 程序健壮性的重要保证。对指针的丢弃是 Java 的明智选择。Java 的安全检查机制使得 Java 更具健壮性。Java语言是安全的:Java通常被用在网络环境中,为此,Java 提供了一个安全机制以防恶意代码的攻击。除了Java 语言具有的许多安全特性以外,Java 对通过网络下载的类具有一个安全防范机制(类 ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类 SecurityManager)让 Java 应用设置安全哨兵。Java 语言是体系结构中立的:Java 程序(后缀为 java 的文件)在 Java 平台上被编译为体系结构中立的字节码格式(后缀为 class 的文件),然后可以在实现这个 Java 平台的任何系统运行。这种途径适合于异构的网络环境和软件的分发。Java 语言是可移植的:这种可移植性来源于体系结构中立性,另外,Java 还严格规定了各个基本数据类型的长度。Java 系统本身也具有很强的可移植性,Java 编译器是用 Java 实现的,Java运行环境是用 ANSI C 实现的。Java 语言是解释型的:如前所述,Java 程序在 Java 平台上被编译为字节码格式,然后可以在实现这个 Java 平台的任何系统运行。在运行时,Java 平台中的 Java 解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。Java 是高性能的:与那些解释型的高级脚本语言相比,Java 的确是高性能的。事实上,Java运行速度随着 JIT(Just-In-Time)编译器技术的发展越来越接近于 C++。Java 语言是多线程的:在 Java 语言中,线程是一种特殊的对象,它必须由 Thread 类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为 Thread(Runnable) 的构造子类将一个实现了 Runnable 接口的对象包装成一个线程,其二,从 Thread 类派生出子类并重写 run 方法,使用该子类创建的对象即为线程。值得注意的是 Thread 类已经实现了 Runnable 接口,因此,任何一个线程均有它的 run 方法,而 run 方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。Java 语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为 synchronized)。Java 语言是动态的:Java 语言的设计目标之一是适应于动态变化的环境。Java 程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java 中的类有一个运行时刻的表示,能进行运行时刻的类型检查。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值