Linux进程task struct结构里有一个很重要的内存相关mm字段,本用例展示mm字段指向的mm struct的重要参数。其中pgd是页全局目录指针,可以追踪进程的虚拟地址到物理地址的映射关系,调试与页表相关的错误,例如页表项是否正确初始化。参数pgtables_bytes表示进程页表占用内存的大小,帮助评估进程页表的内存占用情况。参数total_vm表示进程虚拟内存总大小,通过监控total_vm的变化,可以发现进程是否过度申请虚拟内存,从而导致系统性能下降。参数stack_vm表示栈映射的页面数,通过监控栈的大小,可以发现栈溢出的风险。参数RSS表示进程实际占用的物理内存页数,通过监控RSS增长情况,可以发现内存泄露问题。
1. 在实验本节代码前,准备环境一个ubuntu物理机或者虚拟机,下载并使能交叉编译工具链,然后编译好内核源码,参考《编译内核镜像并在云实验室开发板运行》
2. Linux kernel进程内存参数的示例代码:
在你要写代码的目录下用touch新建两个文件,一个源码文件,一个Makefile。如下
~/linux_driver/ 08_process_mem$ touch 08_process_mem.c
~/linux_driver/08_process_mem$ touch Makefile
~/linux_driver/08_process_mem$ vim 08_process_mem.c
将下面的代码复制到08_process_mem.c, 如下:
/*
* Copyright 2025 NXP
* SPDX-License-Identifier: GPL-2.0+
*/
1 #include <linux/module.h>
2 #include <linux/types.h>
3 #include <linux/init.h>
4 #include <linux/cdev.h>
5 #include <linux/mm.h>
6
7
8 MODULE_LICENSE("GPL");
9 MODULE_AUTHOR("Cloud Lab");
10
11 #define BUF_LEN 0x1000
12 #define VIRTCHARDEV_NAME "virtdev"
13
14 struct virtchardev
15 {
16 dev_t virtdev_id;
17 struct cdev virtdev_cdev;
18 struct class *class;
19 struct device *device;
20 int virtdev_major;
21 int virtdev_minor;
22 struct semaphore sema;
23
24 struct cdev cdev;
25 unsigned char mem[BUF_LEN];
26
27 };
28
29 struct virtchardev *virtdev;
30
31
32 int virtdev_open(struct inode *inode, struct file *filp)
33 {
34 filp->private_data = virtdev;
35
36 down_interruptible(&virtdev->sema);
37 return 0;
38 }
39
40 int virtdev_release(struct inode *inode, struct file *filp)
41 {
42 struct virtchardev *dev = filp->private_data;
43
44 up(&dev->sema);
45 return 0;
46 }
47
48 static ssize_t virtdev_read(struct file *filp, char __user *buf,
49 size_t size, loff_t *ppos)
50 {
51 unsigned long p = *ppos;
52 unsigned int count = size;
53 int ret = 0;
54 struct virtchardev *dev = filp->private_data;
55
56 if (p >= BUF_LEN)
57 {
58 printk("*ppos = %d\n", p);
59 return count ? -ENXIO : 0;
60 }
61
62 if (count > BUF_LEN - p)
63 {
64 count = BUF_LEN - p;
65 }
66 if (copy_to_user(buf, (void*)(dev->mem + p), count))
67 {
68 ret = - EFAULT;
69 }
70 else
71 {
72 *ppos += count;
73 ret = count;
74
75 printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
76 }
77 printk("Process state,priority,schedule policy info::\n"
78 "module : %s\n"
79 "process name : %s\n"
80 "PGD start addr : 0x%lx\n"
81 "PTE table pages number : %d\n"
82 "The pages total mapped : %d KB\n"
83 "Resident Set Size(RSS) : %d KB\n"
84 "Stack size(VM) : %d KB\n",
85 VIRTCHARDEV_NAME,
86 current->comm,
87 current->mm->pgd,
88 current->mm->pgtables_bytes,
89 current->mm->total_vm << (PAGE_SHIFT - 10),
90 get_mm_rss(current->mm) << (PAGE_SHIFT - 10),
91 current->mm->stack_vm << (PAGE_SHIFT - 10)
92 );
93
94 return ret;
95 }
96
97 static ssize_t virtdev_write(struct file *filp, const char __user *buf,
98 size_t size, loff_t *ppos)
99 {
100 unsigned long p = *ppos;
101 unsigned int count = size;
102 int ret = 0;
103 void *kbuf = NULL;
104 struct virtchardev *dev = filp->private_data;
105
106 kbuf = kvmalloc(size, GFP_KERNEL);
107 memset(kbuf, 0, count);
108 if (copy_from_user(kbuf, buf, count) != 0){
109 ret = - EFAULT;
110 }
111 strscpy(dev->mem, kbuf, (count > BUF_LEN ? BUF_LEN : count));
112 ret = count;
113 printk("write bytes: %zu\n", count);
114 printk("Process state,priority,schedule policy info::\n"
115 "module : %s\n"
116 "process name : %s\n"
117 "PGD start addr : 0x%lx\n"
118 "PTE table pages number : %d\n"
119 "The pages total mapped : %d KB\n"
120 "Resident Set Size(RSS) : %d KB\n"
121 "Stack size(VM) : %d KB\n",
122 VIRTCHARDEV_NAME,
123 current->comm,
124 current->mm->pgd,
125 current->mm->pgtables_bytes,
126 current->mm->total_vm << (PAGE_SHIFT - 10),
127 get_mm_rss(current->mm) << (PAGE_SHIFT - 10),
128 current->mm->stack_vm << (PAGE_SHIFT - 10)
129 );
130
131 return ret;
132 }
133
134 static const struct file_operations virtdev_fops =
135 {
136 .owner = THIS_MODULE,
137 .read = virtdev_read,
138 .write = virtdev_write,
139 .open = virtdev_open,
140 .release = virtdev_release,
141 };
142
143
144 int virtdev_init(void)
145 {
146 virtdev = kmalloc(sizeof(struct virtchardev), GFP_KERNEL);
147 memset(virtdev, 0, sizeof(struct virtchardev));
148 printk("%s %d\n", __func__, __LINE__);
149 sema_init(&virtdev->sema, 1);
150 alloc_chrdev_region(&virtdev->virtdev_id, 0, 1,
151 VIRTCHARDEV_NAME );
152 virtdev->virtdev_major = MAJOR(virtdev->virtdev_id);
153
154 cdev_init(&virtdev->virtdev_cdev, &virtdev_fops);
155
156 virtdev->virtdev_cdev.owner = THIS_MODULE;
157 virtdev->virtdev_cdev.ops = &virtdev_fops;
158
159 cdev_add(&virtdev->virtdev_cdev, virtdev->virtdev_id, 1);
160
161 printk("%s %d\n", __func__, __LINE__);
162 virtdev->class = class_create(/*THIS_MODULE,*/ VIRTCHARDEV_NAME);
163
164 device_create(virtdev->class, NULL, virtdev->virtdev_id,
165 NULL, VIRTCHARDEV_NAME);
166 printk("Process state,priority,schedule policy info::\n"
167 "module : %s\n"
168 "process name : %s\n"
169 "PGD start addr : 0x%lx\n"
170 "PTE table pages number : %d\n"
171 "The pages total mapped : %d KB\n"
172 "Resident Set Size(RSS) : %d KB\n"
173 "Stack size(VM) : %d KB\n",
174 VIRTCHARDEV_NAME,
175 current->comm,
176 current->mm->pgd,
177 current->mm->pgtables_bytes,
178 current->mm->total_vm << (PAGE_SHIFT - 10),
179 get_mm_rss(current->mm) << (PAGE_SHIFT - 10),
180 current->mm->stack_vm << (PAGE_SHIFT - 10)
181 );
182
183 return 0;
184 }
185
186 void virtdev_exit(void)
187 {
188 cdev_del(&virtdev->virtdev_cdev);
189 unregister_chrdev_region(virtdev->virtdev_id, 1);
190
191 device_destroy(virtdev->class, virtdev->virtdev_id);
192 class_destroy(virtdev->class);
193
194 kfree(virtdev);
195
196 }
197
198 module_init(virtdev_init);
199 module_exit(virtdev_exit);
编写Makefile:
PWD := $(shell pwd)
KERDIR := /home/test/kernel_build/linux-imx
obj-m += 08_process_mem.o
all:
make -C $(KERDIR) M=$(PWD) modules
clean:
make -C $(KERDIR) M=$(PWD) clean
3. 编译:
~/linux_driver/08_process_mem$ export CROSS_COMPILE=~/toolchain/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
~/linux_driver/08_process_mem$ export ARCH=arm64
~/linux_driver/08_process_mem$ make
编译完成后当前目录会有如下文件
~/linux_driver/08_process_mem$ ls
08_process_mem.ko
4. 替换云实验室开发板镜像并重新启动
根据《编译内核镜像并在云实验室开发板运行》替换dtb和image,这里仍然需要这一步,因为内核模块在加载时需要和内核编译版本相匹配
上传并替换云实验室板子的dtb和Image,依次点击用户网页端下面1,2,3的位置,在点击位置2时,选择TFTP,点击3后选择文件~/kernel_build/linux-imx/arch/arm64/boot/dts/freescale/imx8mp-evk.dtb和~/kernel_build/linux-imx/arch/arm64/boot/Image
上传成功后,点击下方PowerReset EVK按钮,重启开发板。
至此,开发板开始运行自己修改编译的linux镜像。
5. 上传自己编译的字符驱动模块
在4中编译出了关于mutex应用的内核驱动程序08_process_mem.ko,通过一次点击下图中的1,2,3位置,然后切换到本地对应的文件目录上传。
6. 运行字符驱动模块
在执行下面的操作前要先使能串口终端的log打印,否则会看不到串口终端打印信息:
root@imx93evk:~# echo 8 > /proc/sys/kernel/printk
执行insmod加载模块,可以看到log里打印出了pgd,pgtables_byte,,total_vms,stack_vm,RSS等信息。
当向内核模块写入字符串,可以看到sh进程在工作,并打印了相关信息
然后读取刚才写入的信息时,可以看到cat进程的相关信息,log如下: