《操作系統真象還原》第十二章(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.pswp.cn/news/903962.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903962.shtml
英文地址,請注明出處:http://en.pswp.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/網址下載…