Linux内核开发——新添内核用户接口
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核开发——新添内核用户接口相关的知识,希望对你有一定的参考价值。
1. 前言
用户态的程序如果想访问内核态的程序,并不能直接访问,而是通过中断处理机制来完成的。CPU提供一个陷入指令(Traps),亦称访管指令。用户态程序通过syscall附加系统调用号以及参数来完成对内核接口的调用。
syscall是glibc封装的接口,其底层实现主要是发起一个陷入指令中断int $0x80,然后内核处理处理此中断函数。
系统调用号,是一系列从0开始递增的数字,其实质是一组数组的下标,而这个数组存放一内核接口函数的地址。这样在内核响应陷入指令后,就可以通过系统调用号找到相应的函数地址,然后来调用相应的内核函数。
2. 实现代码
不同架构的实现略有不同,此处主要针对x86_64架构。在相关模块中添加源文件fio_test.c(此处添加到fs目录下),并添加相应的代码:
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/time_namespace.h>
#define KTP_COUNT 128
struct timespec64 kernel_tp[KTP_COUNT] = 0;
void clock_time_ex(size_t idx)
if (idx >= KTP_COUNT)
return;
ktime_get_ts64(&kernel_tp[idx]);
timens_add_monotonic(&kernel_tp[idx]);
SYSCALL_DEFINE2(get_fio_time, struct __kernel_timespec __user *, tp, size_t, idx)
int error = 0;
if (idx >= KTP_COUNT)
return -EINVAL;
printk("hello World!\\n");
clock_time_ex(idx);
if (put_timespec64(&kernel_tp[idx], tp))
error = -EFAULT;
return error;
SYSCALL_DEFINE2宏展开大概是:
asmlinkage long sys_get_fio_time(struct __kernel_timespec __user * tp, size_t idx);
将源文件添加到fs目录下makefile文件。
makefile此处是利用隐式编译,即只需要知道目录文件名,就会自动去找同名的源文件并进行编译。
3. 函数声明
函数声明一般放在/include/linux/syscalls.h文件中,也可以放在arch/x86目录下的syscalls.h文件中。
其中的asmlinkage是一个必需的限定词,用于通知gcc编译器从堆栈中提取函数的参数,而不是从寄存器传递。这样无论是标准C还是汇编代码,都可以调用此函数。
asmlinkage long sys_get_fio_time(const __kernel_timespec __user * tp, size_t idx);
4. 配置syscall.tbl
不同的架构,会有不同的系统调用表文件,此处针对x86_64,则位于/arch/x86/entry/syscalls目录,目录中有两个文件,分别为syscall_32.tbl和syscall_64.tbl,前者针对i386架构的cpu,后者针对的是x86_64架构的cpu。此处在syscall_64.tbl中添加。函数声明和syscall.tbl一起组成一个索引对应函数地址的数组。因为Linux内核文件为elf文件,其中不包括函数符号,只能直接调用函数地址。为了更方便地调用函数地址,提供一个系统调用号来与函数地址配对。
syscall.tbl中的abi类型,有i386,64,x32,分别对应gcc的-m32,-m64,mx32。abi类型还有一个common,其兼容-m64和-mx32编译的版本。默认一般填写common即可。
5. 编译内核
下面是直接编译内核,并将相关文件拷贝到系统目录,并更新grub默认项为当前编译版本。
sudo make -j6
sudo mkinitramfs /lib/modules/5.17.12 -o /boot/initrd.img-5.17.12
sudo cp arch/x86/boot/bzImage /boot/vmlinuz-5.17.12
sudo cp System.map /boot/System.map-5.17.12
sudo update-grub
6. 测试代码
335是函数get_fio_time对应的系统调用号,glibc封装了一个标准函数syscall可以调用系统调用号。
可以直接通过系统调用号来调用:
struct timespec ts = 0;
syscall(335, &tS, 0);
可以参考glibc将其封装为一个接口,方便调用:
long get_fio_time(struct timespec *tp, int idx)
return syscall(335, tp, idx);
完整的测试代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <time.h>
long get_fio_time(struct timespec *tp, int idx)
return syscall(335, tp, idx);
int main(void)
char buf[256] = 0;
struct timespec ts = 0;
get_fio_time(&ts, 0);
printf("time1:%ld-%lu\\n",ts.tv_sec, ts.tv_nsec);
clock_gettime(1, &ts);
printf("time2:%ld-%lu\\n",ts.tv_sec, ts.tv_nsec);
return 0;
get_fio_time的作用与clock_gettime一样的。
我们还可以在在dmesg内核日志中看到内核接口中的hello World的打印信息。
7. 附加
- i386或x86,泛指以前32位架构的cpu.
- x86-64或x64,兼容32位指令的64位的新架构。
- gcc编译版本
a. -m32,指定编译运行在i386架构cpu上的程序,如果想运行在x64架构上,需要安装gcc-multilib来兼容老接口。
b. -m64,编译64位的版本,运行在x64架构CPU上。
c. -mx32,编译32位的版本,但是可以直接在x64架构上运行。 - linux内核中的syscall.tbl中的abi类型,有i386,64,x32,分别对应gcc的-m32,-m64,mx32。abi类型还有一个common,其兼容-m64和-mx32编译的版本。
以上是关于Linux内核开发——新添内核用户接口的主要内容,如果未能解决你的问题,请参考以下文章
Linux 内核 内存管理内存管理架构 ① ( 内存管理架构组成 | 用户空间 | 内核空间 | MMU 硬件 | Linux 内核架构层次 | Linux 系统调用接口 )