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. 附加

  1. i386或x86,泛指以前32位架构的cpu.
  2. x86-64或x64,兼容32位指令的64位的新架构。
  3. gcc编译版本
    a. -m32,指定编译运行在i386架构cpu上的程序,如果想运行在x64架构上,需要安装gcc-multilib来兼容老接口。
    b. -m64,编译64位的版本,运行在x64架构CPU上。
    c. -mx32,编译32位的版本,但是可以直接在x64架构上运行。
  4. linux内核中的syscall.tbl中的abi类型,有i386,64,x32,分别对应gcc的-m32,-m64,mx32。abi类型还有一个common,其兼容-m64和-mx32编译的版本。

以上是关于Linux内核开发——新添内核用户接口的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核开发基础

怎样在linux内核中检测传感器的数据

linux内核学习之四 系统调用

Linux 内核 内存管理内存管理架构 ① ( 内存管理架构组成 | 用户空间 | 内核空间 | MMU 硬件 | Linux 内核架构层次 | Linux 系统调用接口 )

Linux内核的启动流程之基础概念

Linux内核中添加系统调用接口简单示例