经典 【操作系统实验】 实验六 设备驱动程序

  • 设备驱动程序
    • 简单介绍一下2.6版本内核添加模块的方法:
    • 虚拟块设备驱动程序内容

设备驱动程序

前言:
本文是基于Linux的设备驱动实验流程记录,涵盖了【字符设备驱动程序】以及【虚拟块设备驱动程序】。
本身尝试了Ubuntu16.04的4.4.49内核,但因为虚拟机问题还有本人能力问题最终放弃,然后尝试了RedHats系统的2.6.32版本内核。
实在是太香了!没有比老内核更适合引入该部分模块内容的了。
应某位小伙伴需求贴出RH6的链接,安装什么的应该不用教了[doge]
Red Hats 6.5 2.6.32
提取码: 1fg6
RedHat5 kernel2.6.18 下载链接(http://mirrors.sohu.com/RHEL/AS5U5/i386/rhel-server-5.5-i386-dvd.iso)(下载引擎打开)
一、 实验目的
1、 理解Linux设备驱动程序的基本知识
2、 掌握设备驱动程序的编写原则和过程
3、 实现一个基于主存的虚拟块设备驱动程序,并进行测试
二、实验原理
1.Linux设备驱动程序:
总体上说,编写linux设备驱动应该是linux内核编程中最难的部分,一般只有在全面熟悉了linux内核之后,并且在接口技术很扎实的情况下才能开始真正地开发设备的驱动。我们实验书上所做的设备驱动只局限于软件部分,即linux内核,并未真正涉及设备。
Linux下设备基本上分为三部分:字符设备、块设备、网络设备。其中网络设备是最复杂的一部分。
在linux下开发设备驱动基本上分为几个过程:

  1. 根据外设的接口协议编写相应的功能函数原型;
  2. 将得到的函数原型用linux的驱动程序接口封装起来;
  3. 将封装好的模块动态加入到linux内核中。
    以上步骤中第一步主要与设备的接口协议相关,后两步主要与操作系统相关。这里主要简单讨论一下后两步。
    以字符设备为例,操作系统要求驱动程序用相应得接口函数封装起来,接口函数为一个函数的集合,集合中包括了所有可能对该设备进行操作的动作,这个函数的集合用一个struct file_operations数据结构来表示:
  4. struct file_operations中的操作按如下顺序出现,除非注明,它们的返回0时表示成功,发生错误时返回一个负的错误编码:
  5. int (*lseek)(struct inode *, struct file *, off_t, int);方法lseek用来修改一个文件的当前读写位置,并将新位置做为(正的)返回值返回。出错时返回一个负的返回值。
  6. int (*read)(struct inode *, struct file *, char *, int);用来从设备中读取数据。当其为NULL指针时将引起read系统调用返回-EINVAL(“非法参数”)。函数返回一个非负值表示成功的读取了多少字节。
  7. int (*write)(struct inode *, struct file *, const char *, int);向设备发送数据。如果没有这个函数,write系统调用向调用程序返回一个-EINVAL。如果返回值非负,它就表示成功地写入的字节数。
  8. int (*readdir)(struct inode *, struct file *, void *, filldir_t);对于设备节点来说,这个字段应该为NULL;它仅用于目录。
  9. int (*select)(struct inode *, struct file *, int, select_table *);select一般用于程序询问设备是否可读和可写,或是否一个“异常”条件发生了。
  10. int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);系统调用ioctl提供一中调用设备相关命令的方法(如软盘的格式化一个磁道,这既不是读操作也不是写操作)。ioctl系统调用将返回-EINVAL。当调用成功时,返回给调用程序一个非负返回值。
  11. int (*mmap)(struct inode *, struct file *, struct vm_area_struct *);mmap用来将设备内存映射到进程内存中。如果设备不支持这个方法,mmap系统调用将返回-ENODEV。
  12. int (*open)(struct inode *, struct file *);尽管这总是操作在设备节点上的第一个操作,然而并不要求驱动程序一定要声明这个方法。
  13. void (*release)(struct inode *, struct file *);当节点被关闭时调用这个操作。与open相仿,release也可以没有。
  14. int (*fsync)(struct inode *, struct file *);刷新设备。如果驱动程序不支持,fsync系统调用返回-EINVAL。
  15. int (*fasync)(struct inode *, struct file *, int);这个操作用来通知设备它的FASYNC标志的变化。
  16. int (*check_media_change)(kdev_t dev);check_media_change只用于块设备,尤其是象软盘这类可移动介质。
  17. int (*revalidate)(kdev_t dev);这是最后一项,与前面提到的那个方法一样,也只适用于块设备。revalidate与缓冲区高速。缓存有关。
    将设备的功能用以上函数封装之后,就形成了一个可以动态加入到linux内核中的模块,遵循一定的规则就可以动态地将新模块加入到正在运行中的内核中,或者从内核中删除不必要的模块。当模块加入后,无需重新启动系统,设备驱动程序就能工作起来。
    模块化编程的一个难点是内核版本的差异,即不同版本的内核其添加系统模块的具体方式不同。实验书上所介绍的方法只适用于2.2以前的内核,而2.4和2.6内核的方式又各有不同。

简单介绍一下2.6版本内核添加模块的方法:

a.编写内核模块文件,如
hello.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>static int hello_init(void)
{ printk("Hello! This is the helloworld module!\n");return 0;
} static void hello_exit(void)
{printk("Module exit! Bye Bye!\n");return;
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

在2.4以前,模块入口函数是int init_module(),出口函数是void cleanup_module() ;而2.6内核中,却必须用module_init(init_modules)和module_exit(cleanup_modules)来显示声明入口出口函数。
b.编写专门的编译内核模块的makefie文件,编译上面写好的内核模块,如:

CONFIG_MODULE_SIG=n
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)obj-m := hello.o
default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:rm -f hello.ko hello.mod.c hello.mod.o hello.o

这个makefile文件与编译一般程序的不同,具体细节这里从略。
c.模块编译之后是一个未连接的.o文件,用相应命令将模块文件添加到内核中:

root# insmod model.ko        //添加模块命令
root# rmmod model            //删除模块命令

模块添加成功后可以在/proc/modules文件中查看,该文件记录着当前系统中所有的模块信息。
也可以直接dmesg命令查看模块执行文件情况

编写设备区驱动程序模块,当设备驱动通过模块添加进内核之后,用户程序就可以像使用文件系统一样最外设进行操作。

以上,你便完成了初步入门,即字符设备驱动模块的编写与载入,测试等。

虚拟块设备驱动程序内容

下面代码,就是该驱动程序的核心了:
myramblock.c
2.6.32版本

//kernel version 2.6.32~39 can be used
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>#define RAMBLOCK_SIZE (1024*1024)
static struct gendisk *my_ramblock_disk;
static struct request_queue *my_ramblock_queue;
static DEFINE_SPINLOCK(my_ramblock_lock);
static int major;
static unsigned char *my_ramblock_buf;
static int my_ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{/* 容量=heads*cylinders*sectors*512 */geo->heads = 2; //磁头数geo->cylinders = 32; //柱面数geo->sectors = RAMBLOCK_SIZE/2/32/512; //扇区数return 0;
}static void do_my_ramblock_request(struct request *q)
{struct request *req;static int r_cnt=0; //实验用,打印出驱动读与写的调度方法static int w_cnt=0;req = blk_fetch_request(q);while (req){unsigned long start = blk_rq_pos(req) *512;unsigned long len = blk_rq_cur_bytes(req);if(rq_data_dir(req)==READ){memcpy(req->buffer, my_ramblock_buf+start, len); //读操作,printk("do_my_ramblock-request read %d times\n",r_cnt++);}else{memcpy( my_ramblock_buf+start,req->buffer, len); //写操作printk("do_my_ramblock request write %d times \n",w_cnt++);}// end_request(req,1);if(!__blk_end_request_cur(req, 0)){req = blk_fetch_request(q);}}
}static const struct block_device_operations my_ramblock_fops =
{.owner = THIS_MODULE,.getgeo = my_ramblock_getgeo, //获取磁盘几何属性
};static int my_ramblock_init(void)
{/*分配一个 gendisk 结构体*/my_ramblock_disk=alloc_disk(10);//次设备个数 ,分区个数 +1major=register_blkdev(0, "my_ramblock");//分配设置请求队列,提供读写能力my_ramblock_queue=blk_init_queue(do_my_ramblock_request,&my_ramblock_lock);//设置硬盘属性my_ramblock_disk->major=major;my_ramblock_disk->first_minor=0;my_ramblock_disk->fops=&my_ramblock_fops;sprintf(my_ramblock_disk->disk_name, "myramblock");my_ramblock_disk->queue=my_ramblock_queue;set_capacity(my_ramblock_disk, RAMBLOCK_SIZE/512);/* 硬件相关操作 */my_ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);add_disk(my_ramblock_disk);return 0;}static void my_ramblock_exit(void)
{unregister_blkdev(major, "myramblock");del_gendisk(my_ramblock_disk);put_disk(my_ramblock_disk);blk_cleanup_queue(my_ramblock_queue);kfree(my_ramblock_buf);
}module_init(my_ramblock_init);
module_exit(my_ramblock_exit);
MODULE_LICENSE("GPL");

另一个版本内核代码 2.6.18

// kernel version 2.6.18~22 can be used
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/errno.h>
#include <linux/bio.h>static int vblkdev_major = 0;
static int hardsect_size = 512;
static int nsectors = 1024; //扇区个数
module_param(vblkdev_major, int, 0);
module_param(hardsect_size, int, 0);
module_param(nsectors, int, 0);#define KERNEL_SECTOR_SIZE 512struct vblkdev {int                  size; /* In sectors. */u8                   *data;spinlock_t           lock;struct request_queue *queue;struct gendisk       *gd;
};#define MINOR_NO 1/* sector is the start sector, nsect is the number of sectors to transfer. */
static void vblkdev_transfer(struct vblkdev *dev, unsigned long sector,unsigned long nsect, char *buffer, int write) {unsigned long offset = sector * KERNEL_SECTOR_SIZE;unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;if ((offset + nbytes) > dev->size) {printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);return;}if (write)memcpy(dev->data + offset, buffer, nbytes);elsememcpy(buffer, dev->data + offset, nbytes);
}/* Transfer a single bio. */
static int vblkdev_xfer_bio(struct vblkdev *dev, struct bio *bio) {int i;struct bio_vec *bvec;sector_t sector = bio->bi_sector;/* Do each segment independently. */bio_for_each_segment(bvec, bio, i) {char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);vblkdev_transfer(dev, sector, bio_cur_sectors(bio),buffer, bio_data_dir(bio) == WRITE);sector += bio_cur_sectors(bio);__bio_kunmap_atomic(bio, KM_USER0);}return 0; /* Always "succeed" */
}/* Transfer a single request. */
static int vblkdev_xfer_request(struct vblkdev *dev, struct request *req) {struct bio *bio;int nsect = 0;rq_for_each_bio(bio, req) {vblkdev_xfer_bio(dev, bio);nsect += bio->bi_size / KERNEL_SECTOR_SIZE;}return nsect;
}/* Handle request. Called by kernel when the request_queue is not empty. */
static void vblkdev_request(request_queue_t *q) {struct request *req;int sectors_xferred;struct vblkdev *dev = q->queuedata;while ((req = elv_next_request(q)) != NULL) {if (! blk_fs_request(req)) {printk (KERN_NOTICE "Skip non-fs request\n");end_request(req, 0);continue;}sectors_xferred = vblkdev_xfer_request(dev, req);if (! end_that_request_first(req, 1, sectors_xferred)) {blkdev_dequeue_request(req);end_that_request_last(req, sectors_xferred);}}}static int vblkdev_open(struct inode *inode, struct file *filp) {struct vblkdev *dev = inode->i_bdev->bd_disk->private_data;filp->private_data = dev;spin_lock(&dev->lock);check_disk_change(inode->i_bdev);spin_unlock(&dev->lock);return 0;
}static int vblkdev_release(struct inode *inode, struct file *filp) {return 0;
}static struct block_device_operations vblkdev_ops = {.owner   = THIS_MODULE,.open    = vblkdev_open,.release = vblkdev_release
};static int setup_device(struct vblkdev *dev) {memset(dev, 0, sizeof(*dev));dev->size = nsectors * hardsect_size;dev->data = vmalloc(dev->size);if (dev->data == NULL) {printk(KERN_NOTICE "vmalloc failed\n");return -1;}spin_lock_init(&dev->lock);dev->queue = blk_init_queue(vblkdev_request, &dev->lock);if (dev->queue == NULL)goto out_vfree;blk_queue_hardsect_size(dev->queue, hardsect_size);dev->queue->queuedata = dev;dev->gd = alloc_disk(MINOR_NO);if (dev->gd == NULL) {printk(KERN_NOTICE "alloc disk failed\n");goto out_vfree;}dev->gd->major = vblkdev_major;dev->gd->first_minor = MINOR_NO;dev->gd->fops = &vblkdev_ops;dev->gd->queue = dev->queue;dev->gd->private_data = dev;snprintf(dev->gd->disk_name, 32, "myramblock");set_capacity(dev->gd, nsectors * (hardsect_size / KERNEL_SECTOR_SIZE));//设置capacity成员,等于扇区数add_disk(dev->gd);return 0;out_vfree:if (dev->data)vfree(dev->data);return -1;
}struct vblkdev *device;static int __init vblkdev_init(void) {/* Registered the driver. */vblkdev_major = register_blkdev(vblkdev_major, "myramblock");if (vblkdev_major <= 0) {printk(KERN_WARNING "vblkdev: unable to get major number\n");return -EBUSY;}device = kmalloc(sizeof(struct vblkdev), GFP_KERNEL);if (device == NULL)goto out_unregister;if (setup_device(device) != 0)goto out_unregister;return 0;out_unregister:unregister_blkdev(vblkdev_major, "myramblock");return -ENOMEM;
}static void __exit vblkdev_exit(void) {if (device->gd) {del_gendisk(device->gd);put_disk(device->gd);}if (device->queue) {blk_cleanup_queue(device->queue);}if (device->data)vfree(device->data);unregister_blkdev(vblkdev_major, "myramblock");kfree(device);
}module_init(vblkdev_init);
module_exit(vblkdev_exit);
MODULE_LICENSE("Dual BSD/GPL");

Makefile
两版本可使用同一个Makefile
这里的Makefile命令中。记得必须用Tab而不是空格,复制粘贴时Linux系统会自动进行缩进转化,必须手动调整。
KERNEL_DIR环境变量可以通过查看/usr/src/kernels/确认,或者如我一样使用命令替代:

KERNELS_DIR = /usr/src/kernels/`uname -r`
all:make -C $(KERNELS_DIR) M=`pwd` modules
clean:make -C $(KERNELS_DIR) M=`pwd` modules cleanrm -rf modules.order
obj-m += myramblock.o


然后直接make;
在该目录下,继续使用载入操作(root权限下进行)

insmod myramblock.ko
# /sbin/insmod myramblock.ko

之后在/dev/路径下可发现已初始化的驱动模块:(我做的时候没做命名修改。。。后面才改的)

初始化虚拟磁盘:mkdosfs /dev/myramblock
挂载磁盘目录: mount /dev/myramblock /tmp
然后进入/tmp下,编写部分文件(vi编辑器均可使用)。
我的内容是 This is my ramblock

卸载挂载目录 umount /tmp
之后生成cat /dev/myramblock > /home/nvidia/myramblock.bin
进行一次磁盘挂载: sudo mount -o loop /home/nvidia/myramblock.bin /mnt
进入/mnt目录下查看情况


两个版本均成功。

经典 【操作系统实验】 实验六 设备驱动程序 RH5 2.6.18 + 2.6.32 内核相关推荐

  1. 操作系统——中断处理程序及设备驱动程序

    中断处理程序及设备驱动程序 中断是指CPU在执行一个程序时,对系统中发生的某个事件做出的一个反应,它在操作系统中有着重要的有着重要的地位,时多道程序得以实现的基础. 引入缓冲区的原因: 外部中断:简称 ...

  2. 实验六 实现一个基于内存映射的设备驱动程序

    实验六 实现一个基于内存映射的设备驱动程序 目录 实验六 实现一个基于内存映射的设备驱动程序 实验环境 一.实验目的 二.实验内容 三.实验步骤 四.实验总结 实验环境 操作系统版本:ubuntu-1 ...

  3. can总线linux程序,CAN通信卡的Linux设备驱动程序 - 嵌入式操作系统 - 电子发烧友网...

    CAN通信卡的Linux设备驱动程序设计实现 目前,许多工业现场如电力系统.化工系统等大量使用控制器局部网(CAN--Controller Area Network)现场总线网络,CAN通信卡作为计算 ...

  4. 写一个块设备驱动程序

    ----------------------- Page 1----------------------- 第 1章 +---------------------------------------- ...

  5. 目前最全面深入的Linux设备驱动程序著作

    <精通Linux驱动程序开发(英文版)> 在Linux内核源代码树提供的各个子系统中,drivers/目录是其中最大的一个分支,它比其他子系统大数倍.随着各种新技术的广泛应用,内核中新的设 ...

  6. linux内核与设备驱动,第二章 Linux内核与设备驱动程序

    2.1 Linux操作系统 Linux操作系统由系统的启动.进程调度.虚拟内存管理器.文件系统.设备驱动程序等多个组成 2.2Linux内核 设备驱动程序中使用的函数也用在内核上,并且影响着内核,因此 ...

  7. ubuntu16.04下linux内核编译升级更新以及设备驱动程序的编写

    ​​​​​​最近学院里的nfc老师要我们做个设备驱动,之前从没接手过这个东西,加上老师给的材料错误也是一大堆,搞了差不多一个星期才搞出来.特此写一个博客,以免后来人和我一样跳坑. 总体考虑 要去写设备 ...

  8. Linux设备驱动程序概念

    所谓设备驱动程序就是控制与管理硬件设备收发的软件,它是应用程序与硬件设备沟通的桥梁.从本质上讲设备驱动程序主要负责硬件设备的数据读写.参数配置与中断配置. 设备驱动程序时操作系统的一部分,通常运行在内 ...

  9. 东北大学软件学院操作系统v实验报告

    课程编号:B080000070     <操作系统>实验报告             姓名   学号   班级   指导教师   实验名称 <操作系统>实验 开设学期 2016 ...

最新文章

  1. Android中Service生命周期、启动、绑定、混合使用
  2. CTFshow php特性 web111
  3. 文件服务器高可用群集,fastDFS文件服务器(三):集群和高可用环境篇
  4. python和shell哪个快_有没有可能让这个shell脚本更快?
  5. Execution Plan 执行计划介绍
  6. Linux 分割、合并文件
  7. vue --- 使用字符串'api'跨域请求资源
  8. linux epoll 开发指南-【ffrpc源码解析】
  9. Spring Mvc 入门Demo
  10. python abc
  11. Google App Engine已经支持JAVA了
  12. 从0到1简易区块链开发手册V0.6-实现打印区块
  13. JBOSS 5.0.0GA的集群搭建
  14. Word2013 设置默认缩进
  15. 如何在快手批量下载高清无水印视频方法
  16. 用电视上网——网络电视机顶盒
  17. 输入日期判断这一年的第几天
  18. 玩转docker之mysql容器(常见问题汇总-续更)
  19. 二进制与十进制转换器
  20. 01 KVM虚拟化简介

热门文章

  1. Unity教程2D入门:22 2D光效(ver. Unity2018)非URP
  2. 爬虫之获取当当网全部图书
  3. 匈牙利算法(处理二分图最大匹配问题)
  4. Service之电话管理器之获取网络和SIM卡信息
  5. 政务服务中心工作人员是公务员吗?
  6. 2021最新微信红包封面怎么获取?「红包封面」易烊千玺红包封面,速来领取
  7. 谈谈TL431与AZ431代换通用问题(个人经验)
  8. 对方正在输入 java_smark和openfire即时通信代码
  9. 关于Python中Inf与Nan的判断问题详解
  10. IntelliJ IDEA安装并激活、汉化