当前位置:文档之家› 谈谈为 Linux 内核写驱动的编码规范

谈谈为 Linux 内核写驱动的编码规范

谈谈为 Linux 内核写驱动的编码规范
谈谈为 Linux 内核写驱动的编码规范

谈谈为 Linux 内核写驱动的编码规范

最近在向Linux内核提交一些驱动程序,在提交的过程中,发现自己的代码离Linux内核的coding style要求还是差很多。当初自己对内核文档里的CodingStyle一文只是粗略的浏览,真正写代码的时候在很多细节上会照顾不周。不过,在不遵守规则的程序员队伍里,我并不是孤独的。如果去看

drivers/staging下的代码,就会发现很多驱动程序都没有严格遵守内核的coding style,而且在很多驱动程序的TODO文件里,都会把"checkpatch.pl fixes"作为自己的目标之一(checkpatch.pl是用来检查代码是否符合coding style的脚本)。

不可否认,coding style是仁者见仁、智者见智的事情。比如Microsoft所推

崇的匈牙利命名法,在Linus看来就是及其脑残(brain damaged)的做法。也许您并不赞成Linus制定的coding style,但在提交内核驱动这件事上,最好还

是以大局为重。对于这么一个庞大的集市式的开发来说,随意书写代码必将带来严重的可维护性的灾难。

(题图来自:mota.ru)

一些辅助工具

当代码量达到一定程度时,手动去检查和修改coding style是非常繁琐的工作,幸好,我们还有一些工具可以使用。

scripts/checkpatch.pl

这是一个检查代码是否符合内核编码规范的的脚本。顾名思义,checkpatch是用来检查patch的,默认的调用也确实如此。如果用来检查原文件,需要加上“-f”的选项。

我们来看一段无聊的代码(文件名为print_msg.c):

1.void print_msg(int a)

2.{

3. switch (a) {

4. case 1:

5. printf("a == 1\n");

6. break;

7.

8. case 2:

9. printf("a == 2\n");

10. break;

11. }

12.}

这段代码的coding style是否有问题呢?用checkpatch.pl来检查一下:

1.scripts/checkpatch.pl -f print_msg.c

检查的结果是:

1.ERROR: switch and case should be at the same indent

2.#3: FILE: switch.c:3:

3.+ switch (a) {

4.+ case 1:

5.[...]

6.+ case 2:

7.

8.total: 1 errors, 0 warnings, 12 lines checked

9.

10.switch.c has style problems, please review. If any of these

errors

11.are false positives report them to the maintainer, see

12.CHECKPATCH in MAINTAINERS.

在Linux内核的coding style里,switch和case要求有相同的缩进。本例的代码很少,错误也只有这一个,手动修改很方便。如果类似的缩紧错误很多怎么办?

scripts/Lindent

scripts目录下的工具Lindent可以用来自动修改缩进问题。提醒一下,使用Lindent要求系统安装indent这个工具。

对于上面这个例子,执行Lindent命令:

1.scripts/Lindent print_msg.c

得到的新代码是:

1.void print_msg(int a)

2.{

3. switch (a) {

4. case 1:

5. printf("a == 1\n");

6. break;

7.

8. case 2:

9. printf("a == 2\n");

10. break;

11. }

12.}

sed

sed是一个流编辑器,其强大的功能可以帮助我们处理很多重复性的工作。比如,Linux内核的coding style要求,行尾不能有空格(包括Tab),去除这些空格就可以借助sed。

我自己的习惯很差,经常在代码的行尾留下一些空格。比如一行代码过长需要换行时,总是下意识的在换行的地方敲一个空格。另外,我常用的编辑器之一的Kate,为了对齐的需要,经常在空行的前面留上几个缩进的Tab(如下图)。

手动去除这些行尾的空格是一件头大的事情,但对于sed来说不过是举手之劳。命令格式如下:

1.sed 's/[ \t]*$//g' your_code.c

一些需要注意的代码风格

缩进

1、除了注释、文档和Kconfig之外,使用Tab缩进,而不是空格,并且Tab的宽度为8个字符;

2、switch ... case ...语句中,switch和case具有相同的缩进(参考上文);

花括号

3、花括号的使用参考K&R风格。

如果是函数,左花括号另起一行:

1.int function(int x)

2.{

3. body of function

4.}

否则,花括号紧接在语句的最后:

1.if (x is true) {

2. we do y

3.}

如果只有一行语句,则不需要用花括号:

1.if (condition)

2. action();

但是,对于条件语句来说,如果一个分支是一行语句,另一个分支是多行,则需要保持一致,使用花括号:

1.if (condition) {

2. do_this();

3. do_that();

4.} else {

5. otherwise();

6.}

空格

4、在关键字“if, switch, case, for, do, while”之后需要加上空格,如:

1.if (something)

5、在关键字“sizeof, typeof, alignof, or __attribute__”之后不要加空格,如:

1.sizeof(struct file)

6、在括号里的表达式两边不要加空格,比如,下面是一个反面的例子:

1.sizeof( struct file )

7、大多说的二元和三元运算符两边需要空格,如“= + - < > * / % | & ^ <= >= == != ? :”;

8、一元运算符后面不要空格,如“& * + - ~ ! sizeof typeof alignof

__attribute__ defined”;

9、在前缀自增自减运算符之后和后缀自增自减运算符之前不需要空格(“++”和“--”);

10、结构成员运算符(“.”和“->”)的两边不需要空格;

11、行尾不需要空格;

注释

12、使用C89的“/* ... */”风格而不是C99的“// ...”风格;

13、对于多行注释,可以参考下例:

1./*

2.* This is the preferred style for multi-line

3.* comments in the Linux kernel source code.

4.* Please use it consistently.

5.*

6.* Description: A column of asterisks on the left side,

7.* with beginning and ending almost-blank lines.

8.*/

Kconfig

14、“config”定义下面的语句用Tab缩进,help下面的语句再额外缩进两个空格,如:

1.config AUDIT

2. bool "Auditing support"

3. depends on NET

4. help

5. Enable auditing infrastructure that can be used with

another

6. kernel subsystem, such as SELinux (which requires this

for

7. logging of avc messages output). Does not do system-call

8. auditing without CONFIG_AUDITSYSCALL.

15、多行的宏定义需要用“do .. while”封装,如:

1.#define macrofun(a, b, c) \

2.do { \

3. if (a == 5) \

4. do_this(b, c); \

5.} while (0)

函数返回值

16、函数返回值的定义最好也要遵循一定的章法。

如果函数的名称是一种动作或者命令式的语句,应该以错误代码的形式返回(通常是0表示成功,-Exxx这种形式的负数表示错误),如:

1.do_something()

如果函数的名称是判断语句,则返回值应该类似与布尔值(通常1表示成功,0表示错误),如:

1.something_is_present()

【参考资料】

(1)Documentation/CodingStyle

(2)https://www.doczj.com/doc/0e17062289.html,/linux/talks/ols_2002_kernel_codingstyle_t alk/html/

来源:https://www.doczj.com/doc/0e17062289.html,/wwang/archive/2011/02/24/1960283.html 【编辑推荐】

1.编码风格不是编码规范

2.编码规范是技术上的遮羞布

3.为什么只有 Linux 内核获得成功

4.Linux 内核 4.2 RC 获史上最大更新量

5.谈谈 Linux 内核驱动的编码风格

6.谈谈为 Linux 内核写驱动的编码规范

Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻塞型IO和休眠]

Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻 塞型I/O和休眠] Linux设备驱动程序学习(5) -高级字符驱动程序操作[(2)阻塞型I/O和休眠]这一部分主要讨论:如果驱动程序无法立即满足请求,该如何响应?(65865346) 一、休眠 进程被置为休眠,意味着它被标识为处于一个特殊的状态并且从调度器的运行队列中移走。这个进程将不被在任何CPU 上调度,即将不会运行。直到发生某 些事情改变了那个状态。安全地进入休眠的两条规则: (1)永远不要在原子上下文中进入休眠,即当驱动在持有一个自旋锁、seqlock或者RCU 锁时不能睡眠;关闭中断也不能睡眠。持有一个信号量时休眠是 合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在持有信号量时的休眠必须短暂, 而且决不能阻塞那个将最终唤醒你的进程。 (2)当进程被唤醒,它并不知道休眠了多长时间以及休眠时发生什么;也不知道是否另有进程也在休眠等待同一事件,且那个进程可能在它之前醒来并获取了 所等待的资源。所以不能对唤醒后的系统状态做任何的假设,并必须重新检查等待条件来确保正确的响应。 除非确信其他进程会在其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在Linux 中,一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在中。 wait_queue_head_t 类型的数据结构非常简单: 它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤

linux驱动开发的经典书籍

