操作系统真象还原:内存管理系统

news2024/9/18 23:07:23

第8章-内存管理系统

这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件

8.1 Makefile简介

8.1.1 Makefile是什么
8.1.2 makefile基本语法

在这里插入图片描述

make 给咱们提供了方法,可以在命令之前加个字符’@’,这样就不会输出命令本身信息了

8.1.3 跳到目标处执行

我们可以用目标名称作为 make 的参数,采用“ make 目标名称”的方式,单独执行目标名称处的规则.

8.1.4 伪目标

make 规定,当规则中不存在依赖文件时,这个目标文件名就称为一一伪目标。伪目标,顾名思义,也就是不产生真实的目标文件,所以当然也就不需要依赖文件了。于是,伪目标所在的规则就变成了纯粹地执行命令,只要给 make 指定该伪目标名做参数,就能让伪目标规则中的命令直接执行。

为了避免伪目标和真实目标文件同名的情况,可以用关键字“ .PHONY”来修饰伪目标,格式为“ .PHONY:伪目标名”,这样不管与伪目标同名的文件是否存在, make 照样执行伪目标处的命令。

在这里插入图片描述

8.1.5 make:递归式推导目标
8.1.6 自定义变量和系统变量

变量定义的格式:变量名=值(字符串),多个值之间用空格分开。 make 程序在处理时会用空格将值打散,然后遍历每一个值。另外,值仅支持字符串类型,即使是数字也被当作字符串来处理 。

变量引用的格式:$(变量名)。这样,每次引用变量时,变量名就会被其值(宇符串)替换。

在这里插入图片描述

8.1.8自动变量

$@:表示规则中的目标文件名集合,如果存在多个目标文件,$@则表示其中每一个文件名。

$<: 表示规则中依赖文件中的第 1 个文件。

$^:表示规则中所有依赖文件的集合,如果集合中有重复的文件,$^会自动去重。

$?:表示规则中,所有比目标文件 mtime 更新的依赖文件集合。
在这里插入图片描述

8.1.9模式规则

%:用来匹配任意多个非空字符。比如%.o 代表所有以.o为结尾的文件,g%s.o是以字符 g 开头的所有以.o。为结尾的文件, make 会拿这个字符串模式去文件系统上查找文件,默认为当前路径下。

在这里插入图片描述

8.2实现assert断言

8.2.1实现开、关中断的函数
#define EFLAGS_IF 0x00000200 //eflags寄存器中if位为1
#define GET_EFLAGS (EFLAG_VAR) asm volatile("pushfl; popl %0":"=g"(EFLAG_VAR)) //读取当前的标志寄存器 EFLAGS 的值,并将其存储到 C 语言中的变量 EFLAG_VAR 中

/*定义中断的两个状态*/
enum intr_status{
    INTR_OFF,
    INTR_ON
};

/*开中断并返回开中断前的状态*/
enum inter_status intr_enable(){
    enum intr_status old_status;
    if(INTR_ON==intr_get_status()){
        old_status = INTR_ON;
        return old_status;
    }else{
        old_status = INTR_OFF;
        asm volatile("sti");        //开启中断,sti指令将IF位置1
        return old_status;
    }
}

/*关中断,并返回开关断前的状态*/
enum inter_status intr_disable(){
    enum intr_status old_status;
    if(INTR_ON==intr_get_status()){
        old_status = INTR_ON;
        asm volatile("cli": : :"memory");        //关闭中断,sti指令将IF位置0
        return old_status;
    }else{
        old_status = INTR_OFF;
        return old_status;
    }
}

/*将中断状态设置为status*/
enum intr_status intr_set_status(enum intr_status status){
    return status & INTR_ON ? intr_enable() : intr_disable();
}

/*获取当前中断状态*/
enum intr_status intr_get_status(){
    uint32_t eflags = 0;
    GET_EFLAGS(eflags);
    return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

8.2.2实现ASSERT

在C语言中 ASSERT是用宏来定义的,其原理是判断传给 ASSERT 的表达式是否成立,若表达式成立则什么都不做,否则打印出错信息并停止执行。

__FILE__,__LINE__,__func__,这三个是预定义的宏,分别表示被编译的文件名、被编译文件中的行号、被编译的函数名。

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-08 09:38:54
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-08 10:26:38
 * @FilePath: /OS/chapter8/8.2/kernel/debug.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "debug.h"
#include "print.h"
#include "interrupt.h"

void panic_spin(char* filename, int line, const char* func, const char* condition){
    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*)condition);put_str("\n");
    while(1);
}
#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line, const char* func, const char* condition);

/***************************************************__VA_ARGS__***************************************************
*__VA_ARGS__是预处理器所支持的专用标识符。
*代表所有与省略号相对应的参数。
*”…”表示定义的宏其参数可变。
*/
#define PANIC(...) panic_spin(__FILE__,__LINE__,__func__,__VA_ARGS__)
/****************************************************************************************************************/

#ifdef NDEBUF
    #define ASSERT(CONDITION) ((void)0)
#else
    #define ASSERT(CONDITION) \
    if(CONDITION){ }else{   \
        /*符号#让编译器将宏的参数转化为字符串字面量,就是转化为字符串*/ \
        PANIC(#CONDITION); \
    }
#endif // NDEBUF

#endif //__KERNEL_DEBUG_H

8.3实现字符串操作函数

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-08 11:22:49
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-08 12:18:47
 * @FilePath: /OS/chapter8/8.3/lib/string.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#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;
}

/*将 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 个字节,若相等则返回 O,若 a_大于 b_ ,返回 +1 ,否则返回-1 */
int memcmp(const void* a_, const void* b_, uint32_t size){
    const uint8_t* a = a_;
    const uint8_t* 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(cosnt char* a, const char* b){
    ASSERT(a!=NULL&&b!=NULL);
    while(*a!=0&&*a==*b){
        a++;
        b++;
    }
    return *a<*b ? -1 : *a>*b;
}

/*从左到右查找字符串str中首次出现字符ch的地址*/
char* strchr(const char* str, const uint8_t ch){
    ASSERT(str!=NULL);
    while(*str!=0){
        f(*str==ch)
            return (char*)str; //需要强制转化成和返回值类型一样否则编译器会报 const 属性丢失
        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* src_){
    ASSERT(dst_!=NULL&&src_!=NULL);
    char* str = dst_;
    while(*str++);
    --str;
    while((*str++=*src++)); //当str被赋值为0时也就是表达式不成立,正好添加了字符串结尾的。
    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及其函数的实现

位图,也就是 bitmap,广泛用于资源管理,是一种管理资源的方式、手段。“资源”包括很多,比如内存或硬盘,对于此类大容量资源的管理一般都会采用位图的方式。

位图包含两个概念:位和图 。 位是指 bit,即字节中的位, 1 字节中有 8 个位。图是指 map, map 这个词在很久之前就介绍过啦,地图本质上就是映射的意思,映射,即对应关系。综合起来,位图就是用字节中的 1 位来映射其他单位大小的资源,按位与资源之间是一对一的对应关系。

位图中的每一位都将表示实际物理内存中的 4kb,也就是一页,即位图中的一位对应物理内存中的一页,如果某位为 0,表示该位对应的页未分配,可以使用,反之如果某位为 1 ,表示该位对应的页己经被分配出去了,在将该页回收之前不可再分配。

在这里插入图片描述

8.4.2位图的定义与实现
/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 10:16:53
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-09 17:25:49
 * @FilePath: /OS/chapter8/8.4/lib/kernel/bitmap.h
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#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);
#endif

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 10:31:56
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-06-10 17:42:57
 * @FilePath: /OS/chapter8/8.5.1/lib/kernel/bitmap.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#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);   
}

/* 判断bit_idx位是否为1,若为1则返回true,否则返回false */
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));
}

/* 在位图中申请连续cnt个位,成功则返回其起始位下标,失败返回-1 */
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)) {
/* 1表示该位已分配,所以若为0xff,则表示该字节内已无空闲位,向下一字节继续找 */
      idx_byte++;
   }

   ASSERT(idx_byte < btmp->btmp_bytes_len);
   if (idx_byte == btmp->btmp_bytes_len) {  // 若该内存池找不到可用空间		
      return -1;
   }

 /* 若在位图数组范围内的某字节内找到了空闲位,
  * 在该字节内逐位比对,返回空闲位的索引。*/
   int idx_bit = 0;
 /* 和btmp->bits[idx_byte]这个字节逐位对比 */
   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;	      // 先将其置为-1,若找不到连续的位就直接返回
   while (bit_left-- > 0) {
      if (!(bitmap_scan_test(btmp, next_bit))) {	 // 若next_bit为0
	      count++;
      } else {
	      count = 0;
      }
      if (count == cnt) {	    // 若找到连续的cnt个空位
         bit_idx_start = next_bit - cnt + 1;
         break;
      }
      next_bit++;          
   }
   return bit_idx_start;
}

/* 将位图btmp的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;    // 取余用于索引数组内的位

/* 一般都会用个0x1这样的数对字节中的位操作,
 * 将1任意移动后再取反,或者先取反再移位,可用来对位置0操作。*/
   if (value) {		      // 如果value为1
      btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);
   } else {		      // 若为0
      btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
   }
}


8.5内存管理系统

8.5.1内存池规划

由于在分页机制下有了虚拟地址和物理地址,为了有效地管理它们,我们需要创建虚拟内存地址池和物理内存地址池。

规划物理内存池:一种可行的方案是将物理内存划分成两部分,一部分只用来运行内核,另一部分只用来运行用户进程,将内存规划出不同的部分,专项专用。 我们把物理内存分成两个内存池, 一部分称为用户物理内存池,此内存池中的物理内存只用来分配给用户进程。另 一部分就是内核物理内存池,此内存池中的物理内存只给操作系统使用。

内存池中的内存也得按单位大小来获取,这个单位大小是4KB,称为页,故,内存池中管理的是一个个大小为 4KB的内存块,从内存池中获取的内存大小至少为 4KB或者为 4KB 的倍数

现在,我们就来进行内存管理的核心准备工作,初始化三个内存池:管理内核可用虚拟地址空间内存池管理内核可用物理地址空间内存池管理用户可用物理地址空间内存池用户可用虚拟地址空间内存池是要等到创建用户进程时才创立,现在不用初始化。

在这里插入图片描述

A、建立管理可用虚拟地址空间的数据结构虚拟内存池:virtual_addr,包含一个管理位图的数据结构、管理的可用虚拟地址空间的起始地址;建立管理可用物理地址空间的数据结构物理内存池:pool,包含一个管理位图的数据结构、管理的可用物理地址空间的起始地址、这个可用物理地址内存空间的大小;

B、通过A建立的数据结构,建立管理管理内核可用虚拟地址空间的内存池变量kernel_vaddr、管理内核可用物理地址空间的内存池变量kernel_pool、管理用户进程可用的物理地址空间内存池变量user_pool

C、根据作者设置与实际情况,初始化kernel_vaddr、user_pool、kernel_pool,就是初始化虚拟内存池内的位图数据结构、管理的地址空间起始地址,物理内存池内的位图数据结构、管理的地址空间起始地址、可用的物理地址空间大小

D、将C封装成一个函数mem_init(),并在init_all()中调用

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 14:17:52
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-09 16:45:14
 * @FilePath: /OS/chapter8/8.4/kernel/memory.h
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"

/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr
{
    /* data */
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;   //虚拟地址起始地址
};

extern struct pool kernel_pool, user_pool;
void mem_init(void);
#endif // !__KERNEL_MEMORY_H

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 14:26:27
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-09 17:33:33
 * @FilePath: /OS/chapter8/8.4/kernel/memory.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "memory.h"
#include "stdint.h"
#include "print.h"

#define PG_SIZE 4096

/***********************************位图地址************************************
 * 因为 Oxc009f000 是内核主线程栈顶, Oxc009e000 是内核主线程的pcb。
 * 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址。0xc009a000
 * 这样本系统最大支持 4 个页框的位图,即 512MB
********************************************************************************/
#define MEM_BITMAP_BASE 0xc009a000
/* 0xcOOOOOOO 是内核从虚拟地址 3G 起
 * 0xcOOOOOOO 是内核从虚拟地址 3G起 OxlOOOOO 意指跨过低端 lMB 内存,使虚拟地址在逻辑上连续
*/
#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 个页目录项指向同一个页表+第 769~ 1022 个页 目录项共指向 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 的倍数
    //对于以页为单位的内存分配策略,不足 1 页的内存不用考虑了

    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长度
    
    uint32_t kp_start = used_mem;   //kernel pool start 内核内存次的起始地址
    uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; //user pool start 内核内存次的起始地址

    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;

    /** 内核内存池和用户内存池位图
     * 位图是全局的数据,长度不固定。
     * 全局或静态的数组需要在编译时知道其长度,
     * 而我们需要根据总内存大小算出需要多少字节,
     * 所以改为指定一块内存来生成位图。
    */
   //内核使用的最高地址是 Oxc009f000,这是主线程的校地址
   //(内核的大小预计为 70KB 左右)
   //32MB内存占用的位图是2KB
   ///内核内存池的位图先定在 MEM_BITMAP_BASE(Oxc009a000 )处
   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);

   /*将位图置0*/
   bitmap_init(&kernel_pool.pool_bitmap);
   bitmap_init(&user_pool.pool_bitmap);

   /*下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/
   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_ini t 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");
}
8.5.2内存管理系统第一步,分配页内存

为减少学习阻力,先给大伙复习下 32 位虚拟地址的转换过程。

  1. 高 10 位是页目录项 pde 的索引,用于在页目录表中定位 pde,细节是处理器获取高 10 位后自动将其乘以 4,再加上页目录表的物理地址,这样便得到了 pde 索引对应的 pde 所在的物理地址,然后自动在该物理地址中,即该 pde 中,获取保存的页表物理地址(为了严谨,说的都有点拗口了)。
  2. 中间 10 位是页表项 pte 的 索引,用于在页表中定位 pte 。细节是处理器获取中间 10 位后自动将其乘以 4,再加上第一步中得到的页表的物理地址,这样便得到了 pte 索引对应的 pte 所在的物理地址,然后自动在该物理地址(该 pte )中获取保存的普通物理页的物理地址。
  3. 低 12 位是物理页内的偏移量,页大小是 4KB, 12 位可寻址的范围正好是 4阻,因此处理器便直接把低 12 位作为第二步中获取的物理页的偏移量,无需乘以 4c 用物理页的物理地址加上这低 12 位的和便是这 32 位虚拟地址最终落向的物理地址 。

注意啦,再提醒一次,页表的作用是将虚拟地址转换成物理地址,此工作表面虚幻,但内心真实,其转换过程中涉及访问的页目录表、页目录项及页表项,都是通过真实物理地址访问的,否则若用虚拟地址访问它们的话,会陷入转换的死循环中不可自拔。

1、代码功能

从内存池中分配地址,然后将分配到的物理地址与虚拟地址建立映射关系。

2、实现原理

物理内存池与虚拟内存池已经初始化完毕,我们自然就能够从这些内存池中申请到虚拟地址与物理地址。通过建立页表,完成虚拟地址到物理地址的映射。

3、代码逻辑

A、写函数完成申请地址空间,包括物理地址与虚拟地址

B、为二者建立映射关系

4、怎么写代码?

A、在memory.h中建立枚举类型结构体pool_flags用于选择从哪个虚拟内存池中分配内存,这样就可以实现用一个函数即能完成从用户虚拟地址空间分配地址,也能完成从内核虚拟地址空间分配地址;定义模块化的页表项字段宏,用于虚拟地址到物理地址的映射时的页表构建。

B、写函数vaddr_get,通过传入的poll_flags值完成对应的从对应的虚拟内存池中分配虚拟地址;写函数palloc,完成从传入的物理内存池中分配物理地址;

C、写宏PDE_IDX与PTE_IDX完成将从一个虚拟地址当中取出PDT与PTE的索引;写函数pde_ptr与pte_ptr将虚拟地址转换成访问虚拟地址对应的页目录表项的地址与页表表项的地址,这是为了当一个虚拟地址没有页表映射时,我们要动态建立映射,这就需要建立页目录表项与页表表项,自然得需要知道这两个的地址。

D、写出将申请得到的虚拟地址空间与物理地址空间,通过修改页表建立映射关系的函数page_table_add

1、页表存在,那么我们只需要将物理地址填入虚拟地址对应页表表项中即可

2、页表不存在(页目录表表项为空),我们需要先申请物理地址来存放页表,然后填入页目录表项这个页表的地址,然后初始化页表,最后将传入的物理地址填入虚拟地址对应页表表项中

E、写函数malloc_page根据传入的pool_flags的值决定是为内核空间还是用户空间分配连续的多个页面,包含从对应的虚拟内存池中分配虚拟地址(调用vaddr_get),从对应的物理内存池中分配物理地址(调用palloc),然后为虚拟地址与物理地址建立映射(调用page_talbe_add)。

F、写函数get_kernel_pages,快捷为内核申请地址空间(调用malloc_page)

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 14:17:52
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-04-10 10:47:04
 * @FilePath: /OS/chapter8/8.4/kernel/memory.h
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#ifndef __KERNEL_MEMORY_H
#define __KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"

/*内存池标记,用于判断是哪个内存池*/
enum pool_flags{
    PF_KERNEL = 1,
    PF_USER = 2,
};

#define PG_P_1 1 //页表项或页目录项存在属性位
#define PG_P_0 0 //页表项或页目录项存在属性位
#define PG_RW_R 0    //R/W 属性位值,读/执行
#define PG_RW_W 2    //R/W 属性位值,读/写/执行
#define PG_US_S 0    //U/S 属性位值,系统级
#define PG_US_U 4    //U/S 属性位值,用户级

 
/*虚拟地址池,用于虚拟地址管理*/
struct virtual_addr
{
    /* data */
    struct bitmap vaddr_bitmap; //虚拟地址用到的位图结构
    uint32_t vaddr_start;   //虚拟地址起始地址
};

extern struct pool kernel_pool, user_pool;
void mem_init(void);
#endif // !__KERNEL_MEMORY_H

/*
 * @Author: Adward-DYX 1654783946@qq.com
 * @Date: 2024-04-09 14:26:27
 * @LastEditors: Adward-DYX 1654783946@qq.com
 * @LastEditTime: 2024-06-10 18:59:08
 * @FilePath: /OS/chapter8/8.4/kernel/memory.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include "memory.h"
#include "bitmap.h"
#include "stdint.h"
#include "global.h"
#include "debug.h"
#include "string.h"
#include "print.h"

#define PG_SIZE 4096

/***********************************位图地址************************************
 * 因为 Oxc009f000 是内核主线程栈顶, Oxc009e000 是内核主线程的pcb。
 * 一个页框大小的位图可表示 128MB 内存,位图位置安排在地址。xc009a000
 * 这样本系统最大支持 4 个页框的位图,即 512MB
********************************************************************************/
#define MEM_BITMAP_BASE 0xc009a000
/* 0xcOOOOOOO 是内核从虚拟地址 3G 起
 * 0xcOOOOOOO 是内核从虚拟地址 3G起 OxlOOOOO 意指跨过低端 lMB 内存,使虚拟地址在逻辑上连续
*/
#define K_HEAP_START 0xc0100000

#define PDE_IDX(addr) ((addr&0xffc00000)>>22) //高10位
#define PTE_IDX(addr) ((addr&0x003ff000)>>12) //中10位

/*存池结构,生成两个实例用于管理内核内存池和用户内存池*/
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;    //此结构用来给内核分配虚拟地址


/*在 pf 表示的虚拟内存池中申请 pg_cnt 个虚拟页,成功则返回虚拟页的起始地址,失败则返回 NULL*/
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt){
    int vaddr_start = 0, bit_idx_start = -1;
    uint32_t cnt = 0;
    if(pf==PF_KERNEL){
        bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap,pg_cnt); //  返回的是成功位置的下标需要*页大小
        if(bit_idx_start == -1) return NULL;
        while(cnt < pg_cnt){
            bitmap_set(&kernel_vaddr.vaddr_bitmap,bit_idx_start+cnt++,1);
        }
        vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
    }else{
        //用户内存池,将来实现用户进程在补充
    }
    return (void*)vaddr_start;
}

/*得看书205页*/
/*得到虚拟地址 vaddr 对应的 pte 指针*/
uint32_t* pte_ptr(uint32_t vaddr){
    /*访问到页表自己 再用页目录项 pde (页目录内页袤的索引)作为pte的索引访问到页表 再用pte的索引作为页内偏移*/
    uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000)>>10) + PTE_IDX(vaddr) * 4); //这里得到的是新的虚拟地址 能够访问到它的页表物理地址
    return pte;
}

