当前位置: 首页 > news >正文

30天开发操作系统 第 20 天 -- API

前言

大家早上好,今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。
为什么这样的功能称为“系统调用”(system call)呢?因为它是由应用程序来调用(操作)系统中的功能来完成某种操作, 这个名字很直白吧。
“API” 这个名字就稍微复杂些,是“application program interface" 的缩写, 即“应用程序(与系统之间的)接口”的意思。请大家把这两个名字记住哦,考试题目中会有的哦.……开玩笑啦,这些其实用不着记啦。 有记这些单词的工夫,还不如多享受一下制作操作系统的乐趣呢。
这值得纪念的第一次,我们就来做个在命令行窗口中显示字符的API吧。BIOS中也有这个功能哦,如果忘了的话请重新看看第二天的内容。怎么样,找到了吧?无论什么样的操作系统, 都会有功能类似的API,这可以说是必需的。

一、程序整理

现在这程序是怎么回事!下面来改造一下我们操作系统, 让它可以使用API吧…
尤其是console task,简直太不像样了。看着如此混乱的程序代码,真是提不起任何干劲来进行改造, 我们还是先把程序整理一下吧。
由于只是改变了程序的写法,并没有改变程序处理的内容,因此这里就不讲解了。 从249行改到了85行的console_task, 哦耶!

console.c
void console_task(struct SHEET *sheet, unsigned int memtotal)
{struct TIMER *timer;struct TASK *task = task_now();struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;int i, fifobuf[128], *fat = (int *) memman_alloc_4k(memman, 4 * 2880);struct CONSOLE cons;char cmdline[30];cons.sht = sheet;cons.cur_x =  8;cons.cur_y = 28;cons.cur_c = -1;fifo32_init(&task->fifo, 128, fifobuf, task);timer = timer_alloc();timer_init(timer, &task->fifo, 1);timer_settime(timer, 50);file_readfat(fat, (unsigned char *) (ADR_DISKIMG + 0x000200));/* 显示提示符 */cons_putchar(&cons, '>', 1);for (;;) {io_cli();if (fifo32_status(&task->fifo) == 0) {task_sleep(task);io_sti();} else {i = fifo32_get(&task->fifo);io_sti();if (i <= 1) { /* 光标用定时器 */if (i != 0) {timer_init(timer, &task->fifo, 0); /* 下次置 0 */if (cons.cur_c >= 0) {cons.cur_c = COL8_FFFFFF;}} else {timer_init(timer, &task->fifo, 1); /* 下次值 1 */if (cons.cur_c >= 0) {cons.cur_c = COL8_000000;}}timer_settime(timer, 50);}if (i == 2) {	/* 光标ON */cons.cur_c = COL8_FFFFFF;}if (i == 3) {	/* 光标 OFF */boxfill8(sheet->buf, sheet->bxsize, COL8_000000, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15);cons.cur_c = -1;}if (256 <= i && i <= 511) { /* 键盘数据(通过任务A) */if (i == 8 + 256) {/* 退格键 */if (cons.cur_x > 16) {/* 用空格擦除光标后将光标前移一位 */cons_putchar(&cons, ' ', 0);cons.cur_x -= 8;}} else if (i == 10 + 256) {/* Enter *//* 将光标用空格擦除后换行 */cons_putchar(&cons, ' ', 0);cmdline[cons.cur_x / 8 - 2] = 0;cons_newline(&cons);cons_runcmd(cmdline, &cons, fat, memtotal);	/* 运行命令 *//* 显示提示符 */cons_putchar(&cons, '>', 1);} else {/* 一般字符 */if (cons.cur_x < 240) {/* 显示一个字将之后将光标后移一位 */cmdline[cons.cur_x / 8 - 2] = i - 256;cons_putchar(&cons, i - 256, 1);}}}/* 重新显示光标 */if (cons.cur_c >= 0) {boxfill8(sheet->buf, sheet->bxsize, cons.cur_c, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15);}sheet_refresh(sheet, cons.cur_x, cons.cur_y, cons.cur_x + 8, cons.cur_y + 16);}}
}void cons_putchar(struct CONSOLE *cons, int chr, char move)
{char s[2];s[0] = chr;s[1] = 0;if (s[0] == 0x09) {	/* 制表符 */for (;;) {putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, " ", 1);cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}if (((cons->cur_x - 8) & 0x1f) == 0) {break;	/* 被32整除则break */}}} else if (s[0] == 0x0a) {	/* 换行 */cons_newline(cons);} else if (s[0] == 0x0d) {	/* 回车 *//* 先不做操作 */} else {	/* 一般字符 */putfonts8_asc_sht(cons->sht, cons->cur_x, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 1);if (move != 0) {/* move为0时光标不后移 */cons->cur_x += 8;if (cons->cur_x == 8 + 240) {cons_newline(cons);}}}return;
}void cons_newline(struct CONSOLE *cons)
{int x, y;struct SHEET *sheet = cons->sht;if (cons->cur_y < 28 + 112) {cons->cur_y += 16; /* 到下一行 */} else {/* 滚动 */for (y = 28; y < 28 + 112; y++) {for (x = 8; x < 8 + 240; x++) {sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];}}for (y = 28 + 112; y < 28 + 128; y++) {for (x = 8; x < 8 + 240; x++) {sheet->buf[x + y * sheet->bxsize] = COL8_000000;}}sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);}cons->cur_x = 8;return;
}void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, "mem") == 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, "cls") == 0) {cmd_cls(cons);} else if (strcmp(cmdline, "dir") == 0) {cmd_dir(cons);} else if (strncmp(cmdline, "type ", 5) == 0) {cmd_type(cons, fat, cmdline);} else if (strcmp(cmdline, "hlt") == 0) {cmd_hlt(cons, fat);} else if (cmdline[0] != 0) {/*不是命令,也不是空行*/putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);cons_newline(cons);cons_newline(cons);}return;
}void cmd_mem(struct CONSOLE *cons, unsigned int memtotal)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;char s[30];sprintf(s, "total   %dMB", memtotal / (1024 * 1024));putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);sprintf(s, "free %dKB", memman_total(memman) / 1024);putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);cons_newline(cons);return;
}void cmd_cls(struct CONSOLE *cons)
{int x, y;struct SHEET *sheet = cons->sht;for (y = 28; y < 28 + 128; y++) {for (x = 8; x < 8 + 240; x++) {sheet->buf[x + y * sheet->bxsize] = COL8_000000;}}sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);cons->cur_y = 28;return;
}void cmd_dir(struct CONSOLE *cons)
{struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);int i, j;char s[30];for (i = 0; i < 224; i++) {if (finfo[i].name[0] == 0x00) {break;}if (finfo[i].name[0] != 0xe5) {if ((finfo[i].type & 0x18) == 0) {sprintf(s, "filename.ext   %7d", finfo[i].size);for (j = 0; j < 8; j++) {s[j] = finfo[i].name[j];}s[ 9] = finfo[i].ext[0];s[10] = finfo[i].ext[1];s[11] = finfo[i].ext[2];putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, s, 30);cons_newline(cons);}}}cons_newline(cons);return;
}void cmd_type(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo = file_search(cmdline + 5, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);char *p;int i;if (finfo != 0) {/* 找到文件的情况 */p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));for (i = 0; i < finfo->size; i++) {cons_putchar(cons, p[i], 1);}memman_free_4k(memman, (int) p, finfo->size);} else {/* 没有找到文件的情况 */putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);cons_newline(cons);}cons_newline(cons);return;
}void cmd_hlt(struct CONSOLE *cons, int *fat)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo = file_search("HLT.HRB", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;char *p;if (finfo != 0) {/* 找到文件的情况 */p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);farjmp(0, 1003 * 8);memman_free_4k(memman, (int) p, finfo->size);} else {/* 没有找到文件的情况 */putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);cons_newline(cons);}cons_newline(cons);return;
}
file.c
struct FILEINFO *file_search(char *name, struct FILEINFO *finfo, int max)
{int i, j;char s[12];for (j = 0; j < 11; j++) {s[j] = ' ';}j = 0;for (i = 0; name[i] != 0; i++) {if (j >= 11) { return 0; /* 没有找到 */ }if (name[i] == '.' && j <= 8) {j = 8;} else {s[j] = name[i];if ('a' <= s[j] && s[j] <= 'z') {/* 蒋小写字母转换为大写字母 */s[j] -= 0x20;} j++;}}for (i = 0; i < max; ) {if (finfo[i].name[0] == 0x00) {break;}if ((finfo[i].type & 0x18) == 0) {for (j = 0; j < 11; j++) {if (finfo[i].name[j] != s[j]) {goto next;}}return finfo + i; /* 找到文件 */}
next:i++;}return 0; /* 没有找到 */
}