linux驱动开发的经典书籍 结构、操作系统、体系结构、编译原理、计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高 从安装使用=>linux常用命令=>linux系统编程=>内核开发阅读内核源码 其中学习linux常用命令时就要学会自己编译内核,优化系统,调整参数 安装和常用命令书太多了,找本稍微详细点的就ok,其间需要学会正则表达式 系统编程推荐《高级unix环境编程》,黑话叫APUE 还有《unix网络编程》 这时候大概还需要看资料理解elf文件格式,连接器和加载器,cmu的一本教材中文名为《深入理解计算机系统》比较好 内核开发阅读内核源码阶段,从写驱动入手逐渐深入linux内核开发 参考书如下《linux device drivers》,黑话叫ldd 《linux kernel development》,黑话叫lkd 《understading the linux kernel》,黑话叫utlk 《linux源码情景分析》 这四本书为搞内核的必读书籍 最后,第三阶段和第四阶段最重动手,空言无益,光看书也不罩,不动手那些东西理解不了 学习linux/unix编程方法的建议 建议学习路径: 首先先学学编辑器,vim, emacs什么的都行。 然后学make file文件,只要知道一点就行,这样就可以准备编程序了。 然后看看《C程序设计语言》K&R,这样呢,基本上就可以进行一般的编程了,顺便找本数据结构的书来看。 如果想学习UNIX/LINUX的编程,《APUE》绝对经典的教材,加深一下功底,学习《UNP》的第二卷。这样基本上系统方面的就可以掌握了。 然后再看Douglus E. Comer的《用TCP/IP进行网际互连》第一卷,学习一下网络的知识,再看《UNP》的第一卷,不仅学习网络编程,而且对系统编程的一些常用的技巧就很熟悉了,如果继续网络编程,建议看《TCP/IP进行网际互连》的第三卷,里面有很多关于应用

linux驱动程序的编写

linux驱动程序的编写 一、实验目的 1.掌握linux驱动程序的编写方法 2.掌握驱动程序动态模块的调试方法 3.掌握驱动程序填加到内核的方法 二、实验内容 1. 学习linux驱动程序的编写流程 2. 学习驱动程序动态模块的调试方法 3. 学习驱动程序填加到内核的流程 三、实验设备 PentiumII以上的PC机,LINUX操作系统,EL-ARM860实验箱 四、linux的驱动程序的编写 嵌入式应用对成本和实时性比较敏感,而对linux的应用主要体现在对硬件的驱动程序的编写和上层应用程序的开发上。 嵌入式linux驱动程序的基本结构和标准Linux的结构基本一致,也支持模块化模式,所以,大部分驱动程序编成模块化形式,而且,要求可以在不同的体系结构上安装。linux是可以支持模块化模式的,但由于嵌入式应用是针对具体的应用,所以,一般不采用该模式,而是把驱动程序直接编译进内核之中。但是这种模式是调试驱动模块的极佳方法。 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。同时,设备驱动程序是内核的一部分,它完成以下的功能:对设备初始化和释放;把数据从内核传送到硬件和从硬件读取数据;读取应用程序传送给设备文件的数据和回送应用程序请求的数据;检测和处理设备出现的错误。在linux操作系统下有字符设备和块设备,网络设备三类主要的设备文件类型。 字符设备和块设备的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般就紧接着发生了;块设备利用一块系统内存作为缓冲区,当用户进程对设备请求满足用户要求时,就返回请求的数据。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。 1 字符设备驱动结构 Linux字符设备驱动的关键数据结构是cdev和file_operations结构体。

Linux驱动程序工作原理简介

Linux驱动程序工作原理简介 一、linux驱动程序的数据结构 (1) 二、设备节点如何产生? (2) 三、应用程序是如何访问设备驱动程序的? (2) 四、为什么要有设备文件系统? (3) 五、设备文件系统如何实现? (4) 六、如何使用设备文件系统? (4) 七、具体设备驱动程序分析 (5) 1、驱动程序初始化时,要注册设备节点,创建子设备文件 (5) 2、驱动程序卸载时要注销设备节点,删除设备文件 (7) 参考书目 (8) 一、linux驱动程序的数据结构 设备驱动程序实质上是提供一组供应用程序操作设备的接口函数。 各种设备由于功能不同,驱动程序提供的函数接口也不相同,但linux为了能够统一管理,规定了linux下设备驱动程序必须使用统一的接口函数file_operations 。 所以,一种设备的驱动程序主要内容就是提供这样的一组file_operations 接口函数。 那么,linux是如何管理种类繁多的设备驱动程序呢? linux下设备大体分为块设备和字符设备两类。 内核中用2个全局数组存放这2类驱动程序。 #define MAX_CHRDEV 255 #define MAX_BLKDEV 255 struct device_struct { const char * name; struct file_operations * fops; }; static struct device_struct chrdevs[MAX_CHRDEV]; static struct { const char *name; struct block_device_operations *bdops; } blkdevs[MAX_BLKDEV]; //此处说明一下,struct block_device_operations是块设备驱动程序内部的接口函数,上层文件系统还是通过struct file_operations访问的。

Linux设备驱动程序举例

Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include //同kernel.h,最基本的内核模块头文件 #include //同module.h,最基本的内核模块头文件 #include //这里包含了进行正确性检查的宏 #include //文件系统所必需的头文件 #include //这里包含了内核空间与用户空间进行数据交换时的函数宏 #include //I/O访问 int my_major=0; //主设备号 static int Device_Open=0; static char Message[]="This is from device driver"; char *Message_Ptr; int my_open(struct inode *inode, struct file *file) {//每当应用程序用open打开设备时,此函数被调用 printk ("\ndevice_open(%p,%p)\n", inode, file); if (Device_Open) return -EBUSY;//同时只能由一个应用程序打开 Device_Open++; MOD_INC_USE_COUNT;//设备打开期间禁止卸载 return 0; } static void my_release(struct inode *inode, struct file *file)

am335x_linux-3.14.43内核移植笔记

本文主要描述在EVB335X-II以Device Tree的方式移植新TI官网AM335X系列最新的linux-3.14.43版本内核以及移植Debian文件系统的过程及遇到的一些问题。整个Device Tree牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式(即.dts文件),又增加 了编译这一文本的工具,同时Bootloader也需要支持将编译后的Device Tree传递给Linux 内核。以下是修改步骤: 一、修改uboot,支持Device Tree EVB335X-II在linux-3.2版本内核移植的时候已经有uboot,因此只需在该uboot上增加Device Tree支持即可,以下是修改步骤: 1、修改include/configs/com335x.h文件,增加支持DT的宏定义: /* Flattened Device Tree */ #define CONFIG_OF_LIBFDT 2、修改uboot启动参数,增加dtb文件的加载和启动(由于目前只是移植EMMC版本的EVB335X-II,因此只需修改EMMC的启动参数即可,大概在405行),修改如下: #elif defined(CONFIG_EMMC_BOOT) #define CONFIG_BOOTCOMMAND \ "run mmcboot;" #define CONFIG_EXTRA_ENV_SETTINGS \ "lcdtype=AUO_AT070TN94\0" \ "console=ttyO0,115200n8\0" \ "mmcroot=/dev/mmcblk0p2 rw\0" \ "mmcrootfstype=ext4 rootwait\0" \ "mmcargs=setenv bootargs console=${console} noinitrd root=${mmcroot} rootfstype=${mmcrootfstype} lcdtype=${lcdtype} consoleblank=0\0" \ "mmcdev=" MMCDEV "\0" \ "loadaddr=0x81000000\0" \ "dtbfile=evb335x-ii-emmc.dtb\0" \ "bootenv=uEnv.txt\0" \ "bootpart=" BOOTPART "\0" \ "loadbootenv=load mmc ${mmcdev} ${loadaddr} ${bootenv}\0" \ "importbootenv=echo Importing environment from mmc ...; " \ "env import -t $loadaddr ${filesize}\0" \ "loadaddr-dtb=0x82000000\0" \ "loadimage=load mmc ${bootpart} ${loadaddr} uImage\0" \ "loaddtb=load mmc ${bootpart} ${loadaddr-dtb} ${dtbfile}\0" \ "mmcboot=mmc dev ${mmcdev}; " \ "if mmc rescan; then " \ "echo SD/MMC found on device ${mmcdev};" \ "if run loadbootenv; then " \ "echo Loaded environment from ${bootenv};" \ "run importbootenv;" \ "fi;" \ "run mmcargs;" \

LINUX字符设备驱动编写基本流程

