抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

今日、海を見た。もう怖くない

原标题:Linux操作系统原理实验 - 实验报告

补档时间:2020-02-12 19:04:35

因为原稿时间久远,相关的图片素材可能已经遗失。

实验一: 安装Arch Linux并配置图形界面

一、实验描述

在虚拟机上安装可以日常使用的Arch Linux,并为之配置图形界面。

二、实验设计

  1. 安装前准备:
    启动Hyper-V虚拟机、下载Arch引导镜像、创建虚拟机并且从镜像启动

  2. 安装Arch Linux:
    检测网络、分区并挂载、更改镜像源、使用pacman安装Arch、配置Arch、解挂载并重启

  3. 安装图形界面:
    登录Arch、安装图形界面软件包

  4. 安装常用软件包
    安装yaourt、安装WMwarm-tools

三、实验原理

  1. pacman包管理器

    和Ubuntu不一样,Arch Linux使用的包管理程序是pacman。使用pacman安装一个新的包的命令是:

    1
    pacman -S <包名>

    和Ubuntu的apt一样,pacman也需要更新它的信息。更新pacman的软件包的数据库可以使用以下命令的任意一条:

    1
    2
    3
    pacman -Syy
    pacman -Syu
    pacman -Sy

    除此之外,pacman还有以下的常用命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 移除软件包
    pacman -R <包名>

    # 移除软件包以及依赖项
    pacman -Rs <包名>

    # 查看可用的包R
    pacman -Sl | grep <关键词>

    # 列出已安装的包
    pacman -Q <包名>
  2. UEFI模式

    对于一台使用了“快速创建”的虚拟机或者物理机,我们首先需要知道它引导启动的模式。在archiso的终端执行命令efivar -l,如果输出正确的列出了UEFI变量,则是UEFI模式启动。

    如果通过UEFI模式,那么需要将分区挂载到/boot/EFI目录,而不是BIOS模式下挂载到/boot目录。

  3. 窗口环境的软件包

    在配置图形界面过程中安装的众多软件包中,不同的软件包有不同的作用:

    • xorg server: 众多窗口环境的软件包的基础
    • sddm: 提供了图形化的用户登录界面
    • xfce4: 常用的Linux桌面环境
    • plasma: 基于KDE的更加美观的桌面环境

    安装以上所有的软件包之后,在用户的登陆页面将出现选项框,可以选择进入不同的桌面环境。

四、实验步骤

安装前准备

因为Windows 10 1903 对旧版的VMware Workstation存在兼容性问题,为了方便,所以本次实验采用基于Windows10自带的Hyper-V的虚拟机。Hyper-V功能默认是关闭的。在控制面板中选择“程序或功能->添加/删除Windows功能”中选择启动Hyper-V,随后系统将会下载相关的数据并且重新启动,Hyper-V功能将启用。

进入官网或者镜像站下载Arch Linux的镜像,这里给出浙江大学镜像站的下载地址,进入后选择archlinux-2019.10.01-x86_64.iso下载即可。将下载的镜像文件作为新建的Hyper-V虚拟机中虚拟光驱引入,完成虚拟机创建。

搜索栏中输入“Hyper-V管理器”,可以打开它。在右侧状态栏中选择新建,手动配置新虚拟机的信息。这次实验我使用的是第一代(基于BIOS)的虚拟机,你也可以使用第二代(基于UEFI)虚拟机,但是具体操作将会与本实验报告描述内容存在出入。使用刚才下载的光盘镜像文件作为虚拟光驱,创建虚拟机并启动。

安装Arch Linux

启动刚刚新建的虚拟机,虚拟机将会自动加载的光驱并且进入引导界面。选择Boot Arch Linux(x86_64)选项启动,进入archiso的tyy控制台界面。此时已经可以使用一些常用的Linux命令操作了,安装过程本身也是使用Linux命令完成的。在控制台中输入ping baidu.com,可以检测网络状态——如果ping通,则网络连接正常。否则,可能需要退出虚拟机检查Hyper-V的虚拟网卡/交换器的设置,这可以参考Microsoft Docs。

屏幕截图_1_.jpg

使用cfdisk命令打开cfdisk为虚拟机进行分区。在这次实验中,我将硬盘划分为四个区域,并将它们分别挂载为不同的目录:

