一、背景
之前的博客 内核相关所有专栏导航(持续更新) 里第八章 内存回收 一章里有不少博客讲到了pagecache及内存回收的一些机理。这篇博客编写一个脚本用来查看系统下的各个elf文件的使用情况,不过我们只关注会用到的那些elf文件的使用情况,并不关注不被用到的elf文件的使用情况。
在下面第二章里,我们给出抓取的相关脚本源码及使用步骤,在第三章里,我们给出一些脚本实现细节上的一些提示及未来的改进。
二、脚本源码及使用步骤
2.1 脚本源码
脚本是基于使用着vmtouch这个工具,vmtouch工具可直接apt-get update,然后apt-get install vmtouch来安装。或者apt install -y git build-essential之后,git clone下面的源码:
bash
git clone https://github.com/hoytech/vmtouch.git
也可以下载网站上的zip包。链接:https://github.com/hoytech/vmtouch
2.1.1 获取elf文件里哪些真正被任务映射且使用的page数和总page数
下面的脚本我命名的是getexecpages.sh,但是实际上运行得到的并不是都是可执行段,而是任务正在映射且使用过的所有elf部分,不仅包含代码段,还包含数据段,如果要只查看代码段还得进一步改造,这会在之后的博客里讲解。
bash
#!/bin/bash
vmtouch -e $1 > /dev/null 2>&1
vmtouch -v $1 | grep -oP 'Resident Pages: \K\d+(?=/)'
上面脚本里的grep -oP是一个很实用的方式,grep的-P也就是perl模式也是能更好地支持正则表达式,也是很好用的,在下面 3.1 里进行展开。
下面的脚本我命名的是getallpages.sh,方式和getexecpages.sh类似,不展开介绍了:
bash
#!/bin/bash
#vmtouch -e $1 > /dev/null 2>&1
vmtouch -v $1 | grep -oP 'Resident Pages: \d+\/\K\d+'
2.1.2 获取一个目录下所有有resident的pages的数量及对应的page总数量
bash
#!/bin/bash
total=0
all=0
temp_file="getdir_temp.txt"
find $1 -type f -print0 > $temp_file
while IFS= read -r -d '' file; do
#echo $file
if [ -L "$file" ]; then
#echo "Skipping symbolic link: $file"
continue
fi
value=$(./getexecpages.sh $file)
#echo $value
if [[ $value =~ ^[0-9]+$ ]]; then
if [[ $value != 0 ]]; then
total=$((total + value))
#echo res:$value
value_all=$(./getallpages.sh $file)
#echo all:$value_all
all=$((all + value_all))
fi
#echo $total
fi
done < $temp_file
echo $total > "getall_temp.txt"
echo $all > "getall_temp_all.txt"
解释两点:
1)上面把find出来的结果保留到一个文件,那是因为直接用重定向会导致运算结果无法保留到该脚本所在环境里,具体阐述见下面 3.2 一节
2)我是判断出resident pages是非0,才去执行getallpages.sh去获取对应的总pages数,这是为了排除掉我们当前系统内不用的elf文件部分。如果我们只是要获取整个文件夹下的resident pages和总pages的话,那么直接用vmtouch -v 文件夹路径 这么用就可以了,完全不需要再用这篇博客里的脚本集。
2.1.3 总的执行脚本
下面的脚本是总的执行脚本,它会依次遍历my_array里的目录元素,去依次执行getdir.sh的逻辑:
bash
#!/bin/bash
my_array=(
"/usr/bin"
"/usr/lib"
)
total_all=0
all_all=0
for element in "${my_array[@]}"; do
echo $element
./getdir.sh $element
value_all=`cat getall_temp.txt`
all_temp=`cat getall_temp_all.txt`
echo $value_all
total_all=$((total_all + value_all))
echo total_pages_all=$total_all
all_all=$((all_all + all_temp))
echo all_all=$all_all
done
page_size=$(getconf PAGE_SIZE)
total_memory=$((total_all * page_size))
all_size=$((all_all * page_size))
echo total_size="$total_memory"
echo all_size="$all_size"
2.2 使用步骤
使用步骤还是比较简单的,按照项目情况去修改getall.sh里的my_array的内容,然后执行./getall.sh即可,记得把脚本都加上可执行权限。
三、脚本的注意事项及未来的改进
3.1 grep的-oP使用详解
先说grep -P的使用,对于\d+这种表示,grep -E就不行,grep -P就可以:

grep -o的使用是用\K来忽略\K之前的内容,用(?=/)来表示匹配的部分的后面是一个/:

增加这个后面的匹配符是为了增加检查,其实如果前面的头就只出现一次的话,后面的条件也可以不加。比如用下面的命令去匹配Resident Pages: 数字/后面的数字内容部分:

上面说到的grep -oP在博客 常用的底层调试用的不容易记住的命令整理(持续更新) 里也讲过,更多的一些调试用的常用的可能记不住的命令见这篇博客。
3.2 脚本里的逻辑慎用重定向
脚本的使用要注意脚本内的变量如果涉及到重定向后的脚本内容里的运算的话,那么这部分运算的结果不会保留到脚本上下文里,如何来理解呢?看下图:

上图里的total=total+value的运算并打印total都是ok的,可以得到正确的数值的,但是就是最后两句就拿的是原始的total 0:

这是因为脚本里使用了重定向,而重定向就涉及到子进程,total变量在子进程里的运算无法保留到父进程里:

3.3 脚本并没有搜出代码段部分
这一点也是之前说到的,脚本当前并没有搜出代码段部分,而是搜了不仅代码段还有其他数据段,如果要仅仅搜出代码段,还得进一步改造,如下图,要先readelf -S得到如下图里的内容,然后过滤出AX的部分,再去用vmtouch或者其他程序去确定下图里的这些AX的段落里有多少被映射和使用。
