用C语言实现内存写入

  • naskfunc.nas中创建一个往指定内存写入内存地址的语句

    ; naskfunc
    ; TAB=4[FORMAT "WCOFF"]              ; 制作目标文件的模式
    [INSTRSET "i486p"]                ; 用来告诉nask,这个程序是给486用的;如果不指定,就会认为是只有16位寄存器的CPU
    [BITS 32]                       ; 制作32位模式用的机械语言; 制作目标文件的信息
    [FILE "naskfunc.nas"]         ; 源文件名信息GLOBAL  _io_hlt,_write_mem8     ; 程序中包含是的函数名; 以下是实际的函数
    [SECTION .text]                 ; 目标文件中写了这些之后再写程序_io_hlt:   ; void io_hlt(void);HLTRET; 将数据放到某个地址空间
    ; 如果在C语言中用到了write_mem8函数,就会跳转到_write_mem8
    _write_mem8:    ; void write_mem8(int addr, int data); MOV      ECX,[ESP+4]        ; [ESP+4]中存放的是地址,将其读入ECXMOV     AL,[ESP+8]     ; [ESP+8]中存放的是数据,将其读入ALMOV      [ECX],ALRET
    
  • 在指定内存地址的地方,不可以使用[CX]或[SP],但使用32位寄存器,[ECX]、[ESP]等都OK。
  • 如果与C语言联合使用,可以自由使用的只有EAX、ECX、EDX这3个。其它寄存器只能使用其值,而不能改变其值。
  • 这里虽然指定的是486,但并不是386中就不能用。到286为止CPU是16位,而386以后CPU是32位。
  • C语言—bootpack.c程序
    void io_hlt(void);
    void write_mem8(int addr, int data);void HariMain(void)
    {int i; /* 变量声明; i是一个32位整数 */int j = 1; // 不同颜色所代表的整数for (i = 0xa0000; i <= 0xaffff; i++) {write_mem8(i, j++); /* MOV BYTE [i],15 */}for (;;) {io_hlt();}
    }
    
  • 结果:

条纹图案

  • 修改bootpack.c

    void io_hlt(void);
    void write_mem8(int addr, int data);/* 显示出有条纹的图案 */
    void HariMain(void)
    {int i; /* 变量声明; i是一个32位整数 */for (i = 0xa0000; i <= 0xaffff; i++) {/* 低四位原封保留,而高四位全部都变成0,所以每个16个像素,色号就会反复一次 */write_mem8(i, i & 0x0f);}for (;;) {io_hlt();}
    }
    
  • 结果:

挑战指针

  • 替代write_mem3的指针语句

  • 指定变量的类型

    char占用的是1个字节 8位
    short占用2个字节 16位
    int占用4字节 32位
    
  • 所以使用指针之前要定义变量的类型
  • 而不管是“char *p”,还是“short *p”,还是“int *p”,变量p都是4字节,因为p是用于记录地址的变量。
  • 修改bootpack.c
    void io_hlt(void);void HariMain(void)
    {int i; /* 变量声明; i是一个32位整数 */char *p; /* *,变量p是用于内存地址到额专用变量 */for (i = 0xa0000; i <= 0xaffff; i++) {p = i; /* 带入地址 */*p = i & 0x0f;/*  这可以代替write_mem8(i, i & 0x0f);  */}for (;;) {io_hlt();}
    }
    
  • 类型转换
    在执行make run之后,会有警告。所以要进行类型转换

    也可以改成
  • 指针和汇编

  • 什么只声明了“char *p; ”却不仅能使用p,还可以使用*p

指针的应用

  • 可以将下面代码进行改写

    void io_hlt(void);void HariMain(void)
    {int i; /* 变量声明; i是一个32位整数 */char *p; /* *,变量p是用于内存地址到额专用变量 */for (i = 0xa0000; i <= 0xaffff; i++) {p = i; /* 带入地址 */*p = i & 0x0f;/*  这可以代替write_mem8(i, i & 0x0f);  */}for (;;) {io_hlt();}
    }
    
  • *(p + i )是6个字符,而p[i]只有4个字符

  • 将p[i]写成i[p]也是可以的

