操作系统实验:添加系统调用修改主机名(hostname)

实验环境配置

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内核源码

作为自由软件,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身份运行。

1.分配系统调用号,修改系统调用表

vim ./arch/x86/entry/syscalls/syscall_64.tbl

下图,是我添加的335调用号,64和common不用深究,调用号,在末尾累加。

添加系统调用号

2.声明系统调用服务例程原型

Linux 系统调用服务例程的原型声明在文件 linux5.9.6/include/linux/syscalls.h 中,可在文件中添加如图 :

vim ./include/linux/syscalls.h

也可以打开vscode,打开syscalls.h文件直接修改
asmlinkage
和"sys_sethostname"除了名字其他一模一样。

3.实现系统调用服务例程

下面为新的系统调用 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;
}

重新编译内核

上面三个步骤已经完成添加一个新系统调用的所有工作,但是要让这个系统调用真正在内核中运行起来,还需要重新编译内核。

1、预先安装一些辅助工具包,如果没有编译过程中会出错:

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

2、清除残留的.config 和.o 文件

在开始完全重新编译之前,需要清除残留的.config 和.o 文件,后续如果编译过程中出现错误,再次开始完全重新编译之前也需要如此清理。方法是进入 linux-5.9.6 子目录,执行以下命令:

make mrproper

3、配置内核

make menuconfig

在出现的页面下方中选择"Save",一路回车,然后选择"Exit"退出。

4、编译内核,生成启动映像文件

内核配置完成后,执行 make 命令开始编译内核,如果编译成功,则生成 Linux 启动映像文件 bzImage(位于./arch/x86_64/boot/bzImage):

make

可使用 make -j2(双核 CPU)或 make -j4(4 核 CPU)来加快编译速度。编译过程中,可能会出现一些错误,通常都是因为缺少某个库,一般根据相应的错误提示,安装相应的包即可,然后重新编译。
我的虚拟机是4核的,所以我用如下命令:

make -j4

下面的所有指令都可添加"-j4" 来加快速度。

5、编译模块

make modules -j4

6、安装内核

安装模块:make modules_install
安装内核:make install

7、配置 grub 引导程序

update-grub2

该命令会自动修改 grub

8、重启系统

reboot -n

9、将使用新内核启动 linux,启动完成后进入终端查看内核版本

uname -a

编写用户态程序测试新系统调用

1、编写测试C程序验证上述添加的系统调用:

操作系统实验:添加系统调用修改主机名(hostname)_第1张图片
我把ubuntu改成了Harmony(鸿蒙)!

2、编译程序

我的C程序名是"test.c"

gcc -o test test.c

3、执行程序

./test

然后重新打开终端,就会发现主机名修改成功!

4、运行效果

操作系统实验:添加系统调用修改主机名(hostname)_第2张图片

还有一个不需要syscall也能改主机名的方法!!!

直接上代码

#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;
}

效果图
操作系统实验:添加系统调用修改主机名(hostname)_第3张图片

你可能感兴趣的:(Linux,内核,linux,ubuntu)