一、情景
Linux驱动适配不同内核时,由于内核版本的不同,有些函数可能没有,或者函数直接变化了,高版本可能增加了一些参数。
二、常规处理方案,根据内核版本判断
一般情况我们处理方式是在使用这些函数时,通过宏来判断当前的内核版本,根据版本来决定怎么使用函数,比如:
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)generic_fillattr(inode, stat);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5,12,0) && LINUX_VERSION_CODE < KERNEL_VERSION(6,3,0)generic_fillattr(mnt_userns, inode, stat);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0) && LINUX_VERSION_CODE < KERNEL_VERSION(6,6,0)generic_fillattr(idmap, inode, stat);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0)generic_fillattr(idmap,request_mask,inode, stat);
#endif
这种我们需要准确知道generic_fillattr
这个函数在哪个内核版本怎么变化了。但是Linux的内核有那么多,要找到从哪个版本开始变化了,很多时候是个巨大的工作量。
三、以vfs_getattr函数为例,介绍推荐的处理方案,编译时测试并决定
Linux的驱动适配,我们都需要在对应适配的内核中去编译一次,基于这个原理,我们可以在编译时,通过测试脚本,来确定使用的函数在当前内核中是否存在,以及是怎么样的形式。
不同内核版本的vfs_getattr
低版本内核中,接受三个参数:
int vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{struct inode *inode = dentry->d_inode;int retval;retval = security_inode_getattr(mnt, dentry);if (retval)return retval;if (inode->i_op->getattr)return inode->i_op->getattr(mnt, dentry, stat);generic_fillattr(inode, stat);return 0;
}
高一点的版本中,mnt和dentry参数直接变成了path结构体,而只接受2个参数:
int vfs_getattr(struct path *path, struct kstat *stat)
{struct inode *inode = path->dentry->d_inode;int retval;retval = security_inode_getattr(path->mnt, path->dentry);if (retval)return retval;if (inode->i_op->getattr)return inode->i_op->getattr(path->mnt, path->dentry, stat);generic_fillattr(inode, stat);return 0;
}
更高的版本中,多传入了2个参数,提供了额外的功能,允许调用者对文件属性获取操作进行更细粒度的控制,
request_mask:用于指定要获取的文件属性的掩码,即希望获取哪些属性的位掩码。
query_flags:用于指定其他查询标志,例如是否忽略安全性检查等。
int vfs_getattr(const struct path *path, struct kstat *stat,u32 request_mask, unsigned int query_flags)
{int retval;retval = security_inode_getattr(path);if (retval)return retval;return vfs_getattr_nosec(path, stat, request_mask, query_flags);
}
测试脚本
我们创建一个名为generate-kernel-function的脚本,包括以下函数:
通用函数:
prefix="KERNEL_API_"while [ "${1:0:1}" = "-" ] ; docase "$1" in-t)shifttmpdir=$1;;-o)shiftoutfile=$1;;esacshift
donerun() {( $@ || return 1 ) >/dev/null 2>&1
}do_test() {local var="$1"local val="$2"[ -n "$var" ]var="${prefix}$var"local src="$tmp_dir/test.c"local obj="$tmp_dir/test.o"cat > "$src"if ( run gcc -o "$obj" -c "$src" ) ; thenout "#define $var ${val:-1}"elif [ -z "$val" ] ; thenout "#undef $var"elseout "// not $var $val"fi
}
do_test 这个函数接受两个参数:
$1:变量名,用于指定要测试的条件。
$2:值,用于指定测试条件的预期值。
函数首先将这两个参数保存到本地变量 var 和 val 中。然后,它检查是否提供了变量名 $var,并在必要时添加前缀。接着,它定义了两个本地变量 src 和 obj,用于指定测试代码的源文件和目标文件的路径。
接下来,函数使用 cat 命令将标准输入中的内容输出到文件 $src 中,以生成测试代码。
接着,函数调用 c_run 函数编译源文件 $src 并生成目标文件 $obj。如果编译成功,则输出 #define $var ${val:-1},其中 ${val:-1} 表示如果 $val 未定义,则使用默认值 1。如果编译失败且 $val 未定义,则输出 #undef $var,表示未定义该变量。否则,输出 // not $var $val,表示未达到预期值。
测试函数:
do_test "VFS_GETATTR_ARGS" 2 <<END
#include <linux/fs.h>
int test(struct path path, struct kstat exec_file_stat, u32 request_mask, unsigned int query_flags) {return vfs_getattr(&path, &exec_file_stat);
}
ENDdo_test "VFS_GETATTR_ARGS" 3 <<END
#include <linux/fs.h>
int test(struct vfsmount *path, struct dentry *dentry, struct kstat exec_file_stat) {return vfs_getattr(path, dentry, &exec_file_stat);
}
ENDdo_test "VFS_GETATTR_ARGS" 4 <<END
#include <linux/fs.h>
int test(struct path path, struct kstat exec_file_stat, u32 request_mask, unsigned int query_flags) {return vfs_getattr(&path, &exec_file_stat, 0, 0);
}
END
编译脚本中调用,和实际的使用
在编译的Makefile文件中,我们可以通过:
generate-kernel-function -t `pwd`/tmp -o kernel-function.h
调用测试函数,并根据测试的结果生成宏,并输出到的一个头文件中,而在实际使用vfs_getattr函数的地方,我们通过判断哪个宏存在,就可以正确的调用对应的函数:
#if (2 == KERNEL_API_VFS_GETATTR_ARGS)
#define our_vfs_getattr(path, exec_file_stat) vfs_getattr(path, exec_file_stat)
#elif (3 == KERNEL_API_VFS_GETATTR_ARGS)
#define our_vfs_getattr(path, exec_file_stat) vfs_getattr((path)->mnt, (path)->dentry, exec_file_stat)
#elif (4 == KERNEL_API_VFS_GETATTR_ARGS)
#define our_vfs_getattr(path, exec_file_stat) vfs_getattr(path, exec_file_stat, 0, 0)
#else
#error The number of arguments to vfs_getattr is neither 2 nor 3 nor 4
#endifstatic int get_exec_eigenvalue(const char* exe_path, uint64_t* eigenvalue) {struct kstat exec_file_stat;our_nameidata_t nd;if (our_nd_from_name(exe_path, 0, &nd) == 0) {if (our_vfs_getattr(&(nd.path), &exec_file_stat)) {
}
四、是否有某个函数测试的例子
测试函数
do_test "HAVE_PATH_LOOKUP" <<END
#include <linux/fs.h>
#include <linux/namei.h>
void test(const char *path) {struct nameidata nd;path_lookup(path, LOOKUP_FOLLOW, &nd);
}
END
c代码中使用
#ifdef KERNEL_API_HAVE_PATH_LOOKUPrc = path_lookup(mnt, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &nd);
#elserc = kern_path(mnt, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &(nd.path));
#endif