Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1779245
  • 博文数量: 272
  • 博客积分: 1272
  • 博客等级: 少尉
  • 技术积分: 1866
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-09 15:51
文章分类

全部博文(272)

文章存档

2016年(16)

2015年(28)

2014年(97)

2013年(59)

2012年(25)

2011年(47)

分类:

2011-05-11 15:36:24

原文地址:Linux字符驱动总结 作者:xgr07happy

1、主设备号和此设备号
    主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备。
    在内核中, dev_t 类型(中定义)用来持有设备编号。对于 2.6.0 内核, dev_t 32 位的量, 12 位用作主编号, 20 位用作次编号.
        应当利用在 中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:

 (dev_t)-->主设备号、次设备号

 MAJOR(dev_t dev)
 MINOR(dev_t dev)

 主设备号、次设备号-->(dev_t)

 MKDEV(int major,int minor) 

 

    在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用。并且应当在不再使用它们时释放它。

int register_chrdev_region(dev_t first, unsigned int count,char *name);   //指定设备编号

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name);   //
动态生成设备编号

void unregister_chrdev_region(dev_t first, unsigned int  count);      //
释放设备编号

安排主编号最好的方式, 我们认为, 是缺省使用动态分配, 而留给自己在加载时或者甚至在编译时指定主编号的选项权.

以下是在scull.c中用来获取主设备好的代码:

if (scull_major) {
    dev
= MKDEV(scull_major, scull_minor);
    result
= register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
    result
= alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");
    scull_major = MAJOR
(dev);
}
if (result < 0) {
    printk
(KERN_WARNING "scull: can't get major %d\n", scull_major);
    return result
;
}

动态分配的缺点是你无法提前创建设备节点, 因为分配给你的模块的主编号会变化. 对于驱动的正常使用, 这不是问题, 因为一旦编号分配了, 你可从 /proc/devices 中读取它.

2、一些重要数据结构

大部分的基础性的驱动操作包括 4 个重要的内核数据结构cdev,file_operations, file inode.

file_operations结构定义在

struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};

struct file,定义于 与用户空间程序的 FILE 指针没有任何关系。文件结构代表一个打开的文件.它由内核在 open 时创建, 并传递给在文件上操作的任何函数, 直到最后的关闭. 在文件的所有实例都关闭后, 内核释放这个数据结构.

struct inode ,是在内核内部用来表示文件的。因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.

struct cdev ,Linux2.6内核与2.4内核不同2.6内核采用了。cdev结构体来描述管理字符设备

struct cdev {
 struct kobject kobj; //嵌在cdev结构中的kobject对象
 struct module *owner;
 struct file_operations *ops;/*file_operation 结构体,最终与硬件打交道的函数都注册在这里*/
 struct list_head list;
 dev_t dev;
 unsigned int count;
};
与其相关的操作函数有:
    1. struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)

struct cdev *my_cdev = cdev_alloc();

    1. 初始化struct cdev

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

    1. 初始化cdev.owner

cdev.owner = THIS_MODULE;

    1. cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

从系统中移除一个字符设备:void cdev_del(struct cdev *p)

以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):

/*
 * Set up the char_dev structure for this device.
 */

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    
int err, devno = MKDEV(scull_major, scull_minor + index);
    
    cdev_init
(&dev->cdev, &scull_fops);
    dev
->cdev.owner = THIS_MODULE;
    dev
->cdev.ops = &scull_fops;  //
这句可以省略,在cdev_init中已经做过
    err
= cdev_add (&dev->cdev, devno, 1);
    
/* Fail gracefully if need be
这步值得注意*/
    
if (err)
        printk
(KERN_NOTICE "Error %d adding scull%d", err, index);
}

open 方法

open 方法提供给驱动来做任何的初始化来准备后续的操作. 在大部分驱动中, open 应当进行下面的工作:

●检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误)
如果它第一次打开, 初始化设备
如果需要, 更新 f_op 指针.
分配并填充要放进 filp->private_data 的任何数据结构

但是, 事情的第一步常常是确定打开哪个设备. 记住 open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);

inode 参数有我们需要的信息,以它的 i_cdev 成员的形式, 里面包含我们之前建立的 cdev 结构.

container_of(pointer, container_type, container_field);

这个宏使用一个指向 container_field 类型的成员的指针, 它在一个 container_type 类型的结构中, 并且返回一个指针指向包含结构. scull_open, 这个宏用来找到适当的设备结构:

struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */

识别打开的设备的另外的方法是查看存储在 inode 结构的次编号. 如果你使用 register_chrdev 注册你的设备, 你必须使用这个技术. 确认使用 iminor inode 结构中获取次编号, 并且确定它对应一个你的驱动真正准备好处理的设备.

int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
scull_trim(dev); /* ignore errors */
}
return 0; /* success */
}

 

release 方法

●释放 open 分配在 filp->private_data 中的任何东西
在最后的 close 关闭设备

scull 的基本形式没有硬件去关闭, 因此需要的代码是最少的:

int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}

scull模型的内存使用

 

以下是scull模型的结构体:

/*
 * Representation of scull quantum sets.
 */

struct scull_qset {
    
void **data;
    
struct scull_qset *next;
};

struct scull_dev {
    
struct scull_qset *data; /* Pointer to first quantum set */
    
int quantum; /* the current quantum size */
    
int qset; /* the current array size */
    
unsigned long size; /* amount of data stored here */
    
unsigned int access_key; /* used by sculluid and scullpriv */
    
struct semaphore sem; /* mutual exclusion semaphore */
    
struct cdev cdev;     /* Char device structure        */
};

scull驱动程序引入了两个Linux内核中用于内存管理的核心函数,它们的定义都在:

void *kmalloc(size_t size, int flags);
void kfree(void *ptr);

以下是scull模块中的一个释放整个数据区的函数(类似清零),将在scull以写方式打开和scull_cleanup_module中被调用:

int scull_trim(struct scull_dev *dev)
{
  struct scull_qset *next, *dptr;
     int qset = dev->qset; /*
量子集中量子的个数*/
     int i;
     for (dptr = dev->data; dptr; dptr

3、完整驱动模版

//=======================字符设备驱动模板开始 ===========================//

#define CHAR_DEV_DEVICE_NAME   "char_dev"   // 设备名

struct class *char_dev _class;  // class结构用于自动创建设备结点
static int major = 0;/* 0
表示动态分配主设备号,可以设置成未被系统分配的具体的数字。*/

static struct cdev char_dev_devs;// 定义一个cdev结构

// 设备建立子函数,被char_dev_init函数调用  
static void char_dev_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)
{
    int err, devno = MKDEV(major, minor);
    
    cdev_init(dev, fops);
    dev->owner = THIS_MODULE;
    dev->ops = fops;
    
    err = cdev_add(dev, devno, 1);
    if( err )
    {
        printk(KERN_NOTICE "Error %d adding char_dev %d\n", err, minor);
    }
}

//  file_operations 结构体设置,该设备的所有对外接口在这里明确,此处只写出了几常用的
static struct file_operations char_dev_fops =
{
    .owner = THIS_MODULE,
    .open  = char_dev_open,      //
打开设备
    .release = char_dev_release, //
关闭设备
    .read  = char_dev_read,      //
实现设备读功能
    .write = char_dev_write,     //
实现设备写功能
    .ioctl = char_dev_ioctl,     //
实现设备控制功能
};

// 进行初始化设置,打开设备,对应应用空间的open 系统调用
int char_dev_open (struct inode *inode, struct file *filp)

{

...   //  这里可以进行一些初始化

return 0;

}

 

// 释放设备,关闭设备,对应应用空间的close 系统调用

static int char_dev_release (struct inode *node, struct file *file)

{

...    //  这里可以进行一些资源的释放

return 0;

}

// 实现读功能,读设备,对应应用空间的read 系统调用

ssize_t char_dev_read (struct file *file,char __user *buff,size_t count,loff_t *offp)

{

     ...

return 0;

}

// 实现写功能,写设备,对应应用空间的write 系统调用

ssize_t char_dev_write(struct file *file,const char __user *buff,size_t count,loff_t *offp)

{

    ...

    return 0;

}

 

// 实现主要控制功能,控制设备,对应应用空间的ioctl 系统调用

static int char_dev _ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)

{  

    ...

    return 0;

}

//   设备初始化
static int char_dev_init(void)
{
    int result;
    dev_t dev = MKDEV(major, 0);
    
    if( major )
    {

// 给定设备号注册
        result = register_chrdev_region(dev, 1, CHAR_DEV_DEVICE_NAME);
    }

else
    {

// 动态分配设备号
        result = alloc_chrdev_region(&dev, 0, 1, CHAR_DEV_DEVICE_NAME);
        major = MAJOR(dev);
    }
    
    char_dev_setup_cdev(&char_dev_devs, 0, &char_dev_fops);
    printk("The major of the char_dev device is %d\n", major);

//==== 有中断的可以在此注册中断:request_irq,并要实现中断服务程序 ===//

// 创建设备结点

char_dev _class = class_create(THIS_MODULE,"ad_class");

if (IS_ERR(char_dev _class))

{

    printk("Err: failed in creating class.\n");

    return 0;

}

device_create(char_dev_class, NULL, dev, NULL, "char_dev");
    return 0;
}

// 设备注销
static void char_dev_cleanup(void)
{

device_destroy(adc_class,dev);

class_destroy(adc_class);
    cdev_del(&char_dev_devs);//
字符设备的注销*/
    unregister_chrdev_region(MKDEV(major, 0), 1);//
设备号的注销

//========  有中断的可以在此注销中断:free_irq  ======//


    printk("char_dev device uninstalled\n");
}

module_init(char_dev_init);//
模块初始化接口
module_exit(char_dev_cleanup);//
模块注销接口

// 以下两句不能省略,否则编译不通过
MODULE_AUTHOR("");
MODULE_LICENSE("GPL");

//==================== 字符设备驱动模板结束 ========================//


Makefile模板编译,Makefile如下:

//======================= Makefile开始 ===========================//

ifeq ($(KERNELRELEASE),)

#KERNELDIR ?= /your/target/source/directory/

KERNELDIR ?= /opt/kernal/linux-2.6.32.10/

PWD := $(shell pwd)

modules:

    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:

    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:

    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

.PHONY: modules modules_install clean

//========================= Makefile结束 =============================//

make编译后生成char_dev.ko,控制台输入加载和卸载命令,还可以使用lsmod查看已经加载的模块信息。
insmod char_dev.ko #
加载驱动,会执行module_init中的语句
rmmod char_dev     #
卸载驱动,会执行module_exit中的语句


阅读(666) | 评论(0) | 转发(0) |
0

上一篇:MMC 卡驱动分析

下一篇:mini2440 LED驱动

给主人留下些什么吧!~~