---简介 Linux下的MISC简单字符设备驱动虽然使用简单,但却不灵活。 只能建立主设备号为10的设备文件。字符设备比较容易理解,同时也能够满足大多数简 单的硬件设备,字符设备通过文件系统中的名字来读取。这些名字就是文件系统中的特 殊文件或者称为设备文件、文件系统的简单结点,一般位于/dev/目录下使用ls进行查 看会显示以C开头证明这是字符设备文件crw--w---- 1 root tty 4, 0 4月 14 11:05 tty0。 第一个数字是主设备号,第二个数字是次设备号。 ---分配和释放设备编号 1)在建立字符设备驱动时首先要获取设备号,为此目的的必要的函数是 register_chrdev_region,在linux/fs.h中声明:int register_chrdev_region(dev_t first, unsigned int count, char *name);first是你想 要分配的起始设备编号,first的次编号通常是0,count是你请求的连续设备编号的 总数。count如果太大会溢出到下一个主设备号中。name是设备的名字,他会出现在 /proc/devices 和sysfs中。操作成功返回0,如果失败会返回一个负的错误码。 2)如果明确知道设备号可用那么上一个方法可行,否则我们可以使用内核动态分配的设 备号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name);dev是个只输出的参数,firstminor请求的第一个要用的次编号, count和name的作用如上1)对于新驱动,最好的方法是进行动态分配 3)释放设备号,void unregister_chrdev_region(dev_t first unsigned int count); ---文件操作file_operations结构体,内部连接了多个设备具体操作函数。该变量内部 的函数指针指向驱动程序中的具体操作,没有对应动作的指针设置为NULL。 1)fops的第一个成员是struct module *owner 通常都是设置成THIS_MODULE。 linux/module.h中定义的宏。用来在他的操作还在被使用时阻止模块被卸载。 2)loff_t (*llseek) (struct file *, loff_t, int);该方法用以改变文件中的当前读/ 写位置 返回新位置。 3)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);该函数用 以从设备文件 中读取数据,读取成功返回读取的字节数。

linux笔记

20150526 echo adfkjeroiu > /var/www/html/index.html service httpd restart ifconfig XXX.XXX.XXX.XXX elinks XXX.XXX.XXX.XXX web地址栏:XXX.XXX.XXX.XXX 20150527 方法一:Setup 设置IP 方法二:vim /etc/sysconfig/network-XXX/ifcfg-eth0 onboot=no改onboot=yes service network restart 虚拟机中安装2个linux,有时2个linux无法连接网络,即使是DHCP自动获取,也不可以;解决办法:打开其中一个linux虚拟机,单机“右下角-小电脑图标” —“设置”—“桥接模式(B);直接连接屋里网络” ,确定即可; 20150528 more /etc/issue 查看当前linux是centos还是redhat; man 命令查看当前命令的使用方法及参数 table 当一个命令不记得全部字母,可以双击table补齐; ctrl +c 终止当前程序 ctrl +l 清屏 20150529 ls -l查看命令;(-l显示更多属性) ls –a 查看隐藏文件; cp -r /etc/aaa /home/bbb复制/etc下的aaa 到/home下,并且改名bbb; (-r是整个文件夹的意思,如果,没有-r是复制单个文件) mv /etc/aaa /home/bbb 移动/etc下的aaa 到/home下,并且改名bbb;

rm –r 删除一个文件;(如果要是一个文件夹,就有询问yes或no) rm –rf 删除一个文件夹;(如果要是一个文件夹,就无询问) touch 创建文件; pwd 查看当前路径; cd.. 返回相对路径; cd / 返回绝对路径; cd- 返回刚才的路径; su – root或其它用户切换用户; mkdir 创建新目录; cat 查看文件内容; more或less 逐屏查看文件内容; useradd 新添加的用户,在没有更改密码前,无法登陆; passwd 更改密码;但是,密码必须符合复杂性; groupadd 添加一个组; 20150602 w 查看谁登陆过本计算机以及对方的IP; last 查看用户的登录日志; lastlog 查看每个用户最后登录的情况;(一般用于电脑被黑了之后); more /var/log/secure who /var/log/wtmp 干了些什么? root账户下输入su - username 切换到username下输入 history 能看到这个用户历史命令,默认最近的1000条 Linux查看History记录加时间戳小技巧 1.[root@servyou_web ~]# export HISTTIMEFORMAT="%F %T `whoami` " 2.[root@servyou_web ~]# history | tail 3. 1014 2011-06-22 19:17:29 root 15 2011-06-22 19:13:02 root ./test.sh 4. 1015 2011-06-22 19:17:29 root 16 2011-06-22 19:13:02 root vim test.sh 5. 1016 2011-06-22 19:17:29 root 17 2011-06-22 19:13:02 root ./test.sh 6. 1017 2011-06-22 19:17:29 root 18 2011-06-22 19:13:02 root vim test.sh 7. 1018 2011-06-22 19:17:29 root 19 2011-06-22 19:13:02 root ./test.sh 8. 1019 2011-06-22 19:17:29 root 20 2011-06-22 19:13:02 root vim test.sh 9. 1020 2011-06-22 19:17:29 root 21 2011-06-22 19:13:02 root ./test.sh

Linux驱动工程师成长之路

本人此刻还不是什么驱动工程师,连入门都谈不上,但我坚信在未来的3-5年我肯定能成为我想像中的人,因为我马上就要进入这一行工作了。写下这个日志来记录我是怎么最后成为我想像中的人才的,呵呵。 《Linux驱动工程师》这个东西是我在大二的时候看到有一篇讲如何学习嵌入式的,点击这里下载PDF,里面讲到嵌入式分为四层:硬件,驱动,系统,应用程序;还说linux驱动最难然后工资也最高就冲着他这句话我就决定我大学毕业的时候要去做这个linux驱动工程师,随后我就先后买了51单片机,ARM7,ARM9还有一大堆的视频教程准备来进行学习。我还跟我旁边那个哈工大哥们说:“我们学校像我这样的人很少,你们学校呢?”他说:“太少了,不过我们学校都是做这种板子卖的人比较多!”。行,你们牛!即使是买了这些东西,从大二到现在都快毕业了但感觉还是没有入门。回想一下我都学过什么啊:1:自己在ARM9上写bootloader(主要锻炼了三方面的知识:C语言应该写了有近万行的代码,ARM9的外设的基本操作方法如UART,LCD,TOUCH,SD,USB,ETHERNET...,makefile);2:移植和学习linux驱动。下面我说一下我学习Linux驱动的一个思路这也是我在面试的时候自我介绍中最重要的部分;1:硬件知识学习Linux驱动首先得了解这个驱动对应的硬件的一些基本原理和操作方法比如LCD你得了解它的场同步,行同步,像素时钟,一个像素的表示模式,还有就是这个LCD是怎么把图像显示在屏幕上的。如果是USB,SD卡就得了解相关协议。可以通过spec(协议)、datasheet来了解,这就是传说中的Linux驱动开发三件宝之二,还有一个就是linux相关源码。2:了解linux驱动框架linux下的每一类驱动差不多都是一个比较完善的子系统,比如FLASH的驱动它就属于MTD子系统从上到下分为四层:设备节点层,设备层,原始设备层,最下面的与具体硬件相关的硬件驱动层,通常要我们自己来实现就是最下面这个与具体硬件相关那部分代码。3:了解这个驱动的数据流。这个过程与第二个过程紧密相关,如果了解了驱动的框架差不多这个过程也算了解了。比如flash.在/dev/目录下有对应flash的字符设备文件和块设备文件,用户对这些文件进行读、写、ioctl操作,其间通过层层的函数调用最终将调用到最下面的硬件驱动层对硬件进行操作。了解这个过程我相信在调试驱动的时候是很有帮助。3:分析与硬件相关通常需要我们实现的那部分源代码。4:三板子上将驱动调试出来。每次调试都会出问题,但我买的板子提供的资料比较全调试过程中遇到的问题都比较浅显,即使是浅显的问题也要把它记录下来。(这个是我上次在华为面试的时候,那个人问我你调试驱动遇到过什么问题吗?你是如何解决的。当时我学习还没有到调试驱动这一步,所以那次面试也惨败收场)。 好像说了这么多,还没有进入正题《工作的选择》。在年前去了龙芯,实习2.8K,转正3.5k,环境还是不错,经理很好,头儿也很帅都是中科院的硕士。不过去了两周我就没去了身边的人都不太理解,我也一度有过后悔的时候,从龙芯出来应该是1月6号,也就是从那个时候开始我就没有再找工作,转而学习linux驱动。一直到上周日。上周日的晚上我就开始投简历一开始要找linux驱动,在智联里面输入linux驱动出来500来个职位,点开一看没有一个自己符合要求的,差不多都要3-5年经验本科,有时候好不容易有个实习的关键字在里面,一看要求硕士,严重打击了我的信心,哎不管了随便投,最后又投了一下嵌入式关键字的职位。最后就瞎申请,看看职位要求差不多就申请。周一来了,这周一共来了6个面试,创下了我求职以来的历史新高。周一下午面了一家感觉还不错不过到现在也没有给我一个通知,估计当时我要了4500把他给要跑了,这家是做测量的不是Linux驱动,差不多是把ARM当单片机用。周二上午一家也是要招linux驱动面了估计不到二分钟,他

linux读书笔记

12.29 Linux系统 Linux是真正的多用户、多任务操作系统。它继承了UNIX系统的主要特征,具有强大的信息处理功能,特别在Internet和Intranet的应用中占有明显优势。是一个完整的UNIX类操作系统。它允许多个用户同时在一个系统上运行多道程序。真正的32位操作系统。 用户接口 用户接口定义了用户和计算机交互作用的方式。Linux操作系统提供4种不同的用户接口。命令行接口 命令行是为具有操作系统使用经验,熟悉所用命令和系统结构的人员设计的。功能强大,使用方便的命令行是UNIX/Linux系统的一个显著特征。支持命令行的系统程序是命令解释程序。它的主要功能是接收用户输入的命令,然后予以解释并执行。 “$ ”是系统提示符。 在UNIX/Linux系统中,通常将命令解释程序称为shell。各种Linux环境下都安装了多种shell。这些shell由不同的人编写并得到一部分用户的青睐,各有其优势,最常用的几种是Bourne shell(sh),C shell(csh),Bourne Again shell(bash)和Korn shell(ksh)。红旗Linux 的默认shell是bash。 Bash 菜单 图形用户接口 程序接口 程序接口也称为系统调用接口。用户在自己的C程序中使用系统调用,从而获得系统提供的更基层的服务。 系统调用是操作系统内核与用户程序,应用程序之间的接口。在UNIX/Linux系统中,系统调用以C函数的形式出现。例如:fd=fopen(“file1.c”,2);其中,open是系统调用。 所有内核之外的程序都必须经由系统调用才能获得操作系统的服务。系统调用只能在C程序中使用,不能作为命令在终端上执行。由于系统调用能直接进入内核执行,所以其执行效率高。 Linux的版本 Linux有两种版本:核心(Kernel)版本和发行(Distribution)版本。 核心版本 核心版本主要是Linux的内核。Linux内核的官方版本由Linus Torvalds本人维护着。核心版本的序号由三部分数字构成,其形式为:major.minor.patchlevel 其中,major是主版本号,minor是次版本号,二者共同构成了当前核心版本好;patchlevel 表示对当前版本的修订次数。例如:2.6.34表示对2.6核心版本的第34次修订。

从零开始搭建Linux驱动开发环境

参考: 韦东山视频第10课第一节内核启动流程分析之编译体验 第11课第三节构建根文件系统之busybox 第11课第四节构建根文件系统之构建根文件系统韦东山书籍《嵌入式linux应用开发完全手册》 其他《linux设备驱动程序》第三版 平台: JZ2440、mini2440或TQ2440 交叉网线和miniUSB PC机(windows系统和Vmware下的ubuntu12.04) 一、交叉编译环境的选型 具体的安装交叉编译工具,网上很多资料都有,我的那篇《arm-linux- gcc交叉环境相关知识》也有介绍,这里我只是想提示大家:构建跟文件系统中所用到的lib库一定要是本系统Ubuntu中的交叉编译环境arm-linux- gcc中的。即如果电脑ubuntu中的交叉编译环境为arm-linux-

二、主机、开发板和虚拟机要三者互通 w IP v2.0》一文中有详细的操作步骤,不再赘述。 linux 2.6.22.6_jz2440.patch组合而来,具体操作: 1. 解压缩内核和其补丁包 tar xjvf linux-2.6.22.6.tar.bz2 # 解压内核 tar xjvf linux-2.6.22.6_jz2440.tar.bz2 # 解压补丁

cd linux_2.6.22.6 patch –p1 < ../linux-2.6.22.6_jz2440.patch 3. 配置 在内核目录下执行make 2410_defconfig生成配置菜单,至于怎么配置,《嵌入式linux应用开发完全手册》有详细介绍。 4. 生成uImage make uImage 四、移植busybox 在我们的根文件系统中的/bin和/sbin目录下有各种命令的应用程序,而这些程序在嵌入式系统中都是通过busybox来构建的,每一个命令实际上都是一个指向bu sybox的链接,busybox通过传入的参数来决定进行何种命令操作。 1)配置busybox 解压busybox-1.7.0,然后进入该目录,使用make menuconfig进行配置。这里我们这配置两项 一是在编译选项选择动态库编译,当然你也可以选择静态,不过那样构建的根文件系统会比动态编译的的大。 ->Busybox Settings ->Build Options

linux 驱动程序开发

