zoukankan      html  css  js  c++  java
  • 自制操作系统笔记-第四章

    4.1 用C语言实现内存写入

    想要在屏幕上画点东西的话,只要在VRAM里写点什么,但是C语言没有直接写入内存地址的语句(其实有),我们创建一个这种函数。

    修改一下前面的naskfunc.nas,这是添加的部分:

    _write_mem8:    ; void write_mem8(int addr, int data);
            MOV        ECX,[ESP+4]        ; [ESP+4]中存放的是地址,将其读入ECX
            MOV        AL,[ESP+8]        ; [ESP+8]中存放的是数据,将其读入AL
            MOV        [ECX],AL
            RET

    类似C语言中的write_mem8(0x1234,0x56),动作上相当于 MOV BYTE[0x1234], 0x56。addr是address的缩写,表示地址。

    在C语言中如果用到write_mem8函数,就会跳转到_write_mem8(这个应该也是规定,书上并没有解释)。 参数指定的数字就会存放在内存中(又是规定):

    第一个参数存在 [ESP + 4]

    第二个参数存在 [ESP + 8]

    第三个参数存在 [ESP + 12]

    第四个参数存在 [ESP + 16]

    在指定内存地址的地方,如果使用16位寄存器指定[CX] 或 [SP] 之类的就会出错,但使用32位寄存器,[ECX]、[ESP]等都OK。基本上没有不能用的寄存器。指定地址时还可以往寄存器里加一个或减一个常数的方式,如上面的 [ESP + 4] 。

    与C语言联合使用的话,能自由使用的只有EAX、ECX、EDX这3个,至于其它寄存器只能使用其值,而不能改变其值,因为这些寄存器在C语言编译后生成的机器语言中,用于记忆非常重要的值。

    所以上面代码中读取第一个参数(即 MOV ECX, [ESP+4] )中用的是ECX,ECX就是与C语言联合使用时可以自由使用的寄存器,而这句中的ESP则只是使用(读取)其值。

    ---------------------------------------------------------------------------------

    naskfunc.nas 还加了一行  INSTRSET指令,告诉nask这个程序是给486用的,也就是说,EAX会被解释成寄存器,否则会把EAX理解为标签或常数。

    [FORMAT "WCOFF"]                ; 创建一个目标文件    
    [INSTRSET "i486p"]          ; 要使用486的指令(即这是32位CPU的程序),我猜i486p代表intel 486 program
    [BITS 32]                        ; 制作32位的机器码
    [FILE "naskfunc.nas"]            ; 源文件名

     这里虽然写着i486p,但并不是说386不能用,但必须是32位以上CPU,286以下的是16位CPU。

    ----------------------------------------------------------------------

    接下来修改C语言代码,这次导入变量:

    void io_hlt(void);
    void write_mem8(int addr, int data);
    
    void HariMain(void)
    {
        int i; /* 变量声明,i是一个32位整数*/
      //0x0000是显示内存的开始地址,见第二章的内存分布图,
        for (i = 0xa0000; i <= 0xaffff; i++) { //一共是65536个点,320*200 = 64000
        // 1111b = 15 (b 表示 binary 即二进制),这应该是4位色,一共只能表示16种颜色 write_mem8(i,
    15); /* MOV BYTE [i],15 */ } for (;;) { io_hlt(); } }
    for (;;) {}就是无限循环
    上面利用for循环,将内存的0xA0000到0xAFFFF,全部设为了15(白色),其实在asmhead.nas中,分辨率设的是320*200,也就是64000,十六进制也就是FA00, 所以将上面代码上的0xaffff改为0xafa00,启动后也一样是全白屏。如果改为0xa7d00( 0x7D00 = 3200 = 64000/2),则启动画面是半个白屏:如下:
    -----------------------------------------------------

    -------------------------------------------------------

    2.2 条纹图案 

    上面的代码会显示为全屏白色,修改bootpack.c,显示为垂直条纹图案:

        for (i = 0xa0000; i <= 0xaffff; i++) {
            write_mem8(i, i & 0x0f); /* MOV BYTE [i],15 */
        }

    & 表示按位与算 ,可将指定位转为0, A & B ,B中的要转换为0的位要设为0,不变的位设为1。IP地址中子网掩码用的就是这个特性。

    | 表示按位或运算, 可将指定位转为1,A | B,B中的要转换为1的位要设为1,不变的为设为0。

    异或,XOR, 可将指定位反转, A XOR B,B中的要转换位要设为1,不变的位设为0.

    i & 0x0f 就是 i & 00001111与运算,也就是 i 的低4位不变,高4位变0,(再高的位因为没有值,也是按0处理的,我理解)。所以,所有的 i 经过运算后:

    0xa0000 & 0x0f,就变成了 0x00000 (即0)

    0xa0001 & 0x0f ,就变成了 0x00001 (即1)

    0xa0002 & 0x0f ,就变成了 0x00002,(即2)

    ...

    0xa000f & 0x0f,就变成了 0x0000f,(即15)

    0xa0010 & 0x0f,就变成了 0x00000,(即0)

    也就是说都是只有最后一位不变,(这里说的是十六进制,其实0x0f二进制是00001111,对于二进制其实是低4位不变)。最后的结果就是:

    00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 04 ...

    每16个数就循环一次,效果就是:

    4.3 挑战指针

    write_mem8(i, i & 0x0f);

    这句可以换成:

    *i = i & 0x0f; //这里i是个地址值,*i在C语言中表示这个地址指向的内存空间,*i=x 就是往这个地址中写入x这个值。

    但这样写会报错。因为上面这句C语句与下面这句汇编语句 编译成“机器码”后是等价的:

    MOV [i], (i & 0x0f)

    MOV [x],y 这样写会报错,因为指定内存[x]时不知道要写入目标内存的数据类型是BYTE、WORD、还是DWORD,只有在后一个操作数y也是寄存器时才能省略类型。

    因为不知道[i]是BYTE、WORD、还是DWORD,所以就会出错。

    char *p;  //变量p是用于内存地址的变量,也就是指针

    p里放入与i相同的值,然后执行:

    *p =  i & 0x0f;

    这样C编译器认为 “p是地址专用变量,用于存放char类型的,所以是BYTE,另外:

    char i  是BYTE 1字节

    short i 是 WORD  2字节

    int i      是 DWORD  4字节

    因为我们是一个字节一个字节写入内存,所以使用char,也就是BYTE。

    但是

    char *p 、short *p、int *p 变量p都是4字节,因为这里p是记录地址的变量,在汇编语言中地址像ECX一样用4字节的寄存器来指定。

    ------------------------------------------------------------

    bootpack.c:

    void io_hlt(void);
    
    void HariMain(void)
    {
        int i; /* 变量声明,i是一个32位整数 */
        char *p; /* 变量p用于 BYTE型 地址 */
    
        for (i = 0xa0000; i <= 0xaffff; i++) {
            p = i; /* 代入地址 */
            *p = i & 0x0f;
            /* 这可以替代 write_mem8(i, i & 0x0f);  */
        }
    
        for (;;) {
            io_hlt();
        }
    }

    执行make run 后可以正常运行,但是命令行 中有个警告:

    指针是表示内存地址的数值,类型转换是改变数值类型的命令。C语言中不用内存地址,而是用”指针“。在C语言中如果将普通整数值赋给内存地址变量(指针),就会警告,所以可以这样写:

    p = (char *) i;

    这就对i 进行了类型转换,使之成为了表示内存地址的整数,(其实数值没变,但对C编译器来说,类型不同差别很大),这样就不会再警告了。

    现在实现了用C语言写内存功能。write_mem8没用可以删了。(naskfunc.nas)。

    学习到72页 to continue...

  • 相关阅读:
    腾讯TBS加载网页无法自适应记录
    filter过滤器实现验证跳转_返回验证结果
    Oracle不连续的值,如何实现查找上一条、下一条
    springmvc.xml 中 <url-pattern></url-pattern>节点详解
    spring拦截器-过滤器的区别
    (转)spring中的拦截器(HandlerInterceptor+MethodInterceptor)
    @Value("${xxxx}")注解的配置及使用
    mybatis BindingException: Invalid bound statement (not found)
    spring声明式事务管理方式( 基于tx和aop名字空间的xml配置+@Transactional注解)
    Spring事务管理详解_基本原理_事务管理方式
  • 原文地址:https://www.cnblogs.com/johnjackson/p/12327671.html
Copyright © 2011-2022 走看看