其他
嵌入式驱动方面,这篇文章讲绝了!
整理:付斌,参考自网络资料
01
嵌入式驱动开发到底学什么
嵌入式大体分为以下四个方向:
二、嵌入式驱动开发:熟练掌握Linux操作系统、系统结构、计算机组成原理、数据结构相关知识。熟悉嵌入式ARM开发,至少掌握Linux字符驱动程序开发。具有单片机、ARM嵌入式处理器的移植开发能力,理解硬件原理图,能独立完成相关硬件驱动调试,具有扎实的硬件知识,能够根据芯片手册编写软件驱动程序。
三、嵌入式系统开发:掌握Linux系统配置,精通处理器体系结构、编程环境、指令集、寻址方式、调试、汇编和混合编程等方面的内容;掌握Linux文件系统制作,熟悉各种文件系统格式(YAFFS2、JAFFS2、RAMDISK等);熟悉嵌入式Linux启动流程,熟悉Linux配置文件的修改;掌握内核裁减、内核移植、交叉编译、内核调试、启动程序Bootloader编写、根文件系统制作和集成部署Linux系统等整个流程;、熟悉搭建Linux软件开发环境(库文件的交叉编译及环境配置等);
四、嵌入式软件开发:精通Linux操作系统的概念和安装方法、Linux下的基本命令、管理配置和编辑器,包括VI编辑器,GCC编译器,GDB调试器和 Make 项目管理工具等知识;精通C语言的高级编程知识,包括函数与程序结构、指针、数组、常用算法、库函数的使用等知识、数据结构的基础内容,包括链表、队列等;掌握面向对象编程的基本思想,以及C++语言的基础内容;精通嵌入式Linux下的程序设计,精通嵌入式Linux开发环境,包括系统编程、文件I/O、多进程和多线程、网络编程、GUI图形界面编程、数据库;熟悉常用的图形库的编程,如QT、GTK、miniGUI、fltk、nano-x等。
公司的日常活动还是看公司的规模,大一点的一般只是让你负责一个模块,这样你就要精通一点。若是公司比较小的话估计要你什么都做一点。还要了解点硬件的东西。
纯软学习的是一门语言,例如C,C++,java,甚至Python,语言说到底只是一门工具,就像学会英语法语日语一样。
但嵌入式学习的是软件+硬件,通俗的讲,它学的是做系统做产品,讲究的是除了具体的语言工具,更多的是如何将一个产品分解为具体可实施的软件和硬件,以及更小的单元。
▍PART 01
我们说的驱动,其实并不局限于硬件的操作,还有操作系统的原理、进程的休眠唤醒调度等概念。想写出一个好的应用,想比较好的解决应用碰到的问题,这些知识大家应该都懂。
▍PART 02
做应用的发展路径个人认为就是业务纯熟。比如在通信行业、IPTV行业、手机行业,行业需求很了解。
▍PART 03
做驱动,其实不能称为“做驱动”,而是可以称为“做底层系统”,做好了这是通杀各行业。比如一个人工作几年,做过手机、IPTV、会议电视,但是这些产品对他毫无差别,因为他只做底层。当应用出现问题,解决不了时,他就可以从内核角度给他们出主意,提供工具。做底层的发展方向,应该是技术专家。
▍PART 04
其实,做底层还是做应用,之间并没有一个界线,有底层经验,再去做应用,会感觉很踏实。有了业务经验,再了解一下底层,很快就可以组成一个团队。
02
嵌入式Linux底层系统包含哪些东西?
一、bootloader
它就是一个稍微复杂的裸板程序。但是要把这裸板程序看懂写好一点都不容易。Windows下好用的工具弱化了我们的编程能力。很多人一玩嵌入式就用ADS、KEIL。能回答这几个问题吗?
Q:一上电,CPU从哪里取指令执行?
A:一般从Flash上指令。
A:全局变量应该在内存里。
A:长期用ADS、KEIL的朋友,你能回答吗?这需要"重定位"。在ADS或KEIL里,重定位的代码是制作这些工具的公司帮你写好了。你可曾去阅读过?
A:这个地址用"链接脚本"决定,在ADS里有scatter文件,KEIL里也有类似的文件。但是,你去研究过吗?
A:是的,要能操作Flash。当然不仅仅是这些,还有设置时钟让系统运行得更快等等。
①对硬件的操作
对硬件的操作,需要看原理图、芯片手册。这需要一定的硬件知识,不要求能设计硬件,但是至少能看懂; 不求能看懂模拟电路,但是要能看懂数字电路。这方面的能力在学校里都可以学到,微机原理、数字电路这2本书就足够了。想速成的话,就先放掉这块吧,不懂就GOOGLE、发贴。另外,芯片手册是肯定要读的,别去找中文的,就看英文的。开始是非常痛苦,以后就会发现那些语法、词汇一旦熟悉后,读任何芯片手册都很容易。
对ARM体系处理器的了解,可以看杜春蕾的<ARM体系架构与编程>,里面讲有汇编指令,有异常模式、MMU等。也就这3块内容需要了解。
程序的基本概念,王道当然是去看编译原理了。可惜,这类书绝对是天书级别的。若非超级天才还是别去看了。可以看韦东山的<嵌入式Linux应用开发完全手册>。
总结一下,看懂硬件原理图、看芯片手册,这都需要自己去找资料。
二、内核
想成为高手,内核必须深刻了解。注意,是了解,要对里面的调度机制、内存管理机制、文件管理机制等等有所了解。
1. 通读<linux内核完全注释>,请看薄的那本2. 选读<Linux内核情景分析>, 想了解哪一块就读哪一节
三、驱动
驱动包含两部分:硬件本身的操作、驱动程序的框架。
又是硬件,还是要看得懂原理图、读得懂芯片手册,多练吧。
①硬件本身的操作
说到驱动框架,有一些书介绍一下。LDD3,即<Linux设备驱动>,老外写的那本,里面介绍了不少概念,值得一读。但是,它的作用 也就限于介绍概念了。入门之前可以用它来熟悉一下概念。
②驱动程序的框架
驱动方面比较全的介绍,应该是宋宝华的<linux设备驱动开发详解>了。要想深入了解某一块,<Linux内核情景分析>绝对是超5星级推荐。别指望把它读完,1800多页,上下两册呢。某一块不清楚时,就去翻一下它。任何一部分,这书都可以讲上2、3百页,非常详细。并且是以某个目标来带你分析内核源码。它以linux2.4为例,但是原理相通,同样适用于其它版本的linux。
四、根文件系统
大家有没有想过这2个问题:
Q:对于Linux做出来的产品,有些用作监控、有些做手机、有些做平板。那么内核启动后,挂载根文件系统后,应该启动哪一个应用程序呢?
A:内核不知道也不管应该启动哪一个用户程序。它只启动init这一个应用程序,它对应/sbin/init。
Q:你写的hello,world程序,有没有想过里面用到的printf是谁实现的?
A:这个函数不是你实现的,是库函数实现的。它运行时,得找到库。
简单的自问自答到这里,要想深入了解,可以看一下busybox的init.c,就可以知道init进程做的事情了。
当然,也可以看<嵌入式Linux应用开发完全手册>里构建根文件系统那章。
03
驱动程序设计的5个方法
04
大牛对于嵌入式驱动开发的建议
譬如,做过网络驱动,那么是不是只停留在会写驱动的表层上,有没有对Linux内核的网络结构,TCP/IP协议作过深入的了解。
当你写过Flash驱动,可能会知道Flash的性能有时候有多重要。
对于不同品牌的Flash,如果使得Flash的驱动做的更具有灵活性。
顺便提一点,适时的提高你的英语水平,对你的职业生涯绝对有帮助。(不要等需要的时候再补,来不及)
有时候是datasheet里面的一句话没有注意,还有好几次调不出来最后查到是PCB的问题,所以有时候特别晕。
05
嵌入式驱动自学者的感受
06
如何编写嵌入式Linux设备驱动程序?
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,
};
int init_module(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;
}
void cleanup_module(void)
{
unregister_chrdev(test_major,"test");
}
#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 ?
07
嵌入式驱动的结构分析
在Linux系统上编写驱动程序,说简单也简单,说难也难。难在于对算法的编写和设备的控制方面,是比较让人头疼的;说它简单是因为在Linux下已经有一套驱动开发的模式,编写的时候只需要按照这个模式写就可以了,而这个模式就是它事先定义好的一些结构体,在驱动编写的时候,只要对这些结构体根据设备的需求进行适当的填充,就实现了驱动的编写。
首先在Linux下,视一切事物皆为文件,它同样把驱动设备也看成是文件,对于简单的文件操作,无非就是open/close/read/write,在Linux对于文件的操作有一个关键的数据结构:file_operation,它的定义在源码目录下的include/linux/fs.h中,内容如下:
[cpp] view plain copy1. struct file_operations { 2. struct module *owner; 3. loff_t (*llseek) (struct file *, loff_t, int); 4. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 7. ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 8. int (*readdir) (struct file *, void *, filldir_t); 9. unsigned int (*poll) (struct file *, struct poll_table_struct *); 10. int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 11. long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 12. long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 13. int (*mmap) (struct file *, struct vm_area_struct *); 14. int (*open) (struct inode *, struct file *); 15. int (*flush) (struct file *, fl_owner_t id); 16. int (*release) (struct inode *, struct file *); 17. int (*fsync) (struct file *, int datasync); 18. int (*aio_fsync) (struct kiocb *, int datasync); 19. int (*fasync) (int, struct file *, int); 20. int (*lock) (struct file *, int, struct file_lock *); 21. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 22. unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 23. int (*check_flags)(int); 24. int (*flock) (struct file *, int, struct file_lock *); 25. ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 26. ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 27. int (*setlease)(struct file *, long, struct file_lock **); 28. };
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);
但是毕竟file_operation是针对文件定义的一个结构体,所以在写驱动时,其中有一些元素是用不到的,所以在2.6版本引入了一个针对驱动的结构体框架:platform,它是通过结构体platform_device来描述设备,用platform_driver描述设备驱动,它们都在源代码目录下的include/linux/platform_device.h中定义,内容如下:
[cpp] view plain copy 1. struct platform_device { 2. const char * name; 3. int id; 4. struct device dev; 5. u32 num_resources; 6. struct resource * resource; 7. const struct platform_device_id *id_entry; 8. /* arch specific additions */ 9. struct pdev_archdata archdata; 10. }; 11. struct platform_driver { 12. int (*probe)(struct platform_device *); 13. int (*remove)(struct platform_device *); 14. void (*shutdown)(struct platform_device *); 15. int (*suspend)(struct platform_device *, pm_message_t state); 16. int (*resume)(struct platform_device *); 17. struct device_driver driver; 18. const struct platform_device_id *id_table; 19. };
[cpp] view plain copy1. struct device_driver { 2. const char *name; 3. struct bus_type *bus; 4. struct module *owner; 5. const char *mod_name; /* used for built-in modules */ 6. bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 7. #if defined(CONFIG_OF) 8. const struct of_device_id *of_match_table; 9. #endif 10. int (*probe) (struct device *dev); 11. int (*remove) (struct device *dev); 12. void (*shutdown) (struct device *dev); 13. int (*suspend) (struct device *dev, pm_message_t state); 14. int (*resume) (struct device *dev); 15. const struct attribute_group **groups; 16. const struct dev_pm_ops *pm; 17. struct driver_private *p; 18. };
在Linux系统中,设备可以大致分为3类:字符设备、块设备和网络设备,而每种设备中又分为不同的子系统,由于具有自身的一些特殊性质,所以有不能归到某个已经存在的子类中,所以可以说是便于管理,也可以说是为了达到同一种定义模式,所以linux系统把这些子系统归为一个新类:misc ,以结构体miscdevice描述,在源代码目录下的include/linux/miscdevice.h中定义,内容如下:
[cpp] view plain copy1. struct miscdevice { 2. int minor; 3. const char *name; 4. const struct file_operations *fops; 5. struct list_head list; 6. struct device *parent; 7. struct device *this_device; 8. const char *nodename; 9. mode_t mode; 10. };
对于这些设备,它们都拥有一个共同主设备号10,所以它们是以次设备号来区分的,对于它里面的元素,大应该很眼熟吧,而且还有一个我们更熟悉的list_head的元素,这里也可以应证我之前说的list_head就是一个桥梁的说法了。
08
嵌入式书籍推荐
2. Linux方面的书:
<ARM体系架构与编程>
<嵌入式Linux应用开发完全手册>
<Linux设备驱动>,老外写的那本
<linux设备驱动开发详解>
<linux内核完全注释>
<Linux内核情景分析>
-END-
推荐阅读
【01】Linux设备驱动之Kobject、Kset【02】工作中非常常用!浅谈Linux PCI设备驱动(建议收藏阅读)【03】985硕士出来嵌入式驱动开发工资能到多少?知道真相的我有了信心【04】手把手教你把驱动代码加入到Linux Kernel【05】如果把嵌入式Linux platform设备驱动过程比作相亲,那么总线即是红娘,设备是男方,驱动是女方.....