linux驱动程序设计
Ⅰ 深入理解linux驱动程序设计怎么样
《LINUX设备驱动程序(第3版)》详细介绍了Linux。
如果您希望在Linux操作系统上支持计算机外部设备,或者在Linux上运行新的硬件,或者只是希望一般性地了解Linux内核的编程,就一定要阅读本书。
本书描述了如何针对各种设备编写驱动程序,而在过去,这些内容仅仅以口头形式交流,或者零星出现在神秘的代码注释中。
Ⅱ 怎样入门Linux驱动程序开发,如:网卡驱动。
网卡驱动不涉及网络编程,所谓驱动就是硬件和OS通信的桥梁。想学linux驱动,自己网上买块开发板,买本linux设备驱动程序的书,然后从最简单的key驱动开始,然后触屏驱动,由浅入深。
Ⅲ 如何进行linux驱动程序设计
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少
Ⅳ 嵌入式Linux驱动程序开发学习路线
关于这个方向,我认为大概分3个阶段:
1、嵌入式linux上层应用,包括QT的GUI开发
2、嵌入式linux系统开发
3、嵌入式linux驱动开发
嵌入式目前主要面向的几个操作系统是,LINUX,WINCE、VxWorks等等
Linux是开源免费的,而且其源代码是开放的,更加适合我们学习嵌入式。
所以你可以尝试以下路线:
(1) C语言是所有编程语言中的强者,单片机、DSP、类似ARM的种种芯片的编程都可以用C语言搞定),因此必须非常熟练的掌握。
推荐书籍:《The C Programming Language》 这本经典的教材是老外写的,也有中译版本。
(2) 操作系统原理,是必需的,如果你是计算机专业毕业那也就无所谓了,如果是非计算机专业的就必须找一本比较浅显的计算机原理书籍看一看,把啥叫“进程”“线程”“系统调度”等等基本问题搞清楚。
(3)Linux操作系统就是用C语言编写的,所以你也应该先学习下Linux方面的编程,只有你会应用了,才能近一步去了解其内核的精髓。
推荐书籍:《UNIX环境高级编程》(第2版)
(4) 了解ARM的架构,原理,以及其汇编指令,我们在嵌入式开发中,一般很少去写汇编,但是最起码的要求是能够看懂arm汇编。
(5) 系统移植的时候,就需要你从最下层的bootloader开始,然后内核移植,文件系统移植等。而移植这部分对硬件的依赖是非常大的,其配置步骤也相对复杂,也没有太多详细资料。
(6) 驱动开发
linux驱动程序设计既是个极富有挑战性的领域,又是一个博大精深的内容。
linux驱动程序设计本质是属于linux内核编程范畴的,因而是对linux内核和内核编程是有要求的。在学习前你要想了解linux内核的组成,因为每一部分要详细研究的话足够可以扩展成一本厚书。
以上只不过是大概的框架,在实际的开发中还会涉及很多东西,比如:交叉编译、makefile、shell脚本等等,所以说学习嵌入式的周期较长,门槛较高,自学的话更是需要较强的学习能力和专业功底。只要能坚持下来一定会取得成功!
…………………………………………
嵌入式非常难,看书的话比较晦涩难懂,不容易入门,我个人比较偏向于看视频教程,因为有老师带着比较容易入门。给看看一篇文章是关于一位专科生怎么自学嵌入式的。
做个自我介绍,我07年考上一所很烂专科民办的学校,学的是生物专业,具体的学校名称我就不说出来献丑了。09年我就辍学了,我在那样的学校,一年学费要1万多,但是根本没有人学习,我实在看不到希望,我就退学了。
退学后我也迷茫,大专都没有毕业,我真的不知道我能干什么,我在纠结着我能做什么。所以辍学后我一段时间,我想去找工作,因为我比较沉默寡言,不是很会说话,我不适合去应聘做业务。我想应聘做技术的,可是处处碰壁。
一次偶然的机会,我才听到嵌入式这个行业。那天我去新华书店,在计算机分类那边想找本书学习。后来有个女孩子走过来,问我是不是读计算机的,有没有兴趣学习嵌入式,然后给我介绍了一下嵌入式现在的火热情况,告诉我学嵌入式多么的有前景,给我了一份传单,嵌入式培训的广告。听了她的介绍,我心里痒痒的,确实我很想去学会一门自己的技术,靠自己的双手吃饭。
回家后,我就上网查了下嵌入式,确实是当今比较热门的行业,也是比较好找工作的,工资也是相对比较高。我就下决心想学嵌入式了。于是我去找嵌入式培训的相关信息,说真的,我也很迷茫,我不知道培训是否真的能像他们宣传的那样好,所以我就想了解一段时间再做打算。
后来,我在网络知道看到一篇让我很鼓舞的文章,是一个嵌入式高手介绍没有基础的朋友怎么自学入门学嵌入式,文章写的很好,包含了如何学习,该怎么学习。他提到一个方法就是看视频,因为看书实在太枯燥和费解的,很多我们也看不懂。这点我真的很认同,我自己看书往往看不了几页。
我在想,为什么别人都能自学成才,我也可以的!我要相信自己,所以我就想自学,如果实在学不会我再去培训。
主意一定,我就去搜索嵌入式的视频,虽然零星找到一些嵌入式的视频,但是都不系统,我是想找一个能够告诉我该怎么学的视频,一套从入门到精通的视频,一个比较完整的资料,最好能有老师教,不懂可以请教的。
后来我又找到一份很好的视频,是在IT学习联盟网站推出的一份视频《零基础嵌入式就业班》(喜欢《零基础嵌入式就业班》的可以复制 sina.lt/qKh 粘贴浏览器按回车键即打开)。里面的教程还不错,很完整,可以让我从基础的开始学起。视频比较便宜。
下面介绍下我的学习流程,希望对和我一样完全没有基础的朋友有所帮助。
收到他们寄过来的光盘后,我就开始学习了,由于我没有什么基础,我就从最简单的C语言视频教程学起,话说简单,其实我还是很多不懂的,我只好请教他们,他们还是很热心的,都帮我解决了。C语言我差不多学了一个礼拜,接下来我就学了linux的基本命令,我在他们提供linux虚拟机上都有做练习,敲linux的基本命令,写简单的C语言代码,差不多也就三个礼拜。我每天都在不停的写一些简单的代码,这样一月后我基本掌握了C和linux的基本操作。
接下来我就去学习了人家的视频的培训教程,是整套的,和去参加培训没有多大的区别,这一看就是两个月,学习了ARM的基本原理,学习嵌入式系统的概念,也掌握了嵌入式的环境的一些搭建,对linux也有更深层次的理解了,明白了嵌入式应用到底是怎么做的,但是驱动我只是有一点点的了解,这个相对难一点,我想以后再慢慢啃。
这两个月,除了吃饭睡觉,我几乎都在学习。因为我知道几乎没有基础,比别人差劲,我只能坚持努力着,我不能放弃,我必要要靠自己来养活自己,必须学好这门技术,然后我就把不懂的问题总结记下来,这样慢慢积累了一段时间,我发现自己真的有点入门了。
最后的一个月,我就去看关于实践部分的内容,了解嵌入式项目具体的开发流程,需要什么样的知识,我就开始准备这方面的知识,也就是学习这方面的视频,同时他们建议我去找了找一些嵌入式面试的题目,为自己以后找工作做准备。我就到网上找了很多嵌入式的题目,把他们理解的记下来,这样差不多准备了20天左右
我觉得自己差不多入门了,会做一些简单的东西了。我就想去找工作看看,于是我就到51job疯狂的投简历,因为我学历的问题,专科没有毕业,说真的,大公司没有人会要我,所以我投的都是民营的小公司,我希望自己的努力有所回报。没有想过几天过后,就有面试了,但是第一次面试我失败了,虽然我自认为笔试很好,因为我之前做了准备,但是他们的要求比较严格,需要有一年的项目经验,所以我没有被选中。
后来陆续面试了几家公司,终于功夫不负有心人。我终于面试上的,是在闵行的一家民营的企业,公司规模比较小,我的职务是嵌入式linux应用开发,做安防产品的应用的。我想我也比较幸运,经理很看重我的努力,就决定录用我,开的工资是3500一个月,虽然我知道在上海3500只能过温饱的生活,但是我想我足够了。我至少不用每天都要靠父母养,我自己也能养活自己的。我想只要我继续努力,我工资一定会翻倍的。
把本文写出来,希望能让和我一样的没有基础的朋友有信心,其实我们没有必要自卑,我们不比别人笨,只要我们肯努力,我们一样会成功。
Ⅳ Linux驱动程序的工作原理
由于你的问题太长我只好转载别人的手打的太累不好意思~~~
Linux是Unix***作系统的一种变种,在Linux下编写驱动程序的原理和
思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的
区别.在Linux环境下设计驱动程序,思想简洁,***作方便,功芤端口芮看?但是
支持函数少,只能依赖kernel中的函数,有些常用的***作要自己来编写,而且调
试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,
获得了一些经验,愿与Linux fans共享,有不当之处,请予指正.
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,
Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关
device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依
据自己的试验结果进行了修正.
一. Linux device driver 的概念
系统调用是***作系统内核和应用程序之间的接口,设备驱动程序是***作系统
内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样
在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象***作普通文件
一样对硬件设备进行***作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux***作系统下有两类主要的设备文件类型,一种是字符设备,另一种是
块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际
的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,
当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际
的I/O***作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间
来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都
都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设
备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个
设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分
他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号
一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是
抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他
的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就
是漫长的fsck.//hehe
(请看下节,实例剖析)
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据
如何编写Linux***作系统下的设备驱动程序
Roy G
二.实例剖析
我们来写一个最简单的字符设备驱动程序.虽然它什么也不做,但是通过它
可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会
获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel
上可能会出现问题,我还没测试过.//xixi
#define __NO_VERSION__
#include
#include
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所
有的驱动程序的开头都要包含,但我看倒是未必.
由于用户进程是通过设备文件同硬件打交道,对设备文件的***作方式不外乎就
是一些系统调用,如 open,read,write,close...., 注意,不是fopen, fread.,
但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据
结构:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用
在对设备文件进行诸如read/write***作时,系统调用通过设备文件的主设备号
找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制
权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写
设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.
相当简单,不是吗?
下面就开始写子程序.
#include
#include
#include
#include
#include
unsigned int test_major = 0;
static int read_test(struct inode *node,struct file *file,
char *buf,int count)
{
int left;
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count left > 0 left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的
缓冲区全部写1.
buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test
被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),
这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的
函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用.
这就用到函数verify_area.
static int write_tibet(struct inode *inode,struct file *file,
const char *buf,int count)
{
return count;
}
static int open_tibet(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
} static void release_tibet(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空***作.实际调用发生时什么也不做,他们仅仅为下面的结构
提供函数指针。
struct file_operations test_fops = {
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test, NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序
可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),
如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能
动态的卸载,不利于调试,所以推荐使用模块方式。
int init_mole(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number ");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在
这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符
设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是
零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,
参数三用来登记驱动程序实际执行***作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_mole(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test
在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o molename.
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod -f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,
并可以看到它的主设备号,。
要卸载的话,运行
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\$2=="test" {print \$1}"
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include
#include
#include
#include
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file ");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d ",buf);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,
DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux***作系统下的设备驱动程序
Roy G
三 设备驱动程序中的一些具体问题。
1. I/O Port.
和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,
在linux下,***作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可以
对任意的I/O口***作,这样就很容易引起混乱。每个驱动程序应该自己避免
误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用
之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports
文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当
于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,
(之所以不说是dos***作系统是因为我认为DOS根本就不是一个***作系统,它实
在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk
提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但
在Linux中是怎样做的呢?
2 内存***作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用
get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,
kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于
kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理
地址的转换是由386cpu硬件完成的,那样汇编指令的***作数应该是线性地址,
驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc
返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可
以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是
linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法
不知对不对,还请高手指教。
言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符
结构占用了。kmalloc用法参见khg.
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000
以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得
重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直
驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上
mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap
之后就可以为DMA所用了。
请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。
3 中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,
void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags,
const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的
中断。
4一些常见的问题。
对硬件***作,有时时序很重要。但是如果用C语言写一些低级的硬件***作
的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,
gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的
办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码
都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要
用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现
出来。
关于kernel的调试工具,我现在还没有发现有合适的。有谁知道请告诉我,
不胜感激。我一直都在printk打印调试信息,倒也还凑合。
关于设备驱动程序还有很多内容,如等待/唤醒机制,块设备的编写等。
我还不是很明白,不敢乱说。
Ⅵ 解释一下linux驱动程序结构框架及工作原理
一、Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
1、对设备初始化和释放;
2、把数据从内核传送到硬件和从硬件读取数据;
3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;
4、检测和处理设备出现的错误。
在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。
二、实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:
STruct file_operatiONs {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。
下面就开始写子程序。
#include <linux/types.h> 基本的类型定义
#include <linux/fs.h> 文件系统使用相关的头文件
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test(struct inode *inode,struct file *file,char *buf,int count)
{
int left; 用户空间和内核空间
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。
static int write_test(struct inode *inode,struct file *file,const char *buf,int count)
{
return count;
}
static int open_test(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去
return 0;
}
static void release_test(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。
struct file_operations test_fops = {?
read_test,
write_test,
open_test,
release_test,
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。
int init_mole(void)
{
int result;
result = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口
if (result < 0) {
printk(KERN_INFO "test: can't get major number\n");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_mole(void)
{
unregister_chrdev(test_major,"test");
}
在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译 :
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c表示输出制定名,自动生成.o文件
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld ?-r ?file1.o ?file2.o ?-o ?molename。
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod ?–f ?test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
}
编译运行,看看是不是打印出全1
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。
Ⅶ 如何系统的学习Linux驱动开发
在学习之前一直对驱动开发非常的陌生,感觉有点神秘。不知道驱动开发和普通的程序开发究竟有什么不同;它的基本框架又是什么样的;他的开发环境有什么特殊的地方;以及怎么写编写一个简单的字符设备驱动前编译加载,下面我就对这些问题一个一个的介绍。
一、驱动的基本框架
1.那么究竟什么是驱动程序,它有什么用呢:
l驱动是硬件设备与应用程序之间的一个中间软件层
l它使得某个特定硬件能够响应一个定义良好的内部编程接口,同时完全隐蔽了设备的工作细节
l用户通过一组与具体设备无关的标准化的调用来完成相应的操作
l驱动程序的任务就是把这些标准化的系统调用映射到具体设备对于实际硬件的特定操作上
l驱动程序是内核的一部分,可以使用中断、DMA等操作
l驱动程序在用户态和内核态之间传递数据
2.Linux驱动的基本框架
3.Linux下设备驱动程序的一般可以分为以下三类
1)字符设备
a)所有能够象字节流一样访问的设备都通过字符设备来实现
b)它们被映射为文件系统中的节点,通常在/dev/目录下面
c)一般要包含open read write close等系统调用的实现
2)块设备
d)通常是指诸如磁盘、内存、Flash等可以容纳文件系统的存储设备。
e)块设备也是通过文件系统来访问,与字符设备的区别是:内核管理数据的方式不同
f)它允许象字符设备一样以字节流的方式来访问,也可一次传递任意多的字节。
3)网络接口设备
g)通常它指的是硬件设备,但有时也可能是一个软件设备(如回环接口loopback),它们由内核中网络子系统驱动,负责发送和接收数据包。
h)它们的数据传送往往不是面向流的,因此很难将它们映射到一个文件系统的节点上。
二、怎么搭建一个驱动的开发环境
因为驱动是要编译进内核,在启动内核时就会驱动此硬件设备;或者编译生成一个.o文件,当应用程序需要时再动态加载进内核空间运行。因此编译任何一个驱动程序都要链接到内核的源码树。所以搭建环境的第一步当然是建内核源码树
1.怎么建内核源码树
a)首先看你的系统有没有源码树,在你的/lib/ moles目录下会有内核信息,比如我当前的系统里有两个版本:
#ls /lib/ moles
2.6.15-rc72.6.21-1.3194.fc7
查看其源码位置:
## ll /lib/moles/2.6.15-rc7/build
lrwxrwxrwx 1 root root 27 2008-04-28 19:19 /lib/moles/2.6.15-rc7/build -> /root/xkli/linux-2.6.15-rc7
发现build是一个链接文件,其所对应的目录就是源码树的目录。但现在这里目标目录已经是无效的了。所以得自己重新下载
b)下载并编译源码树
有很多网站上可以下载,但官方网址是:
http://www.kernel.org/pub/linux/kernel/v2.6/
下载完后当然就是解压编译了
# tar –xzvf linux-2.6.16.54.tar.gz
#cd linux-2.6.16.54
## make menuconfig (配置内核各选项,如果没有配置就无法下一步编译,这里可以不要改任何东西)
#make
…
如果编译没有出错。那么恭喜你。你的开发环境已经搭建好了
三、了解驱动的基本知识
1.设备号
1)什么是设备号呢?我们进系统根据现有的设备来讲解就清楚了:
#ls -l /dev/
crwxrwxrwx 1 root root1,3 2009-05-11 16:36 null
crw------- 1 root root4,0 2009-05-11 16:35 systty
crw-rw-rw- 1 root tty5,0 2009-05-11 16:36 tty
crw-rw---- 1 root tty4,0 2009-05-11 16:35 tty0
在日期前面的两个数(如第一列就是1,3)就是表示的设备号,第一个是主设备号,第二个是从设备号
2)设备号有什么用呢?
l传统上,主编号标识设备相连的驱动.例如, /dev/null和/dev/zero都由驱动1来管理,而虚拟控制台和串口终端都由驱动4管理
l次编号被内核用来决定引用哪个设备.依据你的驱动是如何编写的自己区别
3)设备号结构类型以及申请方式
l在内核中, dev_t类型(在中定义)用来持有设备编号,对于2.6.0内核, dev_t是32位的量, 12位用作主编号, 20位用作次编号.
l能获得一个dev_t的主或者次编号方式:
MAJOR(dev_t dev); //主要
MINOR(dev_t dev);//次要
l但是如果你有主次编号,需要将其转换为一个dev_t,使用: MKDEV(int major, int minor);
4)怎么在程序中分配和释放设备号
在建立一个字符驱动时需要做的第一件事是获取一个或多个设备编号来使用.可以达到此功能的函数有两个:
l一个是你自己事先知道设备号的
register_chrdev_region,在中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first是你要分配的起始设备编号. first的次编号部分常常是0,count是你请求的连续设备编号的总数. name是应当连接到这个编号范围的设备的名子;它会出现在/proc/devices和sysfs中.
l第二个是动态动态分配设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
使用这个函数, dev是一个只输出的参数,它在函数成功完成时持有你的分配范围的第一个数. fisetminor应当是请求的第一个要用的次编号;它常常是0. count和name参数如同给request_chrdev_region的一样.
5)设备编号的释放使用
不管你是采用哪些方式分配的设备号。使用之后肯定是要释放的,其方式如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
6)
2.驱动程序的二个最重要数据结构
1)file_operation
倒如字符设备scull的一般定义如下:
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,
};
file_operation也称为设备驱动程序接口
定义在,是一个函数指针的集合.每个打开文件(内部用一个file结构来代表)与它自身的函数集合相关连(通过包含一个称为f_op的成员,它指向一个file_operations结构).这些操作大部分负责实现系统调用,因此,命名为open, read,等等
2)File
定义位于include/fs.h
struct file结构与驱动相关的成员
lmode_t f_mode标识文件的读写权限
lloff_t f_pos当前读写位置
lunsigned int_f_flag文件标志,主要进行阻塞/非阻塞型操作时检查
lstruct file_operation * f_op文件操作的结构指针
lvoid * private_data驱动程序一般将它指向已经分配的数据
lstruct dentry* f_dentry文件对应的目录项结构
3.字符设备注册
1)内核在内部使用类型struct cdev的结构来代表字符设备.在内核调用你的设备操作前,必须编写分配并注册一个或几个这些结构.有2种方法来分配和初始化一个这些结构.
l如果你想在运行时获得一个独立的cdev结构,可以这样使用:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
l如果想将cdev结构嵌入一个你自己的设备特定的结构;你应当初始化你已经分配的结构,使用:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
2)一旦cdev结构建立,最后的步骤是把它告诉内核,调用:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
说明:dev是cdev结构, num是这个设备响应的第一个设备号, count是应当关联到设备的设备号的数目.常常count是1,但是有多个设备号对应于一个特定的设备的情形.
3)为从系统去除一个字符设备,调用:
void cdev_del(struct cdev *dev);
4.open和release
Ⅷ 如何编写Linux 驱动程序
以装载和卸载模块为例:
1、首先输入代码
#include <linux/init.h>
#include <linux/mole.h>
Ⅸ 如何编写Linux操作系统的设备驱动程序
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和
思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的
区别.在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是
支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调
试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,
获得了一些经验,愿与Linux fans共享,有不当之处,请予指正.
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,
Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关
device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依
据自己的试验结果进行了修正.
一. Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统
内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样
在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件
一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是
块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际
的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,
当用户进程对设备请求读/写时,它首先察看缓冲区的内容,如果缓冲区的数据
能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际
的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间
来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都
都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设
备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个
设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分
他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号
一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是
抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他
的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就
Ⅹ Linux驱动开发入门与实战的目录
第1篇 linux驱动开发基础
第1章 linux驱动开发概述 2
1.1 linux设备驱动的基本概念 2
1.1.1 设备驱动程序概述 2
1.1.2 设备驱动程序的作用 2
1.1.3 设备驱动的分类 3
1.2 linux操作系统与驱动的关系 4
1.3 linux驱动程序开发 4
1.3.1 用户态和内核态 5
1.3.2 模块机制 5
1.3.3 编写设备驱动程序需要了解的知识 6
1.4 编写设备驱动程序的注意事项 6
1.4.1 应用程序开发与驱动程序开发的差异 6
1.4.2 gun c开发驱动程序 7
1.4.3 不能使用c库开发驱动程序 7
1.4.4 没有内存保护机制 7
1.4.5 小内核栈 8
1.4.6 重视可移植性 8
1.5 linux驱动的发展趋势 9
1.5.1 linux驱动的发展 9
.1.5.2 驱动的应用 9
1.5.3 相关学习资源 9
1.6 小结 10
第2章 嵌入式处理器和开发板简介 11
2.1 处理器的选择 11
2.1.1 处理器简述 11
2.1.2 处理器的种类 11
2.2 arm处理器 13
2.2.1 arm处理器简介 14
2.2.2 arm处理器系列 14
2.2.3 arm处理器的应用 16
2.2.4 arm处理器的选型 16
2.2.5 arm处理器选型举例 19
2.3 s3c2440开发板 20
2.3.1 s3c2440开发板简介 20
2.3.2 s3c2440开发板的特性 20
2.4 小结 22
第3章 构建嵌入式驱动程序开发环境 23
3.1 虚拟机和linux安装 23
3.1.1 在windows上安装虚拟机 23
3.1.2 在虚拟机上安装linux 27
3.1.3 设置共享目录 28
3.2 代码阅读工具source insight 29
3.2.1 source insight简介 30
3.2.2 阅读源代码 30
3.3 小结 33
第4章 构建嵌入式linux操作系统 34
4.1 linux操作系统的介绍 34
4.1.1 linux操作系统 34
4.1.2 linux操作系统的优点 35
4.2 linux内核子系统 36
4.2.1 进程管理 36
4.2.2 内存管理 37
4.2.3 文件系统 37
4.2.4 设备管理 37
4.2.5 网络功能 38
4.3 linux源代码结构分析 38
4.3.1 arch目录 38
4.3.2 drivers目录 39
4.3.3 fs目录 39
4.3.4 其他目录 40
4.4 内核配置选项 41
4.4.1 配置编译过程 41
4.4.2 常规配置 42
4.4.3 模块配置 44
4.4.4 块设备层配置 44
4.4.5 cpu类型和特性配置 45
4.4.6 电源管理配置 47
4.4.7 总线配置 49
4.4.8 网络配置 50
4.4.9 设备驱动配置 53
4.4.10 文件系统配置 60
4.5 嵌入式文件系统基础知识 62
4.5.1 嵌入式文件系统 62
4.5.2 嵌入式系统的存储介质 63
4.5.3 jffs文件系统 64
4.5.4 yaffs文件系统 64
4.6 构建根文件系统 64
4.6.1 根文件系统概述 65
4.6.2 linux根文件系统目录结构 65
4.6.3 busybox构建根文件系统 66
4.7 小结 71
第5章 构建第一个驱动程序 72
5.1 开发环境配置之内核升级 72
5.1.1 为什么升级内核 72
5.1.2 内核升级 73
5.1.3 make menconfig的注意事项 75
5.2 hello world驱动程序 77
5.2.1 驱动模块的组成 77
5.2.2 hello world模块 78
5.2.3 编译hello world模块 79
5.2.4 模块的操作 81
5.2.5 hello world模块加载后文件系统的变化 82
5.3 模块参数和模块之间通信 83
5.3.1 模块参数 83
5.3.2 模块的文件格式elf 83
5.3.3 模块之间的通信 84
5.3.4 模块之间的通信实例 85
5.4 将模块加入内核 88
5.4.1 向内核添加模块 88
5.4.2 kconfig 88
5.4.3 kconfig的语法 89
5.4.4 应用实例:在内核中新增加add_sub模块 92
5.4.5 对add_sub模块进行配置 94
5.5 小结 95
第6章 简单的字符设备驱动程序 96
6.1 字符设备驱动程序框架 96
6.1.1 字符设备和块设备 96
6.1.2 主设备号和次设备号 97
6.1.3 申请和释放设备号 98
6.2 初识cdev结构 99
6.2.1 cdev结构体 99
6.2.2 file_operations结构体 101
6.2.3 cdev和file_operations结构体的关系 102
6.2.4 inode结构体 103
6.3 字符设备驱动的组成 103
6.3.1 字符设备加载和卸载函数 103
6.3.2 file_operations结构体和其成员函数 104
6.3.3 驱动程序与应用程序的数据交换 105
6.3.4 字符设备驱动程序组成小结 106
6.4 virtualdisk字符设备驱动 106
6.4.1 virtualdisk的头文件、宏和设备结构体 106
6.4.2 加载和卸载驱动程序 107
6.4.3 cdev的初始化和注册 108
6.4.4 打开和释放函数 109
6.4.5 读写函数 110
6.4.6 seek()函数 111
6.4.7 ioctl()函数 113
6.5 小结 113
第2篇 linux驱动开发核心技术
第7章 设备驱动中的并发控制 116
7.1 并发与竞争 116
7.2 原子变量操作 116
7.2.1 原子变量操作 116
7.2.2 原子整型操作 117
7.2.3 原子位操作 119
7.3 自旋锁 120
7.3.1 自旋锁概述 120
7.3.2 自旋锁的使用 120
7.3.3 自旋锁的使用注意事项 122
7.4 信号量 122
7.4.1 信号量概述 122
7.4.2 信号量的实现 123
7.4.3 信号量的使用 123
7.4.4 自旋锁与信号量的对比 125
7.5 完成量 126
7.5.1 完成量概述 126
7.5.2 完成量的实现 126
7.5.3 完成量的使用 127
7.6 小结 128
第8章 设备驱动中的阻塞和同步机制 129
8.1 阻塞和非阻塞 129
8.2 等待队列 130
8.2.1 等待队列概述 130
8.2.3 等待队列的实现 130
8.2.3 等待队列的使用 131
8.3 同步机制实验 132
8.3.1 同步机制设计 132
8.3.2 实验验证 136
8.4 小结 137
第9章 中断与时钟机制 138
9.1 中断简述 138
9.1.1 中断的概念 138
9.1.2 中断的宏观分类 139
9.1.3 中断产生的位置分类 140
9.1.4 同步和异步中断 140
9.1.5 中断小结 140
9.2 中断的实现过程 141
9.2.1 中断信号线(irq) 141
9.2.2 中断控制器 141
9.2.3 中断处理过程 142
9.2.4 中断的安装与释放 142
9.3 按键中断实例 144
9.3.1 按键设备原理图 144
9.3.2 有寄存器设备和无寄存器设备 144
9.3.3 按键设备相关端口寄存器 145
9.4 按键中断实例程序分析 147
9.4.1 按键驱动程序组成 147
9.4.2 初始化函数s3c2440_buttons_init() 147
9.4.3 中断处理函数isr_button() 148
9.4.4 退出函数s3c2440_buttons_exit() 149
9.5 时钟机制 150
9.5.1 时间度量 150
9.5.2 时间延时 150
9.6 小结 151
第10章 内外存访问 152
10.1 内存分配 152
10.1.1 kmalloc()函数 152
10.1.2 vmalloc()函数 153
10.1.3 后备高速缓存 155
10.2 页面分配 156
10.2.1 内存分配 156
10.2.2 物理地址和虚拟地址之间的转换 159
10.3 设备i/o端口的访问 160
10.3.1 linux i/o端口读写函数 160
10.3.2 i/o内存读写 160
10.3.3 使用i/o端口 164
10.4 小结 166
第3篇 linux驱动开发实用实战
第11章 设备驱动模型 168
11.1 设备驱动模型概述 168
11.1.1 设备驱动模型的功能 168
11.1.2 sysfs文件系统 169
11.1.3 sysfs文件系统的目录结构 170
11.2 设备驱动模型的核心数据结构 171
11.2.1 kobject结构体 171
11.2.2 设备属性kobj_type 175
11.3 注册kobject到sysfs中的实例 179
11.3.1 设备驱动模型结构 179
11.3.2 kset集合 180
11.3.3 kset与kobject的关系 181
11.3.4 kset相关的操作函数 182
11.3.5 注册kobject到sysfs中的实例 183
11.3.6 实例测试 187
11.4 设备驱动模型的三大组件 188
11.4.1 总线 188
11.4.2 总线属性和总线方法 192
11.4.3 设备 194
11.4.4 驱动 196
11.5 小结 198
第12章 rtc实时时钟驱动 199
12.1 rtc实时时钟硬件原理 199
12.1.1 rtc实时时钟 199
12.1.2 rtc实时时钟的功能 199
12.1.2 rtc实时时钟的工作原理 201
12.2 rtc实时时钟架构 205
12.2.1 加载卸载函数 205
12.2.2 rtc实时时钟的平台驱动 206
12.2.3 rtc驱动探测函数 207
12.2.4 rtc实时时钟的使能函数s3c_rtc_enable() 210
12.2.5 rtc实时时钟设置频率函数s3c_rtc_setfreq() 211
12.2.6 rtc设备注册函数 rtc_device_register() 212
12.3 rtc文件系统接口 214
12.3.1 文件系统接口rtc_class_ops 214
12.3.2 rtc实时时钟打开函数s3c_rtc_open() 215
12.3.3 rtc实时时钟关闭函数s3c_rtc_release() 216
12.3.4 rtc实时时钟获得时间函数s3c_rtc_gettime() 216
12.3.5 rtc实时时钟设置时间函数s3c_rtc_settime() 218
12.3.6 rtc驱动探测函数s3c_rtc_getalarm() 219
12.3.7 rtc实时时钟设置报警时间函数s3c_rtc_setalarm() 220
12.3.8 rtc设置脉冲中断使能函数s3c_rtc_setpie() 222
12.3.9 rtc时钟脉冲中断判断函数s3c_rtc_proc() 222
12.4 小结 223
第13章 看门狗驱动程序 224
13.1 看门狗硬件原理 224
13.1.1 看门狗 224
13.1.2 看门狗工作原理 224
13.2 平台设备模型 226
13.2.1 平台设备模型 226
13.2.2 平台设备 227
13.2.3 平台设备驱动 229
13.2.4 平台设备驱动的注册和注销 230
13.2.5 混杂设备 231
13.2.6 混杂设备的注册和注销 232
13.3 看门狗设备驱动程序分析 232
13.3.1 看门狗驱动程序的一些变量定义 232
13.3.2 看门狗模块的加载和卸载函数 233
13.3.3 看门狗驱动程序探测函数 234
13.3.4 设置看门狗复位时间函数s3c2410wdt_set_heartbeat() 235
13.3.5 看门狗的开始函数s3c2410wdt_start()和停止函数
s3c2410wdt_ stop() 237
13.3.6 看门狗驱动程序移除函数s3c2410wdt_remove() 238
13.3.7 平台设备驱动s3c2410wdt_driver中的其他重要函数 238
13.3.8 混杂设备的file_operations中的函数 239
13.3.9 看门狗中断处理函数s3c2410wdt_irq() 242
13.4 小结 243
第14章 iic设备驱动程序 244
14.1 iic设备的总线及其协议 244
14.1.1 iic总线的特点 244
14.1.2 iic总线的信号类型 245
14.1.3 iic总线的数据传输 245
14.2 iic设备的硬件原理 246
14.3 iic设备驱动程序的层次结构 247
14.3.1 iic设备驱动的概述 248
14.3.2 iic设备层 248
14.3.3 i2c_driver和i2c_client的关系 251
14.3.4 iic总线层 251
14.3.5 iic设备层和总线层的关系 253
14.3.6 写iic设备驱动的步骤 253
14.4 iic子系统的初始化 254
14.4.1 iic子系统初始化函数i2c_init() 254
14.4.2 iic子系统退出函数i2c_exit () 254
14.5 适配器驱动程序 255
14.5.1 s3c2440对应的适配器结构体 255
14.5.2 iic适配器加载函数i2c_add_adapter() 257
14.5.3 idr机制 257
14.5.4 适配器卸载函数i2c_del_adapter() 260
14.5.5 iic总线通信方法s3c24xx_i2c_algorithm结构体 260
14.5.6 适配器的传输函数s3c24xx_i2c_doxfer() 262
14.5.7 适配器的中断处理函数s3c24xx_i2c_irq() 265
14.5.8 字节传输函数i2s_s3c_irq_nextbyte() 267
14.5.9 适配器传输停止函数s3c24xx_i2c_stop() 269
14.5.10 中断处理函数的一些辅助函数 270
14.6 iic设备层驱动程序 270
14.6.1 iic设备驱动模块加载和卸载 271
14.6.2 探测函数s3c24xx_i2c_probe() 272
14.6.3 移除函数s3c24xx_i2c_remove() 274
14.6.4 控制器初始化函数s3c24xx_i2c_init() 275
14.6.5 设置控制器数据发送频率函数s3c24xx_i2c_clockrate() 276
14.7 小结 278
第15章 lcd设备驱动程序 279
15.1 framebuffer概述 279
15.1.1 framebuffer的概念 279
15.1.2 framebuffer与应用程序的交互 280
15.1.3 framebuffer显示原理 280
15.1.4 lcd显示原理 281
15.2 framebuffer的结构分析 281
15.2.1 framebuffer架构和其关系 281
15.2.2 framebuffer驱动程序的实现 282
15.2.3 framebuffer架构及其关系 283
15.3 lcd驱动程序分析 288
15.3.1 lcd模块的加载和卸载函数 288
15.3.2 lcd驱动程序的平台数据 290
15.3.3 lcd模块的探测函数 291
15.3.4 移除函数 295
15.4 小结 296
第16章 触摸屏设备驱动程序 297
16.1 触摸屏设备工作原理 297
16.1.1 触摸屏设备概述 297
16.1.2 触摸屏设备的类型 297
16.1.3 电阻式触摸屏 298
16.2 触摸屏设备硬件结构 298
16.2.1 s3c2440触摸屏接口概述 298
16.2.2 s3c2440触摸屏接口的工作模式 299
16.2.3 s3c2440触摸屏设备寄存器 300
16.3 触摸屏设备驱动程序分析 303
16.3.1 触摸屏设备驱动程序组成 303
16.3.2 s3c2440触摸屏驱动模块的加载和卸载函数 304
16.3.3 s3c2440触摸屏驱动模块的探测函数 305
16.3.4 触摸屏设备配置 308
16.3.5 触摸屏设备中断处理函数 309
16.3.6 s3c2440触摸屏驱动模块的remove()函数 314
16.4 测试触摸屏驱动程序 314
16.5 小结 316
第17章 输入子系统设计 317
17.1 input子系统入门 317
17.1.1 简单的实例 317
17.1.2 注册函数input_register_device() 319
17.1.3 向子系统报告事件 323
17.2 handler注册分析 328
17.2.1 输入子系统的组成 328
17.2.2 input_handler结构体 328
17.2.3 注册input_handler 329
17.2.4 input_handle结构体 330
17.2.5 注册input_handle 331
17.3 input子系统 332
17.3.1 子系统初始化函数input_init() 333
17.3.2 文件打开函数input_open_file() 333
17.4 evdev输入事件驱动分析 335
17.4.1 evdev的初始化 335
17.4.2 evdev设备的打开 337
17.5 小结 340
第18章 块设备驱动程序 341
18.1 块设备简介 341
18.1.1 块设备总体概述 341
18.1.2 块设备的结构 342
18.2 块设备驱动程序的架构 344
18.2.1 块设备加载过程 344
18.2.2 块设备卸载过程 345
18.3 通用块层 346
18.3.1 通用块层 346
18.3.2 alloc_disk()函数对应的gendisk结构体 346
18.3.3 块设备的注册和注销 349
18.3.4 请求队列 349
18.3.5 设置gendisk属性中的block_device_operations结构体 350
18.4 不使用请求队列的块设备驱动 351
18.4.1 不使用请求队列的块设备驱动程序的组成 352
18.4.2 宏定义和全局变量 352
18.4.3 加载函数 353
18.4.4 卸载函数 355
18.4.5 自定义请求处理函数 355
18.4.6 驱动的测试 356
18.5 i/o调度器 359
18.5.1 数据从内存到磁盘的过程 359
18.5.2 块i/o请求(bio) 360
18.5.3 请求结构(request) 363
18.5.4 请求队列(request_queue) 364
18.5.5 请求队列、请求结构、bio等之间的关系 365
18.5.6 四种调度算法 365
18.6 自定义i/o调度器 367
18.6.1 virtual_blkdev块设备的缺陷 367
18.6.2 指定noop调度器 368
18.6.3 virtual_blkdev的改进实例 368
18.6.4 编译和测试 369
18.7 脱离i/o调度器 370
18.7.1 请求队列中的bio处理函数 370
18.7.2 通用块层函数调用关系 371
18.7.3 对virtual_blkdev块设备的改进 373
18.7.4 编译和测试 376
18.8 块设备的物理结构 377
18.8.1 为virtual_blkdev块设备添加分区 377
18.8.2 对新的virtual_blkdev代码的分析 378
18.8.3 编译和测试 379
18.8.4 分区数的计算 381
18.8.5 设置virtual_blkdev的结构 382
18.8.6 编译和测试 384
18.9 小结 387
第19章 usb设备驱动程序 389
19.1 usb概述 389
19.1.1 usb概念 389
19.1.2 usb的特点 390
19.1.3 usb总线拓扑结构 391
19.1.4 usb驱动总体架构 391
19.2 usb设备驱动模型 395
19.2.1 usb驱动初探 395
19.2.2 usb设备驱动模型 397
19.2.3 usb驱动结构usb_driver 399
19.3 usb设备驱动程序 404
19.3.1 usb设备驱动加载和卸载函数 404
19.3.2 探测函数probe()的参数usb_interface 405
19.3.3 usb协议中的设备 406
19.3.4 端点的传输方式 412
19.3.5 设置 413
19.3.6 探测函数storage_probe() 415
19.4 获得usb设备信息 418
19.4.1 设备关联函数associate_dev() 418
19.4.2 获得设备信息函数get_device_info() 419
19.4.3 得到传输协议get_transport()函数 420
19.4.4 获得协议信息函数get_protocol() 421
19.4.5 获得管道信息函数get_pipes() 422
19.5 资源的初始化 425
19.5.1 storage_probe()函数调用过程 425
19.5.2 资源获取函数usb_stor_acquire_resources() 426
19.5.3 usb请求块(urb) 427
19.6 控制子线程 430
19.6.1 控制线程 431
19.6.2 扫描线程usb_stor_scan_thread() 433
19.6.3 获得lun函数usb_stor_bulk_max_lun() 434
19.7 小结 441