嗯嗯,比之前的代码易读多了。你看,只要想把代码写得清爽些就一定能做到的,连笔者都 做到了嘛(笑)。这个例子说明,如果持续增加新的功能,一个函数的代码就会变得很长,像这 样定期整理一下还是很有帮助的。 好了,我们来“make run”,输人一些命令试试看。和之前运行的情况一样,很好。

二、显示单个字符的API

现在我们要开始做显示单个字符的API了哦。 说起来其实也不是很难, 只要应用程序能用某 种方法调用cons putchar就可以了。
首先我们做一个测试用的应用程序, 将要显示的字符编码存人AL寄存器, 然后调用操作系统的函数, 字符就显示出来了。

[BITS 32]MOV AL,'A’CALL (cons_putchar的地址)
fin:HLTJMP fin

就是这个样子。CALL是一个用来调用丽数的指令。在C语言中,goto和函数调用的处理方式 完全不同,不过在汇编语言中,CALL指令和JMP指令其实差不多是一码事,它们的区别仅仅在 当执行CALL指令时,为了能够在接下来执行RET指令时正确返回,会先将要返回的目标地 于, 址PUSH到栈中。
关于CALL指令这里想再讲一下。有人可能会想,直接写CALLcons putchar不就好了吗?然 而,hlt.nas这个应用程序在汇编时并不包含操作系统本身的代码,因此汇编器无法得知要调用的 函数地址,汇编就会出错。要解决这个问题,必须人工查好地址后直接写到代码中。在对haribote.sys进行make的时候, 通过一定的方法我们可以查出cons putchar的地址, 没有问题, 那S 么我们就来查一下地址… 且慢!

这样做有个问题, 因为cons_putchar是用C语言写的函数, 即便我们将字符编码存入寄存器 函数也无法接收, 因此我们必须在CALL之前将文字编码推入栈才行, 但这样做也太麻烦了。
没办法,我们只好用汇编语言写一个用来将寄 存器的值推入栈的函数了。这个函数不是应用 程序的一部分, 而是写在操作系统的代码中, 因此我们要改写的是naskfunc.nas。 另一方面, 在 应用程序中, 我们CALL的地址不再是cons_putchar, 而是变成了新写的 asm cons_putchar:

_asm_cons_putchar:PUSH	1AND		EAX,0xff	; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态PUSH	EAXPUSH	(cons的地址)CALL	_cons_putcharADD		ESP,12		; 丢弃栈中数据RET

PUSH的特点是后进先出,因此这个程序的顺序没问题。(这个12,是因为我们push了三次4个字节(32位),运行时栈的增长是从高地址向低地址,大家还记得吧😄)
栈传递(Stack Passing):

调用函数最常见的方法是将参数依次压入堆栈。调用函数后,函数通过堆栈访问这些参数。

push arg3
push arg2
push arg1
call function_name
add esp, 12 ; 清理堆栈(假设3个参数,每个4字节)
大家按照这个理解

这段程序的问题在于 “cons的地址" 到底是多少。应用程序是不知道这个地址的, 唔,那么只能让操作系统把这个地址事先保存在内存中的某个地方 用程序来指定地址难以实现。哪里比较好呢?对了, 就保存在BOOTINFO之前的0x0fec这个地址吧。
现在操作系统这边的工作已经完成了,因此我们先来“ make”一下, 注意这里不是"make run"。因为应用程序还没有准备好呢,所以我们先make。
make完成后, 除了haribote.sys之外, 还会生成一个叫bootpack.map的文件 首然 之前我们一直忽略这个文件的, 不过这次它要派上用场了。
这是一个文本文件,用文本编辑器打开:其中应该可以找到这样一行:
0x00000BE3 :_asm_cons putchar
这就是 _asm_cons putchar 的地址了,我们将地址填在应用程序中:

_asm_cons_putchar:PUSH	1AND		EAX,0xff	; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态PUSH	EAXPUSH	(cons的地址)CALL	_cons_putcharADD		ESP,12		; 丢弃栈中数据RET
void console_task(struct SHEET *sheet, unsigned int memtotal)
{...char cmdline[30];cons.sht = sheet;cons.cur_x =  8;cons.cur_y = 28;cons.cur_c = -1;*((int *) 0x0fec) = (int) &cons; /*这里!*/...
}

现在操作系统这边的工作已经完成了,因此我们先来“ make”一下, 注意这里不是"make run"。因为应用程序还没有准备好呢,所以我们先make。
make完成后, 除了haribote.sys之外, 还会生成一个叫bootpack.map的文件 首然 之前我们一直忽略这个文件的, 不过这次它要派上用场了。
这是一个文本文件,用文本编辑器打开:其中应该可以找到这样一行:
0x00000BE3 :_asm_cons putchar
这就是 _asm_cons putchar 的地址了,我们将地址填在应用程序中:

[BITS 32]MOV		AL,'A'CALL    0xbe3
fin:HLTJMP		fin

然后再进行汇编就可以了,很简单吧。

说起来, 我们写的这些代码里面, 哪个部分是API呢?“MOVE AL, A’”和“CALL 0xbe3” ,就是API了,因为API就是由应用程序来使用操作系统所提供的服务。当然,我们这个是否达到“服务” 的程度就另当别论了。
现在我们的应用程序也已经完成了, “make run”嘿!然后在命令行窗口里面运行 “hlt" 就可以了。
啊! qemu.exe出错关闭了!看来我们遇了一个不得了的大bug。 在真机环境下无法预料会造成什么后果, 因此请大家不要尝试。下面我们来解决这个bug。

像这样会造成模拟器出错关闭的bug, 果然只有在开发操作系统时才能碰到。 如果不用模拟器进行开发的话,不经意间产生的bug有时可能会造成电脑损坏、硬盘被格式化等严重问题, 也许好几天都无法恢复过来。 开发操作系统就是这么刺激。如果通过这次的bug, 大家能够瞥见这种刺激的冰山一角,那么这个bug也算是有点用的吧(苦笑)。

不过,光扯刺激啦什么的也无济于事, 我们还得仔细寻找原因。哦,原来如此,找到了!
原因其实很简单。应用程序对API执行CALL的时候,千万不能忘记加上段号。因此我们不能使用普通的CALL,应用程序所在的段为 “1003 * 8", 而操作系统所在的段为“2*8”,而应该使用far-CALL。
far-CALL实际上和far-JMP一样,只要同时指定段和偏移量即可。

[BITS 32]MOV		AL,'A'CALL    2*8:0xbe3
fin:HLTJMP		fin

好,完工了,这样bug应该就解决了, 我们来试试看。 make run然后运行“hlt"。还是不行。这次虽然没有出错关闭,但qemu.exe停止响应了。
这个问题是由于_asm_cons_putchar的RET指令所造成的。普通的RET指令是用于普通的 CALL 的返回,而不能用于far-CALL的返回,既然我们用了far-CALL,就必须相应地使用far-RET, 也就是RETF指令。因此我们将程序修改一下。

asm_cons_putchar:
(中略)
RETF ;这里!

好啦, 这次应该没问题了吧。
在这里插入图片描述

3.结束应用程序

照现在这个样子,应用程序结束之后会执行HLT,我们就无法在命令行窗口中继续输入命令 了,这多无聊啊。如果应用程序结束后不执行HLT,而是能返回操作系统就好了。
怎样才能实现这样的设想呢?没错,只要将应用程序中的HLT改成RET, 就可以返回了。相应地,操作系统这边也需要用CALL来代替JMP启动应用程序才对。虽说是CALL,不过因为要调用的程序位于不同的段, 所以实际上应该使用far-CALL, 因此应用程序那边也应该使用RETF。 我们的方针已经明确了。

C语言中没有用来执行far-CALL的命令,我们来创建一个farcall函数,这个函数和farjmp大同小异。

_farcall:		; void farcall(int eip, int cs);CALL	FAR	[ESP+4]				; eip, csRET

我们还要把hlt命令的处理改为调用farcall。

void cmd_hlt(struct CONSOLE *cons, int *fat)
{	...if (finfo != 0) {...farcall(0, 1003 * 8); /* 这里 */} else {}...
}

最后我们还要改写一下应用程序hlt.nas, 把HLT换成RETF就可以了。

[BITS 32]MOV		AL,'A'CALL    2*8:0xbe3RETF

完工了哦。好, 我们来 make run 然后运行“hlt”。貌似是有bug (今天我们碰了好几个钉子了嘛)。 qemu.exe又停止响应了, 明白了。由于我们改写了操作系统的代码, 怎么回事呢?导致asm_cons_putchar的地址 发生了变化。重新查看bootpack.map,我们发现地址变成了这样:
0x0000BE8 : asm_cons putchar
因此, 我们把应用程序的地址修改一下:

[BITS 32]MOV		AL,'A'CALL    2*8:0xbe8RETF

'make run", “hlt",怎么样?好了!成功了!
在这里插入图片描述

趁热打铁, 我们再来个新的尝试: hello” 显示

貌似用循环比较好呢?算了,实在太麻烦(笑)。我们运行一下试试看, 结果如下。
在这里插入图片描述

话说回来,现在这个应用程序已经和当初“hlt"这个名字完全对不上号了, 看来我们得赶快给它改改名字了哦。

4.不随操作系统版本而改变的API

所以说我们又要改写console.c。等等,如果修改了操作系统的代码,岂不是asm_cons_ putchar的地址也会像上次那样发生变化?难道说每次我们修改操作系统的代码,都得把应用程序的代码也改一遍?这也太麻烦了。

虽说确实有的操作系统版本一改变,应用程序 也得重新编译,不过还有些系统即便版本改变,应用程序也照样可以运行,大家觉得哪种更好呢?

把这个搞定之后, 我们再考虑命名的事。
解决这个问题的方法其实有很多,这里先为大家介绍其中一种。
CPU中有个专门用来注册函数的地方,也许大家一下子想不起来,其实是中断处理 程序。在前面我们曾经做过“当发生IRQ-1的时候调用这个函数”这样的设置, 大家还记得吗? 这是在IDT中设置的。
而CPU用于通知异常状态的中断最多也只有32种,这些都在CPU规格说 反正IRQ只有0~15, 明书中有明确记载。 不过,IDT中却最多可以设置256个函数,因此还剩下很多没有使用的项。
我们的操作系统从这些项里面借用一个的话, CPU应该也不会有什么意见的吧。所以我们就从IDT中找一个空闲的项来用一下。好,我们就选0x40号(其实0x30~0xff都是空闲的,只要在这个范围内任意一个都可以),并将_asm_ cons_putchar注册在这里。

void init_gdtidt(void)
{...set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);return;
}

我们只要用INT 0x40来代原来的CALL 2*8:0xbd1就可以调用_asm_ cons_putchar了。这样一来,很方便吧?我们来修改一下应用程序吧。

[BITS 32]MOV		AL,'h'INT		0x40MOV		AL,'e'INT		0x40MOV		AL,'l'INT		0x40MOV		AL,'l'INT		0x40MOV		AL,'o'INT		0x40RETF

于是程序变成了这个样子。看到这里,直觉敏锐的你也许已经发现了“跟调用BIOS的时候差不多嘛….” 虽然INT号不同,但通过INT方式调用这一点的确是非常类似。说起来, 没错,MS-DOS的API采用的也是这种INT方式。
另外,使用INT指令来调用的时候会被视作中断来处理, 需要使用 IRETD指令,用RETF是无法返回的, 我们还要改写_asm_ cons_putchar。

_asm_cons_putchar:STIPUSH	1AND		EAX,0xff	; 将AH和EAX的高位置0,将EAX置为已存入字待编码的状态PUSH	EAXPUSH	DWORD [0x0fec]	; 读取内存并PUSH该值CALL	_cons_putcharADD		ESP,12		; 丢弃栈中的数据IRETD	;这里!

用INT调用时,对于CPU来说相当于执行了中 断处理程序,因此在调用的同时CPU会自动执 但我们只是用它来代替CALL使用,这种做法就显得画蛇添足了。 行CLI指令来禁止中断请求。 我们可不想看到“API处理时键盘无法输入”这样的情况, 因此需要在开头添加一条STI指令。
对于这种问题,一般来说可以通过在注册到IDT时修改设置来禁止CPU擅自执行CLI, 其实, 最近貌似懒到家了,得反省一下。 不过这个有点麻烦, 还是算了吧(笑)。

make run → 结果如下:

你看,用这种方法还能把应用程序缩小。这是因为far-CALL指令需要7个字节而INT指令只需要2个字节的缘故。 这次修改还真是一箭双雕呢。
在这里插入图片描述

5.应用程序自由命名

现在我们的应用程序只能用hit这个名字,下面我们来让系统支持其他应用程序名,这次我们就用hello吧。 将console.c中的 “hlt” 改成“hello", 好啦, 这样我们就可以用hello这个应用程序 …··.!别生气别生气,开个玩笑而已(😆)。
好吧, 我们先来改写cons runcmd。

void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, "mem") == 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, "cls") == 0) {cmd_cls(cons);} else if (strcmp(cmdline, "dir") == 0) {cmd_dir(cons);} else if (strncmp(cmdline, "type ", 5) == 0) {cmd_type(cons, fat, cmdline);} else if (cmdline[0] != 0) {if (cmd_app(cons, fat, cmdline) == 0) {/*从此开始*//*不是命令,不是应用程序,也不是空行*/putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);cons_newline(cons);cons_newline(cons);}}/*到此结束 */return;
}

总结一下修改的地方,首先是去掉了cmd_hlt, 并创建了新的cmd_app。
这个函数用来根据命令行的内容判断文件名,并运行相应的应用程序 ,如果找到文件则返回1,没有找到文件则返回0。 现在程序的工作过程是:当输入的命令不是me m、cls、dir、type其中之一时,则调用cmd_app, 如果返回0则作为错误处理。 这样应该能行。
我们在cmd_hlt的基础上稍作修改后得到cmd_app函数, 具体内容如下。

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo;struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;char name[18], *p;int i;/*根据命令行生成文件名*/for (i = 0; i < 13; i++) {if (cmdline[i] <= ' ') {break;}name[i] = cmdline[i];}name[i] = 0; /*暂且将文件名的后面置为0*//* 寻找文件 */finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);if (finfo == 0 && name[i - 1] != '.') {/*由于找不到文件,故在文件名后面加上“.hrb”后重新寻找*/name[i    ] = '.';name[i + 1] = 'H';name[i + 2] = 'R';name[i + 3] = 'B';name[i + 4] = 0;finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);}if (finfo != 0) {/*找到文件的情况*/p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);farcall(0, 1003 * 8);memman_free_4k(memman, (int) p, finfo->size);cons_newline(cons);return 1;}/*没有找到文件的情况*/return 0;
}

我们在程序上动了一点脑筋,使得无论输入“hlt” 还是”“hlt.hrb” 都可以启动。因为在Windows命令行窗口中:不管加不加后面的 .exe都可以运行程序,所以我们就借鉴了这个设计。

差不多完工了,我们将hlt.nas改名为hello.nas, 然后汇编生成hello.hrb。 接下来 make run 用dir命令确认一下磁盘中的内容,再输入“hello”。 ha!出来了!成功了!
在这里插入图片描述

不错!我们再来输人“hlt”试一下,这个文件现在已经没有了, 会不会报错呢?另外, 嗯, 如果输入"hello.hrb” 能否正常运行呢?我们来试试看。
在这里插入图片描述

出现错误信息了, 加上扩展名的情况也可以,太完美了。

6.小心寄存器

hello.hrb的大小现在是21个字节,能不能再让它变小点呢?我们做了如下修改,用了一个循环。

[INSTRSET "i486p"]
[BITS 32]MOV		ECX,msg
putloop:MOV		AL,[CS:ECX]CMP		AL,0JE		finINT		0x40ADD		ECX,1JMP		putloop
fin:RETF
msg:DB	"hello",0

改成这样后make一下,hello.hrb变成了26个字节,居然还大了5个字节,哎,好失望。不过, 这样改也有好处,即便以后要显示很长的字符符,程序也不会变得太大。
在这里插入图片描述

为啥只显示出一个呢? 再把hello.nas仔细检查一遍,也没发现什么不对劲的地方啊···
那问题肯定出在操作系统身上。 既然应用程序没问题, 不过,到底是哪里有问题呢?刚刚找到了点眉目,我们给_asm_cons_putchar添上2行代码,就是PUSHAD和POPAD。

_asm_cons_putchar:STIPUSHAD	; 这里!PUSH	1AND		EAX,0xff	; PUSH	EAXPUSH	DWORD [0x0fec]	; CALL	_cons_putcharADD		ESP,12		; POPAD	; 这里!IRETD

为什么要这么改我们待会儿再讲,先来试验一下。
在这里插入图片描述

果然是这个问题呀。 那为什么会想到加上PUSHAD和POPAD呢?因为推测这有可能是 INT 0x40之后ECX寄存器的值发生了变化所导致的,应该是_cons_putchar改动了ECX的值。因此,我们加上了PUSHAD和POPAD确保可以将全部寄存器的值还原,这样程序就能正常运行了。

7.用API显示字符串

