Linux1.0 kmalloc kfree_s分析

这篇博客详细介绍了Linux1.0版本中内核动态内存管理的实现,尤其是kmalloc和kfree_s操作。内存管理基于分页,以4KB为单位。当申请不同大小的内存时,如果现有空闲块不足,内核会分配新的页面,并创建页描述符记录内存块状态。文章还讨论了关键的数据结构,如块头、页描述符和大小描述符,这些结构用于跟踪内存分配和链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                        kmalloc分析
    kmalloc内存分配函数是Linux内核中动态内存管理中非常重要的函数,在这篇博客中主要来分析内核当中动态内存的分配和释放过程。
    首先来介绍kmalloc和kfree_s的基本原理,在Linux内核当中内存

的管理采用的是分页管理方式,即以页为单位,每页的大小4KB。在系
统当中内核每次对动态内存的申请大小是不确定的,就是你可以申请
的大小可以是32KB,64KB,128KB,,4080KB,现暂且将这个大小
称为size,对于每种大小的内存申请,如果已有的空闲块被用完,则内核申请一页新的内存(4KB),在该页内存中的起始处,会存放一个页描述符,页描述符中记录该内存块中分成的小块的使用情况以及下一块用来分配这样size大小的物理块,然后将该页的剩下内存按照申请块的大小和每块的块头平均分成若干块,在这里的块头是用来记录各块之间的链接关系,块头的下部分内存才是被kmalloc分配出去的内存。
介绍了分配的基本原理之后,再来介绍内核当中关于kmalloc的几个重要结构体:
1、每块内存的块头
struct block_header {
unsigned long bh_flags; /物理页中分配的块的状态/
union {
unsigned long ubh_length; /被使用时记录实际使用大小/
struct block_header fbh_next; /空闲时下一个块的大小*/
} vp;
};
2、分配size大小的物理块页描述符
struct page_descriptor {
struct page_descriptor *next;
struct block_header *firstfree;
int order; /这一页分配的是多大块号/
int nfree; /一页中空闲块的数量/
};
3、记录内核中分配的内存大小以及一页内存中可以分配该种size大小的块的数量,同时记录了也记录了分配该size大小的物理页的地址
struct size_descriptor {
struct page_descriptor firstfree; /分配size大小块的物理页地址*/
int size;
int nblocks; /一页总共能分配多少块/

    int nmallocs;                       /*已经使用size大小的块有多少块*/
    int nfrees;
    int nbytesmalloced;                 /*实际分配的字节数*/
    int npages;                         /*使用了多少页*/
};
4、struct size_descriptor的一个数组,初始化各种可以分配的大小以及一页可以分配的小块的数量。
struct size_descriptor sizes[] = { 
    { NULL,  32,127, 0,0,0,0 },
    { NULL,  64, 63, 0,0,0,0 },
    { NULL, 128, 31, 0,0,0,0 },
    { NULL, 252, 16, 0,0,0,0 },
    { NULL, 508,  8, 0,0,0,0 },
    { NULL,1020,  4, 0,0,0,0 },
    { NULL,2040,  2, 0,0,0,0 },
    { NULL,4080,  1, 0,0,0,0 },
    { NULL,   0,  0, 0,0,0,0 }
};

这几个结构体在内存当中的关系可以用下图来表示

这里写图片描述

            分配之后就是内存块的释放了,当给出一个释放地址时,可以算出该地址所在的物理块然后取物理块开始处的页描述符,
                首先将释放的块添加到该页的空闲块链的链首,然后将所在的物理页放在size大小对应的物理页链表的链首,方便
                下次分配时寻找空闲块。如果所在的整个物理块是空闲的则将整个物理块释放。

