谈一谈linux驱动.docx
《谈一谈linux驱动.docx》由会员分享,可在线阅读,更多相关《谈一谈linux驱动.docx(20页珍藏版)》请在冰豆网上搜索。
谈一谈linux驱动
应用程序与驱动程序通过设备文件进行通信
每个设备文件都有主设备号与次设备号主设备号表示设备的类型次设备号表示具体的设备
在内核中就是根据主设备号来调用相应的驱动程序驱动根据次设备号分辩具体设备以区分操作
主设备号由linux统一分配但是也可以使用临时设备在注册驱动时把主设备号输入为0则由内核自动分配
设备的类型:
主要类型有下面三种
字符设备:
它的保存是以字符流的形式保存
块设备:
它的保存是以块为单位保存,提供和字符驱动一样的接口,当然有自己块操作的接口,如mount,同时支持像字符设备那样的访问方式,
上面两个都是映射到一个设备文件,设备文件通常保存在/dev/目录下,当然你可以保存在任意地方
网络接口:
用于进行网络通信,可能针对某个硬件,如网卡,或是纯软件,如loopback,网络接口只是面向数据包而不是数据流,所以内核的处理也不同,没有映射成任何设备文件,而是按unix标准给它们分配一个唯一的名字
驱动开发时注意点:
1只提供机制,不限制策略,调用者可以按照自己的要求自己订制策略
2权限的判断,应该尽量让系统管理员决定而不是在驱动中指定,除非这个设备对整个系统有影响
3安全编程,从用户进程得到的输入必须检查,返回给用户进程的内存必须初始化(如果这块内存刚好保存口令信息或是其它就会破坏系统安全性)
4内核不连接libc,所以不可以使用libc里面的函数如printf等,必须使用内核自己提供的函数,如果你不知道有哪些函数,那看看内核头文件是不错的选择
5避免名字空间污染,不需要输出的符号就不输出如果你的符号被其它模块引用,那使用EXPORT_SYMBLE声明被输出,要输出的模块最好加上模块名前缀避免冲突.不对外开放的使用static限制在本地使用
6注意可重入性问题,必须考虑到执行到某步时任务切换或是中断处理时应该怎样处理.
在2.6下开发简单的驱动
2.6驱动的开发相对以前各版本来说有了很大的改变先是初始化与退出函数
以前使用init_module函数来声明模块初始化过程使用cleanup_module来定义模块退出时的清除过程
而在2.6中必须使用module_init标志来说明初始化函数使用module_exit来说明清理函数
在2.4中,可以单独编译一个模块而在2.6中,一个模块的编译,必须编译全部所有模块
下面是一个2.6内核的模板
#defineMODULE
#includelinux/module.h>
#includelinux/config.h>
#includelinux/init.h>
staticint__initname_of_initialization_routine(void){
/*
*codehere
*/
}
staticvoid__exitname_of_cleanup_routine(void){
/*
*codehere
*/
}
module_init(name_of_initialization_routine);
module_exit(name_of_cleanup_routine);
MODULE宏也不再是必须的了,在编译时可不定义
编译过程的区别
2.4的编译过程如下
gcc-D__KERNEL__-DMODULE-I/usr/src/linux-2.4.21/include-O2-ctestmod.c
要定义__KERNEL__是因为在内核头文件中,某些符号只在定义这个宏后才会公开
2.6的编译过程
写一个简单的Makefile只有一行
obj-m:
=testmod.o
然后使用如下命令编译
make-C/usr/src/linux-2.6.1SUBDIRS=$WDmodules
$WD是你的内核模块所在的目录使用如上命令时会自动重编译系统的内核模块与你的模块
所以保存内核源目录中的版本号与配置文件与你当前运行内核相同是必要的
一个简单的例子:
testmod.c
#defineMODULE
#include
#include
#include
staticint__inittestmod_init(void)
{
printk("<1>helloworld\n");
return0;
}
staticvoid__exittestmod_cleanup(void)
{
printk("<1>exitmodule\n");
}
module_init(testmod_init);
module_exit(testmod_cleanup);
Makefile编译方法与执行结果
wushuang:
~/work/kernel#catMakefile
obj-m:
=testmod.o
wushuang:
~/work/kernel#cattest
testmod:
modulelicense'unspecified'taintskernel.
helloworld
wushuang:
~/work/kernel#rmmodtestmod
exitmodule
wushuang:
~/work/kernel#
wushuang:
~/work/kernel#make-C/usr/src/linuxSUBDIRS=~/work/kernel/modules
第一个可用的例子
下面是一个简单的字符设备驱动程序
* chardev.c:
Createsaread-onlychardevicethatsayshowmany
* times
* * you'vereadfromthedevfile
* */
#include
#include
#include
#include
#include
#include
staticint__inittestmod_init(void);
staticvoid__exittestmod_exit(void);
staticintdevice_open(structinode*,structfile*);
staticintdevice_release(structinode*,structfile*);
staticssize_tdevice_read(structfile*,char*,size_t,loff_t*);
staticssize_tdevice_write(structfile*,constchar*,size_t,loff_t*);
#defineSUCCESS0
#defineDEVICE_NAME"chardev"/*Devnameasitappearsin/proc/devices */
#defineBUF_LEN80 /*Maxlengthofthemessagefromthedevice*/
/*Globalvariablesaredeclaredas
*static,soareglobalwithinthefile.
**/
staticintMajor; /*Majornumberassignedtoourdevicedriver*/
staticintDevice_Open=0; /*Isdeviceopen?
Usedtopreventmultiple */
staticcharmsg[BUF_LEN]; /*Themsgthedevicewillgivewhenasked */
staticchar*msg_Ptr;
staticstructfile_operationsfops={
.read=device_read,
.write=device_write,
.open=device_open,
.release=device_release
};//注意这里需要是静态变量,不然会出现内核访问错误情况
//.open=device_read,对fops里面的open变量赋值为device_read,
//,这是c99;里面定义的新标准,表示定义结构变量时同时对它进行子元素的赋值
//gcc扩展使用的是open:
device_read,
/* Functions
* */
staticint__inittestmod_init(void)
{
Major=register_chrdev(240,DEVICE_NAME,&fops);
//注册字符驱动第一个参数是主设备号如果第一个参数是0表示由内核自动分配
//第二个是设备名可以在/proc/devices里面看到
//第三个是对此设备的操作函数具体操作可以看file_operations结构定义在linux/fs.h里面
if(Major<0){
printk("Registeringthecharacterdevicefailedwith%d\n",Major);
returnMajor;
}
Major =240;
printk("<1>Iwasassignedmajornumber%d. Totalkto\n",Major);
printk("<1>thedriver,createadevfilewith\n");
printk("'mknod/dev/helloc%d0'.\n",Major);
printk("<1>Tryvariousminornumbers. Trytocatandechoto\n");
printk("thedevicefile.\n");
printk("<1>Removethedevicefileandmodulewhendone.\n");
return0;
}
staticvoid__exittestmod_exit(void)
{
/*Unregisterthedevice*/
intret=unregister_chrdev(Major,DEVICE_NAME);//取消注册取消了后模块可以unload
if(ret<0)printk("Errorinunregister_chrdev:
%d\n",ret);
}
/* Methods
* */
/*Calledwhenaprocesstriestoopenthedevicefile,like
* *"cat/dev/mycharfile"
* */
staticintdevice_open(structinode*inode,structfile*file)
{
staticintcounter=0;
if(Device_Open)return-EBUSY;
Device_Open++;
sprintf(msg,"Ialreadytoldyou%dtimesHelloworld!
\n",counter++);
msg_Ptr=msg;
//MOD_INC_USE_COUNT;在2.6中,会自动维护引用计数,所以不再需要这个宏,但是如果要考虑兼容性那最好保存这个宏
printk("<1>device_opencall\n");
returnSUCCESS;
}
/*Calledwhenaprocessclosesthedevicefile.
* */
staticintdevice_release(structinode*inode,structfile*file)
{
Device_Open--; /*We'renowreadyforournextcaller*/
printk("<1>device_releasecall\n");
/*Decrementtheusagecount,orelseonceyou
*openedthefile,you'll
* nevergetgetridofthemodule.
* */
//MOD_DEC_USE_COUNT;
return0;
}
/*Calledwhenaprocess,whichalreadyopenedthedevfile,
*attemptsto
* readfromit.
* */
staticssize_tdevice_read(structfile*filp,
char*buffer, /*Thebuffertofillwithdata*/
size_tlength, /*Thelengthofthebuffer */
loff_t*offset) /*Ouroffsetinthefile */
{
/*Numberofbytesactuallywrittentothebuffer
**/
intbytes_read=0;
/*Ifwe'reattheendofthemessage,return0
*signifyingendoffile*/
if(*msg_Ptr==0)return0;
/*Actuallyputthedataintothebuffer*/
while(length&&*msg_Ptr) {
//内核空间与用户空间内存是不同的,所以需要使用内存操作函数而不可以直接赋值
/*Thebufferisintheuserdata
*segment,notthekernel
*segment;
* *assignmentwon'twork.
* Wehavetouseput_user
* whichcopiesdatafrom
* *thekerneldata
* segmenttothe
* userdata
* segment.*/
put_user(*(msg_Ptr++),buffer++);
length--;
bytes_read++;
}
/*Mostreadfunctionsreturnthe
*numberofbytesputintothebuffer
**/
returnbytes_read;
}
/* Calledwhenaprocesswritestodevfile:
echo"hi">
* /dev/hello*/
staticssize_tdevice_write(structfile*filp,
constchar*buff,
size_tlen,
loff_t*off)
{
printk("<1>Sorry,thisoperationisn'tsupported.\n");
return-EINVAL;
}
module_init(testmod_init);
module_exit(testmod_exit);
上面是一个简单的字符设备驱动例子
使用的makefile如下:
subdir='/root/work/kernel/'
obj-m:
=testmod.o
all:
make-C/usr/src/linuxSUBDIRS=$(subdir)modules
clean:
-rmtestmod.[^c]*
reset:
cleanall
-rmmodtestmod
-rm*wushuang*
insmodtestmod.ko
mknodtestmod_wushuang0c2400
使用时makereset就OK了
简单的测试办法
echo"hello">testmod_mushuang0
第一次自己写时没有把fops定义成静态的结果每次都报错很是郁闷后面从网上拿到了这个程序改了改
旧的内核需要自己维护模块引用计数所以使用MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT
新内核中自动维护这两个已经不再使用
file_operations定义了字符设备的接口你可以根据自己的需要添加自己要处理的函数不需要处理的函数将为NULL由内核自动处理
如果需要传参数那使用module_param宏
如下:
subdir='/root/work/kernel/'
obj-m:
=testmod.o
all:
make-C/usr/src/linuxSUBDIRS=$(subdir)modules
clean:
-rmtestmod.[^c]*
reset:
cleanall
-rmmodtestmod
-rm*wushuang*
insmodtestmod.ko
mknodtestmod_wushuang0c2400
内核内可使用符号的查找
第一个办法是看/proc/kallsyms
第二个是看内核文档或是代码
http:
//www.kernelnewbies.org/documents/这里列出了很多连接包括内核api文档
第三个是看邮件列表文档总不可能及时更新这时而相同的问题可能别人已经碰到过并处理完这时就没有必要再重复一次以体现自己的伟大了
另外需要注意的是开发时必须include正确的头文件不然会报错虽然为些错有些莫名奇妙(可能因为使用的宏类型比较多吧)
头文件在linux/目录下asm/目录下也有
如果你在linux下工作那建议使用cscope来帮助查找
使用方法
cd/usr/include/linux
cscope-R
然后就出现了cscope的界面
CODE
Globaldefinition:
get_user
FileLine
0uaccess.h171#defineget_user(x,ptr)\
FindthisCsymbol:
查找这个符号
Findthisglobaldefinition:
查找这个定义
Findfunctionscalledbythisfunction:
Findfunctionscallingthisfunction:
Findthistextstring:
Changethistextstring:
Findthisegreppattern:
Findthisfile:
Findfiles#includingthisfile:
使用tab来跳转想退出时按ctrl-d就可以或是ctrl-c
它可以反向查找和正向查找很是方便查找到后按数字或是在选中项上按回车就使用默认编辑器打开到指定页
另外已经集成在了vim中只是vim默认编译时不打开cscope支持如果想在vim中使用还需要自己另外编译
在内核中有一个全局变量current
它是一个task_strut的指针定义在asm/current.h
task_struct定义在linux/scked.h
这个变量指向了当前调用者信息
如果你想取调用者信息的话可以取出它的值
下面是一个简单的例子
staticssize_tdevice_read(structfile*filp,
char*buffer, /*Thebuffertofillwithdata*/
size_tlength, /*Thelengthofthebuffer */
loff_t*offset) /*Ouroffsetinthefile */
{
charoutbuf[2048];
intlen;
if(!
buffer||length<1)
return0;
len=dump_processinfo(outbuf,2047);
outbuf[2047]=0;
if(len>length)
len=length;
copy_to_user(buffer,outbuf,len);
returnlen;
}
staticsize_tdump_processinfo(char*buf,intlen)
{
if(!
buf||len<1)
return0;
returnsnprintf(buf,len,"stat:
%dactvated:
%dpid:
%dcmd:
%s\n",
current->state,current->activated,current->pid,current->comm);
}
把上面的device_read代替上一贴中的de