分区名大小文件系统挂载目录
/dev/sda132GBext4/mnt
/dev/sda21Gvfat/mnt/boot
/dev/sda34GSWAP分区
/dev/sda4余下所有空间ext4/mnt/home

在cfdisk中,上下方向键对存储块进行选择,左右键对命令选择,回车键开始操作。完成分区操作之后要执行[Write]命令保存分区信息后方可退出。可以使用lsblk命令展示分区的状态。完成分区之后,需要对分区进行挂载。挂载的命令如下:

1
2
3
4
5
6
7
8
9
10
11
mkfs.ext4 /dev/sda1
mkfs.vfat -F32 /dev/sda2
mkswap /dev/sda3
mkfs.ext4 /dev/sda4

mount /dev/sda1 /mnt
mkdir /mnt/boot
mount /dev/sda2 /mnt/boot
swapon /dev/sda3
mkdir /mnt/home
mount /dev/sda4 /mnt/home

这一步完成之后,再在终端中输入命令genfstab -U /mnt >> /mnt/etc/fstab生成文件系统信息。这一步是必要的,因为生成的文件系统信息会被用来在后面的步骤生成启动引导文件。

屏幕截图_2_.jpg

Arch的安装是使用pacman安装的,他需要接收一些必要的数据才可以安装。而国内直接连接Arch官网的速度是非常的不理想的,所以需要更换镜像站,使用国内的镜像站下载必要的文件。首先,可以在控制台中输入vi /etc/pacman.d/mirrorlist以使用vim打开pacman的镜像文件,然后在文件开头加入你喜欢的镜像站,比如:

1
2
Server = http://mirrors.163.com/archlinux/$repo/os/$arch
Server = http://mirrors.aliyun.com/archlinux/$repo/os/$arch

添加后控制台执行pacman -Syy更新镜像源,就完成了镜像源的替换。

在终端执行pacstrap -i /mnt base base-devel linux linux-firmware命令,使用缺省的默认设置(一路回车)就可以安装Arch了。特别注意的是,因为新版的Arch基本包已经不再包含Linux内核了,所以安装过程中一定要安装linux和linux-firmware两个包。否则之后创建引导的时候会因为找不到系统而安装失败。

屏幕截图_4_.jpg

此时,Arch Linux已经安装到了虚拟机的硬盘中,为了配置刚安装的Arch Linux,我们需要从光盘的终端中切换到安装好的系统的终端中去。实现这件事的命令是arch-chroot /mnt /bin/bash。archiso-tyy执行这个命令之后的行首会发生变化,很容易观察到。在新的系统的终端中,我们可以:

  • 配置语言区域
    使用vi etc/locale.gen打开文件,删除en_US.UTF-8前的#号,保存之后在终端运行locale-gen.

屏幕截图_8_.jpg

  • 配置时区

    1
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  • 安装grub并生成引导文件

    1
    2
    3
    pacman -S grub 
    grub-install --force /dev/sda
    grub-mkconfig -o /boot/grub/grub.cfg
  • 安装dhcpcd并配置网络
    对于有线网络,因为Base包里已经不包括联网所需的程序,所以需要下载dhcp客户端之后才能启动服务。

    1
    2
    pacman -S dhcpcd
    systemctl enable dhcpcd.service
  • 设置主机名

    1
    echo shiohalinux > /etc/hostname
  • 将主机名增加到/etc/hosts
    使用vim打开这个文件,将主机名添加到这个文件的后面,大概就像这样

    1
    2
    3
    127.0.0.1    localhost.localdomain    localhost
    ::1 localhost.localdomain localhost
    127.0.1.1 shirohalinux.localdomain shirohalinux
  • 设置root的密码
    终端中输入passwd命令后输入两次要创建的密码就可以了。

  • 创建登录用户并给予它sudo权限
    终端中输入useradd -m -G wheel -s /bin/bash shiroha就可以创建名为shiroha的用户。这里的命令最重要的是-G:它指定了用户要加入的附加组列表;现在我们将用户加到wheel组中,之后可以在接下来的操作中给予这个组执行sudo命令的权限,可以方便使用。(当然,你也可以在新用户中使用su - root切换到root用户执行sudo命令)使用passwd shiroha命令可以为新用户shiroha创建密码。可以通过修改/etc/sudoers文件为普通用户提权,但是一般使用命令visudo修改。使用该命令打开sudoers之后,删除wheel组前的#就可以了。特别说明:在使用visudo之前,请确保系统中已经安装了vim。

