linux内核实现添加系统调用,实现修改和读取nice值

 费德  2016/11/14 22:38  11,993 次

因操作系统课程设计需要,最近一直在折腾内核编译
前前后后折腾了将近三个星期,今天总算做完了第一个实验。
下面分享一下具体的经验。

一、编译内核

这一步之前一直觉得可以省略,反正添加完系统调用后,也要重新编译的。求教老师后,她说直接添加调用,如果出错了,不能分析出是编译的问题还是系统调用的问题。所以,这步还是不能省的喽。

准备

我使用的是ubuntu 16.04 LTS,下载的是4.4.30的内核。
第一个要注意的是:版本不要选择太高的,我第一次就选的4.8.7,结果经过将近8个小时编译后(和电脑配置有关系,我同学i7的配置,直接给虚拟机分配8个G内存,整个过程只花了40分钟,泪奔~),键盘驱动直接无法使用。
所以选择4.4.30或者接近的就好了。
第二个要注意的是:硬盘至少需要30g,不然会爆掉...其次,随着失败次数的增加,源码包将越来越大,ls命令都可能提示空间不足。如果出现这种情况,可以把历史源码文件夹删除掉,然后再解压一份新的源码包。

内核下载地址

编译内核

编译内核其实很简单~

我们先切换到root用户,把下载好的内核压缩文件放到/usr/src下,然后进入这个目录

解压源码包

xz -d linux-4.4.30.tar.xz
tar -xvf linux-4.4.30.tar

然后如果不是第一次编译或者编译出错需要重新编译最好都执行一下下面的命令

清除编译历史

make mrproper

执行这条命令需要先安装一个包

apt-get install libncurses5-dev

我们是刚下载的的干净的内核源码包,所以不用执行make mrproper这条命令。

make mrproper -Remove all generated files + config + various back files

make clean - Remove most generated files but keep the config and enough build support to build external modules

所以我们可以知道make mrproper和make clean的区别主要就是前者会删除所有的编译生成文件,而后者会保留配置文件.config,还有足够的扩展模块的支持。所以其实想清除编译残留数据的话执行make clean就可以了。实际上make mrproper一开始就会调用make clean的。

好了 下面我们要

配置内核选项

make menuconfig

这个一般保持默认即可,但是4.4.30需要勾选两个地方。
微信截图_20161115074355.png
微信截图_20161115074419.png

之后就是激动人心的编译内核时刻 我们先安装一个包

apt-get install libssl-dev

生成启动映像文件

make bzImage

然后去看个片的等吧 不过可能要多看几集

编译完成之后继续编译模块

make modules

注意我们一直是在/usr/src/linux-4.4.30这个文件夹下操作的,这次时间就更长了。我的配置一般i3而且是虚拟机,加上编译内核一共可能要几个小时,所以去睡觉吧

模块编译好之后的步骤都很快了,

安装模块

make modules_install

建立载入虚拟内存盘的编在内核中的根文件系统镜像

mkinitramfs 4.4.30 -o /boot/initrd-4.4.30.img

安装内核

make install

配置grub引导

update-grub2

重启系统大功告成

reboot

通过uname -a查看内核版本

微信截图_20161115074604.png

二、添加新的系统调用

分配系统调用号

先去查看一下系统的调用号使用到多少了

查找一下系统调用表

/usr/src/linux-4.4.30/arch/x86/entry/syscalls/syscall_64.tbl

我的版本使用到了325,所以我新的系统调用用326号。注意文件里要看属于x64的系统调用号。
然后我们修改/usr/include/asm-generic/unistd.h设置系统调用号
添加

  #define __NR_mysetnice 326
  __SYSYCALL(__NR_mysetnice,sys_mysetnice)

并且将后面的__NR_syscalls的号加1。

修改系统调用表

关联调用号与调用的服务例程地址 /usr/src/linux-4.4.30/arch/x86/entry/syscalls/syscall_64.tbl

326 64 mysetnice sys_mysetnice

编写服务例程

要为我们的服务写程序了
/usr/src/linux-4.4.30/kernel/sys.c
我这里是一个简单的实现读取进程的nice值和修改进程nice值的服务
参数flag为1时修改nice
参数为0时读取nice

SYSCALL_DEFINE3(mysetnice, pid_t, pid, int, flag, int, nicevalue){    
    int error = 0;
    struct task_struct *p;
    for(p = &init_task;(p = next_task(p)) != &init_task;){    
        if(p->pid == pid){
            if(flag == 0){
                printk("the process's nice = %d",task_nice(p));
            } else if(flag == 1){
                set_user_nice(p,nicevalue);
                printk("the process changed to %d",task_nice(p));
            } else {
                error = -EFAULT;
            }
        }
    }
    return error;
}

SYSCALL_DEFINE3的3即为参数个数,其他就不过多解释了,程序轻喷....
上面程序的主要思路是遍历进程的,简单粗暴,但是效率不高,下面提供一种稍微麻烦点,但是更直接高效的方式。
有很多函数涉及到了linux内核的api。可以在网上找一份,下载地址如下:http://download.csdn.net/detail/zhanglu231123/4584636

 SYSCALL_DEFINE3(mysetnice, pid_t, pid, int, flag, int, niceval)
{
    struct pid * kpid;
    struct task_struct * task;
    int nicebef;
    kpid = find_get_pid(pid);/* 返回pid */
    task = pid_task(kpid, PIDTYPE_PID);/* 返回task_struct */
    nicebef = task_nice(task);/* 返回进程当前nice值 */
    if(flag == 1)
    {
        set_user_nice(task, niceval);/* 修改进程nice值 */
        printk("修改前nice值:%d\t修改后nice值:%d\n", nicebef, niceval);
        return 0;
    }
    else if(flag == 0)
    {
        printk("该进程的nice值为%d\n", nicebef);
        return 0;
    }
    return EFAULT;
}

重新编译内核

不重新编译无法生效...等吧=。

三、编写用户态程序测试

下面就可以写程序测试啦
得引入下头文件,下面演示修改当前进程的程序,如果需要修改指定进程,可以先用top随便找一个正在运行的进程,修改后看下效果就可以了。test.c

#include<linux/unistd.h>
#include<sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#define __NR_mysetnice 326

int main(int argc, char *argv[])
{
     pid_t tid;
     int nicevalue;
     tid = getpid();
     syscall(326,tid,0,0);//read
     syscall(326,tid,1,20);//set
     syscall(326,tid,0,0);//read
     return 0;
}

gcc -o test test.c

然后可以选择一个进程调用我们的服务修改或者读取nice值啦,
由于使用了printk,

检查

我们要用dmesg查看输出信息。

查看指定进程的信息。

可以使用top -p pid
顺利完成~

四、总结

这次的经历告诉我:

  • 电脑配置真的很重要。

  • 其次要多和同学交流,如果遇到问题虚心请教,他们有问题的时候自然也会来问你,彼此帮助,氛围很好。

  • 既然编译后运行需要很久,可以利用gdb调试工具,暂停CPU,然后调试内核。操作比较繁琐和复杂,打算下次课再去求教老师,掌握了之后,会另外写一篇文章。
    下次的文章可能会是gdb如何使用,或者深入理解fork原理。

 作者:费德

少年费德的奇幻漂流

本博客如无特殊说明皆为原创,转载请注明来源:linux内核实现添加系统调用,实现修改和读取nice值

仅有一条评论

  1. 柏叶明 柏叶明

    太感谢了,非常简洁易懂
    托你的福我一下午就编好了~

添加新评论