编译Linux内核并添加系统调用


写在前面

今天无聊。。好像我一直都很无聊,想着用VS2019为Linux开发应用程序,过程比较顺利,安装完相应的组件直接建立解决方案就OK了。
然而我觉得用图形化的Linux来作为调试客户端有点占内存(这里用的是本地的虚拟机),所以我就去Ubuntu官网下载了server版,只有命令行窗口,内存分配了2G,暂时解决了我的问题
可是新的疑问产生了,Linux是最伟大的开源软件,可以很方便的得到它的源代码,那么要经过什么样的过程,这些源代码才会变成供我们使用的操作系统呢?
带着这个疑问,我打开了百度搜索,结果耗费了6个小时,不过好在编译成功了

运行环境

Oracle VM VirtualBox
Ubuntu20.04-Server
linux-5.12-rc7
如何在VM Box中安装Ubuntu Server还请充分利用网络资源

Let's Go!

传输内核
你可以直接使用wget命令获取Linux内核,当然这需要你的虚拟机联网,命令如下:
wget https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/snapshot/linux-5.12-rc7.tar.gz
介于国内和谐的网络环境,你可以使用迅雷下载,然后使用scp命令传输上去,命令如下:

#需要终端连接工具,这里推荐git bash,安装Windows版本的git就可以得到
#ljc545w是我所使用的用户名,后面的IP是虚拟机地址,请替换成你自己的内容
scp linux-5.12-rc7.tar.gz ljc545w@192.168.1.4:~/

解压缩

sudo tar xvzf linux-5.12-rc7.tar.gz -C /usr/src/

安装依赖

#执行该命令后可以解决大多数问题
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

配置文件

cd /usr/src/linux-5.12-rc7
#可以使用已有的配置文件,但编译时间会很久
sudo cp /boot/config-5.4.0-72-generic .config
#执行以下命令,在弹出的菜单里先选择save再exit
sudo make menuconfig
#或者使用默认配置,编译时间会大幅缩短
sudo make defconfig

一个错误
如果使用已有的配置文件,需要注释掉一条语句,不然编译过程会报错

sudo vim .config
#在倒数200行左右找到如下语句并注释掉,在开头加#即可
CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"

注意:在按i进入编辑模式前按shift+g可以快速跳到尾行

添加系统调用

修改sys.c

sudo vim /usr/src/linux-5.12-rc7/kernel/sys.c

在末尾贴入以下代码:

SYSCALL_DEFINE1(ljc545w,char,n)
{
        printk("ljc545w的系统调用!");
        if(n>='0'&&n<='9')return 0;
        if(n>='a'&&n<='z')return 1;
        else return 2;
}

修改syscalls.h

sudo vim /usr/src/linux-5.12-rc7/include/linux/syscalls.h
#在末尾添加
asmlinkage long sys_ljc545w(char n);

修改syscall_64.tbl

sudo vim /usr/src/linux-5.12-rc7/arch/x86/entry/syscalls/syscall_64.tbl

添加一个系统调用号,如图所示,443就是我添加的,注意不要与已有调用号重复了
添加系统调用号

开始编译

可以使用如下命令开始编译:
sudo make
或者使用多线程,n表示cpu核数*2,如果是两核就写j4:
sudo make -jn
如果你使用现有的配置文件,这会是一个漫长的过程...


two years later...
OK,现在你已经编译完成了,使用如下命令来安装吧~

#为内核安装模块
sudo make modules_install
#将内核安装到当前系统
sudo make install

这一步也许会很久,也许一下就好了

启用引导菜单

内核安装完毕会自动更新grub2配置文件,但是引导菜单默认是跳过的,现在我们启用它

sudo vim /etc/default/grub
#这一行要注释掉
GRUB_TIMEOUT_STYLE=hidden
#将等待时间设为10
GRUB_TIMEOUT=10
#更新配置文件
sudo update-grub2

然后使用sudo reboot命令重启虚拟机,就可以看到引导菜单了
引导菜单
选择Advanced options,点击Enter,可以看到下图:
内核列表
选择自己编译的内核,回车即可进入系统
文章写完我试了一下,好像Ubuntu那个选项就是刚编译的内核

校验系统调用

这里提供两种方案,第一种是查询关键字

cat /proc/kallsyms | grep ljc545w

或者写一个C程序,vim mycall.c,内容如下:

#include<stdio.h>
#include<unistd.h>
#include<linux/kernel.h>
#include<error.h>
#include<sys/syscall.h>
int main()
{
        char n;
        long int t;
        printf("Please Enter a symbol:");
        scanf("%c",&n);
        //这里用到了系统调用号
        t=syscall(443,n);
        printf("System call sys_ljc545w_syscall return %ld\n",t);
        if(t==0)
                printf("This is a number.\n");
        else if(t==1)
                printf("This is a small letter.\n");
        else
                printf("This is another notation.\n");
        return 0;
}

使用gcc编译
gcc -o mycall.out mycall.c
执行
./mycall.out
根据提示输入一个字符,即可看到系统调用的返回值即相应的判断结果:
返回结果
查看printk信息
在系统调用里面我们添加一行printk打印调用日志,这一行默认是不显示在命令行窗口的,可以使用dmesg来查看:
dmesg

写在后面

一下午踩了不少坑,主要是sys.c中的声明方式,跟网上多数教程提到的不太一样,所以编译过程中报错了,不过幸好,一个下午搞定,以后可以为Linux添加一些自己的想法了。