完成了对新系统的配置之后,就可以退出光驱启动而直接从硬盘启动Arch Linux了。先在当前的终端中执行exit退回tyy终端,再使用umount -R /mnt解挂载光驱,并执行reboot命令重启。特别说明:因为Hyper-V的解挂载并不能弹出虚拟光驱,所以不需要执行解挂载的过程,使用shutdown关机之后再虚拟机管理页面弹出光驱,再手动启动虚拟机就可以了。

屏幕截图_14_.jpg

安装图形界面

如果安装Arch Linux成功完成,那么可通过控制台登录到Arch。首先可以使用root登录到Arch,输入用户名root和正确的密码即可。

图形界面是Arch Linux中额外的软件包提供的功能。所以安装图形界面就是安装和这个图形界面相关的软件包就可以了。与这个相关的软件包主要包括xorg,sddm,xfce4,plasma,ifconfig.当然,这一步骤还会包括安装一些使用软件。

  • 安装脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    pacman -S xorg
    pacman -S xterm
    pacman -S xorg-xinit
    pacman -S sddm
    pacman -S xfce4
    pacman -S xfce4-goodies
    pacman -S plasma
    pacman -S kde-applications

    所有的软件包都是使用pacman命令安装,特别要注意的是plasma安装前必须要确保xorg已经安装到虚拟机中;此外,因为plasma桌面环境是基于KDE桌面环境的,所以需要安装kde环境常用的软件包。

  • 配置脚本:

    1
    2
    sddm --example-config > /etc/sddm.conf
    systemctl enable sddm.service

    这步主要是配置sddm:首先生成ssdm的配置文件,随后将ssdm服务增加开机启动项。ssdm主要提供了用户登录界面。

屏幕截图_21_.jpg

ssdm的登陆只能使用普通用户登录。这意味着如果你在之前的步骤没有增加一个普通用户,那么你需要使用useradd命令为电脑增加一个用户,并使用passwd为其设置密码。

安装常用软件包

需要安装的一些常用软件包,主要是ifconfig,ssh,zsh,git以及wget.安装这些所有的软件包的脚本如下:

1
2
3
4
5
6
7
8
9
10
pacman -S net-tools dnsutils inetutils iproute2
pacman -Syy openssh
systemctl start sshd
ps -e | grep sshd
systemctl enable sshd.service
pacman -S zsh
pacman -S git
chsh -s $(which zsh)
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
pacman -S wget

虽然pacman用作包管理已经极其方便,但是他还是缺少了一些软件包。我们可以通过安装包管理器yaourt来解决pacman软件包不全的问题。安装yaourt可以这么做:

  • /etc/pacman.conf文件末尾处增加镜像源:
    1
    2
    [archlinuxcn]  
    Server=https://mirrors.ustc.edu.cn/archlinuxcn/$arch
  • 更新镜像源并安装yaourt:
    1
    2
    3
    pacman -Sy
    pacman -S yaourt
    pacman -S archlinuxcn-keyring
  • 使用yaourt安装软件包:
    比如安装Chrome可以使用命令yaourt google-chrome

如果使用的虚拟机是VMWare而不是Hyper-V,可以为Arch安装vmware提供的VMwarm-tools。安装过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 通过虚拟机软件挂载VMwarm-tools

# 挂载安装程序
mount /dev/cdrom /mnt

# 将工具解压
tar xf /mnt/VMwareTools*.tar.gz -C /home/zhaozhao/Desktop
cd /etc
mkdir init.d
for i in {0,1,2,3,4,5,6}; do mkdir rc$i.d; done

# 找到解压文件存放的位置
cd /home/zhaozhao/Desktop/vmware-distrid

# 执行安装脚本
./vmware-install.pl

# 重启计算机
reboot

这个程序提供了将Windows文件夹挂载到Linux的功能,可以方便的在虚拟机和物理机的操作系统之间传输数据。如果虚拟机软件支持这个软件包,建议安装一下。但是似乎Hyper-V并不支持这个包。

五、实验结果

安装Linux:重新启动后,虚拟机画面上出现了grub的加载界面,其中的首选项是+Arch Linux。选择该项启动后,进入了Arch Linux的登陆终端而不是archiso的tyy终端,输入设置的用户以及对应的密码可以进入终端,安装完成。

安装图形界面:此步完成后,启动虚拟机,待看到了服务启动的终端输出之后,将会看到sddm提供的用户登陆界面。输入正确的用户密码后会看到plasma桌面环境的加载画面,随后进入plasma桌面环境。


实验二: 编译并安装Linux内核

一、实验描述

在实验一中安装的Arch Linux虚拟机中编译一个从Arch官方网站上下载的内核,并且将这个内核安装到虚拟机中。

二、实验设计

  1. 下载、编译、安装
    进入官网下载内核、编译内核、安装内核模块、将编译的模块复制到/boot目录

  2. 制作初始化内存盘
    制作初始化内存盘、复制System.map

  3. 更新引导程序
    更新grub配置、重启验证结果

三、实验原理

  1. 编译内核的menuconfig

    编译内核需要一个配置文件,而获得这个配置文件有很多种方法。其中一种方法是通过make menuconfig命令进入一个图形化的配置页面进行对内核的自定义配置,然后生成对应的.config文件。接下来简要的介绍一下键入这个命令后系统的执行的流程:

    屏幕截图_30_.jpg

    首先,这个过程涉及到了以下的文件:

    Linux内核根目录下的/scripts文件夹
    arch/$ARCH/Kconfig文件、各层目录下的Kconfig文件
    Linux内核根目录下的makefile文件、各层目录下的makefile文件
    Linux内核根目录下的的.config文件、arm/$ARCH/.config文件
    Linux内核根目录下的include/generated/autoconf.h文件

    首先系统读取arch/$ARCH/Kconfig文件,使用/scripts下的绘制图形相关的文件生成图形化的config界面。环境变量$ARCH是定义在根目录的makefile文件中的。

    arch/$ARCH/configs目录下存储了默认的配置。内核会默认读取根目录下的.config文件作为默认配置。但是为了和内核完全匹配,往往需要对这个文件进行修改。

    在图形化的配置界面调整了不同的选项之后,最后保存退出时Linux内核会把新的选项新到.config中。此时将.config重命名为其它文件保存起来(当执行make distclean时系统会把.config文件删除)就可以在需要的时候还原这个配置了。

  2. make命令

    make命令被执行时,它会扫描当前目录下makefile文件找到目标以及其依赖。如果这些依赖自身也是目标,继续为这些依赖扫描Makefile文件并且建立其依赖关系,然后编译它们。一旦主依赖编译之后,然后就编译主目标。如果之后某个源文件发生了修改,再次执行make命令,它将只编译与该源文件相关的目标文件。

    make指令的命令参量:

    -f:指定“makefile”文件;
    -i:忽略命令执行返回的出错信息;
    -s:沉默模式,在执行之前不输出相应的命令行信息;
    -r:禁止使用build-in规则;
    -n:非执行模式,输出所有执行命令,但并不执行;
    -t:更新目标文件;
    -q:make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息;
    -p:输出所有宏定义和目标文件描述;
    -d:Debug模式,输出有关文件和检测时间的详细信息。
    -C dir:在读取makefile 之前改变到指定的目录dir;
    -I dir:当包含其他makefile文件时,利用该选项指定搜索目录;
    -h:help文挡,显示所有的make选项;
    -w:在处理makefile之前和之后,都显示工作目录。
    -j:指定编译的线程数,线程数越大理论越快。

    make install是编译后的安装过程,它从makefile中读取信息,并且将软件包安装到指定位置。可以使用configure生成makefile文件。往往需要sudo权限来向系统写入数据。有的软件包在安装之前可能需要先运行 make checkmake test来进行一些测试。

    make clean命令在编译前会清除之前编译的可执行文件及配置文件。而make distclean命令则会在编译前删除所有之前的编译生成的文件。使用这两项命令会使得编译过程延长,但是在编译内核的时候可以保证生成的内核树的绝对清洁。

四、实验步骤

安装前准备