1 什么是驱动 a)裸板驱动 b)有系统驱动linux 将驱动封装了一套框架(每个驱动) c)大量和硬件无关的代码已写好只需要编程实现和硬件相关的代码 d)难点:框架的理解代码的理解 e)需要三方面的知识: i.硬件相关的知识 1.电路原理图 2.芯片的数据手册 3.总线协议rs232 i2c等 ii.内核的知识 1.内核驱动属于内核的一部分,它运行在内核态需要对内核知识有了解 2.内存管理 3.解决竞争状态(如上锁) 4.。。。 iii.驱动框架的知识 1.内核中已经实现了大量硬件驱动完成了驱动的框架编程只需要根据硬 件进行添加 2 搭建linux驱动开发工具 a)安装交叉编译环境 i.arm-linux-gcc uboot PATH b)移植uboot c)移植内核 d)制作根文件系统然后通过nfs方式让开发板可以加载 3 内核驱动开发的基本知识 a)如何学驱动编程? i.最好的老师就是内核源码(没有man 功能) 1.要是用某个函数就去查看某个函数的定义注释 2.查看内核中其他模块儿时如何使用该函数的 3.专业书籍: a)内核开发:linux内核的设计与实现机械工程出版社 b)驱动开发:圣经级别的-LDD3:LINUX DEVICE c)操作性别叫强的:精通linux设备驱动程序开发

关于linux内核: 1)linux内核中所使用的函数都是自身实现的它肯定不会调用c库中的函数 2)linux中代码绝大多数代码时gun c语言完成的不是标准c语言可以理解为标c的扩展版和少部分汇编 需要注意的问题: 1)内核态不能做浮点数运算 2)用户空间的每个进程都有独立的0-3G的虚拟空间 多个进程共享同一个内核 内核使用的地址空间为3G-4G 3)每个线程有独立的栈空间 4 写一个最简单的内核模块儿(因为驱动时内核的一个模块套路都一样) a)几个宏 i.__FUNCTION__:展开为所在函数的名称 ii.__LINE__:展开为printk所在的行号 iii.__DATE__:展开为编译程序的日期 b)通用头文件 i.#include ii.#include c)没有main函数 然后写一个makefile 其中:obj -m +=helloworld.o -m表示生成模块儿 make -C 内核路径编译对象路径modules(固定表示模块儿) 例子:make -C /home/changjian/dirver/kernel M=$(PWD) modules 报错:如taints kernel(污染内核)因为写的驱动没有声明license 因为linux为开源所以写的驱动也必须声明为开源可以在程序里加入:MODULE_LICENSE(“GPL”);声明为开源 模块儿驱动开发 1、模块儿参数 a)内核中安装模块时也可以传递参数 i.insmod xx.ko var=123 b)模块参数的使用方法 i.首先在模块中定义全局变量 ii.然后使用module_param 或者module_param_array来修饰该变量 这样一个普通的全局变量就变成可以安装模块时传递参数的模块参数 module_param(name,type,perm) name:变量名称 type: name的类型(不包括数组) perm:权限类型rwxr-x 等类型内核做了相关的宏定义形如efine S_IRWXG 表示r w x g(同组) module_param_array(name,type,nump,perm)将某个数组声明为模块 参数

linux驱动学习笔记LED

LED驱动学习: 是一个char字符类型的驱动 //配置模式为输出端口 static unsigned int led_cfg_table [] = { S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP, }; s3c2410_gpio_cfgpin(S3C2410_GPB5, S3C2410_GPB5_OUTP); s3c2410_gpio_cfgpin(37, 0x01 << 10); 这个在\arch\arm\mach-s3c2410\include\mach\regs-gpio.h中定义 #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPB5_INP (0x00 << 10) #define S3C2410_GPB5_OUTP (0x01 << 10) #define S3C2410_GPB5_nXBACK (0x02 << 10) S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5) #define S3C2410_GPIONO(bank,offset) ((bank) + (offset)) #define S3C2410_GPIO_BANKA (32*0) #define S3C2410_GPIO_BANKB(32*1) static int __init dev_init(void) { int ret; int i; for (i = 0; i < 4; i++) { s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]); s3c2410_gpio_setpin(led_table[i], 0); } 在驱动的初始化函数中经常看到,__init 前缀,这个在下面文件中定义 file:/include/linux/init.h ? /* These macros are used to mark some functions or ?* initialized data (doesn't apply to uninitialized data) ?* as `initialization' functions. The kernel can take this ?* as hint that the function is used only during the initialization ?* phase and free up used memory resources after ?* ?* Usage: ?* For functions: ?* ?* You should add __init immediately before the function name, like: ?*

Linux设备驱动程序说明介绍

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. 读/写时,它首先察看缓冲区的内容,如果缓冲区的数据 如何编写Linux操作系统下的设备驱动程序 二、实例剖析 我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel上可能会出现问题,我还没测试过. [code]#define __NO_VERSION__

Linux设备驱动程序学习(18)-USB 驱动程序(三)

Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb

struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/

相关主题
文本预览
相关文档 最新文档