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

《操作系统真象还原》第十二章(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初步实现。


结语

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

相关文章:

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

文章目录 前言可变参数的原理实现系统调用write更新syscall.h更新syscall.c更新syscall-init.c 实现printf编写stdio.h编写stdio.c 第一次测试main.cmakefile结果截图 完善printf修改main.c 结语 前言 上部分链接&#xff1a;《操作系统真象还原》第十二章&#xff08;1&#…...

MIT6.S081-lab8前置

MIT6.S081-lab8前置 注&#xff1a;本部分除了文件系统还包含了调度的内容。 调度 调度涉及到保存寄存器&#xff0c;恢复寄存器&#xff0c;就这一点而言&#xff0c;和我们的 trap 很像&#xff0c;但是实际上&#xff0c;我们实现并不是复用了 trap 的逻辑&#xff0c;我…...

Java从入门到精通 - Java语法

Java 语法 此笔记参考黑马教程&#xff0c;仅学习使用&#xff0c;如有侵权&#xff0c;联系必删 文章目录 Java 语法01 变量详解1. 变量里的数据在计算机中的存储原理1.1 二进制1.2 十进制转二进制的算法1.3 计算机中表示数据的最小单元总结1.4 字符在计算机中是如何存储的呢…...

【CF】Day50——Codeforces Round 960 (Div. 2) BCD

B. Array Craft 题目&#xff1a; 思路&#xff1a; 有点意思的构造 首先题目告诉我们 y < x&#xff0c;这是一个重要的条件 我们先来考虑简单情况&#xff0c;假如可以放0进去&#xff0c;那么我们只需要在 y ~ x 之间全放 1 &#xff0c;其余都是 0 即可&#xff0c;但…...

MySQL 日期加减函数详解

MySQL 日期加减函数详解 1. DATE_ADD 函数 基本语法 DATE_ADD(date, INTERVAL expr unit)功能 在指定日期/时间上添加一个时间间隔 参数说明 date&#xff1a;要处理的日期/时间值&#xff08;可以是DATE, DATETIME或TIMESTAMP类型&#xff09;expr&#xff1a;要添加的间…...

NV189NV195美光固态闪存NV197NV199

NV189NV195美光固态闪存NV197NV199 在存储技术持续迭代的2025年&#xff0c;美光固态闪存NV189、NV195、NV197、NV199系列凭借其差异化的性能定位&#xff0c;正在重新定义数据存储的边界。本文将从技术参数、场景适配、行业价值等维度&#xff0c;为不同领域的专业人士提供深度…...

C语言-回调函数

回调函数 通过函数指针调用函数&#xff0c;而这个被调用的函数称为回调函数 回调函数是C语言中一种强大的机制&#xff0c;允许将函数作为参数传递给其他函数&#xff0c;从而在特定时机由后者调用。它的核心在于函数指针的使用 以下是回调函数的使用例子 先创建好一个函数…...

启发式算法-蚁群算法

蚁群算法是模拟蚂蚁觅食行为的仿生优化算法&#xff0c;原理是信息素的正反馈机制&#xff0c;蚂蚁通过释放信息素来引导同伴找到最短路径。把问题的元素抽象为多条路径&#xff0c;每次迭代时为每只蚂蚁构建一个解决方案&#xff0c;该解决方案对应一条完整的路径&#xff0c;…...

DeepSeek与MySQL:开启数据智能新时代

目录 一、引言&#xff1a;技术融合的力量二、DeepSeek 与 MySQL&#xff1a;技术基石2.1 DeepSeek 技术探秘2.2 MySQL 数据库深度解析 三、DeepSeek 与 MySQL 集成&#xff1a;从理论到实践3.1 集成原理剖析3.2 集成步骤详解 四、应用案例&#xff1a;实战中的价值体现4.1 电商…...

Modbus 通讯协议(超详细,简单易懂)

目录 一、协议中的寄存器定义 二、协议概述 三、使用串口的Modbus 报文帧 ​编辑 3.1、Modbus ASCII 模式 3.2、Modbus RTU 模式 3.3、功能码概要 3.4、Modbus 报文分析 四、什么是RS-485 RS-232&#xff1f; 一、协议中的寄存器定义 阅读 Modbus 协议时会发现它的概念别扭…...

单细胞测序试验设计赏析(一)

单细胞测序试验设计赏析&#xff08;一&#xff09; 单细胞测序试验设计中&#xff0c;单细胞测序技术通常会结合其它的技术来共同说明问题&#xff0c;或者结合年龄、性别等临床数据&#xff0c;进行分层分析说明问题以下以发表文章来进行一定的分析。 Single-cell RNA seque…...

ES6入门---第二单元 模块三:对象新增、

一&#xff1a;对象简洁语法&#xff1a; 1、变量简洁 <script>let name Strive;let age 18;let json {name, //name:name,age //age:age};console.log(json);</script> 2、函数简洁 let json {name, //name:name,age, //age:age/* showA:functi…...

多元随机变量协方差矩阵

主要记录多元随机变量数字特征相关内容。 关键词&#xff1a;多元统计分析 二元随机变量(X, Y) 说明&#xff1a;可以理解变量中的 X为身高、Y为体重 总体协方差 σ X Y c o v ( X , Y ) E [ ( X − μ X ) ( Y − μ Y ) ] E ( X Y ) − μ X μ Y \sigma_{XY}cov(X, Y)E[…...

计算机网络-同等学力计算机综合真题及答案

计算机网络-同等学力计算机综合真题及答案 &#xff08;2003-2024&#xff09; 2003 年网络 第二部分 计算机网络&#xff08;共 30 分&#xff09; &#xff08;因大纲变动因此 2004 年真题仅附真题&#xff0c;不作解析。&#xff09; 一、填空题&#xff08;共 10 分&#…...

[案例二] 菜单条制作(Menuscript)与工具条制作(Toolbar)

最近五一正好毕业论文盲审,抽时间研究一下菜单条制作(Menuscript)与工具条制作(Toolbar)的制作,在NX二次开发中唐康林老师已经讲的很详细了,在这里只对视频中的内容进行总结,并且根据自己的想法进行补充。在里海博主的直播教学中发现一个很有趣的NX图标工具,本人大概做了一…...

bellard.org‌ : QuickJS 如何使用 qjs 执行 js 脚本

参阅上一篇&#xff1a;Fabrice Bellard&#xff08;个人网站&#xff1a;‌bellard.org‌&#xff09;介绍 Fabrice Bellard&#xff08;个人网站&#xff1a;‌bellard.org‌&#xff09;是计算机领域最具影响力的程序员之一&#xff0c;其贡献跨越多个技术领域并持续推动开…...

计组复习笔记 3

前言 继续做例题。昨天做到第一个就把我难住了。可恶。 4.1 地址码越长&#xff0c;操作码越短。因为两者加起来是指令字&#xff0c;指令字的大小一般是固定的。扩展编码按照操作码从短到长进行编码。算了先放一下。我先看一下别的复习资料。等会儿再看这个题。 鼓励自己 …...

GCD 深入解析:从使用到底层实现

前言 Grand Central Dispatch (GCD) 是 Apple 基于 C 语言开发的一套完整的并发编程框架。它不仅仅是一个简单的线程管理工具&#xff0c;而是一个高度优化的并发编程解决方案。GCD 的设计理念是将并发编程的复杂性封装在框架内部&#xff0c;为开发者提供简单易用的接口。本文…...

JavaScript中的AES加密与解密:原理、代码与实战

前言 关于有js加密、js解密&#xff0c;js业务相关&#xff0c;找jsjiami官网站长v。 另外前段时间做了个单子跑单了&#xff0c;出售TEMU助手。eller点kuajingmaihuo点com的全自动化助手&#xff0c;可以批量合规&#xff0c;批量实拍图&#xff0c;批量资质上传等。 一、A…...

计算机组成原理实验(7) 堆指令部件模块实验

实验七 堆指令部件模块实验 一、实验目的 1、掌握指令部件的组成方式。 2、熟悉指令寄存器的打入操作&#xff0c;PC计数器的设置和加1操作&#xff0c;理解跳转指令的实现过程。 二、实验要求 按照实验步骤完成实验项目&#xff0c;掌握数据打入指令寄存器IR1、PC计数器的…...

Windows系统下Node.js环境部署指南:使用nvm管理多版本

Windows系统下Node.js环境部署指南&#xff1a;使用nvm管理多版本 一、Node.js介绍二、为什么需要nvm&#xff1f;三、安装前的准备工作1. 本次环境说明2. 卸载现有Node.js&#xff08;如有&#xff09; 三、nvm-windows安装步骤1. 下载安装包2. 安装过程3. 验证安装 四、使用n…...

数据结构*队列

队列 什么是队列 是一种线性的数据结构&#xff0c;和栈不同&#xff0c;队列遵循“先进先出”的原则。如下图所示&#xff1a; 在集合框架中我们可以看到LinkedList类继承了Queue类&#xff08;队列&#xff09;。 普通队列&#xff08;Queue&#xff09; Queue中的方法 …...

C语言蓝桥杯真题代码

以下是不同届蓝桥杯C语言真题代码示例&#xff0c;供参考&#xff1a; 第十三届蓝桥杯省赛 C语言大学B组 真题&#xff1a;卡片 题目&#xff1a;小蓝有很多数字卡片&#xff0c;每张卡片上都是数字1-9。他想拼出1到n的数列&#xff0c;每张卡片只能用一次&#xff0c;求最大的…...

Sharding-JDBC分库分表中的热点数据分布不均匀问题及解决方案

引言 在现代分布式应用中&#xff0c;使用Sharding-JDBC进行数据库的分库分表是提高系统性能和扩展性的常见策略。然而&#xff0c;在实际应用中&#xff0c;某些特定的数据&#xff08;如最新订单、热门商品等&#xff09;可能会成为“热点”&#xff0c;导致这些部分的数据处…...

Dagster中的Ops与Assets:数据管道构建的两种选择

Dagster是一个强大的数据编排平台&#xff0c;它提供了多种工具来帮助数据工程师构建可靠的数据管道。在Dagster中&#xff0c;Ops和Assets是两种核心概念&#xff0c;用于定义数据处理逻辑。本文将全面介绍Ops的概念、特性及其使用方法&#xff0c;特别补充了Op上下文和Op工厂…...

thonny提示自动补全功能

THONNY IDE 自动补全功能配置 在 Thonny IDE 中启用和优化自动补全功能可以显著提升编程体验。为了确保该功能正常工作&#xff0c;需要确认几个设置选项。 配置自动补全 Thonyy IDE 的自动补全默认情况下是开启的。如果发现自动补全未按预期运行&#xff0c;可以通过调整首选…...

PyTorch_阿达玛积

阿达玛积指的是矩阵对应位置的元素相乘&#xff0c;可以使用乘号运算符&#xff0c;也可以使用mul函数来完成计算。 代码 import torch import numpy as np # 1. 使用 mul 函数 def test01():data1 torch.tensor([[1, 2], [3, 4]])data2 torch.tensor([[5, 6], [7, 8]])dat…...

蓝桥杯 摆动序列

摆动序列 原题目链接 题目描述 如果一个序列的奇数项都比前一项大&#xff0c;偶数项都比前一项小&#xff0c;则称为一个摆动序列。 即对于任意整数 i&#xff08;i ≥ 1&#xff09;满足&#xff1a; a₂ᵢ < a₂ᵢ₋₁&#xff0c;a₂ᵢ₊₁ > a₂ᵢ 小明想知道&…...

AI 与生物技术的融合:开启精准医疗的新纪元

在科技飞速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;与生物技术的融合正在成为推动医疗领域变革的重要力量。精准医疗作为现代医学的重要发展方向&#xff0c;旨在通过深入了解个体的基因信息、生理特征和生活方式&#xff0c;为患者提供个性化的治疗方案。AI…...

三、shell脚本--运算符与表达式:让脚本学会“思考”

一、算术运算符&#xff1a;加减乘除取模 在我们写shell脚本时&#xff0c;做点基本的数学运算还是经常需要的。常用的算术运算符跟我们平时学的一样&#xff1a; : 加- : 减* : 乘 (小提示&#xff1a;有时候在某些命令里可能需要写成 \*)/ : 除 (在 Shell 里通常是取整数部分…...

c++ 指针参数传递的深层原理

指针参数传递的深层原理 理解为什么可以修改指针指向的内容但不能直接修改指针本身&#xff0c;需要深入理解指针在内存中的表示方式和函数参数传递机制。 1. 指针的内存表示 指针本质上是一个变量&#xff0c;它存储的是另一个变量的内存地址。在内存中&#xff1a; 假设有…...

【查看.ipynp 文件】

目录 如何打开 .ipynb 文件&#xff1f; 如果确实是 .ipynp 文件&#xff1a; .ipynp 并不是常见的 Jupyter Notebook 文件格式。通常&#xff0c;Jupyter Notebook 文件的扩展名是 .ipynb&#xff08;即 Interactive Python Notebook&#xff09;。如果你遇到的是 .ipynb 文…...

C++ 简单工厂模式详解

简单工厂模式&#xff08;Simple Factory Pattern&#xff09;是最简单的工厂模式&#xff0c;它不属于GoF 23种设计模式&#xff0c;但它是工厂方法模式和抽象工厂模式的基础。 概念解析 简单工厂模式的核心思想是&#xff1a; 将对象的创建逻辑集中在一个工厂类中 客户端不…...

ubuntu使用apt安装软件

1、使用apt list |grep jdk查看要安装的软件 此处以jdk为例 2、执行名称&#xff1a;安装指定版本的软件 sudo apt install openjdk-11-jdk...

TFT(薄膜晶体管)和LCD(液晶显示器)区别

TFT&#xff08;薄膜晶体管&#xff09;和LCD&#xff08;液晶显示器&#xff09;是显示技术中常见的术语&#xff0c;二者既有联系又有区别。以下是它们的核心区别和关系&#xff1a; 1. 基本概念 LCD&#xff08;液晶显示器&#xff09; LCD是一种利用液晶材料特性控制光线通…...

【文献阅读】中国湿地随着保护和修复的反弹

一、研究背景 滨海湿地是全球最具生态价值的生态系统之一&#xff0c;广泛分布在河口、潮间带、泻湖和盐沼等地带&#xff0c;在调节气候、水质净化、生物栖息以及防止海岸侵蚀等方面发挥着关键作用。然而&#xff0c;近年来滨海湿地正面临严峻威胁&#xff0c;全球估计约有50%…...

用Ensaio下载GIS数据

文章目录 简介重力场绘制 简介 Ensaio在葡萄牙语中是随笔的意思&#xff0c;是一个用于下载开源数据集的python库。其底层基于Pooch来下载和管理数据。 Ensaio可通过pip或者conda来安装 pip isntall ensaio conda install ensaio --channel conda-forge由于这个库功能较为单…...

【算法基础】递归算法 - JAVA

一、递归基础 1.1 什么是递归算法 递归算法是一种通过函数调用自身来解决问题的方法。简单来说&#xff0c;就是"自己调用自己"。递归将复杂问题分解为同类的更简单子问题&#xff0c;直到达到易于直接解决的基本情况。 1.2 递归的核心要素 递归算法由两个关键部…...

连续变量与离散变量的互信息法

1. 互信息法简介 互信息&#xff08;Mutual Information, MI&#xff09; 是一种衡量两个变量之间相互依赖程度的统计量&#xff0c;它来源于信息论。互信息可以用于评估特征与目标变量之间的相关性&#xff0c;无论这些变量是连续的还是离散的。互信息法是一种强大的特征选择…...

java_Lambda表达式

1、背景 lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。lambda表达式就和方法一样样&#xff0c;它提供了一个正常的参数列表和一个使用这些参数的主体&#xff08;body&#xff0c;可以是一个表达式和一个代码块&#xff09;。La…...

Python Cookbook-6.17 NuIl对象设计模式的实现

任务 你想减少代码中的条件声明&#xff0c;尤其是针对特殊情况的检查。 解决方案 一种常见的代表“这里什么也没有”的占位符是 None&#xff0c;但我们还可以定义一个类&#xff0c;其行为方式和这种占位符相似&#xff0c;而且效果更好: class Null(object):Null对象总是…...

Java接口全面教程:从入门到精通

目录 接口的基本概念 接口的特性 1. 访问修饰符 2. 接口中的常量 3. 接口中的方法 3.1 抽象方法&#xff08;传统用法&#xff09; 3.2 默认方法&#xff08;Java 8 引入&#xff09; 3.3 静态方法&#xff08;Java 8 引入&#xff09; 3.4 私有方法&#xff08;Java …...

Power Query精通指南3:数据库(查询折叠与数据隐私)、批量合并文件、自定义函数

文章目录 九、批量合并文件9.1 案例背景9.2 合并文件的标准流程9.3 示例&#xff1a;合并文件9.3.1 连接到文件夹9.3.1.1 连接到本地 / 网络文件夹9.3.1.2 连接到 SharePoint 文件夹9.3.1.3 连接到 OneDrive for Business9.3.1.4 连接到其他文件系统 9.3.2 筛选文件9.3.3 合并文…...

Python 学习

这里主要是为了记录我学习Python的过程&#xff0c;更多是使我规范书写Pyhton语言&#xff01; 1. 第一章 Python 定义&#xff1a;一种解释型的语言&#xff0c;区别于其他的高级语言&#xff0c;逐行翻译进行执行。 过程&#xff1a;首先编写编程语言&#xff0c;利用Pytho…...

生成式 AI 的优势

在科技飞速发展的今天,人工智能已经不再是一个遥不可及的概念,而是逐渐渗透到我们生活的方方面面。其中,生成式 AI 更是如同一颗璀璨的新星,在人工智能的浩瀚星空中闪耀着独特的光芒。它究竟有哪些令人瞩目的优势,又为何会成为我们这个时代无法忽视的存在呢? 生成式 AI …...

Hal库下备份寄存器

首先要确保有外部电源给VBAT供电 生成后应该会有这两个文件&#xff08;不知道为什么生成了好几次都没有&#xff0c;复制工程在试一次就有了&#xff09; 可以看到stm32f407有20个备份寄存器 读写函数 void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t Backup…...

P1537 数字反转(升级版)详解

这个题目还是对于新手比较锻炼思维严谨性的&#xff0c;我认为是在我做过的一些题目中&#xff0c;此题算上等马 先看题目 我先说明我自己的思路&#xff0c;以及这个题目你需要特别注意的地方 1&#xff0c;数字反转&#xff0c;①可用<algorithm>库里面的reverse函数…...

operator 可以根据需要重载 == 运算符进行比较

要将 vector<AppInfo> 类型的 A 和 B 两个容器进行比较&#xff0c;并且当 B 中有 A 中没有的元素时&#xff0c;插入到数据库中&#xff0c;你可以通过以下步骤实现&#xff1a; 比较元素&#xff1a;遍历 vector<B>&#xff0c;检查每个元素是否在 vector<A&…...

网格不迷路:用 CSS 网格生成器打造完美布局

前言 你是否曾因写错 grid-template-areas 而捶键盘?是否在面对千层嵌套的复杂布局时,瞬间怀疑人生,甚至思考要不要转行去卖奶茶?别慌,CSS 网格生成器闪亮登场,像拼乐高一样,帮你轻松搭建网页结构,还能自动输出干净代码,堪称“前端界的乐高大师”。让我们放下枯燥的代…...

Go小技巧易错点100例(二十八)

本期分享&#xff1a; 1. runtime.Caller(1)获取调用者信息 2. for循环 select{}语法 正文&#xff1a; runtime.Caller(1)获取调用者信息 在 Go 语言中&#xff0c;runtime.Caller(1) 是 runtime 包提供的一个函数&#xff0c;用于获取当前 goroutine 的调用堆栈中的特定…...