在开始之前,我们需要先查看一下虚拟机上的Arch Linux的当前的内核信息。查看内核信息可以通过screenfetch命令。这个软件包不是Arch Linux的基本包中包含的内容,所以再使用之前需要先安装:

1
pacman -S screenfetch

安装完成后就可以使用screenfetch命令查看内核信息了。截图保存以与应用新自定义内核后形成对比。

屏幕截图_33_.jpg

首先,可以访问官网选择想要安装的内核版本进行下载。但是由于国内访问官网速度较慢,也可以通过国内的开源镜像站下载,比如清华大学镜像站。选择需要下载的镜像之后使用wget命令下载。Arch的基本包是没有wget的,所以如果之前没有安装wget则需要通过pacman安装。这里我选择了Linux 5.3版本的内核,执行的命令如下:

1
2
3
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.3.tar.xz
xz -d linux-5.3.tar.xz
tar -xvf linux-5.3.tar

下载完成后,使用xz命令和tar命令解压下载的内核包。解压后使用cd命令进入解压得到的目录下。

屏幕截图_28_.jpg

编译内核

进入目录后就可以开始编译了,编译使用的命令是make。它有很多的模式可以选择,比如make menuconfig命令会进入由Kconfig和scripts构建的图形化的配置页面,完成配置后会生成一个.config文件。这里我使用的是缺省配置:

1
zcat /proc/config.gz > .config

复制内核配置文件之后,还需要通过修改得到的config文件的CONFIG_LOCALVERSION的值来修改内核版本,以避免将要编译的内核文件覆盖当前内核文件。

得到了.config文件之后就可以开始编译了。编译的命令是make -j<线程数>。可以根据虚拟机的配置修改这里线程数的值。因为内核的编译需要一段较长的时间,合理的线程数可以增加编译的速度。我使用的是make -j2

安装内核并验证

完成了内核的编译之后就可以开始安装内核了。安装内核主要包括这些步骤:安装内核模块->复制内核文件->制作initramfs镜像->复制System.map->生成新的启动引导。执行的命令脚本如下:

  • 安装内核模块:

    1
    sudo make modules_install 

    这条命令会将编译好的模块安装到主目录/lib/modules下。这样,会使得这些模块独立于虚拟机原有内核的模块。

  • 复制内核文件:

    1
    2
    3
    4
    5
    # 对于32位(i686)内核:
    sudo cp -v arch/x86/boot/bzImage /boot/vmlinuz-linux53

    # 对于64位(x86_64)内核:
    sudo cp -v arch/x86_64/boot/bzImage /boot/vmlinuz-linux53

    将内核编译完成生成的bzImage(较大的压缩的内核映像,使用gzip压缩)文件复制到/boot目录下。

  • 制作initramfs(初始内存盘)镜像:
    复制并且修改mkinitcpio(一个创建initramfs的脚本)preset就可以通过官方内核一样的方式生成自定义内核的initramfs镜像。复制之后需要使用vim修改这个文件。

    1
    2
    sudo cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux53.preset
    sudo vi /etc/mkinitcpio.d/linux53.preset

    打开linux53.preset文件之后,修改部分字段使得它与新的自定义内核所匹配。需要修改的字段如下:

    1
    2
    3
    4
    5
    # /etc/mkinitcpio.d/linux53.preset

    ALL_kver="/boot/vmlinuz-linux53"
    default_image="/boot/initramfs-linux53.img"
    fallback_image="/boot/initramfs-linux53-fallback.img"

    修改并保存之后执行sudo mkinitcpio -p linux53就可以使用官方内核生成的方式生成自定义内核的initramfs镜像。

  • 复制System.map
    如果虚拟机的/boot挂载到的分区的文件系统是ext4格式。就需要将解压目录下的System.map文件复制到/boot中,并且创建/boot/System.map,将新建的System.map软链接到复制到其中的System.map中。需要执行的命令如下:

    1
    2
    3
    sudo touch /boot/System.map
    sudo cp System.map /boot/System.map-MyKernel
    sudo ln -sf /boot/System.map-Mykernel /boot/System.map

    但是由于本次使用的虚拟机的boot分区挂载的分区是vfat文件系统的,无需也不必创建软链接。

  • 更新grub引导的配置信息
    使用命令grub-mkconfig -o /boot/grub/grub.cfg可以生成grub默认的配置信息。它会自动地将刚添加的内核增加到启动配置中。在Ubuntu这种系统中,这个命令被包装成了update-grub或其他形式。

屏幕截图_37_.jpg

随后使用reboot命令重新启动虚拟机,使用screenfetch命令查看当前的系统信息,就可以看到系统的内核发生了变化。这样内核的编译和安装就完成了。

五、实验结果

实验前和试验后,对Arch Linux虚拟机使用screenfetch命令获得的内核信息不相同,内核信息变成了安装的内核的版本信息,说明用户的自定义内核已经成功的编译并且安装。


实验三: 为Linux内核增加系统调用

一、实验描述

在实验一的Arch Linux虚拟机的基础上,在Linux内核中增加一个系统调用,并对这个修改生成patch。将这个patch应用到一个Linux内核中,编译并安装这个自定义内核,验证对内核增加的系统调用是否已经成功的应用到操作系统。

二、实验设计

  1. 修改内核源码
    向内核中增加一个函数、将函数注册为系统调用

  2. 生成补丁并重新编译内核
    使用diff生成补丁、安装patch补丁、编译并安装内核

  3. 测试补丁是否成功patch
    编写调用自定义系统调用的源文件、执行源文件观察结果

三、实验原理

  1. patch
    Patch有两种常用意思:

    Patch文件

    也就是补丁文件。在Linux社区中,如果某人对某一个版本的Linux源码做出了一定的修改以解决某些问题,并且希望把它分享出去,他就可以为代码生成一个patch文件供社区使用。往往,patch文件的目的是用于修正已知错误或者作为一种调试手段调试代码。一般来说,patch文件可以使用diff指令比较原有代码和修改过的代码生成。

    Patch指令

    是Linux中的一个指令。可以用于将patch文件的修改应用到一个代码中。并且在此基础上可以提供一些实用的功能:比如消除父目录,检测对象代码是否已经安装过此补丁等。关于patch命令的详细使用方法可以使用man命令或者求助Google。

    屏幕截图_50_.jpg

    本次实验需要生成一个自定义的系统调用的patch文件,并且将这个patch文件应用到内核中。生成patch文件可以使用diff指令:

    1
    diff -Naur kernel/ kernel_new/ > new.patch

    指令中,kernel/是修改前的内核源码所在的文件夹,kernel_new/目录是修改过的内核源代码所在的文件夹。这条指令将会比较两个内核的源码的差别并且根据对比结果生成"new.patch"文件。

    如果要将这个patch文件应用到一个未修改的内核,可以使用patch命令将patch文件中记录的改动应用到内核源码中:

    1
    2
    3
    cp -v new.patch kernel/
    cd kernel
    patch -p1 < new.patch

    首先将patch文件放在将要打补丁的源码目录下,再使用patch命令就可以了。由于patch文件中记录了基于原代码所在目录的名字(所在目录的绝对地址),可能和当前打补丁的源码所在目录不匹配。比如当向Linux-5.3.x打Linux-5.3的补丁,就需要使用使用-p1参数忽视文件名目录的第一个分量。

    官方网站也提供了Patch包下载。这些Patch包是用于大版本内的小版本更新的。使用时,只需要将对应的Patch文件patch到对应版本的内核源码文件中,编译安装patch后的内核即可。

  2. 系统调用
    增加最简单的系统调用主要分为增加新函数->增加新声明->将新调用增加到系统调用注册表三个步骤。

    增加新的系统调用函数:比如可以向内核缓存区打印一条消息。可以使用printk函数实现这个功能。打印到内核缓存区的消息不会直接输出到控制台中,需要使用dmesg -c命令清除所有缓冲区日志并且查看内核缓存区信息。printk函数打印的信息将会这里显示。

    增加系统调用的声明:不同的Linux发行版所需要增加声明的头文件地址不同。大体是在内核文件夹下的include/目录下的unistd.h和syscalls.h文件中。对于Arch Linux,需要修改的头文件的目录是include/uapi/asm-generic/unistd.hinclude/linux/syscalls.h。可以在这个文件的对应位置增加在系统源文件(一般是sys.c)中增加的系统调用函数的声明。

    注册新系统调用:不同的Linux发行版OS的系统调用表所在的位置不同,对于Arch Linux而言,系统调用注册表的位置在arch/x86/entry/syscalls目录下。它包含了32位系统和64位系统的注册表,分别是syscall_32.tblsyscall_64.tbl

  3. 内核编程相关
    dmesg 命令:

    dmesg命令在设备故障诊断中有重要的用途。它可以用于检测硬件连接或断开的信息,可以查看内核缓存区的信息,可以使用Linux管道规定它的输出范围获得需要的数据:比如搜索和特定硬件相关的消息等。关于这个命令的使用可以参考man或者Google。以下只介绍基本的一些使用方法。

    • 列出所有检测到的硬件:

      比如要显示所有被内核检测到的硬盘设备,可以使用grep参量搜索sda关键词。最后执行的命令是:dmesg | grep sda。此外,grep命令参量可以附加-i选项表示忽略搜索字符串的大小写。

    • 输出指定行数的日志:

      可以通过跟随head或者tail命令参量来控制显示的日志范围。比如需要显示前20行日志,可以使用命令dmesg | head -20

    • 清空dmesg缓冲区日志

      可以使用命令dmesg -c来清空dmesg的日志。该命令会清空dmesg环形缓冲区中的日志。但是依然可以查看存储在‘/var/log/dmesg’文件中的日志。此时连接任何的设备都会产生dmesg日志输出。

    asmlinkage修饰符:

    asmlinkage的定义:

    位于/usr/include/asm/linkage.h中,定义内容如下:

    1
    >#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

    __attribute__是关键字,是gcc的C语言扩展,regparm(0)表示不从寄存器传递参数。如果是__attribute__((regparm(3))),那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数。

    这主要涉及了函数传参的方式是否通过寄存器。asmlinkage表示这些函数通过堆栈而不是通过寄存器传递参数。gcc编译器在汇编过程中调用C语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器。

    一般来说,内核中的函数都是通过asmlinkage限定使用堆栈传递参数的。我们向内核源文件中增加的系统调用函数也需要使用asmlinkage修饰。

