《操作系统真象还原》第十二章(2)——进一步完善内核

文章目录

    • 前言
    • 可变参数的原理
    • 实现系统调用write
      • 更新syscall.h
      • 更新syscall.c
      • 更新syscall-init.c
    • 实现printf
      • 编写stdio.h
      • 编写stdio.c
    • 第一次测试
      • main.c
      • makefile
      • 结果截图
    • 完善printf
      • 修改main.c
    • 结语

前言

上部分链接:《操作系统真象还原》第十二章(1)——进一步完善内核-CSDN博客。上部分我们是寄存器存参数,书的结尾还提到了用栈传递参数实现系统调用,我们不使用这种方法。

之前我们屏幕打印是直接操作显存,显然用户进程没有这样的权限。这部分完成printf函数,让用户进程也能打印信息。


可变参数的原理

这里摘一些我认为比较关键的内容吧。

早期操作系统只能申请静态内存。随着计算机的进步,操作系统开始支持堆内存管理,堆内存专 门用于程序运行时的内存申请,因此编译器也开始支持程序在运行时动态内存申请,也就是编译器开始支 持源码中的变长数据结构。

程序中的数据结构终归有个长度,此长度要么在编译时确定,要么在运行时确 定。编译时确定是指数据结构在源码编译阶段就能确定下来,说白了就是编译器必须提前知道数据结构的 长度,它为此类数据结构分配的是静态内存,也就是程序被操作系统加载时分配的内存。运行时确定是指 数据结构的长度是在程序运行阶段确定下来的,编译器为此类数据结构(如 C99 中的变长数组)在堆中 分配内存,已经说过了,堆本来就是用于程序运行时的动态内存分配,因此可以在运行阶段确定长度。

函数占用的也是静态内存,因此也得提前告诉编译器自己占用的内存大小。为了在 编译时获取函数调用时所需要的内存空间(这通常是在栈中分配内存单元),编译器要求提供函数声明, 声明中描述了函数参数的个数及类型,编译器用它们来计算参数所占据的栈空间。因此编译器不关心函数 声明中参数的名称,它只关心参数个数及类型(您懂的,函数声明中的参数可以不包括参数名,但必须包 括类型),编译器用这两个信息才能确定为函数在栈中分配的内存大小。重点来了,函数并不是在堆中分 配内存,因此它需要提前确定内存空间,这通常取决于参数的个数及类型 大小,但编译器却允许函数的参数个数不固定(可变参数)。

其实这种可变仍然是静态的。参数是由调用者压入的,调用者当然知道栈中压入了几个参数,参数占用了多少空间,因 此无论函数的参数个数是否固定,采用 C 调用约定,调用者都能完好地回收栈空间,不必担心栈溢出等 问题。因此,看似“动态”的可变参数函数,其实也是“静态”“固定”的,传入参数的个数是由编译器 在编译阶段就确定下来的。

拿格式化输出函数 printf(char* format, arg1, arg2,…)举例,比如printf(”hello %s!”, ”martin”),其中的”hello %s!”便是 format——格式化字符串。通过%+占位符,就能实现可变参数。

linux通过三个宏定义支持可变参数,下面是3个宏的说明。

  1. va_start(ap,v),参数 ap 是用于指向可变参数的指针变量,参数v是支持可变参数的函数的第1个 参数(如对于printf来说,参数v就是字符串format)。此宏的功能是使指针ap指向v的地址,它的调用 必须先于其他两个宏,相当于初始化ap指针的作用。
  2. va_arg(ap,t),参数 ap 是用于指向可变参数的指针变量,参数t是可变参数的类型,此宏的功能是 使指针ap指向栈中下一个参数的地址并返回其值。
  3. va_end(ap),将指向可变参数的变量ap置为null,也就是清空指针变量ap。

后续我们会实现这三个宏。


实现系统调用write

linux的系统调用write 接受 3个参数,其中的fd是文件描述符,buf是被 输出数据所在的缓冲区,count 是输出的字符数,write 的功 能是把buf中count个字符写到文件描述符fd指向的文件中。

我们这里先实现一个简易版本,只接受一个参数——待打印字符指针。

我们按三部曲完成简单版write。

更新syscall.h

第一步添加新的子功能号

#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "../kernel/stdint.h"enum SYSCALL_NR
{SYS_GETPID,SYS_WRITE
};
uint32_t getpid(void);     // 获取任务pid
uint32_t write(char *str); // 打印字符串并返回字符串长度#endif

更新syscall.c

第二步添加系统调用的用户接口

#include "./syscall.h"/*从上到下,分别是0、1、2、3参数的系统调用,结构基本一致*eax是子程序号,剩下三个存在ebx、ecx、edx中*//*({ ... })是gcc扩展*将一组语句封装为一个表达式,返回最后一个语句的值*/
#define _syscall0(NUMBER) ({ \int retval;              \asm volatile(            \"int $0x80"          \: "=a"(retval)       \: "a"(NUMBER)        \: "memory");         \retval;                  \
})#define _syscall1(NUMBER, ARG1) ({ \int retval;                    \asm volatile(                  \"int $0x80"                \: "=a"(retval)             \: "a"(NUMBER), "b"(ARG1)   \: "memory");               \retval;                        \
})#define _syscall2(NUMBER, ARG1, ARG2) ({    \int retval;                             \asm volatile(                           \"int $0x80"                         \: "=a"(retval)                      \: "a"(NUMBER), "b"(ARG1), "c"(ARG2) \: "memory");                        \retval;                                 \
})#define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({         \int retval;                                        \asm volatile(                                      \"int $0x80"                                    \: "=a"(retval)                                 \: "a"(NUMBER), "b"(ARG1), "c"(ARG2), "d"(ARG3) \: "memory");                                   \retval;                                            \
})/*返回当前任务的pid*/
uint32_t getpid()
{return _syscall0(SYS_GETPID);
}/*打印字符串str*/
uint32_t write(char *str)
{return _syscall1(SYS_WRITE, str);
}

更新syscall-init.c

第三步定义子功能处理函数,并在syscall_table中注册


#include "./syscall-init.h"
#include "../lib/kernel/stdint.h"
#include "../lib/user/syscall.h"
#include "../thread/thread.h"
#include "../lib/kernel/print.h"
#include "../device/console.h"
#include "../lib/string.h"#define syscall_nr 32 // 最大支持的子功能个数
typedef void *syscall;
syscall syscall_table[syscall_nr];/*返回当前任务的pid*/
uint32_t sys_getpid(void)
{return running_thread()->pid;
}/*打印字符串str*/
uint32_t sys_wirte(char *str)
{console_put_str(str);return strlen(str);
}/*初始化系统调用*/
void syscall_init(void)
{put_str("syscall_init start\n");syscall_table[SYS_GETPID] = sys_getpid;syscall_table[SYS_WRITE] = sys_wirte;put_str("syscall_init done\n");
}

到此我们实现了文件管理系统之前的简化版write。


实现printf

printf是vsprintf和write的封装,write已经完成,本 节要完成vsprintf、用于可变参数解析的3个宏以及转换函数itoa,这些实现后就完成了基本的printf,本 节的目标是使printf支持十六进制输出,即完成“%x”的功能。

关于linux中的vsprintf函数:

此函数的功能是把 ap 指向的可变参数,以字符串格式format中的符号’%'为替 换标记,不修改原格式字符串format,将format中除“%类型字符”以外的内容复制到str,把“%类型字 符”替换成具体参数后写入str中对应“%类型字符”的位置,也就是说函数执行后,str的内容相当于格 式字符串format中的“%类型字符”被具体参数替换后的format字符串。vsprintf 执行完成后返回字符串str的长度。

同样,我们参考这个函数写我们的vsprintf,路径是lib/stdio.c .h

编写stdio.h


#ifndef __LIB_STDIO_H
#define __LIB_STDIO_H
#include "./kernel/stdint.h"
typedef char *va_list;
uint32_t vsprintf(char *str, const char *format, va_list ap);
uint32_t printf(const char *format, ...);#endif

先给出头文件,再给出函数实现。

编写stdio.c

这部分最长的代码,注释很清楚,不再赘述


#include "./stdio.h"
#include "./kernel/stdint.h"
#include "./string.h"
#include "../kernel/debug.h"
#include "./user/syscall.h"#define va_start(ap, v) ap = (va_list) & v // ap指向第一个固定参数v
#define va_arg(ap, t) *((t *)(ap += 4))    // ap依次指向下一个参数,通过解除引用返回其值
#define va_end(ap) ap = NULL/*将整型转化为字符ascii*/
/*三个参数依次是带转化数值,转化后字符保存的缓冲区,转化进制*/
static void itoa(uint32_t value, char **buf_ptr_addr, uint8_t base)
{uint32_t m = value % base; // 余数uint32_t i = value / base; // 倍数if (i){itoa(i, buf_ptr_addr, base);}if (m < 10){// 第一次解引用后是缓冲区地址,++提供下一个字符的位置// 第二次解引用后是char,赋值为对应的字符*((*buf_ptr_addr)++) = m + '0';}else{*((*buf_ptr_addr)++) = m - 10 + 'A';}
}/*将参数ap按照格式format输出到字符串str,并返回替换后str长度*/
uint32_t vsprintf(char *str, const char *format, va_list ap)
{char *buf_ptr = str;const char *index_ptr = format;char index_char = *index_ptr;int32_t arg_int;while (index_char) // 没有到达末尾就一直处理{if (index_char != '%') // 没有遇到%,直接复制即可{*buf_ptr = index_char;buf_ptr++;index_ptr++;index_char = *index_ptr;continue;}// 以下为遇到%后的处理过程// 先跳过%index_ptr++;index_char = *index_ptr;// 然后判断占位符是哪种// 目前先实现x,代表后面的参数是无符号整形if (index_char == 'x'){// 获得第一个参数,并且ap指向下一个参数arg_int = va_arg(ap, int);// 将无符号整型转化为字符,并放到str后面itoa(arg_int, &buf_ptr, 16);// 跳过x,并且准备好进行后面的处理index_ptr++;index_char = *index_ptr;}else{PANIC("Undefined placeholder");}}return strlen(str);
}/*格式化输出字符串format,即printf*/
/*包含可变参数*/
uint32_t printf(const char *format, ...)
{va_list args; // 可变参数列表va_start(args, format);char buf[1024] = {0}; // 最终拼接后字符串储存位置vsprintf(buf, format, args);va_end(args);return write(buf);
}

第一次测试

main.c

// 内核的入口函数
#include "../lib/kernel/print.h"
#include "./init.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "./interrupt.h"
#include "../userprog/process.h"
// 本章测试头文件
#include "../lib/user/syscall.h"
#include "../userprog/syscall-init.h"
#include "../lib/stdio.h"void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);
int main(void)
{put_str("HongBai's OS kernel\n");init_all(); // 初始化所有模块process_execute(u_prog_a, "user_prog_a");process_execute(u_prog_b, "user_prog_b");intr_enable();console_put_str(" main_pid:0x");console_put_int(sys_getpid());console_put_char('\n');thread_start("k_thread_a", 31, k_thread_a, "argA: ");thread_start("k_thread_b", 31, k_thread_b, "argB: ");while (1){};
}void k_thread_a(void *arg)
{char *para = arg;console_put_str(" thread_a_pid:0x");console_put_int(sys_getpid());console_put_char('\n');while (1){};
}void k_thread_b(void *arg)
{char *para = arg;console_put_str(" thread_b_pid:0x");console_put_int(sys_getpid());console_put_char('\n');while (1){};
}void u_prog_a(void)
{printf(" program_a_pid:0x%x\n", getpid());while (1){};
}void u_prog_b(void)
{printf(" program_b_pid:0x%x\n", getpid());while (1){};
}

