【Linux】Linux添加系统调用以及内核编译过程

来源:互联网 发布:timbuk2什么档次知乎 编辑:程序博客网 时间:2024/05/29 14:40

在想要替换原有系统内核或者需要在原来的系统中添加一些系统调用的时候就会涉及到Linux内核的编译。但是内核编译虽然步骤简单,但是需要注意的东西还是太多了。首先一点就是由于Linux的开源性导致的版本问题,并不是所有的内核都是可以编译并安装到当前系统的。其次编译过程中所需要做的一些个性化选择,当然这需要对内核了解的比较透彻,对系统各个模块也比较熟悉的时候可以定制化编译自己的内核。最后就是一些细节,需要在编译试验中不断的学习啦。
这篇博文就算是对自己编译了好几遍内核痛苦过程的一个总结和记录。编译的初衷就是在内核中添加一个简单的四则运算的系统调用,然后在用户程序中调用这个系统调用。需求如此简单,但是对于一个小白而言还是走了很多弯路。

首先说一下最重要的系统和内核版本问题:
编译的是Ubuntu的64位版本,至于是用14LTS或者12LTS无所谓,关键是要注意系统位数。因为32位和64位添加系统调用修改的内核文件是不一样的,所以这里仅仅针对64位版本而言。原来的内核是2.6.32.38,在官网下载内核版本是2.6.32.65 。现在的目标就是先修改 2.6.32.65内核的kernel文件和相关文件,然后将其编译安装替换原来的 2.6.32.38内核。

编译过程中还是使用虚拟机比较好,原因就不用说了

步骤如下:

添加系统调用

添加系统调用一共需要修改如下的三个内核文件:
1. /kernel/sys.c :用于添加系统调用函数

    SYSCALL_DEFINE4(my_oper, int *, result, int, num1, int, num2, char *, op)    {        int errno = 3; // 自定义的错误码,返回3代表操作成功,返回-3代表操作失败        int cur_result = 0;        char cur_op ;        // 由于用户空间和系统空间并不相同        // 所以用户空间的指针在系统空间并不能用        // 因此要使用另外的系统调用copy_from_user()        copy_from_user(&cur_op, op, 1);         switch(cur_op){            case '+':                cur_result = num1 + num2;                break;            case '-':                cur_result = num1 - num2;                break;            case '*':                cur_result = num1 * num2;                break;            case '/':                if(num2 == 0){                    errno = -3;                    cur_result = 0;                }                else                    cur_result = num1 / num2;                break;        }        copy_to_user(result, &cur_result, sizeof(cur_result));        return errno;    }

首先可以先看一下系统调用函数命名规范

其次,该函数需要注意的就是用户空间指针和系统空间指针的区别,平时自己调用自定义函数时,指针都是在用户空间中,所以不会出错。然而现在将用户空间的指针拿到了系统空间中去,就相当于是一个指向随机地址的指针,当然会出错。就好比二次元中的东西拿到现实世界的时候并不好用,通常还会出错。
最后的结果也是需要将其从系统空间转到用户空间。

  1. /arch/x86/kernel/syscall_table_32.S : 用于在系统函数表添加函数表项。是按顺序递增的,根据自己系统的实际数目即可

这里是第337个

  1. /arch/x86/include/asm/unistd_64.h : 添加系统调用号(就是第二步中添加的函数在函数表项中对应的编号)。该文件对应有两个,一个是unistd_64.h,一个是unistd_32.h,分别针对于64位和32位系统的系统调用号。

将上述的三个文件修改完成之后就可以对内核进行编译了,内核编译的资料比较多,鸟哥的书上也有专门讲过编译过程。以为本身自己对内核了解也不是很透彻,所使用的是傻瓜式的编译方式。
之所以说是傻瓜式的,是因为编译选项使用的是系统原有的默认配置信息

两种使用默认配置进行编译的方法:

  1. 可以将/boot下的config拷贝到源码根目录,命名为.config,以使用针对于本机的配置进行编译
  2. 也可以直接执行命令make menuconfig ,该命令直接根据本机的实际情况生成.config文件,自行编译不需要用户去设置。

编译添加系统调用后的内核

编译过程如下:
1. su root 切换到root权限
2. 执行make
3. 执行make modules 编译模块
4. 执行make modules-install
5. 执行make install 安装新内核

修改启动项

编译完成之后需要修改内核的启动项信息(因为此处编译之后安装将原有内核替换掉了,所以默认是进入新内核,所以此处修改启动信息,保证自己可以选择进入哪个内核),确定系统启动的时候进入的是哪个版本的内核:

修改grub文件/etc/default/grub;
注释掉如下语句:(在前面添加’#’即可)
GRUB_HIDDEN_TIMEOUT=0
注释掉之后在系统重启的时候可以选择进入的内核

由于 /boot/grub/grub.cfg文件是自动生成的,通过修改该文件选择不同的内核进行启动时无法实现的

更新启动文件:
执行update-grub就可以更新grub.cfg文件

重启进入新内核:
reboot 即可

使用 uname –a 就可以查看自己当前使用的内核是新的还是旧的

使用自己添加的系统调用

在用户空间调用自己添加的系统调用

    #include <unistd.h>    #include <sys/syscall.h>    #define __NR_my_oper 337    syscall(__NR_my_oper, result, num1, num2, op)

其中337 是自己添加的系统调用的调用号,syscall()是调用系统调用的方式,书上也会讲使用_syscallx宏来实现调用,但是2.6.20之后的内核调用并没有成功,所以就使用syscall的方式,顺利达到了目标。

0 0