详细的代码注释:https://github.com/wanggx/Linux1.0
#include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/ctype.h> #define DEVICE_NAME "my_char_dev" #define CLASS_NAME "my_char_class" // 在模块代码中添加模块信息宏 MODULE_INFO(intree, "N"); // 明确声明为out-of-tree模块 // MODULE_LICENSE("GPL v2"); // 确保使用兼容的许可证 MODULE_VERSION("1.0"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Character device driver with uppercase option"); static int cap = 0; module_param(cap, int, S_IRUGO); MODULE_PARM_DESC(cap, "Enable uppercase conversion (1=enable)"); static int major_num; static struct class *char_class = NULL; static struct cdev my_cdev; static ssize_t dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { char *kernel_buf; int i; // 分配内核缓冲区(+1用于空字符) kernel_buf = kmalloc(count + 1, GFP_KERNEL); if (!kernel_buf) return -ENOMEM; // 从用户空间复制数据 if (copy_from_user(kernel_buf, buf, count)) { kfree(kernel_buf); return -EFAULT; } kernel_buf[count] = '\0'; // 确保字符串终止 // 大写转换处理 if (cap) { for (i = 0; i < count; i++) kernel_buf[i] = toupper(kernel_buf[i]); } // 打印内核日志(实际使用时建议用print_hex_dump) printk(KERN_INFO "Received %zu bytes: %s\n", count, kernel_buf); kfree(kernel_buf); return count; } static struct file_operations fops = { .owner = THIS_MODULE, .write = dev_write, }; static int __init char_dev_init(void) { dev_t dev_num; // 动态分配设备号 if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) return -1; major_num = MAJOR(dev_num); // 初始化cdev结构 cdev_init(&my_cdev, &fops); my_cdev.owner = THIS_MODULE; // 添加字符设备到系统 if (cdev_add(&my_cdev, dev_num, 1) < 0) { unregister_chrdev_region(dev_num, 1); return -1; } // 创建设备类 char_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(char_class)) { cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); return PTR_ERR(char_class); } // 创建设备节点 device_create(char_class, NULL, dev_num, NULL, DEVICE_NAME); printk(KERN_INFO "Char device loaded (cap=%d)\n", cap); return 0; } static void __exit char_dev_exit(void) { dev_t dev_num = MKDEV(major_num, 0); // 销毁设备节点和类 device_destroy(char_class, dev_num); class_destroy(char_class); // 删除cdev并释放设备号 cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_INFO "Char device unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); 详细解释每一行代码
08-08
/* \*file char_dev.c \*brief the source for homework2.3 \* homework2.3: linux kernel code \*author huangzhifu<huangzhifu@tp-link.com.hk> \*version 1.0.0 \*date 08/07/2025 \*history */ #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/device.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/ctype.h> #define DEVICE_NAME "my_char_dev" #define CLASS_NAME "my_char_class" // 在模块代码中添加模块信息宏 MODULE_INFO(intree, "N"); // 明确声明为out-of-tree模块 MODULE_VERSION("1.0"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Character device driver with uppercase option"); static int cap = 0; module_param(cap, int, S_IRUGO); MODULE_PARM_DESC(cap, "Enable uppercase conversion (1=enable)"); static int major_num; static struct class *char_class = NULL; static struct cdev my_cdev; static ssize_t dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { char *kernel_buf; int i; // 分配内核缓冲区(+1用于空字符) kernel_buf = kmalloc(count + 1, GFP_KERNEL); if (!kernel_buf) return -ENOMEM; // 从用户空间复制数据 if (copy_from_user(kernel_buf, buf, count)) { kfree(kernel_buf); return -EFAULT; } kernel_buf[count] = '\0'; // 确保字符串终止 // 大写转换处理 if (cap) { for (i = 0; i < count; i++) kernel_buf[i] = toupper(kernel_buf[i]); } // 打印内核日志(实际使用时建议用print_hex_dump) printk(KERN_INFO "Received %zu bytes: %s\n", count, kernel_buf); kfree(kernel_buf); return count; } static struct file_operations fops = { .owner = THIS_MODULE, .write = dev_write, }; static int __init char_dev_init(void) { dev_t dev_num; // 动态分配设备号 if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) return -1; major_num = MAJOR(dev_num); // 初始化cdev结构 cdev_init(&my_cdev, &fops); my_cdev.owner = THIS_MODULE; // 添加字符设备到系统 if (cdev_add(&my_cdev, dev_num, 1) < 0) { unregister_chrdev_region(dev_num, 1); return -1; } // 创建设备类 char_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(char_class)) { cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); return PTR_ERR(char_class); } // 创建设备节点 device_create(char_class, NULL, dev_num, NULL, DEVICE_NAME); printk(KERN_INFO "Char device loaded (cap=%d)\n", cap); return 0; } static void __exit char_dev_exit(void) { dev_t dev_num = MKDEV(major_num, 0); // 销毁设备节点和类 device_destroy(char_class, dev_num); class_destroy(char_class); // 删除cdev并释放设备号 cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); printk(KERN_INFO "Char device unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); 详细解释每一行代码,包括每一行中的参量含义,需要详细具体
最新发布
08-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值