能显示字符申的API远比只能显示单个字符的API要来的方便, 从实际的应用程序开发角度来说,因为一次显示一串字符的情况比一次只显示个字符的情况多得多。从其他操作系统的显示字符申的API来看,一般有两种方式:一种是显示一串字符,遇到字符编码0则结束;另一种是先指定好要显示的字串的长度再显示。我们到底要用哪一种呢? 再三考虑之后,我们打算同时实现两种方式(笑)。

void cons_putstr0(struct CONSOLE *cons, char *s)
{for (; *s != 0; s++) {cons_putchar(cons, *s, 1);}return;
}void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{int i;for (i = 0; i < l; i++) {cons_putchar(cons, s[i], 1);}return;
}

哦,对了, 有了这个函数,就可以简化mem、 dir、type这几个命令的代码,趁着还没忘记, 赶紧改良一下。

void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, "mem") == 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, "cls") == 0) {cmd_cls(cons);} else if (strcmp(cmdline, "dir") == 0) {cmd_dir(cons);} else if (strncmp(cmdline, "type ", 5) == 0) {cmd_type(cons, fat, cmdline);} else if (cmdline[0] != 0) {if (cmd_app(cons, fat, cmdline) == 0) {/*不是命令,不是应用程序,也不是空行*/cons_putstr0(cons, "Bad command.\n\n");/* 这里 */}}return;
}void cmd_mem(struct CONSOLE *cons, unsigned int memtotal)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;char s[60];/*从此开始*/sprintf(s, "total   %dMB\nfree %dKB\n\n", memtotal / (1024 * 1024), memman_total(memman) / 1024);cons_putstr0(cons, s);/*到此结束*/return;
}void cmd_dir(struct CONSOLE *cons)
{struct FILEINFO *finfo = (struct FILEINFO *) (ADR_DISKIMG + 0x002600);int i, j;char s[30];for (i = 0; i < 224; i++) {if (finfo[i].name[0] == 0x00) {break;}if (finfo[i].name[0] != 0xe5) {if ((finfo[i].type & 0x18) == 0) {sprintf(s, "filename.ext   %7d\n", finfo[i].size);for (j = 0; j < 8; j++) {s[j] = finfo[i].name[j];}s[ 9] = finfo[i].ext[0];s[10] = finfo[i].ext[1];s[11] = finfo[i].ext[2];cons_putstr0(cons, s);/*这里!*/}}}cons_newline(cons);return;
}void cmd_type(struct CONSOLE *cons, int *fat, char *cmdline)
{struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;struct FILEINFO *finfo = file_search(cmdline + 5, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);char *p;if (finfo != 0) {p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));cons_putstr1(cons, p, finfo->size);/*这里!*/memman_free_4k(memman, (int) p, finfo->size);} else {cons_putstr0(cons, "File not found.\n");/*这里!*/}cons_newline(cons);return;
}

代码缩减了12行,什么嘛!一开始就这样写不就好了吗?不过不管怎么说也算是个值得高兴 的事吧。
在上面字符串中我们使用了“\n” 这个新的符号, 这里来讲解一下。在C语言中, “\”这个 字符有特殊的含义,用来表示一些特殊字符。 这里出现的“\n” 代表换行符,即0x0a,也就是说用2个字符来表示1个字节的信息, 有点怪吧。此外还有 “\t”, 它代表制表符, 即0x09。顺便说一 下, 换行符“\n” 之所以用“n”,是因为它是“new line" 的缩写。

我们已经有了cons_putstr0 和 cons_putstr1,那么怎样把它们变成API呢?最简单的方法就是像显示单个字符的API那样, 分配INT0x41和INT0x42来调用这两个函数。 不过这样一来, 只能设置256个项目的IDT很快就会被用光。
既然如此,我们就借鉴BIOS的调用方式, 在寄存器中存人功能号, 使得只用1个INT就可以用来选择调用不同的函数。 在BIOS中, 存放功能号的寄存器一般是AH, 我们也可以照搬, 但这样最多只能设置256个API函数。而如果我们改用EDX来存放功能号, 就可以设置多达42亿个API函数。这样总不会不够用了吧。

功能号暂时按下面那样划分, 寄存器的用法也是随意设定的, 如果不喜欢的话尽管修改就好哦。

功能号1………….显示单个字符(AL=字符编码)
功能号2… 显示字符串0(EBX=字符串地址)
功能号3………显示字符串1(EBX=字符串地址,ECX=字符串长度)
接下来我们将_asm_cons_putchar改写成一个新的函数。

_asm_hrb_api:STIPUSHAD	; 用于保存寄存器值的PUSHPUSHAD	; 用于向hrb_api传值的PUSHCALL	_hrb_apiADD		ESP,32POPADIRETD

这个函数非常短, 因为我们想尽量用C语言来编写API处理程序, 而且这样大家也更容易理解。
用C语言编写的API处理程序如下:

void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);} else if (edx == 2) {cons_putstr0(cons, (char *) ebx);} else if (edx == 3) {cons_putstr1(cons, (char *) ebx, ecx);}return;
}

嗯,还是挺好理解的吧。开头的寄存器顺序是按照PUSHAD的顺序写的,如果在_asm_hrb_api 中不用PUSHAD,而是个一个分别去PUSH的话,那当然可以按照自己喜欢的顺序来。
啊,对了对了,我们还得改一下IDT的设置,将INT 0x40改为调_asm_hrb_api。

void init_gdtidt(void)
{...set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x40, (int) asm_hrb_api,      2 * 8, AR_INTGATE32);return;
}

这样改写之后, 现在的hello.nas就无法正常运行了, 因为我们需要往EDX里面存人1才能调用相应的API。虽说我们加上一条向EDX中存入1的指令就可以, 不过既然已经写好了cons_putstr0, 那就于脆用这个新的API写一个hello2.nas吧。

[INSTRSET "i486p"]
[BITS 32]MOV		EDX,2MOV		EBX,msgINT		0x40RETF
msg:DB	"hello",0

完工了, 好, 赶紧 运行 "hello2” 试试看。 make run
在这里插入图片描述

……貌似失败了,怎么回事昵?今天已经很累了, 脑子都不转了,我们还是明天再来找原因吧。总之,我们先将这个放在一边,在以前的hello.nas中加一条EDX= 1;试试看吧。

[INSTRSET "i486p"]
[BITS 32]MOV		ECX,msgMOV		EDX,1 ;这里!
putloop:MOV		AL,[CS:ECX]CMP		AL,0JE		finINT		0x40ADD		ECX,1JMP		putloop
fin:RETF
msg:DB	"hello",0

在这里插入图片描述

成功了, 总算稍稍松了口气。 今天我们在最后的最后碰了个大钉子(就是hello2),心情有点不爽, 不过已经困得不行了, 就先到这吧!大家明天见。

