Linux内核系统调用开发流程?


省流:

1、kernel/sys.c里实现函数

2、Include/linux/syscalls.h在系统调用表里声明

3、arch/x86/entry/syscalls/syscall_64.tbl里映射:462 ——> sys_get_process_memory_info

4、make clean && make -j$(nproc)编译内核

5、make install && update-grub安装新内核,更新GRUB

img


获取源码:从Linux官网或Git仓库克隆内核源码。

环境准备:安装编译工具和相关依赖。

配置内核:使用make menuconfig等工具配置内核选项。 *****这一步就是开发过程

编译内核:通过make命令编译内核和模块。

安装内核:使用make install安装内核和make modules_install安装模块。

更新引导程序:更新GRUB配置,确保系统使用新内核。

测试内核:重启并验证新内核是否正常加载。

提交补丁:编写和提交补丁,经过审查后合并到主线代码中。


实现简单的系统调用以读取进程内存信息的实验为例

获取源码

Linux官网或Git仓库克隆内核源码。

环境准备

安装编译工具和相关依赖。

配置内核

使用make menuconfig等工具配置内核选项。 *****这一步就是开发过程

(1)实现新的系统调用

kernel/sys.c文件中实现sys_get_process_memory_info()函数

系统调用是用户空间程序与内核空间交互的接口,允许用户程序请求内核执行特定操作。sys.c文件就是实现这些系统调用的地方。

系统调用服务例程:每个系统调用都对应一个内核服务例程来实现系统调用的功能

kernel/sys.c 中如何编程?

// kernel/sys.c 中编程
SYSCALL_DEFINE2(get_process_memory_info, pid_t, pid, struct mem_info __user *, info)
// N=2,表示有2个参数
// get_process_memory_info是系统调用的名称

#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <uapi/linux/mem_info.h>
SYSCALL_DEFINE2(get_process_memory_info, pid_t, pid, struct mem_info __user *, info){
struct task_struct *task;
struct mm_struct *mm;
struct mem_info mem_info;

// 根据 PID 查找任务结构体
task = find_task_by_vpid(pid);
if (!task) {
return -ESRCH; // 没有找到对应的进程
}

// 获取进程的内存描述符
mm = get_task_mm(task);
if (!mm) {
return -EINVAL; // 无效的内存描述符
}

// 填充内存信息
mem_info.start_code = mm->start_code;
mem_info.end_code = mm->end_code;
mem_info.start_data = mm->start_data;
mem_info.end_data = mm->end_data;
mem_info.start_stack = mm->start_stack;
mem_info.start_brk = mm->start_brk;
mem_info.brk = mm->brk;

// 释放内存描述符
mmput(mm);

// 将内存信息复制到用户空间
if (copy_to_user(info, &mem_info, sizeof(struct mem_info))) {
return -EFAULT; // 复制到用户空间失败
}

return 0; // 成功
}
// 各个头文件,提供了什么结构体和函数

#include <linux/sched.h>:进程调度相关。task_struct任务结构体(进程状态信息)
#include <linux/mm.h>:内存管理相关。
#include <linux/uaccess.h>:内核空间和用户空间之间数据交换。copy_to_user, copy_from_user
#include <uapi/linux/mem_info.h>:自定义的头文件。

// 以find_task_by_vpid(pid_t vnr)为例
struct task_struct *find_task_by_vpid(pid_t vnr);
// 通过PID找到相应进程的task_struct

在编写过程中如何查询库函数的源码?也就是包含的头文件中的函数的源码

// 借助工具 cscope
// 构建 cscope 数据库
cscope -Rb

// 进入交互界面
cscope -d

// 在相应的搜索栏里寻找
// 进入文件
// 输入"/balabala"寻找 (会从光标当前位置往后寻找,可gg来到文件最开头)

// 以find_task_by_vpid(pid_t vnr)为例
// /find_task_by_vpid
struct task_struct *find_task_by_vpid(pid_t vnr);

(2)在系统调用表里声明函数

系统调用表的作用是将用户程序发出的系统调用请求映射到相应的内核函数。当用户程序通过中断或陷阱进入内核模式时,操作系统会查找系统调用表,并根据系统调用号调用相应的内核函数来执行具体的操作。

系统调用表在Include/linux/syscalls.h,编写格式是asmlinkage long sys_get_process_memory_info(pid_t pid, struct mem_info __user *info);

// include/linux/syscalls.h
#include <uapi/linux/mem_info.h> // 处于方便,我把结构体mem_info单独放到了一个头文件里
asmlinkage long sys_get_process_memory_info(pid_t pid, struct mem_info __user *info);

// include/uapi/linux/Kbuild
header-y += mem_info.h // header-y:这是一个变量,用来列出在构建过程中需要包含的头文件。
// 该行用于 Linux 内核的 Kbuild 系统,这是一个用于编译内核代码的构建系统,这里所说的构建就是之后的内核及内核模块的编译构建。

// include/uapi/linux/mem_info.h
#ifndef _UAPI_LINUX_MEM_INFO_H
#define _UAPI_LINUX_MEM_INFO_H

struct mem_info {
unsigned long start_code, end_code;
unsigned long start_data, end_data;
unsigned long start_stack;
unsigned long start_brk, brk;
};
#endif /* _UAPI_LINUX_MEM_INFO_H */

(3)系统调用映射表

arch/x86/entry/syscalls/syscall_64.tbl,系统调用映射表映射了每个系统调用号与内核中相应的系统调用函数之间的关系,当用户在应用程序中用到系统调用号时,会向内核请求调用映射的相应系统调用函数。

编写格式:

// 系统调用号 标志(通用) 系统调用
462 common get_process_memory_info sys_get_process_memory_info

编译内核

通过make命令编译内核和模块。

make clean && make -j$(nproc)

安装内核

使用make install安装内核和make modules_install安装模块。

更新引导程序

更新GRUB配置,确保系统使用新内核。

make install && update-grub

测试内核

重启并验证新内核是否正常加载,uname -r

本实验需要编写一个应用程序调用我们实现的系统调用函数,同时也是对系统调用函数的检验。

// main/syscall_test/test1
#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define SYS_GET_PROCESS_MEMORY_INFO 462
struct mem_info {
unsigned long start_code, end_code;
unsigned long start_data, end_data;
unsigned long start_stack;
unsigned long start_brk, brk;
};
int main() {
pid_t pid = getpid();
struct mem_info info;
printf("Enter PID:");
scanf("%d", &pid);
long result = syscall(SYS_GET_PROCESS_MEMORY_INFO, pid, &info);
if (result == 0) {
printf("Memory info for process %d:\n", pid);
printf(" Code: 0x%lx - 0x%lx\n", info.start_code, info.end_code);
printf(" Data: 0x%lx - 0x%lx\n", info.start_data, info.end_data);
printf(" Stack: 0x%lx\n", info.start_stack);
printf(" Heap: 0x%lx - 0x%lx\n", info.start_brk, info.brk);
} else {
printf("Failed to retrieve memory info for process %d, error: %s\n",
pid, strerror(-result));
}
return 0;
}

成功。


遇到的问题

1、安装新内核5.10.234之后,WiFi模块似乎不兼容了,无法联网

2、写好系统调用实现之后编译时出现报错,undefined reference to __x64___x64_sys_get_process_memory_info,内核无法引用

3、运行用户空间程序,报错:Failed to retrieve memory info for process 35328, error: -1

解决方法

1、重新尝试安装内核6.8.1,WiFi终于可以用了

2、在syscall_64.tbl文件里注册时尝试修改之前的注册,注意到报错信息多了一个__x64_,在注册时改成

3、因为我错误地使用旧内核运行了该程序,使得调用函数失败,需要切换内核