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

一、课程设计目的

知识方面
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

定义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];

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

static int __init _init(void)  
static void __exit _exit(void)  

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

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;
set_capacity( zombotany_blkdev_disk, SIMP_BLKDEV_BYTES>>9);

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

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;

最后添加磁盘块设备。

    add_disk( zombotany_blkdev_disk);
    return 0;

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

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(xxxx_init);
module_exit(xxxx_exit);

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

struct request *req;
struct bio *req_bio;
struct bio_vec *bvec;
char *disk_mem;     
char *buffer;

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

if((blk_rq_pos(req)<<SECTOR_SIZE_SHIFT)+blk_rq_bytes(req)>SIMP_BLKDEV_BYTES){
            blk_end_request_all(req, -EIO);
            continue;
        }

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

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

判断请求类型处理读请求与写请求的过程大同小异的。在处理读请求时遍历请求列表找到缓冲区与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;

在处理写请求时是把缓冲区内容复制到磁盘上。只需在调用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;

该模块完整代码见附录文件名为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

生成.o文件

else
		obj-m := simp_blkdev.o
	endif

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

zombotany_blkdev.c

#include <linux/module.h>
#include <linux/blkdev.h>

#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);// 声明模块的出口
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“操作系统课程设计:新增Linux驱动程序(重制版)” 的相关文章