Linux 块设备驱动代码编写

按照ldd的说法,linux的设备驱动包括了char,block,net三种设备。char设备是比较简单的,只要分配了major、minor号,就可以进行读写处理了。相对而言,block和net要稍微复杂些。net设备姑且按下不谈,我们在以后的博文中会有涉及。今天,我们可以看看一个简单的block是怎么设计的。

为了将block和fs分开,kernel的设计者定义了request queue这一种形式。换一句话说,所有fs对block设备的请求,最终都会转变为request的形式。所以,对于block设备驱动开发的朋友来说,处理好了request queue就掌握了block设备的一半。当然,block设备很多,hd、floppy、ram都可以这么来定义,有兴趣的朋友可以在drivers/block寻找相关的代码来阅读。兴趣没有那么强的同学,可以看看我们这篇博文,基本上也能学个大概。有个基本的概念,再加上一个简单浅显的范例,对于一般的朋友来说,已经足够了。

闲话不多说,我们看看一个ramdisk代码驱动是怎么写的,代码来自《深入linux 设备驱动程序内核机制》,

#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 RAMHD_NAME "ramhd"
#define RAMHD_MAX_DEVICE 2
#define RAMHD_MAX_PARTITIONS 4 

#define RAMHD_SECTOR_SIZE 512
#define RAMHD_SECTORS 16
#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;
  struct gendisk* gd;
}RAMHD_DEV; 

static char* sdisk[RAMHD_MAX_DEVICE] = {NULL};
static RAMHD_DEV* rdev[RAMHD_MAX_DEVICE] = {NULL}; 

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);
    if(!sdisk[i]){
      err = -ENOMEM;
      return err;
    } 

    memset(sdisk[i], 0, RAMHD_SIZE);
  } 

  return err;
} 

static void ramhd_space_clean(void)
{
  int i;
  for(i = 0; i < RAMHD_MAX_DEVICE; i++){
    vfree(sdisk[i]);
  }
} 

static int ramhd_open(struct block_device* bdev, fmode_t mode)
{
  return 0;
} 

static int ramhd_release(struct gendisk*gd, fmode_t mode)
{
  return 0;
} 

static int ramhd_ioctl(struct block_device* bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
  int err;
  struct hd_geometry geo; 

  switch(cmd)
  {
    case HDIO_GETGEO:
      err = !access_ok(VERIFY_WRITE, arg, sizeof(geo));
      if(err)
        return -EFAULT; 

      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)))
        return -EFAULT; 

      return 0;
  } 

  return -ENOTTY;
} 

static struct block_device_operations ramhd_fops = {
  .owner = THIS_MODULE,
  .open = ramhd_open,
  .release = ramhd_release,
  .ioctl = ramhd_ioctl,
}; 

static int ramhd_make_request(struct request_queue* q, struct bio* bio)
{
  char* pRHdata;
  char* pBuffer;
  struct bio_vec* bvec;
  int i;
  int err = 0; 

  struct block_device* bdev = bio->bi_bdev;
  RAMHD_DEV* pdev = bdev->bd_disk->private_data; 

  if(((bio->bi_sector * RAMHD_SECTOR_SIZE) + bio->bi_size) > RAMHD_SIZE){
    err = -EIO;
    return err;
  } 

  pRHdata = pdev->data + (bio->bi_sector * RAMHD_SECTOR_SIZE);
  bio_for_each_segment(bvec, bio, i){
    pBuffer = kmap(bvec->bv_page) + bvec->bv_offset;
    switch(bio_data_dir(bio)){
      case READ:
        memcpy(pBuffer, pRHdata, bvec->bv_len);
        flush_dcache_page(bvec->bv_page);
        break; 

      case WRITE:
        flush_dcache_page(bvec->bv_page);
        memcpy(pRHdata, pBuffer, bvec->bv_len);
        break; 

      default:
        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;
  for(i = 0; i < RAMHD_MAX_DEVICE; i++){
    rdev[i] = kzalloc(sizeof(RAMHD_DEV), GFP_KERNEL);
    if(!rdev[i]){
      return -ENOMEM;
    }
  } 

  return 0;
} 

static void clean_ramdev(void)
{
  int i; 

  for(i = 0; i < RAMHD_MAX_DEVICE; i++){
    if(rdev[i])
      kfree(rdev[i]);
  }
} 

static int __init ramhd_init(void)
{
  int i; 

  ramhd_space_init();
  alloc_ramdev(); 

  ramhd_major = register_blkdev(0, RAMHD_NAME); 

  for(i = 0; i < RAMHD_MAX_DEVICE; i++){
    rdev[i]->data = sdisk[i];
    rdev[i]->queue = blk_alloc_queue(GFP_KERNEL);
    blk_queue_make_request(rdev[i]->queue, ramhd_make_request); 

    rdev[i]->gd = alloc_disk(RAMHD_MAX_PARTITIONS);
    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, "ramhd%c", 'a' +i);
    rdev[i]->gd->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO;
    set_capacity(rdev[i]->gd, RAMHD_SECTOR_TOTAL);
    add_disk(rdev[i]->gd);
  } 

  return 0;
} 

static void __exit ramhd_exit(void)
{
  int i;
  for(i = 0; i < RAMHD_MAX_DEVICE; i++){
    del_gendisk(rdev[i]->gd);
    put_disk(rdev[i]->gd);
    blk_cleanup_queue(rdev[i]->queue);
  } 

  clean_ramdev();
  ramhd_space_clean();
  unregister_blkdev(ramhd_major, RAMHD_NAME);
} 

module_init(ramhd_init);
module_exit(ramhd_exit); 

MODULE_AUTHOR("dennis__chen@ AMDLinuxFGL");
MODULE_DESCRIPTION("The ramdisk implementation with request function");
MODULE_LICENSE("GPL"); 

为了大家方便,顺便也把Makefile放出来,看过前面blog的朋友都知道,这其实很简单,

ifneq ($(KERNELRELEASE),)
obj-m := ramdisk.o 

else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
  $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
  rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif

这段代码究竟有没有用呢?可以按照下面的步骤来做,

a)make 一下,生成ramdisk.ko;

b)编译好了之后,就可以安装驱动了,在linux下是这么做的,sudo insmod ramdisk.ko;

c)安装好了,利用ls /dev/ramhd*, 就会发现在/dev下新增两个结点,即/dev/ramhda和/dev/ramhdb;

d)不妨选择其中一个节点进行分区处理, sudo fdisk /dev/ramhda,简单处理的话就建立一个分区, 生成/dev/ramhda1;

e)创建文件系统,sudo mkfs.ext3 /dev/ramhda1;

f)有了上面的文件系统,就可以进行mount处理,不妨sudo mount /dev/ramhda1 /mnt;

g)上面都弄好了,大家就可以copy、delete文件试试了,是不是很简单。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • 详解linux usb host驱动编写入门
  • 详解linux 摄像头驱动编写
  • 详解linux电源管理驱动编写
  • 详解linux 看门狗驱动编写
  • linux nand flash驱动编写
  • 详解linux驱动编写(入门)
  • 详解linux 驱动编写(sd卡驱动)
  • 详解linux dma驱动编写
  • 详解linux lcd驱动编写
  • linux 触摸屏驱动编写
  • 详解linux pwm驱动编写
(0)

相关推荐

  • linux nand flash驱动编写

    很长一段时间,nand flash都是嵌入式的标配产品.nand flash价格便宜,存储量大,适用于很多的场景.现在很普及的ssd,上面的存储模块其实也是由一块一块nand flash构成的.对于linux嵌入式来说,开始uboot的加载是硬件完成的,中期的kernel加载是由uboot中的nand flash驱动完成的,而后期的rootfs加载,这就要靠kernel自己来完成了.当然,这次还是以三星s3c芯片为例进行说明. 1.nand flash驱动在什么地方,可以从drviers/mtd

  • 详解linux 驱动编写(sd卡驱动)

    随着sd卡的流行,sd卡在嵌入式设备上使用的场景也越来越多.那下面我们可以看一下,linux驱动框架上是怎么处理sd卡驱动的? 1.代码目录地址 drivers/mmc 2.基本结构 从mmc的代码结构可以看得出,主要分为两个部分,其中core为协议部分,host为各个soc的适配部分 host是我们需要真正关心的代码 3.以s3c为例,观察makefile obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o ...... obj-$(CONFIG_MMC_S3

  • 详解linux usb host驱动编写入门

    usb协议是一个复杂的协议,目前涉及到的版本就有usb1.0, usb2.0, usb3.0.大家如果打开kernel usb host目录,就会发现下面包含了ohci,uhci,ehci,xhci,whci等多种形式的控制器驱动.那么,对于我们这些不是很了解usb的开发人员,如何了解usb的代码结构呢? 1.代码分布 drivers/usb目录下面,host目录包括了host驱动代码,core目录包含了主要的api接口代码,而其他目录则主要是device驱动代码. 2.device驱动怎么看

  • 详解linux驱动编写(入门)

    在我离职之前,工作内容几乎不涉及到驱动方面的知识.我所要做的内容就是把客户对设备的请求拆分成一个一个的接口,调用驱动的设置进行配置就可以了.当然,至于驱动下面是怎么实现那就要根据具体情况而定了.比如说,有的驱动是芯片厂商直接写好的,假设芯片厂商提供了对应平台的sdk函数,那么驱动的工作就是对这些sdk函数进行封装就可以了,另外一种就是自己编写具体平台的驱动接口了.比如说,现在你需要编写串口.i2c.i2s.FLASH.网卡.LCD.触摸屏.USB驱动了.这个时候,你手里面除了一堆芯片手册,啥也没

  • 详解linux 摄像头驱动编写

    对于现代嵌入式设备,特别是手机来说,摄像头是很重要的一个设备.很多同学买手机,一看颜值,第二就看摄像头拍照如何.所以,从某个角度来说,摄像头是各个厂家主打的应用功能.那么,linux是如何支持摄像头的,我们可以来看一下? 1.代码目录地址 drivers/media 2.v4l2框架 目前linux上的camera都是按照v4l2框架来设计,它的地址位于drivers/media/v4l2-core 3.查看三星soc是如何支持camera的,可以查看drviers/media/platform

  • 详解linux电源管理驱动编写

    对于嵌入式设备来说,合适的电源管理,不仅可以延长电池的寿命,而且可以省电,延长设备运行时间,在提高用户体验方面有很大的好处.所以,各个soc厂家在这方面花了很多的功夫.下面,我们可以看看linux是如何处理电源管理驱动的. 1.代码目录 drivers/regulator 2.查看目录下的Kconfig文件 menuconfig REGULATOR bool "Voltage and Current Regulator Support" help Generic Voltage and

  • 详解linux pwm驱动编写

    pwm方波可以用来控制很多的设备,比如它可以被用来控制电机.简单来说,就是单位时间内的方波越多,那么电机的转速就会越快:反之就越慢.通过这个特性,soc就可以轻松地利用pwm对外设进行自动控制.所以,今天的主题就是pwm驱动. 1.驱动目录 drivers/pwm 2.查看对应目录下的Kconfig config PWM_SAMSUNG tristate "Samsung PWM support" depends on PLAT_SAMSUNG || ARCH_EXYNOS help

  • 详解linux lcd驱动编写

    有些嵌入式设备是不需要lcd的,比如路由器.但是,还有些设备是需要lcd显示内容的,比如游戏机.测试仪.智能手表等等.所以,今天我们就看看lcd驱动在linux上是怎么进行的. 1.代码目录 drivers/video 2.查看video下的Makefile文件 # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_VGASTATE) += vgastate.o obj-$(CONFIG_HDMI) += hdmi.o obj-$(CONFIG_VT)

  • 详解linux 看门狗驱动编写

    看门狗是linux驱动的一个重要环节.某些特殊的设备,有时候需要放在一些环境恶劣的地方,比如电信设备.但是,任何软件都不可能100%没有bug.如何保证软件在遇到严重bug.死机的时候也能正常运行呢,那么看门狗就是有效的一种方法.看门狗一般要求用户定时喂狗,如果一段时间没有喂狗的话,那么系统就会自动重启.今天,我们就来看看这个看门狗驱动怎么编写? 1.代码目录 drivers/watchdog 2.阅读目录下的Kconfig,可以找一个s3c模块macro config HAVE_S3C2410

  • 详解linux dma驱动编写

    linux下面的驱动虽然什么样的情形都有,但是dma驱动却并不少见.dma可以有很多的好处,其中最重要的功能就是能够帮助我们将数据搬来搬去,这个时候cpu就由时间去做别的事情了,提高了设备效率. 1.dma驱动在什么地方 drivers/dma 2.如何看s3c的dma驱动,先看Kconfig config S3C24XX_DMAC bool "Samsung S3C24XX DMA support" depends on ARCH_S3C24XX || COMPILE_TEST se

  • linux 触摸屏驱动编写

    早在诺基亚手机还比较流行的时候,那时候触摸屏用的还不多.但是随着触摸屏手机.即智能手机的流行,触摸屏基本成了手机的标配.所以,今天可以看看触摸屏驱动在linux上是如何进行的. 1.驱动目录 drivers/input 2.看看这个目录的Makefile如何设计 obj-$(CONFIG_INPUT) += input-core.o input-core-y := input.o input-compat.o input-mt.o ff-core.o obj-$(CONFIG_INPUT_TOU

随机推荐