微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > 嵌入式设计 > 字符设备驱动模型浅析

字符设备驱动模型浅析

时间:01-08 来源:3721RD 点击:

在linux系统中,很多驱动是字符型驱动,有些是直接编译集成在内核中,另一些是单独编译成"。ko"动态加载的。其实字符驱动只是个外壳,用于内核与应用程序间通信,无非是调用open,release,read,write和ioctl等例程。所以根据应用不同,字符驱动能会调用其他驱动模块,如i2c、spi和v4l2等,于是字符驱动还可分WDT驱动、RTC驱动和MTD驱动等。所以在分析其他驱动模块之前有必要好好分析下字符设备驱动模型。本篇文章要讲的就是字符设备驱动模型,也就是字符设备驱动是怎么注册和注销的,怎么生成设备节点的,怎么和应用程序关联的,例程调用具体如何实现的等等。

一、字符设备驱动的注册和注销

对于写过linux-2.6内核(本文采用linux-2.6.18内核)字符驱动的程序员来说,对下面这段程序的形式肯定不陌生。int result;

/*

* Register the driver in the kernel

* Dynmically get the major number for the driver using

* alloc_chrdev_region function

*/

result = alloc_chrdev_region( 0, 1,"testchar");

/* if it fails return error */

if (result dev = 1;

base->range = ~0; /*初始的范围很大*/

base->get = base_probe; /*保存函数指针*/

for (i = 0; i probes[i] = base; /*所有指针都指向同一个base */

p->lock = lock;

return p;

}.

复制代码该函数只是分配了一个结构体struct kobj_map,并做了初始化,保存了函数指针base_probe和全局锁lock。

下面就按照驱动注册流程一个个解析这些例程调用吧。首先是alloc_chrdev_region()函数,解析它之前,先看看结构体(定义了255个结构体指针),static struct char_device_struct {

/*被255整除后相同的设备号链成一个单向链表*/

struct char_device_struct *next;

unsigned int major; /*主设备号*/

unsigned int baseminor; /*次设备起始号*/

int minorct; /*次设备号范围*/

char name[64]; /*驱动的名字*/

struct file_operations *fops; /*保存文件操作指针,目前没有使用*/

struct cdev *cdev; /* will die */ /*目前没有使用*/

} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */

复制代码它的作用仅仅是用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找。

alloc_chrdev_region()函数很简单,通过调用__register_chrdev_region()来实现,通过英语注释你也可以明白,这个函数有两个作用,一是,如果主设备号为0,则分配一个最近的主设备号,返回给调用者;二是,如果主设备号不为0,则占用好该主设备号对应的位置,返回给调用者。如下,static struct char_device_struct *

__register_chrdev_region(unsigned int major, unsigned int baseminor,

int minorct, const char *name)

{

struct char_device_struct *cd, **cp;

int ret = 0;

int i;

cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

if (cd == NULL)

return ERR_PTR(-ENOMEM);

mutex_lock( /*这下看到了吧,加锁,就允许你一个人进来*/

/* temporary */

if (major == 0) { /*如果主设备号为零,则找一个最近空闲的号码分配*/

for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

if (chrdevs[i] == NULL)

break;

}

if (i == 0) {

ret = -EBUSY;

goto out;

}

major = i;

ret = major;

}

/*这些不用说你懂的*/

cd->major = major;

cd->baseminor = baseminor;

cd->minorct = minorct;

strncpy(cd->name,name, 64);

i = major_to_index(major);

/*如果主设备号不为0,则占用好该主设备号对应的位置*/

for (cp = *cp; cp =

if ((*cp)->major > major ||

((*cp)->major == major (*cp)->baseminor >= baseminor))

break;

if (*cp (*cp)->major == major

(*cp)->baseminor next = *cp;

*cp = cd;

mutex_unlock( /*开锁,队列里的下一个人可以进来了*/

return cd;

out:

mutex_unlock(

kfree(cd);

return ERR_PTR(ret);

}

复制代码接着是cdev_init()函数,先说说cdev的结构体,struct cdev {

struct kobject kobj; /*不多解释了,看看鄙人前面写的文章吧*/

struct module *owner; /*模块锁定和加载时用得着*/

const struct file_operations *ops; /*保存文件操作例程结构体*/

struct list_head list; /* open时,会将其inode加到该链表中,方便判别是否空闲*/

dev_t dev; /*设备号*/

unsigned int count;

};

复制代码cdev结构体把字符设备驱动和文件系统相关联,后面解析字符设备驱动怎样运行的时候会详谈。

cdev_init()函数如下,void cdev_init(struct cdev *cdev, const struct file_operations *fops)

{

memset(cdev, 0, sizeof *cdev);

INIT_LIST_HEAD(

cdev->kobj.ktype = /*卸载驱动时会用到,别急,后面详讲*/

kobject_init(

cdev->ops = fops; /*用户写的字符设备驱动fops就保存在这了*/

}.

复制代码你也看到了,该函数就是对变量做了初始化,关于kobject的解析,建议你看看鄙人博客上写的《Linux设备模型浅析之设备篇》和《Linux设备模型浅析之驱动篇》两篇文章,这里就不详谈了。

用户的fops,在本文中是test_fops,一般形式是这样的,

static const struct file_operations test_fops = {

。owner = THIS_MODULE,

。open = test_fops_open,

。release = test_fops_release,

。ioctl = test_fops_ioctl,

。read = test_fops_read,

。write = test_fops_write,

};

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top