色号设定

  • 使用的是320 * 200的8位色彩模式,色号使用8位(二进制)数,也就是只能使用0 ~ 255的数
  • 8位色彩模式,0 ~ 255的数字和颜色一一对应(比如25号颜色对应#ffffff)

修改bootpack.c文件

修改HariMain
  • 代码

    void init_palette(void)
    {static unsigned char table_rgb[16 * 3] = {0x00, 0x00, 0x00,   /*  0:黑  */0xff, 0x00, 0x00,    /*  1:亮红 */0x00, 0xff, 0x00,    /*  2:亮绿 */0xff, 0xff, 0x00,    /*  3:亮黄 */0x00, 0x00, 0xff,    /*  4:亮蓝 */0xff, 0x00, 0xff,    /*  5:亮紫 */0x00, 0xff, 0xff,    /*  6:浅亮蓝 */0xff, 0xff, 0xff,   /*  7:白  */0xc6, 0xc6, 0xc6,    /*  8:亮灰 */0x84, 0x00, 0x00,    /*  9:暗红 */0x00, 0x84, 0x00,    /* 10:暗绿 */0x84, 0x84, 0x00,    /* 11:暗黄 */0x00, 0x00, 0x84,    /* 12:暗青 */0x84, 0x00, 0x84,    /* 13:暗紫 */0x00, 0x84, 0x84,    /* 14:浅暗蓝 */0x84, 0x84, 0x84    /* 15:暗灰 */};set_palette(0, 15, table_rgb);return;/* C语言中的static char语句只能用于数据,相当于汇编中的DB质量*/
    }
    
  • char a[3]的含义

    相当于
    a:
    RESB 3
    ————————————————————
    “RESB”, “RESW”, “RESD”, “RESQ” , “REST”
    它们声明 未初始化的存储空间

  • char a[3] = {1, 2, 3};的含义

    相当于:
    char a[3];
    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    但是这种写法会多耗费很多字节

  • static可以控制变量的作用域在局部作用域内
  • signedunsigned未指定型
    char、int、short都分signed和unsigned

    signed型用于处理-128~127的整数
    unsigned型能够处理0~255的整数
    未指定型是指没有特别指定时,可由编译器决定是unsigned还是signed

  • 0xff就是255,表示最大亮度
修改set_palette
  • 代码:

    void set_palette(int start, int end, unsigned char *rgb)
    {int i, eflags;eflags = io_load_eflags();  /* 记录中断许可标志的值 */io_cli();                   /* 将中断许可标志置为0,禁止中断 */io_out8(0x03c8, start);     /* 往指定装置里传送数据的函数 */for (i = start; i <= end; i++) {io_out8(0x03c9, rgb[0] / 4);io_out8(0x03c9, rgb[1] / 4);io_out8(0x03c9, rgb[2] / 4);rgb += 3;}io_store_eflags(eflags);  /* 复原中断许可标志 */return;
    }
    
  • CPU的管脚与很多设备相连(内存、键盘、网卡、声卡、软盘)。向设备发送电信号的是OUT指令;从设备取得电信号是IN指令。为了区别不同的设备,需要使用设备号码【port】。在C语言中没有IN或OUT指令相当的语句。
调色板功能的简单实现
  • 硬件在显示像素颜色时,从像素对应的显存读取这个八位数,然后把这个数当做下标,在RGB颜色组中找到对应的RGB颜色值,再把这个颜色值显示到对应的像素上
  • 显存调色板模式的设置方式:
  1. 关闭中断,防止CPU被干扰
  2. 将调色板的号码写入端口0x03c8, 由于我们现在只有一个调色板,因此调色板的编号默认为0,如果要设置多个调色板,那么其他调色板的编号可以一次为1,2…等
  3. 将RGB的颜色数值依次写入端口0x3c9, 假设我们要写入的RGB颜色值是
    0x848484 暗灰
    那么在C语言中,要分3次调用io_out8, 例如:
    io_out(0x3c9, 0x84);
    io_out(0x3c9, 0x84);
    io_out(0x3c9, 0x84);
  • 完整代码:

    void io_hlt(void);
    void io_cli(void);
    void io_out8(int port, int data);
    int io_load_eflags(void);
    void io_store_eflags(int eflags);/* 就算写在同一个源文件里,如果想在定义前使用,还是必须实现声明一下 */void init_palette(void);
    void set_palette(int start, int end, unsigned char *rgb);void HariMain(void)
    {int i; /* 声明变量。变量i是32位整数型 */char *p; /* 变量p是BYTE [...]用的地址 */init_palette(); /* 设定调色板 */p = (char *) 0xa0000; /* 指定地址 */for (i = 0; i <= 0xffff; i++) {p[i] = i & 0x0f;}for (;;) {io_hlt();}
    }void init_palette(void)
    {static unsigned char table_rgb[16 * 3] = {0x00, 0x00, 0x00,   /*  0:黑  */0xff, 0x00, 0x00,    /*  1:亮红 */0x00, 0xff, 0x00,    /*  2:亮绿 */0xff, 0xff, 0x00,    /*  3:亮黄 */0x00, 0x00, 0xff,    /*  4:亮蓝 */0xff, 0x00, 0xff,    /*  5:亮紫 */0x00, 0xff, 0xff,    /*  6:浅亮蓝 */0xff, 0xff, 0xff,   /*  7:白  */0xc6, 0xc6, 0xc6,    /*  8:亮灰 */0x84, 0x00, 0x00,    /*  9:暗红 */0x00, 0x84, 0x00,    /* 10:暗绿 */0x84, 0x84, 0x00,    /* 11:暗黄 */0x00, 0x00, 0x84,    /* 12:暗青 */0x84, 0x00, 0x84,    /* 13:暗紫 */0x00, 0x84, 0x84,    /* 14:浅暗蓝 */0x84, 0x84, 0x84    /* 15:暗灰 */};set_palette(0, 15, table_rgb);return;/* C语言中的static char语句只能用于数据,相当于汇编中的DB质量*/
    }void set_palette(int start, int end, unsigned char *rgb)
    {int i, eflags;eflags = io_load_eflags();  /* 记录中断许可标志的值 */io_cli();                   /* 将中断许可标志置为0,禁止中断 */io_out8(0x03c8, start);     /* 往指定装置里传送数据的函数 */for (i = start; i <= end; i++) {io_out8(0x03c9, rgb[0] / 4);io_out8(0x03c9, rgb[1] / 4);io_out8(0x03c9, rgb[2] / 4);rgb += 3;}io_store_eflags(eflags);  /* 复原中断许可标志 */return;
    }
    

修改naskfunc.nas文件

端口读写数据原理
  • 读取指定端口8位数据
  • 根据C语言的规约,执行RET语句时,EAX中的值就被看作是函数的返回值
    ;读取指定端口8位数据  int io_in8(int port);
    io_in8:mov edx, [esp+4]  ; 将端口号放入edx中mov eax, 0          ; 先将eax中原来的内容清空in al, dx        ; 将此端口号中的内容读取到al中ret
    
  • 向指定端口号写入8位数据
    ;向指定端口写入8位数据 void io_out8(int port, int value);
    io_out8:mov edx, [esp+4]    ; 将端口号放入edx中mov al, [esp+8]       ; 将数据放入esp中out dx, al           ; 将数据输出到某端口号中ret
    
eflags寄存器
  • 各个标志所在EFLAGS中的位置
  • CF(进位标志):产生进位或借位时CF = 1,否则CF = 0
  • PF(奇偶标志):1的个数为偶数,PF = 1,否则 PF = 0
  • ZF(零标志):相关指令执行后结果为0,那么ZF = 1,否则ZF = 0
  • SF(符号标志):结果为负SF = 1,非负 SF = 0
  • OF(溢出标志):有符号运算的结果是否发生了溢出,溢出OF = 1,否则OF = 0
  • AF(辅助进位标志):当进行加减运算时,若运算结果的低字节的低四位向高四位有进位或借位,则AF为1,否则为0
  • DF(方向标志):当DF为1时,每次操作后使变址寄存器SI和DI减小。这样就使串处理从高地址向低地址方向进行,当DF = 0,反之
  • IF(中断标志):当IF为1,允许CPU响应可屏蔽中断请求,否则,反之。
  • TF(跟踪标志):当TF为1时,每条指令执行完后产生中断,CPU处于单步运行方式。当TF位为0时,CPU正常工作,程序连续执行
  • PUSHFD:将标志位的值按双字节长压入栈。
  • POPFD:按双字节长将标志位从栈弹出
eflags寄存器的读写
  • 汇编语言能直接访问EFLAGS寄存器,因为汇编中没有 "mov eax, EFLAGS" 这种指令,pushfd 指令是专门把EFLAGS寄存器的内容压入堆栈的,使用指令 pushfdEFLAGS的数据压入堆栈,然后再从堆栈中,把压入的数据弹出到eax寄存器里面。
  • 根据C语言的规约,执行RET语句时,EAX中的值就被看作是函数的返回值
    ; naskfunc
    ; TAB=4[FORMAT "WCOFF"]              ; 制作目标文件的模式
    [INSTRSET "i486p"]                ; 使用到486为止的指令
    [BITS 32]                       ; 制作32位模式用的机器语言
    [FILE "naskfunc.nas"]         ; 源程序文件名GLOBAL  _io_hlt, _io_cli, _io_sti, _io_stihltGLOBAL _io_in8,  _io_in16,  _io_in32GLOBAL _io_out8, _io_out16, _io_out32GLOBAL    _io_load_eflags, _io_store_eflags[SECTION .text]_io_hlt:    ; void io_hlt(void);HLT   ; 使程序停止运行,处理器进入暂停状态,不执行任何操作,不影响标志。RET_io_cli:    ; void io_cli(void);CLI   ; 该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用RET   _io_sti:   ; void io_sti(void);STI   ; 允许中断发生,在STI起效之后,所有外部中断都被恢复,这样可以打破被保护代码的运行,允许硬件中断转而处理中断的作用RET_io_stihlt: ; void io_stihlt(void);STIHLTRET_io_in8:    ; int io_in8(int port);MOV      EDX,[ESP+4]        ; portMOV       EAX,0IN     AL,DXRET_io_in16:   ; int io_in16(int port);MOV     EDX,[ESP+4]        ; portMOV       EAX,0IN     AX,DXRET_io_in32:   ; int io_in32(int port);MOV     EDX,[ESP+4]        ; portIN        EAX,DXRET_io_out8:  ; void io_out8(int port, int data);MOV      EDX,[ESP+4]        ; portMOV       AL,[ESP+8]     ; dataOUT       DX,ALRET_io_out16:  ; void io_out16(int port, int data);MOV     EDX,[ESP+4]        ; portMOV       EAX,[ESP+8]        ; dataOUT       DX,AXRET_io_out32:  ; void io_out32(int port, int data);MOV     EDX,[ESP+4]        ; portMOV       EAX,[ESP+8]        ; dataOUT       DX,EAXRET_io_load_eflags:   ; int io_load_eflags(void);PUSHFD       ; PUSH EFLAGS 偲偄偆堄枴POP      EAXRET_io_store_eflags: ; void io_store_eflags(int eflags);MOV      EAX,[ESP+4]PUSH    EAXPOPFD        ; POP EFLAGS 偲偄偆堄枴RET
    

绘制矩形

  • 在当前画面模式中,画面上有320 * 200个像素
  • 假设左上点的坐标是(0,0),右下点的坐标是(319,199),那么像素坐标(x, y)对应的VRAM地址应按下式计算
  • 修改bootpack.c代码
    void io_hlt(void);
    void io_cli(void);
    void io_out8(int port, int data);
    int io_load_eflags(void);
    void io_store_eflags(int eflags);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);#define COL8_000000     0
    #define COL8_FF0000     1
    #define COL8_00FF00     2
    #define COL8_FFFF00     3
    #define COL8_0000FF     4
    #define COL8_FF00FF     5
    #define COL8_00FFFF     6
    #define COL8_FFFFFF     7
    #define COL8_C6C6C6     8
    #define COL8_840000     9
    #define COL8_008400     10
    #define COL8_848400     11
    #define COL8_000084     12
    #define COL8_840084     13
    #define COL8_008484     14
    #define COL8_848484     15void HariMain(void)
    {char *p; /* p变量的地址 */p = (char *) 0xa0000; /* 将地址赋值进去 */boxfill8(p, 320, COL8_FF0000,  20,  20, 120, 120);boxfill8(p, 320, COL8_00FF00,  70,  50, 170, 150);boxfill8(p, 320, COL8_0000FF, 120,  80, 220, 180);for (;;) {io_hlt();}
    }// VRAM 的地址;屏幕的长度;颜色;
    void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
    {int x, y;for (y = y0; y <= y1; y++) {for (x = x0; x <= x1; x++)vram[y * xsize + x] = c;}return;
    }
    

今天的成果

  • 代码

    void io_hlt(void);
    void io_cli(void);
    void io_out8(int port, int data);
    int io_load_eflags(void);
    void io_store_eflags(int eflags);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);#define COL8_000000     0
    #define COL8_FF0000     1
    #define COL8_00FF00     2
    #define COL8_FFFF00     3
    #define COL8_0000FF     4
    #define COL8_FF00FF     5
    #define COL8_00FFFF     6
    #define COL8_FFFFFF     7
    #define COL8_C6C6C6     8
    #define COL8_840000     9
    #define COL8_008400     10
    #define COL8_848400     11
    #define COL8_000084     12
    #define COL8_840084     13
    #define COL8_008484     14
    #define COL8_848484     15void HariMain(void)
    {char *vram;int xsize, ysize;vram = (char *) 0xa0000;// 屏幕的宽和高xsize = 320;ysize = 200;boxfill8(vram, xsize, COL8_008484,  0,         0,          xsize -  1, ysize - 29);boxfill8(vram, xsize, COL8_C6C6C6,  0,         ysize - 28, xsize -  1, ysize - 28);boxfill8(vram, xsize, COL8_FFFFFF,  0,         ysize - 27, xsize -  1, ysize - 27);boxfill8(vram, xsize, COL8_C6C6C6,  0,         ysize - 26, xsize -  1, ysize -  1);boxfill8(vram, xsize, COL8_FFFFFF,  3,         ysize - 24, 59,         ysize - 24);boxfill8(vram, xsize, COL8_FFFFFF,  2,         ysize - 24,  2,         ysize -  4);boxfill8(vram, xsize, COL8_848484,  3,         ysize -  4, 59,         ysize -  4);boxfill8(vram, xsize, COL8_848484, 59,         ysize - 23, 59,         ysize -  5);boxfill8(vram, xsize, COL8_000000,  2,         ysize -  3, 59,         ysize -  3);boxfill8(vram, xsize, COL8_000000, 60,         ysize - 24, 60,         ysize -  3);boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize -  4, ysize - 24);boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize -  4);boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize -  3, xsize -  4, ysize -  3);boxfill8(vram, xsize, COL8_FFFFFF, xsize -  3, ysize - 24, xsize -  3, ysize -  3);for (;;) {io_hlt();}
    }void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
    {int x, y;for (y = y0; y <= y1; y++) {for (x = x0; x <= x1; x++)vram[y * xsize + x] = c;}return;
    }
    

问题

  • 在set_palette函数中,为什么要将每个颜色的值除以4?

    最开始看到这个除以4,我首先想到的是在opencv在处理像素时,会通过一个简单的除法来进行颜色空间缩减以提高性能,但是重新读了代码后,知道不是一回事,将除数的值增大,色彩的亮度会变暗,如果不设除数,后8种色彩很难与黑色分辨,接着我去查阅了一些资料:
    电脑屏幕上的所有颜色,都由这红色绿色蓝色三种色光按照不同的比例混合而成的。一组红色绿色蓝色就是一个最小的显示单位。屏幕上的任何一个颜色都可以由一组RGB值来记录和表达。每三个确定一个颜色,分别代表红绿蓝的分配比例,分配的越多,代表的颜色就越深。这样就似乎明白作者为什么说只要16种颜色了。确实,16*16=256,
    我们只要修改这个/号除的数,就可以得到不同的颜色。就不进行除法操作那个来说,就是调色板的原色,而进行了除法操作的都是相应色调降低了的。色调是各种图像色彩模式下原色的明暗程度,级别范围从0到255,共256级色调。例如对灰度图像,当色调级别为255时,就是白色,当级别为0时,就是黑色,中间是各种程度不同的灰色。在RGB模式中,色调代表红、绿、蓝三种原色的明暗程度,对绿色就有淡绿、浅绿、深绿等不同的色调。色调
    是指色彩外观的基本倾向。在明度、纯度、色相这三个要素中,某种因素起主导作有用,可以称之为某种色调。实验的结果似乎是证明的原色的明暗程度的改变。
  • 为什么在设定调色板的时候要禁止中断?
    我没有查到具体的解释,只查到了在什么情况下需要关闭中断:
    1、操作的对象是共享资源(全局变量),其他任务可能在整个操作流程中打断你的操作,并且会更改你的全局变量,最终会引起非常严重的后果的情况下需要关中断。
    2、当任务在执行对时序要求非常高的操作,因为被其它任务中断浪费了很多时间,当回来的时候错过了dead
    time,最终导致操作失败。
    我只能推测在向端口写入信息的时候,要通过禁止中断,防止因中断而导致向端口写入了其他信息。

Day4—自制操作系统相关推荐

  1. 为什么《30天自制操作系统》封面中的猫是两只尾巴

    刚刚在一社区,发了一贴,被指出一问题,询一高人,得一答案.这便是我没有关注到的封面上的那只猫,我想这也是很多读者没有关注到的.因为在我微博的200转发贴中,并没有人提到封面中的猫为何有两只尾巴.于是咨 ...

  2. 发布在《30天自制操作系统》之前的帮助阅读贴

    说明:这是8月15日即将上市的一本新书,本文的摘选也可以命名为<30天自制操作系统>上市之前必读.本书幽默,有趣,可以说是技术书里的幽默书,让您读起来绝对不会感到乏味.在本书上市之前,您一 ...

  3. 如何读emmc里的引导程序_自制操作系统学习1 引导程序

    本系列学习有前面的汇编学习基础最好,如果没有影响也不大本系列学习主要资源来自<[30天自制操作系统].(川合秀实)>,<自己动手写操作系统>两本书 一.准备工作 bochs v ...

  4. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)--hello bitzhuwei's OS! 最初的OS代码 1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序的 ...

  5. 写在《30天自制操作系统》上市之前

       这本<30天自制操作系统>马上就要在各大书店和网上商城全面上架了,作为本书的4位译者之一,我负责翻译了本书约三分之二的内容.这是我参与翻译的第一本译著,我感到很激动也很紧张,因为我知 ...

  6. 自制操作系统(十) 图像叠加处理

    2016.07.12 参考书籍:<30天自制操作系统>.<自己动手写操作系统> qq:992591601  欢迎交流 图像叠加处理的原理很简单,就是给图像分层,从低下往上面画, ...

  7. 《30天自制操作系统》笔记(04)——显示器256色

    <30天自制操作系统>笔记(04)--显示器256色 进度回顾 从最开始的(01)篇到上一篇为止,已经解决了开发环境问题和OS项目的顶层设计问题. 本篇做一个小练习:设置显卡显示256色. ...

  8. 自制操作系统Antz -- 系列文章

    自制操作系统Antz day10--实现shell(上) AntzUhl 2018-10-10 16:25 阅读:192 评论:0 Linux内核源码分析 day01--内存寻址 AntzUhl 20 ...

  9. 操作系统源代码_计算机自制操作系统(八):仿生DOS操作系统源代码

    一.真机运行 我们已经完成了仿生DOS操作系统的制作,并在上一章的末尾给大家在虚拟机上做了演示.今天,我们要将该操作系统在真机上启动运行,是不是非常期待自己做出的第一款比较有意义的操作系统? 在&qu ...

最新文章

  1. 在Spring事务管理下,Synchronized为啥还线程不安全?
  2. [1204 寻找子串位置] 解题报告
  3. iOS项目功能模块封装SDK使用总结
  4. 服务器上传至云系统,上传至云服务器命令
  5. 【图像处理】图像强度变换、直方图均衡化(Image Intensity Transformations and Histogram Equalization)
  6. 20172329 2017-2018-2《程序设计与数据结构》课程总结
  7. FL Studio20.8.2(水果win10)中文版主要软件更新内容
  8. 【linux运维】linux运维常用工具有哪些?
  9. STM32 低功耗STOP模式,RTC唤醒
  10. 三层交换机配置实现不同网络互通
  11. 礼金记账本安卓_礼金记账本
  12. 有一字符串,包含n个字符。写一函数,将此字符串中从第m个字符开始的全部字符复制成 为另一个字符串
  13. 生成二进制反射格雷码
  14. Word行间距调整不了?学会这个技巧轻松调节行间距
  15. JDK之ZGC介绍.JAVAEE最新JDK剖析
  16. android 9.0 10.0 添加系统字体并且设置为默认字体
  17. 学Android开发的人可以去的几个网站
  18. 针对可视化平台页面组件的数据库设计
  19. python爬虫获取vpn代理
  20. Android项目实战记录

热门文章

  1. typescript基础
  2. 使用SC 修改服务启动账户
  3. 发布轻开平台移动App服务器
  4. 阿里P6+Java研发工程师,到底牛在哪儿?真了
  5. 淘晶驰串口屏_ 串口屏卡顿的原因
  6. 伏临扰雨(北京的雨季)
  7. java如何叠加图片_图片叠加效果Java代码实现
  8. Baizhi Memcached GJF
  9. mysql查询当前时间的前后几天时间
  10. Unity实现AR扫描图片