总结

今天我们在最后的最后碰了个大钉子(就是hello2),心情有点不爽, 不过已经困得不行了, 就先到这吧!
祝大家元宵节快乐,团团圆圆,巳巳如意!
我们明天见!

相关文章:

30天开发操作系统 第 20 天 -- API

前言 大家早上好&#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢&#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…...

DeepSeek处理自有业务的案例:让AI给你写一份小众编辑器(EverEdit)的语法着色文件

1 DeepSeek处理自有业务的案例&#xff1a;让AI给你写一份小众编辑器(EverEdit)的语法着色文件 1.1 背景 AI能力再强&#xff0c;如果不能在企业的自有业务上产生助益&#xff0c;那基本也是一无是处。将企业的自有业务上传到线上训练&#xff0c;那是脑子进水的做法&#xff…...

在香橙派5 NPU上使用Yolov5

【香橙派】使用NPU部署Yolov5的完整解决方案 香橙派使用NPU部署Yolov5的完整解决方案 Orangepi 5 Pro(香橙派5pro)部署yolov5 RK3588实战&#xff1a;调用npu加速&#xff0c;yolov5识别图像、ffmpeg发送到rtmp服务器 香橙派5 RK3588 yolov5模型转换rknn及部署踩坑全记录 orang…...

常用排序算法

1. 基础排序算法 1.1 冒泡排序&#xff08;Bubble Sort&#xff09; 原理&#xff1a; 依次比较相邻元素&#xff0c;将较大的元素逐步"冒泡"到右侧。 def bubble_sort(arr):n len(arr)for i in range(n):swapped Falsefor j in range(0, n-i-1):if arr[j] >…...

MySQL判空函数--IFNULL函数的使用

文章目录 IFNULL函数介绍IFNULL函数的语法举例相关扩展 IFNULL函数介绍 在MySQL中&#xff0c;IFNULL函数用于判断给定的表达式是否为NULL。如果表达式为NULL&#xff0c;则IFNULL函数返回指定的替代值&#xff1b;如果表达式不为NULL&#xff0c;则返回表达式本身的值。 IFN…...

Git 设置代理

设置 HTTP 和 HTTPS 代理 运行以下命令来配置 Git 的 HTTP 和 HTTPS 代理&#xff1a; git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy https://127.0.0.1:7890 验证代理设置 你可以通过以下命令检查代理是否设置成功&#xff1a; g…...

nsc account 及user管理

从安全角度&#xff0c;推荐使用sign 模式进行nats account及用户管理 把权限放到account level 用户密码泄露可以通过快速更换用户可以设置过期日期&#xff0c;进行安全轮换 此外通过nsc 管理用户和权限&#xff0c;可以统一实现全局管控&#xff0c;包括subject管控&#…...

llama.cpp部署 DeepSeek-R1 模型

一、llama.cpp 介绍 使用纯 C/C推理 Meta 的LLaMA模型&#xff08;及其他模型&#xff09;。主要目标llama.cpp是在各种硬件&#xff08;本地和云端&#xff09;上以最少的设置和最先进的性能实现 LLM 推理。纯 C/C 实现&#xff0c;无任何依赖项Apple 芯片是一流的——通过 A…...

NO.18十六届蓝桥杯备战|循环嵌套|乘法表|斐波那契|质数|水仙花数|(C++)

循环嵌套 循环嵌套的使⽤ while &#xff0c; do while &#xff0c; for &#xff0c;这三种循环往往会嵌套在⼀起才能更好的解决问题&#xff0c;就是我们所说的&#xff1a;循环嵌套。这三种循环都可以任意嵌套使⽤ ⽐如&#xff1a; 写⼀个代码&#xff0c;打印⼀个乘法⼝…...

用什么办法能实现ubuntu里面运行的自己开发的python程序能自动升级。

要实现Ubuntu中自己开发的Python程序自动升级&#xff0c;可以通过以下几种方式&#xff1a; 1. 使用 Git 仓库 定时任务 如果你的Python程序托管在Git仓库中&#xff0c;可以通过定时拉取最新代码来实现自动升级。 步骤&#xff1a; 确保Python程序在Git仓库中。在Ubuntu上…...

org.apache.kafka.common.errors.TimeoutException

个人博客地址&#xff1a;org.apache.kafka.common.errors.TimeoutException | 一张假钞的真实世界 使用kafka-console-producer.sh向远端Kafka写入数据时遇到以下错误&#xff1a; $ bin/kafka-console-producer.sh --broker-list 172.16.72.202:9092 --topic test This is …...

【AI实践】deepseek支持升级git

当前Windows 11 WSL的git是2.17&#xff0c;Android Studio提示需要升级到2.19版本 网上找到指导文章 安装git 2.19.2 cd /usr/src wget https://www.kernel.org/pub/software/scm/git/git-2.19.2.tar.gz tar xzf git-2.19.2.tar.gz cd git-2.19.2 make prefix/usr/l…...

IntelliJ IDEA 2024.1.4版无Tomcat配置

IntelliJ IDEA 2024.1.4 (Ultimate Edition) 安装完成后&#xff0c;调试项目发现找不到Tomcat服务&#xff1a; 按照常规操作添加&#xff0c;发现服务插件中没有Tomcat。。。 解决方法 1、找到IDE设置窗口 2、点击Plugins按钮&#xff0c;进入插件窗口&#xff0c;搜索T…...

利用Firewalld和Iptables实现IP端口限制与开放

这里写目录标题 前言一、FirewalldIP端口限制1.1 确认启动状态1.2 启动Firewalld1.3 查看当前连接到Nacos的IP1.4 添加访问规则1.5 重新加载配置1.6 查看当前活动的规则列表1.7 移除某个规则 二、Firewalld 开放端口2.1 开放 6379端口2.2 重新加载防火墙2.3 验证规则 三、Iptab…...

两步在 Vite 中配置 Tailwindcss

第一步&#xff1a;安装依赖 npm i -D tailwindcss tailwindcss/vite第二步&#xff1a;引入 tailwindcss 更改配置 // src/main.js import tailwindcss/index// vite.config.js import vue from vitejs/plugin-vue import tailwindcss from tailwindcss/viteexport default …...

单片机原理与运用

个人主页&#xff1a;java之路-CSDN博客(期待您的关注) 目录 一、走进单片机的世界 二、单片机是什么 &#xff08;一&#xff09;定义与本质 &#xff08;二&#xff09;与普通计算机的区别 三、单片机的工作原理深度剖析 &#xff08;一&#xff09;硬件组成及功能 &am…...

MYSQL-数据恢复与备份

个人主页&#xff1a;java之路-CSDN博客(期待您的关注) 目录 数据的重要性与备份恢复的意义 MySQL 备份大揭秘 备份类型大盘点 备份工具展示台 备份实操全流程 MySQL 恢复大作战 恢复原理深剖析 恢复方法大集合 实战案例大放送 备份与恢复的最佳实践 备份策略制定法 …...

【Java】实现后端请求接口

【Java】实现后端请求接口 【一】使用 HttpURLConnection 实现四种请求方式的示例【1】Get请求【2】POST请求【3】PUT请求【4】DELETE 请求【5】汇总工具类&#xff0c;通过传参实现4种请求 【二】HttpClient 实现四种请求方式的示例【1】GET请求【2】POST 请求【3】PUT 请求【…...

人工智能之深度学习的革命性突破

深度学习的革命性突破 深度学习是机器学习的一个子领域&#xff0c;通过模拟人脑神经网络的结构和功能&#xff0c;实现对复杂数据的高效处理。近年来&#xff0c;深度学习在计算机视觉、自然语言处理、语音识别等领域取得了革命性突破。本文将深入探讨深度学习的核心架构、突…...

JavaScript document.write()介绍(直接将内容写入HTML文档的早期方法,已逐渐被现代DOM操作方法取代)

文章目录 **一、基本语法****二、核心功能**1. **在文档加载阶段写入**2. **文档加载后调用会导致覆盖****三、注意事项**1. **覆盖风险**2. **性能问题**3. **XSS 漏洞**4. **已关闭的文档流** **四、使用场景&#xff08;不推荐&#xff0c;但需了解&#xff09;****五、现代…...

mybatis-plus逆向code generator pgsql实践

mybatis-plus逆向code generator pgsql实践 环境准备重要工具的版本供参考pom依赖待逆向的SQL 配置文件CodeGenerator配置类配置类说明 环境准备 重要工具的版本 jdk1.8.0_131springboot 2.7.6mybatis-plus 3.5.7pgsql 14.15 供参考pom依赖 <?xml version"1.0&quo…...

【二叉树学习7】

力扣236.二叉树的最近公共祖先 链接: link 思路 要找p&#xff0c;q的公共祖先&#xff0c;可以从下往上遍历二叉树&#xff0c;而二叉树的后序遍历是天然的从下往上遍历。这题采用的是递归的方法&#xff0c;递归结束条件就是root为null或者rootp或者rootq就结束递归。 然后…...

LabVIEW显微镜成像偏差校准

在高精度显微镜成像中&#xff0c;用户常常需要通过点击图像的不同位置&#xff0c;让电机驱动探针移动到指定点进行观察。然而&#xff0c;在实际操作中&#xff0c;经常会遇到一个问题&#xff1a;当点击位于图像中心附近的点时&#xff0c;探针能够相对准确地定位&#xff1…...

什么是弧形光源

工业检测中的弧形光源是一种专门设计用于机器视觉和自动化检测的照明设备,通常用于提供均匀、高对比度的照明,以增强图像采集质量,便于检测系统识别和分析目标物体的特征。以下是关于工业检测弧形光源的详细介绍: 特点 1均匀照明: 弧形设计能够提供均匀的漫反射光,减少阴…...

Linux性能分析工具Trace使用

Linux Trace是⼀种⽤于抓取和分析系统运⾏时信息的⼯具。允许开发⼈员跟踪和分析系统的各种活动,以便深⼊了解系统的性能、⾏为和故障。下⾯是关于Linux Trace数据抓取的说明: 1. 数据抓取范围:Linux Trace可以抓取各种级别的数据,包括系统级别、进程级别和内核级别的数据。…...

【Apache Paimon】-- 作为一名小白,如何系统地学习 Apache paimon?

目录 一、整体规划 1. 了解基本概念与背景 2. 学习资料的选择 3. 学习路径与规划 4. 学习建议 5. 注意事项 6. 参考学习资料 二、详细计划 阶段 1&#xff1a;了解基础&#xff08;1-2 周&#xff09; 阶段 2&#xff1a;深入掌握核心功能&#xff08;3-4 周&#xf…...

Vue2组件通信

目录 【props】【$emit】【Event Bus】【provide和inject】【Vuex】 【props】 父组件通过props向子组件传递数据&#xff1b; <!-- 父组件 --> <template><ChildComponent :message"parentMessage" /> </template> <script> import…...

HarmonyOS:使用List实现分组列表(包含粘性标题)

一、支持分组列表 在列表中支持数据的分组展示&#xff0c;可以使列表显示结构清晰&#xff0c;查找方便&#xff0c;从而提高使用效率。分组列表在实际应用中十分常见&#xff0c;如下图所示联系人列表。 联系人分组列表 在List组件中使用ListItemGroup对项目进行分组&#…...

纪念日倒数日项目的实现-【纪念时刻-时光集】

纪念日/倒数日项目的实现## 一个练手的小项目&#xff0c;uniappnodemysql七牛云。 在如今快节奏的生活里&#xff0c;大家都忙忙碌碌&#xff0c;那些具有特殊意义的日子一不小心就容易被遗忘。今天&#xff0c;想给各位分享一个“纪念日”项目。 【纪念时刻-时光集】 一…...

Kafka知识点总结

一、概述 &#xffe5;1. 推拉模式 pull&#xff08;拉&#xff09;模式&#xff1a;consumer采用从broker中主动拉取数据&#xff0c;不足之处是如果没有数据&#xff0c;消费者可能会陷入循环中&#xff0c;一直返回空数据。push&#xff08;推&#xff09;模式&#xff1a…...

[AI]从零开始的llama.cpp部署与DeepSeek格式转换、量化、运行教程

一、前言 在上一次的DeepSeek的部署教程中&#xff0c;我们使用Ollama与LM Studio很轻松的部署了DeepSeek并且也完成了相关API的调用&#xff0c;如果还有不会的小伙伴请看下面的教程&#xff1a; DeepSeek本地部署&#xff1a;[AI]从零开始的DeepSeek本地部署及本地API调用教…...

#define宏与编译时定义的本质对决:从const常量到typedef的类型安全演进

目录 一、#define宏与const常量的本质差异&#xff1a;从文本替换到类型安全的编程抉择 1. 预处理阶段的文本替换&#xff08;#define&#xff09; 2. 编译时的类型安全&#xff08;const&#xff09; 3. 跨文件访问的限制 4. 代码示例对比 5. 最佳实践 总结表 二、类型…...

【天地图】绘制、删除点线面

使用天地图绘制、删除点线面 实现效果图地图组件完整代码使用地图组件完整代码 实现效果图 地图组件完整代码 // 天地图组件 <template><div class"map-container"><div id"mapCon"></div></div> </template><scri…...

2025年:边缘计算崛起下运维应对新架构挑战

一、引言 随着科技的飞速发展&#xff0c;2025年边缘计算正以前所未有的速度崛起&#xff0c;给运维行业带来了全新的架构挑战。在这个充满机遇与挑战的时代&#xff0c;美信时代公司的美信监控易运维管理软件成为运维领域应对这些挑战的有力武器。 二、边缘计算崛起带来的运维…...

【docker知识】快速找出服务器中占用内存较高的容器

本文由Markdown语法编辑器编辑完成。 1.背景&#xff1a; 近期在处理现场问题&#xff0c;观察服务器时&#xff0c;会遇到某些进程占用较高内存的情况。由于我们的服务&#xff0c;基本上都是以容器的方式在运行&#xff0c;因此就需要找到&#xff0c;到底是哪个容器&#…...

SQL联合查询

文章目录 MySQL系列&#xff1a;1.内连接2.外连接3.自连接4.子查询5.合并查询6.插入查询 MySQL系列&#xff1a; 初识MySQL&#xff0c;MySQL常用数据类型和表的操作&#xff0c;增删改查(CRUD)操作(总),数据库约束数据库设计 #班级表 drop table if exists class; create ta…...

cameralib 安装

目录 linux安装&#xff1a; 测试安装是否成功&#xff1a; linux安装&#xff1a; pip install githttps://github.com/isarandi/cameralib.git pip install githttps://github.com/isarandi/boxlib.git pip install githttps://github.com/isarandi/poseviz.git githttps…...

Windows软件自动化利器:pywinauto python

Pywinauto WindowsAPP UI自动化 Windows软件自动化利器&#xff1a;pywinauto python...

清华大学《DeepSeek:从入门到精通》

近日&#xff0c;清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室发布了由余梦珑博士后及其团队撰写的《DeepSeek&#xff1a;从入门到精通》手册。这份长达104页的指南&#xff0c;旨在帮助用户全面掌握国产通用人工智能平台DeepSeek的核心功能与应用技巧。 DeepSeek简…...

网易易盾接入DeepSeek,数字内容安全“智”理能力全面升级

今年农历新年期间&#xff0c;全球AI领域再度掀起了一波革命性浪潮&#xff0c;国产通用大模型DeepSeek凭借其强大的多场景理解与内容生成能力迅速“出圈”&#xff0c;彻底改写全球人工智能产业的格局。 作为国内领先的数字内容风控服务商&#xff0c;网易易盾一直致力于探索…...

天童美语:观察你的生活

在孩子的认知里&#xff0c;世界宛如一片充满神秘色彩的未知之境&#xff0c;有着无尽的奥秘等待他们去探索。家长们&#xff0c;引导孩子用心观察世界&#xff0c;领略其中的美妙&#xff0c;这对孩子的成长进程有着极为关键的作用。贵阳天童教育相信&#xff1a;观察生活&…...

易仓科技ai面试

请解释PHP中的面向对象编程的基本概念&#xff0c;并举例说明如何在PHP中定义一个类。 回答思路&#xff1a;需理解类、对象、继承和多态等基本概念&#xff0c;并能通过实例代码展示如何定义类及其属性和方法。 . 类&#xff08;Class&#xff09; 类是一个封装了数据和操作…...

MySQL-SQL

1.客户端内置命令 客户端内置命令客户端独有&#xff0c;可能不同数据库产品的客户端内置命令存在很大差异&#xff0c;不像SQL命令有标准规范。 help \h ? \? 这四个命令都可以输出帮助文档查看客户端内置命令 &#xff1f;&#xff08;\&#xff1f;&#xff09;“帮助”…...

基础连接已经关闭: 服务器关闭了本应保持活动状态的连接

您在进行 HTTP 请求时遇到“基础连接已经关闭: 服务器关闭了本应保持活动状态的连接”的错误&#xff0c;这通常与连接的保持活动&#xff08;Keep-Alive&#xff09;设置有关。以下是可能的原因和解决方法&#xff1a; 可能的原因&#xff1a; Keep-Alive 设置&#xff1a; 默…...

【React组件通讯双重视角】函数式 vs 类式开发指南

目录 前言 正文 父组件向子组件传值 函数式写法 类式写法 子组件向父组件传值 函数式写法 类式写法 兄弟组件通信 函数式写法 类式写法 跨层级通信&#xff08;使用Context&#xff09; 函数式写法 类式写法 进阶通讯方式&#xff08;补充说明&#xf…...

开源项目Perplexica-master

一个和https://www.perplexity.ai/差不多的开源项目 大模型的key可以用groq的&#xff0c;免费&#xff08;https://console.groq.com/keys&#xff09;&#xff0c;有速率限制 项目clone后&#xff0c;修改配置&#xff0c;项目根目录config.toml 填写对应的大模型的key就行…...

数值积分:通过复合梯形法计算

在物理学和工程学中&#xff0c;很多问题都可以通过数值积分来求解&#xff0c;特别是当我们无法得到解析解时。数值积分是通过计算积分区间内离散点的函数值来近似积分的结果。在这篇博客中&#xff0c;我将讨论如何使用 复合梯形法 来进行数值积分&#xff0c;并以一个简单的…...

DeepSeek全生态接入指南:官方通道+三大云平台

DeepSeek全生态接入指南&#xff1a;官方通道三大云平台 一、官方资源入口 1.1 核心交互平台 &#x1f5a5;️ DeepSeek官网&#xff1a; https://chat.deepseek.com/ &#xff08;体验最新对话模型能力&#xff09; 二、客户端工具 OllamaChatboxCherry StudioAnythingLLM …...

shell脚本的一些学习笔记----(三)流程控制

1.条件判断 if单条件判断 if [ 条件判断式 ] then 语句块 fi 案例1&#xff1a;统计根分区使用率 [rootlocalhost ~]$ vi sh/if1.sh #!/bin/bash #把根分区使用率作为变量值赋予变量rate rate$(df -h | grep "/dev/sda1" | awk {print $5}’| cut -d "%"-f…...

Docker Desktop WebAPI《1》

方法1 》》生成 的文档不要动&#xff0c; 》》执行 Container&#xff08;Dockerfile&#xff09; 会生成镜像文件和容器 》》生成的镜像和容器 在 Docker Desktop 中可以查看 用VS 的 Container Dockerfile 调试 但把这个调试工工具 停止&#xff0c;WebAPi就不能访问了 …...