四、实验步骤

准备工作

将从官网上下载的一个Linux内核源码解压,并将解压得到的文件夹复制一份。一份作为原版源码,对另一份进行修改。这里我使用实验二中使用的Linux内核作为基础。

增加系统调用

进入待修改的内核源码根目录,之后所有的相对路径都建立在内核源代码根目录的基础上。使用vim打开将要增加系统调用的实现的源文件kernel/sys.c。这里我们要增加的是最简单的系统调用,即在内核缓冲区打印消息,所以在它的末尾增加如下函数:

1
2
3
4
5
6
asmlinkage void sys_shirohashow(void)
{
printk("Shiroha do your best!");
printk("\n@Edit by Shiroha on 2019-11-15.");
return 0;
}

这样,我们就增加了一个系统调用函数sys_shirohashow的实现。

屏幕截图_51_.jpg

增加完实现之后,我们还需要为这个函数增加声明。增加声明主要需要修改两个文件。首先使用vim打开include/uapi/asm-generic/unistd.h,在这个文件的末尾处增加宏:

1
2
#define __NR_shirohashow 765
__SYSCALL(__NR_shirohashow,sys_shirohashow)

这里的765是系统调用的编号。注意不是所有的数字都可以使用,可以在设置这个数字之前前往系统调用注册表(find syscall_64.tbl寻找这个文件并使用vi或者nano查看确认系统调用编号的使用情况),然后使用没有被占用的系统调用编号。

屏幕截图_52_.jpg

增加宏之后,还需要再使用命令vi include/linux/syscalls.h打开syscalls头文件,将函数的声明asmlinkage void sys_shirohashow(void);增加到该头文件中/* kernel/sys.c */的部分中。

注册系统调用

完成了系统调用的声明和实现后,还需要将系统调用注册到注册表中,才可以使得它可以被正确的系统调用编号调用。系统调用注册表文件储存在arch/x86/entry/syscalls目录下。32位系统是syscall_32.tbl,64位系统是syscall_64.tbl。可以根据需要修改对应的注册表。这里以修改64位注册表为例,将在syscall_64.tbl的第435号系统调用后增加以下内容:

1
765     common  shirohashow         sys_shirohashow

