tmpfs的监控筛选/dev/shm下的shmem创建
一、背景
在一个比较注重性能的系统上,共享内存的使用肯定非常普遍。为了能更好的了解系统里共享内存的使用,比如创建、删除等操作,我们是可以对其进行监控的。
这篇博客以共享内存的创建监控为例来介绍如何监控共享内存。
这里有一个概念就是/dev/shm下的文件,是放在内存上的文件,这个/dev/shm如下面的df -h命令可以看到,它是一个tmpfs文件系统,而系统上的tmpfs文件系统其实有很多,/dev/shm是其中一个。我们其实在监控共享内存时,一般来说都是只感兴趣/dev/shm下的也就是shm_open这套shmem接口来创建的共享内存文件。
在下面第二章里,我们贴出改动源码和结果展示,在第三章里,我们给出源码分析和相关细节分析。
二、源码和结果展示
2.1 内核shmem.c里的改动部分
在内核shmem.c里增加下面这段代码:
static u32 _enable_output_path = 0;
void shmem_set_enable_output_path(void) {
_enable_output_path = 1;
}
EXPORT_SYMBOL_GPL(shmem_set_enable_output_path);
void shmem_set_disable_output_path(void) {
_enable_output_path = 0;
}
EXPORT_SYMBOL_GPL(shmem_set_disable_output_path);
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/fs.h>
#include <linux/mount.h>
#if 0
void print_superblock_root_path(struct super_block *sb) {
struct dentry *root_dentry = sb->s_root;
char path_buffer[256];
struct path root_path;
root_path.dentry = root_dentry;
root_path.mnt = root_dentry->d_sb->s_root->mnt; // 获取挂载点
if (d_path(&root_path, path_buffer, sizeof(path_buffer)) < 0) {
printk(KERN_ERR "Failed to get path\n");
} else {
printk(KERN_INFO "Root path: %s\n", path_buffer);
}
}
#endif
extern struct dentry* get_dentry_by_sb_currns(struct super_block *sb);
static struct super_block *_psb_devshm = NULL;
int outputfullpath(const char* i_prefix, struct inode *i_inode, struct dentry* i_dentry)
{
struct dentry *dentry;
char *buffer, *buffer_mount1, *buffer_mount2, *buffer_mount3, *path;
buffer = (char *)__get_free_page(GFP_KERNEL);
if (!buffer)
return -ENOMEM;
buffer_mount1 = (char *)__get_free_page(GFP_KERNEL);
if (!buffer_mount1)
return -ENOMEM;
buffer_mount2 = (char *)__get_free_page(GFP_KERNEL);
if (!buffer_mount2)
return -ENOMEM;
buffer_mount3 = (char *)__get_free_page(GFP_KERNEL);
if (!buffer_mount3)
return -ENOMEM;
if (_psb_devshm) {
if (i_inode->i_sb != _psb_devshm) {
printk("not /dev/shm!\n");
goto free_label;
}
}
hlist_for_each_entry(dentry, &i_inode->i_dentry, d_u.d_alias) {
path = dentry_path_raw(dentry, buffer, PAGE_SIZE);
if (IS_ERR(path)){
continue;
}
{
struct dentry* de = get_dentry_by_sb_currns(i_inode->i_sb);
char *path1 = dentry_path_raw(de, buffer_mount1, PAGE_SIZE);
char *path2 = NULL;
char *path3 = NULL;
//printk("path1 = %s\n", path1);
if (de->d_sb) {
if (strcmp(path1, "/") != 0) {
de = get_dentry_by_sb_currns(de->d_sb);
path2 = dentry_path_raw(de, buffer_mount2, PAGE_SIZE);
//printk("path2 = %s\n", path2);
if (de->d_sb) {
if (strcmp(path2, "/") != 0) {
de = get_dentry_by_sb_currns(de->d_sb);
path3 = dentry_path_raw(de, buffer_mount3, PAGE_SIZE);
//printk("path3 = %s\n", path3);
}
else {
path2 = NULL;
}
}
}
else {
path1 = NULL;
}
}
else {
goto free_label;
}
if (path1 && path2) {
if ((strcmp(path2, "/dev") == 0)
&& (strcmp(path1, "/shm") == 0)) {
if (strcmp(path3, "/") == 0) {
printk("[/dev/shm][%s]dentry name = %s , path = %s, i_dentry=%s, i_dentry->d_parent.name=%s"
" sb->s_root.name=%s (d_parent==sb->s_root)[%u]\n",
i_prefix, dentry->d_name.name, path, i_dentry->d_name.name,
i_dentry->d_parent->d_name.name, i_inode->i_sb->s_root->d_name.name,
(i_dentry->d_parent == i_inode->i_sb->s_root)?1:0);
_psb_devshm = i_inode->i_sb;
goto free_label;
}
}
}
// if (path2) {
// printk("dentry name = %s%s%s\n", path2, path1, path);
// }
// else if (path1) {
// printk("dentry name = %s%s\n", path1, path);
// }
// else {
// printk("dentry name = %s\n", path);
// }
}
}
free_label:
free_page((unsigned long)buffer);
free_page((unsigned long)buffer_mount1);
free_page((unsigned long)buffer_mount2);
free_page((unsigned long)buffer_mount3);
//spin_unlock(&inode->i_lock);
return 0;
}
然后在shmem.c里的shmem_mknod函数里增加下面这段红框部分的代码:
另外,和之前的博客 获取inode的完整路径包含挂载的路径_内核如何通过inode获取完整路径-CSDN博客 里增加在namespace.c里 3.1 一节里讲的内容一样,增加了下面这个函数:
struct dentry* get_dentry_by_sb_currns(struct super_block *sb)
{
struct mnt_namespace *mnt_ns = current->nsproxy->mnt_ns;
struct mount *mnt;
struct dentry* dentry = NULL;
lock_mount_hash();
list_for_each_entry(mnt, &mnt_ns->list, mnt_list) {
if (mnt->mnt.mnt_sb == sb) {
dentry = mnt->mnt_mountpoint;
break;
}
}
unlock_mount_hash();
return dentry;
}
EXPORT_SYMBOL_GPL(get_dentry_by_sb_currns);
2.2 内核模块用来启动/关闭shmem的这个调试判断和打印
#include <linux/module.h>
#include <linux/capability.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/poll.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/errno.h>
#include <linux/stddef.h>
#include <linux/lockdep.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <trace/events/workqueue.h>
#include <linux/sched/clock.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/tracepoint.h>
#include <trace/events/osmonitor.h>
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <trace/events/kmem.h>
#include <linux/ptrace.h>
#include <linux/uaccess.h>
#include <asm/processor.h>
#include <linux/sched/task_stack.h>
#include <linux/nmi.h>
#include <asm/apic.h>
#include <linux/version.h>
#include <linux/sched/mm.h>
#include <asm/irq_regs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhaoxin");
MODULE_DESCRIPTION("Module for kernel get time.");
MODULE_VERSION("1.0");
extern void shmem_set_enable_output_path(void);
extern void shmem_set_disable_output_path(void);
static int __init testtmpfs_init(void)
{
shmem_set_enable_output_path();
return 0;
}
static void __exit testtmpfs_exit(void)
{
shmem_set_disable_output_path();
}
module_init(testtmpfs_init);
module_exit(testtmpfs_exit);
2.3 结果展示
我们先在/dev/shm里创建文件夹和创建新的文件都可以打印出加的调试打印:
如果我们自己mount一个tmpfs的分区,然后在里面创建文件并不会打印出/dev/shm目录下才会打印的上图里的打印,而是打印下图里的not /dev/shm字样:
三、源码分析和相关细节分析
3.1 tmpfs文件系统和shmem接口的应用
shmem.c文件是服务于系统里的所有tmpfs文件系统里的操作。而tmpfs系统在系统里的情况非常普遍:
而我们感兴趣的往往是/dev/shm这样的使用shm的接口的程序应用,有关的例子如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> // For O_* constants
#include <sys/mman.h> // For shm_open, mmap
#include <unistd.h> // For close
#include <sys/stat.h> // For mode constants
#include <sys/types.h>
#include <errno.h>
int main() {
const char *name = "/my_shm"; // 共享内存的名称
const size_t size = 1024*1024*1024; // 共享内存的大小
int shm_fd; // 共享内存文件描述符
void *ptr; // 指向共享内存的指针
// 创建共享内存对象
shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
// 调整共享内存的大小
if (ftruncate(shm_fd, size) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
// 将共享内存映射到进程的地址空间
ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
getchar();
// 锁定共享内存区域中的页
if (mlock(ptr, size) != 0) {
perror("mlock");
exit(EXIT_FAILURE);
}
getchar();
// 写入数据到共享内存
sprintf((char*)ptr, "Hello, Shared Memory!");
// 读取数据
printf("Data from shared memory: %s\n", (char *)ptr);
// 解锁共享内存区域
if (munlock(ptr, size) != 0) {
perror("munlock");
exit(EXIT_FAILURE);
}
// 解除映射
if (munmap(ptr, size) == -1) {
perror("munmap");
exit(EXIT_FAILURE);
}
// 关闭共享内存对象
if (close(shm_fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
// 删除共享内存对象
if (shm_unlink(name) == -1) {
perror("shm_unlink");
exit(EXIT_FAILURE);
}
return 0;
}
3.2 shmem.c里的shmem_mknod函数
tmpfs系统下的创建文件夹和创建文件的操作都会走到shmem_mknod函数里来,如下图依次是创建文件夹和创建文件的函数shmem_mkdir和shmem_create,这两个函数最终都是调用的shmem_mknod函数:
所以,我们在shmem_mknod函数里加监测逻辑是能覆盖tmpfs文件系统里的创建文件夹和创建文件的行为。
3.3 shmem.c里的shmem_link和shmem_symlink
shmem.c里还有一些其他接口,如下图里的shmem_link是指硬链接,shmem_symlink是指软链接:
有关硬链接和软链接及相关的inode,dentry的实验见之前的博客 关于inode,dentry结合软链接及硬链接的实验-CSDN博客。
3.4 shmem_mknod函数里增加的“判断是不是/dev/shm下的mknod事件”的逻辑
我们在shmem_mknod里增加了判断逻辑,检查是否是/dev/shm下的mknod事件,用的依次判断是否是/dev和/shm这两级super_block的mnt_mountpoint,而获取mnt_mountpoint的这块核心逻辑在之前的博客 获取inode的完整路径包含挂载的路径_内核如何通过inode获取完整路径-CSDN博客 里进行了分析:
在shmem.c里使用了该get_dentry_by_sb_currns函数进行了几级mountpoint的dentry的记录,然后判断完整的mountpoint的dentry链是否是/dev/shm:
如果判断到是/dev/shm这个完整路径,就记录下来这个super_block指针:
这样,接下来的判断就会更加快了: