您的位置:首页 > 运维架构 > Linux

Linux设备驱动程序系列(二) 字符设备驱动程序(1)

2011-03-24 13:24 441 查看
DownloadthisMP3-(RightClick)伴着音乐开始Linux设备驱动之旅,接下来几篇日志都将记录“字符设备驱动程序”的编写与测试。
我选择边贴代码边说明的方式来进行分析设计我们的驱动程序,因为这样比较明确清楚。
自定义头文件demo.h如下:



我们的字符设备驱动的自定义头文件
#ifndef_DEMO_H_
#define_DEMO_H_

#include<linux/ioctl.h>/*neededforthe_IOWetcstuffusedlater*/

/********************************************************
*Macrostohelpdebugging
********************************************************/
#undefPDEBUG/*undefit,justincase*/
#ifdefDEMO_DEBUG
#ifdef__KERNEL__
#definePDEBUG(fmt,args...)printk(KERN_DEBUG"DEMO:"fmt,##args)
#else//usrspace
#definePDEBUG(fmt,args...)fprintf(stderr,fmt,##args)
#endif
#else
#definePDEBUG(fmt,args...)/*notdebugging:nothing*/
#endif

#undefPDEBUGG
#definePDEBUGG(fmt,args...)/*nothing:it'saplaceholder*/

//设备号
#defineDEMO_MAJOR224
#defineDEMO_MINOR0
#defineCOMMAND11
#defineCOMMAND22

//设备结构
structDEMO_dev
{
	structcdevcdev;	/*Chardevicestructure		*/
};

//函数申明
ssize_tDEMO_read(structfile*filp,char__user*buf,size_tcount,
loff_t*f_pos);
ssize_tDEMO_write(structfile*filp,constchar__user*buf,size_tcount,
loff_t*f_pos);
loff_tDEMO_llseek(structfile*filp,loff_toff,intwhence);
intDEMO_ioctl(structinode*inode,structfile*filp,
unsignedintcmd,unsignedlongarg);

#endif/*_DEMO_H_*/

这里面比较需要说明的是

1:#defineDEMO_MAJOR224

2:#defineDEMO_MINOR0

这里分别为主设备号、次设备号的定义。至于主次设备号的用处一会在源代码里面解释,这里提醒一下。

下面有很多的函数声明,这些函数是写驱动程序都应该有的,接下来解释具体实现。

下面为源文件demo.c

1:#include<linux/module.h>
2:#include<linux/kernel.h>
3:#include<linux/fs.h>
4:#include<linux/errno.h>
5:#include<linux/types.h>
6:#include<linux/fcntl.h>
7:#include<linux/cdev.h>
8:#include<linux/version.h>
9:#include<linux/vmalloc.h>
10:#include<linux/ctype.h>
11:#include<linux/pagemap.h>
12:
13:#include"demo.h"
14:
15:MODULE_AUTHOR("sunnysun");
16:MODULE_LICENSE("DualBSD/GPL");
17:
18:structDEMO_dev*DEMO_devices;
19:staticunsignedchardemo_inc=0;
20:staticu8demoBuffer[256];

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}


这里首先列出的是各种头文件、以及结构体定义、变量定义和宏调用。

头文件再用到里面内容的时候具体说明,如果你好奇也可以直接去我给出的网址看源代码。

MODULE_AUTHOR("sunnysun");
MODULE_LICENSE("DualBSD/GPL");

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

这里的两个宏来自

#include<linux/module.h>

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

第一个宏声明模块作者是谁,关于第二个宏为内核认识的特定许可有,"GPL"(适用GNU通用公共许可的任何版本),

"GPLv2"(只适用GPL版本2),"GPLandadditionalrights","DualBSD/GPL","DualMPL/GPL",和"Proprietary".除非你的

模块明确标识是在内核认识的一个自由许可下,否则就假定它是私有的,内核在模块加载时被"弄污浊"了。

关于类似的宏有以下:

MODULE_DESCRIPION(一个人可读的关于模块做什么的声明),

MODULE_VERSION(一个代码修订版本号;看<linux/module.h>的注释以便知道创建版本字串使用的惯例),

MODULE_ALIAS(模块为人所知的另一个名子),

MODULE_DEVICE_TABLE(来告知用户空间,模块支持那些设备).


下面说一下:

structDEMO_dev*DEMO_devices;

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

可以看到这个结构体来自demo.h头文件

structDEMO_dev
{
structcdevcdev;/*Chardevicestructure*/
};
看到这会感觉这个structcdev从那跑出来的,其实这个结构体正是Linux下的字符设备的结构体,chardevice的简写。
到Linux内核中找到linux/include/linux/cdev.h可以找到这个结构体的实现:

1:structcdev{
2:structkobjectkobj;
3:structmodule*owner;//所属模块
4:conststructfile_operations*ops;
5://文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
6:structlist_headlist;
7:dev_tdev;//设备号
8:unsignedintcount;
9:};

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}


看这个结构体的时候,又发现了新结构体kobject

这个结构体稍后说明,先说明structcdev的使用。下面一组函数用来对cdev结构体进行操作:

voidcdev_init(structcdev*,conststructfile_operations*);

初始化,建立cdev和file_operation之间的连接。

structcdev*cdev_alloc(void);

动态申请一个cdev内存。
voidcdev_put(structcdev*p);

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

释放。
intcdev_add(structcdev*,dev_t,unsigned);

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

注册设备,通常发生在驱动模块的加载函数中。
voidcdev_del(structcdev*);

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

注销设备,通常发生在驱动模块的卸载函数中。
在注册时应该先调用:
intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name)

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

此函数可以用下面函数进行代替:
intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}


他们之间的区别在于:register_chrdev_region()用于已知设备号时,另一个用于动态申请,其优点在于不会造成设备号重复的冲突。
在注销之后,应调用:
voidunregister_chrdev_region(dev_tfrom,unsignedcount)

函数释放原先申请的设备号。
它们之间的顺序关系如下:

register_chrdev_region()-->cdev_add()//此过程在加载模块中
cdev_del()-->unregister_chrdev_region()//此过程在卸载模块中

cdev_add()函数的原型如下:
#include<linux/cdev.h>
intcdev_add(structcdev*p,dev_tdev,unsignedcount);

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

第1个参数为cdev结构体指针;
第2个参数为dev_t类型表达的设备号(dev_t类型表达的设备号用MKDEV(intmajor,intminor)宏来实现);
第3个参数是设备号的数目。
cdev_add()函数的实现代码为
1:/**
2:*cdev_add()-addachardevicetothesystem
3:*@p:thecdevstructureforthedevice
4:*@dev:thefirstdevicenumberforwhichthisdeviceisresponsible
5:*@count:thenumberofconsecutiveminornumberscorrespondingtothis
6:*device
7:*
8:*cdev_add()addsthedevicerepresentedby@ptothesystem,makingit
9:*liveimmediately.Anegativeerrorcodeisreturnedonfailure.
10:*/
11:intcdev_add(structcdev*p,dev_tdev,unsignedcount)
12:{
13:p->dev=dev;
14:p->count=count;
15:returnkobj_map(cdev_map,dev,count,NULL,exact_match,exact_lock,p);
16:}

.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

一般的,count的值为1,但是有些情形也可能是大于1的数。比如SCSI磁带机,它通过给每个物理设备安排多个此设备号来允许用户在应用程序里选择操作模式(比如密度)。
cdev_add如果失败了,那么返回一个负值,表明驱动无法加载到系统中。然而它一般情况下都会成功,一旦cdev_add返回,设备也就“活”了起来,于是所对应的操作方法(file_operations结构里所定义的各种函数)也就能为内核所调用。

至此,这个结构体说明结束。接下来说明kobject结构体的实现以及应用。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: