linux的内存管理

Linux的内存管理是操作系统中至关重要的组成部分之一。它负责管理系统中的物理内存,以便进程可以访问和使用它。以下是Linux内存管理的一些关键方面:

  1. 虚拟内存管理

    • Linux使用虚拟内存系统,将物理内存进程地址空间分开管理
    • 每个进程都有自己的虚拟地址空间,它包含了进程可用的全部内存地址范围
    • 虚拟内存允许多个进程同时运行,每个进程都认为它具有连续的、私有的内存地址空间。
    • Linux的虚拟内存管理是基于分页的系统,它将物理内存和进程地址空间分离,为每个进程提供了一个独立的虚拟地址空间。以下是Linux虚拟内存管理的主要方面:

    • 地址空间分配

      • 每个进程都有自己的虚拟地址空间,这个空间通常被划分为几个段,包括代码段、数据段、堆段和栈段等。
      • 用户空间的地址范围通常是从0开始到进程地址空间的最大限制(通常是2^32或2^64),而内核空间则是高地址的一部分。
    • #include <stdio.h>
      #include <stdlib.h>
      
      int global_var = 10;
      
      int main() {
          int local_var = 20;
          int *heap_var = (int *)malloc(sizeof(int));
          *heap_var = 30;
          
          printf("Address of global_var: %p\n", (void*)&global_var);
          printf("Address of local_var: %p\n", (void*)&local_var);
          printf("Address of heap_var: %p\n", (void*)heap_var);
      
          free(heap_var);
      
          return 0;
      }
      

      在这个程序中,我们定义了一个全局变量 global_var,一个局部变量 local_var,以及一个动态分配的堆变量 heap_var

      当我们运行这个程序时,Linux会为该进程分配地址空间。这个地址空间通常包括以下部分:                                                                                                                           代码段:存放程序的机器指令,通常是只读的。                                                           数据段:包括全局变量和静态变量。                                                                              堆段:用于动态分配内存,例如通过 malloc 分配的内存。                                          栈段:用于存放局部变量和函数调用的信息。                                                                 在运行上述程序时,Linux将会为该进程分配地址空间,并在其中安排这些变量的存储位置。每个变量都会被分配一个地址,该地址位于进程的地址空间中的不同部分。

    • 程序执行后,会打印出各个变量的地址。这些地址在虚拟地址空间中,实际的物理地址会在需要时由操作系统进行映射

      需要注意的是,虽然程序中的变量都有自己的地址,但这些地址是虚拟的,并不直接对应于物理内存中的位置。物理内存的分配和映射由操作系统的内存管理模块负责

    • 分页机制

      • Linux使用分页机制将虚拟地址空间划分成固定大小的页面(通常为4KB)。物理内存也被划分成相同大小的页面
      • 当进程访问虚拟内存中的某个地址时,Linux内核将虚拟地址转换为对应的物理地址。
      • 假设我们有一个多进程的程序,每个进程都会创建一个大型数组,并且多个进程会共享这些数组。这种情况下,Linux 的分页机制可以帮助节省内存,并允许多个进程在不同的虚拟地址空间中访问相同的物理内存。

        以下是一个简化的示例代码,演示了两个进程如何共享一个大型数组:

      • #include <stdio.h>
        #include <stdlib.h>
        #include <sys/mman.h>
        #include <fcntl.h>
        #include <unistd.h>
        
        #define ARRAY_SIZE 1000000  // 1 million integers
        
        int main() {
            // 创建一个共享内存对象
            int fd = shm_open("/shared_array", O_CREAT | O_RDWR, 0666);
            if (fd == -1) {
                perror("shm_open");
                exit(1);
            }
        
            // 调整共享内存对象的大小
            if (ftruncate(fd, ARRAY_SIZE * sizeof(int)) == -1) {
                perror("ftruncate");
                exit(1);
            }
        
            // 映射共享内存到进程的地址空间
            int *array = mmap(NULL, ARRAY_SIZE * sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
            if (array == MAP_FAILED) {
                perror("mmap");
                exit(1);
            }
        
            // 在共享数组中写入数据
            for (int i = 0; i < ARRAY_SIZE; i++) {
                array[i] = i * 2;
            }
        
            // 等待用户输入,然后解除映射并关闭共享内存对象
            printf("Press enter to unmap and close shared memory...\n");
            getchar();
        
            if (munmap(array, ARRAY_SIZE * sizeof(int)) == -1) {
                perror("munmap");
                exit(1);
            }
        
            if (close(fd) == -1) {
                perror("close");
                exit(1);
            }
        
            // 删除共享内存对象
            if (shm_unlink("/shared_array") == -1) {
                perror("shm_unlink");
                exit(1);
            }
        
            return 0;
        }
        
        #include <stdio.h>
        #include <stdlib.h>
        #include <sys/mman.h>
        #include <fcntl.h>
        #include <unistd.h>
        
        #define ARRAY_SIZE 1000000  // 1 million integers
        
        int main() {
            // 打开共享内存对象
            int fd = shm_open("/shared_array", O_RDWR, 0666);
            if (fd == -1) {
                perror("shm_open");
                exit(1);
            }
        
            // 映射共享内存到进程的地址空间
            int *array = mmap(NULL, ARRAY_SIZE * sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
            if (array == MAP_FAILED) {
                perror("mmap");
                exit(1);
            }
        
            // 从共享数组中读取数据并打印
            printf("Shared array elements:\n");
            for (int i = 0; i < ARRAY_SIZE; i++) {
                printf("%d ", array[i]);
            }
            printf("\n");
        
            // 解除映射并关闭共享内存对象
            if (munmap(array, ARRAY_SIZE * sizeof(int)) == -1) {
                perror("munmap");
                exit(1);
            }
        
            if (close(fd) == -1) {
                perror("close");
                exit(1);
            }
        
            return 0;
        }
        

        在这个示例中,两个进程通过共享内存来共享一个大型数组。它们分别打开了同一个共享内存对象 /shared_array,并将其映射到各自的地址空间中。由于使用了 MAP_SHARED 标志,它们可以在不同的虚拟地址空间中访问相同的物理内存

        这个示例演示了如何利用 Linux 分页机制来实现进程间的内存共享。通过共享内存,这两个进程可以高效地共享大量数据,而无需进行显式的进程间通信。

    • 页表:

      • Linux使用页表来管理虚拟地址到物理地址的映射关系。每个进程都有自己的页表。
      • 页表将虚拟地址划分为页面,并存储了每个页面对应的物理地址。
      • 我们来看一个稍微复杂一些的 Linux 分页机制的案例,涉及到多级页表。在多级页表中,物理内存被划分为多个页框,而虚拟内存则被划分为多个页表项。通过多级页表,操作系统可以管理大量的物理内存,并将其映射到不同的虚拟地址空间中。

        以下是一个简化的示例,演示了 Linux 下多级页表的工作原理:

      • #include <stdio.h>
        #include <stdlib.h>
        #include <stdint.h>
        
        // 定义页表的大小和页框的大小
        #define PAGE_SIZE 4096 // 4 KB
        #define NUM_PAGES 1024
        #define NUM_FRAMES 256
        
        // 定义页表项和页框结构
        typedef struct {
            uint32_t valid : 1;
            uint32_t frame_num : 8;
        } PageTableEntry;
        
        typedef struct {
            uint8_t data[PAGE_SIZE];
        } Frame;
        
        // 定义全局的页表和页框数组
        PageTableEntry page_table[NUM_PAGES];
        Frame physical_memory[NUM_FRAMES];
        
        // 初始化页表和页框
        void init() {
            // 初始化页表项
            for (int i = 0; i < NUM_PAGES; i++) {
                page_table[i].valid = 0;
                page_table[i].frame_num = 0;
            }
        
            // 初始化页框
            for (int i = 0; i < NUM_FRAMES; i++) {
                // 假设每个页框存储的数据是其索引值
                for (int j = 0; j < PAGE_SIZE; j++) {
                    physical_memory[i].data[j] = i;
                }
            }
        }
        
        // 虚拟地址到物理地址的转换
        uint32_t translate_address(uint32_t virtual_address) {
            uint32_t page_num = virtual_address / PAGE_SIZE;
            uint32_t offset = virtual_address % PAGE_SIZE;
        
            if (page_table[page_num].valid) {
                uint32_t frame_num = page_table[page_num].frame_num;
                uint32_t physical_address = (frame_num * PAGE_SIZE) + offset;
                return physical_address;
            } else {
                printf("Page fault: Page %d is not in memory.\n", page_num);
                return 0; // 返回无效的物理地址
            }
        }
        
        int main() {
            // 初始化页表和页框
            init();
        
            // 虚拟地址转换为物理地址的示例
            uint32_t virtual_address = 8192; // 假设虚拟地址为 8192
            uint32_t physical_address = translate_address(virtual_address);
        
            if (physical_address != 0) {
                printf("Virtual Address %u maps to Physical Address %u.\n", virtual_address, physical_address);
            }
        
            return 0;
        }
        

        在这个示例中,我们定义了一个简单的多级页表结构,包括一个页表数组 page_table 和一个页框数组 physical_memory。通过 init() 函数初始化了这些数据结构。

        然后,我们实现了一个简单的地址转换函数 translate_address(),它将给定的虚拟地址转换为物理地址。如果所需的页不在内存中,函数会打印出页错误信息。

        main() 函数中,我们示范了如何使用 translate_address() 函数将虚拟地址转换为物理地址。

        这个示例演示了 Linux 下多级页表的基本工作原理,包括地址转换和页错误处理。多级页表允许操作系统有效地管理大量的物理内存,并将其映射到不同的虚拟地址空间中。

    • 页面置换

      • 当物理内存不足时,Linux使用页面置换算法来选择需要被替换出去的页面。
      • 常用的页面置换算法包括最近最少使用(LRU)算法、最不经常使用(LFU)算法等。
      • 我们可以看一个稍微复杂一点的页面置换算法的案例,比如最常见的页面置换算法之一:LRU(Least Recently Used,最近最少使用)算法。LRU 算法的核心思想是淘汰最长时间未被使用的页面。
      • #include <stdio.h>
        #include <stdlib.h>
        #include <stdbool.h>
        
        // 定义页面和页框的数量
        #define NUM_PAGES 10
        #define NUM_FRAMES 4
        
        // 定义页表项结构
        typedef struct {
            int page_num;
            bool valid;
        } PageTableEntry;
        
        // 定义页面结构
        typedef struct {
            int page_num;
            int last_used; // 记录页面最近一次被使用的时间
        } Page;
        
        // 定义全局的页表和页框数组
        PageTableEntry page_table[NUM_PAGES];
        Page frames[NUM_FRAMES];
        
        // 初始化页表和页框
        void init() {
            // 初始化页表项
            for (int i = 0; i < NUM_PAGES; i++) {
                page_table[i].page_num = i;
                page_table[i].valid = false;
            }
        
            // 初始化页框
            for (int i = 0; i < NUM_FRAMES; i++) {
                frames[i].page_num = -1;
                frames[i].last_used = 0;
            }
        }
        
        // LRU 页面置换算法
        void lru_page_replace(int page_num) {
            // 找到最近最少使用的页框
            int lru_frame = 0;
            for (int i = 1; i < NUM_FRAMES; i++) {
                if (frames[i].last_used < frames[lru_frame].last_used) {
                    lru_frame = i;
                }
            }
        
            // 替换最近最少使用的页框
            frames[lru_frame].page_num = page_num;
            frames[lru_frame].last_used = 0;
        }
        
        // 处理页面访问
        void access_page(int page_num) {
            // 更新页表项的有效标志位
            page_table[page_num].valid = true;
        
            // 检查页面是否已经在内存中
            bool page_in_memory = false;
            for (int i = 0; i < NUM_FRAMES; i++) {
                if (frames[i].page_num == page_num) {
                    page_in_memory = true;
                    break;
                }
            }
        
            // 如果页面不在内存中,则进行页面置换
            if (!page_in_memory) {
                lru_page_replace(page_num);
            }
        }
        
        int main() {
            // 初始化页表和页框
            init();
        
            // 模拟页面访问序列
            int page_access_sequence[] = {0, 1, 2, 3, 4, 5, 0, 1, 2, 6, 7, 8, 9, 3, 4, 5};
        
            // 处理页面访问
            for (int i = 0; i < sizeof(page_access_sequence) / sizeof(page_access_sequence[0]); i++) {
                access_page(page_access_sequence[i]);
                
                // 更新页框的最近使用时间
                for (int j = 0; j < NUM_FRAMES; j++) {
                    if (frames[j].page_num == page_access_sequence[i]) {
                        frames[j].last_used = i;
                    }
                }
            }
        
            // 输出页框中的页面
            printf("Frames in memory after page accesses:\n");
            for (int i = 0; i < NUM_FRAMES; i++) {
                printf("Frame %d: Page %d\n", i, frames[i].page_num);
            }
        
            return 0;
        }
        

        在这个示例中,我们定义了一个简单的页表结构 page_table 和一个页面结构 frames,用于模拟操作系统中的页表和物理内存。

        我们使用了 LRULRU 页面置换算法来实现 lru_page_replace() 函数,该函数在需要进行页面置换时找到最近最少使用的页框,并将其替换为新页面。

        main() 函数中,我们模拟了一系列页面访问,并通过 access_page() 函数来处理每次页面访问。同时,我们在模拟过程中更新了每个页框的最近使用时间,以便在进行页面置换时找到最近最少使用的页框。

        最后,我们输出了模拟结束后页框中的页面,以检查页面置换的结果。

    • 内存映射

      • Linux允许文件和设备映射到进程的虚拟地址空间,使得它们可以像内存一样访问。
      • 内存映射可以提高IO性能,允许对文件进行直接操作而无需通过复制数据到用户空间。
      • 假设我们有一个需求:我们有两个进程,一个是生产者进程,负责生成数据并写入文件,另一个是消费者进程,负责读取文件中的数据并进行处理。为了提高效率,我们希望这两个进程之间能够共享内存,而不是通过磁盘文件进行数据交换。我们可以通过内存映射来实现这个需求。

        以下是一个简化的示例,演示了如何在 Linux 中使用内存映射和共享内存来实现这个需求:

      • #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <sys/mman.h>
        #include <fcntl.h>
        #include <sys/wait.h>
        #include <string.h>
        
        // 定义共享内存大小
        #define SHARED_MEMORY_SIZE 4096
        
        int main() {
            // 创建共享内存对象
            int fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
            if (fd == -1) {
                perror("shm_open");
                exit(EXIT_FAILURE);
            }
        
            // 调整共享内存大小
            if (ftruncate(fd, SHARED_MEMORY_SIZE) == -1) {
                perror("ftruncate");
                exit(EXIT_FAILURE);
            }
        
            // 将共享内存映射到进程的地址空间
            void *ptr = mmap(NULL, SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
            if (ptr == MAP_FAILED) {
                perror("mmap");
                exit(EXIT_FAILURE);
            }
        
            // 创建生产者进程
            pid_t producer_pid = fork();
            if (producer_pid == -1) {
                perror("fork");
                exit(EXIT_FAILURE);
            } else if (producer_pid == 0) {
                // 在子进程中写入数据到共享内存
                sprintf((char*)ptr, "Hello from producer!");
                exit(EXIT_SUCCESS);
            }
        
            // 创建消费者进程
            pid_t consumer_pid = fork();
            if (consumer_pid == -1) {
                perror("fork");
                exit(EXIT_FAILURE);
            } else if (consumer_pid == 0) {
                // 在子进程中读取共享内存中的数据并进行处理
                printf("Consumer received: %s\n", (char*)ptr);
                exit(EXIT_SUCCESS);
            }
        
            // 等待子进程结束
            wait(NULL);
            wait(NULL);
        
            // 关闭共享内存文件描述符
            close(fd);
        
            // 删除共享内存对象
            if (shm_unlink("/my_shared_memory") == -1) {
                perror("shm_unlink");
                exit(EXIT_FAILURE);
            }
        
            return 0;
        }
        

        在这个示例中,我们首先使用 shm_open() 函数创建了一个共享内存对象,并指定了一个名字 "/my_shared_memory"。然后,我们使用 ftruncate() 函数调整共享内存的大小,确保其足够存储我们要传输的数据。

        接下来,我们使用 mmap() 函数将共享内存映射到当前进程的地址空间中,并获得了指向共享内存的指针 ptr

        然后,我们创建了两个子进程,一个是生产者进程,另一个是消费者进程。在生产者进程中,我们使用 sprintf() 将数据写入到共享内存中;在消费者进程中,我们从共享内存中读取数据并进行处理。

        最后,我们等待两个子进程结束,关闭共享内存文件描述符,并使用 shm_unlink() 函数删除共享内存对象。

        这个示例演示了如何在 Linux 中使用内存映射和共享内存来实现进程间的高效通信。

    • 内存保护

      • Linux使用页表来实现内存保护机制,通过设置页面权限来保护内存的访问。
      • 例如,可以将某些页面设置为只读或禁止访问,以防止未经授权的访问。
      • Linux 中的内存保护机制包括内存分页、内存权限和地址空间隔离等,这些机制共同确保了系统的安全性和稳定性。以下是一个简单的案例,演示了如何使用内存保护来防止程序访问非法内存区域:
      • #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <signal.h>
        #include <sys/mman.h>
        
        // 定义全局变量
        int *ptr;
        
        // 信号处理函数,用于处理访问非法内存时的信号
        void segfault_handler(int signum) {
            printf("Segmentation fault occurred. Invalid memory access attempted.\n");
            exit(EXIT_FAILURE);
        }
        
        int main() {
            // 注册信号处理函数,处理访问非法内存时的信号
            signal(SIGSEGV, segfault_handler);
        
            // 分配一块内存,并标记为只读
            ptr = (int*)mmap(NULL, sizeof(int), PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            if (ptr == MAP_FAILED) {
                perror("mmap");
                exit(EXIT_FAILURE);
            }
        
            // 尝试向只读内存写入数据
            *ptr = 42;
        
            // 打印写入的数据
            printf("Value at ptr: %d\n", *ptr);
        
            // 释放内存映射
            if (munmap(ptr, sizeof(int)) == -1) {
                perror("munmap");
                exit(EXIT_FAILURE);
            }
        
            return 0;
        }
        

        在这个案例中,我们使用了 mmap() 函数分配了一块内存,并且将其标记为只读 (PROT_READ)。然后,我们尝试向这块只读内存写入数据 *ptr = 42;

        由于我们将内存标记为只读,当程序尝试写入数据时,操作系统会检测到非法内存访问,从而触发 SIGSEGV 信号。我们通过注册信号处理函数 segfault_handler 来处理这个信号,输出错误信息并退出程序。

        这个案例演示了如何使用内存保护机制来防止程序访问非法内存区域,提高了系统的稳定性和安全性。

  2. 页面分配和交换

    • Linux通过页面(通常大小为4KB)来管理内存。物理内存被分成一系列页面,并按需分配给进程使用。
    • 当物理内存不足时,Linux可以将不常用的页面移出到交换空间(通常是硬盘),以释放物理内存供其他进程使用。
  3. 内存分配器

    • Linux内核包含了多种内存分配器,用于分配不同用途的内存块。其中包括slab分配器、buddy分配器等。
    • slab分配器用于管理小块内存的高效分配,例如用于管理内核对象的内存。
    • buddy分配器用于分配较大的内存块,它将内存划分为连续的、大小为2的幂次方的块。
    • #include <linux/mm.h>
      #include <linux/slab.h>
      #include <linux/module.h>
      #include <linux/init.h>
      
      #define BLOCK_SIZE 4096
      #define NUM_BLOCKS 10
      
      static void *mem_pool[NUM_BLOCKS];
      
      static int __init buddy_allocator_init(void) {
          int i;
      
          // 初始化内存池
          for (i = 0; i < NUM_BLOCKS; i++) {
              mem_pool[i] = kmalloc(BLOCK_SIZE, GFP_KERNEL);
              if (!mem_pool[i]) {
                  // 内存分配失败,释放之前已经分配的内存并返回错误
                  for (i--; i >= 0; i--) {
                      kfree(mem_pool[i]);
                  }
                  printk(KERN_ERR "内存池初始化失败\n");
                  return -ENOMEM;
              }
              printk(KERN_INFO "分配内存块 %d, 地址: %p\n", i, mem_pool[i]);
          }
      
          return 0;
      }
      
      static void __exit buddy_allocator_exit(void) {
          int i;
      
          // 释放内存池中的所有内存块
          for (i = 0; i < NUM_BLOCKS; i++) {
              kfree(mem_pool[i]);
              printk(KERN_INFO "释放内存块 %d\n", i);
          }
      }
      
      module_init(buddy_allocator_init);
      module_exit(buddy_allocator_exit);
      
      MODULE_LICENSE("GPL");
      MODULE_AUTHOR("Your Name");
      MODULE_DESCRIPTION("Buddy Allocator Example");
      

      在这个示例中,我们定义了一个内存池 mem_pool,用于存储固定大小的内存块。在模块初始化函数 buddy_allocator_init() 中,我们使用 kmalloc() 函数从内核的 Buddy Allocator 中分配内存块,并将它们存储在内存池中。如果分配失败,我们会释放之前已经分配的内存,并返回错误。在模块退出函数 buddy_allocator_exit() 中,我们释放内存池中的所有内存块。

      请注意,在实际的 Linux 内核编程中,需要包含更多的错误检查和处理逻辑,以确保内存分配和释放的正确性和健壮性。此外,还需要考虑到内存的对齐、内存池的管理、内存分配的性能等方面的问题。

  4. 内存映射

    • Linux允许文件和设备映射到进程的虚拟地址空间,使得它们可以像内存一样访问。
    • 这种内存映射方式可以提高IO性能,允许对文件进行直接操作而无需通过复制数据到用户空间。
    • 如何在 Linux 中使用内存映射来创建一个简单的内存共享机制。在这个示例中,我们将创建一个父子进程,父进程将一个字符串写入到内存映射区域,子进程则从该内存映射区域读取并打印该字符串。
    • #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/mman.h>
      #include <sys/wait.h>
      #include <string.h>
      
      #define SHARED_MEM_SIZE 1024
      
      int main() {
          int fd[2];
          char *shared_memory;
      
          // 创建匿名内存映射区域
          shared_memory = mmap(NULL, SHARED_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
          if (shared_memory == MAP_FAILED) {
              perror("内存映射失败");
              exit(EXIT_FAILURE);
          }
      
          // 创建管道
          if (pipe(fd) == -1) {
              perror("管道创建失败");
              exit(EXIT_FAILURE);
          }
      
          // 创建子进程
          pid_t pid = fork();
          if (pid == -1) {
              perror("子进程创建失败");
              exit(EXIT_FAILURE);
          }
      
          if (pid == 0) {
              // 子进程
              close(fd[1]); // 关闭写端
      
              // 从父进程读取数据
              read(fd[0], shared_memory, SHARED_MEM_SIZE);
              printf("子进程读取的数据: %s\n", shared_memory);
      
              close(fd[0]);
          } else {
              // 父进程
              close(fd[0]); // 关闭读端
      
              // 将数据写入到共享内存中
              char *data = "Hello, shared memory!";
              strncpy(shared_memory, data, strlen(data));
      
              // 向子进程发送通知
              write(fd[1], "1", 1);
      
              close(fd[1]);
      
              // 等待子进程结束
              wait(NULL);
      
              // 解除内存映射
              if (munmap(shared_memory, SHARED_MEM_SIZE) == -1) {
                  perror("解除内存映射失败");
                  exit(EXIT_FAILURE);
              }
          }
      
          return 0;
      }
      

      在这个示例中,我们首先使用 mmap() 函数创建了一个共享的匿名内存映射区域,大小为 SHARED_MEM_SIZE 字节。然后,我们创建了一个管道 fd,用于父子进程间的通信。接下来,我们使用 fork() 函数创建了一个子进程,父进程和子进程分别负责写入和读取共享内存中的数据。

      父进程将字符串 "Hello, shared memory!" 写入到共享内存中,然后通过管道通知子进程开始读取数据。子进程从共享内存中读取数据,并将其打印出来。

      在示例的最后,父进程等待子进程结束,然后关闭管道、解除内存映射,并退出。

      请注意,在实际的程序中,需要进行更多的错误检查和处理,以确保内存映射和进程间通信的正确性和健壮性。

  5. 内存管理策略

    • Linux内核采用多种策略来管理内存,包括LRU(最近最少使用)页面替换算法、页面回收策略等。
    • 内核会动态调整页面回收的策略,以优化系统的整体性能和资源利用率。
    • Linux 中使用 C 语言编写一个简单的内存管理模拟器。该模拟器将模拟内存分配、释放和碎片整理等操作,以便更好地理解内存管理策略的工作原理。
    • #include <stdio.h>
      #include <stdlib.h>
      
      #define MEMORY_SIZE 1024
      #define ALLOC_SIZE 128
      
      typedef struct {
          int is_free;
          int size;
      } MemoryBlock;
      
      MemoryBlock memory[MEMORY_SIZE];
      
      // 初始化内存
      void initialize_memory() {
          for (int i = 0; i < MEMORY_SIZE; i++) {
              memory[i].is_free = 1;
              memory[i].size = 0;
          }
      }
      
      // 内存分配(首次适配算法)
      void *my_malloc(int size) {
          for (int i = 0; i < MEMORY_SIZE; i++) {
              if (memory[i].is_free && memory[i].size >= size) {
                  // 分配内存块
                  memory[i].is_free = 0;
                  return &memory[i];
              }
          }
          return NULL;
      }
      
      // 内存释放
      void my_free(void *ptr) {
          MemoryBlock *block = (MemoryBlock *)ptr;
          block->is_free = 1;
      }
      
      // 显示内存情况
      void display_memory() {
          printf("Memory Status:\n");
          for (int i = 0; i < MEMORY_SIZE; i++) {
              printf("[%d] - %s (Size: %d)\n", i, memory[i].is_free ? "Free" : "Allocated", memory[i].size);
          }
          printf("\n");
      }
      
      int main() {
          initialize_memory();
      
          // 分配内存块
          void *ptr1 = my_malloc(ALLOC_SIZE);
          if (ptr1 == NULL) {
              printf("内存分配失败\n");
              return -1;
          }
          ((MemoryBlock *)ptr1)->size = ALLOC_SIZE;
      
          // 分配第二个内存块
          void *ptr2 = my_malloc(ALLOC_SIZE);
          if (ptr2 == NULL) {
              printf("内存分配失败\n");
              return -1;
          }
          ((MemoryBlock *)ptr2)->size = ALLOC_SIZE;
      
          // 释放第一个内存块
          my_free(ptr1);
      
          // 显示内存情况
          display_memory();
      
          // 释放第二个内存块
          my_free(ptr2);
      
          // 显示内存情况
          display_memory();
      
          return 0;
      }
      

      在这个示例中,我们定义了一个固定大小的内存数组 memory,用于模拟实际的内存空间。每个内存块由一个 MemoryBlock 结构表示,其中包含了是否空闲的标志和大小信息。

      我们实现了三个主要函数:initialize_memory():用于初始化内存数组,将所有内存块标记为空闲状态。my_malloc():模拟内存分配操作,采用简单的首次适配算法遍历内存数组找到满足要求的空闲内存块。在 main() 函数中,我们进行了简单的测试:首先分配两个内存块,然后释放第一个内存块,最后释放第二个内存块,并显示内存情况以验证内存管理操作的正确性。my_free():模拟内存释放操作,将指定的内存块标记为可用。

总的来说,Linux的内存管理系统是一个复杂而强大的子系统,它在保证系统稳定性和性能的同时,还能够灵活地适应不同的工作负载和硬件环境。

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内存管理主要包括内存节点、分区、页框和虚拟内存等概念。 1. 内存节点 Linux 根据 CPU 访问代价的不同将内存划分为不同的分区,即内存节点。内核以 struct zone 来描述内存分区。通常一个节点分为 DMA、Normal 和 High Memory 内存区。其中,DMA 内存区为直接内存访问分区,通常为物理内存的起始16M,供外设使用,外设和内存直接访问数据而无需 CPU 参与;Normal 内存区为从 16M 到 896M 的内存区;HighMemory 内存区为 896M 以后的内存区。 2. 分区 内存节点中的分区是内存管理的基本单位,每个分区都有自己的页框列表和空闲页框列表。页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。 3. 页框 页框是内存管理的最小单位,通常为 4KB。内核通过页框来管理内存,将内存分为多个页框,每个页框都有自己的状态,包括已分配、未分配、已使用等。内核通过页表来映射虚拟地址和物理地址,将虚拟地址转换为物理地址。 4. 虚拟内存 虚拟内存是一种将硬盘中划出一段 swap 分区当作虚拟的内存,用来存放内存中暂时用不到的内存页,等到需要的时候再从 swap 分区中将对应的内存页调入到内存中的技术。硬盘此时相当于一个虚拟的内存Linux 通过虚拟内存技术来扩展内存,使得进程可以使用比物理内存更大的内存空间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值