操作系统课程设计:新增linux驱动程序

简介: 操作系统课程设计:新增linux驱动程序

一、设计内容及具体要求

新增Linux驱动程序

增加一个驱动程序(使用内存模拟设备),使用模块编译方式。

要求:

(1)可以动态加载和卸载新的驱动。

(2)通过程序或命令行使用该驱动。

(3)至少能通过该驱动保存256MB的数据,还能将这些数据读取出来。

(4)要重新编译Linux内核,可模仿ramdisk的实现方式。

二、设计思想

题目要求使用内存模拟设备增加一个驱动程序。而内存模拟设备可以模仿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挂载的操作。要读写设备中的文件时,需要先把设备的分区挂载到系统中的一个目录上,通过访问该目录来访问设备。

三、 设计实现与源码剖析

在设计属于自己的驱动时,需要实现加载模块时的初始化函数即驱动模块的入口函数。还需要实现卸载模块时的函数,即模块的出口函数。同时,也要实现设备自己的请求处理函数。

首先,对该模块的数据结构进行设计。先定义该块的块设备名、主设备号、大小(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完整代码见附录。

四、操作流程

将模块源码zombotany_blkdev.c和Makefile文件放在同一目录下。

在该目录下打开控制台,输入make。则能正确地生成.o文件和.ko文件

五、测试与分析

模块编译完成后,首先回到该目录,执行语句insmod zombotany_blkdev.ko,将刚编译完成的zombotany_blkdev.ko模块插入。执行完成后,再执行lsmod,查看当前系统中的块设备列表。可以看到,zombotany_blkdev已经存在,已经被插入过了,且大小为256MB。也可以执行lsblk,查看当前块设备。

在根目录下的/dev/ 路径中,可以看到zombotany_blkdev已经被插入了。执行ls /dev/

在插入完成后,需要对该模块进行格式化,建立文件系统。输入mkfs.ext3 /dev/zombotany_blkdev,则在该内存模拟设备上建立了ext3文件系统。

在建立完成文件系统后,就可以将该设备挂载到文件系统的目录下。首先需要创建需要挂载的目录。mkdir -p /mnt/temp1。将块设备挂载到该目录下。mount /dev/zombotany_blkdev /mnt/temp1。再运行mount | grep zombotany_blkdev。即挂载完成。

再次执行lsmod,查看模块被调用的情况。该模块被一个用户调用。

执行ls/mnt/temp1/ 则看到当前块设备有且只有一个文件:lost+found文件。将当前目录所有文件都复制到块设备上,例如当前在该模块的源代码文件夹目录上。执行cp ./* /mnt/temp1/完成复制,再查看当前块设备文件名单。执行ls /mnt/temp1/,则可以看到该块设备被正确地写入了文件,并可以被读取到。

执行df -H,则查看当前各个设备使用情况。新增的设备zombotany_blkdev已用了2.9MB。

执行vi /mnt/temp1/zombotany_blkdev.c,还能读取这个文件。

最后对该模块进行卸载。首先删除该目录内所有文件。rm -rf /mnt/temp1/*。

先取消挂载。执行umount /mnt/temp1后执行lsmod | grep zombotany_blkdev。可以看到,这个256MB大小的设备被0个用户调用。

执行rmmod zombotany_blkdev。该语句的作用是移除该模块。运行完成后,再次执行lsmod grep zombotany_blkdev。可以在控制台上看到系统并没有任何输出。说明:zombotany_blkdev模块已经彻底被移除了。

附录

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);// 声明模块的出口


相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
17天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
43 1
|
18天前
|
安全 Linux 数据安全/隐私保护
深入Linux操作系统:文件系统和权限管理
在数字世界的海洋中,操作系统是连接用户与硬件的桥梁,而Linux作为其中的佼佼者,其文件系统和权限管理则是这座桥梁上不可或缺的结构。本文将带你探索Linux的文件系统结构,理解文件权限的重要性,并通过实际案例揭示如何有效地管理和控制这些权限。我们将一起航行在Linux的命令行海洋中,解锁文件系统的奥秘,并学习如何保护你的数据免受不必要的访问。
|
19天前
|
搜索推荐 Linux
深入理解Linux操作系统的启动过程
本文旨在揭示Linux操作系统从开机到完全启动的神秘面纱,通过逐步解析BIOS、引导加载程序、内核初始化等关键步骤,帮助读者建立对Linux启动流程的清晰认识。我们将探讨如何自定义和优化这一过程,以实现更高效、更稳定的系统运行。
|
17天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
18天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
20天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
22天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
|
前端开发 rax Linux
Linux 程序 Linux编译 Linux编译过程的来龙去脉
Linux 程序编译过程的来龙去脉 大家肯定都知道计算机程序设计语言通常分为机器语言、汇编语言和高级语言三类。高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类,一种是编译型语言,例如C,C++,Java,另一种是解释型语言,例如Python、Ruby、MATLAB 、JavaScript。
1230 0
|
1月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
96 8
|
1月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
281 6

热门文章

最新文章