Ununtu 20.04.1 添加系统调用,修改nice值,并读取nice和prio值,编写简单测试用例,并利用git生成系统补丁

2,066 阅读6分钟

编程环境

  • 系统:Ubuntu 20.04.1
  • 原始内核版本:5.4.0-42
  • 编译内核版本:5.10.7

遇到的情况:本人在虚拟机使用22.04.1版本的Ubuntu系统会出现各种问题,最后采用20.04.1版本。

修改内核

1. 安装编译环境

sudo apt-get install build-essential
sudo apt-get install libelf-dev
sudo apt-get install libncurses-dev
sudo apt-get install flex
sudo apt-get install bison
sudo apt-get install openssl
sudo apt-get install libncurses5-dev
sudo apt-get install make
sudo apt-get install libssl-dev

2. 解压内核文件

image.png

3. 修改系统调用表项

Linux系统有几百个系统调用,为了唯一的标识每一个系统调用,Linux为每一个系统调用定义了一个唯一的编号,这个编号就是系统调用号。

系统调用实质上就是函数调用,只不过调用的是系统函数,处于内核态而已。 用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行,最后返回。

系统调用表项在 arch/x86/entry/syscalls/syscall_64.tbl 目录下。

使用命令 vim arch/x86/entry/syscalls/syscall_64.tbl 打开文件,添加我们的系统调用以及系统调用号。我们使用的函数为:

int setnicebypid(pid_t pid, int niceval); 
        -pid:进程ID;
        -niceval:设置进程nice值;
        -返回值:0设置成功,-1设置失败;
int getnicebypid(pid_t pid, void __user *nice, void __user *prio);
        -pid:进程ID;
        - *nice:返回用户态的进程nice值;
        - *prio:返回用户态的进程prio值;
        -返回值:0设置成功,-1设置失败

添加两个系统调用441和442(需要记住这两个号码,后面调用的时候会用到),函数名为get\setnicebupid,服务例程入口为sys_set(get)nicebypid,应以sys_开头,如下图所示。注意:系统调用号应紧接着上面的系统调用号填写。 image.png

4.修改服务例程声明syscalls.h头文件

该文件在include/linux/syscalls.h目录下。

命令 vim include/linux/syscalls.h打开头文件,在最下方#endif前添加头文件声明,注意,是以sys_开头的函数。

asmlinkage int sys_setnicebypid(pid_t pid, int niceval);
asmlinkage int sys_getnicebypid(pid_t pid, void __user *nice, void __user *prio);

函数定义前加宏asmlinkage,表示这些函数通过堆栈而不是通过寄存器传递参数。gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage。

image.png

5.编写服务例程函数写入sys.c文件

该文件在 kernel/sys.c 目录。

命令 vim kernel/sys.c打开sys.c文件,在最下面插入如下代码。

扩展: SYSCALL_DEFINE的定义,如下:

#define SYSCALL_DEFINE0(name)	   asmlinkage long sys_##name(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

SYSCALL_DEFINEx里面的x代表的是系统调用参数个数,例如上文我们修改nice值的函数 setnicebypid(pid_t pid, int niceval)有两个参数pidniceval,所以我们使用后缀为2 的SYSCALL_DEFINE2函数,即SYSCALL_DEFINE2(setnicebypid, pid_t, pid, int, niceval)

注意:需要以SYSCALL_DEFINE*开始,里面第一个参数为系统调用的函数名,后面跟着系统调用函数的参数。参数与类型之间也要加逗号!

SYSCALL_DEFINE2(setnicebypid, pid_t, pid, int, niceval)
{
        struct pid * kpid;
        struct task_struct * task;
        kpid = find_get_pid(pid); // 返回pid
        task = pid_task(kpid, PIDTYPE_PID);  // 返回task_struct
        int n;
        n = task_nice(task); // 返回进程当前nice值
        int p;
        p = task_prio(task);  // 返回进程当前prio值
        set_user_nice(task, niceval); // 修改进程nice值 
        n = task_nice(task);
        return 0;
}
SYSCALL_DEFINE3(getnicebypid, pid_t, pid, void __user *, nice, void __user *, prio)
{
        struct pid * kpid;
        struct task_struct * task;
        kpid = find_get_pid(pid);
        task = pid_task(kpid, PIDTYPE_PID);
        int n;
        n = task_nice(task);
        int p;
        p = task_prio(task);
        copy_to_user(nice, &n, sizeof(n)); // 将nice值拷贝到用户空间
        copy_to_user(prio, &p, sizeof(p));
        return 0;
}

image.png

编译内核

1.menuconfig

make menuconfig O=../linux-5.10.7-obj –j12

O=../linux-5.10.7-obj : 生成到同级文件linux-5.10.7-obj下,注意是大写的“O”

-j12 : 12是cpu的内核数目,可加可不加。

有一个图形化界面框,直接EXIT就行了。

image.png](p3-juejin.byteimg.com/tos-cn-i-k3…?) 生成文件后,cd ../linux-5.10.7-obj进入该文件夹,修改.config文件。查找CONFIG_SYSTEM_TRUSTED_KEYS,并将其值修改为空值。 image.png

2.编译

make -j12

=-j12是cpu参加编译的数量

编译过程中,应该还会出现其他问题,大部分是环境未安装,可以搜一下安装即可。

安装内核

sudo make modules_install

把生成ko文件移动到/lib/modules/linux-veriosn下,本文指的是5.10.7,即/lib/modules/5.10.7。

sudo make install

更换内核,是把config-linux-veriosn,initrd-linux-veriosn,system-map-linux-veriosn,vmlinuz-linux-veriosn移动到/boot下。

此时,可以查看到/boot目录下我们产生的内核文件。

然后reboot重启命令即可以我们加载的内核开机运行。可用uname -a查看当时运行的内核版本。 也可以通过启动过程中的高级设置,grub选项选择内核版本。

可能出现的错误:

Ubuntu 20.04所用的Grub 2.04无法支持过大的initrd文件(如500M),导致内核启动时(即开机的那个黑屏界面)卡在“loading initial ramdisk”(Can’t allocate initrd,中文的话好像是载入内存中)。

sudo make INSTALL_MOD_STRIP=1 modules_install在安装模块时添加INSTALL_MOD_STRIP=1, 使用该命令即可解决。

如果不出现grub选项

可以更改grub设置。

sudo gedit /etc/default/grub修改配置文件。

将 GRUB_TIMEOUT_STYLE=hidden 命令注释掉,并且 GRUB_TIMEOUT=10 命令的值改为10。

image.png

测试代码

#include <stdio.h>
#include <unistd.h>
#include<sys/syscall.h>
#include<error.h>
#include<string.h>
int main(){
    pid_t pid;
    int nicevalue;
    int p = 0;
    int n = 0;
    int *prio;
    int *nice;
    prio = &p;
    nice = &n;

    printf("请输入pid:\n");
    scanf("%d",&pid);

    printf("请输入nice:\n");
    scanf("%d",&nicevalue);

    syscall(442,pid,nice,prio); // 获取nice和prio的系统调用
    printf("nice = %d, prio = %d \n", n, p);  // 获取nice和prio值
    printf("替换nice值\n");
    syscall(441, pid, nicevalue);  // 替换nice的系统调用
    syscall(442, pid, nice, prio);
    printf("nice = %d, prio = %d \n", n, p);  // 获取nice和prio值
     
    sleep(30);
    return 0;
}

通过 top -b 命令查看系统运行进程情况,可在运行测试程序的情况下,修改测试程序的nice值。nice值的取值范围为-20~19。切勿过大或过小。

利用git产生系统补丁

该目录下与上面所描述过程不在一个时间线上,可以单独执行,所以该部分属于一个单独的模块

进入到内核文件目录下,即linux-5.10.7。

git创建仓库

  • git init . 初始化git环境
  • git config --global user.mail “****@***”
  • git config --global user.name “****” 配置全局git账户
  • git add . 添加最初状态的内核文件
  • git commit -m "最初版本" 提交类似于标记,可用git log查看

git 创建补丁 修改完系统调用或者其他代码后,会产生与最初状态的代码差异,所以该差异利用git产生系统补丁。将补丁发送给其他人后,其他人只需将补丁打入内核代码,然后重新编译内核即可。

  • git status // 查看修改得文件
  • git add . 或者 git add <修改的文件>
  • git commit –m “**************”
  • git log //查看提交日志的hash数
  • git format-patch <git log的hash数> -1 //生成补丁 0001-*******.pacth
  • git reset --hard <git log的hash数> //回退到原始状态,需要原始状态的hash数值

打补丁

  • cd <path>/<to>/linux-5.10.7
  • patch –p1 < 0001-*******.pacth 安装补丁
  • patch –p1 -R < 0001-*******.pacth 卸载补丁