《真象还原》读书笔记——第八章 内存管理系统(字符串操作、位图定义与实现)

8.1 makefile

8.2 实现 assert 断言

8.2.1 实现开、关中断的函数

kernel/interrupt.c
补充了获取中断状态和设置中断的函数


#include "io.h"
#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "print.h"#define IDT_DESC_CNT	0x21	//支持的中断数目
#define PIC_M_CTRL  0x20    // 主片的控制端口是 0x20 
#define PIC_M_DATA  0x21    // 主片的数据端口是 0x21
#define PIC_S_CTRL  0xa0    // 从片的控制端口是 0xa0
#define PIC_S_DATA  0xa1    // 从片的数据端口是 0xa1#define EFLAGS_IF   0x00000200  //eflags 寄存器if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAG_VAR));// 中断门描述符结构体
struct gate_desc{uint16_t    func_offset_low_word;uint16_t    selector;uint8_t     dcount;uint8_t     attribute;uint16_t    func_offset_high_word;
};//静态函数声明 非必须
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT]; // idt 是中断描述符表
// 声明引用定义在 kernel.S 中的中断处理函数入口数组
extern intr_handler intr_entry_table[IDT_DESC_CNT]; 
char *intr_name[IDT_DESC_CNT];  //保存异常的名字
/*定义中断处理程序数组,在kernel.asm中定义的intrXXentry只是中断处理程序的入口,最终调用的是 ide_table 中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];/*初始化可编程中断处理器 8259A*/
static void pic_init(void) {/*初始化主片 */ outb (PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4 outb (PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为 0x20 // 也就是 IR[0-7] 为 0x20 ~ 0x27 outb (PIC_M_DATA, 0x04); // ICW3: IR2 接从片outb (PIC_M_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI /*初始化从片 */ outb (PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联 8259, 需要 ICW4 outb (PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为 0x28 // 也就是 IR[8-15]为 0x28 ~ 0x2F outb (PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的 IR2 引脚outb (PIC_S_DATA, 0x01); // ICW4: 8086 模式, 正常 EOI /*打开主片上 IR0,也就是目前只接受时钟产生的中断 */ outb (PIC_M_DATA, 0xfe); //1111 1110 feoutb (PIC_S_DATA, 0xff); put_str(" pic_init done\n"); }/*创建中断门描述符*/
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (int32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void){int i;for(i = 0; i < IDT_DESC_CNT; ++i){make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);}put_str("   idt_desc_init done\n");
}/*通用的中断处理函数*/static void general_intr_handler(uint8_t vec_nr){if(vec_nr == 0x27 || vec_nr == 0x2f){//IRQ7和IRQ5会产生伪中断,无需处理//0x2f是从片 8259A 上的最后一个 IRQ 引脚,保留项return;}put_str("int vector : 0x");put_int(vec_nr);put_char(' ');put_str(intr_name[vec_nr]);put_char('\n');}/*完成一般中断处理函数注册及异常名称注册*/
static void exception_init(void){int i;for(i = 0; i < IDT_DESC_CNT; ++i){/*idt_table中的函数是在进入中断后根据中断向量好调用的见kernel/kernel.asm 的 call[idt_table + %1*4]*/idt_table[i] = general_intr_handler;//默认为general_intr_handler//以后会有 register_handler注册具体的处理函数intr_name[i] = "unknown";   //先统一为 "unknown"}intr_name[0] = "#DE Divide Error";intr_name[1] = "#DB Debug Exception";intr_name[2] = "NMI Interrupt";intr_name[3] = "#BP Breakpoint Exception";intr_name[4] = "#OF Overflow Exception";intr_name[5] = "#BR BOUND Range Exceeded Exception";intr_name[6] = "#UD Invalid Opcode Exception";intr_name[7] = "#NM Device Not Available Exception";intr_name[8] = "#DF Double Fault Exception";intr_name[9] = "Coprocessor Segment Overrun"; intr_name[10] = "#TS Invalid TSS Exception";intr_name[11] = "#NP Segment Not Present";intr_name[12] = "#SS Stack Fault Exception";intr_name[13] = "#GP General Protection Exception"; intr_name[14] = "#PF Page-Fault Exception";// intr_name[15] 第 15 项是 intel 保留项,未使用intr_name[16] = "#MF x87 FPU Floating-Point Error";intr_name[17] = "#AC Alignment Check Exception"; intr_name[18] = "#MC Machine-Check Exception";intr_name[19] = "#XF SIMD Floating-Point Exception";intr_name[0x20] = "#CLOCK";}/*获取当前中断状态*/
enum intr_status intr_get_status(void){uint32_t eflags = 0;GET_EFLAGS(eflags);return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}
/*设置中断状态*/
enum intr_status intr_set_status(enum intr_status status){return (status & INTR_ON) ? intr_enable():intr_disable();
}/*开中断,返回之前的状态*/
enum intr_status intr_enable(){enum intr_status old_status;if(INTR_ON == intr_get_status()){old_status = INTR_ON;}else{old_status = INTR_OFF;asm volatile("cli":::"memory");//关中断 cli 将IF置1}return old_status;
}
/*关闭中断*/
enum intr_status intr_disable(void){enum intr_status old_status;if(INTR_ON == intr_get_status()){old_status = INTR_ON;asm volatile("cli" : : : "memory");}else{old_status = INTR_OFF;}return old_status;
}/*完成有关中断的所有初始化工作*/void idt_init(){put_str("idt_init start\n");idt_desc_init();    // 初始化中断描述符表exception_init();   //异常名和中断处理函数初始化pic_init();         //初始化 8259A/*加载 idt*/uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)((uint32_t)idt << 16))); asm volatile("lidt %0"::"m"(idt_operand));put_str("idt_init done\n");}

kernel / interrupt.h

#ifndef __KERNEL_INTERRUPT_H
#define __KERNEL_INTERRUPT_H#include "stdint.h"
typedef void* intr_handler;enum intr_status{INTR_OFF,INTR_ON
};void idt_init();enum intr_status intr_get_status(void);
enum intr_status intr_set_status(enum intr_status);
enum intr_status intr_enable(void);
enum intr_status intr_disable(void);#endif

kernel / debug.h

#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_Hvoid panic_spin(char* filename, int line, const char* func, const char* condition); 
/************* __VA_ARGV__ ********** __VA_ARGS__ 是预处理器所支持的专用标识符* 代表所有与省略号相对应的参数* "..."表示定义的宏其参数可变。*/
#define PANIC(...) panic_spin(__FILE__, __LINE__, __func__, __VA_ARGS__)
/*********************************************/#ifdef NDEBUG#define ASSERT(CONDITION) ((void)0)#else#define ASSERT(CONDITION) \if(CONDITION){}else{ \/*符号#让编译器将宏的参数转化为字符串字面量 */ \PANIC(#CONDITION);  \}#endif /*__NDEBUG*/
#endif /*__KERNEL_DEBUG_H*/

kernel / debug.c

#include "debug.h"
#include "print.h"
#include "interrupt.h"/*打印文件名、行号、函数名、条件并使程序悬停*/
void panic_spin(char *filename, int line, const char *func, const char *cons )
{intr_disable();//有时候会单独调用 panic_spin , 所以在此处关中断put_str("\n\n\n!!!!! error !!!!!\n");put_str("filename:");put_str(filename);put_str("\n");put_str("line:0x");put_int(line);put_str("\n");put_str("function:");put_str((char*)func);put_str("\n");put_str("condition:");put_str((char*)cons);put_str("\n");while(1);
}

kernel / main.c

#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){put_str("I am kernel\n");init_all();ASSERT(1==2);//asm volatile("sti");while(1);return 0;
}

运行结果:
在这里插入图片描述

8.3 实现字符串操作函数

lib / string.h

#ifndef __LIB_STRING_H
#define __LIB_STRING_H#include "stdint.h"/*将dst_起始的size个字节置为value*/void memset(void *dst_, uint8_t value, uint32_t size);/*将src_起始的size个字节复制到 dst_ */void memcpy(void *dst_, const void *src, uint32_t size);/*连续比较以地址a_和地址b_开头的size个字节.相等则返回 0, a>b return 1, a<b return -1;*/
int memcmp(const void *a_, const void *b_, uint32_t size);/*src_ 复制到 dst_*/
char *strcpy(char *dst_, const char *src_);/*返回字符串长度*/
uint32_t strlen(const char *str){;
/*比较两个字符串,若a_中的字符大于b_中的字符调用1,相等返回0,否则返回-1*/
int8_t strcmp(const char *a, const char *b);/*左到右找str中首次出现的 ch 的地址*/
char *strchr(const char *str, const uint8_t ch);/*从后往前找字符串str中首次出现的ch的地址*/
char *strrchr(const char *str, const uint8_t ch);/*将字符串 src 拼接到dst_后,返回拼接的字符串地址*/
char *strcat(char *dst_, const char *str_);/*在字符串str中查找字符ch出现的次数*/
uint32_t strchrs(const char *str, uint8_t ch);#endif /*__LIB_STRING_H*/

lib / string.c

#include "string.h"
#include "global.h"
#include "debug.h"/*将dst_起始的size个字节置为value*/void memset(void *dst_, uint8_t value, uint32_t size){ASSERT(dst_ != NULL);uint8_t *dst = (uint8_t*)dst_;while(size-- > 0){*dst++ = value;}return;}/*将src_起始的size个字节复制到 dst_ */void memcpy(void *dst_, const void *src, uint32_t size){ASSERT(dst_ != NULL && src_ != NULL);uint8_t *dst = dst_;const uint8_t *src = src_;while(size-- > 0){*dst++ = *src++;}}/*连续比较以地址a_和地址b_开头的size个字节.相等则返回 0, a>b return 1, a<b return -1;*/
int memcmp(const void *a_, const void *b_, uint32_t size)
{const char *a = a_;const char *b = b_;ASSERT(a != NULL || b != NULL);while(size-- > 0){if(*a != *b){return (*a > *b) ? 1: -1;}++a;++b;}return 0;
}/*src_ 复制到 dst_*/
char *strcpy(char *dst_, const char *src_)
{ASSERT(dst_ != NULL && src_ != NULL);char *r = dst_;while((*dst_ ++ = *src_ ++));return r;
}/*返回字符串长度*/
uint32_t strlen(const char *str){ASSERT(str != NULL);const char *p = str;while(*p++);return (p - str - 1);
}
/*比较两个字符串,若a_中的字符大于b_中的字符调用1,相等返回0,否则返回-1*/
int8_t strcmp(const char *a, const char *b)
{ASSERT(a != NULL && b != NULL);while(*a != 0 && *a == *b){++a;++b;}//if(*a < *b) return -1;//if(*a > *b) return 1; else return 0;return (*a < *b) ? -1 : (*a > *b);
}/*左到右找str中首次出现的 ch 的地址*/
char *strchr(const char *str, const uint8_t ch)
{ASSERT(str != NULL);while(*str != 0){if(*str == ch){return (char *)str;//需要强制转化为返回值类型。}++str;}return NULL;
}/*从后往前找字符串str中首次出现的ch的地址*/
char *strrchr(const char *str, const uint8_t ch)
{ASSERT(str != NULL);const char *last_char = NULL;while(*str != 0){if(*str == ch){last_char = str;}++str;}return (char *)last_char;
}/*将字符串 src 拼接到dst_后,返回拼接的字符串地址*/
char *strcat(char *dst_, const char *str_)
{ASSERT(dst_ != NULL && src_ != NULL);char *str = dst_;while(*str++);--str;while((*str++ = *src_++));return dst_;
}/*在字符串str中查找字符ch出现的次数*/
uint32_t strchrs(const char *str, uint8_t ch){ASSERT(str != NULL);uint32_t ch_cnt = 0;const char *p = str;while(*p != 0){if(*p == ch){++ch_cnt;}++p;}return ch_cnt;
}

8.4 位图 bitmap 及其函数的实现

8.4.1 位图简介

一个字节有8位,所以位图的一个字节对应8个资源单位。假设是管理内存,每一位都将表示实际物理内存中的 4KB。也就是 1页。如果某位为0,就是可以分配,如果某位为1,就是不可分配。
在这里插入图片描述

8.4.2 位图的定义和实现

lib / kernel / bitmap.h

#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct bitmap{uint32_t btmp_bytes_len;/*遍历位图时候是以字节为单位,微操是位。所以此处的位图指针必须是单字节*/uint8_t *bits;
};
void bitmap_init(struct bitmap *btmp);
bool bitmap_scan_test(struct bitmap *btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap *btmp, uint32_t cnt);
void bitmap_set(struct bitmap *btmp, uint32_t bit_idx, int8_t value);

lib/ kernel/ bitmap.c

#include "bitmap.h"
#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"/*初始化 btmp 位图*/
void bitmap_init(struct bitmap *btmp)
{memset(btmp->bits, 0, btmp->btmp_bytes_len);
}bool bitmap_scan_test(struct bitmap *btmp, uint32_t bit_idx)
{uint32_t byte_idx = bit_idx / 8;//向下取整用于数组索引。uint32_t bit_odd = bit_idx % 8; //取余用索引数组内的位return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
}int bitmap_scan(struct bitmap *btmp, uint32_t cnt)
{uint32_t idx_byte = 0;/*先字节比较*/while((0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)){//该字节无空位,去下一个字节++idx_byte;}ASSERT(idx_byte < btmp->btmp_bytes_len);if(idx_byte == btmp->btmp_bytes_len){ //找不到可用空间return -1;}//某字节有空位,则依次查找int idx_bit = 0;while((uint8_t)(BITMAP_MASK << idx_bit & btmp->bits [idx_byte]){++idx_bit;}int bit_idx_start = idx_byte * 8 + idx_bit;if(cnt == 1){return bit_idx_start;}uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start);//记录还有多少位可以判断uint32_t next_bit = bit_idx-start + 1;uint32_t count = 1;bit_idx_start = -1;while (bit_left -- > 0){if(!(bitmap_scan_test(btmp, next_bit))){count++;}else{count = 0;}if(count == cnt){bit_idx_start = next_bit - cnt + 1;break;}next_bit++;}return bit_idx_start;
}
/*将位图bit_idx设置为value*/
void bitmap_set(struct bitmap *btmp, uint32_t bit_idx, int8_t value)
{ASSERT((value == 0) || (value == 1));uint32_t byte_idx = bit_idx / 8;uint32_t bit_odd = bit_idx % 8;if(value){btmp->bits[byte_idx] != (BITMAP_MASK << bit_odd);}else{btmp->bits[byte_idx] &= (BITMAP_MASK << bit_odd);}
}

8.5 内存管理系统

8.5.1 内存池规划

分页机制下有了虚拟、物理这两种地址,们本节所讨论的就是有关这两类地址的内存池规划问题。
创建虚拟内存地址池和物理内存地址池
内存的规划
分为内核内存池和用户内存池。
内存池中的内存单位大小是 4KB.
所以任务都有各自的 4GB,需要为所有任务维护它们自己的虚拟地址池,一个任务一个。
在这里插入图片描述
kernel / memory.h

#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H#include "stdint.h"
#include "bitmap.h"/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr{struct bitmap vaddr_bitmap; // 虚拟地址用到的位图结构uint32_t vaddr_start;       // 虚拟地址起始地址
};
extern struct pool kernel_pool, user_pool;
void mem_init(void);#endif

kernel / memory.c

#include "memory.h"
#include "stdint.h"
#include "print.h"#define PG_SIZE 4096/*************** 位图地址 ******************* 因为0xc009f00 是内核主线程栈顶, 0xc009e000 是内核主线程的 pcb* 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址 0xc009a000,* 这样本系统最大支持 4个页框的位图,即 512 MB
*/
#define MEM_BITMAP_BASE 0xc009a000/* 0xc0000000 是内核从虚拟地址 3G 起,0x100000 指跨过低端 1MB 内存,使虚拟地址在逻辑上连续。*/
#define K_HEAP_START 0xc0100000
/* 内存池结构,生成两个实例,用于管理内核和用户内存池*/
struct pool{struct bitmap pool_bitmap;  //本内存池用到的位图结构,用于管理物理内存uint32_t phy_addr_start;    // 本内存池所管理物理内存的起始地址uint32_t pool_size;         // 本内存池字节容量
};
struct pool kernel_pool, user_pool; // 生成内核内存池和用户内存池
struct virtual_addr kernel_vaddr; // 此结构用来给内核分配虚拟地址/*初始化内存池*/
static void mem_pool_init(uint32_t all_mem)
{put_str("   mem_pool_init start\n");uint32_t page_table_size = PG_SIZE * 256;/*页表大小 = 页目录表(1页) + 第0和第768页目录框指向同一个页表(第1个页表)+第 769~1022个页目录项共指向 254 个页表(254页),共 256 个页框*/uint32_t used_mem = page_table_size + 0x100000;//0x100000 为低端 1MB 内存。uint32_t free_mem = all_mem - used_mem;uint16_t all_free_pages = free_mem / PG_SIZE;/*1页为4KB,不管总内存是不是 4k 的倍数,*对于以页为单位的内存分配策略,不足一页的内存不用考虑了*/uint16_t kernel_free_pages = all_free_pages / 2; uint16_t user_free_pages = all_free_pages - kernel_free_pages;/* 为简化位图操作,余数不处理,坏处是这样做会丢内存。好处是不用做内存的越界检查,因为位图表示的内存少于实际物理内存*/uint32_t kbm_length = kernel_free_pages / 8; // Kernel BitMap 的长度,位图中的一位表示一页,以字节为单位uint32_t ubm_length = user_free_pages / 8;// User BitMap 的长度//Kernel pool start,内核内存池的起始地址。uint32_t kp_start = used_mem;//User Pool start,  用户内存池的起始地址uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;kernel_pool.phy_addr_start = kp_start;user_pool.phy_addr_start = up_start;kernel_pool.pool_size = kernel_free_pages * PG_SIZE;user_pool.pool_size = user_free_pages * PG_SIZE;kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;user_pool.pool_bitmap.btmp_bytes_len = ubm_length;/********* 内核内存池和用户内存池位图 ************ 位图是全局的数据,长度不固定。* 全局或静态的数组需要在编译时知道其长度,* 而我们需要根据总内存大小算出需要多少字节,* 所以改为指定一块内存来生成位图。***********************************************///内核使用的最高 0xc009f000,这是主线程的栈地址//(内核的大小预计为70KB左右)//32MB内存占有的位图为 2KB//内核内存池的位图先定在 MEM_BITMAP_BASE (0xc009a000) 处kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;/* 用户内存池的位图紧跟在内核内存池位图之后 */user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);/********************输出内存池信息**********************/ put_str(" kernel_pool_bitmap_start:");put_int((int)kernel_pool.pool_bitmap.bits); put_str(" kernel_pool_phy_addr_start:"); put_int(kernel_pool.phy_addr_start);put_str("\n"); put_str("user_pool_bitmap_start:");put_int((int)user_pool.pool_bitmap.bits);put_str(" user_pool_phy_addr_start:");put_int(user_pool.phy_addr_start);put_str("\n");/* 将位图置于 0*/bitmap_init(&kernel_pool.pool_bitmap);bitmap_init(&user_pool.pool_bitmap); put_str("bitmap_init2\n");/* 下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/ kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length; // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致/* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);kernel_vaddr.vaddr_start = K_HEAP_START;bitmap_init(&kernel_vaddr.vaddr_bitmap);put_str("   mem_pool_init done\n");
}/*内存管理部分初始化入口*/
void mem_init()
{put_str("mem_init start\n");uint32_t mem_bytes_total = (*(uint32_t *)(0xb00));mem_pool_init(mem_bytes_total);// 初始化内存池put_str("mem_init done\n");
}

还有一点:
这里的 total_mem_bytes 一定要放在这个位置上,恰好是 地址 0xb000。
因为 mem_init() 中要用到这个地址。上面存放的是内存大小。

loader.asm

%include "boot.inc"section loader vstart=LOADER_BASE_ADDRGDT_BASE:dd  0x00000000dd  0x00000000
CODE_DESC:dd  0x0000FFFFdd  DESC_CODE_HIGH4
DATA_STACK_DESC:dd  0x0000FFFFdd  DESC_DATA_HIGH4
VIDEO_DESC:dd  0x80000007dd  DESC_VIDEO_HIGH4
GDT_SIZE            equ $-GDT_BASE
GDT_LIMIT           equ GDT_SIZE-1
times   60 dq   0;预留60个描述符的空位
;选择子
SELECTOR_CODE   equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA   equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO  equ (0x0003 << 3) + TI_GDT + RPL0;============= 读取内存数 
total_mem_bytes     dd  0  ;4 保存内存容量
;gdt 指针
gdt_ptr dw  GDT_LIMITdd  GDT_BASEards_buf    times   244 db 0
ards_nr     dw      0
/* ...略 */

kernel / init.c

#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"
#include "memory.h"/*负责初始化所有模块*/
void init_all()
{put_str("init_all\n");idt_init(); //初始化中断//timer_init();mem_init();
}

kernel / main.c

#include "print.h"
#include "init.h"
#include "debug.h"
int main(void){put_str("I am kernel\n");init_all();//ASSERT(1==2);//asm volatile("sti");while(1);return 0;
}

运行结果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Navicat Premium创建MySQL存储过程

1、点击“函数”&#xff0c;新建函数&#xff1b; 2、选择“过程”&#xff0c;输入存储过程名称&#xff0c;点击完成即可&#xff0c;可以跳过参数的设置&#xff1b; 3、编写存储过程的代码&#xff0c;保存&#xff1b; CREATE DEFINERrootlocalhost PROCEDURE insertuser…

【DDD】学习笔记-领域驱动设计对持久化的影响

资源库的实现 如何重用资源库的实现&#xff0c;以及如何隔离领域层与基础设施层的持久化实现机制&#xff0c;具体的实现还要取决于开发者对 ORM 框架的选择。Hibernate、MyBatis、jOOQ 或者 Spring Data JPA&#xff08;当然也包括基于 .NET 的 Entity Framework、NHibernat…

​LeetCode解法汇总2673. 使二叉树所有路径值相等的最小代价

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个整数 n 表示一棵 满二叉树 里面节…

抖音作品评论id提取工具|视频内容提取软件

抖音视频提取便捷高效&#xff0c;抖音作品评论id提取工具助您快速获取数据 针对抖音作品评论id提取的需求&#xff0c;我们推出了一款功能强大的工具&#xff0c;旨在帮助用户快速提取抖音作品的评论id。无论您是进行数据分析、社交媒体研究还是其他用途&#xff0c;我们的工…

Linux------进程地址空间

目录 一、进程地址空间 二、地址空间本质 三、什么是区域划分 四、为什么要有地址空间 1.让进程以统一的视角看到内存 2.进程访问内存的安全检查 3.将进程管理与内存管理进行解耦 一、进程地址空间 在我们学习C/C的时候&#xff0c;一定经常听到数据存放在堆区、栈区、…

java多线程并发实战,java高并发场景面试题

阶段一&#xff1a;筑基 Java基础掌握不牢&#xff0c;对于一个开发人员来说无疑是非常致命的。学习任何一个技术知识无疑不是从基础开始&#xff1b;在面试的时候&#xff0c;面试官无疑不是从基础开始拷问。 内容包括&#xff1a;Java概述、Java基本语法、Java 执行控制流程、…

代码随想录算法训练营第26天—回溯算法06 | ● *332.重新安排行程 ● *51. N皇后 ● *37. 解数独 ● 总结

*332.重新安排行程 https://programmercarl.com/0332.%E9%87%8D%E6%96%B0%E5%AE%89%E6%8E%92%E8%A1%8C%E7%A8%8B.html 考点 图论里的深度优先搜索&#xff08;本题使用回溯来解决&#xff09;这是一道hard题&#xff0c;一刷先放过去&#xff0c;二刷有精力再做 我的思路 无思…

Hybird App开发,纯血鸿蒙系统快速兼容救星

2024年1月18日的开发者&#xff08;HDC&#xff09;大会上&#xff0c;就官宣了“纯血鸿蒙”操作系统即将于2024年3季度正式投产。与此同时&#xff0c;支付宝、京东、小红书、微博、高德地图、中国移动等在内的超百个头部应用都启动了鸿蒙原生应用开发&#xff0c;鸿蒙开发者日…

多域名ov ssl证书1200元

SSL证书是一种特殊的数字证书产品&#xff0c;它是维护互联网信息安全的重要手段之一&#xff0c;部署到服务器之后可以保护网站信息传输安全。因此&#xff0c;随着互联网的发展&#xff0c;SSL证书也随之越来越受到众多开发者的重视。SSL证书的数字证书产品多种多样&#xff…

手写mybatis插件之分页查询

yml文件 server:port: 8081mybatis:mapper-locations: classpath:mapper/*.xmlconfig-location: classpath:mybatis-config.xmlspring:datasource:password: 1234username: rootdriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis?useUnicod…

react-组件基础

1.目标 能够使用函数创建组件 能够使用class创建组件 能够给React元素绑定事件 能够使用state和setState() 能够处理事件中的this指向问题 能够使用受控组件方式处理表单 2.目录 React组件介绍 React组件的两种创建方式 React事件处理 有状态组件和无状态组件 组件中的state…

4G工牌室内外定位系统

4G工牌室内外定位系统是一种高效、精准的定位技术&#xff0c;它利用4G通信网络和GPS卫星定位系统&#xff0c;实现了对人员和物品的实时跟踪和定位。该系统广泛应用于企业管理、安全监控、智能交通等领域&#xff0c;为企业提供了更加高效、便捷的管理方式。 在室内环境中&am…

链表(C语言版)超详细讲解

链表 链表基础 一、链表的概念 定义&#xff1a; 链表是一种物理存储上非连续&#xff0c;数据元素的逻辑顺序通过链表中的指针链接次序&#xff0c;实现的一种线性存储结构。二、链表的构成 构成&#xff1a;链表由一个个结点组成&#xff0c;每个结点包含两个部分&#xff1…

全网最详细的Jmeter接口自动化测试

前面我们复习了jmeter 的非图形化界面运行我们的测试接口。 大家可以翻看往期jmeter的文章。 具体来说就是&#xff1a;jmeter -n -t ****.jmx -l ****.jtl -e -o **** (*号代表路径&#xff09; 生成了测试报告。 但是这个非图形化运行有个缺点&#xff0c;就是只能运…

蓝牙资产标签信标

随着科技的不断进步&#xff0c;蓝牙技术的应用已经深入到我们的日常生活中。其中&#xff0c;蓝牙资产标签作为一种新型的资产管理方式&#xff0c;正逐渐受到广泛欢迎。蓝牙资产标签是一种基于蓝牙技术的小型电子标签&#xff0c;可以粘贴在各种资产上&#xff0c;通过手机或…

代码随想录算法训练营第27天—贪心算法01 | ● 理论基础 ● 455.分发饼干 ● 376. 摆动序列 ● 53. 最大子序和

理论基础 https://programmercarl.com/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 贪心算法的本质&#xff1a;由局部最优推到全局最优贪心算法的套路&#xff1a;无固定套路 455.分发饼干 https://programmercarl.com/0455.%E5%88%8…

(白盒测试)简单循环测试

简单循环测试 1.为什么要引入简单循环测试&#xff1f; 用来测试代码中的循环结构是否能正常执行 是否会少执行一次&#xff1f;多执行一次&#xff1f; 通过循环测试就可以得知 2.什么是简单循环&#xff1f; 没有嵌套的循环⇒简单循环 比如 单层的for循环 单层的while循…

【Qt】鼠标拖拽修改控件尺寸---八个方位修改

前提 在开发一个类似qdesiger的项目中 使用QGraphicsProxyWidget将Qt基础控件作为item放在场景视图中显示和编辑 创建自定义类继承QGraphicsProxyWidget&#xff0c;管理控件 成员变量 有控件的xywh等&#xff0c;其中x、y坐标存储是基于最底层widgetitem的 坐标系 x轴以右为正…

anaconda指定目录创建环境无效/环境无法创建到指定位置

已经设置目录到D盘 创建环境时还是分配到C盘 可能是指定位置没有开启读写权限&#xff0c;如我在这里安装到了anaconda文件夹&#xff0c;则打开该文件夹的属性->安全->编辑 allusers下的权限全都打勾

【DAY05 软考中级备考笔记】线性表,栈和队列,串数组矩阵和广义表

线性表&#xff0c;栈和队列&#xff0c;串数组矩阵和广义表 2月28日 – 天气&#xff1a;阴转晴 时隔好几天没有学习了&#xff0c;今天补上。明天发工资&#xff0c;开心&#x1f604; 1. 线性表 1.1 线性表的结构 首先线性表的结构分为物理结构和逻辑结构 物理结构按照实…