`
coolerbaosi
  • 浏览: 728937 次
文章分类
社区版块
存档分类
最新评论

linux设备驱动--字符设备模型

 
阅读更多

linux设备驱动--字符设备模型

最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步。

作者:liufei_learning(转载请注明出处)

email:flying0216@foxmail.com

IT学习交流群:160855096

开发环境:Win7(主机)+ VisualBox + ubuntu10.10(虚拟机) + TQ2440开发板(2.6.30.4内核)

功能: 1.linux设备驱动--LED驱动cdev实现led驱动的编写

2.学习字符设备模型(转载)

目录:1.cdev实现led驱动的编写

1)实现及源码

2cdev结构分析

2.学习字符设备模型

1)字符设备模型

2)字符设备的设备号

3)文件系统中对字符设备文件的访问


cdev实现led驱动的编写

在上一节代码基础上修改的,详细看linux设备驱动--LED驱动

/*
 * tq2440_leds.c
 *
 *  Created on: 2011-11-29
 *      Author: liufei_learning
 *
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>

#define DEVICE_NAME	"tq2440_leds"    //设备名称
#define LED_MAJOR	231                //主设备号
#define IOCTL_LED_ON	1                 //LED亮状态
#define IOCTL_LED_OFF	0                //LED灭状态

static led_major = LED_MAJOR;
struct cdev led_cdev; 
//控制LED的IO口
static unsigned long led_table[] =
{
	S3C2410_GPB5,
	S3C2410_GPB6,
	S3C2410_GPB7,
	S3C2410_GPB8,
};

//LED IO口的模式
static unsigned int led_cfg_table[] =
{
	S3C2410_GPB5_OUTP,
	S3C2410_GPB6_OUTP,
	S3C2410_GPB7_OUTP,
	S3C2410_GPB8_OUTP,
};

static struct class *leds_class;

static int __init leds_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int __init leds_ioctl(struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg)
{
	//检测是第几个LED,因开发板上只有4个,索引从0开始
	if(arg < 0 || arg > 3)
	{
		return -EINVAL;
	}

	//判断LED要执行哪种状态
	switch(cmd)
	{
		case IOCTL_LED_ON:
		{
			s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_ON));
			break;
		}
		case IOCTL_LED_OFF:
		{
			s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_OFF));
			break;
		}
		default:
		{
			return -EINVAL;
		}
	}

	return 0;
}

static struct file_operations led_fops =
{
	.owner = THIS_MODULE,
	.open = leds_open,
	.ioctl = leds_ioctl,
};

static int __init leds_init(void)
{
	int ret;
	int i;
	for(i = 0; i < 4; i++)
	{
		//初始化各IO口为输出模式
		s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
		//由原理图可知LED电路是共阳极的(即各IO口输出低电平0才会点亮)
		//这里初始化为1,不让LED点亮
		s3c2410_gpio_setpin(led_table[i], ~(IOCTL_LED_OFF));
	}


    dev_t devno = MKDEV(led_major, 0);
    /* 静态申请设备号*/
    if (led_major)
      ret = register_chrdev_region(devno, 2, DEVICE_NAME);
    else /* 动态分配设备号 */
    {
      ret = alloc_chrdev_region(&devno, 0, 2, DEVICE_NAME);
      led_major = MAJOR(devno);
    } 
    if (ret < 0)
      return ret;
	 
	/*初始化cdev结构*/
    cdev_init(&led_cdev, &led_fops);
    led_cdev.owner = THIS_MODULE;
    led_cdev.ops = &led_fops;
  
    /* 注册字符设备 */
    cdev_add(&led_cdev, MKDEV(led_major, 0), 1);
	

	//注册一个类,使mdev可以在/dev/下面建立设备节点
	leds_class = class_create(THIS_MODULE, DEVICE_NAME);
	if( IS_ERR(leds_class) )
	{
		printk("creat leds_class failed!");
		return -1;
	}

	//创建一个设备节点,节点名字为DEVICE_NAME
	device_create(leds_class, NULL, MKDEV(led_major, 0), NULL, DEVICE_NAME);
	printk(DEVICE_NAME "initialized!");
	return 0;
}

static void __init leds_exit(void)
{
    cdev_del(&led_cdev); /*注销设备*/
    unregister_chrdev_region(MKDEV(led_major, 0), 2); /*释放设备号*/
	//删除设备节点
	device_destroy(leds_class, MKDEV(led_major, 0));
	//注销类
	class_destroy(leds_class);
}

module_init( leds_init);
module_exit( leds_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("liufei_learning");

MODULE_DESCRIPTION("tq2440 leds driver");


分析:
Linux/cdev.h

在Linux内核中使用cdev结构体描述字符设备。该结构体是所有字符设备的抽象,其包含了大量字符设备所共有的特性。cdev结构体定义如下:

struct cdev {
    struct kobject kobj;       
                           /*内嵌的kobject结构,用于内核设备驱动模型的管理*/
    struct module *owner;  
                           /*指向包含该结构的模块的指针,用于引用计数*/
    const struct file_operations *ops;      /*指向字符设备操作函数集的指针*/
    struct list_head list;     
                           /*该结构将使用该驱动的字符设备连接成一个链表*/
    dev_t dev;              /*该字符设备的起始设备号,一个设备可能有多个设备号*/
    unsigned int count;     /*使用该字符设备驱动的设备数量*/
};


cdev结构中的kobj结构用于内核管理字符设备,驱动开发人员一般不使用该成员。ops是指向file_operations结构的指针,该结构定义了操作字符设备的函数。dev就是用来存储字符设备所申请的设备号。count表示目前有多少个字符设备在使用该驱动程序。当使用rmmod卸载模块时,如果count成员不为0,那么系统不允许卸载模块。

list结构是一个双向链表,用于将其他结构体连接成一个双向链表。structlist_head {

struct list_head *next, *prev;

};

cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。这样,使cdev结构与inode结点组成了一个双向链表。inode结构体表示/dev目录下的设备文件。

每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点。这样可以通过inode结点的i_cdev字段找到cdev字符结构体。通过cdevops指针,就能找到设备A的操作函数。

可以使用如下宏调用来获得主、次设备号:

MAJOR(dev_t dev)

MINOR(dev_t dev)

一个 cdev一般它有两种定义初始化方式:静态的和动态的。

静态内存定义初始化:

struct cdevmy_cdev;

cdev_init(&my_cdev,&fops);

my_cdev.owner= THIS_MODULE;

动态内存定义初始化:

struct cdev*my_cdev = cdev_alloc();

my_cdev->ops= &fops;

my_cdev->owner= THIS_MODULE;

两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

下面贴出了两个函数的代码,以具体看一下它们之间的差异。

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
 530{
 531       memset(cdev, 0, sizeof *cdev);
 532       INIT_LIST_HEAD(&cdev->list);
 533       kobject_init(&cdev->kobj, &ktype_cdev_default);
 534       cdev->ops = fops;
 535}
 
struct cdev*cdev_alloc(void)
 512{
 513       struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
 514       if (p) {
 515               INIT_LIST_HEAD(&p->list);
 516                kobject_init(&p->kobj,&ktype_cdev_dynamic);
 517       }
 518       return p;
 519}


由此可见,两个函数完成都功能基本一致,只是cdev_init() 还多赋了一个 cdev->ops 的值。

初始化 cdev后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。

intcdev_add(struct cdev *p, dev_t dev, unsigned count)
{
  p->dev = dev;
  p->count = count;
  return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

关于 kobj_map()函数就不展开了,我只是大致讲一下它的原理。内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev结构变量,从而取出其中的 ops 字段。

当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用cdev_del() 函数来释放 cdev 占用的内存。

voidcdev_del(struct cdev *p)
{
  cdev_unmap(p->dev, p->count);
  kobject_put(&p->kobj);
}

其中cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。

学习字符设备模型

基础数据结构

structcdev {
    struct kobject kobj;       
                           /*内嵌的kobject结构,用于内核设备驱动模型的管理*/
    struct module *owner;  
                           /*指向包含该结构的模块的指针,用于引用计数*/
    const struct file_operations *ops;      /*指向字符设备操作函数集的指针*/
    struct list_head list;     
                           /*该结构将使用该驱动的字符设备连接成一个链表*/
    dev_t dev;              /*该字符设备的起始设备号,一个设备可能有多个设备号*/
    unsigned int count;     /*使用该字符设备驱动的设备数量*/
};
structkobj_map {
  20       struct probe {
  21                struct probe *next;
  22                dev_t dev;
  23                unsigned long range;
  24                struct module *owner;
  25                kobj_probe_t *get;
  26                int (*lock)(dev_t, void *);
  27                void *data;
  28       } *probes[255];
  29       struct mutex *lock;
  30};
 
 
staticstruct char_device_struct {
  53       struct char_device_struct *next;
  54       unsigned int major;
  55       unsigned int baseminor;
  56       int minorct;
  57       char name[64];
  58       struct cdev *cdev;              /*will die */
  59} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
#defineCHRDEV_MAJOR_HASH_SIZE        255

字符设备模型

每个字符驱动由一个 cdev 结构来表示.

在设备驱动模型(device driver model)中, 使用 (kobject mapping domain)来记录字符设备驱动.

这是由 struct kobj_map 结构来表示的. 它内嵌了255个struct probe指针数组

kobj_map由全局变量 cdev_map 引用: static struct kobj_map *cdev_map;


相关函数说明:

cdev_alloc() 用来创建一个cdev的对象

cdev_add() 用来将cdev对象添加到驱动模型中,其主要是通过kobj_map()来实现的.

kobj_map() 会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data 指向cdev

struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev,int *index)

根据设备号,在cdev_map中查找其cdev对象内嵌的kobject.(probe->data->kobj),返回的是cdev的kobject

2. 字符设备的设备号

字符设备的主,次设备号的分配:

全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 structchar_device_struct的元素.

每一个对应一个相应的主设备号.

如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中.

这样,通过chrdevs数组,我们就可以知道分配了哪些设备号.


相关函数:

register_chrdev_region( ) 分配指定的设备号范围

alloc_chrdev_region( ) 动态分配设备范围

他们都主要是通过调用函数__register_chrdev_region() 来实现的

要注意,这两个函数仅仅是注册设备号! 如果要和cdev关联起来,还要调用cdev_add()

register_chrdev( ) 申请指定的设备号,并且将其注册到字符设备驱动模型中.

它所做的事情为:

1. 注册设备号, 通过调用 __register_chrdev_region() 来实现

2. 分配一个cdev, 通过调用 cdev_alloc() 来实现

3. 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现

4. 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev.由于register_chrdev()是老的接口,这一步在新的接口中并不需要.


3. 文件系统中对字符设备文件的访问

对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL,则说明该设备文件没有被打开.

由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表


首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open.

(最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open.这一系列的调用过程,本文暂不讨论)

int chrdev_open(struct inode * inode, struct file * filp)

chrdev_open()所做的事情可以概括如下:

1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup()来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.

2. 设置inode->i_cdev , 指向找到的cdev.

3. 将inode添加到cdev->list的链表中.

4. 使用cdev的ops 设置file对象的f_op

5. 如果ops中定义了open方法,则调用该open方法

6. 返回.

执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read,write等操作,就会执行cdev的相应操作.

此处3个分析转载自http://blog.csdn.net/cuijianzhongswust/article/details/6887993

分享到:
评论

相关推荐

    linux字符设备驱动模型

    linux字符设备驱动模型

    Linux设备驱动程序学习

    Linux设备驱动程序学习(1)-字符设备驱动程序 ·Linux设备驱动程序学习(0)-Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备驱动程序学习(4)-...

    Linux设备驱动程序第三版2.6.CHM

    本书是经典著作《Linux 设备驱动程序》的第三版。该版本已针对 Linux 内核的 2.6.10 彻底更新过了。...DMA、驱动程序模型和 sysfs、热插拔设备、对常见总线的描述,包括 SCSI、PCI、USB 和 IEEE1394(火线)。

    老同学给的入门级驱动开发资料

    9.Linux字符设备驱动模型 10.Linux驱动接口 11.Linux的GPIO操作 12.Linux驱动中断编程 13.Linux驱动--等待队列&poll接口 14.Linux内核定时器、工作队列、tasklet 15.Linux字符设备驱动--原子操作、信号量 16....

    Linux 驱动学习笔记pdf文档

    ·Linux设备驱动程序学习(0)-设备驱动介绍& Hello, world!模块 ·Linux设备驱动程序学习(2)-调试技术 ·Linux设备驱动程序学习(3)-并发和竞态 ·Linux设备驱动程序学习(4)-高级字符驱动程序操作[(1)...

    《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

    4.3 linux设备模型71 4.3.1 udev71 4.3.2 sysfs、kobject和设备类73 4.3.3 热插拔和冷插拔76 4.3.4 微码下载76 4.3.5 模块自动加载77 4.4 内存屏障78 4.5 电源管理79 4.6 查看源代码79 第5章 ...

    linux设备驱动模型一字符设备open系统调用流程.docx

    linux设备驱动模型一字符设备open系统调用流程.docx

    LINUX字符设备模型浅析

    LINUX字符设备模型,对理解linux驱动很有理解

    linux设备驱动程序第三版

    1. Linux 设备驱动第三版 .................................................................................................................... 5 2. 第 1 章 设备驱动简介 ....................................

    精通LINUX设备驱动程序开发

    64 4.2.3 设备实例:导航杆 65 4.2.4 softirq和tasklet 68 4.3 linux设备模型 71 4.3.1 udev 71 4.3.2 sysfs、kobject和设备类 73 4.3.3 热插拔和冷插拔 76 4.3.4 微码下载 76 4.3.5 模块自动加载 77 4.4 ...

    LINUX设备驱动第三版_588及代码.rar

    第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备和驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备...

    linux下驱动模型介绍

    注册字符设备,没有涉及到设备驱动模型,而驱动模型里,device_driver 根本没有涉及到 设备操作的函数、file_operations 等,只有一些电源管理,热插拔相关的函数。 platform_device 里也主要是resource 的管理,...

    Linux设备驱动程序学习总结

    Linux设备驱动程序学习总结: *字符设备驱动程序 *调试技术 *并发和竞态 *Linux中的循环缓冲区 *内核的数据类型 *分配内存 *与硬件通信 *时间、延迟及延缓操作 *中断处理 *Linux设备模型

    Linux驱动开发:Linux内核模块、字符设备驱动、IO模型、设备树、GPIO子系统、中断子系统.zip

    Linux驱动开发:Linux内核模块、字符设备驱动、IO模型、设备树、GPIO子系统、中断子系统、platform总线驱动、I2C总线驱动、SPI总线驱动 Linux项目是一个开放源代码的操作系统项目,由林纳斯·托瓦兹(Linus Torvalds...

    LINUX驱动开发中字符设备模型研究.pdf

    LINUX驱动开发中字符设备模型研究.pdf

    linux设备驱动书籍详细说明

    对LDD3不太懂的地方做了细致的说明。从字符驱动开始,还有块设备驱动的用法。等相关的地方进行说明。

    国嵌培训课件Linux驱动程序设计

    第一天 1.Linux驱动简介 2.字符设备驱动程序设计 3.驱动调试技术 4. 并发与竞态 第二天 1.Ioctl型驱动 2.内核等待队列 3. 阻塞型驱动程序设计 4.Poll设备操作 第三天 1.Mmap设备操作 2. 硬件访问 3. 混杂...

    Linux 设备模型之终端设备(tty)驱动架构分析

    在 Linux 系统中,终端是一种字符型设备,它有多种类型,通常使用 tty 来简称各种类型的 终端设备。tty 是 Teletype 的缩写,Teletype 是最早出现的一种终端设备,很像电传打字 机,是由 Teletype 公司生产的。Linux...

    Linux 字符设备驱动框架详细介绍

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能...

Global site tag (gtag.js) - Google Analytics