• 操作系统课程设计:新增Linux驱动程序(重制版)


    写在前面

    本文为本人当初提交操作系统课程设计报告的版本,整合了如下文章:
    1.linux内核编译
    https://blog.csdn.net/qq_46640863/article/details/122684580

    2.本人课设主体任务
    https://blog.csdn.net/qq_46640863/article/details/122952706

    3.小组任务整合流程
    https://blog.csdn.net/qq_46640863/article/details/122751766

    一、课程设计目的

    知识方面:
    1.掌握操作系统功能模块的设计与实现方法。
    能力与素质方面:
    1.能够在阅读和分析开源操作系统的基础上,对其进行功能模块划分;能够指出现有功能模块的不足,并能够通过文献的研究给出解决方案。
    2.能够完成操作系统功能模块的设计、实现与测试,同时在设计操作系统功能模块中,能体现优化和创新意识。
    3.能够制定合理的实验方案及对实验结果进行分析并得出结论,针对实验结果分析解决过程的影响因素,论证解决方案的合理性,以获得有效结论。
    4.能够根据设计任务和要求组成团队,分工协作,并能承担个体、团队成员以及负责人的角色。
    5.能够用口头和书面方式清晰表述设计原理及相关概念与原理,包括陈述发言,清晰表达和回应指令。
    6.能够撰写比较规范的课程设计报告。

    二、设计内容及具体要求

    题目3:新增Linux驱动程序
    增加一个驱动程序(使用内存模拟设备),使用模块编译方式。
    要求:
    (1)可以动态加载和卸载新的驱动。
    (2)通过程序或命令行使用该驱动。
    (3)至少能通过该驱动保存256MB的数据,还能将这些数据读取出来。
    (4)要重新编译Linux内核,可模仿ramdisk的实现方式。

    三、实验环境

    物理机:Windows11 教育版
    虚拟机:VMware Workstation Pro 16
    虚拟机操作系统:CentOS 8 64位(CentOS-8.5.2111-x86_64)
    Linux内核版本:linux-4.18.20

    四、实验步骤-内核编译

    在实验开始前,需要预先与队友统一实验环境中的虚拟机操作系统与Linux内核版本,以在后续的整合步骤中便于操作。
    首先,在CentOS官网上点击“Download”,在该页面点击“x86_64”,并进入找到CentOS在国内的镜像下载站,选择进入南京大学的镜像下载站。http://mirrors.nju.edu.cn/centos/8.5.2111/isos/x86_64/
    并下载文件大小约为10GB的iso文件,而不是文件大小约为800m的boot文件。
    图1 CentOS的iso文件
    安装VMware Workstation 16 pro。由于之前已经装过VMware14,在更新过程中可能系统会提示安装失败的情况。经查阅相关资料,需要按图中流程进行操作:以管理员身份打开cmd,执行”sc stop vmx86& sc delete vmx86”和”sc stop vstor2-mntapi20-shared &sc delete vstor2-mntapi20-shared”。在”C:\Windows\System32\drivers\”目录下删除”vmx86.sys”、”vstor2-mntapi20-shared.sys”、”vstor2-x64.sys”。在”C:\windows\system32\drvstore”目录下删除所有前缀为”vmx86”的文件。同时,也要以管理员身份打开组策略编辑器(gpedit),在HEKY_LOCAL_MACHINE\SOFTWARE中删除与VMware有关的项目。
    最后重启主机。运行VMware 16 Pro安装程序。至此,VMware虚拟机得以正确地安装。
    图2 彻底删干净VMware旧版的流程
    图3 在win11的组策略上删除VMware相关项目
    打开VMware,点击“创建新的虚拟机”,运行安装向导。采用典型配置,进行简易安装,如图所示。
    图4 采用简易安装
    为了方便后续操作,可以将用户账户和根账户(root)的密码设置成1。
    图5 设置用户账户与密码
    虚拟机网络采用网络地址转换NAT。在虚拟机操作系统安装完成后,即可直接连接外部网络。
    图6 使用网络地址转换
    在配置虚拟机硬件时,虚拟机内存可以分3GB。处理器可以分4个,每个处理器有两个内核,以在编译内核时可以使用8个内核运行编译。磁盘空间需要开足够大,使用80GB而不是默认推荐的20GB,以防止内核编译过程中出现磁盘空间不足的情况。同时,需要注意删去虚拟机硬件设置中的打印机与USB接口。否则,在物理机中可能会出现蓝屏的情况。
    图7 硬件配置
    在虚拟机创建完成后,即可以启动虚拟机,配置系统。需要说明的是,CentOS 8的系统配置过程较为简单,只需等待其进度条读完后,即可以直接进入图形化界面。
    为了方便地调出terminal控制台,可以在桌面右键设置Display Settings,在键盘上添加快捷键,如图,设置可以用快捷键打开控制台。
    图8 控制台快捷键
    调出terminal控制台后,输入cat /proc/version,即可以查看当前使用的Linux内核版本号与gcc编译器版本号。
    图9 linux版本号
    由于windows11系统可能与vmware的vmware tools不兼容,不能从主机直接拖文件向虚拟机,所以需要使用xftp共享文件夹的方式从主机向虚拟机传文件。Centos8的网络IP地址配置文件在/etc/sysconfig/network-scripts 文件夹下,名为ifcfg-ens160。在cmd输入ifconfig,得到本机dhcp时的IP地址inet。利用xftp进行连接,会话的主机就是本次ifconfig得到的ip地址。利用sftp协议。
    图10 ifconfig结果
    图11 xftp会话
    图12文件已传入
    下载需要进行编译的新的内核的版本。在本文中,选用linux-4.18.20版本。如图,已经成功将linux源码文件包传入。但是,如需直接传入/usr/src/kernels文件夹中,可能会因为权限问题而传输失败。所以,需要先将文件传入~/Desktop.在CentOS 8 中,可以直接双击解压,无需输入命令行。但在把Desktop的文件复制到/usr/src/kernels时,因为用户没有权限,所以只能通过命令行的形式将文件复制进入。执行如下语句:
    sudo cp -r /home/zombotany/Desktop/linux-4.18.20 /usr/src/kernels
    接下来的步骤为配置编译环境。需要说明的是,CentOS 8的编译环境安装过程与其他Linux发行版的安装流程区别较大。在此,将CentOS8的编译环境安装过程进行详细介绍。
    首先,安装gcc。sudo yum install gcc
    安装g++。在centos中,安装g++的命令需要写成sudo yum install gcc-c++ 在安装过程中可能会提示未找到匹配的包。经查阅相关资料,知道了是yum源的原因。于是安装 EPEL 源,使用最新版本8。执行语句:yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
    在CentOS8中,安装devtoolset时,不能用yum,而是使用dnf.需要注意的是,scl在CentOS8的yum源中是不提供的。需要执行语句:sudo dnf -y group install "Development Tools"
    接下来,同样地,执行语句sudo dnf install devtoolset-9-gcc
    还需要安装的是bison、flex、gdb、make、bc等工具。这些语句在CentOS各个版本是相同的,不作赘述。
    sudo yum install gdb -y
    sudo yum install make -y
    sudo yum install bison -y
    sudo yum install flex -y
    sudo yum install bc -y
    在想要编译的文件夹中输入make,会被提示缺文件。被系统建议安装libelf-dev libelf-develefutils-libelf-devel
    图13安装efutils-libelf-devel
    但是,只能安装第三个,前两个都不是centos能装的,只能执行sudo yum -y install efutils-libelf-devel
    在生成编译配置.config之前,还需要执行如下两条语句:
    sudo yum install ncurses-devel -y
    sudo yum install openssl-devel -y
    在安装完如上文所述的所有依赖后,进入需要编译的内核所在的文件目录,执行命令:cd /usr/src/kernels/linux-4.18.20
    在编译之前,首先需要执行清理命令。执行:make mrproper。在执行完之前残留的编译结果后,需要生成编译配置文件。执行make menuconfig,在等待数秒后,控制台会显示如图14的图形界面。利用键盘方向键进行选择,直接全部使用默认参数。选定save项,并采用默认的配置文件名.config,如图15所示。
    输入make -j8,启动8个核开始编译。这一步骤大约需要一小时左右。如果发现系统很快执行完,则说明该步骤没有被正确地执行完毕,仍然存在较多文件没有被正确地编译。若报错missing file,则make相应的文件。若在make bzImage时又被提示”no rule to make target ‘certs/rhel.pem’”,则用vi编辑器打开.config文件,中注释掉CONFIG_SYSTEM_TRUSTED_KEYS或写死CONFIG_SYSTEM_TRUSTED_KEYS=””,解决该问题。该配置项大约在文件结束处。修改完成后,输入键盘的esc,并输入”:wq”退出并写入文件。
    编译完成后生成了bzImage,该文件在目录arch/x86_64/boot下,如图17所示。编译完成后输入make modules编译模块。输入make modules_install安装模块,输入make install安装新内核。安装完成新内核后,输入reboot,进入新内核。
    图14 make menuconfig进入界面
    图15配置文件名
    图16报错
    图17内核编译结果

    五、实验步骤-实现设计内容

    5.1设计思想

    题目要求使用内存模拟设备增加一个驱动程序。而内存模拟设备可以模仿Ram Disk的实现方式。经查阅相关资料可得知:Ram Disk的功能是将一部分内存挂载(mount)为外存空间(磁盘)的分区进行使用。从用户的视角看,Ram Disk分区就像磁盘的分区一样,也能对文件进行读写。
    但是,Ram Disk与真正的磁盘仍然存在一定区别。在虚拟机重启后,Ram Disk分区消失,Ram Disk分区内部的数据也将消失。
    Ram Disk也存在自己的意义。若有几个文件需要被频繁的读写,则可以将其放到由内存开辟的Ram Disk上,大大提高了读写的速度。
    在本题目中,采取的就是模仿Ram Disk的实现。在第六章节中,将展示模仿Ram Disk的实现能得到的类似于Ram Disk的效果。
    Linux系统将所有设备都视作文件,/dev/设备名 不是目录,而类似于指针指向该块设备,不能直接对其进行读写而需要先进行mount挂载的操作。要读写设备中的文件时,需要先把设备的分区挂载到系统中的一个目录上,通过访问该目录来访问设备。

    5.2 设计实现与源码剖析

    在设计属于自己的驱动时,需要实现加载模块时的初始化函数即驱动模块的入口函数。还需要实现卸载模块时的函数,即模块的出口函数。同时,也要实现设备自己的请求处理函数。
    首先,对该模块的数据结构进行设计。先定义该块的块设备名、主设备号、大小(25610241024bytes,即256MB)、扇区数为9。

    #define SIMP_BLKDEV_DISKNAME "zombotany_blkdev"
    #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR 
    #define SIMP_BLKDEV_BYTES (256*1024*1024)
    #define SECTOR_SIZE_SHIFT 9
    
    • 1
    • 2
    • 3
    • 4

    定义gendisk表示一个简单的磁盘设备、定义该块设备的拥有者、定义块设备的请求队列指针、开辟该块设备的存储空间。

    static struct gendisk * zombotany_blkdev_disk;
    static struct block_device_operations  zombotany_blkdev_fops = { 
        .owner = THIS_MODULE,
    };
    static struct request_queue * zombotany_blkdev_queue;
    unsigned char  zombotany_blkdev_data[SIMP_BLKDEV_BYTES];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    入口函数与出口函数这两个函数的方法头如下:

    static int __init _init(void)  
    static void __exit _exit(void)  
    
    • 1
    • 2

    在入口函数中需要实现的功能包括4个步骤。1.申请设备资源。若申请失败,则退出。2.设置设备有关属性。3.初始化请求队列,若失败则退出。4.添加磁盘块设备。
    首先,申请设备资源。判断申请是否成功,若失败则退出。

    zombotany_blkdev_disk = alloc_disk(1);
    if(! zombotany_blkdev_disk){
    	ret = -ENOMEM;
    	goto err_alloc_disk;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接下来,设置设备有关属性。设置设备名、设备号、fops指针、扇区数

    strcpy( zombotany_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
    zombotany_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
    zombotany_blkdev_disk->first_minor = 0;
    zombotany_blkdev_disk->fops = & zombotany_blkdev_fops;
    set_capacity( zombotany_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    初始化请求队列,若失败则退出。

    zombotany_blkdev_queue = blk_init_queue( zombotany_blkdev_do_request, NULL);
        if(! zombotany_blkdev_queue){
            ret = -ENOMEM;
            goto err_init_queue;
        }
    zombotany_blkdev_disk->queue =  zombotany_blkdev_queue;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后添加磁盘块设备。

        add_disk( zombotany_blkdev_disk);
        return 0;
    
    • 1
    • 2

    模块的出口函数较为简单,只需释放磁盘块设备、释放申请的设备资源、清除请求队列。

    static void __exit  zombotany_blkdev_exit(void){
       	del_gendisk( zombotany_blkdev_disk);
       	put_disk( zombotany_blkdev_disk);   
       	blk_cleanup_queue( zombotany_blkdev_queue);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在实现完入口与出口函数后,需要再声明模块出入口。

    module_init(xxxx_init);
    module_exit(xxxx_exit);
    
    • 1
    • 2

    实现模块的请求处理函数。请求处理函数涉及到的数据结构如下:当前请求、当前请求bio(通用块层用bio来管理一个请求)、当前请求bio的段链表、当前磁盘区域、缓冲区。

    struct request *req;
    struct bio *req_bio;
    struct bio_vec *bvec;
    char *disk_mem;     
    char *buffer;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于某个请求,先判断该请求是否合法。判断请求是否合法的办法就是判断其是否出现了地址越界的情况。

    if((blk_rq_pos(req)<<SECTOR_SIZE_SHIFT)+blk_rq_bytes(req)>SIMP_BLKDEV_BYTES){
                blk_end_request_all(req, -EIO);
                continue;
            }
    
    • 1
    • 2
    • 3
    • 4

    若请求合法,则获取当前地址位置。

    disk_mem =zombotany_blkdev_data + (blk_rq_pos(req) << SECTOR_SIZE_SHIFT);
    req_bio = req->bio;
    
    • 1
    • 2

    判断请求类型,处理读请求与写请求的过程大同小异的。在处理读请求时,遍历请求列表,找到缓冲区与bio,将磁盘内容复制到缓冲区。找到磁盘下一区域,然后处理请求队列下一个请求。

    while(req_bio != NULL){
    	for(i=0; i<req_bio->bi_vcnt; i++){
    		bvec = &(req_bio->bi_io_vec[i]);
    		buffer = kmap(bvec->bv_page) + bvec->bv_offset;
    		memcpy(buffer, disk_mem, bvec->bv_len);
    		kunmap(bvec->bv_page);
          	disk_mem += bvec->bv_len;
           	}
        req_bio = req_bio->bi_next;
        }
      	__blk_end_request_all(req, 0);
      	break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在处理写请求时,是把缓冲区内容复制到磁盘上。只需在调用memcpy时将两个参数互换即可,其余相同。
    memcpy(disk_mem, buffer, bvec->bv_len);
    该部分代码如下:

    while(req_bio != NULL){
                    for(i=0; i<req_bio->bi_vcnt; i++){
                        bvec = &(req_bio->bi_io_vec[i]);
                        buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                        memcpy(disk_mem, buffer, bvec->bv_len);
                        kunmap(bvec->bv_page);
                        disk_mem += bvec->bv_len;
                    }
                    req_bio = req_bio->bi_next;
                }
                __blk_end_request_all(req, 0);
                break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    该模块完整代码见附录,文件名为zombotany_blkdev.c

    在编写完模块代码后,还需要编写Makefile文件。Linux的文件系统中,文件没有扩展名。Makefile文件没有扩展名。
    首先,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make只会执行else之后的内容。
    ifneq ($(KERNELRELEASE),)
    得到内核源码的路径与当前的工作路径
    KDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    若之前执行过Makefile,则需要清理掉之前编译过的模块。

    modules:
    		$(MAKE) -C $(KDIR) M=$(PWD) modules
    	modules_install:
    $(MAKE) -C $(KDIR) M=$(PWD) modules_install
    clean:
            rm -rf *.o *.ko .depend *.mod.o *.mod.c Module.* modules.*
    .PHONY:modules modules_install clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    生成.o文件

    else
    		obj-m := simp_blkdev.o
    	endif
    
    • 1
    • 2
    • 3

    Makefile完整代码见附录。

    5.3 操作流程

    将模块源码zombotany_blkdev.c和Makefile文件放在同一目录下,如图18。
    图18模块文件
    在该目录下打开控制台,输入make。则能正确地生成.o文件和.ko文件
    图19 make执行结果
    该模块编译完成后,对其进行的测试与分析的结果详见第六章节。

    六、个人结果测试与分析

    模块编译完成后,首先回到该目录,执行语句insmod zombotany_blkdev.ko,将刚编译完成的zombotany_blkdev.ko模块插入。执行完成后,再执行lsmod,查看当前系统中的块设备列表。可以看到,zombotany_blkdev已经存在,已经被插入过了,且大小为256MB。也可以执行lsblk,查看当前块设备。
    图20 lsblk
    图21 lsmod
    在根目录下的/dev/ 路径中,可以看到zombotany_blkdev已经被插入了。执行ls /dev/
    图22 ls /dev/
    在插入完成后,需要对该模块进行格式化,建立文件系统。输入mkfs.ext3 /dev/zombotany_blkdev,则在该内存模拟设备上建立了ext3文件系统。
    图23 建立文件系统
    在建立完成文件系统后,就可以将该设备挂载到文件系统的目录下。首先需要创建需要挂载的目录。mkdir -p /mnt/temp1。将块设备挂载到该目录下。mount /dev/zombotany_blkdev /mnt/temp1。再运行mount | grep zombotany_blkdev。即挂载完成。
    再次执行lsmod,查看模块被调用的情况。该模块被一个用户调用。
    图24 再次lsmod
    执行ls/mnt/temp1/ 则看到当前块设备有且只有一个文件:lost+found文件。将当前目录所有文件都复制到块设备上,例如当前在该模块的源代码文件夹目录上。执行cp ./* /mnt/temp1/完成复制,再查看当前块设备文件名单。执行ls /mnt/temp1/,则可以看到该块设备被正确地写入了文件,并可以被读取到。
    图25 再次查看块设备文件
    执行df -H,则查看当前各个设备使用情况。新增的设备zombotany_blkdev已用了2.9MB。
    图26 查看已用空间
    执行vi /mnt/temp1/zombotany_blkdev.c,还能读取这个文件。
    图27对文件进行读写
    最后对该模块进行卸载。首先删除该目录内所有文件。rm -rf /mnt/temp1/*
    先取消挂载。执行umount /mnt/temp1后执行lsmod | grep zombotany_blkdev。可以看到,这个256MB大小的设备被0个用户调用。
    执行rmmod zombotany_blkdev。该语句的作用是移除该模块。运行完成后,再次执行lsmod grep zombotany_blkdev。可以在控制台上看到系统并没有任何输出。说明:zombotany_blkdev模块已经彻底被移除了。
    图28模块的卸载与移除
    简而言之,通过测试与分析,本新增模块正确地完成了题目要求:使用模块编译的方式。可以动态加载和卸载新的驱动,如图21与28所示。可以通过命令行使用该驱动,在测试过程中每一步都有所体现。至少能保存256MB数据,并且能读取出来,如图26与27所示。需要重新编译Linux内核,模仿ramdisk实现方式。详见章节四与章节5.1。

    七、小组整合

    7.1本组分工情况

    如表1所示
    表1 小组分工

    姓名分工
    1题目1:新增Linux系统调用
    2题目2:实现基于模块的文件系统
    zombotany题目3:新增Linux驱动程序,并负责小组的工作的整合与答辩
    4题目4:统计Linux系统缺页的次数并协助进行小组工作的整合
    5题目5:进程/线程通信

    7.2小组整合思路

    题目1与题目4存在的共同点是:在进行内核的编译之前需要修改内核中的文件。题目2与题目3存在的共同点是:需要编译内核,在编译完成的新内核进行模块的编译。而题目5完全不涉及内核。
    因此,整合的总体流程如下:首先修改内核源码文件中涉及到题目1与题目4的部分。接下来,进行长达1~2小时的编译内核、编译模块与安装新内核。然后,进入新的内核,传入题目2与题目3涉及到的源代码文件,安装并卸载相应的模块进行测试。最后,编译并测试题目5涉及到的源代码。
    在本文的章节四中,本小组已经事先统一了用同一个版本的Linux发行版与同一个版本的Linux内核源码。因此,整合的过程得到了一定的简化。
    题目1涉及到的内核源码的文件包括:
    arch/x86/entry/syscalls/syscall_64.tbl
    kernel/sys.c
    include/linux/syscalls.h
    题目4涉及到的内核源码的文件包括:
    arch/x86/mm/fault.c
    include/linux/mm.h
    kernel/kallsyms.c
    在替换了这6个文件后,按第四章节的流程操作,对内核进行重新编译。
    进入新的内核。
    在新的内核中,题目1需要使用程序对内核进行测试。
    题目2涉及到的文件包括:super.c、sysfs.c、file.c、Makefile。将这4个文件放在同一个目录下,进行模块编译。
    题目3涉及到的文件包括:zombotany_blkdev.c、Makefile。将这2个文件放在同一个目录下,进行模块编译。
    题目4涉及到的文件包括:readpfcount.c、Makefile。将这2个文件放在同一个目录下,进行模块编译。利用模块的形式,对缺页中断次数进行了测试。
    题目5涉及到的文件包括:share.c、read.c。这2个文件不涉及也不调用内核。在这2个文件中,就可以加入题目1设计到的系统调用。例如,可以系统调用,计算当前图书馆内已有人数的三次方。如图29所示。可以用gcc -c share.c -o share.out 和gcc -c read.c -o read.out直接编译运行。
    图29题5与题1的结合
    其余运行结果不再贴图赘述。

    7.3编译新内核时遇到的问题与解决思路

    因为在完成个人的题目时,反复编译了内核,所以在整合小组工作并重新编译时出现了boot分区不够的情况,不能在boot分区安装新内核,如图30所示。运行df -hl,发现boot分区只开了300M,且空间即将耗尽。所以,在安装新内核之前需要先对boot分区进行扩容。
    图30 boot分区不够用
    首先关机,创建新的磁盘,重新开机后将/boot分区取消挂载。对新的磁盘(nvme0n2)分区,执行命令fdist /dev/nvme0n2,创建一个新分区,全部采取默认选项。
    图31 在新的磁盘内创建新分区
    运行lsblk命令,查看新磁盘的新分区。对新分区进行格式化 mkfs.ext4 /dev/nvme0n2p1
    图32 对新分区进行格式化
    将旧内核复制到boot_old文件夹(cp -r /boot/ /boot_old),备份旧内核中的文件。之后,把boot分区挂载回来,挂载到新分区。mount /dev/nvme-n2p1 /boot。在挂载完成后,再把boot_old的备份文件复制回来。cp -r /boot_old/. /boot
    将永久挂载写入到/etc/fstab里。先执行blkid查看所有分区的uuid。如图33。
    图33 查看UUID
    打开/etc/fstab,找到nvme0n2p1的分区填入。加入记录:
    UUID=5b624350-9fce-495d-934e-650f62cfe189 ext4 /boot defaults 0 1
    保存并退出后更新挂载信息。mount -alsblk。可以看到,/boot分区被挂载到了有20GB的新磁盘上。
    图34 重新挂载/boot
    重新挂载/boot分区后,重新make install安装内核模块。但是,还需要运行grub2-mkconfig -o /boot/grub2/grub.cfg更新引导文件。否则,会出现如下情况:在旧内核中,/boot分区被正确地识别到并挂载到nvme0n2p1分区,但在新内核找不到/boot。更新引导文件后,新内核也能找到/boot分区。再次重启虚拟机,终于可以成功进入新内核。编译新内核时可能遇到的/boot分区不足的情况被通过这种办法成功得以解决。
    编译安装新内核过程中还可能遇到的情况如图35:客户机操作系统已禁用CPU。此问题解决办法较为简单:在物理机开机时按f12进入bios,在bios中设置允许虚拟机。若已经设置允许虚拟机,则需要关掉windows defender或腾讯电脑管家或360。当虚拟机占用主机过多资源时就有可能也会出现该情况。图35 客户机禁用CPU

    参考文献

    [1] https://blog.csdn.net/cxy_chen/article/details/80998510
    [2] https://blog.csdn.net/wys7250578/article/details/9045237
    [3] https://blog.csdn.net/skywalker_123/article/details/102587813
    [4]https://blog.csdn.net/m0_46362426/article/details/118879627
    [5]https://forums.pvpgn.pro/viewtopic.php?id=2226
    [6]https://stackoverflow.com/questions/61590926/how-to-install-gcc-g-9-on-centos-8-docker-centoslatest
    [7]https://communities.vmware.com/t5/VMware-Workstation-Pro/Update-to-Workstation-14-1-2-fails-and-destroys-existing/td-p/2735925

    源程序清单

    Makefile

    ifeq ($(KERNELRELEASE),)
    KDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    modules:
    	$(MAKE) -C $(KDIR) M=$(PWD) modules
    modules_install:
    	$(MAKE) -C $(KDIR) M=$(PWD) modules_install
    clean:
    	rm -rf *.o *.ko .depend *.mod.o *.mod.c Module.* modules.*
    .PHONY:modules modules_install clean
    else
    	obj-m := zombotany_blkdev.o
    endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    zombotany_blkdev.c

    #include 
    #include 
    
    #define SIMP_BLKDEV_DISKNAME "zombotany_blkdev"//设备名称
    #define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR //主设备号
    #define SIMP_BLKDEV_BYTES (256*1024*1024)            // 块设备大小为256MB
    #define SECTOR_SIZE_SHIFT 9//9个扇区
    
    static struct gendisk * zombotany_blkdev_disk;// gendisk结构表示一个简单的磁盘设备
    static struct block_device_operations  zombotany_blkdev_fops = { 
        .owner = THIS_MODULE,//设备主体
    };
    static struct request_queue * zombotany_blkdev_queue;//指向块设备请求队列的指针
    unsigned char  zombotany_blkdev_data[SIMP_BLKDEV_BYTES];// 虚拟磁盘块设备的存储空间
    
    //请求处理函数
    static void  zombotany_blkdev_do_request(struct request_queue *q){
        struct request *req;// 正在处理的请求队列中的请求
        struct bio *req_bio;// 当前请求的bio
        struct bio_vec *bvec;// 当前请求的bio的段(segment)链表
        char *disk_mem;      // 需要读/写的磁盘区域
        char *buffer;        // 磁盘块设备的请求在内存中的缓冲区
    
        while((req = blk_fetch_request(q)) != NULL){//得到请求
            // 判断当前请求是否合法
            if((blk_rq_pos(req)<<SECTOR_SIZE_SHIFT) + blk_rq_bytes(req) > SIMP_BLKDEV_BYTES){//判断地址是否越界访问
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME":bad request:block=%llu, count=%u\n",(unsigned long long)blk_rq_pos(req),blk_rq_sectors(req));//越界访问了,则输出
                blk_end_request_all(req, -EIO);
                continue;//获取下一请求
            }
            //获取需要操作的内存位置
            disk_mem =  zombotany_blkdev_data + (blk_rq_pos(req) << SECTOR_SIZE_SHIFT);
            req_bio = req->bio;// 获取当前请求的bio
    
            switch (rq_data_dir(req)) {  //判断请求的类型
            case READ:
                // 遍历req请求的bio链表
                while(req_bio != NULL){
                    // for循环处理bio结构中的bio_vec结构体数组(bio_vec结构体数组代表一个完整的缓冲区)
                    for(int i=0; i<req_bio->bi_vcnt; i++){
                        bvec = &(req_bio->bi_io_vec[i]);
                        buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                        memcpy(buffer, disk_mem, bvec->bv_len);//把内存中数据复制到缓冲区
                        kunmap(bvec->bv_page);
                        disk_mem += bvec->bv_len;
                    }
                    req_bio = req_bio->bi_next;//请求链表下一个项目
                }
                __blk_end_request_all(req, 0);//被遍历完了
                break;
            case WRITE:
                while(req_bio != NULL){
                    for(int i=0; i<req_bio->bi_vcnt; i++){
                        bvec = &(req_bio->bi_io_vec[i]);
                        buffer = kmap(bvec->bv_page) + bvec->bv_offset;
                        memcpy(disk_mem, buffer, bvec->bv_len);//把缓冲区中数据复制到内存
                        kunmap(bvec->bv_page);
                        disk_mem += bvec->bv_len;
                    }
                    req_bio = req_bio->bi_next;//请求链表下一个项目
                }
                __blk_end_request_all(req, 0);//请求链表遍历结束
                break;
            default:
                /* No default because rq_data_dir(req) is 1 bit */
                break;
            }
        }
    }
    
    
    //模块入口函数
    static int __init  zombotany_blkdev_init(void){
        int ret;
    
        //添加设备之前,先申请设备的资源
         zombotany_blkdev_disk = alloc_disk(1);
        if(! zombotany_blkdev_disk){
            ret = -ENOMEM;
            goto err_alloc_disk;
        }
    
        //设置设备的有关属性(设备名,设备号,fops指针
        strcpy( zombotany_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME);
         zombotany_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
         zombotany_blkdev_disk->first_minor = 0;
         zombotany_blkdev_disk->fops = & zombotany_blkdev_fops;
        //将块设备请求处理函数的地址传入blk_init_queue函数,初始化一个请求队列
         zombotany_blkdev_queue = blk_init_queue( zombotany_blkdev_do_request, NULL);
        if(! zombotany_blkdev_queue){
            ret = -ENOMEM;
            goto err_init_queue;
        }
         zombotany_blkdev_disk->queue =  zombotany_blkdev_queue;
    	//初始化扇区数
        set_capacity( zombotany_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
    
        //入口处添加磁盘块设备
        add_disk( zombotany_blkdev_disk);
        return 0;
    
        err_alloc_disk:
            return ret;
        err_init_queue:
            return ret;
    }
    
    
    //模块的出口函数
    static void __exit  zombotany_blkdev_exit(void){
    // 释放磁盘块设备
        del_gendisk( zombotany_blkdev_disk);
    // 释放申请的设备资源
        put_disk( zombotany_blkdev_disk);   
    // 清除请求队列
        blk_cleanup_queue( zombotany_blkdev_queue);
    }
    
    
    module_init( zombotany_blkdev_init);// 声明模块的入口
    module_exit( zombotany_blkdev_exit);// 声明模块的出口
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
  • 相关阅读:
    OAuth2.0第三方授权原理与实战
    Solidity智能合约开发 — 4.3-合约的事件和继承
    uniapp的h5项目怎样分享指定信息到Facebook
    执行Maven项目时,无法解析项目的依赖关系
    球型黑白棋
    第六章认识Node.js服务器开发
    CTFHUB-web-文件上传
    HTTP请求-【RestTemplate】
    【Hadoop】- MapReduce & YARN的部署[8]
    Docker Swarm 节点维护
  • 原文地址:https://blog.csdn.net/qq_46640863/article/details/125910774