保存退出后,对内核的源文件的修改就全部完成了。

生成补丁并patch

完成上述步骤中所有的操作后,就可以生成patch补丁了。可以通过diff命令对比原内核代码和修改后的内核代码得到补丁文件,执行命令:

1
diff -Naur ../../linux-5.3/kernel/ kernel/ > new.patch

可以将生成的patch文件应用到原内核源码中,也可以直接编译修改过的内核源码。(本质上,对原内核源码运行patch命令打得到的补丁文件变为修改过后的版本)打补丁的命令是:

1
2
3
cp -v new.patch ../../linux-5.3/kernel/new.patch
cd ../../linux-5.3/kernel/
patch -p1 < new.patch

安装patch之后就可以编译安装内核了。为了得到纯净的内核树,推荐编译过程中使用make clean && make mrproper命令,但是这样的编译速度较慢,耗时较多。

屏幕截图_56_.jpg

编译完成之后,可以采用实验二中的步骤(安装内核模块->复制内核文件->制作initramfs镜像->生成新的启动引导)来安装新的内核。安装完成后,重启虚拟机,打过自定义补丁的内核就安装完成了。

验证系统调用

我们对内核的修改是增加了一条系统调用。这个系统调用会在内核缓存目录中打印一条消息。所以验证该系统调用是否被正确应用,只需要编写一个程序调用该系统调用,并观察该系统调用是否正确执行即可。

屏幕截图_60_.jpg

首先,使用以下命令新建一个C程序源文件并使用vim打开它:

1
2
touch myTest.c
vi myTest.c

在新建的源文件中输入以下的编码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/types.h>

int main()
{
syscall(765);
return 0;
}

保存并退出vim之后,使用以下命令编译并执行:

1
2
gcc -o myTest myTest.c
./myTest

执行编译生成的二进制文件之后,控制台没有任何输出。这是因为printk函数是向内核缓冲区打印信息而不是控制台。需要使用dmesg -c命令输出内核缓冲区的信息,可以检查printk的输出结果。

五、实验结果

在安装了patch过的内核的Arch Linux虚拟机中执行调用新系统调用的C程序,并且使用dmesg -c命令查看内核缓存区,发现了系统调用使用printk函数打印的字符串。这说明增加的系统调用成功注册,并且随着编译后的内核成功安装到了虚拟机中。

屏幕截图_61_.jpg

2020-3-8 更新: 原本的截图可以去这里下载。


附录

疑问和未解决问题

虚拟机分辨率问题

采用了Microsoft Docs中提到的,在grub配置文件中修改分辨率的方法。只对虚拟机运行时的tyy终端界面有效,而无法改变图形环境的分辨率。

虚拟机没有显卡

使用screenfetch命令查看系统信息的时候发现虚拟机没有显卡。初步判定这是导致无法改变桌面环境的分辨率以及执行OpenGL程序的原因。

patch后内核编译缓慢

使用patch对已经预编译的内核进行打补丁操作之后,使用make编译时,make将所有的文件重新编译。而这种情况下,make应当只重新编译被patch修改过的极少部分文件。

参考链接

ArchLinux 安装/配置/美化 - 米V米
arch里没有update-grub这个命令吗 - 百度知道
Hyper-V虚拟机安装Linux后修改Linux的屏幕分辨率 - CSDN
Linux环境下root用户与普通用户切换 - CSDN
Windows 10 hyper-v安装manjaro 18.0.4+Xfce4 后调整分辨率 - CSDN
美轮美奂的Arch, 详解Arch虚拟机安装 - 简书
Arch Linux 安装指南(ArchISO 2014.10) - Ubuntu论坛
defconfig、.config、kconfig与makefile和make menuconfig流程 - CSDN
make oldconfig和make defconfig有何区别 - 百度知道
Linux 内核编译 - CSDN
zimage和bzimage有什么区别 - 博客园
asmlinkage的用法 - CSDN
dmesg七种用法 - 博客园
关于asmlinkage - 博客园
汇编语言的主程序与子程序之间的参数传递方式 - CSDN
Archlinux官方Wiki - mkinitcpio(简体中文)
Dian团队 - 网安组@李勉学长
Installation guide (简体中文) - Arch Wiki

评论