Ubuntu18.04.5,Linux内核:5.9.6,VMware Workstation Pro,内存:4G,CPU:4x1=4核,外存:128G。
ubuntu安装、内核源码下载及环境配置参考我的另一篇博客 虚拟机安装Ubuntu18.04
添加一个系统调用改变主机名称为自定义字符串
提示:参考/etc/hostname文件内容
其实就是把sys.c文件里的sethostname函数复制过来然后删除一个if语句
作为自由软件,Linux 内核版本不断更新,新内核会修订旧内核的 bug,并增加若干新特性,如支持更多的硬件、具备更好的系统管理能力、运行速度更快、更稳定等。实验中所用的是5.9.6版本内核。
将下载的新内核压缩文件复制到/home 或其他比较空闲的目录中,然后进入压缩文件所在子目录,打开终端,输入"sudo su",回车,输入密码,以root身份运行,然后分两步解压缩:
(1)xz -d linux-5.9.6.tar.xz
大概执行 1 分钟左右,中间没有任何信息显示。
(2)tar –xvf linux-5.9.6.tar
进入解压后的linux-5.9.6文件中,打开终端,输入"sudo su",回车,输入密码,以root身份运行。
vim ./arch/x86/entry/syscalls/syscall_64.tbl
下图,是我添加的335调用号,64和common不用深究,调用号,在末尾累加。
Linux 系统调用服务例程的原型声明在文件 linux5.9.6/include/linux/syscalls.h 中,可在文件中添加如图 :
vim ./include/linux/syscalls.h
也可以打开vscode,打开syscalls.h文件直接修改
和"sys_sethostname"除了名字其他一模一样。
下面为新的系统调用 magichostname 编写服务例程 sys_magichostname,通常添加在 sys.c 文件中(我放到了它的母体函数sethostname下面),其完整路径为:linux-5.9.6/kernel/sys.c:
vim ./kernel/sys.c
也可以在vscode中直接复制粘贴修改,更加方便,具体代码为:
SYSCALL_DEFINE2(magichostname, char __user *, name, int, len)
{
int errno;
char tmp[__NEW_UTS_LEN];
if (len < 0 || len > __NEW_UTS_LEN)
return -EINVAL;
errno = -EFAULT;
if (!copy_from_user(tmp, name, len)) {
struct new_utsname *u;
down_write(&uts_sem);
u = utsname();
memcpy(u->nodename, tmp, len);
memset(u->nodename + len, 0, sizeof(u->nodename) - len);
errno = 0;
uts_proc_notify(UTS_PROC_HOSTNAME);
up_write(&uts_sem);
}
return errno;
}
DEFINE2表示这个函数有两个参数,下面是sethostname函数的代码,能发现它们之间差的if语句在哪吗?
SYSCALL_DEFINE2(sethostname, char __user *, name, int, len)
{
int errno;
char tmp[__NEW_UTS_LEN]; //__NEW_UTS_LEN=64
if (!ns_capable(current->nsproxy->uts_ns->user_ns, CAP_SYS_ADMIN)) //首先检查当前进程是否拥有CAP_SYS_ADMIN的授权
return -EPERM;
if (len < 0 || len > __NEW_UTS_LEN)
return -EINVAL;
errno = -EFAULT;
if (!copy_from_user(tmp, name, len)) { //copy_from_user将name指向的字符串从用户空间拷贝到内核空间,失败返回没有被拷贝的字节数,成功返回0
struct new_utsname *u;
down_write(&uts_sem); //写者使用该函数来得到读写信号量sem,它会导致调用者睡眠,只能在进程上下文使用,用于获取Linux内核读写信号量中的写锁
u = utsname(); //获取当前内核名称和其它信息,成功执行时,返回0。失败返回-1,errno被设为EFAULT,表示buf无效。
memcpy(u->nodename, tmp, len); //从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中
memset(u->nodename + len, 0, sizeof(u->nodename) - len);
errno = 0;
uts_proc_notify(UTS_PROC_HOSTNAME); //使用UTS namespace隔离hostname
up_write(&uts_sem); //释放写锁
}
return errno;
}
上面三个步骤已经完成添加一个新系统调用的所有工作,但是要让这个系统调用真正在内核中运行起来,还需要重新编译内核。
apt-get install make
apt-get install libncurses5-dev
apt-get install libssl-dev
apt-get install bison
apt-get install flex
apt-get install pkg-config
在开始完全重新编译之前,需要清除残留的.config 和.o 文件,后续如果编译过程中出现错误,再次开始完全重新编译之前也需要如此清理。方法是进入 linux-5.9.6 子目录,执行以下命令:
make mrproper
make menuconfig
在出现的页面下方中选择"Save",一路回车,然后选择"Exit"退出。
内核配置完成后,执行 make 命令开始编译内核,如果编译成功,则生成 Linux 启动映像文件 bzImage(位于./arch/x86_64/boot/bzImage):
make
可使用 make -j2(双核 CPU)或 make -j4(4 核 CPU)来加快编译速度。编译过程中,可能会出现一些错误,通常都是因为缺少某个库,一般根据相应的错误提示,安装相应的包即可,然后重新编译。
我的虚拟机是4核的,所以我用如下命令:
make -j4
下面的所有指令都可添加"-j4" 来加快速度。
make modules -j4
安装模块:make modules_install
安装内核:make install
update-grub2
该命令会自动修改 grub
reboot -n
uname -a
我的C程序名是"test.c"
gcc -o test test.c
./test
然后重新打开终端,就会发现主机名修改成功!
直接上代码
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char magic_stack[STACK_SIZE];
char* const magic_args[] = {"/bin/bash", NULL};
int magic_main(void* arg){
printf("Accept!\n");
sethostname("Harmony", 7);
execv(magic_args[0], magic_args);
printf("Wrong Answer!\n");
return 1;
}
int main(int argc, char **argv){
printf("It's time to witness the miracle !\n"); //见证奇迹的时刻到了!
int magic_pid = clone(magic_main, magic_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
waitpid(magic_pid, NULL, 0);
return 0;
}