linux内核之字符设备驱动图解

字符设备驱动分层结构图

         下图展示的是字符设备在打开(open)时的调用细节流程图,主要关注点是file_operations结构体的绑定操作。

         

字符设备在内核空间的组织结构图

      在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。

      在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数。

static struct char_device_struct {  
    struct char_device_struct *next;  
    unsigned int major;  
    unsigned int baseminor;  
    int minorct;  
    char name[64];  
    struct cdev *cdev;      /* will die */  
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 

      内核中的每一个字符设备驱动程序都由一个 char_device_struct 结构体来描述,包含主设备号、起始次设备号、次设备号个数等信息。

      内核使用 chrdevs 这个指针数组来管理所有的字符设备驱动程序,数组范围 0-255 ,看上去好像还是只支持 256 个字符设备驱动程序,其实并不然,每一个 char_device_struct 结构都含有一个 next 指针,它可以指向与其主设备号相同的其它字符设备驱动程序,它们之间主设备号相同,各自的次设备号范围相互不重叠。

                101804_si9o_2896894.png

字符设备驱动模板

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/device.h>
#include <linux/init.h>  
#include <linux/delay.h>  
#include <linux/irq.h>  
#include <asm/uaccess.h>  
#include <asm/irq.h>  
#include <asm/io.h>  
#include <linux/poll.h>  
#include <linux/cdev.h>  
  
/* 1. 确定主设备号 */  
static int major;  
  
static int hello_open(struct inode *inode, struct file *file)  
{  
    printk("hello_open\n");  
    return 0;  
}  

/*读函数*/
static ssize_t hello_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  char data[] = "hello world."
  /*读数据到用户空间:内核空间->用户空间交换数据*/  
  if (copy_to_user(buf, (void*)data, sizeof(data)))
  {
    ret =  - EFAULT;
  }

  return ret;
}
  
/* 2. 构造file_operations */  
static struct file_operations hello_fops = {  
    .owner = THIS_MODULE,  
    .open  = hello_open,  
    .read = hello_read,
};  
  
  
#define HELLO_CNT   2  
  
static struct cdev hello_cdev;  
static struct class *cls;  
  
static int hello_init(void)  
{  
    dev_t devid;  
      
    /* 3. 告诉内核 */  
#if 0 // 老方法  
    /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */  
    major = register_chrdev(0, "hello", &hello_fops);   
#else // 新方法  
    if (major) {  
        devid = MKDEV(major, 0);  
         /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */  
        register_chrdev_region(devid, HELLO_CNT, "hello");   
    } else {  
        /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */  
        alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");   
        major = MAJOR(devid);                       
    }  
      
    cdev_init(&hello_cdev, &hello_fops);  
    cdev_add(&hello_cdev, devid, HELLO_CNT);  
    
#endif  
  
    cls = class_create(THIS_MODULE, "hello");  
    device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */  
    device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */  
      
    return 0;  
}  
  
static void hello_exit(void)  
{  
    device_destroy(cls, MKDEV(major, 0));  
    device_destroy(cls, MKDEV(major, 1));   
    class_destroy(cls);  
  
    cdev_del(&hello_cdev);  
    unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);  
}  
  
module_init(hello_init);  
module_exit(hello_exit);  
  
  
MODULE_LICENSE("GPL"); 

 下面是Makefile

ifneq ($(KERNELRELEASE),)
     obj-m:=hello.o
else
     KERNELDIR?=/lib/modules/$(shell uname -r)/build
     PWD:=$(shell pwd)
default:
     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

下面可以写一个应用程序测试一下:

#include <stdio.h>

int main()
{
    FILE *fp = NULL;
    char buf[4096];

    /*打开设备文件*/
    fp = fopen("/dev/hello0","r+");
    if (fp == NULL)
    {
        return -1;
    }
   
    /*读设备*/
    fread(buf, sizeof(buf), 1, fp);
    
    /*检测结果*/
    printf("buf: %s\n",buf);
    
    return 0;    
}

 

 

转载于:https://my.oschina.net/fileoptions/blog/951984

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值