• 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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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);
    
    • 1

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6. 测试代码

    335是函数get_fio_time对应的系统调用号,glibc封装了一个标准函数syscall可以调用系统调用号。
    可以直接通过系统调用号来调用:

        struct timespec ts = {0}; 
        syscall(335, &tS, 0);
    
    • 1
    • 2

    可以参考glibc将其封装为一个接口,方便调用:

    long get_fio_time(struct timespec *tp, int idx)
    {
         return syscall(335, tp, idx);
    }
    
    • 1
    • 2
    • 3
    • 4

    完整的测试代码:

    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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编译的版本。
  • 相关阅读:
    数据库和缓存如何保证一致性?
    Jenkins nginx自动化构建前端vue项目
    【JavaEE初阶 -- 多线程2】
    Java Set类简介说明
    实战使用scrapy与selenium来爬取数据
    236. 二叉树的最近公共祖先
    Folyd
    Adobe XD最新版号查询,如何使用?
    怎么渲染半透明乳白色物体
    Kafka:容器安装篇
  • 原文地址:https://blog.csdn.net/feihe027/article/details/125425475