/*得到虚拟地址 vaddr 对应的 pde 的指针*/
uint32_t* pde_ptr (uint32_t vaddr){
    /* Oxfffff 用来访问到页表本身所在的地址 */
    uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr)*4);
    return pde;
}

/*在 m_pool 指向的物理内存池中分配 1 个物理页,成功则返回页框的物理地址,失败则返回 NULL */
static void* palloc(struct pool* m_pool){
    /*扫描或设置位图要保证原子操作*/
    int bit_idx = bitmap_scan(&m_pool->pool_bitmap,1);  //找到一个物理页
    if(bit_idx == -1)
        return NULL;
    bitmap_set(&m_pool->pool_bitmap,bit_idx,1); //将这个位置1,表示以用
    uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
    return (void*)page_phyaddr;
}

/*表中添加虚拟地址_vaddr 与物理地址_page_phyaddr 的映射*/
static void page_table_add(void* _vaddr, void* _page_phyaddr){
    uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;
    uint32_t* pde = pde_ptr(_vaddr);
    uint32_t* pte = pte_ptr(_vaddr);

    /** 注意
     * 执行*pte ,会访问到空的pde。所以确保pde创建完成后才能执行*pte,
     * 否则会引发 page_fault。因此在*pde 为 0 时,
     * pte 只能出现在下面 else 语句块中的* pde 后面。
    */
   /*先在页目录内判断 目录项的 p 位,若为 1 ,则表示该表已存在*/
   if(*pde & 0x00000001){
    //页目录项和页表项的第 0 位为 p ,此处判断目录项是否存在
    ASSERT(!(*pte & 0x00000001));
    if(!(*pte & 0x00000001)){
        //只要是创建页表, pte 就应该不存在,多判断一下放心
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }else{  ///目前应该不会执行到这,因为上面的 ASSERT 会先执行
        PANIC("pte repeat");
        *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
    }
   }else{//页吕录项不存在,所以要先创建页目录再创建页表项
    //页表中用到的页框一律从内核空间分配
    uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
    *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);

    /** 分配到的物理页地址 pde_phyaddr 对应的物理内存清 O,
     * 避免里面的陈旧数据变成了页表项,从而让页表混乱。
     * 访问到 pde 对应的物理地址,用 pte 取高 20 位便可。
     * 因为 pte 基于该 pde 对应的物理地址内再寻址,
     * 把低 12 位置0。便是该 pde 对应的物理页的起始*
    */
    memset((void*)((int)pte & 0xfffff000),0,PG_SIZE); //这里要的地址必须是pte的虚拟地址
    ASSERT(!(*pte & 0x00000001));
    *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
   }
}

/*分配 pg_cnt 个页空间,成功则返回起始虚拟地址,失败时返回 NULL*/
void* malloc_page(enum pool_flags pf, uint32_t pg_cnt){
    ASSERT(pg_cnt > 0 && pg_cnt < 3840);

    /** malloc__page 的原理是三个动作的合成:
     * 1 通过 vaddr_get 在虚拟内存池中申请虚拟地址
     * 2 通过 palloc 在物理内存池中申请物理页
     * 3 通过 page_table_add 将以上得到的虚拟地址和物理地址在页表中完成映射
    */

    void* vaddr_start = vaddr_get(pf,pg_cnt);
    if(vaddr_start==NULL)   return NULL;

    uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;
    struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

    /*因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射*/
    while(cnt-->0){
        void* page_phyaddr = palloc(mem_pool);
        if(page_phyaddr == NULL)    ///失败时要将曾经已申请的虚拟地址和物理页全部回滚,在将来完成内存回收时再补充
            return NULL;
        page_table_add((void*)vaddr,page_phyaddr);  //在页表中映射
        vaddr+=PG_SIZE;
    }
    return vaddr_start;
}

/*从内核物理内存池中申请 1 页内存,成功则返回其虚拟地址,失败则返回 NULL */
void* get_kernel_pages(uint32_t pg_cnt){
    void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
    if(vaddr != NULL)   //若分配的地址不为空,将页框清0后返回
        memset(vaddr,0,pg_cnt*PG_SIZE);
    return 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 个页目录项指向同一个页表+第 769~ 1022 个页 目录项共指向 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 的倍数
    //对于以页为单位的内存分配策略,不足 1 页的内存不用考虑了

    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长度
    
    uint32_t kp_start = used_mem;   //kernel pool start 内核内存次的起始地址
    uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; //user pool start 内核内存次的起始地址

    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;

    /** 内核内存池和用户内存池位图
     * 位图是全局的数据,长度不固定。
     * 全局或静态的数组需要在编译时知道其长度,
     * 而我们需要根据总内存大小算出需要多少字节,
     * 所以改为指定一块内存来生成位图。
    */
   //内核使用的最高地址是 Oxc009f000,这是主线程的校地址
   //(内核的大小预计为 70KB 左右)
   //32MB内存占用的位图是2KB
   ///内核内存池的位图先定在 MEM_BITMAP_BASE(Oxc009a000 )处
   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);

   /*将位图置0*/
   bitmap_init(&kernel_pool.pool_bitmap);
   bitmap_init(&user_pool.pool_bitmap);

   /*下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/
   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_ini t 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");
}

所以,申请内存这个行为反映到代码上的实质是,将一个4k空间起始的物理地址,填入4K虚拟空间起始地址对应的页表表项中

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

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

相关文章

网络分析(ArcPy)

一.前言 GIS中的网络分析最重要的便是纠正拓扑关系&#xff0c;建立矫正好的网络数据集&#xff0c;再进行网络分析&#xff0c;一般大家都是鼠标在arcgis上点点点&#xff0c;今天说一下Arcpy来解决的方案&#xff0c;对python的要求并不高,具体api参数查询arcgis帮助文档即可…

Java_Map集合

认识Map集合 Map集合称为双列集合&#xff0c;格式&#xff1a;{key1value&#xff0c;key2value2,key3value3,…},一次需要存一对数据作为一个元素。 Map集合的每个元素“Keyvalue” 称为一个键值对/键值对对象/一个Entry对象&#xff0c;Map集合也被叫做“键值对集合” Map集…

Simscape Multibody与RigidBodyTree:机器人建模

RigidBodyTree&#xff1a;主要用于表示机器人刚体结构的动力学模型&#xff0c;重点关注机器人的几何结构、质量和力矩&#xff0c;以及它们如何随时间变化。它通常用于计算机器人的运动和受力情况。Simscape Multibody&#xff1a;作为Simscape的一个子模块&#xff0c;专门用…

10.2 Go Channel

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

虚拟机调用摄像头设备一直 select timeout问题的解决

在VMware里面调用v4l2-ctl捕获图像&#xff0c;或者opencv的VideoCapture(0)捕获图像&#xff0c;或者直接调用v4l2的函数&#xff0c;在streamon后&#xff0c;调用select读取数据&#xff0c;均会一直提示select timeout的问题&#xff0c;大概率是由于USB版本的兼容性造成的…

【氵】Archlinux+KDE Plasma 6+Wayland 安装nvidia驱动 / 开启HDR

参考: NVIDIA - Arch Linux 中文维基 &#xff08;其实就是把 wiki 简化了一下 注&#xff1a;本教程适用 GeForce 930 起、10 系至 20 系、 Quadro / Tesla / Tegra K-系列以及更新的显卡&#xff08;NV110 以及更新的显卡家族&#xff09;&#xff0c;此处以 RTX3060 为例 …

PHP 寿光蔬菜大棚宣传平台-计算机毕业设计源码88288

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于寿光蔬菜大棚宣传平台当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了寿光蔬菜大棚宣传平台&#xff0c;它彻底…

连续状态方程的离散化例子

连续状态方程的离散化 在控制系统中,连续状态方程的离散化是一个重要的步骤,用于将连续时间系统转换为离散时间系统,以便在数字控制器中实现。这通常涉及将连续时间的微分方程转换为离散时间的差分方程。常用的离散化方法 前向欧拉法(Forward Euler)简单易实现,但精度较…

详解python中的pandas.read_csv()函数

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

OpenGL绘制简单图形

绘制了一个紫色矩形和一个三角形&#xff0c;代码如下&#xff1a; #include <Windows.h> #include <gl/glut.h> void display(void) {glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //设置清屏颜色glClear(GL_COLOR_BUFFER_BIT); //刷新颜色缓冲区&#xff1b;glColor3f…

“程序员职业素养全解析:技能、态度与价值观的融合“

文章目录 每日一句正能量前言专业精神专业精神的重要性技术执着追求的故事结论 沟通能力沟通能力的重要性团队合作意识实际工作中的沟通案例结论 持续学习持续学习的重要性学习方法进步经验结论 后记 每日一句正能量 梦不是为想象&#xff0c;而是让我们继续前往。 前言 在数字…

Policy-Based Reinforcement Learning(1)

之前提到过Discount Return&#xff1a; Action-value Function &#xff1a; State-value Function: &#xff08;这里将action A积分掉&#xff09;这里如果策略函数很好&#xff0c;就会很大&#xff1b;反之策略函数不好&#xff0c;就会很小。 对于离散类型&#xff1a; …

Qt中解决编译中文乱码和编译失败的问题

解决方法 1.使用#pragma execution_character_set(“utf-8”) QT5中在cpp中使用#pragma execution_character_set(“utf-8”)解决中文乱码&#xff0c;不过这里要求该源代码必须保存成带Bom的utf-8格式&#xff0c;这也是有些在网上下载的代码&#xff0c;加上这句源代码后还…

QPS,平均时延和并发数

我们当前有两个服务A和B&#xff0c;想要知道哪个服务的性能更好&#xff0c;该用什么指标来衡量呢&#xff1f; 1. 单次请求时延 一种最简单的方法就是使用同一请求体同时请求两个服务&#xff0c;性能越好的服务时延越短&#xff0c;即 R T 返回结果的时刻 − 发送请求的…

对猫毛过敏?怎么有效的缓解过敏症状,宠物空气净化器有用吗?

猫过敏是一种常见的过敏反应&#xff0c;由猫的皮屑、唾液或尿液中的蛋白质引起。这些蛋白质被称为过敏原&#xff0c;它们可以通过空气传播&#xff0c;被人体吸入后&#xff0c;会触发免疫系统的过度反应。猫过敏是宠物过敏中最常见的类型之一&#xff0c;对许多人来说&#…

C语言学习系列:初识C语言

前言&#xff0c;C语言是什么 语言&#xff0c;比如中文、英语、法语、德语等&#xff0c;是人与人交流的工具。 C语言也是语言&#xff0c;不过是一种特殊的语言&#xff0c;是人与计算机交流的工具。 为什么叫C语言呢&#xff1f; 这就要从C语言的历史说起了。 一&#…

11. MySQL 备份、恢复

文章目录 【 1. MySQL 备份类型 】【 2. 备份数据库 mysqldump 】2.1 备份单个数据表2.2 备份多个数据库2.3 备份所有数据库2.4 备份文件解析 【 3. 恢复数据库 mysql 】【 4. 导出表数据 OUTFILE 】【 5. 恢复表数据 INFILE 】 问题背景 尽管采取了一些管理措施来保证数据库的…

14. RTCP 协议

RTCP 协议概述 RTCP&#xff08;Real-time Transport Control Protocol 或 RTP Control Protocol 或简写 RTCP&#xff09;&#xff0c;实时传输控制协议&#xff0c;是实时传输协议&#xff08;RTP&#xff09;的一个姐妹协议。 注&#xff1a;RTP 协议和 RTP 控制协议&#…

Oracle数据库连接并访问Microsoft SQL Server数据库

Oracle数据库连接并访问Microsoft SQL Server数据库 说明&#xff1a;  1.实际开发中&#xff0c;Oracle数据库与SQLServer数据库之间可能需要相互进行访问&#xff0c;方便业务数据抽取&#xff0c;编写视图及表等操作。  2.SQLServer访问Oracle数据库配置相对较为简单&…

Python数据分析与机器学习在电子商务推荐系统中的应用

文章目录 &#x1f4d1;引言一、推荐系统的类型二、数据收集与预处理2.1 数据收集2.2 数据预处理 三、基于内容的推荐3.1 特征提取3.2 计算相似度3.3 推荐物品 四、协同过滤推荐4.1 基于用户的协同过滤4.2 基于物品的协同过滤 五、混合推荐与评估推荐系统5.1 结合推荐结果5.2 评…