Linux内核编程初探:块设备驱动程序——Ramdisk

第一个步骤: 编写hello world驱动程序

       (1) 构造内核源码树

       (2) 到hello.c文件目录下执行make,生成hello.ko文件以及其他相关文件

       (3) 执行sudo insmod  ./hello.ko加载模块

       (4)执行Ismod   (查看模块的命令)就可以看到hello模块

       (5) sudo  rmmod hello

Makefile文件内容:

obj-m := ramhd_req.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
	$(MAKE)  -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -f *.o *.ko *.mod.* *.order *.symvers

代码分析:int register_blkdev(unsigned int major, const char *name);major 参数是块设备要使用的主设备号,name为设备名,它会在/proc/devices中被显示。 如果major为0,内核会自动分配一个新的主设备号register_blkdev()函数的返回值就是这个主设备号。如果返回1个负值,表明发生了一个错误。

kmalloc和vmalloc的区别
1、kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续
2、kmalloc能分配的大小有限,vmalloc能分配的大小相对较大
3、vmalloc比kmalloc要慢
4、kmallloc使用的是slab内存分配机制,而vmalloc使用的是伙伴系统分配机制,这也是造成它们区别的根本所在

return   -EFAULT;   //errno:14   地址错
return   -EAGAIN;   //errno:11   资源暂时不可用
return   -EINTR;   //errno:4     中断的函数调用
return   -ESPIPE     //errno:29   无效的文件指针重定位
return   -ENOTTY;//errno:25     不适当的IO控制操作

重点内容:.owner = THIS_MODULE为什么加“点”的原因
这种方式称为指定初始化  源自ISO C99标准 初始化不必严格按照定义时的顺序

#include <linux/module.h>     //支持动态添加和卸载模块
#include <linux/kernel.h>    //驱动要写入内核,与内核相关的头文件
#include <linux/init.h>      //初始化头文件

#include <linux/fs.h>        //包含了文件操作相关struct的定义
#include <linux/types.h>     //对一些特殊类型的定义
#include <linux/fcntl.h>     //定义了文件操作等所用到的相关宏
#include <linux/vmalloc.h>  //vmalloc()分配的内存虚拟地址上连续,物理地址不连续
#include <linux/blkdev.h>  //采用request方式 块设备驱动程序需要调用blk_init_queue 分配请求队列
#include <linux/hdreg.h> //硬盘参数头文件,定义访问硬盘寄存器端口、状态码和分区表等信息。

#define RAMHD_NAME              "Mao_rd"    //设备名称
#define RAMHD_MAX_DEVICE        2           //最大设备数
#define RAMHD_MAX_PARTITIONS    4           //最大分区数

#define RAMHD_SECTOR_SIZE       512        //扇区大小
#define RAMHD_SECTORS           16         //扇区数  http://www.embedu.org/Column/Column863.htm
#define RAMHD_HEADS             4         //磁头数
#define RAMHD_CYLINDERS         256      //磁道(柱面)数 

#define RAMHD_SECTOR_TOTAL      (RAMHD_SECTORS * RAMHD_HEADS * RAMHD_CYLINDERS)  //总大小
#define RAMHD_SIZE              (RAMHD_SECTOR_SIZE * RAMHD_SECTOR_TOTAL) //8MB

typedef struct{
    unsigned char   *data;             //设备数据空间首地址
    struct request_queue *queue;       //设备请求队列
    spinlock_t      lock;             //互斥自旋锁
    struct gendisk  *gd;              //通用磁盘结构体
}RAMHD_DEV;

static char *sdisk[RAMHD_MAX_DEVICE];  //分配内存的首地址
static RAMHD_DEV *rdev[RAMHD_MAX_DEVICE];  //分配内存的首地址

static dev_t ramhd_major;   //主设备号

static int ramhd_space_init(void)
{
    int i;
    int err = 0;
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){
        sdisk[i] = vmalloc(RAMHD_SIZE);  //申请RAMBLK_SIZE内存  物理地址不连续,虚拟地址连续
        if(!sdisk[i]){
            err = -ENOMEM;  //errno:12 内存不足
            return err;
        }
        memset(sdisk[i], 0, RAMHD_SIZE);  //用0来初始化分配的内存空间
    }

    return err;
}

static void ramhd_space_clean(void)
{
    int i;
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){
        vfree(sdisk[i]);                   //释放分配的内存
    }
}

static int alloc_ramdev(void)
{
    int i;
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){
        rdev[i] = kzalloc(sizeof(RAMHD_DEV), GFP_KERNEL); //向内核申请存放RAMHD_DEV结构体的内存空间
        if(!rdev[i])
            return -ENOMEM;   //errno:12  内存不足
    }
    return 0;
}

static void clean_ramdev(void)
{
    int i;
    for(i = 0; i < RAMHD_MAX_DEVICE; i++){
        if(rdev[i])
            kfree(rdev[i]);   //释放分配的内存
    }   
}       

int ramhd_open(struct block_device *bdev, fmode_t mode)   //设备打开用到
{   
    return 0;
}

void ramhd_release(struct gendisk *gd, fmode_t mode)   //设备关闭用到
{   
    
}

static int ramhd_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) //IO控制
{
    int err;
    struct hd_geometry geo;  //hd_geometry结构体包含磁头,扇区,柱面等信息

    switch(cmd)
    {
        case HDIO_GETGEO:  //获取块设备的物理参数 
            err = !access_ok(VERIFY_WRITE, arg, sizeof(geo));//检查指针所指向的存储块是否可写
            if(err) return -EFAULT;     //errno:14   地址错 
        
            geo.cylinders = RAMHD_CYLINDERS;     //柱面数
            geo.heads = RAMHD_HEADS;            //磁头数
            geo.sectors = RAMHD_SECTORS;        //扇区数
            geo.start = get_start_sect(bdev);   //起始地址
            if(copy_to_user((void *)arg, &geo, sizeof(geo)))
             //把内核地址&geo指示的数据复制到arg指代的用户空间的地址上
                return -EFAULT;   //errno:14   地址错 
            return 0;
    }
            
    return -ENOTTY;      //errno:25     不适当的IO控制操作 
}

static struct block_device_operations ramhd_fops =  //用来描述一个块设备的操作函数集
{   .owner = THIS_MODULE,//“加点”这种方式称为指定初始化  源自ISO C99标准 初始化不必严格按照定义时的顺序
    .open = ramhd_open,
    .release = ramhd_release,
    .ioctl = ramhd_ioctl,
};

void ramhd_req_func (struct request_queue *q)  //处理传递给这个设备的请求
{
    struct request *req;  //用来提取req
	RAMHD_DEV *pdev;
	char *pData;
	unsigned long addr, size, start;
	req = blk_fetch_request(q); //从块设备队列提取存储的req;
        //blk_fetch_request()可以多次调用,如果queue里面没有内容,req将返回NULL
	while (req) {   //判断当前request是否合法  循环从请求队列中获取下一个要处理的请求
		start = blk_rq_pos(req); // 获取当前request结构的起始扇区 
		pdev = (RAMHD_DEV *)req->rq_disk->private_data; //获得设备结构体指针
		pData = pdev->data;//设备地址
		addr = (unsigned long)pData + start * RAMHD_SECTOR_SIZE;//计算地址
        size = blk_rq_cur_bytes(req); //访问 req 的下一段数据
		if (rq_data_dir(req) == READ) //获得数据传送方向.返回0表示从设备读取,否则表示写向设备.
			memcpy(req->buffer, (char *)addr, size); //读
		else
			memcpy((char *)addr, req->buffer, size); //写
	
		if(!__blk_end_request_cur(req, 0))  //这个函数处理完返回false
			req = blk_fetch_request(q);  //继续取出请求队列中的请求
	}
}

int ramhd_init(void)   //初始化
{
    int i;
    ramhd_space_init();
    alloc_ramdev();
    
    ramhd_major = register_blkdev(0, RAMHD_NAME); //块设备驱动注册到内核中
    //major为0,内核会自动分配一个新的主设备号(ramhd_major )  
    for(i = 0; i < RAMHD_MAX_DEVICE; i++)
    {
        rdev[i]->data = sdisk[i]; 
        rdev[i]->gd = alloc_disk(RAMHD_MAX_PARTITIONS);
        spin_lock_init(&rdev[i]->lock);  //初始化自旋锁
        rdev[i]->queue = blk_init_queue(ramhd_req_func, &rdev[i]->lock);//初始化将ramhd_req_func函数与队列绑定
        rdev[i]->gd->major = ramhd_major; 
        rdev[i]->gd->first_minor = i * RAMHD_MAX_PARTITIONS;
        rdev[i]->gd->fops = &ramhd_fops;  //关联到这个设备的方法集合
        rdev[i]->gd->queue = rdev[i]->queue;
        rdev[i]->gd->private_data = rdev[i]; //使用这个成员来指向分配的数据
        sprintf(rdev[i]->gd->disk_name, "ram_MaoRi_%c", 'a'+i);
        set_capacity(rdev[i]->gd, RAMHD_SECTOR_TOTAL);
        add_disk(rdev[i]->gd);   //向系统中添加这个块设备
    }
        
    return 0;
}

void ramhd_exit(void)  //模块卸载函数
{
    int i;
    for(i = 0; i < RAMHD_MAX_DEVICE; i++)
    {
        del_gendisk(rdev[i]->gd); //删除gendisk结构体   
        put_disk(rdev[i]->gd);   //减少gendisk结构体的引用计数
        blk_cleanup_queue(rdev[i]->queue);  //清除请求对列
    }
    unregister_blkdev(ramhd_major,RAMHD_NAME); ; //注销块设备 
    clean_ramdev();
    ramhd_space_clean();  
}

module_init(ramhd_init);
module_exit(ramhd_exit);

MODULE_AUTHOR("MaoRi");
MODULE_DESCRIPTION("The Ramdisk implementation with request function");
MODULE_LICENSE("GPL");

参考文献:(1) ubantu14.04 32位下第一个hello world驱动程序

                         http://blog.csdn.net/damotiansheng/article/details/44463193


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值