linux块设备驱动学习笔记(源码适用高版本内核)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Rocky_zhm/article/details/50372243

1. 字符设备与块设备 I/O 操作的不同如下。
(1)块设备只能以块为单位接受输入和返回输出,而字符设备则以字节为单位。大多数设备是字符设备,因为它们不需要缓冲而且不以固定块大小进行操作。
(2)块设备对于 I/O 请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无须缓冲且被直接读写。对于存储设备而言调整读写的顺序作用巨大,因为在读写连续的扇区比分离的扇区更快。
(3)字符设备只能被顺序读写,而块设备可以随机访问。虽然块设备可随机访问,但是对于磁盘这类机械设备而言,顺序地组织块设备的访问可以提高性能,而对 SD 卡、RamDisk 等块设备而言,不存在机械上的原因,进行这样的调整没有必要。


2. 块设备例程

将ldd3(linxu device driver 3)中块设备驱动部分的代码在kerne 3.19.0 编译时,会出现很多问题,主要是由于ldd3示例代码使用的内核版本较低(2.6.10版本),对于块设备子系统,很多接口都已经发生了改变,修改后的驱动代码如下ramdisk.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/fs.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

#define RAMDISK_NAME "ramdisk"
#define RAMDISK_MAX_DEVICE 1
#define RAMDISK_MAX_PARTITIONS 4

#define RAMDISK_SECTOR_SIZE 512
#define RAMDISK_SECTORS 16
#define RAMDISK_HEADS 4
#define RAMDISK_CYLINDERS 256

#define RAMDISK_SECTOR_TOTAL (RAMDISK_SECTORS * RAMDISK_HEADS * RAMDISK_CYLINDERS * 50) //16383 sectors
#define RAMDISK_SIZE (RAMDISK_SECTOR_SIZE * RAMDISK_SECTOR_TOTAL) //8MB = 16383 x 512k

typedef struct {
    unsigned char* data;
    struct request_queue* queue;
    struct gendisk* gd;
}RAMDISK_DEV;

static char* sdisk[RAMDISK_MAX_DEVICE] = {NULL};
static RAMDISK_DEV* rdev[RAMDISK_MAX_DEVICE] = {NULL};

static dev_t ramdisk_major;

static int ramdisk_space_init(void)
{
    int i;
    int err = 0;
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        sdisk[i] = vmalloc(RAMDISK_SIZE);
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
        if(!sdisk[i]){
            err = -ENOMEM;
            return err;
        }
        
        memset(sdisk[i], 0, RAMDISK_SIZE);
    }
    
    return err;
}

static void ramdisk_space_clean(void)
{
    int i;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        vfree(sdisk[i]);
    }
}

static int ramdisk_open(struct block_device* bdev, fmode_t mode)
{
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
    return 0;
}

static void ramdisk_release(struct gendisk*gd, fmode_t mode)
{
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);
    return;
}

static int ramdisk_ioctl(struct block_device* bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
    int err;
    struct hd_geometry geo;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);     
    switch(cmd)
    {
        case HDIO_GETGEO:
            err = !access_ok(VERIFY_WRITE, arg, sizeof(geo));
            if(err)
                return -EFAULT;
                
            geo.cylinders = RAMDISK_CYLINDERS;
            geo.heads = RAMDISK_HEADS;
            geo.sectors = RAMDISK_SECTORS;
            geo.start = get_start_sect(bdev);
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);             
            if(copy_to_user((void*)arg, &geo, sizeof(geo)))
                return -EFAULT;
            
            return 0;
    }
    
    return -ENOTTY;
}

static struct block_device_operations ramdisk_fops = {
    .owner = THIS_MODULE,
    .open = ramdisk_open,
    .release = ramdisk_release,
    .ioctl = ramdisk_ioctl,
};

static int ramdisk_make_request(struct request_queue* q, struct bio* bio)
{
    char* pRHdata;
    char* pBuffer;
    struct bio_vec bvec;
    int err = 0;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);     
    struct block_device* bdev = bio->bi_bdev;
    RAMDISK_DEV* pdev = bdev->bd_disk->private_data;
    
    if(((bio->bi_iter.bi_sector * RAMDISK_SECTOR_SIZE) + bio->bi_iter.bi_size) > RAMDISK_SIZE){
        err = -EIO;
        return err;
    }
    
    pRHdata = pdev->data + (bio->bi_iter.bi_sector * RAMDISK_SECTOR_SIZE);
    bio_for_each_segment(bvec, bio, bio->bi_iter){
        pBuffer = kmap(bvec.bv_page) + bvec.bv_offset;
        switch(bio_data_dir(bio)){
            case READ:
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
                memcpy(pBuffer, pRHdata, bvec.bv_len);
                flush_dcache_page(bvec.bv_page);
                break;
                
            case WRITE:
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);
                flush_dcache_page(bvec.bv_page);
                memcpy(pRHdata, pBuffer, bvec.bv_len);
                break;
                
            default:
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
                kunmap(bvec.bv_page);
                goto out;
        }
        
        kunmap(bvec.bv_page);
        pRHdata += bvec.bv_len;
    }
    
out:
    bio_endio(bio, err);
    return 0;
}

static int alloc_ramdev(void)
{
    int i;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        rdev[i] = kzalloc(sizeof(RAMDISK_DEV), GFP_KERNEL);
        if(!rdev[i]){
            return -ENOMEM;
        }
    }
    
    return 0;
}

static void clean_ramdev(void)
{
    int i;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);     
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        if(rdev[i])
            kfree(rdev[i]);
    }
}

static int __init ramdisk_init(void)
{
    int i;
    
    ramdisk_space_init();
    alloc_ramdev();
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__);    
    ramdisk_major = register_blkdev(0, RAMDISK_NAME);
    
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        rdev[i]->data = sdisk[i];
        rdev[i]->queue = blk_alloc_queue(GFP_KERNEL);
        blk_queue_make_request(rdev[i]->queue, ramdisk_make_request);
        
        rdev[i]->gd = alloc_disk(RAMDISK_MAX_PARTITIONS);
        rdev[i]->gd->major = ramdisk_major;
        rdev[i]->gd->first_minor = i * RAMDISK_MAX_PARTITIONS;
        rdev[i]->gd->fops = &ramdisk_fops;
        rdev[i]->gd->queue = rdev[i]->queue;
        rdev[i]->gd->private_data = rdev[i];
        sprintf(rdev[i]->gd->disk_name, "ramdisk%c", 'A' +i);
        rdev[i]->gd->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO;
        set_capacity(rdev[i]->gd, RAMDISK_SECTOR_TOTAL);
        add_disk(rdev[i]->gd);
    }
    
    return 0;
}

static void __exit ramdisk_exit(void)
{
    int i;
printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); 
    for(i = 0; i < RAMDISK_MAX_DEVICE; i++){
        del_gendisk(rdev[i]->gd);
        put_disk(rdev[i]->gd);
        blk_cleanup_queue(rdev[i]->queue);
    }
    
    clean_ramdev();
    ramdisk_space_clean();
    unregister_blkdev(ramdisk_major, RAMDISK_NAME);
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);

MODULE_AUTHOR("ZhongMing");
MODULE_DESCRIPTION("block device");
MODULE_LICENSE("GPL");


编译时,使用的makefile如下:

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

default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
	rm -rf Module.markers modules.order Module.symvers

验证方法

首先编译:
# make
make -C /lib/modules/3.19.0-41-generic/build M=/work/LinuxBSP/testcode/block-ram/ramdisk modules
make[1]: Entering directory `/usr/src/linux-headers-3.19.0-41-generic'
  CC [M]  /work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.o
/work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.c: In function ‘ramdisk_make_request’:
/work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.c:114:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
     struct block_device* bdev = bio->bi_bdev;
     ^
/work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.c: In function ‘ramdisk_init’:
/work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.c:189:9: warning: passing argument 2 of ‘blk_queue_make_request’ from incompatible pointer type [enabled by default]
         blk_queue_make_request(rdev[i]->queue, ramdisk_make_request);
         ^
In file included from /work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.c:9:0:
include/linux/blkdev.h:999:13: note: expected ‘void (*)(struct request_queue *, struct bio *)’ but argument is of type ‘int (*)(struct request_queue *, struct bio *)’
 extern void blk_queue_make_request(struct request_queue *, make_request_fn *);
             ^
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.mod.o
  LD [M]  /work/LinuxBSP/testcode/block-ram/ramdisk/ramdisk.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.19.0-41-generic'

#
加载模块
#sudo insmod ramdisk.ko
#
用lsmod看看。
这里我们注意到,该模块的Used by为0,因为它既没有被其他模块使用,也没有被mount。
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
如果当前系统支持udev,在调用add_disk()函数时即插即用机制会自动为我们在/dev/目录下建立设备文件。
设备文件的名称为我们在gendisk.disk_name中设置的simp_blkdev,主、从设备号也是我们在程序中设定的72和0。
如果当前系统不支持udev,那么很不幸,你需要自己用mknod /dev/simp_blkdev  b 72 0来创建设备文件了。
# ls -l /dev/ramdiskA
brw-rw---- 1 root disk 251, 0 12月 21 14:23 /dev/ramdiskA

扫描二维码关注公众号,回复: 3820677 查看本文章

创建块设备中的分区

sudo fdisk /dev/ramdiskA
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x7670efa4.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): n // 创建一个新分区
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p //选择分区类型,主分区(p)
Partition number (1-4, default 1): 1 // 分区编号,默认为1
First sector (2048-819199, default 2048): 2048 //分区起始sector默认为2048
Last sector, +sectors or +size{K,M,G} (2048-819199, default 819199): 200000 //设定分区大小

Command (m for help): w //保存设定的分区信息
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.


在块设备中创建文件系统,这里我们创建常用的ext3。
当然,作为通用的块设备,创建其他类型的文件系统也没问题。
# mkfs.ext3 /dev/ramdiskA
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
4096 inodes, 16384 blocks
819 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=16777216
2 block groups
8192 blocks per group, 8192 fragments per group
2048 inodes per group
Superblock backups stored on blocks:
        8193

Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 38 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
#
如果这是第一次使用,建议创建一个目录用来mount这个设备中的文件系统。
当然,这不是必需的。如果你对mount之类的用法很熟,你完全能够自己决定在这里干什么,甚至把这个设备mount成root。
# mkdir -p /mnt/temp1
#
把建立好文件系统的块设备mount到刚才建立的目录中
# mount /dev/ramdiskA /mnt/temp1
#
看看现在的mount表
# mount
...
/dev/ramdiskA on /mnt/temp1 type ext3 (rw)
#
看看现在的模块引用计数,从刚才的0变成1了,
原因是我们mount了。
# lsmod
Module                  Size  Used by
ramdiskA            16784008  1
...
#
看看文件系统的内容,有个mkfs时自动建立的lost+found目录。
# ls /mnt/temp1
lost+found
#
随便拷点东西进去
# cp /etc/init.d/* /mnt/temp1
#
再看看
# ls /mnt/temp1
acpid           conman              functions  irqbalance    mdmpd           NetworkManagerDispatcher  rdisc            sendmail        winbind
anacron         cpuspeed            gpm        kdump         messagebus      nfs                       readahead_early  setroubleshoot  wpa_supplicant
apmd            crond               haldaemon  killall       microcode_ctl   nfslock                   readahead_later  single          xfs
atd             cups                halt       krb524        multipathd      nscd                      restorecond      smartd          xinetd
auditd          cups-config-daemon  hidd       kudzu         netconsole      ntpd                      rhnsd            smb             ypbind
autofs          dhcdbd              ip6tables  lost+found    netfs           pand                      rpcgssd          sshd            yum-updatesd
avahi-daemon    dund                ipmi       lvm2-monitor  netplugd        pcscd                     rpcidmapd        syslog
avahi-dnsconfd  firstboot           iptables   mcstrans      network         portmap                   rpcsvcgssd       vmware
bluetooth       frecord             irda       mdmonitor     NetworkManager  psacct                    saslauthd        vncserver
#
现在这个块设备的使用情况是
# df
文件系统               1K-块        已用     可用 已用% 挂载点
...
/dev/ramdiskA         15863      1440     13604  10% /mnt/temp1
#
再全删了玩玩
# rm -rf /mnt/temp1/*
#
看看删完了没有
# ls /mnt/temp1
#
好了,大概玩够了,我们把文件系统umount掉
# umount /mnt/temp1
#
模块的引用计数应该还原成0了吧
# lsmod
Module                  Size  Used by
ramdiskA         16784008  0
...
#
最后一步,移除模块
# sudo rmmod ramdiskA
#

 

猜你喜欢

转载自blog.csdn.net/Rocky_zhm/article/details/50372243
今日推荐