makefile

BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS =  -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS =  -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \$(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \$(BUILD_DIR)/stdio.o################	c代码编译   ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \lib/kernel/stdint.h kernel/init.h kernel/debug.h \kernel/memory.h thread/thread.h kernel/interrupt.h \device/console.h userprog/process.h lib/user/syscall.h \userprog/syscall-init.h lib/stdio.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \lib/kernel/stdint.h kernel/interrupt.h device/timer.h \kernel/memory.h thread/thread.h device/console.h \device/keyboard.h userprog/tss.h userprog/syscall-init.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \lib/kernel/stdint.h kernel/global.h kernel/io.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \kernel/io.h lib/kernel/print.h kernel/interrupt.h \thread/thread.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \lib/kernel/print.h kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/string.o: lib/string.c lib/string.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \lib/string.h thread/sync.h thread/thread.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \lib/string.h kernel/interrupt.h lib/kernel/print.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \lib/kernel/stdint.h lib/kernel/list.h lib/string.h \kernel/memory.h kernel/interrupt.h kernel/debug.h \lib/kernel/print.h userprog/process.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \lib/kernel/stdint.h thread/thread.h kernel/debug.h \kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/console.o: device/console.c device/console.h \lib/kernel/print.h thread/sync.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \lib/kernel/print.h kernel/interrupt.h kernel/io.h \lib/kernel/stdint.h device/ioqueue.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \lib/kernel/stdint.h thread/thread.h thread/sync.h \kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \lib/kernel/stdint.h thread/thread.h kernel/global.h \lib/kernel/print.h lib/string.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \kernel/global.h lib/kernel/stdint.h thread/thread.h \kernel/debug.h userprog/tss.h device/console.h \lib/string.h kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \lib/kernel/stdint.h lib/user/syscall.h thread/thread.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h \lib/kernel/stdint.h lib/string.h kernel/debug.h \lib/user/syscall.h$(CC) $(CFLAGS) $< -o $@##############    汇编代码编译    ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/print.o: lib/kernel/print.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/switch.o: thread/switch.S$(AS) $(ASFLAGS) $< -o $@##############    连接所有目标文件    #############
$(BUILD_DIR)/kernel.bin: $(OBJS)$(LD) $(LDFLAGS) $^ -o $@.PHONY : mk_dir hd clean allmk_dir:if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fihd:dd if=$(BUILD_DIR)/kernel.bin \of=/home/hongbai/bochs/bin/c.img \bs=512 count=200 seek=10 conv=notruncclean:cd $(BUILD_DIR) && rm -f ./*build: $(BUILD_DIR)/kernel.binall: mk_dir build hd

结果截图

还是非常成功的。


完善printf

一口气实现对“%c”、“%s”和“%d”三种占位符的处理。

对应单个字符,字符串,int类型。


#include "./stdio.h"
#include "./kernel/stdint.h"
#include "./string.h"
#include "../kernel/debug.h"
#include "./user/syscall.h"#define va_start(ap, v) ap = (va_list) & v // ap指向第一个固定参数v
#define va_arg(ap, t) *((t *)(ap += 4))    // ap依次指向下一个参数,通过解除引用返回其值
#define va_end(ap) ap = NULL/*将整型转化为字符ascii*/
/*三个参数依次是带转化数值,转化后字符保存的缓冲区,转化进制*/
static void itoa(uint32_t value, char **buf_ptr_addr, uint8_t base)
{uint32_t m = value % base; // 余数uint32_t i = value / base; // 倍数if (i){itoa(i, buf_ptr_addr, base);}if (m < 10){// 第一次解引用后是缓冲区地址,++提供下一个字符的位置// 第二次解引用后是char,赋值为对应的字符*((*buf_ptr_addr)++) = m + '0';}else{*((*buf_ptr_addr)++) = m - 10 + 'A';}
}/*将参数ap按照格式format输出到字符串str,并返回替换后str长度*/
uint32_t vsprintf(char *str, const char *format, va_list ap)
{char *buf_ptr = str;const char *index_ptr = format;char index_char = *index_ptr;int32_t arg_int;char *arg_str;while (index_char) // 没有到达末尾就一直处理{if (index_char != '%') // 没有遇到%,直接复制即可{*buf_ptr = index_char;buf_ptr++;index_ptr++;index_char = *index_ptr;continue;}// 以下为遇到%后的处理过程// 先跳过%index_ptr++;index_char = *index_ptr;// 然后判断占位符是哪种类型// %x,后面的参数是16进制unsigned intif (index_char == 'x'){// 获得第一个参数,并且ap指向下一个参数arg_int = va_arg(ap, int);// 将无符号整型转化为字符,并放到str后面itoa(arg_int, &buf_ptr, 16);// 跳过x,并且准备好进行后面的处理index_ptr++;index_char = *index_ptr;}// %d,后面的参数是intelse if (index_char == 'd'){arg_int = va_arg(ap, int);// 负数需要进行补码操作转化为正数,然后额外输出一个-if (arg_int < 0){arg_int = 0 - arg_int;*buf_ptr = '-';buf_ptr++;}itoa(arg_int, &buf_ptr, 10);index_ptr++;index_char = *index_ptr;}// %c,后面的参数是charelse if (index_char == 'c'){*buf_ptr = va_arg(ap, char);buf_ptr++;index_ptr++;index_char = *index_ptr;}// %s,后面的参数是string(char*)else if (index_char == 's'){arg_str = va_arg(ap, char *);strcpy(buf_ptr, arg_str);buf_ptr += strlen(arg_str);index_ptr++;index_char = *index_ptr;}else{PANIC("Undefined placeholder");}}return strlen(str);
}/*格式化输出字符串format,即printf*/
/*包含可变参数*/
uint32_t printf(const char *format, ...)
{va_list args; // 可变参数列表va_start(args, format);char buf[1024] = {0}; // 最终拼接后字符串储存位置vsprintf(buf, format, args);va_end(args);return write(buf);
}uint32_t sprintf(char *buf, const char *format, ...)
{va_list args;uint32_t retval;va_start(args, format);retval = vsprintf(buf, format, args);va_end(args);return retval;
}

修改main.c

// 内核的入口函数
#include "../lib/kernel/print.h"
#include "./init.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "./interrupt.h"
#include "../userprog/process.h"
// 本章测试头文件
#include "../lib/user/syscall.h"
#include "../userprog/syscall-init.h"
#include "../lib/stdio.h"void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);
int main(void)
{put_str("HongBai's OS kernel\n");init_all(); // 初始化所有模块process_execute(u_prog_a, "user_prog_a");process_execute(u_prog_b, "user_prog_b");intr_enable();printf(" main_pid:0x%x\n",getpid());thread_start("k_thread_a", 31, k_thread_a, "argA: ");thread_start("k_thread_b", 31, k_thread_b, "argB: ");while (1){};
}void k_thread_a(void *arg)
{char *para = arg;printf(" thread_a_pid:0x%x\n",getpid());while (1){};
}void k_thread_b(void *arg)
{char *para = arg;printf(" thread_b_pid:0x%x\n",getpid());while (1){};
}void u_prog_a(void)
{printf("%s%d%c", " program_a_pid:",getpid(),'\n');while (1){};
}void u_prog_b(void)
{printf("%s%d%c", " program_b_pid:",getpid(),'\n');while (1){};
}

makefile不变,结果截图

ok那么四种占位符都测试完毕,prints初步实现。


结语

第二部分,整体还是比较简单,马上就要进入最难的内存部分了,郑钢老师提到我们要重构我们的内存管理系统,还好我之前梳理过,希望下一部分能高效率顺利完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/903962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ICML2021 | DeiT | 训练数据高效的图像 Transformer 与基于注意力的蒸馏

Training data-efficient image transformers & distillation through attention 摘要-Abstract引言-Introduction相关工作-Related Work视觉Transformer&#xff1a;概述-Vision transformer: overview通过注意力机制蒸馏-Distillation through attention实验-Experiments…

深度学习:AI 机器人时代

在科技飞速发展的当下&#xff0c;AI 机器人时代正以汹涌之势席卷而来&#xff0c;而深度学习作为其核心驱动力&#xff0c;正重塑着我们生活与工作的方方面面。 从智能工厂的自动化生产&#xff0c;到家庭中贴心服务的智能助手&#xff0c;再到复杂环境下执行特殊任务的专业机…

《告别试错式开发:TDD的精准质量锻造术》

深度解锁TDD&#xff1a;应用开发的创新密钥 在应用开发的复杂版图中&#xff0c;如何雕琢出高质量、高可靠性的应用&#xff0c;始终是开发者们不懈探索的核心命题。测试驱动开发&#xff08;TDD&#xff09;&#xff0c;作为一种颠覆性的开发理念与方法&#xff0c;正逐渐成…

应用层自定义协议序列与反序列化

目录 一、网络版计算器 二、网络版本计算器实现 2.1源代码 2.2测试结果 一、网络版计算器 应用层定义的协议&#xff1a; 应用层进行网络通信能否使用如下的协议进行通信呢&#xff1f; 在操作系统内核中是以这种协议进行通信的&#xff0c;但是在应用层禁止以这种协议进行…

Excel-CLI:终端中的轻量级Excel查看器

在数据驱动的今天&#xff0c;Excel 文件处理成为了我们日常工作中不可或缺的一部分。然而&#xff0c;频繁地在图形界面与命令行界面之间切换&#xff0c;不仅效率低下&#xff0c;而且容易出错。现在&#xff0c;有了 Excel-CLI&#xff0c;一款运行在终端中的轻量级Excel查看…

百度后端开发一面

mutex, rwmutex 在Go语言中&#xff0c;Mutex&#xff08;互斥锁&#xff09;和RWMutex&#xff08;读写锁&#xff09;是用于管理并发访问共享资源的核心工具。以下是它们的常见问题、使用场景及最佳实践总结&#xff1a; 1. Mutex 与 RWMutex 的区别 Mutex: 互斥锁&#xf…

STM32 IIC总线

目录 IIC协议简介 IIC总线系统结构 IIC总线物理层特点 IIC总线协议层 空闲状态 应答信号 数据的有效性 数据传输 STM32的IIC特性及架构 STM32的IIC结构体 0.96寸OLED显示屏 SSD1306框图及引脚定义 4针脚I2C接口模块原理图 字节传输-I2C 执行逻辑框图 命令表…

【unity游戏开发入门到精通——UGUI】整体控制一个UGUI面板的淡入淡出——CanvasGroup画布组组件的使用

注意&#xff1a;考虑到UGUI的内容比较多&#xff0c;我将UGUI的内容分开&#xff0c;并全部整合放在【unity游戏开发——UGUI】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言CanvasGroup画布组组件参数 实战专栏推荐完结 前言 如果我们想要整体控制…

大型语言模型个性化助手实现

大型语言模型个性化助手实现 目录 大型语言模型个性化助手实现PERSONAMEM,以及用户资料和对话模拟管道7种原位用户查询类型关于大语言模型个性化能力评估的研究大型语言模型(LLMs)已经成为用户在各种任务中的个性化助手,从提供写作支持到提供量身定制的建议或咨询。随着时间…

生成式 AI 的未来

在人类文明的长河中,技术革命始终是推动社会跃迁的核心引擎。从蒸汽机解放双手,到电力点亮黑夜,再到互联网编织全球神经网络,每一次技术浪潮都在重塑人类的生产方式与认知边界。而今天,生成式人工智能(Generative AI)正以一种前所未有的姿态登上历史舞台——它不再局限于…

【序列化与反序列化详解】

文章目录 一、序列化与反序列化是什么&#xff1f;1. 为什么需要序列化&#xff1f;2. 反序列化的作用 二、常见的序列化格式三、不同编程语言的序列化与反序列化示例1. Python 的序列化与反序列化JSON 序列化Pickle 序列化&#xff08;仅限 Python&#xff09; 2. Java 的序列…

【单例模式】简介

目录 概念理解使用场景优缺点实现方式 概念理解 单例模式要保证一个类在整个系统运行期间&#xff0c;无论创建多少次该类的对象&#xff0c;始终只会有一个实例存在。就像操作系统中的任务管理器&#xff0c;无论何时何地调用它&#xff0c;都是同一个任务管理器在工作&#…

目标检测YOLO实战应用案例100讲- 无人机平台下露天目标检测与计数

目录 知识储备 基于YOLOv8改进的无人机露天目标检测与计数 一、环境配置与依赖安装 二、核心代码实现(带详细注释) 1. 改进YOLOv8模型定义(添加注意力机制) 2. 无人机视角数据增强(drone_augment.py ) 3. 多目标跟踪与计数(tracking_counter.py ) 4. 完整推理流…

【在Spring Boot中集成Redis】

在Spring Boot中集成Redis 依赖在application.yml中配置Redis服务地址创建Redis配置类缓存工具类使用 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency&…

计算机视觉——基于树莓派的YOLO11模型优化与实时目标检测、跟踪及计数的实践

概述 设想一下&#xff0c;你在多地拥有多个仓库&#xff0c;要同时监控每个仓库的实时状况&#xff0c;这对于时间和精力而言&#xff0c;都构成了一项艰巨挑战。从成本和可靠性的层面考量&#xff0c;大规模部署计算设备也并非可行之策。一方面&#xff0c;大量计算设备的购…

通信协议记录仪-产品规格书

以下是为 ​​通信协议记录仪(ProtoLogger Pro)​​ 的​​详细产品规格书​​,覆盖 ​​技术细节、场景需求、竞品差异化​​,确保可作为产品开发、市场营销及竞品分析的核心依据。 ​​通信协议记录仪产品规格书​​ ​​产品名称​​:ProtoLogger Pro(中文名称:蹲守…

python:sklearn 决策树(Decision Tree)

5. 决策树&#xff08;Decision Tree&#xff09; - 第5章 算法思想&#xff1a;基于信息增益&#xff08;ID3&#xff09;或基尼不纯度&#xff08;CART&#xff09;递归划分特征。 编写 test_dtree_1.py 如下 # -*- coding: utf-8 -*- """ 5. 决策树&…

【2-sat】2-sat算法内容及真题

A.2-sat简介 2-sat算法可以求解给定推出关系下的一种合法情况。题目中重常常&#xff0c;给定一些布尔变量A、B、C、D…&#xff0c;再给出一系列形如 B ⟶ A , C ⟶ D B \longrightarrow A , C \longrightarrow \neg D B⟶A,C⟶D的推出关系&#xff0c;询问使得所有推出关系…

【git】获取特定分支和所有分支

1 特定分支 1.1 克隆指定分支&#xff08;默认只下载该分支&#xff09; git clone -b <分支名> --single-branch <仓库URL> 示例&#xff08;克隆 某一个 分支&#xff09;&#xff1a; git clone -b xxxxxx --single-branch xxxxxxx -b &#xff1a;指定分支…

LWIP带freeRTOS系统移植笔记

以正点原子学习视频为基础的文章 LWIP带freeRTOS系统移植 准备资料/工程 1、lwIP例程1 lwIP裸机移植 工程 &#xff0c; 作为基础工程 改名为LWIP_freeRTOS_yizhi工程 2、lwIP例程6 lwIP_FreeRTOS移植 工程 3、freeRTO源码 打开https://www.freertos.org/网址下载…